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:
@@ -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("数据清理定时任务已停止")
|
||||
|
||||
Reference in New Issue
Block a user