feat(业务用户组): AUG26-003 业务用户组与店铺负责人分组导入
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 1h43m42s
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 1h43m42s
- 迁移 000221:新增 tb_business_user_group、tb_business_user_group_member、tb_shop_business_owner_import_task,成员一账号一行由部分唯一索引保证,店铺所属组按当前负责人实时推导,不回填历史分组。 - 用户组 CRUD、成员改组/清空归属、店铺批量交接(原子失败不部分写入)。 - 店铺负责人 CSV 导入任务:逐行独立事务、逐行明细、任务级与行级失败分离。 - 读侧推导与筛选:未分组、业务线、停用组可筛出并带停用标记。 - 补齐操作审计动作与资源、openapi 清单、发布门禁巡检表清单。 - 归档 add-shop-salesperson-groups 变更并同步 openspec/specs/business-user-group,补齐 AUG26-003 验证证据链。
This commit is contained in:
@@ -482,6 +482,20 @@ const (
|
||||
AuditActionIntegrationAttemptStarted = "integration.attempt_started"
|
||||
// AuditActionIntegrationInboundReceived 表示入站回调处理前已记录的审计事实。
|
||||
AuditActionIntegrationInboundReceived = "integration.inbound_received"
|
||||
// AuditActionBusinessUserGroupCreated 表示创建业务用户组。
|
||||
AuditActionBusinessUserGroupCreated = "business_user_group.create"
|
||||
// AuditActionBusinessUserGroupUpdated 表示更新业务用户组名称、业务线、排序或备注。
|
||||
AuditActionBusinessUserGroupUpdated = "business_user_group.update"
|
||||
// AuditActionBusinessUserGroupEnabled 表示启用业务用户组。
|
||||
AuditActionBusinessUserGroupEnabled = "business_user_group.enable"
|
||||
// AuditActionBusinessUserGroupDisabled 表示停用业务用户组。
|
||||
AuditActionBusinessUserGroupDisabled = "business_user_group.disable"
|
||||
// AuditActionBusinessUserGroupDeleted 表示删除无成员业务用户组。
|
||||
AuditActionBusinessUserGroupDeleted = "business_user_group.delete"
|
||||
// AuditActionBusinessUserGroupMembersUpdated 表示批量设置或清空平台用户的业务用户组归属。
|
||||
AuditActionBusinessUserGroupMembersUpdated = "business_user_group.update_members"
|
||||
// AuditActionShopBusinessOwnerBatchUpdated 表示勾选店铺批量设置或清空平台业务员归属。
|
||||
AuditActionShopBusinessOwnerBatchUpdated = "shop.batch_update_business_owner"
|
||||
// AuditActionIotCardImportTaskCreated 表示创建 IoT 卡导入任务。
|
||||
AuditActionIotCardImportTaskCreated = "iot_card_import_task.create"
|
||||
// AuditActionIotCardImportTaskCompleted 表示 IoT 卡导入任务完成。
|
||||
@@ -494,6 +508,12 @@ const (
|
||||
AuditActionAssetPackageBatchOrderTaskCreated = "asset_package_batch_order_task.create"
|
||||
// AuditActionAssetPackageBatchOrderTaskCompleted 表示资产套餐批量订购任务完成。
|
||||
AuditActionAssetPackageBatchOrderTaskCompleted = "asset_package_batch_order_task.complete"
|
||||
// AuditActionShopBusinessOwnerImportTaskCreated 表示创建店铺负责人 CSV 导入任务。
|
||||
AuditActionShopBusinessOwnerImportTaskCreated = "shop_business_owner_import_task.create"
|
||||
// AuditActionShopBusinessOwnerImportTaskCompleted 表示店铺负责人 CSV 导入任务完成。
|
||||
AuditActionShopBusinessOwnerImportTaskCompleted = "shop_business_owner_import_task.complete"
|
||||
// AuditActionShopBusinessOwnerImported 表示导入单行实际变更了店铺负责人归属。
|
||||
AuditActionShopBusinessOwnerImported = "shop.import_business_owner"
|
||||
// AuditActionOrderPackageInvalidateTaskCreated 表示创建订单套餐批量失效任务。
|
||||
AuditActionOrderPackageInvalidateTaskCreated = "order_package_invalidate_task.create"
|
||||
// AuditActionOrderPackageInvalidateTaskCompleted 表示订单套餐批量失效任务完成。
|
||||
@@ -637,6 +657,12 @@ const (
|
||||
AuditResourceDeviceImportTask = "device_import_task"
|
||||
// AuditResourceAssetPackageBatchOrderTask 表示资产套餐批量订购任务资源。
|
||||
AuditResourceAssetPackageBatchOrderTask = "asset_package_batch_order_task"
|
||||
// AuditResourceShopBusinessOwnerImportTask 表示店铺负责人 CSV 导入任务资源。
|
||||
AuditResourceShopBusinessOwnerImportTask = "shop_business_owner_import_task"
|
||||
// AuditResourceBusinessUserGroup 表示业务用户组资源。
|
||||
AuditResourceBusinessUserGroup = "business_user_group"
|
||||
// AuditResourceShopBusinessOwnerBatch 表示店铺负责人批量交接批次根资源。
|
||||
AuditResourceShopBusinessOwnerBatch = "shop_business_owner_batch"
|
||||
// AuditResourceOrderPackageInvalidateTask 表示订单套餐批量失效任务资源。
|
||||
AuditResourceOrderPackageInvalidateTask = "order_package_invalidate_task"
|
||||
// AuditResourceExportTask 表示业务导出任务资源。
|
||||
@@ -779,6 +805,10 @@ const (
|
||||
AuditResourceRoleApprovalOutbox = "approval_outbox"
|
||||
// AuditResourceRoleBatchTask 表示批量根事件的任务资源。
|
||||
AuditResourceRoleBatchTask = "batch_task"
|
||||
// AuditResourceRoleBusinessUserGroupTarget 表示业务用户组维护的主要目标。
|
||||
AuditResourceRoleBusinessUserGroupTarget = "business_user_group_target"
|
||||
// AuditResourceRoleShopBusinessOwnerBatch 表示店铺负责人批量交接的批次根资源。
|
||||
AuditResourceRoleShopBusinessOwnerBatch = "shop_business_owner_batch"
|
||||
// AuditResourceRoleRetentionMonth 表示留存清理目标自然月。
|
||||
AuditResourceRoleRetentionMonth = "retention_month"
|
||||
// AuditResourceRoleNotificationTarget 表示本次写操作的通知资源。
|
||||
|
||||
51
pkg/constants/business_user_group.go
Normal file
51
pkg/constants/business_user_group.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package constants
|
||||
|
||||
// 业务用户组是平台用户的业务分类,只用于店铺负责人推导、筛选与批量维护,
|
||||
// 不进入登录、角色权限或数据范围判定。
|
||||
|
||||
// 业务用户组所属业务线取值,来源于 AUG26-003 的三枚举,允许为空表示未分类。
|
||||
const (
|
||||
// BusinessLineStandard 表示标品业务线。
|
||||
BusinessLineStandard = "standard"
|
||||
// BusinessLineSmart 表示智能产品业务线。
|
||||
BusinessLineSmart = "smart"
|
||||
// BusinessLineOther 表示其他业务线。
|
||||
BusinessLineOther = "other"
|
||||
)
|
||||
|
||||
// GetBusinessLineName 返回业务线单值的中文名称,空值表示未设置业务线。
|
||||
func GetBusinessLineName(businessLine string) string {
|
||||
switch businessLine {
|
||||
case BusinessLineStandard:
|
||||
return "标品"
|
||||
case BusinessLineSmart:
|
||||
return "智能产品"
|
||||
case BusinessLineOther:
|
||||
return "其他"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// IsValidBusinessLine 判断业务线单值是否合法;允许为空表示未设置业务线。
|
||||
func IsValidBusinessLine(businessLine string) bool {
|
||||
switch businessLine {
|
||||
case "", BusinessLineStandard, BusinessLineSmart, BusinessLineOther:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 业务用户组维护入口的角色约束与统一失败文案。
|
||||
const (
|
||||
// PlatformManagementForbiddenMessage 表示非超级管理员/平台账号访问平台维护入口的统一失败文案。
|
||||
PlatformManagementForbiddenMessage = "无权限操作该资源或资源不存在"
|
||||
)
|
||||
|
||||
// IsAvailablePlatformBusinessOwner 判定账号是否为当前有效的平台业务员。
|
||||
// 语义为「平台用户 + 启用 + 未软删」,供本 Change 的成员维护、批量交接与导入共用;
|
||||
// 既有内联判定按 As-Is 保留,不回改。需要数据库条件的调用方自行在查询中表达同一条件。
|
||||
func IsAvailablePlatformBusinessOwner(userType, status int, deleted bool) bool {
|
||||
return userType == UserTypePlatform && status == StatusEnabled && !deleted
|
||||
}
|
||||
@@ -75,6 +75,8 @@ const (
|
||||
// 订单套餐失效任务类型
|
||||
TaskTypeOrderPackageInvalidate = "order:package:invalidate" // 批量失效订单套餐
|
||||
TaskTypeAssetPackageBatchOrder = "asset:package:batch_order" // 资产套餐批量订购
|
||||
// 店铺负责人 CSV 导入任务类型,独立成队列以隔离逐行执行的长时任务。
|
||||
TaskTypeShopBusinessOwnerImport = "shop_business_owner:import"
|
||||
|
||||
// 订单超时任务类型
|
||||
TaskTypeOrderExpire = "order:expire" // 订单超时自动取消
|
||||
@@ -209,34 +211,35 @@ const (
|
||||
QueueDefault = "default" // 旧版默认队列(兼容历史任务)
|
||||
QueueLow = "low" // 旧版低优先级队列(兼容历史任务)
|
||||
|
||||
QueueEmailSend = TaskTypeEmailSend // 发送邮件任务队列
|
||||
QueueCommission = TaskTypeCommission // 分佣计算任务队列
|
||||
QueueIotCardImport = TaskTypeIotCardImport // IoT 卡导入任务队列
|
||||
QueueDeviceImport = TaskTypeDeviceImport // 设备导入任务队列
|
||||
QueueExportDispatch = TaskTypeExportDispatch // 导出分发任务队列
|
||||
QueueExportShard = TaskTypeExportShard // 导出分片任务队列
|
||||
QueueExportFinalize = TaskTypeExportFinalize // 导出收尾任务队列
|
||||
QueueCommissionStatsUpdate = TaskTypeCommissionStatsUpdate // 佣金统计更新任务队列
|
||||
QueueCommissionStatsSync = TaskTypeCommissionStatsSync // 佣金统计同步任务队列
|
||||
QueueCommissionStatsArchive = TaskTypeCommissionStatsArchive // 佣金统计归档任务队列
|
||||
QueuePollingRealname = TaskTypePollingRealname // 实名轮询任务队列
|
||||
QueuePollingCarddata = TaskTypePollingCarddata // 流量轮询任务队列
|
||||
QueuePollingPackage = TaskTypePollingPackage // 套餐轮询任务队列
|
||||
QueuePollingProtect = TaskTypePollingProtect // 保护期轮询任务队列
|
||||
QueuePollingCardStatus = TaskTypePollingCardStatus // 卡状态轮询任务队列
|
||||
QueuePackageFirstActivation = TaskTypePackageFirstActivation // 首次实名激活任务队列
|
||||
QueuePackageQueueActivation = TaskTypePackageQueueActivation // 主套餐排队激活任务队列
|
||||
QueuePackageDataReset = TaskTypePackageDataReset // 套餐流量重置任务队列
|
||||
QueueOrderPackageInvalidate = TaskTypeOrderPackageInvalidate // 批量失效订单套餐任务队列
|
||||
QueueAssetPackageBatchOrder = TaskTypeAssetPackageBatchOrder // 资产套餐批量订购任务队列
|
||||
QueueOrderExpire = TaskTypeOrderExpire // 订单超时取消任务队列
|
||||
QueueAutoPurchase = TaskTypeAutoPurchaseAfterRecharge // 充值后自动购包任务队列
|
||||
QueueAlertCheck = TaskTypeAlertCheck // 告警检查任务队列
|
||||
QueueDataCleanup = TaskTypeDataCleanup // 数据清理任务队列
|
||||
QueueDailyTrafficFlush = TaskTypeDailyTrafficFlush // 每日流量落盘任务队列
|
||||
QueueOutboxDeliver = TaskTypeOutboxDeliver // 公共 Outbox 投递队列
|
||||
QueueCardObservationSeries = TaskTypeCardObservationSeries // 卡观测事件序列队列
|
||||
QueueWeComApproval = "wecom:approval" // 企业微信审批同步与恢复队列
|
||||
QueueEmailSend = TaskTypeEmailSend // 发送邮件任务队列
|
||||
QueueCommission = TaskTypeCommission // 分佣计算任务队列
|
||||
QueueIotCardImport = TaskTypeIotCardImport // IoT 卡导入任务队列
|
||||
QueueDeviceImport = TaskTypeDeviceImport // 设备导入任务队列
|
||||
QueueExportDispatch = TaskTypeExportDispatch // 导出分发任务队列
|
||||
QueueExportShard = TaskTypeExportShard // 导出分片任务队列
|
||||
QueueExportFinalize = TaskTypeExportFinalize // 导出收尾任务队列
|
||||
QueueCommissionStatsUpdate = TaskTypeCommissionStatsUpdate // 佣金统计更新任务队列
|
||||
QueueCommissionStatsSync = TaskTypeCommissionStatsSync // 佣金统计同步任务队列
|
||||
QueueCommissionStatsArchive = TaskTypeCommissionStatsArchive // 佣金统计归档任务队列
|
||||
QueuePollingRealname = TaskTypePollingRealname // 实名轮询任务队列
|
||||
QueuePollingCarddata = TaskTypePollingCarddata // 流量轮询任务队列
|
||||
QueuePollingPackage = TaskTypePollingPackage // 套餐轮询任务队列
|
||||
QueuePollingProtect = TaskTypePollingProtect // 保护期轮询任务队列
|
||||
QueuePollingCardStatus = TaskTypePollingCardStatus // 卡状态轮询任务队列
|
||||
QueuePackageFirstActivation = TaskTypePackageFirstActivation // 首次实名激活任务队列
|
||||
QueuePackageQueueActivation = TaskTypePackageQueueActivation // 主套餐排队激活任务队列
|
||||
QueuePackageDataReset = TaskTypePackageDataReset // 套餐流量重置任务队列
|
||||
QueueOrderPackageInvalidate = TaskTypeOrderPackageInvalidate // 批量失效订单套餐任务队列
|
||||
QueueAssetPackageBatchOrder = TaskTypeAssetPackageBatchOrder // 资产套餐批量订购任务队列
|
||||
QueueShopBusinessOwnerImport = TaskTypeShopBusinessOwnerImport // 店铺负责人 CSV 导入任务队列
|
||||
QueueOrderExpire = TaskTypeOrderExpire // 订单超时取消任务队列
|
||||
QueueAutoPurchase = TaskTypeAutoPurchaseAfterRecharge // 充值后自动购包任务队列
|
||||
QueueAlertCheck = TaskTypeAlertCheck // 告警检查任务队列
|
||||
QueueDataCleanup = TaskTypeDataCleanup // 数据清理任务队列
|
||||
QueueDailyTrafficFlush = TaskTypeDailyTrafficFlush // 每日流量落盘任务队列
|
||||
QueueOutboxDeliver = TaskTypeOutboxDeliver // 公共 Outbox 投递队列
|
||||
QueueCardObservationSeries = TaskTypeCardObservationSeries // 卡观测事件序列队列
|
||||
QueueWeComApproval = "wecom:approval" // 企业微信审批同步与恢复队列
|
||||
|
||||
DefaultRetryMax = 5 // 默认任务最大重试次数
|
||||
DefaultTimeout = 10 * time.Minute // 默认任务超时时间
|
||||
@@ -286,6 +289,8 @@ func QueueForTaskType(taskType string) string {
|
||||
return QueueOrderPackageInvalidate
|
||||
case TaskTypeAssetPackageBatchOrder:
|
||||
return QueueAssetPackageBatchOrder
|
||||
case TaskTypeShopBusinessOwnerImport:
|
||||
return QueueShopBusinessOwnerImport
|
||||
case TaskTypeOrderExpire:
|
||||
return QueueOrderExpire
|
||||
case TaskTypeAutoPurchaseAfterRecharge:
|
||||
@@ -318,37 +323,38 @@ func QueueForTaskType(taskType string) string {
|
||||
// DefaultTaskQueueWeights 返回 Worker 默认监听的全部任务队列及其权重。
|
||||
func DefaultTaskQueueWeights() map[string]int {
|
||||
return map[string]int{
|
||||
QueueCritical: 6,
|
||||
QueueIotCardImport: 6,
|
||||
QueueDeviceImport: 6,
|
||||
QueueAutoPurchase: 5,
|
||||
QueuePackageFirstActivation: 5,
|
||||
QueuePackageQueueActivation: 5,
|
||||
QueueOrderPackageInvalidate: 5,
|
||||
QueueAssetPackageBatchOrder: 5,
|
||||
QueueOrderExpire: 4,
|
||||
QueueEmailSend: 4,
|
||||
QueueExportDispatch: 4,
|
||||
QueueExportFinalize: 4,
|
||||
QueueCommission: 3,
|
||||
QueueAlertCheck: 3,
|
||||
QueueExportShard: 2,
|
||||
QueueCommissionStatsUpdate: 2,
|
||||
QueueCommissionStatsSync: 2,
|
||||
QueueCommissionStatsArchive: 2,
|
||||
QueuePollingRealname: 1,
|
||||
QueuePollingCarddata: 1,
|
||||
QueuePollingPackage: 1,
|
||||
QueuePollingProtect: 1,
|
||||
QueuePollingCardStatus: 1,
|
||||
QueuePackageDataReset: 1,
|
||||
QueueDataCleanup: 1,
|
||||
QueueDailyTrafficFlush: 1,
|
||||
QueueOutboxDeliver: 4,
|
||||
QueueCardObservationSeries: 2,
|
||||
QueueWeComApproval: 2,
|
||||
QueueDefault: 1,
|
||||
QueueLow: 1,
|
||||
QueueCritical: 6,
|
||||
QueueIotCardImport: 6,
|
||||
QueueDeviceImport: 6,
|
||||
QueueAutoPurchase: 5,
|
||||
QueuePackageFirstActivation: 5,
|
||||
QueuePackageQueueActivation: 5,
|
||||
QueueOrderPackageInvalidate: 5,
|
||||
QueueAssetPackageBatchOrder: 5,
|
||||
QueueShopBusinessOwnerImport: 4,
|
||||
QueueOrderExpire: 4,
|
||||
QueueEmailSend: 4,
|
||||
QueueExportDispatch: 4,
|
||||
QueueExportFinalize: 4,
|
||||
QueueCommission: 3,
|
||||
QueueAlertCheck: 3,
|
||||
QueueExportShard: 2,
|
||||
QueueCommissionStatsUpdate: 2,
|
||||
QueueCommissionStatsSync: 2,
|
||||
QueueCommissionStatsArchive: 2,
|
||||
QueuePollingRealname: 1,
|
||||
QueuePollingCarddata: 1,
|
||||
QueuePollingPackage: 1,
|
||||
QueuePollingProtect: 1,
|
||||
QueuePollingCardStatus: 1,
|
||||
QueuePackageDataReset: 1,
|
||||
QueueDataCleanup: 1,
|
||||
QueueDailyTrafficFlush: 1,
|
||||
QueueOutboxDeliver: 4,
|
||||
QueueCardObservationSeries: 2,
|
||||
QueueWeComApproval: 2,
|
||||
QueueDefault: 1,
|
||||
QueueLow: 1,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
73
pkg/constants/shop_business_owner_import.go
Normal file
73
pkg/constants/shop_business_owner_import.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package constants
|
||||
|
||||
import "time"
|
||||
|
||||
// 店铺负责人 CSV 导入的场景常量。
|
||||
const (
|
||||
// StoragePurposeShopBusinessOwnerImport 表示店铺负责人 CSV 导入上传用途。
|
||||
StoragePurposeShopBusinessOwnerImport = "shop_import"
|
||||
// ShopBusinessOwnerImportStoragePrefix 表示店铺负责人导入文件的对象存储目录。
|
||||
ShopBusinessOwnerImportStoragePrefix = "shop-imports"
|
||||
// ShopBusinessOwnerImportTaskTimeout 表示单个店铺负责人导入任务的最长执行时间。
|
||||
ShopBusinessOwnerImportTaskTimeout = 2 * time.Hour
|
||||
// ShopBusinessOwnerImportProgressBatchSize 表示导入按批更新进度计数的批大小。
|
||||
ShopBusinessOwnerImportProgressBatchSize = 100
|
||||
// ShopBusinessOwnerImportItemStatusSuccess 表示导入单行已成功变更店铺负责人。
|
||||
ShopBusinessOwnerImportItemStatusSuccess = 3
|
||||
// ShopBusinessOwnerImportItemStatusFailed 表示导入单行校验或执行失败并保留原值。
|
||||
ShopBusinessOwnerImportItemStatusFailed = 4
|
||||
)
|
||||
|
||||
// 店铺负责人 CSV 导入的操作类型取值,行内互相排斥:换绑必须填业务员登录账号,清空不得填写。
|
||||
const (
|
||||
// ShopBusinessOwnerImportOperationRebind 表示把店铺负责人换绑到指定平台业务员。
|
||||
ShopBusinessOwnerImportOperationRebind = "换绑"
|
||||
// ShopBusinessOwnerImportOperationClear 表示清空店铺负责人。
|
||||
ShopBusinessOwnerImportOperationClear = "清空"
|
||||
)
|
||||
|
||||
// ShopBusinessOwnerImportColumns 表示导入文件首行必须完全一致的固定列序。
|
||||
var ShopBusinessOwnerImportColumns = []string{"店铺编码", "操作类型", "业务员登录账号", "备注"}
|
||||
|
||||
// 店铺负责人 CSV 导入的任务级失败原因,与行级失败原因分开记录。
|
||||
const (
|
||||
// ShopBusinessOwnerImportErrorFileFormat 表示文件格式或表头与固定列序不符。
|
||||
ShopBusinessOwnerImportErrorFileFormat = "文件格式或表头与固定列序不符"
|
||||
// ShopBusinessOwnerImportErrorEncoding 表示文件既非合法 UTF-8 也无法按 GBK 解码。
|
||||
ShopBusinessOwnerImportErrorEncoding = "文件编码无法按 UTF-8 或 GBK 解码"
|
||||
// ShopBusinessOwnerImportErrorNoDataRow 表示文件没有数据行。
|
||||
ShopBusinessOwnerImportErrorNoDataRow = "文件没有数据行"
|
||||
)
|
||||
|
||||
// 店铺负责人 CSV 导入的行级失败原因,取值集合固定,不包含数据范围无权。
|
||||
const (
|
||||
// ShopBusinessOwnerImportRowErrorShopMissing 表示店铺编码不存在或已删除。
|
||||
ShopBusinessOwnerImportRowErrorShopMissing = "店铺编码不存在或已删除"
|
||||
// ShopBusinessOwnerImportRowErrorOperation 表示操作类型不是换绑或清空。
|
||||
ShopBusinessOwnerImportRowErrorOperation = "操作类型非法"
|
||||
// ShopBusinessOwnerImportRowErrorOwnerRequired 表示换绑未填写业务员登录账号。
|
||||
ShopBusinessOwnerImportRowErrorOwnerRequired = "换绑必须填写业务员登录账号"
|
||||
// ShopBusinessOwnerImportRowErrorOwnerForbidden 表示清空却填写了业务员登录账号。
|
||||
ShopBusinessOwnerImportRowErrorOwnerForbidden = "清空不得填写业务员登录账号"
|
||||
// ShopBusinessOwnerImportRowErrorOwnerInvalid 表示业务员登录账号不存在或非启用平台用户。
|
||||
ShopBusinessOwnerImportRowErrorOwnerInvalid = "业务员登录账号不存在或非启用平台用户"
|
||||
// ShopBusinessOwnerImportRowErrorFormat 表示该行列数与固定列序不一致。
|
||||
ShopBusinessOwnerImportRowErrorFormat = "行格式错误"
|
||||
)
|
||||
|
||||
const (
|
||||
// ShopOwnerImportAccessDescription 表示店铺负责人导入入口的角色说明。
|
||||
ShopOwnerImportAccessDescription = "仅超级管理员和平台账号可访问,代理与企业账号返回 403。"
|
||||
)
|
||||
|
||||
// GetShopBusinessOwnerImportItemStatusName 返回导入行状态中文名称。
|
||||
func GetShopBusinessOwnerImportItemStatusName(status int) string {
|
||||
switch status {
|
||||
case ShopBusinessOwnerImportItemStatusSuccess:
|
||||
return "成功"
|
||||
case ShopBusinessOwnerImportItemStatusFailed:
|
||||
return "失败"
|
||||
default:
|
||||
return "未知"
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,8 @@ func BuildDocHandlers() *bootstrap.Handlers {
|
||||
Refund: admin.NewRefundHandler(nil),
|
||||
OrderPackageInvalidate: admin.NewOrderPackageInvalidateHandler(nil),
|
||||
AssetPackageBatchOrder: admin.NewAssetPackageBatchOrderHandler(nil, nil),
|
||||
BusinessUserGroup: admin.NewBusinessUserGroupHandler(nil, nil),
|
||||
ShopBusinessOwnerImport: admin.NewShopBusinessOwnerImportHandler(nil),
|
||||
ClientWechat: app.NewClientWechatHandler(nil, nil, nil),
|
||||
SuperAdmin: admin.NewSuperAdminHandler(nil),
|
||||
SystemConfig: admin.NewSystemConfigHandler(nil, nil),
|
||||
|
||||
@@ -74,6 +74,7 @@ func (h *Handler) RegisterHandlers() *asynq.ServeMux {
|
||||
h.registerDeviceImportHandler()
|
||||
h.registerOrderPackageInvalidateHandler()
|
||||
h.registerAssetPackageBatchOrderHandler()
|
||||
h.registerShopBusinessOwnerImportHandler()
|
||||
h.registerExportHandlers()
|
||||
h.registerCommissionStatsHandlers()
|
||||
h.registerCommissionCalculationHandler()
|
||||
@@ -141,6 +142,18 @@ func (h *Handler) registerAssetPackageBatchOrderHandler() {
|
||||
h.logger.Info("注册资产套餐批量订购任务处理器", zap.String("task_type", constants.TaskTypeAssetPackageBatchOrder))
|
||||
}
|
||||
|
||||
func (h *Handler) registerShopBusinessOwnerImportHandler() {
|
||||
handler := task.NewShopBusinessOwnerImportHandler(
|
||||
h.db,
|
||||
h.workerResult.Stores.ShopBusinessOwnerImportTask,
|
||||
h.storage,
|
||||
h.logger,
|
||||
audit.NewWriter(audit.NewRegistry(), nil),
|
||||
)
|
||||
h.mux.HandleFunc(constants.TaskTypeShopBusinessOwnerImport, handler.Handle)
|
||||
h.logger.Info("注册店铺负责人导入任务处理器", zap.String("task_type", constants.TaskTypeShopBusinessOwnerImport))
|
||||
}
|
||||
|
||||
func (h *Handler) registerDeviceImportHandler() {
|
||||
deviceImportHandler := task.NewDeviceImportHandler(
|
||||
h.db,
|
||||
|
||||
@@ -22,37 +22,38 @@ type OrderExpirer interface {
|
||||
|
||||
// WorkerStores Worker 侧所有 Store 的集合
|
||||
type WorkerStores struct {
|
||||
AssetAllocationRecord *postgres.AssetAllocationRecordStore
|
||||
IotCardImportTask *postgres.IotCardImportTaskStore
|
||||
IotCard *postgres.IotCardStore
|
||||
DeviceImportTask *postgres.DeviceImportTaskStore
|
||||
ExportTask *postgres.ExportTaskStore
|
||||
ExportShardTask *postgres.ExportShardTaskStore
|
||||
Device *postgres.DeviceStore
|
||||
DeviceSimBinding *postgres.DeviceSimBindingStore
|
||||
ShopSeriesCommissionStats *postgres.ShopSeriesCommissionStatsStore
|
||||
ShopPackageAllocation *postgres.ShopPackageAllocationStore
|
||||
CommissionRecord *postgres.CommissionRecordStore
|
||||
Shop *postgres.ShopStore
|
||||
ShopSeriesAllocation *postgres.ShopSeriesAllocationStore
|
||||
PackageSeries *postgres.PackageSeriesStore
|
||||
Order *postgres.OrderStore
|
||||
OrderItem *postgres.OrderItemStore
|
||||
Package *postgres.PackageStore
|
||||
PackageUsage *postgres.PackageUsageStore
|
||||
PackageUsageDailyRecord *postgres.PackageUsageDailyRecordStore
|
||||
PollingAlertRule *postgres.PollingAlertRuleStore
|
||||
PollingAlertHistory *postgres.PollingAlertHistoryStore
|
||||
DataCleanupConfig *postgres.DataCleanupConfigStore
|
||||
DataCleanupLog *postgres.DataCleanupLogStore
|
||||
AgentWallet *postgres.AgentWalletStore
|
||||
AgentWalletTransaction *postgres.AgentWalletTransactionStore
|
||||
AssetWallet *postgres.AssetWalletStore
|
||||
AssetIdentifier *postgres.AssetIdentifierStore
|
||||
PersonalCustomer *postgres.PersonalCustomerStore
|
||||
PersonalCustomerPhone *postgres.PersonalCustomerPhoneStore
|
||||
OrderPackageInvalidateTask *postgres.OrderPackageInvalidateTaskStore
|
||||
AssetPackageBatchOrderTask *postgres.AssetPackageBatchOrderTaskStore
|
||||
AssetAllocationRecord *postgres.AssetAllocationRecordStore
|
||||
IotCardImportTask *postgres.IotCardImportTaskStore
|
||||
IotCard *postgres.IotCardStore
|
||||
DeviceImportTask *postgres.DeviceImportTaskStore
|
||||
ExportTask *postgres.ExportTaskStore
|
||||
ExportShardTask *postgres.ExportShardTaskStore
|
||||
Device *postgres.DeviceStore
|
||||
DeviceSimBinding *postgres.DeviceSimBindingStore
|
||||
ShopSeriesCommissionStats *postgres.ShopSeriesCommissionStatsStore
|
||||
ShopPackageAllocation *postgres.ShopPackageAllocationStore
|
||||
CommissionRecord *postgres.CommissionRecordStore
|
||||
Shop *postgres.ShopStore
|
||||
ShopSeriesAllocation *postgres.ShopSeriesAllocationStore
|
||||
PackageSeries *postgres.PackageSeriesStore
|
||||
Order *postgres.OrderStore
|
||||
OrderItem *postgres.OrderItemStore
|
||||
Package *postgres.PackageStore
|
||||
PackageUsage *postgres.PackageUsageStore
|
||||
PackageUsageDailyRecord *postgres.PackageUsageDailyRecordStore
|
||||
PollingAlertRule *postgres.PollingAlertRuleStore
|
||||
PollingAlertHistory *postgres.PollingAlertHistoryStore
|
||||
DataCleanupConfig *postgres.DataCleanupConfigStore
|
||||
DataCleanupLog *postgres.DataCleanupLogStore
|
||||
AgentWallet *postgres.AgentWalletStore
|
||||
AgentWalletTransaction *postgres.AgentWalletTransactionStore
|
||||
AssetWallet *postgres.AssetWalletStore
|
||||
AssetIdentifier *postgres.AssetIdentifierStore
|
||||
PersonalCustomer *postgres.PersonalCustomerStore
|
||||
PersonalCustomerPhone *postgres.PersonalCustomerPhoneStore
|
||||
OrderPackageInvalidateTask *postgres.OrderPackageInvalidateTaskStore
|
||||
AssetPackageBatchOrderTask *postgres.AssetPackageBatchOrderTaskStore
|
||||
ShopBusinessOwnerImportTask *postgres.ShopBusinessOwnerImportTaskStore
|
||||
}
|
||||
|
||||
// WorkerServices Worker 侧所有 Service 的集合
|
||||
|
||||
@@ -36,7 +36,7 @@ func (s *Service) GenerateFileKey(purpose, fileName string) (string, error) {
|
||||
if ext == "" {
|
||||
ext = ".bin"
|
||||
}
|
||||
if (purpose == constants.StoragePurposeAssetPackageBatchOrder || purpose == constants.StoragePurposeDeviceBatchAllocation) && !strings.EqualFold(ext, ".csv") {
|
||||
if (purpose == constants.StoragePurposeAssetPackageBatchOrder || purpose == constants.StoragePurposeDeviceBatchAllocation || purpose == constants.StoragePurposeShopBusinessOwnerImport) && !strings.EqualFold(ext, ".csv") {
|
||||
return "", errors.New(errors.CodeInvalidParam, "批量业务文件必须为CSV格式")
|
||||
}
|
||||
|
||||
|
||||
@@ -19,4 +19,7 @@ var PurposeMappings = map[string]PurposeMapping{
|
||||
"attachment": {Prefix: "attachments", ContentType: ""},
|
||||
constants.StoragePurposeAssetPackageBatchOrder: {Prefix: constants.AssetPackageBatchOrderStoragePrefix, ContentType: "text/csv"},
|
||||
constants.StoragePurposeDeviceBatchAllocation: {Prefix: constants.DeviceBatchAllocationStoragePrefix, ContentType: "text/csv"},
|
||||
constants.StoragePurposeShopBusinessOwnerImport: {
|
||||
Prefix: constants.ShopBusinessOwnerImportStoragePrefix, ContentType: "text/csv",
|
||||
},
|
||||
}
|
||||
|
||||
40
pkg/utils/encoding.go
Normal file
40
pkg/utils/encoding.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
)
|
||||
|
||||
// utf8BOM 是 UTF-8 字节顺序标记,上传的 CSV 常带该前缀。
|
||||
var utf8BOM = []byte{0xEF, 0xBB, 0xBF}
|
||||
|
||||
// replacementChar 是解码器无法映射字节时产生的替换字符。
|
||||
const replacementChar = "\uFFFD"
|
||||
|
||||
// DecodeTextToUTF8 把上传文本解码为 UTF-8 字节。
|
||||
// 先剥离 UTF-8 BOM;剥离后已是合法 UTF-8 时原样返回,否则按 GBK 回退解码。
|
||||
// 仅在 UTF-8 校验失败时才尝试 GBK,避免对合法 UTF-8 内容做启发式改写;
|
||||
// GBK 解码失败或仍残留无法映射的字节时返回错误,由调用方按任务级失败处理。
|
||||
func DecodeTextToUTF8(data []byte) ([]byte, error) {
|
||||
trimmed := data
|
||||
if len(trimmed) >= len(utf8BOM) && string(trimmed[:len(utf8BOM)]) == string(utf8BOM) {
|
||||
trimmed = trimmed[len(utf8BOM):]
|
||||
}
|
||||
if utf8.Valid(trimmed) {
|
||||
return trimmed, nil
|
||||
}
|
||||
decoded, err := simplifiedchinese.GBK.NewDecoder().Bytes(trimmed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GBK 解码失败: %w", err)
|
||||
}
|
||||
if !utf8.Valid(decoded) {
|
||||
return nil, fmt.Errorf("GBK 解码结果不是合法 UTF-8")
|
||||
}
|
||||
if strings.Contains(string(decoded), replacementChar) {
|
||||
return nil, fmt.Errorf("GBK 解码存在无法映射的字节")
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
||||
Reference in New Issue
Block a user