This commit is contained in:
zhengchiliu
2026-09-15 00:53:04 +08:00
parent 32a0b87e9e
commit 112c5f8e46
42 changed files with 1121 additions and 304 deletions
+51 -5
View File
@@ -9,6 +9,7 @@ import (
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqldao"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/redisdao"
"gorm.io/gorm"
)
// StartBenefitCompensationScheduler 启动权益发放补偿定时任务:周期性重试发放失败的权益。
@@ -46,6 +47,15 @@ func runBenefitCompensation() {
defer redisdao.Unlock(context.Background(), lockKey, lockVal)
taskDao := mysqldao.NewBenefitGrantTaskDao()
s := NewOrderService()
// 对账已支付但任务未完整落库的订单,覆盖支付状态提交后进程崩溃/数据库瞬时错误。
if orders, err := mysqldao.NewOrderDao().OrderListPaidMissingBenefitTasks(ctx, 100); err == nil {
for i := range orders {
s.grantOrderBenefits(ctx, &orders[i])
}
} else {
slog.ErrorContext(ctx, "reconcile paid order benefits failed", "error", err)
}
// 恢复超时的「发放中」任务(认领后崩溃的场景),使其回到待发放继续重试。
_, _ = taskDao.BenefitGrantTaskRecoverStale(ctx, nowUnix()-int64((10*time.Minute).Seconds()))
@@ -55,14 +65,19 @@ func runBenefitCompensation() {
slog.ErrorContext(ctx, "list pending benefit tasks failed", "error", err)
return
}
if len(tasks) == 0 {
return
}
s := NewOrderService()
for i := range tasks {
s.retryBenefitTask(ctx, &tasks[i])
}
revokeDao := mysqldao.NewBenefitRevokeTaskDao()
_ = revokeDao.RecoverStale(ctx, nowUnix()-int64((10*time.Minute).Seconds()))
if revokeTasks, err := revokeDao.ListPending(ctx, 100); err == nil {
for i := range revokeTasks {
s.executeRevokeTask(ctx, &revokeTasks[i])
}
} else {
slog.ErrorContext(ctx, "list pending benefit revoke tasks failed", "error", err)
}
}
// retryBenefitTask 重试单个补偿任务(幂等:先查发放流水避免重复发放)。
@@ -110,3 +125,34 @@ func (s *OrderService) benefitAlreadyGranted(ctx context.Context, task *mysqlmod
return false, nil
}
}
func (s *OrderService) executeRevokeTaskByOrder(ctx context.Context, orderNo string) {
task, err := mysqldao.NewBenefitRevokeTaskDao().GetByOrderNo(ctx, orderNo)
if err == nil {
s.executeRevokeTask(ctx, task)
}
}
func (s *OrderService) executeRevokeTask(ctx context.Context, task *mysqlmodel.BenefitRevokeTask) {
dao := mysqldao.NewBenefitRevokeTaskDao()
if task.Status == BenefitTaskSuccess {
return
}
if task.RetryCount >= task.MaxRetry {
slog.ErrorContext(ctx, "benefit revoke task exceeded normal retry threshold; continuing for eventual consistency", "order_no", task.OrderNo, "retry_count", task.RetryCount)
}
claimed, err := dao.Claim(ctx, task.ID)
if err != nil || !claimed {
return
}
order, err := s.orderDao.OrderGetByOrderNo(ctx, task.OrderNo)
if err == nil {
err = s.revokeOrderBenefits(ctx, order)
}
if err != nil {
_ = dao.Update(ctx, task.ID, map[string]any{"status": BenefitTaskPending, "retry_count": gorm.Expr("retry_count + 1"), "last_error": err.Error()})
slog.ErrorContext(ctx, "revoke refunded order benefits failed", "order_no", task.OrderNo, "error", err)
return
}
_ = dao.Update(ctx, task.ID, map[string]any{"status": BenefitTaskSuccess, "last_error": ""})
}