feat: 实现订单超时自动取消功能,支持钱包余额解冻和 Asynq Scheduler 统一调度
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m58s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m58s
- 新增 expires_at 字段和复合索引,待支付订单 30 分钟超时自动取消 - 实现 cancelOrder/unfreezeWalletForCancel 钱包余额解冻逻辑 - 创建 Asynq 定时任务(order_expire/alert_check/data_cleanup) - 将原有 time.Ticker 轮询迁移至 Asynq Scheduler 统一调度 - 同步 delta specs 到 main specs 并归档变更
This commit is contained in:
@@ -15,9 +15,9 @@ import (
|
||||
"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"
|
||||
pkgBootstrap "github.com/break/junhong_cmp_fiber/pkg/bootstrap"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/config"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/database"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/logger"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/queue"
|
||||
@@ -158,11 +158,36 @@ func main() {
|
||||
zap.Int("concurrency", cfg.Queue.Concurrency),
|
||||
zap.Any("queues", cfg.Queue.Queues))
|
||||
|
||||
// 初始化告警服务并启动告警检查器
|
||||
alertChecker := startAlertChecker(ctx, workerResult.Services.AlertService, appLogger)
|
||||
// 创建 Asynq Scheduler(定时任务调度器:订单超时、告警检查、数据清理)
|
||||
asynqScheduler := asynq.NewScheduler(
|
||||
asynq.RedisClientOpt{
|
||||
Addr: redisAddr,
|
||||
Password: cfg.Redis.Password,
|
||||
DB: cfg.Redis.DB,
|
||||
},
|
||||
&asynq.SchedulerOpts{Location: time.Local},
|
||||
)
|
||||
|
||||
// 初始化数据清理服务并启动定时清理任务
|
||||
cleanupChecker := startCleanupScheduler(ctx, workerResult.Services.CleanupService, appLogger)
|
||||
// 注册定时任务:订单超时检查(每分钟)
|
||||
if _, err := asynqScheduler.Register("@every 1m", asynq.NewTask(constants.TaskTypeOrderExpire, nil)); err != nil {
|
||||
appLogger.Fatal("注册订单超时定时任务失败", zap.Error(err))
|
||||
}
|
||||
// 注册定时任务:告警检查(每分钟)
|
||||
if _, err := asynqScheduler.Register("@every 1m", asynq.NewTask(constants.TaskTypeAlertCheck, nil)); err != nil {
|
||||
appLogger.Fatal("注册告警检查定时任务失败", zap.Error(err))
|
||||
}
|
||||
// 注册定时任务:数据清理(每天凌晨 2 点)
|
||||
if _, err := asynqScheduler.Register("0 2 * * *", asynq.NewTask(constants.TaskTypeDataCleanup, nil)); err != nil {
|
||||
appLogger.Fatal("注册数据清理定时任务失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 启动 Asynq Scheduler
|
||||
go func() {
|
||||
if err := asynqScheduler.Run(); err != nil {
|
||||
appLogger.Fatal("Asynq Scheduler 启动失败", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
appLogger.Info("Asynq Scheduler 已启动(订单超时: @every 1m, 告警检查: @every 1m, 数据清理: 0 2 * * *)")
|
||||
|
||||
// 优雅关闭
|
||||
quit := make(chan os.Signal, 1)
|
||||
@@ -181,11 +206,8 @@ func main() {
|
||||
<-quit
|
||||
appLogger.Info("正在关闭 Worker 服务器...")
|
||||
|
||||
// 停止告警检查器
|
||||
close(alertChecker)
|
||||
|
||||
// 停止数据清理定时任务
|
||||
close(cleanupChecker)
|
||||
// 停止 Asynq Scheduler
|
||||
asynqScheduler.Shutdown()
|
||||
|
||||
// 停止轮询调度器
|
||||
scheduler.Stop()
|
||||
@@ -235,70 +257,3 @@ func initGateway(cfg *config.Config, appLogger *zap.Logger) *gateway.Client {
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func startAlertChecker(ctx context.Context, alertService *pollingSvc.AlertService, appLogger *zap.Logger) chan struct{} {
|
||||
stopChan := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
appLogger.Info("告警检查器已启动,检查间隔: 1分钟")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := alertService.CheckAlerts(ctx); err != nil {
|
||||
appLogger.Error("告警检查失败", zap.Error(err))
|
||||
}
|
||||
case <-stopChan:
|
||||
appLogger.Info("告警检查器已停止")
|
||||
return
|
||||
case <-ctx.Done():
|
||||
appLogger.Info("告警检查器因 context 取消而停止")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return stopChan
|
||||
}
|
||||
|
||||
func startCleanupScheduler(ctx context.Context, cleanupService *pollingSvc.CleanupService, appLogger *zap.Logger) chan struct{} {
|
||||
stopChan := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
calcNextRun := func() time.Duration {
|
||||
now := time.Now()
|
||||
next := time.Date(now.Year(), now.Month(), now.Day(), 2, 0, 0, 0, now.Location())
|
||||
if now.After(next) {
|
||||
next = next.Add(24 * time.Hour)
|
||||
}
|
||||
return time.Until(next)
|
||||
}
|
||||
|
||||
timer := time.NewTimer(calcNextRun())
|
||||
defer timer.Stop()
|
||||
|
||||
appLogger.Info("数据清理定时任务已启动,每天凌晨2点执行")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
appLogger.Info("开始执行定时数据清理")
|
||||
if err := cleanupService.RunScheduledCleanup(ctx); err != nil {
|
||||
appLogger.Error("定时数据清理失败", zap.Error(err))
|
||||
}
|
||||
timer.Reset(calcNextRun())
|
||||
case <-stopChan:
|
||||
appLogger.Info("数据清理定时任务已停止")
|
||||
return
|
||||
case <-ctx.Done():
|
||||
appLogger.Info("数据清理定时任务因 context 取消而停止")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return stopChan
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user