feat: 实现单卡资产分配与回收功能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m45s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m45s
- 新增单卡分配/回收 API(支持 ICCID 列表、号段范围、筛选条件三种选卡方式) - 新增资产分配记录查询 API(支持多条件筛选和分页) - 新增 AssetAllocationRecord 模型、Store、Service、Handler 完整实现 - 扩展 IotCardStore 新增批量更新、号段查询、筛选查询等方法 - 修复 GORM Callback 处理 slice 类型(BatchCreate)的问题 - 新增完整的单元测试和集成测试 - 同步 OpenSpec 规范并归档 change
This commit is contained in:
@@ -59,6 +59,15 @@ const (
|
||||
CodeWithdrawalNotFound = 1052 // 提现申请不存在
|
||||
CodeWalletNotFound = 1053 // 钱包不存在
|
||||
|
||||
// IoT 卡相关错误 (1070-1089)
|
||||
CodeIotCardNotFound = 1070 // IoT 卡不存在
|
||||
CodeIotCardBoundToDevice = 1071 // IoT 卡已绑定设备
|
||||
CodeIotCardStatusNotAllowed = 1072 // 卡状态不允许此操作
|
||||
CodeAssetAllocationRecordNotFound = 1073 // 分配记录不存在
|
||||
CodeNotDirectSubordinate = 1074 // 非直属下级店铺
|
||||
CodeCannotAllocateToSelf = 1075 // 不能分配给自己
|
||||
CodeCannotRecallFromSelf = 1076 // 不能从自己回收
|
||||
|
||||
// 服务端错误 (2000-2999) -> 5xx HTTP 状态码
|
||||
CodeInternalError = 2001 // 内部服务器错误
|
||||
CodeDatabaseError = 2002 // 数据库错误
|
||||
@@ -114,6 +123,13 @@ var allErrorCodes = []int{
|
||||
CodeInsufficientBalance,
|
||||
CodeWithdrawalNotFound,
|
||||
CodeWalletNotFound,
|
||||
CodeIotCardNotFound,
|
||||
CodeIotCardBoundToDevice,
|
||||
CodeIotCardStatusNotAllowed,
|
||||
CodeAssetAllocationRecordNotFound,
|
||||
CodeNotDirectSubordinate,
|
||||
CodeCannotAllocateToSelf,
|
||||
CodeCannotRecallFromSelf,
|
||||
CodeInternalError,
|
||||
CodeDatabaseError,
|
||||
CodeRedisError,
|
||||
@@ -132,55 +148,62 @@ func init() {
|
||||
|
||||
// errorMessages 错误消息映射表(中文)
|
||||
var errorMessages = map[int]string{
|
||||
CodeSuccess: "成功",
|
||||
CodeInvalidParam: "参数验证失败",
|
||||
CodeMissingToken: "缺失认证令牌",
|
||||
CodeInvalidToken: "无效或过期的令牌",
|
||||
CodeUnauthorized: "未授权访问",
|
||||
CodeForbidden: "禁止访问",
|
||||
CodeNotFound: "资源未找到",
|
||||
CodeConflict: "资源冲突",
|
||||
CodeTooManyRequests: "请求过多,请稍后重试",
|
||||
CodeRequestTooLarge: "请求体过大",
|
||||
CodeAccountNotFound: "账号不存在",
|
||||
CodeAccountDisabled: "账号已禁用",
|
||||
CodeAccountDeleted: "账号已删除",
|
||||
CodeUsernameExists: "用户名已存在",
|
||||
CodePhoneExists: "手机号已存在",
|
||||
CodeInvalidPassword: "密码格式不正确",
|
||||
CodePasswordTooWeak: "密码强度不足",
|
||||
CodeParentIDRequired: "非 root 用户必须提供上级账号",
|
||||
CodeInvalidParentID: "上级账号不存在或无效",
|
||||
CodeCannotModifyParent: "禁止修改上级账号",
|
||||
CodeCannotModifyUserType: "禁止修改用户类型",
|
||||
CodeRoleNotFound: "角色不存在",
|
||||
CodeRoleNameExists: "角色名称已存在",
|
||||
CodePermissionNotFound: "权限不存在",
|
||||
CodePermCodeExists: "权限编码已存在",
|
||||
CodeInvalidPermCode: "权限编码格式不正确(应为 module:action 格式)",
|
||||
CodeRoleAlreadyAssigned: "角色已分配",
|
||||
CodePermAlreadyAssigned: "权限已分配",
|
||||
CodeShopNotFound: "店铺不存在",
|
||||
CodeShopCodeExists: "店铺编号已存在",
|
||||
CodeShopLevelExceeded: "店铺层级不能超过 7 级",
|
||||
CodeEnterpriseNotFound: "企业不存在",
|
||||
CodeEnterpriseCodeExists: "企业编号已存在",
|
||||
CodeCustomerNotFound: "个人客户不存在",
|
||||
CodeCustomerPhoneExists: "个人客户手机号已存在",
|
||||
CodeInvalidStatus: "状态不允许此操作",
|
||||
CodeInsufficientBalance: "余额不足",
|
||||
CodeWithdrawalNotFound: "提现申请不存在",
|
||||
CodeWalletNotFound: "钱包不存在",
|
||||
CodeInvalidCredentials: "用户名或密码错误",
|
||||
CodeAccountLocked: "账号已锁定",
|
||||
CodePasswordExpired: "密码已过期",
|
||||
CodeInvalidOldPassword: "旧密码错误",
|
||||
CodeInternalError: "内部服务器错误",
|
||||
CodeDatabaseError: "数据库错误",
|
||||
CodeRedisError: "缓存服务错误",
|
||||
CodeServiceUnavailable: "服务暂时不可用",
|
||||
CodeTimeout: "请求超时",
|
||||
CodeTaskQueueError: "任务队列错误",
|
||||
CodeSuccess: "成功",
|
||||
CodeInvalidParam: "参数验证失败",
|
||||
CodeMissingToken: "缺失认证令牌",
|
||||
CodeInvalidToken: "无效或过期的令牌",
|
||||
CodeUnauthorized: "未授权访问",
|
||||
CodeForbidden: "禁止访问",
|
||||
CodeNotFound: "资源未找到",
|
||||
CodeConflict: "资源冲突",
|
||||
CodeTooManyRequests: "请求过多,请稍后重试",
|
||||
CodeRequestTooLarge: "请求体过大",
|
||||
CodeAccountNotFound: "账号不存在",
|
||||
CodeAccountDisabled: "账号已禁用",
|
||||
CodeAccountDeleted: "账号已删除",
|
||||
CodeUsernameExists: "用户名已存在",
|
||||
CodePhoneExists: "手机号已存在",
|
||||
CodeInvalidPassword: "密码格式不正确",
|
||||
CodePasswordTooWeak: "密码强度不足",
|
||||
CodeParentIDRequired: "非 root 用户必须提供上级账号",
|
||||
CodeInvalidParentID: "上级账号不存在或无效",
|
||||
CodeCannotModifyParent: "禁止修改上级账号",
|
||||
CodeCannotModifyUserType: "禁止修改用户类型",
|
||||
CodeRoleNotFound: "角色不存在",
|
||||
CodeRoleNameExists: "角色名称已存在",
|
||||
CodePermissionNotFound: "权限不存在",
|
||||
CodePermCodeExists: "权限编码已存在",
|
||||
CodeInvalidPermCode: "权限编码格式不正确(应为 module:action 格式)",
|
||||
CodeRoleAlreadyAssigned: "角色已分配",
|
||||
CodePermAlreadyAssigned: "权限已分配",
|
||||
CodeShopNotFound: "店铺不存在",
|
||||
CodeShopCodeExists: "店铺编号已存在",
|
||||
CodeShopLevelExceeded: "店铺层级不能超过 7 级",
|
||||
CodeEnterpriseNotFound: "企业不存在",
|
||||
CodeEnterpriseCodeExists: "企业编号已存在",
|
||||
CodeCustomerNotFound: "个人客户不存在",
|
||||
CodeCustomerPhoneExists: "个人客户手机号已存在",
|
||||
CodeInvalidStatus: "状态不允许此操作",
|
||||
CodeInsufficientBalance: "余额不足",
|
||||
CodeWithdrawalNotFound: "提现申请不存在",
|
||||
CodeWalletNotFound: "钱包不存在",
|
||||
CodeIotCardNotFound: "IoT 卡不存在",
|
||||
CodeIotCardBoundToDevice: "IoT 卡已绑定设备,不能单独操作",
|
||||
CodeIotCardStatusNotAllowed: "卡状态不允许此操作",
|
||||
CodeAssetAllocationRecordNotFound: "分配记录不存在",
|
||||
CodeNotDirectSubordinate: "只能操作直属下级店铺",
|
||||
CodeCannotAllocateToSelf: "不能分配给自己",
|
||||
CodeCannotRecallFromSelf: "不能从自己回收",
|
||||
CodeInvalidCredentials: "用户名或密码错误",
|
||||
CodeAccountLocked: "账号已锁定",
|
||||
CodePasswordExpired: "密码已过期",
|
||||
CodeInvalidOldPassword: "旧密码错误",
|
||||
CodeInternalError: "内部服务器错误",
|
||||
CodeDatabaseError: "数据库错误",
|
||||
CodeRedisError: "缓存服务错误",
|
||||
CodeServiceUnavailable: "服务暂时不可用",
|
||||
CodeTimeout: "请求超时",
|
||||
CodeTaskQueueError: "任务队列错误",
|
||||
}
|
||||
|
||||
// GetMessage 获取错误码对应的消息
|
||||
|
||||
@@ -13,6 +13,16 @@ var (
|
||||
ErrTooManyRequests = errors.New("too many requests")
|
||||
)
|
||||
|
||||
// 预定义业务错误(常用错误可直接引用)
|
||||
var (
|
||||
ErrAssetAllocationRecordNotFound = &AppError{Code: CodeAssetAllocationRecordNotFound, Message: "分配记录不存在"}
|
||||
ErrNotDirectSubordinate = &AppError{Code: CodeNotDirectSubordinate, Message: "只能操作直属下级店铺"}
|
||||
ErrIotCardBoundToDevice = &AppError{Code: CodeIotCardBoundToDevice, Message: "IoT 卡已绑定设备,不能单独操作"}
|
||||
ErrIotCardStatusNotAllowed = &AppError{Code: CodeIotCardStatusNotAllowed, Message: "卡状态不允许此操作"}
|
||||
ErrCannotAllocateToSelf = &AppError{Code: CodeCannotAllocateToSelf, Message: "不能分配给自己"}
|
||||
ErrCannotRecallFromSelf = &AppError{Code: CodeCannotRecallFromSelf, Message: "不能从自己回收"}
|
||||
)
|
||||
|
||||
// AppError 表示带错误码的应用错误
|
||||
type AppError struct {
|
||||
Code int // 应用错误码
|
||||
|
||||
@@ -2,6 +2,7 @@ package gorm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/logger"
|
||||
@@ -236,13 +237,47 @@ func RegisterDataPermissionCallback(db *gorm.DB, shopStore ShopStoreInterface) e
|
||||
func RegisterSetCreatorUpdaterCallback(db *gorm.DB) error {
|
||||
err := db.Callback().Create().Before("gorm:create").Register("set_creator_updater", func(tx *gorm.DB) {
|
||||
ctx := tx.Statement.Context
|
||||
if userID, ok := tx.Statement.Context.Value(constants.ContextKeyUserID).(uint); ok {
|
||||
if f := tx.Statement.Schema; f != nil {
|
||||
if c, ok := f.FieldsByName["Creator"]; ok {
|
||||
_ = c.Set(ctx, tx.Statement.ReflectValue, userID)
|
||||
userID, ok := tx.Statement.Context.Value(constants.ContextKeyUserID).(uint)
|
||||
if !ok || tx.Statement.Schema == nil {
|
||||
return
|
||||
}
|
||||
|
||||
creatorField, hasCreator := tx.Statement.Schema.FieldsByName["Creator"]
|
||||
updaterField, hasUpdater := tx.Statement.Schema.FieldsByName["Updater"]
|
||||
if !hasCreator && !hasUpdater {
|
||||
return
|
||||
}
|
||||
|
||||
rv := tx.Statement.ReflectValue
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
elem := rv.Index(i)
|
||||
if elem.Kind() == reflect.Ptr {
|
||||
elem = elem.Elem()
|
||||
}
|
||||
if u, ok := f.FieldsByName["Updater"]; ok {
|
||||
_ = u.Set(ctx, tx.Statement.ReflectValue, userID)
|
||||
if hasCreator {
|
||||
_ = creatorField.Set(ctx, elem, userID)
|
||||
}
|
||||
if hasUpdater {
|
||||
_ = updaterField.Set(ctx, elem, userID)
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
if hasCreator {
|
||||
_ = creatorField.Set(ctx, rv, userID)
|
||||
}
|
||||
if hasUpdater {
|
||||
_ = updaterField.Set(ctx, rv, userID)
|
||||
}
|
||||
case reflect.Ptr:
|
||||
elem := rv.Elem()
|
||||
if elem.Kind() == reflect.Struct {
|
||||
if hasCreator {
|
||||
_ = creatorField.Set(ctx, elem, userID)
|
||||
}
|
||||
if hasUpdater {
|
||||
_ = updaterField.Set(ctx, elem, userID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user