统一导出任务
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m4s

This commit is contained in:
2026-04-27 09:47:03 +08:00
parent 531de3c760
commit 60debb7505
29 changed files with 2580 additions and 0 deletions

View File

@@ -46,6 +46,9 @@ const (
TaskTypeCommission = "commission:calculate" // 分佣计算
TaskTypeIotCardImport = "iot_card:import" // IoT 卡批量导入
TaskTypeDeviceImport = "device:import" // 设备批量导入
TaskTypeExportDispatch = "export:dispatch" // 导出任务分发
TaskTypeExportShard = "export:shard" // 导出任务分片执行
TaskTypeExportFinalize = "export:finalize" // 导出任务收尾汇总
TaskTypeCommissionStatsUpdate = "commission:stats:update" // 佣金统计更新
TaskTypeCommissionStatsSync = "commission:stats:sync" // 佣金统计同步
TaskTypeCommissionStatsArchive = "commission:stats:archive" // 佣金统计归档
@@ -175,6 +178,48 @@ const (
DefaultConcurrency = 10
)
// 导出任务相关常量
const (
ExportTaskSceneDevice = "device" // 导出场景:设备
ExportTaskSceneIotCard = "iot_card" // 导出场景IoT 卡
)
// 导出文件格式常量
const (
ExportTaskFormatXLSX = "xlsx" // 导出格式Excel
ExportTaskFormatCSV = "csv" // 导出格式CSV
)
// 导出主任务状态常量
const (
ExportTaskStatusPending = 1 // 待处理
ExportTaskStatusProcessing = 2 // 处理中
ExportTaskStatusCompleted = 3 // 已完成
ExportTaskStatusFailed = 4 // 已失败
ExportTaskStatusCancelled = 5 // 已取消
)
// 导出分片任务状态常量
const (
ExportShardStatusPending = 1 // 待处理
ExportShardStatusProcessing = 2 // 处理中
ExportShardStatusSuccess = 3 // 已成功
ExportShardStatusFailed = 4 // 已失败
ExportShardStatusCancelled = 5 // 已取消
)
// 导出任务执行配置常量
const (
ExportDefaultShardSize = 2000 // 默认分片大小
ExportDownloadURLExpire = 24 * time.Hour // 下载链接固定有效期
ExportDispatchTaskTimeout = 10 * time.Minute // dispatch 任务超时
ExportShardTaskTimeout = 15 * time.Minute // shard 任务超时
ExportFinalizeTaskTimeout = 10 * time.Minute // finalize 任务超时
ExportFinalizeRetryMax = 30 // finalize 最大重试次数
ExportShardRetryMax = 3 // shard 最大重试次数
ExportDispatchRetryMax = 5 // dispatch 最大重试次数
)
// 店铺配置常量
const (
MaxShopLevel = 7 // 店铺最大层级
@@ -260,6 +305,42 @@ func GetShelfStatusName(status int) string {
}
}
// GetExportTaskStatusName 获取导出主任务状态名称
func GetExportTaskStatusName(status int) string {
switch status {
case ExportTaskStatusPending:
return "待处理"
case ExportTaskStatusProcessing:
return "处理中"
case ExportTaskStatusCompleted:
return "已完成"
case ExportTaskStatusFailed:
return "已失败"
case ExportTaskStatusCancelled:
return "已取消"
default:
return "未知"
}
}
// GetExportShardStatusName 获取导出分片任务状态名称
func GetExportShardStatusName(status int) string {
switch status {
case ExportShardStatusPending:
return "待处理"
case ExportShardStatusProcessing:
return "处理中"
case ExportShardStatusSuccess:
return "已成功"
case ExportShardStatusFailed:
return "已失败"
case ExportShardStatusCancelled:
return "已取消"
default:
return "未知"
}
}
// GetExchangeStatusName 获取换货单状态名称
// 用于 exchange_status 字段
func GetExchangeStatusName(status int) string {

View File

@@ -46,6 +46,27 @@ func RedisTaskStatusKey(taskID string) string {
return fmt.Sprintf("task:status:%s", taskID)
}
// RedisExportDispatchLockKey 生成导出 dispatch 执行锁的 Redis 键
// 用途:防止同一导出任务并发执行 dispatch
// 过期时间5 分钟
func RedisExportDispatchLockKey(taskID uint) string {
return fmt.Sprintf("export:dispatch:lock:%d", taskID)
}
// RedisExportShardLockKey 生成导出分片执行锁的 Redis 键
// 用途:防止同一分片被多个 Worker 并发处理
// 过期时间10 分钟
func RedisExportShardLockKey(taskID, shardID uint) string {
return fmt.Sprintf("export:shard:lock:%d:%d", taskID, shardID)
}
// RedisExportFinalizeLockKey 生成导出 finalize 执行锁的 Redis 键
// 用途:防止同一导出任务并发执行 finalize
// 过期时间2 分钟
func RedisExportFinalizeLockKey(taskID uint) string {
return fmt.Sprintf("export:finalize:lock:%d", taskID)
}
// RedisShopSubordinatesKey 生成店铺下级 ID 列表的 Redis 键
// 用途:缓存递归查询的下级店铺 ID 列表
// 过期时间30 分钟

View File

@@ -36,6 +36,7 @@ func BuildDocHandlers() *bootstrap.Handlers {
Authorization: admin.NewAuthorizationHandler(nil),
IotCard: admin.NewIotCardHandler(nil),
IotCardImport: admin.NewIotCardImportHandler(nil),
ExportTask: admin.NewExportTaskHandler(nil),
Device: admin.NewDeviceHandler(nil),
DeviceImport: admin.NewDeviceImportHandler(nil),
AssetAllocationRecord: admin.NewAssetAllocationRecordHandler(nil),

View File

@@ -6,6 +6,7 @@ import (
"go.uber.org/zap"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/exporter"
"github.com/break/junhong_cmp_fiber/internal/gateway"
"github.com/break/junhong_cmp_fiber/internal/polling"
iot_card_svc "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
@@ -64,6 +65,7 @@ func (h *Handler) RegisterHandlers() *asynq.ServeMux {
h.registerIotCardImportHandler()
h.registerDeviceImportHandler()
h.registerExportHandlers()
h.registerCommissionStatsHandlers()
h.registerCommissionCalculationHandler()
h.registerPollingHandlers()
@@ -113,6 +115,46 @@ func (h *Handler) registerDeviceImportHandler() {
h.logger.Info("注册设备导入任务处理器", zap.String("task_type", constants.TaskTypeDeviceImport))
}
func (h *Handler) registerExportHandlers() {
sceneRegistry := exporter.NewDefaultRegistry(h.db)
dispatchHandler := task.NewExportDispatchHandler(
h.db,
h.redis,
h.workerResult.Stores.ExportTask,
h.workerResult.Stores.ExportShardTask,
h.asynqClient,
sceneRegistry,
h.logger,
)
shardHandler := task.NewExportShardHandler(
h.redis,
h.workerResult.Stores.ExportTask,
h.workerResult.Stores.ExportShardTask,
h.asynqClient,
h.storage,
sceneRegistry,
h.logger,
)
finalizeHandler := task.NewExportFinalizeHandler(
h.redis,
h.workerResult.Stores.ExportTask,
h.workerResult.Stores.ExportShardTask,
h.storage,
sceneRegistry,
h.logger,
)
h.mux.HandleFunc(constants.TaskTypeExportDispatch, dispatchHandler.HandleExportDispatch)
h.logger.Info("注册导出 dispatch 任务处理器", zap.String("task_type", constants.TaskTypeExportDispatch))
h.mux.HandleFunc(constants.TaskTypeExportShard, shardHandler.HandleExportShard)
h.logger.Info("注册导出 shard 任务处理器", zap.String("task_type", constants.TaskTypeExportShard))
h.mux.HandleFunc(constants.TaskTypeExportFinalize, finalizeHandler.HandleExportFinalize)
h.logger.Info("注册导出 finalize 任务处理器", zap.String("task_type", constants.TaskTypeExportFinalize))
}
func (h *Handler) registerCommissionStatsHandlers() {
updateHandler := task.NewCommissionStatsUpdateHandler(
h.redis,

View File

@@ -22,6 +22,8 @@ type WorkerStores struct {
IotCardImportTask *postgres.IotCardImportTaskStore
IotCard *postgres.IotCardStore
DeviceImportTask *postgres.DeviceImportTaskStore
ExportTask *postgres.ExportTaskStore
ExportShardTask *postgres.ExportShardTaskStore
Device *postgres.DeviceStore
DeviceSimBinding *postgres.DeviceSimBindingStore
ShopSeriesCommissionStats *postgres.ShopSeriesCommissionStatsStore