feat: 套餐系统升级 - Worker 重构、流量重置、文档与规范更新
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m54s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m54s
- 重构 Worker 启动流程,引入 bootstrap 模块统一管理依赖注入 - 实现套餐流量重置服务(日/月/年周期重置) - 新增套餐激活排队、加油包绑定、囤货待实名激活逻辑 - 新增订单创建幂等性防重(Redis 业务键 + 分布式锁) - 更新 AGENTS.md/CLAUDE.md:新增注释规范、幂等性规范,移除测试要求 - 添加套餐系统升级完整文档(API文档、使用指南、功能总结、运维指南) - 归档 OpenSpec package-system-upgrade 变更,同步 specs 到主目录 - 新增 queue types 抽象和 Redis 常量定义
This commit is contained in:
@@ -256,3 +256,21 @@ func RedisPollingInitProgressKey() string {
|
||||
func RedisPackageActivationLockKey(carrierType string, carrierID uint) string {
|
||||
return fmt.Sprintf("package:activation:lock:%s:%d", carrierType, carrierID)
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 订单幂等性相关 Redis Key
|
||||
// ========================================
|
||||
|
||||
// RedisOrderIdempotencyKey 生成订单创建幂等性检测的 Redis 键
|
||||
// 用途:防止相同买家在短时间内对同一载体重复下单(SETNX 快速拒绝)
|
||||
// 过期时间:3 分钟
|
||||
func RedisOrderIdempotencyKey(businessKey string) string {
|
||||
return fmt.Sprintf("order:idempotency:%s", businessKey)
|
||||
}
|
||||
|
||||
// RedisOrderCreateLockKey 生成订单创建分布式锁的 Redis 键
|
||||
// 用途:防止同一载体的订单创建并发执行
|
||||
// 过期时间:10 秒
|
||||
func RedisOrderCreateLockKey(carrierType string, carrierID uint) string {
|
||||
return fmt.Sprintf("order:create:lock:%s:%d", carrierType, carrierID)
|
||||
}
|
||||
|
||||
@@ -8,10 +8,6 @@ import (
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
||||
"github.com/break/junhong_cmp_fiber/internal/polling"
|
||||
"github.com/break/junhong_cmp_fiber/internal/service/commission_calculation"
|
||||
"github.com/break/junhong_cmp_fiber/internal/service/commission_stats"
|
||||
packagepkg "github.com/break/junhong_cmp_fiber/internal/service/package"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/internal/task"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/storage"
|
||||
@@ -25,9 +21,20 @@ type Handler struct {
|
||||
storage *storage.Service
|
||||
gatewayClient *gateway.Client
|
||||
pollingCallback task.PollingCallback
|
||||
workerResult *WorkerBootstrapResult
|
||||
asynqClient *asynq.Client
|
||||
}
|
||||
|
||||
func NewHandler(db *gorm.DB, redis *redis.Client, storageSvc *storage.Service, gatewayClient *gateway.Client, pollingCallback task.PollingCallback, logger *zap.Logger) *Handler {
|
||||
func NewHandler(
|
||||
db *gorm.DB,
|
||||
redis *redis.Client,
|
||||
storageSvc *storage.Service,
|
||||
gatewayClient *gateway.Client,
|
||||
pollingCallback task.PollingCallback,
|
||||
workerResult *WorkerBootstrapResult,
|
||||
asynqClient *asynq.Client,
|
||||
logger *zap.Logger,
|
||||
) *Handler {
|
||||
return &Handler{
|
||||
mux: asynq.NewServeMux(),
|
||||
logger: logger,
|
||||
@@ -36,6 +43,8 @@ func NewHandler(db *gorm.DB, redis *redis.Client, storageSvc *storage.Service, g
|
||||
storage: storageSvc,
|
||||
gatewayClient: gatewayClient,
|
||||
pollingCallback: pollingCallback,
|
||||
workerResult: workerResult,
|
||||
asynqClient: asynqClient,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,32 +74,55 @@ func (h *Handler) RegisterHandlers() *asynq.ServeMux {
|
||||
}
|
||||
|
||||
func (h *Handler) registerIotCardImportHandler() {
|
||||
importTaskStore := postgres.NewIotCardImportTaskStore(h.db, h.redis)
|
||||
iotCardStore := postgres.NewIotCardStore(h.db, h.redis)
|
||||
iotCardImportHandler := task.NewIotCardImportHandler(h.db, h.redis, importTaskStore, iotCardStore, h.storage, h.pollingCallback, h.logger)
|
||||
iotCardImportHandler := task.NewIotCardImportHandler(
|
||||
h.db,
|
||||
h.redis,
|
||||
h.workerResult.Stores.IotCardImportTask,
|
||||
h.workerResult.Stores.IotCard,
|
||||
h.storage,
|
||||
h.pollingCallback,
|
||||
h.logger,
|
||||
)
|
||||
|
||||
h.mux.HandleFunc(constants.TaskTypeIotCardImport, iotCardImportHandler.HandleIotCardImport)
|
||||
h.logger.Info("注册 IoT 卡导入任务处理器", zap.String("task_type", constants.TaskTypeIotCardImport))
|
||||
}
|
||||
|
||||
func (h *Handler) registerDeviceImportHandler() {
|
||||
importTaskStore := postgres.NewDeviceImportTaskStore(h.db, h.redis)
|
||||
deviceStore := postgres.NewDeviceStore(h.db, h.redis)
|
||||
bindingStore := postgres.NewDeviceSimBindingStore(h.db, h.redis)
|
||||
iotCardStore := postgres.NewIotCardStore(h.db, h.redis)
|
||||
deviceImportHandler := task.NewDeviceImportHandler(h.db, h.redis, importTaskStore, deviceStore, bindingStore, iotCardStore, h.storage, h.logger)
|
||||
deviceImportHandler := task.NewDeviceImportHandler(
|
||||
h.db,
|
||||
h.redis,
|
||||
h.workerResult.Stores.DeviceImportTask,
|
||||
h.workerResult.Stores.Device,
|
||||
h.workerResult.Stores.DeviceSimBinding,
|
||||
h.workerResult.Stores.IotCard,
|
||||
h.storage,
|
||||
h.logger,
|
||||
)
|
||||
|
||||
h.mux.HandleFunc(constants.TaskTypeDeviceImport, deviceImportHandler.HandleDeviceImport)
|
||||
h.logger.Info("注册设备导入任务处理器", zap.String("task_type", constants.TaskTypeDeviceImport))
|
||||
}
|
||||
|
||||
func (h *Handler) registerCommissionStatsHandlers() {
|
||||
statsStore := postgres.NewShopSeriesCommissionStatsStore(h.db)
|
||||
allocationStore := postgres.NewShopPackageAllocationStore(h.db)
|
||||
|
||||
updateHandler := task.NewCommissionStatsUpdateHandler(h.redis, statsStore, allocationStore, h.logger)
|
||||
syncHandler := task.NewCommissionStatsSyncHandler(h.db, h.redis, statsStore, h.logger)
|
||||
archiveHandler := task.NewCommissionStatsArchiveHandler(h.db, h.redis, statsStore, h.logger)
|
||||
updateHandler := task.NewCommissionStatsUpdateHandler(
|
||||
h.redis,
|
||||
h.workerResult.Stores.ShopSeriesCommissionStats,
|
||||
h.workerResult.Stores.ShopPackageAllocation,
|
||||
h.logger,
|
||||
)
|
||||
syncHandler := task.NewCommissionStatsSyncHandler(
|
||||
h.db,
|
||||
h.redis,
|
||||
h.workerResult.Stores.ShopSeriesCommissionStats,
|
||||
h.logger,
|
||||
)
|
||||
archiveHandler := task.NewCommissionStatsArchiveHandler(
|
||||
h.db,
|
||||
h.redis,
|
||||
h.workerResult.Stores.ShopSeriesCommissionStats,
|
||||
h.logger,
|
||||
)
|
||||
|
||||
h.mux.HandleFunc(constants.TaskTypeCommissionStatsUpdate, updateHandler.HandleCommissionStatsUpdate)
|
||||
h.logger.Info("注册佣金统计更新任务处理器", zap.String("task_type", constants.TaskTypeCommissionStatsUpdate))
|
||||
@@ -103,58 +135,23 @@ func (h *Handler) registerCommissionStatsHandlers() {
|
||||
}
|
||||
|
||||
func (h *Handler) registerCommissionCalculationHandler() {
|
||||
// 创建所有需要的 Store 实例
|
||||
commissionRecordStore := postgres.NewCommissionRecordStore(h.db, h.redis)
|
||||
shopStore := postgres.NewShopStore(h.db, h.redis)
|
||||
shopPackageAllocationStore := postgres.NewShopPackageAllocationStore(h.db)
|
||||
shopSeriesAllocationStore := postgres.NewShopSeriesAllocationStore(h.db)
|
||||
packageSeriesStore := postgres.NewPackageSeriesStore(h.db)
|
||||
iotCardStore := postgres.NewIotCardStore(h.db, h.redis)
|
||||
deviceStore := postgres.NewDeviceStore(h.db, h.redis)
|
||||
walletStore := postgres.NewWalletStore(h.db, h.redis)
|
||||
walletTransactionStore := postgres.NewWalletTransactionStore(h.db, h.redis)
|
||||
orderStore := postgres.NewOrderStore(h.db, h.redis)
|
||||
orderItemStore := postgres.NewOrderItemStore(h.db, h.redis)
|
||||
packageStore := postgres.NewPackageStore(h.db)
|
||||
commissionStatsStore := postgres.NewShopSeriesCommissionStatsStore(h.db)
|
||||
|
||||
// 创建 commission_stats.Service
|
||||
commissionStatsService := commission_stats.New(commissionStatsStore)
|
||||
|
||||
// 创建 commission_calculation.Service
|
||||
commissionCalculationService := commission_calculation.New(
|
||||
commissionCalculationHandler := task.NewCommissionCalculationHandler(
|
||||
h.db,
|
||||
commissionRecordStore,
|
||||
shopStore,
|
||||
shopPackageAllocationStore,
|
||||
shopSeriesAllocationStore,
|
||||
packageSeriesStore,
|
||||
iotCardStore,
|
||||
deviceStore,
|
||||
walletStore,
|
||||
walletTransactionStore,
|
||||
orderStore,
|
||||
orderItemStore,
|
||||
packageStore,
|
||||
commissionStatsStore,
|
||||
commissionStatsService,
|
||||
h.workerResult.Services.CommissionCalculation,
|
||||
h.logger,
|
||||
)
|
||||
|
||||
// 创建并注册 Handler
|
||||
commissionCalculationHandler := task.NewCommissionCalculationHandler(h.db, commissionCalculationService, h.logger)
|
||||
h.mux.HandleFunc(constants.TaskTypeCommission, commissionCalculationHandler.HandleCommissionCalculation)
|
||||
h.logger.Info("注册佣金计算任务处理器", zap.String("task_type", constants.TaskTypeCommission))
|
||||
}
|
||||
|
||||
// registerPollingHandlers 注册轮询任务处理器
|
||||
func (h *Handler) registerPollingHandlers() {
|
||||
// 创建套餐相关 Store 和 Service(用于流量扣减)
|
||||
packageUsageStore := postgres.NewPackageUsageStore(h.db, h.redis)
|
||||
packageUsageDailyRecordStore := postgres.NewPackageUsageDailyRecordStore(h.db, h.redis)
|
||||
usageService := packagepkg.NewUsageService(h.db, h.redis, packageUsageStore, packageUsageDailyRecordStore, h.logger)
|
||||
|
||||
pollingHandler := task.NewPollingHandler(h.db, h.redis, h.gatewayClient, usageService, h.logger)
|
||||
pollingHandler := task.NewPollingHandler(
|
||||
h.db,
|
||||
h.redis,
|
||||
h.gatewayClient,
|
||||
h.workerResult.Services.UsageService,
|
||||
h.logger,
|
||||
)
|
||||
|
||||
h.mux.HandleFunc(constants.TaskTypePollingRealname, pollingHandler.HandleRealnameCheck)
|
||||
h.logger.Info("注册实名检查任务处理器", zap.String("task_type", constants.TaskTypePollingRealname))
|
||||
@@ -166,45 +163,18 @@ func (h *Handler) registerPollingHandlers() {
|
||||
h.logger.Info("注册套餐检查任务处理器", zap.String("task_type", constants.TaskTypePollingPackage))
|
||||
}
|
||||
|
||||
// registerPackageActivationHandlers 注册套餐激活任务处理器
|
||||
// 任务 22.6 和 23.6: 注册首次实名激活和排队激活任务 Handler
|
||||
func (h *Handler) registerPackageActivationHandlers() {
|
||||
// 创建套餐相关 Store 和 Service
|
||||
packageUsageStore := postgres.NewPackageUsageStore(h.db, h.redis)
|
||||
packageStore := postgres.NewPackageStore(h.db)
|
||||
packageUsageDailyRecordStore := postgres.NewPackageUsageDailyRecordStore(h.db, h.redis)
|
||||
|
||||
activationService := packagepkg.NewActivationService(
|
||||
h.db,
|
||||
h.redis,
|
||||
packageUsageStore,
|
||||
packageStore,
|
||||
packageUsageDailyRecordStore,
|
||||
h.logger,
|
||||
)
|
||||
|
||||
// 创建 Asynq 客户端用于任务提交
|
||||
redisOpt := asynq.RedisClientOpt{
|
||||
Addr: h.redis.Options().Addr,
|
||||
Password: h.redis.Options().Password,
|
||||
DB: h.redis.Options().DB,
|
||||
}
|
||||
queueClient := asynq.NewClient(redisOpt)
|
||||
|
||||
// 创建套餐激活处理器
|
||||
packageActivationHandler := polling.NewPackageActivationHandler(
|
||||
h.db,
|
||||
h.redis,
|
||||
queueClient,
|
||||
activationService,
|
||||
h.asynqClient,
|
||||
h.workerResult.Services.ActivationService,
|
||||
h.logger,
|
||||
)
|
||||
|
||||
// 任务 22.6: 注册首次实名激活任务 Handler
|
||||
h.mux.HandleFunc(constants.TaskTypePackageFirstActivation, packageActivationHandler.HandlePackageFirstActivation)
|
||||
h.logger.Info("注册首次实名激活任务处理器", zap.String("task_type", constants.TaskTypePackageFirstActivation))
|
||||
|
||||
// 任务 23.6: 注册排队激活任务 Handler
|
||||
h.mux.HandleFunc(constants.TaskTypePackageQueueActivation, packageActivationHandler.HandlePackageQueueActivation)
|
||||
h.logger.Info("注册排队激活任务处理器", zap.String("task_type", constants.TaskTypePackageQueueActivation))
|
||||
}
|
||||
|
||||
52
pkg/queue/types.go
Normal file
52
pkg/queue/types.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"github.com/break/junhong_cmp_fiber/internal/service/commission_calculation"
|
||||
"github.com/break/junhong_cmp_fiber/internal/service/commission_stats"
|
||||
packagepkg "github.com/break/junhong_cmp_fiber/internal/service/package"
|
||||
pollingSvc "github.com/break/junhong_cmp_fiber/internal/service/polling"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
)
|
||||
|
||||
// WorkerStores Worker 侧所有 Store 的集合
|
||||
type WorkerStores struct {
|
||||
IotCardImportTask *postgres.IotCardImportTaskStore
|
||||
IotCard *postgres.IotCardStore
|
||||
DeviceImportTask *postgres.DeviceImportTaskStore
|
||||
Device *postgres.DeviceStore
|
||||
DeviceSimBinding *postgres.DeviceSimBindingStore
|
||||
ShopSeriesCommissionStats *postgres.ShopSeriesCommissionStatsStore
|
||||
ShopPackageAllocation *postgres.ShopPackageAllocationStore
|
||||
CommissionRecord *postgres.CommissionRecordStore
|
||||
Shop *postgres.ShopStore
|
||||
ShopSeriesAllocation *postgres.ShopSeriesAllocationStore
|
||||
PackageSeries *postgres.PackageSeriesStore
|
||||
Wallet *postgres.WalletStore
|
||||
WalletTransaction *postgres.WalletTransactionStore
|
||||
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
|
||||
}
|
||||
|
||||
// WorkerServices Worker 侧所有 Service 的集合
|
||||
type WorkerServices struct {
|
||||
CommissionCalculation *commission_calculation.Service
|
||||
CommissionStats *commission_stats.Service
|
||||
UsageService *packagepkg.UsageService
|
||||
ActivationService *packagepkg.ActivationService
|
||||
ResetService *packagepkg.ResetService
|
||||
AlertService *pollingSvc.AlertService
|
||||
CleanupService *pollingSvc.CleanupService
|
||||
}
|
||||
|
||||
// WorkerBootstrapResult Worker Bootstrap 结果
|
||||
type WorkerBootstrapResult struct {
|
||||
Stores *WorkerStores
|
||||
Services *WorkerServices
|
||||
}
|
||||
Reference in New Issue
Block a user