fwhTaskSiteInfoUpdate.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. package fwhQueue
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "fwh-toolbox-server/domain"
  8. "fwh-toolbox-server/dto"
  9. "fwh-toolbox-server/repositories"
  10. "time"
  11. "github.com/hibiken/asynq"
  12. "go.mongodb.org/mongo-driver/bson/primitive"
  13. "go.uber.org/zap"
  14. )
  15. // handleSiteInfoUpdate 是站点信息更新任务的 Asynq handler
  16. func handleSiteInfoUpdate(ctx context.Context, task *asynq.Task) error {
  17. var payload domain.TaskSiteInfoUpdate
  18. if err := json.Unmarshal(task.Payload(), &payload); err != nil {
  19. return fmt.Errorf("unmarshal SiteInfoUpdate payload: %w", err)
  20. }
  21. taskId := payload.Id.Hex()
  22. // 更新运行状态
  23. if err := repositories.UpdateSiteInfoUpdateResult(payload.Id, domain.ProgressRunning, 0); err != nil {
  24. zap.L().Error("[SiteInfoUpdate] 更新状态失败(Running)", zap.String("id", taskId))
  25. return err
  26. }
  27. // 执行任务
  28. total, err := executeSiteInfoUpdate(ctx, payload)
  29. if errors.Is(err, context.Canceled) {
  30. // CancelSiteInfoUpdate 发出的取消(asynq 超时为 DeadlineExceeded,按失败处理)
  31. zap.L().Info("[SiteInfoUpdate] 任务已取消", zap.String("id", taskId))
  32. repositories.UpdateSiteInfoUpdateResult(payload.Id, domain.ProgressCancelled, 0)
  33. return err
  34. }
  35. if err != nil {
  36. zap.L().Error("[SiteInfoUpdate] 任务执行失败", zap.String("id", taskId), zap.String("err", err.Error()))
  37. repositories.UpdateSiteInfoUpdateResult(payload.Id, domain.ProgressError, 0)
  38. return nil // 业务错误不重试
  39. }
  40. zap.L().Info("[SiteInfoUpdate] 任务执行成功", zap.String("id", taskId), zap.Uint64("total", total))
  41. repositories.UpdateSiteInfoUpdateResult(payload.Id, domain.ProgressComplete, total)
  42. return nil
  43. }
  44. // executeSiteInfoUpdate 执行站点信息更新
  45. func executeSiteInfoUpdate(ctx context.Context, payload domain.TaskSiteInfoUpdate) (uint64, error) {
  46. var pageSize uint = 50
  47. var pageNum uint = 1
  48. var total uint64 = 0
  49. var realTotal uint64 = 0
  50. for {
  51. // 每页检查一次:取消(CancelSiteInfoUpdate)或超时(SiteInfoUpdateTimeout)时停止
  52. if err := ctx.Err(); err != nil {
  53. return 0, err
  54. }
  55. resp, err := repositories.QuerySiteInfoList(dto.SiteInfoPara{
  56. PageSize: pageSize,
  57. PageNum: pageNum,
  58. })
  59. if err != nil {
  60. return 0, fmt.Errorf("查询站点信息失败(page=%d): %w", pageNum, err)
  61. }
  62. for _, info := range *resp.Result {
  63. if err := repositories.InsertSiteInfoData(*domain.FwhSiteInfoToSiteInfoDomain(&info)); err != nil {
  64. return 0, fmt.Errorf("保存站点信息失败(page=%d): %w", pageNum, err)
  65. }
  66. realTotal++
  67. }
  68. pageNum++
  69. total = resp.Total
  70. if (int64(pageNum)-1)*int64(pageSize) >= int64(total) {
  71. break
  72. }
  73. }
  74. return realTotal, nil
  75. }
  76. // SiteInfoUpdateTimeout 站点信息同步任务的执行超时
  77. const SiteInfoUpdateTimeout = 4 * time.Hour
  78. // CancelSiteInfoUpdate 取消一次站点信息同步:
  79. // 正在执行的通知 worker 停止(worker 停下后把记录标记为 cancelled);还在排队或已不在队列中的直接删除并标记。
  80. // 可在主服务或只入队的独立进程(fwh-agent-mcp)中调用,需要 inspector 已初始化。
  81. func CancelSiteInfoUpdate(id primitive.ObjectID) error {
  82. if inspector == nil {
  83. return fmt.Errorf("任务队列未初始化")
  84. }
  85. taskId := newTaskID(TaskTypeSiteInfoUpdate, id.Hex())
  86. info, err := inspector.GetTaskInfo(QueueMain, taskId)
  87. switch {
  88. case errors.Is(err, asynq.ErrTaskNotFound) || errors.Is(err, asynq.ErrQueueNotFound):
  89. // 队列中已没有这个任务(如主服务重启时丢失):只更新记录
  90. case err != nil:
  91. return err
  92. case info.State == asynq.TaskStateActive:
  93. return inspector.CancelProcessing(taskId)
  94. case info.State == asynq.TaskStateCompleted || info.State == asynq.TaskStateArchived:
  95. // 任务已经结束,worker 已写入最终状态,不覆盖
  96. return nil
  97. case info.State == asynq.TaskStatePending || info.State == asynq.TaskStateScheduled || info.State == asynq.TaskStateRetry:
  98. if err := inspector.DeleteTask(QueueMain, taskId); err != nil {
  99. return err
  100. }
  101. }
  102. return repositories.UpdateSiteInfoUpdateResult(id, domain.ProgressCancelled, 0)
  103. }