taskQueue.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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. default:
  89. logger.Std.Println("Unknown task type:", base.Type)
  90. }
  91. // 任务完成
  92. ackTask(TaskStreamInfo{
  93. Ctx: ctx,
  94. StreamName: streamName,
  95. GroupName: groupName,
  96. ID: id,
  97. }, true, nil)
  98. }
  99. }
  100. }
  101. }
  102. func ackTask(streamInfo TaskStreamInfo, success bool, err error) {
  103. if success {
  104. // 确认任务完成
  105. if err := cache.XAck(streamInfo.Ctx, streamInfo.StreamName, streamInfo.GroupName, streamInfo.ID); err != nil {
  106. logger.Std.Println("XAck error:", err)
  107. }
  108. } else {
  109. // 任务失败
  110. logger.Std.Println("Task queue execution failed, error:", err)
  111. }
  112. }
  113. func EnqueueTask[T any](payload TaskPayload[T]) error {
  114. data, err := json.Marshal(payload)
  115. if err != nil {
  116. return err
  117. }
  118. if _, err := cache.XAdd(ctx, streamName, map[string]interface{}{"payload": string(data)}); err != nil {
  119. return err
  120. }
  121. return nil
  122. }