feat: 套餐系统升级 - Worker 重构、流量重置、文档与规范更新
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:
2026-02-12 14:24:15 +08:00
parent 655c9ce7a6
commit c665f32976
51 changed files with 7289 additions and 424 deletions

View File

@@ -11,13 +11,12 @@ import (
"github.com/hibiken/asynq"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
"github.com/break/junhong_cmp_fiber/internal/gateway"
"github.com/break/junhong_cmp_fiber/internal/polling"
pollingSvc "github.com/break/junhong_cmp_fiber/internal/service/polling"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/bootstrap"
pkgBootstrap "github.com/break/junhong_cmp_fiber/pkg/bootstrap"
"github.com/break/junhong_cmp_fiber/pkg/config"
"github.com/break/junhong_cmp_fiber/pkg/database"
"github.com/break/junhong_cmp_fiber/pkg/logger"
@@ -31,7 +30,7 @@ func main() {
panic("加载配置失败: " + err.Error())
}
if _, err := bootstrap.EnsureDirectories(cfg, nil); err != nil {
if _, err := pkgBootstrap.EnsureDirectories(cfg, nil); err != nil {
panic("初始化目录失败: " + err.Error())
}
@@ -119,20 +118,40 @@ func main() {
}
}()
// 创建 Worker 依赖
workerDeps := &bootstrap.WorkerDependencies{
DB: db,
Redis: redisClient,
Logger: appLogger,
AsynqClient: asynqClient,
StorageService: storageSvc,
GatewayClient: gatewayClient,
}
// Bootstrap Worker 组件
workerResult, err := bootstrap.BootstrapWorker(workerDeps)
if err != nil {
appLogger.Fatal("Worker Bootstrap 失败", zap.Error(err))
}
// 创建 Asynq Worker 服务器
workerServer := queue.NewServer(redisClient, &cfg.Queue, appLogger)
// 初始化轮询调度器(在创建 Handler 之前,因为 Handler 需要使用调度器作为回调)
scheduler := polling.NewScheduler(db, redisClient, asynqClient, appLogger)
// 注入流量重置服务到调度器
dataResetHandler := polling.NewDataResetHandler(workerResult.Services.ResetService, appLogger)
scheduler.SetResetService(dataResetHandler)
if err := scheduler.Start(ctx); err != nil {
appLogger.Error("启动轮询调度器失败", zap.Error(err))
// 调度器启动失败不阻止 Worker 启动,但不传递给 Handler
} else {
appLogger.Info("轮询调度器已启动")
}
// 创建任务处理器管理器并注册所有处理器(传递 scheduler 作为轮询回调)
taskHandler := queue.NewHandler(db, redisClient, storageSvc, gatewayClient, scheduler, appLogger)
// 创建任务处理器管理器并注册所有处理器
taskHandler := queue.NewHandler(db, redisClient, storageSvc, gatewayClient, scheduler, workerResult, asynqClient, appLogger)
taskHandler.RegisterHandlers()
appLogger.Info("Worker 服务器配置完成",
@@ -140,10 +159,10 @@ func main() {
zap.Any("queues", cfg.Queue.Queues))
// 初始化告警服务并启动告警检查器
alertChecker := startAlertChecker(ctx, db, redisClient, appLogger)
alertChecker := startAlertChecker(ctx, workerResult.Services.AlertService, appLogger)
// 初始化数据清理服务并启动定时清理任务
cleanupChecker := startCleanupScheduler(ctx, db, appLogger)
cleanupChecker := startCleanupScheduler(ctx, workerResult.Services.CleanupService, appLogger)
// 优雅关闭
quit := make(chan os.Signal, 1)
@@ -217,19 +236,11 @@ func initGateway(cfg *config.Config, appLogger *zap.Logger) *gateway.Client {
return client
}
// startAlertChecker 启动告警检查器
// 返回一个 stop channel关闭它可以停止检查器
func startAlertChecker(ctx context.Context, db *gorm.DB, redisClient *redis.Client, appLogger *zap.Logger) chan struct{} {
func startAlertChecker(ctx context.Context, alertService *pollingSvc.AlertService, appLogger *zap.Logger) chan struct{} {
stopChan := make(chan struct{})
// 创建告警服务所需的 stores
ruleStore := postgres.NewPollingAlertRuleStore(db)
historyStore := postgres.NewPollingAlertHistoryStore(db)
alertService := pollingSvc.NewAlertService(ruleStore, historyStore, redisClient, appLogger)
// 启动检查器 goroutine
go func() {
ticker := time.NewTicker(1 * time.Minute) // 每分钟检查一次
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
appLogger.Info("告警检查器已启动,检查间隔: 1分钟")
@@ -253,18 +264,10 @@ func startAlertChecker(ctx context.Context, db *gorm.DB, redisClient *redis.Clie
return stopChan
}
// startCleanupScheduler 启动数据清理定时任务
// 每天凌晨2点运行清理任务
func startCleanupScheduler(ctx context.Context, db *gorm.DB, appLogger *zap.Logger) chan struct{} {
func startCleanupScheduler(ctx context.Context, cleanupService *pollingSvc.CleanupService, appLogger *zap.Logger) chan struct{} {
stopChan := make(chan struct{})
// 创建清理服务
configStore := postgres.NewDataCleanupConfigStore(db)
logStore := postgres.NewDataCleanupLogStore(db)
cleanupService := pollingSvc.NewCleanupService(configStore, logStore, appLogger)
go func() {
// 计算到下一个凌晨2点的时间间隔
calcNextRun := func() time.Duration {
now := time.Now()
next := time.Date(now.Year(), now.Month(), now.Day(), 2, 0, 0, 0, now.Location())
@@ -286,7 +289,6 @@ func startCleanupScheduler(ctx context.Context, db *gorm.DB, appLogger *zap.Logg
if err := cleanupService.RunScheduledCleanup(ctx); err != nil {
appLogger.Error("定时数据清理失败", zap.Error(err))
}
// 重置定时器到下一个凌晨2点
timer.Reset(calcNextRun())
case <-stopChan:
appLogger.Info("数据清理定时任务已停止")