taskQueue.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package taskQueue
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "fwh-toolbox-server/cache"
  7. "fwh-toolbox-server/domain"
  8. "fwh-toolbox-server/logger"
  9. "time"
  10. "github.com/redis/go-redis/v9"
  11. )
  12. var (
  13. ctx = context.Background()
  14. streamName = "fwhtoolbox_stream"
  15. groupName = "task_queue"
  16. consumerID = "worker-taskQueue"
  17. )
  18. type TaskStreamInfo struct {
  19. Ctx context.Context
  20. StreamName string
  21. GroupName string
  22. ID string
  23. }
  24. type TaskPayloadBase struct {
  25. Type string `json:"type"`
  26. }
  27. type TaskPayload[T any] struct {
  28. TaskPayloadBase
  29. Payload T `json:"payload"`
  30. }
  31. func InitTaskQueue() {
  32. // 确保消费组存在(忽略已存在错误)
  33. _ = cache.XGroupCreate(ctx, streamName, groupName, "$")
  34. // 启动后台任务队列(不会阻塞主线程)
  35. go startTaskQueueWorker()
  36. }
  37. // 任务消费者(单 worker,顺序执行)
  38. func startTaskQueueWorker() {
  39. streamsMap := make(map[string]string)
  40. streamsMap[streamName] = ">"
  41. for {
  42. res, err := cache.XReadGroup(ctx, groupName, consumerID, streamsMap, 1) // 等待任务
  43. if err != nil {
  44. if err == redis.Nil {
  45. continue
  46. }
  47. logger.Std.Println("XReadGroup error:", err)
  48. time.Sleep(time.Second)
  49. continue
  50. }
  51. for _, stream := range res {
  52. for _, message := range stream.Messages {
  53. id := message.ID
  54. rawPayload := message.Values["payload"].(string)
  55. // 先反序列化成 map 确定 type
  56. var base TaskPayloadBase
  57. if err := json.Unmarshal([]byte(rawPayload), &base); err != nil {
  58. ackTask(TaskStreamInfo{
  59. Ctx: ctx,
  60. StreamName: streamName,
  61. GroupName: groupName,
  62. ID: id,
  63. }, false, fmt.Errorf("unmarshal base error - %s", err.Error()))
  64. continue
  65. }
  66. switch base.Type {
  67. case TaskTypeSiteAnalysis: // 站点分析任务
  68. var payload TaskPayload[domain.TaskSiteAnalysis]
  69. if err := json.Unmarshal([]byte(rawPayload), &payload); err != nil {
  70. ackTask(TaskStreamInfo{
  71. Ctx: ctx,
  72. StreamName: streamName,
  73. GroupName: groupName,
  74. ID: id,
  75. }, false, fmt.Errorf("unmarshal payload error - %s", err.Error()))
  76. continue
  77. }
  78. logger.Std.Printf("Processing task %s: %+v\n", id, payload)
  79. if err := handleTaskSiteAnalysis(ctx, payload.Payload); err != nil {
  80. ackTask(TaskStreamInfo{
  81. Ctx: ctx,
  82. StreamName: streamName,
  83. GroupName: groupName,
  84. ID: id,
  85. }, false, fmt.Errorf("handle task SiteAnalysis error - %s", err.Error()))
  86. continue
  87. }
  88. case TaskTypeShutdownStatistics: // Shutdown统计任务
  89. var payload TaskPayload[domain.TaskShutdownStatistics]
  90. if err := json.Unmarshal([]byte(rawPayload), &payload); err != nil {
  91. ackTask(TaskStreamInfo{
  92. Ctx: ctx,
  93. StreamName: streamName,
  94. GroupName: groupName,
  95. ID: id,
  96. }, false, fmt.Errorf("unmarshal payload error - %s", err.Error()))
  97. continue
  98. }
  99. logger.Std.Printf("Processing task %s: %+v\n", id, payload)
  100. if err := handleTaskAlarmStatistics(ctx, payload.Payload); err != nil {
  101. ackTask(TaskStreamInfo{
  102. Ctx: ctx,
  103. StreamName: streamName,
  104. GroupName: groupName,
  105. ID: id,
  106. }, false, fmt.Errorf("handle task SiteAnalysis error - %s", err.Error()))
  107. continue
  108. }
  109. default:
  110. logger.Std.Println("Unknown task type:", base.Type)
  111. }
  112. // 任务完成
  113. ackTask(TaskStreamInfo{
  114. Ctx: ctx,
  115. StreamName: streamName,
  116. GroupName: groupName,
  117. ID: id,
  118. }, true, nil)
  119. }
  120. }
  121. }
  122. }
  123. func ackTask(streamInfo TaskStreamInfo, success bool, err error) {
  124. if success {
  125. // 确认任务完成
  126. if err := cache.XAck(streamInfo.Ctx, streamInfo.StreamName, streamInfo.GroupName, streamInfo.ID); err != nil {
  127. logger.Std.Println("XAck error:", err)
  128. }
  129. } else {
  130. // 任务失败
  131. logger.Std.Println("Task queue execution failed, error:", err)
  132. }
  133. }
  134. func EnqueueTask[T any](payload TaskPayload[T]) error {
  135. data, err := json.Marshal(payload)
  136. if err != nil {
  137. return err
  138. }
  139. if _, err := cache.XAdd(ctx, streamName, map[string]interface{}{"payload": string(data)}); err != nil {
  140. return err
  141. }
  142. return nil
  143. }