[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fqueue":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fqueue":2346,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fqueue":2372},{"source":4,"connector":5,"page":6},"sqlite","better-sqlite3",{"path":7,"title":8,"description":9,"category":10,"tags":11,"date":14,"pinned":15,"draft":15,"body":16,"seo":2342,"stem":2343,"id":2344,"extension":2345},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fqueue","队列（Bull）","异步任务可用 BullMQ（@nestjs\u002Fbullmq）等方案，需 Redis。","技能小册",[12,13],"nestjs","queue","2021-07-01",false,{"type":17,"value":18,"toc":2333},"minimark",[19,23,37,63,77,82,89,531,545,549,560,1082,1098,1140,1165,1169,1179,1184,1191,1473,1477,1483,1650,1654,1660,1746,1765,1769,1772,2111,2125,2144,2148,2206,2210,2267,2271,2274,2329],[20,21,8],"h1",{"id":22},"队列bull",[24,25,26,27,31,32,36],"p",{},"异步任务可用 ",[28,29,30],"strong",{},"BullMQ","（",[33,34,35],"code",{},"@nestjs\u002Fbullmq","）等方案，需 Redis。",[24,38,39,40,31,42,44,45,50,51,62],{},"官方推荐使用 ",[28,41,30],{},[33,43,35],{},"）处理基于 Redis 的异步任务。下文示例使用较早的 ",[28,46,47],{},[33,48,49],{},"@nestjs\u002Fbull","（Bull 3.x），模块名与 API 与 BullMQ 包不同；",[28,52,53,54,61],{},"新项目请直接按 ",[55,56,60],"a",{"href":57,"rel":58},"https:\u002F\u002Fdocs.nestjs.com\u002Ftechniques\u002Fqueues",[59],"nofollow","官方 Queues 文档"," 使用 BullMQ","。",[24,64,65,66,69,70,69,73,76],{},"核心模型：",[28,67,68],{},"Queue","、",[28,71,72],{},"Producer",[28,74,75],{},"Consumer","（Worker）。",[78,79,81],"h3",{"id":80},"模块注册与配置nestjsbull-示例","模块注册与配置（@nestjs\u002Fbull 示例）",[24,83,84,85,88],{},"使用前注册 ",[33,86,87],{},"BullModule","（Bull 3.x）：",[90,91,96],"pre",{"className":92,"code":93,"language":94,"meta":95,"style":95},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark","\u002F\u002F app.module.ts\nimport { Module } from \"@nestjs\u002Fcommon\";\nimport { BullModule } from \"@nestjs\u002Fbull\";\n\n@Module({\n  imports: [\n    BullModule.forRootAsync({\n      useFactory: (configService: ConfigService) => ({\n        redis: {\n          host: configService.get(\"REDIS_HOST\", \"localhost\"),\n          port: configService.get\u003Cnumber>(\"REDIS_PORT\", 6379),\n        },\n        prefix: \"my-queue\", \u002F\u002F 自定义队列键名前缀\n        defaultJobOptions: {\n          \u002F\u002F 全局默认作业选项\n          attempts: 3, \u002F\u002F 失败时重试3次\n          removeOnComplete: true, \u002F\u002F 完成后自动移除\n        },\n      }),\n      inject: [ConfigService],\n    }),\n    BullModule.registerQueueAsync({\n      name: \"email\", \u002F\u002F 队列名称，用于后续注入\n    }),\n  ],\n})\nexport class AppModule {}\n","typescript","",[33,97,98,107,142,164,171,187,200,216,249,260,300,343,349,369,379,385,401,418,423,433,446,456,470,490,499,507,516],{"__ignoreMap":95},[99,100,103],"span",{"class":101,"line":102},"line",1,[99,104,106],{"class":105},"sutJx","\u002F\u002F app.module.ts\n",[99,108,110,114,118,122,125,128,132,136,139],{"class":101,"line":109},2,[99,111,113],{"class":112},"sVHd0","import",[99,115,117],{"class":116},"sP7_E"," {",[99,119,121],{"class":120},"su5hD"," Module",[99,123,124],{"class":116}," }",[99,126,127],{"class":112}," from",[99,129,131],{"class":130},"sjJ54"," \"",[99,133,135],{"class":134},"s_sjI","@nestjs\u002Fcommon",[99,137,138],{"class":130},"\"",[99,140,141],{"class":116},";\n",[99,143,145,147,149,152,154,156,158,160,162],{"class":101,"line":144},3,[99,146,113],{"class":112},[99,148,117],{"class":116},[99,150,151],{"class":120}," BullModule",[99,153,124],{"class":116},[99,155,127],{"class":112},[99,157,131],{"class":130},[99,159,49],{"class":134},[99,161,138],{"class":130},[99,163,141],{"class":116},[99,165,167],{"class":101,"line":166},4,[99,168,170],{"emptyLinePlaceholder":169},true,"\n",[99,172,174,177,181,184],{"class":101,"line":173},5,[99,175,176],{"class":116},"@",[99,178,180],{"class":179},"sGLFI","Module",[99,182,183],{"class":120},"(",[99,185,186],{"class":116},"{\n",[99,188,190,194,197],{"class":101,"line":189},6,[99,191,193],{"class":192},"skxfh","  imports",[99,195,196],{"class":116},":",[99,198,199],{"class":120}," [\n",[99,201,203,206,209,212,214],{"class":101,"line":202},7,[99,204,205],{"class":120},"    BullModule",[99,207,208],{"class":116},".",[99,210,211],{"class":179},"forRootAsync",[99,213,183],{"class":120},[99,215,186],{"class":116},[99,217,219,222,224,227,231,234,238,241,245,247],{"class":101,"line":218},8,[99,220,221],{"class":179},"      useFactory",[99,223,196],{"class":116},[99,225,226],{"class":116}," (",[99,228,230],{"class":229},"s99_P","configService",[99,232,196],{"class":233},"smGrS",[99,235,237],{"class":236},"sbgvK"," ConfigService",[99,239,240],{"class":116},")",[99,242,244],{"class":243},"sbsja"," =>",[99,246,226],{"class":120},[99,248,186],{"class":116},[99,250,252,255,257],{"class":101,"line":251},9,[99,253,254],{"class":192},"        redis",[99,256,196],{"class":116},[99,258,259],{"class":116}," {\n",[99,261,263,266,268,271,273,276,278,280,283,285,288,290,293,295,297],{"class":101,"line":262},10,[99,264,265],{"class":192},"          host",[99,267,196],{"class":116},[99,269,270],{"class":120}," configService",[99,272,208],{"class":116},[99,274,275],{"class":179},"get",[99,277,183],{"class":120},[99,279,138],{"class":130},[99,281,282],{"class":134},"REDIS_HOST",[99,284,138],{"class":130},[99,286,287],{"class":116},",",[99,289,131],{"class":130},[99,291,292],{"class":134},"localhost",[99,294,138],{"class":130},[99,296,240],{"class":120},[99,298,299],{"class":116},",\n",[99,301,303,306,308,310,312,314,317,321,324,326,328,331,333,335,339,341],{"class":101,"line":302},11,[99,304,305],{"class":192},"          port",[99,307,196],{"class":116},[99,309,270],{"class":120},[99,311,208],{"class":116},[99,313,275],{"class":179},[99,315,316],{"class":116},"\u003C",[99,318,320],{"class":319},"sZMiF","number",[99,322,323],{"class":116},">",[99,325,183],{"class":120},[99,327,138],{"class":130},[99,329,330],{"class":134},"REDIS_PORT",[99,332,138],{"class":130},[99,334,287],{"class":116},[99,336,338],{"class":337},"srdBf"," 6379",[99,340,240],{"class":120},[99,342,299],{"class":116},[99,344,346],{"class":101,"line":345},12,[99,347,348],{"class":116},"        },\n",[99,350,352,355,357,359,362,364,366],{"class":101,"line":351},13,[99,353,354],{"class":192},"        prefix",[99,356,196],{"class":116},[99,358,131],{"class":130},[99,360,361],{"class":134},"my-queue",[99,363,138],{"class":130},[99,365,287],{"class":116},[99,367,368],{"class":105}," \u002F\u002F 自定义队列键名前缀\n",[99,370,372,375,377],{"class":101,"line":371},14,[99,373,374],{"class":192},"        defaultJobOptions",[99,376,196],{"class":116},[99,378,259],{"class":116},[99,380,382],{"class":101,"line":381},15,[99,383,384],{"class":105},"          \u002F\u002F 全局默认作业选项\n",[99,386,388,391,393,396,398],{"class":101,"line":387},16,[99,389,390],{"class":192},"          attempts",[99,392,196],{"class":116},[99,394,395],{"class":337}," 3",[99,397,287],{"class":116},[99,399,400],{"class":105}," \u002F\u002F 失败时重试3次\n",[99,402,404,407,409,413,415],{"class":101,"line":403},17,[99,405,406],{"class":192},"          removeOnComplete",[99,408,196],{"class":116},[99,410,412],{"class":411},"syTEX"," true",[99,414,287],{"class":116},[99,416,417],{"class":105}," \u002F\u002F 完成后自动移除\n",[99,419,421],{"class":101,"line":420},18,[99,422,348],{"class":116},[99,424,426,429,431],{"class":101,"line":425},19,[99,427,428],{"class":116},"      }",[99,430,240],{"class":120},[99,432,299],{"class":116},[99,434,436,439,441,444],{"class":101,"line":435},20,[99,437,438],{"class":192},"      inject",[99,440,196],{"class":116},[99,442,443],{"class":120}," [ConfigService]",[99,445,299],{"class":116},[99,447,449,452,454],{"class":101,"line":448},21,[99,450,451],{"class":116},"    }",[99,453,240],{"class":120},[99,455,299],{"class":116},[99,457,459,461,463,466,468],{"class":101,"line":458},22,[99,460,205],{"class":120},[99,462,208],{"class":116},[99,464,465],{"class":179},"registerQueueAsync",[99,467,183],{"class":120},[99,469,186],{"class":116},[99,471,473,476,478,480,483,485,487],{"class":101,"line":472},23,[99,474,475],{"class":192},"      name",[99,477,196],{"class":116},[99,479,131],{"class":130},[99,481,482],{"class":134},"email",[99,484,138],{"class":130},[99,486,287],{"class":116},[99,488,489],{"class":105}," \u002F\u002F 队列名称，用于后续注入\n",[99,491,493,495,497],{"class":101,"line":492},24,[99,494,451],{"class":116},[99,496,240],{"class":120},[99,498,299],{"class":116},[99,500,502,505],{"class":101,"line":501},25,[99,503,504],{"class":120},"  ]",[99,506,299],{"class":116},[99,508,510,513],{"class":101,"line":509},26,[99,511,512],{"class":116},"}",[99,514,515],{"class":120},")\n",[99,517,519,522,525,528],{"class":101,"line":518},27,[99,520,521],{"class":112},"export",[99,523,524],{"class":243}," class",[99,526,527],{"class":236}," AppModule",[99,529,530],{"class":116}," {}\n",[532,533,534],"ul",{},[535,536,537,538,540,541,544],"li",{},"使用 ",[33,539,465],{}," 能按需创建队列。",[33,542,543],{},"name"," 字段是队列的唯一标识，在后续的生产和消费环节都需要用到。",[78,546,548],{"id":547},"生产者-producer派发作业","📤 生产者 (Producer)：派发作业",[24,550,551,552,555,556,559],{},"在服务中通过 ",[33,553,554],{},"@InjectQueue()"," 注入特定的队列实例，并使用 ",[33,557,558],{},"add()"," 方法向队列中派发任务。",[90,561,563],{"className":92,"code":562,"language":94,"meta":95,"style":95},"\u002F\u002F email.service.ts\nimport { Injectable } from \"@nestjs\u002Fcommon\";\nimport { InjectQueue } from \"@nestjs\u002Fbull\";\nimport { Queue } from \"bull\";\n\nexport interface EmailJobData {\n  to: string;\n  subject: string;\n  template: string;\n}\n\n@Injectable()\nexport class EmailService {\n  constructor(@InjectQueue(\"email\") private emailQueue: Queue) {}\n\n  async sendWelcomeEmail(to: string) {\n    \u002F\u002F Job 名称是 send-welcome，用于区分不同类型的任务\n    await this.emailQueue.add(\"send-welcome\", {\n      to,\n      subject: \"Welcome!\",\n      template: \"welcome\",\n    } as EmailJobData);\n  }\n\n  async scheduleBirthdayEmail(to: string, date: Date) {\n    \u002F\u002F 延迟执行：在设定时间之后才被消费\n    const delay = date.getTime() - Date.now();\n    await this.emailQueue.add(\"send-birthday\", { to }, { delay });\n  }\n\n  async sendHighPriorityNotification(to: string) {\n    await this.emailQueue.add(\"send-notify\", { to }, { priority: 1 }); \u002F\u002F 数字越小优先级越高\n  }\n}\n",[33,564,565,570,591,612,634,638,650,663,674,685,690,694,704,715,751,755,777,782,814,821,837,853,866,871,875,904,909,946,990,995,1000,1020,1072,1077],{"__ignoreMap":95},[99,566,567],{"class":101,"line":102},[99,568,569],{"class":105},"\u002F\u002F email.service.ts\n",[99,571,572,574,576,579,581,583,585,587,589],{"class":101,"line":109},[99,573,113],{"class":112},[99,575,117],{"class":116},[99,577,578],{"class":120}," Injectable",[99,580,124],{"class":116},[99,582,127],{"class":112},[99,584,131],{"class":130},[99,586,135],{"class":134},[99,588,138],{"class":130},[99,590,141],{"class":116},[99,592,593,595,597,600,602,604,606,608,610],{"class":101,"line":144},[99,594,113],{"class":112},[99,596,117],{"class":116},[99,598,599],{"class":120}," InjectQueue",[99,601,124],{"class":116},[99,603,127],{"class":112},[99,605,131],{"class":130},[99,607,49],{"class":134},[99,609,138],{"class":130},[99,611,141],{"class":116},[99,613,614,616,618,621,623,625,627,630,632],{"class":101,"line":166},[99,615,113],{"class":112},[99,617,117],{"class":116},[99,619,620],{"class":120}," Queue",[99,622,124],{"class":116},[99,624,127],{"class":112},[99,626,131],{"class":130},[99,628,629],{"class":134},"bull",[99,631,138],{"class":130},[99,633,141],{"class":116},[99,635,636],{"class":101,"line":173},[99,637,170],{"emptyLinePlaceholder":169},[99,639,640,642,645,648],{"class":101,"line":189},[99,641,521],{"class":112},[99,643,644],{"class":243}," interface",[99,646,647],{"class":236}," EmailJobData",[99,649,259],{"class":116},[99,651,652,656,658,661],{"class":101,"line":202},[99,653,655],{"class":654},"sucvu","  to",[99,657,196],{"class":233},[99,659,660],{"class":319}," string",[99,662,141],{"class":116},[99,664,665,668,670,672],{"class":101,"line":218},[99,666,667],{"class":654},"  subject",[99,669,196],{"class":233},[99,671,660],{"class":319},[99,673,141],{"class":116},[99,675,676,679,681,683],{"class":101,"line":251},[99,677,678],{"class":654},"  template",[99,680,196],{"class":233},[99,682,660],{"class":319},[99,684,141],{"class":116},[99,686,687],{"class":101,"line":262},[99,688,689],{"class":116},"}\n",[99,691,692],{"class":101,"line":302},[99,693,170],{"emptyLinePlaceholder":169},[99,695,696,698,701],{"class":101,"line":345},[99,697,176],{"class":116},[99,699,700],{"class":179},"Injectable",[99,702,703],{"class":120},"()\n",[99,705,706,708,710,713],{"class":101,"line":351},[99,707,521],{"class":112},[99,709,524],{"class":243},[99,711,712],{"class":236}," EmailService",[99,714,259],{"class":116},[99,716,717,720,723,726,728,730,732,734,737,740,743,745,747,749],{"class":101,"line":371},[99,718,719],{"class":243},"  constructor",[99,721,722],{"class":116},"(@",[99,724,725],{"class":179},"InjectQueue",[99,727,183],{"class":120},[99,729,138],{"class":130},[99,731,482],{"class":134},[99,733,138],{"class":130},[99,735,736],{"class":120},") ",[99,738,739],{"class":243},"private",[99,741,742],{"class":229}," emailQueue",[99,744,196],{"class":233},[99,746,620],{"class":236},[99,748,240],{"class":116},[99,750,530],{"class":116},[99,752,753],{"class":101,"line":381},[99,754,170],{"emptyLinePlaceholder":169},[99,756,757,760,764,766,769,771,773,775],{"class":101,"line":387},[99,758,759],{"class":243},"  async",[99,761,763],{"class":762},"sVXei"," sendWelcomeEmail",[99,765,183],{"class":116},[99,767,768],{"class":229},"to",[99,770,196],{"class":233},[99,772,660],{"class":319},[99,774,240],{"class":116},[99,776,259],{"class":116},[99,778,779],{"class":101,"line":403},[99,780,781],{"class":105},"    \u002F\u002F Job 名称是 send-welcome，用于区分不同类型的任务\n",[99,783,784,787,791,793,796,798,801,803,805,808,810,812],{"class":101,"line":420},[99,785,786],{"class":112},"    await",[99,788,790],{"class":789},"s39Yj"," this",[99,792,208],{"class":116},[99,794,795],{"class":120},"emailQueue",[99,797,208],{"class":116},[99,799,800],{"class":179},"add",[99,802,183],{"class":192},[99,804,138],{"class":130},[99,806,807],{"class":134},"send-welcome",[99,809,138],{"class":130},[99,811,287],{"class":116},[99,813,259],{"class":116},[99,815,816,819],{"class":101,"line":425},[99,817,818],{"class":120},"      to",[99,820,299],{"class":116},[99,822,823,826,828,830,833,835],{"class":101,"line":435},[99,824,825],{"class":192},"      subject",[99,827,196],{"class":116},[99,829,131],{"class":130},[99,831,832],{"class":134},"Welcome!",[99,834,138],{"class":130},[99,836,299],{"class":116},[99,838,839,842,844,846,849,851],{"class":101,"line":448},[99,840,841],{"class":192},"      template",[99,843,196],{"class":116},[99,845,131],{"class":130},[99,847,848],{"class":134},"welcome",[99,850,138],{"class":130},[99,852,299],{"class":116},[99,854,855,857,860,862,864],{"class":101,"line":458},[99,856,451],{"class":116},[99,858,859],{"class":112}," as",[99,861,647],{"class":236},[99,863,240],{"class":192},[99,865,141],{"class":116},[99,867,868],{"class":101,"line":472},[99,869,870],{"class":116},"  }\n",[99,872,873],{"class":101,"line":492},[99,874,170],{"emptyLinePlaceholder":169},[99,876,877,879,882,884,886,888,890,892,895,897,900,902],{"class":101,"line":501},[99,878,759],{"class":243},[99,880,881],{"class":762}," scheduleBirthdayEmail",[99,883,183],{"class":116},[99,885,768],{"class":229},[99,887,196],{"class":233},[99,889,660],{"class":319},[99,891,287],{"class":116},[99,893,894],{"class":229}," date",[99,896,196],{"class":233},[99,898,899],{"class":236}," Date",[99,901,240],{"class":116},[99,903,259],{"class":116},[99,905,906],{"class":101,"line":509},[99,907,908],{"class":105},"    \u002F\u002F 延迟执行：在设定时间之后才被消费\n",[99,910,911,914,918,921,923,925,928,931,934,936,938,941,944],{"class":101,"line":518},[99,912,913],{"class":243},"    const",[99,915,917],{"class":916},"s_hVV"," delay",[99,919,920],{"class":233}," =",[99,922,894],{"class":120},[99,924,208],{"class":116},[99,926,927],{"class":179},"getTime",[99,929,930],{"class":192},"() ",[99,932,933],{"class":233},"-",[99,935,899],{"class":120},[99,937,208],{"class":116},[99,939,940],{"class":179},"now",[99,942,943],{"class":192},"()",[99,945,141],{"class":116},[99,947,949,951,953,955,957,959,961,963,965,968,970,972,974,977,980,982,984,986,988],{"class":101,"line":948},28,[99,950,786],{"class":112},[99,952,790],{"class":789},[99,954,208],{"class":116},[99,956,795],{"class":120},[99,958,208],{"class":116},[99,960,800],{"class":179},[99,962,183],{"class":192},[99,964,138],{"class":130},[99,966,967],{"class":134},"send-birthday",[99,969,138],{"class":130},[99,971,287],{"class":116},[99,973,117],{"class":116},[99,975,976],{"class":120}," to",[99,978,979],{"class":116}," },",[99,981,117],{"class":116},[99,983,917],{"class":120},[99,985,124],{"class":116},[99,987,240],{"class":192},[99,989,141],{"class":116},[99,991,993],{"class":101,"line":992},29,[99,994,870],{"class":116},[99,996,998],{"class":101,"line":997},30,[99,999,170],{"emptyLinePlaceholder":169},[99,1001,1003,1005,1008,1010,1012,1014,1016,1018],{"class":101,"line":1002},31,[99,1004,759],{"class":243},[99,1006,1007],{"class":762}," sendHighPriorityNotification",[99,1009,183],{"class":116},[99,1011,768],{"class":229},[99,1013,196],{"class":233},[99,1015,660],{"class":319},[99,1017,240],{"class":116},[99,1019,259],{"class":116},[99,1021,1023,1025,1027,1029,1031,1033,1035,1037,1039,1042,1044,1046,1048,1050,1052,1054,1057,1059,1062,1064,1066,1069],{"class":101,"line":1022},32,[99,1024,786],{"class":112},[99,1026,790],{"class":789},[99,1028,208],{"class":116},[99,1030,795],{"class":120},[99,1032,208],{"class":116},[99,1034,800],{"class":179},[99,1036,183],{"class":192},[99,1038,138],{"class":130},[99,1040,1041],{"class":134},"send-notify",[99,1043,138],{"class":130},[99,1045,287],{"class":116},[99,1047,117],{"class":116},[99,1049,976],{"class":120},[99,1051,979],{"class":116},[99,1053,117],{"class":116},[99,1055,1056],{"class":192}," priority",[99,1058,196],{"class":116},[99,1060,1061],{"class":337}," 1",[99,1063,124],{"class":116},[99,1065,240],{"class":192},[99,1067,1068],{"class":116},";",[99,1070,1071],{"class":105}," \u002F\u002F 数字越小优先级越高\n",[99,1073,1075],{"class":101,"line":1074},33,[99,1076,870],{"class":116},[99,1078,1080],{"class":101,"line":1079},34,[99,1081,689],{"class":116},[24,1083,1084,1086,1087,1090,1091,62,1094,1097],{},[33,1085,558],{}," 方法接受三个参数，核心是",[28,1088,1089],{},"作业名称","和",[28,1092,1093],{},"作业数据",[33,1095,1096],{},"Job Options"," 则提供了丰富的控制选项：",[532,1099,1100,1106,1112,1118,1124,1130],{},[535,1101,1102,1105],{},[33,1103,1104],{},"delay",": 延迟执行，单位是毫秒（ms），常用于实现定时或延时任务。",[535,1107,1108,1111],{},[33,1109,1110],{},"priority",": 设置作业优先级，队列会优先处理高优先级任务。",[535,1113,1114,1117],{},[33,1115,1116],{},"jobId",": 自定义作业ID，可通过此ID对作业进行后续操作。",[535,1119,1120,1123],{},[33,1121,1122],{},"attempts",": 任务失败时的重试次数，这是保障队列可靠性的关键之一。",[535,1125,1126,1129],{},[33,1127,1128],{},"backoff",": 重试时的间隔策略，例如每次重试的等待时间可以递增。",[535,1131,1132,1135,1136,1139],{},[33,1133,1134],{},"removeOnComplete"," \u002F ",[33,1137,1138],{},"removeOnFail",": 作业完成或失败后是否自动从Redis中移除，有助于管理内存。",[1141,1142,1143],"blockquote",{},[24,1144,1145,1146,1149,1150,1153,1154,1164],{},"关于作业的重要说明：作业本身（及其承载的数据）会",[28,1147,1148],{},"被序列化后存储在 Redis 中","。有时即便作业被成功处理，它仍可能因“等待回调”而在视觉上残留或延迟完成，此时有效的解决办法是确保在 ",[33,1151,1152],{},"@Process()"," 方法结束时",[28,1155,1156,1157,1160,1161],{},"始终调用 ",[33,1158,1159],{},"callback"," 或 ",[33,1162,1163],{},"resolve\u002Freturn","，让Bull明确知晓作业已处理完毕。",[78,1166,1168],{"id":1167},"️-消费者-consumer处理作业","️ 消费者 (Consumer)：处理作业",[24,1170,1171,1172,1175,1176,1178],{},"消费者通过 ",[33,1173,1174],{},"@Processor()"," 和 ",[33,1177,1152],{}," 装饰器定义。有两种主要的组织结构：",[1180,1181,1183],"h4",{"id":1182},"_1-分类式-one-processor轻量选择共享资源","1. 分类式 (One Processor)：轻量选择，共享资源",[24,1185,1186,1187,1190],{},"一个 ",[33,1188,1189],{},"@Processor"," 类根据作业名称来消费同一个队列中的多种作业，适合快速上手以及那些需要共享资源的任务。",[90,1192,1194],{"className":92,"code":1193,"language":94,"meta":95,"style":95},"\u002F\u002F email.processor.ts\nimport { Processor, Process } from \"@nestjs\u002Fbull\";\nimport { Job } from \"bull\";\n\n@Processor(\"email\")\nexport class EmailProcessor {\n  @Process(\"send-welcome\")\n  async handleWelcome(job: Job\u003CEmailJobData>) {\n    const { to, subject, template } = job.data;\n    \u002F\u002F 具体发送逻辑\n    await this.sendEmail(to, subject, template);\n    \u002F\u002F 可选: 更新进度\n    await job.progress(100);\n  }\n\n  @Process(\"send-birthday\")\n  async handleBirthday(job: Job\u003CEmailJobData>) {\n    \u002F\u002F 成员方法可使用共享属性\n  }\n}\n",[33,1195,1196,1201,1227,1248,1252,1269,1280,1298,1324,1356,1361,1388,1393,1413,1417,1421,1437,1460,1465,1469],{"__ignoreMap":95},[99,1197,1198],{"class":101,"line":102},[99,1199,1200],{"class":105},"\u002F\u002F email.processor.ts\n",[99,1202,1203,1205,1207,1210,1212,1215,1217,1219,1221,1223,1225],{"class":101,"line":109},[99,1204,113],{"class":112},[99,1206,117],{"class":116},[99,1208,1209],{"class":120}," Processor",[99,1211,287],{"class":116},[99,1213,1214],{"class":120}," Process",[99,1216,124],{"class":116},[99,1218,127],{"class":112},[99,1220,131],{"class":130},[99,1222,49],{"class":134},[99,1224,138],{"class":130},[99,1226,141],{"class":116},[99,1228,1229,1231,1233,1236,1238,1240,1242,1244,1246],{"class":101,"line":144},[99,1230,113],{"class":112},[99,1232,117],{"class":116},[99,1234,1235],{"class":120}," Job",[99,1237,124],{"class":116},[99,1239,127],{"class":112},[99,1241,131],{"class":130},[99,1243,629],{"class":134},[99,1245,138],{"class":130},[99,1247,141],{"class":116},[99,1249,1250],{"class":101,"line":166},[99,1251,170],{"emptyLinePlaceholder":169},[99,1253,1254,1256,1259,1261,1263,1265,1267],{"class":101,"line":173},[99,1255,176],{"class":116},[99,1257,1258],{"class":179},"Processor",[99,1260,183],{"class":120},[99,1262,138],{"class":130},[99,1264,482],{"class":134},[99,1266,138],{"class":130},[99,1268,515],{"class":120},[99,1270,1271,1273,1275,1278],{"class":101,"line":189},[99,1272,521],{"class":112},[99,1274,524],{"class":243},[99,1276,1277],{"class":236}," EmailProcessor",[99,1279,259],{"class":116},[99,1281,1282,1285,1288,1290,1292,1294,1296],{"class":101,"line":202},[99,1283,1284],{"class":116},"  @",[99,1286,1287],{"class":179},"Process",[99,1289,183],{"class":120},[99,1291,138],{"class":130},[99,1293,807],{"class":134},[99,1295,138],{"class":130},[99,1297,515],{"class":120},[99,1299,1300,1302,1305,1307,1310,1312,1314,1316,1319,1322],{"class":101,"line":218},[99,1301,759],{"class":243},[99,1303,1304],{"class":762}," handleWelcome",[99,1306,183],{"class":116},[99,1308,1309],{"class":229},"job",[99,1311,196],{"class":233},[99,1313,1235],{"class":236},[99,1315,316],{"class":116},[99,1317,1318],{"class":236},"EmailJobData",[99,1320,1321],{"class":116},">)",[99,1323,259],{"class":116},[99,1325,1326,1328,1330,1332,1334,1337,1339,1342,1344,1346,1349,1351,1354],{"class":101,"line":251},[99,1327,913],{"class":243},[99,1329,117],{"class":116},[99,1331,976],{"class":916},[99,1333,287],{"class":116},[99,1335,1336],{"class":916}," subject",[99,1338,287],{"class":116},[99,1340,1341],{"class":916}," template",[99,1343,124],{"class":116},[99,1345,920],{"class":233},[99,1347,1348],{"class":120}," job",[99,1350,208],{"class":116},[99,1352,1353],{"class":120},"data",[99,1355,141],{"class":116},[99,1357,1358],{"class":101,"line":262},[99,1359,1360],{"class":105},"    \u002F\u002F 具体发送逻辑\n",[99,1362,1363,1365,1367,1369,1372,1374,1376,1378,1380,1382,1384,1386],{"class":101,"line":302},[99,1364,786],{"class":112},[99,1366,790],{"class":789},[99,1368,208],{"class":116},[99,1370,1371],{"class":179},"sendEmail",[99,1373,183],{"class":192},[99,1375,768],{"class":120},[99,1377,287],{"class":116},[99,1379,1336],{"class":120},[99,1381,287],{"class":116},[99,1383,1341],{"class":120},[99,1385,240],{"class":192},[99,1387,141],{"class":116},[99,1389,1390],{"class":101,"line":345},[99,1391,1392],{"class":105},"    \u002F\u002F 可选: 更新进度\n",[99,1394,1395,1397,1399,1401,1404,1406,1409,1411],{"class":101,"line":351},[99,1396,786],{"class":112},[99,1398,1348],{"class":120},[99,1400,208],{"class":116},[99,1402,1403],{"class":179},"progress",[99,1405,183],{"class":192},[99,1407,1408],{"class":337},"100",[99,1410,240],{"class":192},[99,1412,141],{"class":116},[99,1414,1415],{"class":101,"line":371},[99,1416,870],{"class":116},[99,1418,1419],{"class":101,"line":381},[99,1420,170],{"emptyLinePlaceholder":169},[99,1422,1423,1425,1427,1429,1431,1433,1435],{"class":101,"line":387},[99,1424,1284],{"class":116},[99,1426,1287],{"class":179},[99,1428,183],{"class":120},[99,1430,138],{"class":130},[99,1432,967],{"class":134},[99,1434,138],{"class":130},[99,1436,515],{"class":120},[99,1438,1439,1441,1444,1446,1448,1450,1452,1454,1456,1458],{"class":101,"line":403},[99,1440,759],{"class":243},[99,1442,1443],{"class":762}," handleBirthday",[99,1445,183],{"class":116},[99,1447,1309],{"class":229},[99,1449,196],{"class":233},[99,1451,1235],{"class":236},[99,1453,316],{"class":116},[99,1455,1318],{"class":236},[99,1457,1321],{"class":116},[99,1459,259],{"class":116},[99,1461,1462],{"class":101,"line":420},[99,1463,1464],{"class":105},"    \u002F\u002F 成员方法可使用共享属性\n",[99,1466,1467],{"class":101,"line":425},[99,1468,870],{"class":116},[99,1470,1471],{"class":101,"line":435},[99,1472,689],{"class":116},[1180,1474,1476],{"id":1475},"_2-分离式-multiple-consumers资源隔离专人专事","2. 分离式 (Multiple Consumers)：资源隔离，专人专事",[24,1478,1479,1480,1482],{},"每种作业可以各自拥有独立的 ",[33,1481,1189],{}," 类，每个类内部只处理该作业。这种结构让关注点分离，适合业务复杂的情况。",[90,1484,1486],{"className":92,"code":1485,"language":94,"meta":95,"style":95},"\u002F\u002F welcome.processor.ts\n@Processor(\"email\")\nexport class WelcomeEmailProcessor {\n  @Process(\"send-welcome\")\n  async handle(job: Job) {\n    \u002F* ... *\u002F\n  }\n}\n\n\u002F\u002F birthday.processor.ts\n@Processor(\"email\")\nexport class BirthdayEmailProcessor {\n  @Process(\"send-birthday\")\n  async handle(job: Job) {\n    \u002F* ... *\u002F\n  }\n}\n",[33,1487,1488,1493,1509,1520,1536,1555,1560,1564,1568,1572,1577,1593,1604,1620,1638,1642,1646],{"__ignoreMap":95},[99,1489,1490],{"class":101,"line":102},[99,1491,1492],{"class":105},"\u002F\u002F welcome.processor.ts\n",[99,1494,1495,1497,1499,1501,1503,1505,1507],{"class":101,"line":109},[99,1496,176],{"class":116},[99,1498,1258],{"class":179},[99,1500,183],{"class":120},[99,1502,138],{"class":130},[99,1504,482],{"class":134},[99,1506,138],{"class":130},[99,1508,515],{"class":120},[99,1510,1511,1513,1515,1518],{"class":101,"line":144},[99,1512,521],{"class":112},[99,1514,524],{"class":243},[99,1516,1517],{"class":236}," WelcomeEmailProcessor",[99,1519,259],{"class":116},[99,1521,1522,1524,1526,1528,1530,1532,1534],{"class":101,"line":166},[99,1523,1284],{"class":116},[99,1525,1287],{"class":179},[99,1527,183],{"class":120},[99,1529,138],{"class":130},[99,1531,807],{"class":134},[99,1533,138],{"class":130},[99,1535,515],{"class":120},[99,1537,1538,1540,1543,1545,1547,1549,1551,1553],{"class":101,"line":173},[99,1539,759],{"class":243},[99,1541,1542],{"class":762}," handle",[99,1544,183],{"class":116},[99,1546,1309],{"class":229},[99,1548,196],{"class":233},[99,1550,1235],{"class":236},[99,1552,240],{"class":116},[99,1554,259],{"class":116},[99,1556,1557],{"class":101,"line":189},[99,1558,1559],{"class":105},"    \u002F* ... *\u002F\n",[99,1561,1562],{"class":101,"line":202},[99,1563,870],{"class":116},[99,1565,1566],{"class":101,"line":218},[99,1567,689],{"class":116},[99,1569,1570],{"class":101,"line":251},[99,1571,170],{"emptyLinePlaceholder":169},[99,1573,1574],{"class":101,"line":262},[99,1575,1576],{"class":105},"\u002F\u002F birthday.processor.ts\n",[99,1578,1579,1581,1583,1585,1587,1589,1591],{"class":101,"line":302},[99,1580,176],{"class":116},[99,1582,1258],{"class":179},[99,1584,183],{"class":120},[99,1586,138],{"class":130},[99,1588,482],{"class":134},[99,1590,138],{"class":130},[99,1592,515],{"class":120},[99,1594,1595,1597,1599,1602],{"class":101,"line":345},[99,1596,521],{"class":112},[99,1598,524],{"class":243},[99,1600,1601],{"class":236}," BirthdayEmailProcessor",[99,1603,259],{"class":116},[99,1605,1606,1608,1610,1612,1614,1616,1618],{"class":101,"line":351},[99,1607,1284],{"class":116},[99,1609,1287],{"class":179},[99,1611,183],{"class":120},[99,1613,138],{"class":130},[99,1615,967],{"class":134},[99,1617,138],{"class":130},[99,1619,515],{"class":120},[99,1621,1622,1624,1626,1628,1630,1632,1634,1636],{"class":101,"line":371},[99,1623,759],{"class":243},[99,1625,1542],{"class":762},[99,1627,183],{"class":116},[99,1629,1309],{"class":229},[99,1631,196],{"class":233},[99,1633,1235],{"class":236},[99,1635,240],{"class":116},[99,1637,259],{"class":116},[99,1639,1640],{"class":101,"line":381},[99,1641,1559],{"class":105},[99,1643,1644],{"class":101,"line":387},[99,1645,870],{"class":116},[99,1647,1648],{"class":101,"line":403},[99,1649,689],{"class":116},[1180,1651,1653],{"id":1652},"️-worker-配置优化","⚙️ Worker 配置优化",[24,1655,1656,1657,1659],{},"可以通过 ",[33,1658,1189],{}," 的第二个参数来配置底层 Worker 的行为，例如控制并发数或限流。",[90,1661,1663],{"className":92,"code":1662,"language":94,"meta":95,"style":95},"\u002F\u002F 并发5个任务，每秒最多处理10个任务\n@Processor('email', { concurrency: 5, limiter: { max: 10, duration: 1000 } })\nexport class EmailProcessor { ... }\n",[33,1664,1665,1670,1730],{"__ignoreMap":95},[99,1666,1667],{"class":101,"line":102},[99,1668,1669],{"class":105},"\u002F\u002F 并发5个任务，每秒最多处理10个任务\n",[99,1671,1672,1674,1676,1678,1681,1683,1685,1687,1689,1692,1694,1697,1699,1702,1704,1706,1709,1711,1714,1716,1719,1721,1724,1726,1728],{"class":101,"line":109},[99,1673,176],{"class":116},[99,1675,1258],{"class":179},[99,1677,183],{"class":120},[99,1679,1680],{"class":130},"'",[99,1682,482],{"class":134},[99,1684,1680],{"class":130},[99,1686,287],{"class":116},[99,1688,117],{"class":116},[99,1690,1691],{"class":192}," concurrency",[99,1693,196],{"class":116},[99,1695,1696],{"class":337}," 5",[99,1698,287],{"class":116},[99,1700,1701],{"class":192}," limiter",[99,1703,196],{"class":116},[99,1705,117],{"class":116},[99,1707,1708],{"class":192}," max",[99,1710,196],{"class":116},[99,1712,1713],{"class":337}," 10",[99,1715,287],{"class":116},[99,1717,1718],{"class":192}," duration",[99,1720,196],{"class":116},[99,1722,1723],{"class":337}," 1000",[99,1725,124],{"class":116},[99,1727,124],{"class":116},[99,1729,515],{"class":120},[99,1731,1732,1734,1736,1738,1740,1743],{"class":101,"line":144},[99,1733,521],{"class":112},[99,1735,524],{"class":243},[99,1737,1277],{"class":236},[99,1739,117],{"class":116},[99,1741,1742],{"class":233}," ...",[99,1744,1745],{"class":116}," }\n",[532,1747,1748],{},[535,1749,1750,1753,1754,1757,1758,1760,1761,1764],{},[33,1751,1752],{},"concurrency",": 决定",[33,1755,1756],{},"同一时间","该处理器可并行处理的任务数。注意，即使在同一 ",[33,1759,1189],{}," 内，也可为不同 ",[33,1762,1763],{},"@Process"," 方法设置不同的并发数，实现内部差异化。",[78,1766,1768],{"id":1767},"事件监听-listeners追踪作业全生命周期","🎤 事件监听 (Listeners)：追踪作业全生命周期",[24,1770,1771],{},"Bull 提供了完整的事件系统，用于监控作业从入队到完成（或失败）的整个生命周期。",[90,1773,1775],{"className":92,"code":1774,"language":94,"meta":95,"style":95},"\u002F\u002F email.listener.ts\nimport { Processor, OnQueueCompleted, OnQueueFailed, OnQueueProgress } from \"@nestjs\u002Fbull\";\n\n@Processor(\"email\")\nexport class EmailListener {\n  @OnQueueCompleted()\n  onCompleted(job: Job, result: any) {\n    console.log(`Job ${job.id} completed with result: ${result}`);\n  }\n\n  @OnQueueFailed()\n  onFailed(job: Job, err: Error) {\n    console.error(`Job ${job.id} failed: ${err.message}`);\n    \u002F\u002F 可在此集成 Sentry 等错误追踪系统\n  }\n\n  @OnQueueProgress()\n  onProgress(job: Job, progress: number) {\n    console.log(`Job ${job.id} progress: ${progress}`);\n  }\n}\n",[33,1776,1777,1782,1817,1821,1837,1848,1857,1884,1929,1933,1937,1946,1973,2017,2022,2026,2030,2039,2066,2103,2107],{"__ignoreMap":95},[99,1778,1779],{"class":101,"line":102},[99,1780,1781],{"class":105},"\u002F\u002F email.listener.ts\n",[99,1783,1784,1786,1788,1790,1792,1795,1797,1800,1802,1805,1807,1809,1811,1813,1815],{"class":101,"line":109},[99,1785,113],{"class":112},[99,1787,117],{"class":116},[99,1789,1209],{"class":120},[99,1791,287],{"class":116},[99,1793,1794],{"class":120}," OnQueueCompleted",[99,1796,287],{"class":116},[99,1798,1799],{"class":120}," OnQueueFailed",[99,1801,287],{"class":116},[99,1803,1804],{"class":120}," OnQueueProgress",[99,1806,124],{"class":116},[99,1808,127],{"class":112},[99,1810,131],{"class":130},[99,1812,49],{"class":134},[99,1814,138],{"class":130},[99,1816,141],{"class":116},[99,1818,1819],{"class":101,"line":144},[99,1820,170],{"emptyLinePlaceholder":169},[99,1822,1823,1825,1827,1829,1831,1833,1835],{"class":101,"line":166},[99,1824,176],{"class":116},[99,1826,1258],{"class":179},[99,1828,183],{"class":120},[99,1830,138],{"class":130},[99,1832,482],{"class":134},[99,1834,138],{"class":130},[99,1836,515],{"class":120},[99,1838,1839,1841,1843,1846],{"class":101,"line":173},[99,1840,521],{"class":112},[99,1842,524],{"class":243},[99,1844,1845],{"class":236}," EmailListener",[99,1847,259],{"class":116},[99,1849,1850,1852,1855],{"class":101,"line":189},[99,1851,1284],{"class":116},[99,1853,1854],{"class":179},"OnQueueCompleted",[99,1856,703],{"class":120},[99,1858,1859,1862,1864,1866,1868,1870,1872,1875,1877,1880,1882],{"class":101,"line":202},[99,1860,1861],{"class":762},"  onCompleted",[99,1863,183],{"class":116},[99,1865,1309],{"class":229},[99,1867,196],{"class":233},[99,1869,1235],{"class":236},[99,1871,287],{"class":116},[99,1873,1874],{"class":229}," result",[99,1876,196],{"class":233},[99,1878,1879],{"class":319}," any",[99,1881,240],{"class":116},[99,1883,259],{"class":116},[99,1885,1886,1889,1891,1894,1896,1899,1902,1905,1907,1909,1912,1914,1917,1919,1922,1925,1927],{"class":101,"line":218},[99,1887,1888],{"class":120},"    console",[99,1890,208],{"class":116},[99,1892,1893],{"class":179},"log",[99,1895,183],{"class":192},[99,1897,1898],{"class":130},"`",[99,1900,1901],{"class":134},"Job ",[99,1903,1904],{"class":130},"${",[99,1906,1309],{"class":120},[99,1908,208],{"class":130},[99,1910,1911],{"class":120},"id",[99,1913,512],{"class":130},[99,1915,1916],{"class":134}," completed with result: ",[99,1918,1904],{"class":130},[99,1920,1921],{"class":120},"result",[99,1923,1924],{"class":130},"}`",[99,1926,240],{"class":192},[99,1928,141],{"class":116},[99,1930,1931],{"class":101,"line":251},[99,1932,870],{"class":116},[99,1934,1935],{"class":101,"line":262},[99,1936,170],{"emptyLinePlaceholder":169},[99,1938,1939,1941,1944],{"class":101,"line":302},[99,1940,1284],{"class":116},[99,1942,1943],{"class":179},"OnQueueFailed",[99,1945,703],{"class":120},[99,1947,1948,1951,1953,1955,1957,1959,1961,1964,1966,1969,1971],{"class":101,"line":345},[99,1949,1950],{"class":762},"  onFailed",[99,1952,183],{"class":116},[99,1954,1309],{"class":229},[99,1956,196],{"class":233},[99,1958,1235],{"class":236},[99,1960,287],{"class":116},[99,1962,1963],{"class":229}," err",[99,1965,196],{"class":233},[99,1967,1968],{"class":236}," Error",[99,1970,240],{"class":116},[99,1972,259],{"class":116},[99,1974,1975,1977,1979,1982,1984,1986,1988,1990,1992,1994,1996,1998,2001,2003,2006,2008,2011,2013,2015],{"class":101,"line":351},[99,1976,1888],{"class":120},[99,1978,208],{"class":116},[99,1980,1981],{"class":179},"error",[99,1983,183],{"class":192},[99,1985,1898],{"class":130},[99,1987,1901],{"class":134},[99,1989,1904],{"class":130},[99,1991,1309],{"class":120},[99,1993,208],{"class":130},[99,1995,1911],{"class":120},[99,1997,512],{"class":130},[99,1999,2000],{"class":134}," failed: ",[99,2002,1904],{"class":130},[99,2004,2005],{"class":120},"err",[99,2007,208],{"class":130},[99,2009,2010],{"class":120},"message",[99,2012,1924],{"class":130},[99,2014,240],{"class":192},[99,2016,141],{"class":116},[99,2018,2019],{"class":101,"line":371},[99,2020,2021],{"class":105},"    \u002F\u002F 可在此集成 Sentry 等错误追踪系统\n",[99,2023,2024],{"class":101,"line":381},[99,2025,870],{"class":116},[99,2027,2028],{"class":101,"line":387},[99,2029,170],{"emptyLinePlaceholder":169},[99,2031,2032,2034,2037],{"class":101,"line":403},[99,2033,1284],{"class":116},[99,2035,2036],{"class":179},"OnQueueProgress",[99,2038,703],{"class":120},[99,2040,2041,2044,2046,2048,2050,2052,2054,2057,2059,2062,2064],{"class":101,"line":420},[99,2042,2043],{"class":762},"  onProgress",[99,2045,183],{"class":116},[99,2047,1309],{"class":229},[99,2049,196],{"class":233},[99,2051,1235],{"class":236},[99,2053,287],{"class":116},[99,2055,2056],{"class":229}," progress",[99,2058,196],{"class":233},[99,2060,2061],{"class":319}," number",[99,2063,240],{"class":116},[99,2065,259],{"class":116},[99,2067,2068,2070,2072,2074,2076,2078,2080,2082,2084,2086,2088,2090,2093,2095,2097,2099,2101],{"class":101,"line":425},[99,2069,1888],{"class":120},[99,2071,208],{"class":116},[99,2073,1893],{"class":179},[99,2075,183],{"class":192},[99,2077,1898],{"class":130},[99,2079,1901],{"class":134},[99,2081,1904],{"class":130},[99,2083,1309],{"class":120},[99,2085,208],{"class":130},[99,2087,1911],{"class":120},[99,2089,512],{"class":130},[99,2091,2092],{"class":134}," progress: ",[99,2094,1904],{"class":130},[99,2096,1403],{"class":120},[99,2098,1924],{"class":130},[99,2100,240],{"class":192},[99,2102,141],{"class":116},[99,2104,2105],{"class":101,"line":435},[99,2106,870],{"class":116},[99,2108,2109],{"class":101,"line":448},[99,2110,689],{"class":116},[24,2112,2113,2114,2116,2117,2120,2121,2124],{},"每个 ",[33,2115,1763],{}," 中调用 ",[33,2118,2119],{},"job.progress()"," 时，",[33,2122,2123],{},"@OnQueueProgress"," 对应的事件就会触发，便于追踪进度。",[1141,2126,2127],{},[24,2128,2129,2130,2132,2133,69,2136,2139,2140,2143],{},"如果需要对某个事件的“全局”版本进行监听（不限于特定 ",[33,2131,1189],{},"），可使用 ",[33,2134,2135],{},"OnGlobalQueueCompleted",[33,2137,2138],{},"OnGlobalQueueFailed"," 等带 ",[33,2141,2142],{},"Global"," 字样的装饰器。这对跨队列的统一监控非常有用。",[78,2145,2147],{"id":2146},"高级特性一览延迟重试移除与重复","⚡ 高级特性一览：延迟、重试、移除与重复",[532,2149,2150,2161,2174],{},[535,2151,2152,2155,2156,1175,2158,2160],{},[28,2153,2154],{},"延迟与优先级","：在生产者添加作业时，通过 ",[33,2157,1104],{},[33,2159,1110],{}," 选项可轻松实现延时任务和优先级排序，这在功能说明部分已收到过。",[535,2162,2163,2166,2167,2170,2171,62],{},[28,2164,2165],{},"移除作业","：若需动态管理队列（如用户取消预约），可通过 ",[33,2168,2169],{},"queue.getJob(jobId)"," 获取作业对象并调用 ",[33,2172,2173],{},"job.remove()",[535,2175,2176,2179,2180],{},[28,2177,2178],{},"重复作业 (Repeatable Jobs)","：Bull 支持按 Cron 表达式或特定时间间隔创建重复执行的作业。\n",[2181,2182,2183,2200],"ol",{},[535,2184,2185,2186,2189,2190,2192,2193,2196,2197,62],{},"从生产端使用 ",[33,2187,2188],{},"queue.add()"," 时，在 ",[33,2191,1096],{}," 中增加 ",[33,2194,2195],{},"repeat"," 字段，例如 ",[33,2198,2199],{},"{ cron: '0 9 * * *', tz: 'Asia\u002FShanghai' }",[535,2201,2202,2203,2205],{},"从消费端来看，某队列可在多个实例中仅由一个实例的 ",[33,2204,1763],{}," 进行处理，这依赖于 Redis 的分布式锁机制，有效避免多个消费者同时消费同一个重复作业，从而达到分布式调度的效果。",[78,2207,2209],{"id":2208},"最佳实践总结","✨ 最佳实践总结",[532,2211,2212,2222,2243,2257],{},[535,2213,2214,2217,2218,2221],{},[28,2215,2216],{},"命名策略","：建议使用 ",[33,2219,2220],{},"kebab-case"," 为队列命名。作业名称与处理函数名称保持一致，利于全局搜索和维护。",[535,2223,2224,2230,2231,2233,2234,2236,2237,2239,2240,2242],{},[28,2225,2226,2229],{},[33,2227,2228],{},"JobOptions"," 选择原则","：比较关键的配置包括 ",[33,2232,1110],{}," (优先级)、",[33,2235,1104],{}," (延迟)、",[33,2238,1122],{}," (重试次数) 与 ",[33,2241,1128],{}," (退避策略)。",[535,2244,2245,2248,2249,2252,2253,2256],{},[28,2246,2247],{},"数据大小限制","：Redis 对所有键值的大小和数量均有实际限制。务必不要将大块数据 (如 base64 图片) 直接放进 ",[33,2250,2251],{},"job.data","，应改为存入对象存储（如 OSS）的元数据与文件路径。设计优雅的 ",[33,2254,2255],{},"Job"," 结构能有效降低对 Redis 的存储压力。",[535,2258,2259,2262,2263,2266],{},[28,2260,2261],{},"监控与告警","：务必为关键业务队列实现 ",[33,2264,2265],{},"@OnQueueFailed"," 或者附加全局监听，以便及时发现处理失败的任务。",[2268,2269,2270],"h2",{"id":2270},"参考文献",[24,2272,2273],{},"以下链接在编写时均可正常访问：",[2275,2276,2277,2290],"table",{},[2278,2279,2280],"thead",{},[2281,2282,2283,2287],"tr",{},[2284,2285,2286],"th",{},"资料",[2284,2288,2289],{},"说明",[2291,2292,2293,2306,2317],"tbody",{},[2281,2294,2295,2303],{},[2296,2297,2298],"td",{},[55,2299,2302],{"href":2300,"rel":2301},"https:\u002F\u002Fdocs.nestjs.com\u002F",[59],"NestJS 文档",[2296,2304,2305],{},"官方",[2281,2307,2308,2314],{},[2296,2309,2310],{},[55,2311,2313],{"href":57,"rel":2312},[59],"Queues",[2296,2315,2316],{},"本章主题",[2281,2318,2319,2326],{},[2296,2320,2321],{},[55,2322,2325],{"href":2323,"rel":2324},"https:\u002F\u002Fdocs.nestjs.com\u002Ffaq\u002Frequest-lifecycle",[59],"Request lifecycle",[2296,2327,2328],{},"执行顺序",[2330,2331,2332],"style",{},"html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .syTEX, html code.shiki .syTEX{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sucvu, html code.shiki .sucvu{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sVXei, html code.shiki .sVXei{--shiki-light:#E53935;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":95,"searchDepth":109,"depth":109,"links":2334},[2335,2336,2337,2338,2339,2340,2341],{"id":80,"depth":144,"text":81},{"id":547,"depth":144,"text":548},{"id":1167,"depth":144,"text":1168},{"id":1767,"depth":144,"text":1768},{"id":2146,"depth":144,"text":2147},{"id":2208,"depth":144,"text":2209},{"id":2270,"depth":109,"text":2270},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fend\u002FNestJS\u002Fnew\u002Fqueue","knowledge\u002Fknowledge\u002Fbooklet\u002Fend\u002FNestJS\u002Fnew\u002Fqueue.md","md",{"items":2347},[2348,2352,2356,2360,2364,2368],{"path":2349,"title":2350,"description":2351,"category":10,"score":189},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fauth","认证与授权","认证（Authentication）：确认「你是谁」（如 JWT、Session）。 - 授权（Authorization）：确认「你能做什么」（如 RBAC、策略检查）。",{"path":2353,"title":2354,"description":2355,"category":10,"score":189},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcache","缓存","@nestjs\u002Fcache-manager 统一缓存 API，存储实现可插拔。",{"path":2357,"title":2358,"description":2359,"category":10,"score":189},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fservice","提供者与服务","Service 是最常见的 Provider，封装业务逻辑。见 module。",{"path":2361,"title":2362,"description":2363,"category":10,"score":189},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fguards","守卫 (Guards) 与授权","守卫决定是否放行请求，常用于认证与授权。见 auth。",{"path":2365,"title":2366,"description":2367,"category":10,"score":189},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fconfig","配置管理（Config 模块）","@nestjs\u002Fconfig 加载 .env 并提供 ConfigService。见 工程化 env。",{"path":2369,"title":2370,"description":2371,"category":10,"score":189},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fdatabase","数据库集成（以 TypeORM 为例）","Nest 通过 @nestjs\u002Ftypeorm 等包集成 ORM；生产环境用 migration，慎用 synchronize。亦可选用 Prisma、MikroORM 等（见 官方 Database）。",{"series":2373,"title":2374,"items":2375,"index":387,"prev":2436,"next":2437},"booklet\u002Fend\u002Fnestjs\u002Fnew","new",[2376,2378,2382,2384,2386,2390,2394,2398,2400,2404,2408,2410,2414,2418,2422,2426,2429,2430,2432],{"path":2349,"title":2350,"date":2377,"pinned":15},"2025-12-23",{"path":2379,"title":2380,"date":2381,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fbase","NestJS 基础","2021-03-09",{"path":2353,"title":2354,"date":2383,"pinned":15},"2025-07-04",{"path":2365,"title":2366,"date":2385,"pinned":15},"2023-08-26",{"path":2387,"title":2388,"date":2389,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcontroller","控制器与路由","2020-07-18",{"path":2391,"title":2392,"date":2393,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcron","定时任务","2023-04-12",{"path":2395,"title":2396,"date":2397,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcustom-decorators","自定义装饰器","2021-09-19",{"path":2369,"title":2370,"date":2399,"pinned":15},"2023-05-26",{"path":2401,"title":2402,"date":2403,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fdeploy","部署与运维","2023-01-24",{"path":2405,"title":2406,"date":2407,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Ferror","异常处理","2020-01-28",{"path":2361,"title":2362,"date":2409,"pinned":15},"2023-09-09",{"path":2411,"title":2412,"date":2413,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fintercept","拦截器 (Interceptors)","2021-04-26",{"path":2415,"title":2416,"date":2417,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Flogger","日志记录","2022-10-10",{"path":2419,"title":2420,"date":2421,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fmiddleware","中间件 (Middleware)","2021-11-26",{"path":2423,"title":2424,"date":2425,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fmodule","模块系统与依赖注入","2022-06-15",{"path":2427,"title":2428,"date":2389,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fpipes","管道 (Pipes) 与数据验证",{"path":7,"title":8,"date":14,"pinned":15},{"path":2357,"title":2358,"date":2431,"pinned":15},"2024-12-02",{"path":2433,"title":2434,"date":2435,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fwebsocket","WebSocket（网关）","2022-12-14",{"path":2427,"title":2428,"date":2389,"pinned":15},{"path":2357,"title":2358,"date":2431,"pinned":15}]