bindbox-game/docs/优化抖音定时任务/DESIGN_优化抖音定时任务.md

5.0 KiB
Executable File
Raw Permalink Blame History

DESIGN - 优化抖音定时任务

整体架构图

graph TB
    subgraph "定时任务层"
        T1[5分钟定时器<br/>直播奖品发放]
        T2[1小时定时器<br/>全量订单同步]
        T3[2小时定时器<br/>退款状态同步]
    end
    
    subgraph "服务层"
        DS[DouyinService]
        DS --> |GrantLivestreamPrizes| DB[(MySQL)]
        DS --> |SyncAllOrders| API[抖音API<br/>代理IP]
        DS --> |SyncRefundStatus| API
    end
    
    subgraph "接口层"
        A1[管理后台<br/>手动同步]
        A2[前端按需<br/>5秒限流]
    end
    
    T1 --> DS
    T2 --> DS
    T3 --> DS
    A1 --> DS
    A2 --> DS
    
    style T1 fill:#90EE90
    style T2 fill:#FFB6C1
    style T3 fill:#FFB6C1
    style API fill:#FF6B6B

核心组件设计

1. 定时任务调度器 (scheduler.go)

修改前:

// 单一定时器,每 5 分钟执行所有任务
for {
    FetchAndSyncOrders()  // 冗余
    SyncAllOrders()
    GrantLivestreamPrizes()
    SyncRefundStatus()
    time.Sleep(5 * time.Minute)
}

修改后:

// 多定时器,分频执行
ticker5min := time.NewTicker(5 * time.Minute)
ticker1h := time.NewTicker(1 * time.Hour)
ticker2h := time.NewTicker(2 * time.Hour)

for {
    select {
    case <-ticker5min.C:
        GrantLivestreamPrizes()  // 不调用API
    case <-ticker1h.C:
        SyncAllOrders()          // 调用API
    case <-ticker2h.C:
        SyncRefundStatus()       // 调用API
    }
}

2. 管理后台接口 (douyin_admin.go)

新增文件结构:

package admin

type douyinHandler struct {
    logger logger.CustomLogger
    douyin douyinsvc.Service
}

// 手动全量同步
func (h *douyinHandler) ManualSyncAll() core.HandlerFunc

// 手动退款同步
func (h *douyinHandler) ManualSyncRefund() core.HandlerFunc

// 手动发放奖品
func (h *douyinHandler) ManualGrantPrizes() core.HandlerFunc

3. 路由注册 (router.go)

新增路由:

adminGroup := r.Group("/api/admin")
adminGroup.Use(middleware.AdminAuth())
{
    douyin := adminGroup.Group("/douyin")
    {
        douyin.POST("/sync-all", douyinHandler.ManualSyncAll())
        douyin.POST("/sync-refund", douyinHandler.ManualSyncRefund())
        douyin.POST("/grant-prizes", douyinHandler.ManualGrantPrizes())
    }
}

模块依赖关系图

graph LR
    A[scheduler.go] --> B[douyin.Service]
    C[douyin_admin.go] --> B
    D[livestream_public.go] --> B
    
    B --> E[order_sync.go]
    B --> F[reward_dispatcher.go]
    
    E --> G[MySQL]
    E --> H[抖音API]
    F --> G

接口契约定义

管理后台接口

1. 手动全量同步

POST /api/admin/douyin/sync-all
Headers: Authorization: Bearer {admin_token}
Body: {
    "duration_hours": 1  // 可选,默认1小时
}
Response: {
    "total_fetched": 100,
    "new_orders": 5,
    "matched_users": 3
}

2. 手动退款同步

POST /api/admin/douyin/sync-refund
Headers: Authorization: Bearer {admin_token}
Response: {
    "refunded_count": 2
}

3. 手动发放奖品

POST /api/admin/douyin/grant-prizes
Headers: Authorization: Bearer {admin_token}
Response: {
    "granted_count": 5
}

数据流向图

sequenceDiagram
    participant T as 定时器
    participant S as DouyinService
    participant A as 抖音API
    participant D as MySQL
    
    Note over T: 每1小时触发
    T->>S: SyncAllOrders(1h)
    S->>A: GET /api/order/searchlist
    A-->>S: 订单列表
    S->>D: 批量更新订单
    S->>D: 自动发放奖励
    
    Note over T: 每5分钟触发
    T->>S: GrantLivestreamPrizes()
    S->>D: 查询未发放记录
    S->>D: 创建订单+发货

异常处理策略

1. API 调用失败

// 重试机制 (已有)
for i := 0; i < 3; i++ {
    resp, err := client.Do(req)
    if err == nil {
        break
    }
    time.Sleep(1 * time.Second)
}

2. 定时器异常

// 使用 defer + recover 防止 panic
defer func() {
    if r := recover(); r != nil {
        logger.Error("定时任务异常", zap.Any("panic", r))
    }
}()

3. 并发控制

// 使用 singleflight 防止重复执行 (已有)
v, err, _ := s.sfGroup.Do("SyncAllOrders", func() {...})

性能优化

API 调用频率对比

优化前: 每 5 分钟 × 4 个任务 = 12 次/小时
优化后: 每 1 小时 × 1 次 + 每 2 小时 × 1 次 = 1.5 次/小时
降低: 87.5%

响应时间预估

定时任务: 1-60 秒 (取决于代理IP速度)
手动触发: 1-60 秒 (同上)
前端按需: <5 秒 (限流跳过) 或 1-60 秒

质量保证

单元测试

  • 测试定时器触发逻辑
  • 测试管理接口权限控制
  • 测试 Service 方法调用

集成测试

  • 验证定时任务正常执行
  • 验证手动触发接口可用
  • 验证前端按需同步不受影响

回滚方案

如果出现问题,可快速回滚:

// 恢复单一定时器
for {
    svc.SyncAllOrders(ctx, 1*time.Hour)
    svc.GrantLivestreamPrizes(ctx)
    svc.SyncRefundStatus(ctx)
    time.Sleep(5 * time.Minute)
}