dataDownloadQueue.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. package dataDownloadQueue
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "time"
  8. "fwh-toolbox-server/domain"
  9. "fwh-toolbox-server/jobs"
  10. minioRepo "fwh-toolbox-server/repositories/minio"
  11. mongoRepo "fwh-toolbox-server/repositories/mongo"
  12. "fwh-toolbox-server/services"
  13. task_core "fwh-toolbox-server/task/core"
  14. "github.com/hibiken/asynq"
  15. "go.mongodb.org/mongo-driver/bson"
  16. "go.mongodb.org/mongo-driver/bson/primitive"
  17. "go.uber.org/zap"
  18. )
  19. const (
  20. TaskTypePlan = "data-download:plan"
  21. TaskTypeItem = "data-download:item"
  22. TaskTypePackage = "data-download:package"
  23. QueuePlan = "data-download:plan"
  24. QueueItem = "data-download:item"
  25. )
  26. var client *asynq.Client
  27. func InitClient(redisOpt asynq.RedisClientOpt) {
  28. if client == nil {
  29. client = asynq.NewClient(redisOpt)
  30. }
  31. }
  32. type planPayload struct {
  33. TaskID string `json:"taskId"`
  34. }
  35. type itemPayload struct {
  36. ItemID string `json:"itemId"`
  37. AwaitRequestID string `json:"awaitRequestId,omitempty"`
  38. DeferredCount int `json:"deferredCount,omitempty"`
  39. }
  40. type packagePayload struct {
  41. JobID string `json:"jobId"`
  42. }
  43. func Init(tc *task_core.TaskCore) {
  44. mux := asynq.NewServeMux()
  45. mux.HandleFunc(TaskTypePlan, handlePlan)
  46. mux.HandleFunc(TaskTypeItem, handleItem)
  47. mux.HandleFunc(TaskTypePackage, handlePackage)
  48. client = tc.CreateTask(task_core.TaskConfig{
  49. Name: "DataDownload",
  50. SvrCfg: asynq.Config{Concurrency: 8, Queues: map[string]int{QueueItem: 6, QueuePlan: 2}, ErrorHandler: asynq.ErrorHandlerFunc(func(_ context.Context, task *asynq.Task, err error) {
  51. zap.L().Error("data-download task failed", zap.String("type", task.Type()), zap.String("err", err.Error()))
  52. })},
  53. Mux: mux,
  54. })
  55. }
  56. func EnqueuePackage(jobID primitive.ObjectID) error {
  57. payload, _ := json.Marshal(packagePayload{JobID: jobID.Hex()})
  58. _, err := client.Enqueue(asynq.NewTask(TaskTypePackage, payload, asynq.Queue(QueueItem), asynq.MaxRetry(1), asynq.Timeout(2*time.Hour), asynq.TaskID("dd-package-"+jobID.Hex()), asynq.Retention(30*24*time.Hour)))
  59. return err
  60. }
  61. func handlePackage(ctx context.Context, task *asynq.Task) error {
  62. var payload packagePayload
  63. if err := json.Unmarshal(task.Payload(), &payload); err != nil {
  64. return err
  65. }
  66. id, err := primitive.ObjectIDFromHex(payload.JobID)
  67. if err != nil {
  68. return err
  69. }
  70. job, err := mongoRepo.GetDataDownloadPackageJob(ctx, id)
  71. if err != nil {
  72. return err
  73. }
  74. if job.Status == domain.DataDownloadCancelled || job.Status == domain.DataDownloadCancelling {
  75. if job.Status == domain.DataDownloadCancelling {
  76. _ = mongoRepo.UpdateDataDownloadPackageJob(ctx, id, bson.M{"status": domain.DataDownloadCancelled})
  77. }
  78. return nil
  79. }
  80. now := time.Now().UTC()
  81. _ = mongoRepo.UpdateDataDownloadPackageJob(ctx, id, bson.M{"status": domain.DataDownloadRunning, "startedAt": now})
  82. zap.L().Info("[DataDownload] 打包开始",
  83. zap.String("jobId", id.Hex()),
  84. zap.Int("taskCount", len(job.TaskIDs)),
  85. zap.String("filename", job.Filename),
  86. )
  87. data, count, err := services.BuildDataDownloadPackage(ctx, job, func(done, total int64) error {
  88. current, getErr := mongoRepo.GetDataDownloadPackageJob(ctx, id)
  89. if getErr != nil {
  90. return getErr
  91. }
  92. if current.Status == domain.DataDownloadCancelling || current.Status == domain.DataDownloadCancelled {
  93. return context.Canceled
  94. }
  95. return mongoRepo.UpdateDataDownloadPackageJob(ctx, id, bson.M{"doneCount": done, "totalCount": total})
  96. })
  97. if err != nil {
  98. status, reason := domain.DataDownloadFailed, err.Error()
  99. if err == context.Canceled {
  100. status, reason = domain.DataDownloadCancelled, ""
  101. }
  102. zap.L().Error("[DataDownload] 打包失败", zap.String("jobId", id.Hex()), zap.String("status", string(status)), zap.String("err", err.Error()))
  103. _ = mongoRepo.UpdateDataDownloadPackageJob(ctx, id, bson.M{"status": status, "failureReason": reason})
  104. return nil
  105. }
  106. objectKey, err := minioRepo.PutPackage(ctx, job.Filename, data)
  107. if err != nil {
  108. zap.L().Error("[DataDownload] 打包上传 MinIO 失败", zap.String("jobId", id.Hex()), zap.String("err", err.Error()))
  109. _ = mongoRepo.UpdateDataDownloadPackageJob(ctx, id, bson.M{"status": domain.DataDownloadFailed, "failureReason": err.Error()})
  110. return nil
  111. }
  112. completed := time.Now().UTC()
  113. expires := completed.Add(7 * 24 * time.Hour)
  114. purge := expires.Add(30 * 24 * time.Hour)
  115. zap.L().Info("[DataDownload] 打包完成",
  116. zap.String("jobId", id.Hex()),
  117. zap.Int64("fileCount", count),
  118. zap.Int("bytes", len(data)),
  119. zap.String("objectKey", objectKey),
  120. )
  121. return mongoRepo.UpdateDataDownloadPackageJob(ctx, id, bson.M{"status": domain.DataDownloadSucceeded, "objectKey": objectKey, "size": len(data), "doneCount": count, "totalCount": count, "completedAt": completed, "expiresAt": expires, "purgeAt": purge, "failureReason": ""})
  122. }
  123. func EnqueuePlan(taskID primitive.ObjectID) error {
  124. if client == nil {
  125. return fmt.Errorf("data-download queue is not initialized")
  126. }
  127. payload, _ := json.Marshal(planPayload{TaskID: taskID.Hex()})
  128. _, err := client.Enqueue(asynq.NewTask(TaskTypePlan, payload, asynq.Queue(QueuePlan), asynq.MaxRetry(3), asynq.Timeout(15*time.Minute), asynq.TaskID("dd-plan-"+taskID.Hex()), asynq.Retention(30*24*time.Hour)))
  129. if err != nil {
  130. zap.L().Error("[DataDownload] plan 入队失败", zap.String("taskId", taskID.Hex()), zap.String("err", err.Error()))
  131. } else {
  132. zap.L().Info("[DataDownload] plan 已入队", zap.String("taskId", taskID.Hex()))
  133. }
  134. return err
  135. }
  136. func enqueueItem(itemID primitive.ObjectID) error {
  137. payload, _ := json.Marshal(itemPayload{ItemID: itemID.Hex()})
  138. _, err := client.Enqueue(asynq.NewTask(TaskTypeItem, payload, asynq.Queue(QueueItem), asynq.MaxRetry(3), asynq.Timeout(40*time.Minute), asynq.TaskID("dd-item-"+itemID.Hex()), asynq.Retention(30*24*time.Hour)))
  139. if errors.Is(err, asynq.ErrTaskIDConflict) {
  140. return nil
  141. }
  142. return err
  143. }
  144. func enqueueDeferredItem(itemID primitive.ObjectID, requestID string, deferredCount int, after time.Duration) error {
  145. payload, _ := json.Marshal(itemPayload{ItemID: itemID.Hex(), AwaitRequestID: requestID, DeferredCount: deferredCount})
  146. _, err := client.Enqueue(asynq.NewTask(TaskTypeItem, payload,
  147. asynq.Queue(QueueItem),
  148. asynq.MaxRetry(3),
  149. asynq.Timeout(40*time.Minute),
  150. asynq.ProcessIn(after),
  151. asynq.TaskID(fmt.Sprintf("dd-item-wait-%s-%d", itemID.Hex(), time.Now().UnixNano())),
  152. asynq.Retention(30*24*time.Hour)))
  153. return err
  154. }
  155. func handlePlan(ctx context.Context, job *asynq.Task) error {
  156. var payload planPayload
  157. if err := json.Unmarshal(job.Payload(), &payload); err != nil {
  158. return err
  159. }
  160. id, err := primitive.ObjectIDFromHex(payload.TaskID)
  161. if err != nil {
  162. return err
  163. }
  164. task, err := mongoRepo.GetDataDownloadTask(ctx, id)
  165. if err != nil {
  166. return err
  167. }
  168. zap.L().Info("[DataDownload] plan 开始",
  169. zap.String("taskId", id.Hex()),
  170. zap.Int("snCount", len(task.SNs)),
  171. zap.String("status", string(task.Status)),
  172. )
  173. if task.Status == domain.DataDownloadCancelled || task.Status == domain.DataDownloadCancelling {
  174. if task.Status == domain.DataDownloadCancelling {
  175. _ = mongoRepo.UpdateDataDownloadTaskStatus(ctx, id, domain.DataDownloadCancelled)
  176. }
  177. zap.L().Info("[DataDownload] plan 因任务取消而终止", zap.String("taskId", id.Hex()))
  178. return nil
  179. }
  180. if err := mongoRepo.UpdateDataDownloadTaskStatus(ctx, id, domain.DataDownloadRunning); err != nil {
  181. return err
  182. }
  183. totalItems := 0
  184. // Plan and enqueue one site at a time so an unbounded SN list never expands
  185. // into one large in-memory slice or MongoDB document.
  186. for _, sn := range task.SNs {
  187. latest, getErr := mongoRepo.GetDataDownloadTask(ctx, id)
  188. if getErr != nil {
  189. return getErr
  190. }
  191. if latest.Status == domain.DataDownloadCancelling || latest.Status == domain.DataDownloadCancelled {
  192. _ = mongoRepo.UpdateDataDownloadTaskStatus(ctx, id, domain.DataDownloadCancelled)
  193. break
  194. }
  195. siteTask := *task
  196. siteTask.SNs = []string{sn}
  197. items, planErr := services.PlanDataDownloadTask(ctx, &siteTask)
  198. if planErr != nil {
  199. zap.L().Error("[DataDownload] plan 站点规划失败",
  200. zap.String("taskId", id.Hex()),
  201. zap.String("sn", sn),
  202. zap.String("err", planErr.Error()),
  203. )
  204. _ = mongoRepo.UpdateDataDownloadTaskStatus(ctx, id, domain.DataDownloadError)
  205. return planErr
  206. }
  207. if err := mongoRepo.InsertDataDownloadItems(ctx, items); err != nil {
  208. return err
  209. }
  210. totalItems += len(items)
  211. zap.L().Debug("[DataDownload] plan 站点拆分完成",
  212. zap.String("taskId", id.Hex()),
  213. zap.String("sn", sn),
  214. zap.Int("itemCount", len(items)),
  215. )
  216. for i := range items {
  217. if items[i].Status == domain.DataDownloadFailed || items[i].Status == domain.DataDownloadSkipped {
  218. continue
  219. }
  220. if err := enqueueItem(items[i].ID); err != nil {
  221. zap.L().Error("[DataDownload] item 入队失败",
  222. zap.String("taskId", id.Hex()),
  223. zap.String("itemId", items[i].ID.Hex()),
  224. zap.String("kind", string(items[i].Kind)),
  225. zap.String("err", err.Error()),
  226. )
  227. _ = mongoRepo.UpdateDataDownloadItem(ctx, items[i].ID, domain.DataDownloadFailed, "enqueue", err.Error())
  228. }
  229. }
  230. _ = mongoRepo.RecountDataDownloadTask(ctx, id)
  231. }
  232. if totalItems == 0 {
  233. latest, getErr := mongoRepo.GetDataDownloadTask(ctx, id)
  234. if getErr == nil && latest.Status == domain.DataDownloadCancelled {
  235. return nil
  236. }
  237. zap.L().Info("[DataDownload] plan 完成,无有效 item,任务直接置为成功", zap.String("taskId", id.Hex()))
  238. _ = mongoRepo.UpdateDataDownloadTaskStatus(ctx, id, domain.DataDownloadSucceeded)
  239. return nil
  240. }
  241. zap.L().Info("[DataDownload] plan 完成,item 已全部入队",
  242. zap.String("taskId", id.Hex()),
  243. zap.Int("totalItems", totalItems),
  244. )
  245. return aggregate(ctx, id)
  246. }
  247. func EnqueueItem(itemID primitive.ObjectID) error {
  248. payload, _ := json.Marshal(itemPayload{ItemID: itemID.Hex()})
  249. _, err := client.Enqueue(asynq.NewTask(TaskTypeItem, payload, asynq.Queue(QueueItem), asynq.MaxRetry(3), asynq.Timeout(40*time.Minute), asynq.TaskID(fmt.Sprintf("dd-item-%s-%d", itemID.Hex(), time.Now().UnixNano())), asynq.Retention(30*24*time.Hour)))
  250. return err
  251. }
  252. func handleItem(ctx context.Context, job *asynq.Task) error {
  253. var payload itemPayload
  254. if err := json.Unmarshal(job.Payload(), &payload); err != nil {
  255. return err
  256. }
  257. id, err := primitive.ObjectIDFromHex(payload.ItemID)
  258. if err != nil {
  259. return err
  260. }
  261. item, err := mongoRepo.GetDataDownloadItem(ctx, id)
  262. if err != nil {
  263. return err
  264. }
  265. task, err := mongoRepo.GetDataDownloadTask(ctx, item.TaskID)
  266. if err != nil {
  267. return err
  268. }
  269. retried, _ := asynq.GetRetryCount(ctx)
  270. zap.L().Debug("[DataDownload] item 开始处理",
  271. zap.String("itemId", id.Hex()),
  272. zap.String("taskId", item.TaskID.Hex()),
  273. zap.String("kind", string(item.Kind)),
  274. zap.String("sn", item.SiteSN),
  275. zap.String("subtype", item.Subtype),
  276. zap.String("date", item.Date),
  277. zap.Int("attempt", retried),
  278. )
  279. if task.Status == domain.DataDownloadCancelled || task.Status == domain.DataDownloadCancelling {
  280. zap.L().Info("[DataDownload] item 因任务取消而跳过", zap.String("itemId", id.Hex()), zap.String("taskId", item.TaskID.Hex()))
  281. _ = services.ReleaseFaultCallOwnership(context.Background(), item)
  282. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadCancelled, "cancelled", "")
  283. return aggregate(ctx, item.TaskID)
  284. }
  285. if item.Kind == "site_validation" {
  286. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadRunning, "validation", "")
  287. retryTask := *task
  288. retryTask.SNs = []string{item.SiteSN}
  289. planned, planErr := services.PlanDataDownloadTask(ctx, &retryTask)
  290. if planErr != nil || (len(planned) == 1 && planned[0].Kind == "site_validation") {
  291. reason := "site does not exist"
  292. if planErr != nil {
  293. reason = planErr.Error()
  294. } else if len(planned) == 1 {
  295. reason = planned[0].FailureReason
  296. }
  297. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadFailed, "validation", reason)
  298. return aggregate(ctx, item.TaskID)
  299. }
  300. if err := mongoRepo.InsertDataDownloadItems(ctx, planned); err != nil {
  301. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadFailed, "validation", err.Error())
  302. return aggregate(ctx, item.TaskID)
  303. }
  304. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadItemSucceeded, "validation", "")
  305. for i := range planned {
  306. if err := enqueueItem(planned[i].ID); err != nil {
  307. _ = mongoRepo.UpdateDataDownloadItem(ctx, planned[i].ID, domain.DataDownloadFailed, "enqueue", err.Error())
  308. }
  309. }
  310. return aggregate(ctx, item.TaskID)
  311. }
  312. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadRunning, "fetch", "")
  313. err = services.ExecuteStructuredDataDownloadItem(ctx, item, payload.AwaitRequestID)
  314. if err != nil {
  315. var faultCallDeferred *services.DataDownloadFaultCallDeferred
  316. if errors.As(err, &faultCallDeferred) {
  317. _ = mongoRepo.DeferDataDownloadItem(ctx, id)
  318. if enqueueErr := enqueueDeferredItem(id, payload.AwaitRequestID, payload.DeferredCount+1, faultCallDeferred.After); enqueueErr != nil {
  319. _ = services.ReleaseFaultCallOwnership(context.Background(), item)
  320. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadFailed, "enqueue", enqueueErr.Error())
  321. }
  322. return aggregate(ctx, item.TaskID)
  323. }
  324. var snDeferred *services.DataDownloadSNDeferred
  325. if errors.As(err, &snDeferred) {
  326. delay := snDeferred.After
  327. zap.L().Debug("[DataDownload] EMS 召唤名额被占用,延迟处理",
  328. zap.String("itemId", id.Hex()), zap.String("sn", item.SiteSN), zap.Duration("delay", delay))
  329. _ = mongoRepo.DeferDataDownloadItem(ctx, id)
  330. if enqueueErr := enqueueDeferredItem(id, payload.AwaitRequestID, payload.DeferredCount+1, delay); enqueueErr != nil {
  331. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadFailed, "enqueue", enqueueErr.Error())
  332. }
  333. return aggregate(ctx, item.TaskID)
  334. }
  335. var deferred *services.DataDownloadRequestDeferred
  336. if errors.As(err, &deferred) {
  337. delay := deferred.After
  338. switch payload.DeferredCount {
  339. case 1:
  340. delay = 30 * time.Second
  341. case 2:
  342. delay = time.Minute
  343. default:
  344. if payload.DeferredCount > 2 {
  345. delay = time.Minute
  346. }
  347. }
  348. zap.L().Debug("[DataDownload] item 等待请求租约,延迟重入队",
  349. zap.String("itemId", id.Hex()),
  350. zap.String("requestId", deferred.RequestID),
  351. zap.Int("deferredCount", payload.DeferredCount+1),
  352. zap.Duration("delay", delay),
  353. )
  354. _ = mongoRepo.DeferDataDownloadItem(ctx, id)
  355. if enqueueErr := enqueueDeferredItem(id, deferred.RequestID, payload.DeferredCount+1, delay); enqueueErr != nil {
  356. zap.L().Error("[DataDownload] item 延迟重入队失败", zap.String("itemId", id.Hex()), zap.String("err", enqueueErr.Error()))
  357. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadFailed, "enqueue", enqueueErr.Error())
  358. return aggregate(ctx, item.TaskID)
  359. }
  360. return aggregate(ctx, item.TaskID)
  361. }
  362. maxRetry, _ := asynq.GetMaxRetry(ctx)
  363. if shouldRetryItem(err, retried, maxRetry) {
  364. zap.L().Warn("[DataDownload] item 执行失败,将重试",
  365. zap.String("itemId", id.Hex()),
  366. zap.String("kind", string(item.Kind)),
  367. zap.Int("attempt", retried),
  368. zap.Int("maxRetry", maxRetry),
  369. zap.String("err", err.Error()),
  370. )
  371. return err
  372. }
  373. var partial *services.DataDownloadPartialFailure
  374. if errors.As(err, &partial) && partial.Succeeded > 0 {
  375. zap.L().Warn("[DataDownload] item 部分成功(重试耗尽)",
  376. zap.String("itemId", id.Hex()),
  377. zap.String("kind", string(item.Kind)),
  378. zap.Int("succeeded", partial.Succeeded),
  379. zap.String("err", err.Error()),
  380. )
  381. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadPartial, "fetch", err.Error())
  382. } else {
  383. zap.L().Error("[DataDownload] item 最终失败(重试耗尽)",
  384. zap.String("itemId", id.Hex()),
  385. zap.String("taskId", item.TaskID.Hex()),
  386. zap.String("kind", string(item.Kind)),
  387. zap.String("sn", item.SiteSN),
  388. zap.String("err", err.Error()),
  389. )
  390. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadFailed, "fetch", err.Error())
  391. }
  392. _ = services.ReleaseFaultCallOwnership(context.Background(), item)
  393. } else {
  394. latest, getErr := mongoRepo.GetDataDownloadTask(ctx, item.TaskID)
  395. if getErr == nil && (latest.Status == domain.DataDownloadCancelling || latest.Status == domain.DataDownloadCancelled) {
  396. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadCancelled, "cancelled", "")
  397. } else {
  398. zap.L().Info("[DataDownload] item 执行成功",
  399. zap.String("itemId", id.Hex()),
  400. zap.String("taskId", item.TaskID.Hex()),
  401. zap.String("kind", string(item.Kind)),
  402. zap.String("sn", item.SiteSN),
  403. )
  404. _ = mongoRepo.UpdateDataDownloadItem(ctx, id, domain.DataDownloadItemSucceeded, "complete", "")
  405. }
  406. }
  407. return aggregate(ctx, item.TaskID)
  408. }
  409. func shouldRetryItem(err error, retried, maxRetry int) bool {
  410. return retried < maxRetry && !errors.Is(err, jobs.ErrEmsLogCallTimeout) && !errors.Is(err, jobs.ErrEmsLogCallSlotLost) && !errors.Is(err, services.ErrFaultRecordCallTimeout) && !errors.Is(err, services.ErrFaultRecordCallFailed)
  411. }
  412. func aggregate(ctx context.Context, taskID primitive.ObjectID) error {
  413. task, taskErr := mongoRepo.GetDataDownloadTask(ctx, taskID)
  414. if taskErr != nil {
  415. return taskErr
  416. }
  417. items, err := mongoRepo.ListDataDownloadItems(ctx, taskID)
  418. if err != nil {
  419. return err
  420. }
  421. var succeeded, partial, failed, cancelled int
  422. for _, item := range items {
  423. switch item.Status {
  424. case domain.DataDownloadItemSucceeded, domain.DataDownloadSkipped:
  425. succeeded++
  426. case domain.DataDownloadPartial:
  427. partial++
  428. case domain.DataDownloadFailed:
  429. failed++
  430. case domain.DataDownloadCancelled:
  431. cancelled++
  432. default:
  433. _ = mongoRepo.RecountDataDownloadTask(ctx, taskID)
  434. return nil
  435. }
  436. }
  437. status := domain.DataDownloadSucceeded
  438. if task.Status == domain.DataDownloadCancelling || task.Status == domain.DataDownloadCancelled {
  439. status = domain.DataDownloadCancelled
  440. } else if cancelled == len(items) {
  441. status = domain.DataDownloadCancelled
  442. } else if failed == len(items) {
  443. status = domain.DataDownloadFailed
  444. } else if partial > 0 || failed > 0 || cancelled > 0 {
  445. status = domain.DataDownloadPartial
  446. }
  447. if err := mongoRepo.RecountDataDownloadTask(ctx, taskID); err != nil {
  448. return err
  449. }
  450. zap.L().Info("[DataDownload] 任务完成",
  451. zap.String("taskId", taskID.Hex()),
  452. zap.String("status", string(status)),
  453. zap.Int("total", len(items)),
  454. zap.Int("succeeded", succeeded),
  455. zap.Int("partial", partial),
  456. zap.Int("failed", failed),
  457. zap.Int("cancelled", cancelled),
  458. )
  459. return mongoRepo.UpdateDataDownloadTaskStatus(ctx, taskID, status)
  460. }