fwhTaskQueue.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package fwhQueue
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "fwh-toolbox-server/dto"
  7. task_core "fwh-toolbox-server/task/core"
  8. "github.com/hibiken/asynq"
  9. "go.uber.org/zap"
  10. )
  11. // 任务类型常量
  12. const (
  13. TaskTypeBatchDownload = "fwh:BatchDownload"
  14. TaskTypeCallFile = "fwh:CallFile"
  15. TaskTypeSiteAnalysis = "fwh:SiteAnalysis"
  16. TaskTypeShutdownStats = "fwh:ShutdownStatistics"
  17. TaskTypeSiteInfoUpdate = "fwh:SiteInfoUpdate"
  18. )
  19. // 队列名称
  20. const (
  21. QueueMain = "fwh:main" // 主任务队列(顺序执行)
  22. QueueDownload = "fwh:download" // 文件下载队列(可并发)
  23. )
  24. // Redis key 前缀
  25. const (
  26. // 子任务计数器 key: fwh:callfile:counter:{mainTaskId}
  27. callFileCounterPrefix = "fwh:callfile:counter:"
  28. // 子任务失败原因 key: fwh:callfile:failures:{mainTaskId}
  29. callFileFailuresPrefix = "fwh:callfile:failures:"
  30. )
  31. // 模块级变量
  32. var (
  33. TaskClient *asynq.Client
  34. inspector *asynq.Inspector
  35. )
  36. // InitFwhTaskQueue 初始化新的 FWH 任务队列
  37. // 通过 TaskCore 创建 Asynq Server(worker)、Client(入队)、Inspector(状态查询)
  38. func InitFwhTaskQueue(tc *task_core.TaskCore) {
  39. // 获取 Inspector(用于主任务轮询子任务状态)
  40. inspector = tc.Inspector.GetInspector()
  41. // 注册处理函数
  42. mux := asynq.NewServeMux()
  43. mux.HandleFunc(TaskTypeBatchDownload, handleBatchDownload)
  44. mux.HandleFunc(TaskTypeCallFile, handleCallFile)
  45. mux.HandleFunc(TaskTypeSiteAnalysis, handleSiteAnalysis)
  46. mux.HandleFunc(TaskTypeShutdownStats, handleShutdownStatistics)
  47. mux.HandleFunc(TaskTypeSiteInfoUpdate, handleSiteInfoUpdate)
  48. // 通过 TaskCore 创建任务 Server 和 Client
  49. TaskClient = tc.CreateTask(task_core.TaskConfig{
  50. Name: "FwhTask",
  51. SvrCfg: asynq.Config{
  52. Concurrency: 6, // 总并发数
  53. Queues: map[string]int{
  54. QueueDownload: 4, // 文件下载队列:4 个并发 worker
  55. QueueMain: 2, // 主任务队列:2 个并发 worker
  56. },
  57. // 错误处理
  58. ErrorHandler: asynq.ErrorHandlerFunc(func(ctx context.Context, task *asynq.Task, err error) {
  59. zap.L().Error("[fwhQueue] task failed",
  60. zap.String("type", task.Type()),
  61. zap.String("err", err.Error()),
  62. )
  63. }),
  64. // 重试延迟策略(指数退避)
  65. RetryDelayFunc: func(n int, e error, t *asynq.Task) time.Duration {
  66. return time.Duration(n*n) * 30 * time.Second // 30s, 2min, 4.5min
  67. },
  68. },
  69. Mux: mux,
  70. })
  71. zap.L().Info("[fwhQueue] initialized successfully")
  72. }
  73. // GetClient 获取 Asynq Client(供内部使用)
  74. func GetClient() *asynq.Client {
  75. return TaskClient
  76. }
  77. // GetInspector 获取 Asynq Inspector(供内部使用)
  78. func GetInspector() *asynq.Inspector {
  79. return inspector
  80. }
  81. // GetTaskInfoList 获取所有 FWH 队列中的任务列表(供外部查询)
  82. func GetTaskInfoList() ([]dto.BackgroundTaskItem, error) {
  83. if inspector == nil {
  84. return nil, fmt.Errorf("inspector not initialized")
  85. }
  86. var result []dto.BackgroundTaskItem
  87. queues := []string{QueueMain, QueueDownload}
  88. for _, queue := range queues {
  89. // 查询活跃任务(running)
  90. activeTasks, err := inspector.ListActiveTasks(queue, asynq.PageSize(100))
  91. if err == nil {
  92. for _, t := range activeTasks {
  93. result = append(result, dto.BackgroundTaskItem{
  94. Id: t.ID,
  95. Status: "running",
  96. Retry: int64(t.Retried),
  97. Payload: string(t.Payload),
  98. })
  99. }
  100. }
  101. // 查询等待中的任务(pending)
  102. pendingTasks, err := inspector.ListPendingTasks(queue, asynq.PageSize(100))
  103. if err == nil {
  104. for _, t := range pendingTasks {
  105. result = append(result, dto.BackgroundTaskItem{
  106. Id: t.ID,
  107. Status: "pending",
  108. Retry: int64(t.Retried),
  109. Payload: string(t.Payload),
  110. })
  111. }
  112. }
  113. // 查询重试中的任务(retry)
  114. retryTasks, err := inspector.ListRetryTasks(queue, asynq.PageSize(100))
  115. if err == nil {
  116. for _, t := range retryTasks {
  117. result = append(result, dto.BackgroundTaskItem{
  118. Id: t.ID,
  119. Status: "retry",
  120. Retry: int64(t.Retried),
  121. Payload: string(t.Payload),
  122. })
  123. }
  124. }
  125. // 查询已完成的任务(completed)
  126. completedTasks, err := inspector.ListCompletedTasks(queue, asynq.PageSize(100))
  127. if err == nil {
  128. for _, t := range completedTasks {
  129. result = append(result, dto.BackgroundTaskItem{
  130. Id: t.ID,
  131. Status: "completed",
  132. Retry: int64(t.Retried),
  133. Payload: string(t.Payload),
  134. })
  135. }
  136. }
  137. // 查询失败的任务(archived/failed)
  138. archivedTasks, err := inspector.ListArchivedTasks(queue, asynq.PageSize(100))
  139. if err == nil {
  140. for _, t := range archivedTasks {
  141. result = append(result, dto.BackgroundTaskItem{
  142. Id: t.ID,
  143. Status: "failed",
  144. Retry: int64(t.Retried),
  145. Payload: string(t.Payload),
  146. })
  147. }
  148. }
  149. }
  150. return result, nil
  151. }
  152. // newTaskID 生成固定格式的 TaskID,方便追踪
  153. func newTaskID(taskType string, id string) string {
  154. return fmt.Sprintf("%s:%s", taskType, id)
  155. }