taskQueue.go 3.5 KB

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