[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcustom-decorators":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcustom-decorators":1653,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcustom-decorators":1679},{"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":1649,"stem":1650,"id":1651,"extension":1652},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcustom-decorators","自定义装饰器","用 SetMetadata 与 createParamDecorator 复用元数据与参数提取。","技能小册",[12,13],"nestjs","custom-decorators","2021-09-19",false,{"type":17,"value":18,"toc":1638},"minimark",[19,22,35,61,69,72,78,98,103,114,290,293,350,361,530,534,543,619,622,629,635,644,651,747,756,766,1036,1043,1053,1060,1117,1121,1197,1200,1275,1279,1379,1385,1491,1495,1502,1506,1572,1575,1579,1582,1634],[20,21,8],"h1",{"id":8},[23,24,25,26,30,31,34],"p",{},"用 ",[27,28,29],"code",{},"SetMetadata"," 与 ",[27,32,33],{},"createParamDecorator"," 复用元数据与参数提取。",[23,36,37,38,41,42,41,45,41,48,51,52,56,57,60],{},"NestJS 内置了大量装饰器（",[27,39,40],{},"@Get","、",[27,43,44],{},"@Post",[27,46,47],{},"@Body",[27,49,50],{},"@Req"," 等），但有时需要创建自己的装饰器来复用通用逻辑或提取特定数据。自定义装饰器可以分为",[53,54,55],"strong",{},"参数装饰器","和",[53,58,59],{},"方法\u002F类装饰器","两类。",[62,63,65,66,68],"h3",{"id":64},"一自定义参数装饰器createparamdecorator","一、自定义参数装饰器（",[27,67,33],{},"）",[23,70,71],{},"参数装饰器用于从请求中提取特定数据，比如当前登录用户、分页参数、请求头中的某字段。",[23,73,74,75,77],{},"使用 ",[27,76,33],{}," 工厂函数创建，它接收两个参数：",[79,80,81,88],"ul",{},[82,83,84,87],"li",{},[27,85,86],{},"data","：装饰器传入的静态参数（可选）。",[82,89,90,93,94,97],{},[27,91,92],{},"ctx","：",[27,95,96],{},"ExecutionContext"," 对象，可以获取请求实例。",[23,99,100],{},[53,101,102],{},"示例：提取当前用户",[23,104,105,106,109,110,113],{},"假设在认证守卫中已经将用户信息挂载到 ",[27,107,108],{},"request.user","，我们可以创建一个 ",[27,111,112],{},"@CurrentUser()"," 装饰器直接获取：",[115,116,121],"pre",{"className":117,"code":118,"language":119,"meta":120,"style":120},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","import { createParamDecorator, ExecutionContext } from \"@nestjs\u002Fcommon\";\n\nexport const CurrentUser = createParamDecorator((data: unknown, ctx: ExecutionContext) => {\n  const request = ctx.switchToHttp().getRequest();\n  return request.user; \u002F\u002F 返回用户对象\n});\n","ts","",[27,122,123,166,173,228,260,280],{"__ignoreMap":120},[124,125,128,132,136,140,143,146,149,152,156,160,163],"span",{"class":126,"line":127},"line",1,[124,129,131],{"class":130},"sVHd0","import",[124,133,135],{"class":134},"sP7_E"," {",[124,137,139],{"class":138},"su5hD"," createParamDecorator",[124,141,142],{"class":134},",",[124,144,145],{"class":138}," ExecutionContext",[124,147,148],{"class":134}," }",[124,150,151],{"class":130}," from",[124,153,155],{"class":154},"sjJ54"," \"",[124,157,159],{"class":158},"s_sjI","@nestjs\u002Fcommon",[124,161,162],{"class":154},"\"",[124,164,165],{"class":134},";\n",[124,167,169],{"class":126,"line":168},2,[124,170,172],{"emptyLinePlaceholder":171},true,"\n",[124,174,176,179,183,187,191,194,197,199,202,205,209,211,214,216,219,222,225],{"class":126,"line":175},3,[124,177,178],{"class":130},"export",[124,180,182],{"class":181},"sbsja"," const",[124,184,186],{"class":185},"s_hVV"," CurrentUser",[124,188,190],{"class":189},"smGrS"," =",[124,192,139],{"class":193},"sGLFI",[124,195,196],{"class":138},"(",[124,198,196],{"class":134},[124,200,86],{"class":201},"s99_P",[124,203,204],{"class":189},":",[124,206,208],{"class":207},"sZMiF"," unknown",[124,210,142],{"class":134},[124,212,213],{"class":201}," ctx",[124,215,204],{"class":189},[124,217,145],{"class":218},"sbgvK",[124,220,221],{"class":134},")",[124,223,224],{"class":181}," =>",[124,226,227],{"class":134}," {\n",[124,229,231,234,237,239,241,244,247,251,253,256,258],{"class":126,"line":230},4,[124,232,233],{"class":181},"  const",[124,235,236],{"class":185}," request",[124,238,190],{"class":189},[124,240,213],{"class":138},[124,242,243],{"class":134},".",[124,245,246],{"class":193},"switchToHttp",[124,248,250],{"class":249},"skxfh","()",[124,252,243],{"class":134},[124,254,255],{"class":193},"getRequest",[124,257,250],{"class":249},[124,259,165],{"class":134},[124,261,263,266,268,270,273,276],{"class":126,"line":262},5,[124,264,265],{"class":130},"  return",[124,267,236],{"class":138},[124,269,243],{"class":134},[124,271,272],{"class":138},"user",[124,274,275],{"class":134},";",[124,277,279],{"class":278},"sutJx"," \u002F\u002F 返回用户对象\n",[124,281,283,286,288],{"class":126,"line":282},6,[124,284,285],{"class":134},"}",[124,287,221],{"class":138},[124,289,165],{"class":134},[23,291,292],{},"在控制器中使用：",[115,294,296],{"className":117,"code":295,"language":119,"meta":120,"style":120},"@Get('profile')\ngetProfile(@CurrentUser() user: UserEntity) {\n  return user;\n}\n",[27,297,298,319,336,345],{"__ignoreMap":120},[124,299,300,303,306,308,311,314,316],{"class":126,"line":127},[124,301,302],{"class":134},"@",[124,304,305],{"class":193},"Get",[124,307,196],{"class":138},[124,309,310],{"class":154},"'",[124,312,313],{"class":158},"profile",[124,315,310],{"class":154},[124,317,318],{"class":138},")\n",[124,320,321,324,327,330,333],{"class":126,"line":168},[124,322,323],{"class":193},"getProfile",[124,325,326],{"class":138},"(@",[124,328,329],{"class":193},"CurrentUser",[124,331,332],{"class":138},"() user: UserEntity) ",[124,334,335],{"class":134},"{\n",[124,337,338,340,343],{"class":126,"line":175},[124,339,265],{"class":130},[124,341,342],{"class":138}," user",[124,344,165],{"class":134},[124,346,347],{"class":126,"line":230},[124,348,349],{"class":134},"}\n",[23,351,352,93,355,357,358,360],{},[53,353,354],{},"传递参数给装饰器",[27,356,33],{}," 的第一个参数 ",[27,359,86],{}," 就是调用时传入的值。",[115,362,364],{"className":117,"code":363,"language":119,"meta":120,"style":120},"export const CurrentUser = createParamDecorator(\n  (key: string, ctx: ExecutionContext) => {\n    const user = ctx.switchToHttp().getRequest().user;\n    return key ? user?.[key] : user;\n  },\n);\n\n\u002F\u002F 使用\n@Get()\ngetUserEmail(@CurrentUser('email') email: string) { ... }\n",[27,365,366,381,408,437,467,472,478,483,489,499],{"__ignoreMap":120},[124,367,368,370,372,374,376,378],{"class":126,"line":127},[124,369,178],{"class":130},[124,371,182],{"class":181},[124,373,186],{"class":185},[124,375,190],{"class":189},[124,377,139],{"class":193},[124,379,380],{"class":138},"(\n",[124,382,383,386,389,391,394,396,398,400,402,404,406],{"class":126,"line":168},[124,384,385],{"class":134},"  (",[124,387,388],{"class":201},"key",[124,390,204],{"class":189},[124,392,393],{"class":207}," string",[124,395,142],{"class":134},[124,397,213],{"class":201},[124,399,204],{"class":189},[124,401,145],{"class":218},[124,403,221],{"class":134},[124,405,224],{"class":181},[124,407,227],{"class":134},[124,409,410,413,415,417,419,421,423,425,427,429,431,433,435],{"class":126,"line":175},[124,411,412],{"class":181},"    const",[124,414,342],{"class":185},[124,416,190],{"class":189},[124,418,213],{"class":138},[124,420,243],{"class":134},[124,422,246],{"class":193},[124,424,250],{"class":249},[124,426,243],{"class":134},[124,428,255],{"class":193},[124,430,250],{"class":249},[124,432,243],{"class":134},[124,434,272],{"class":138},[124,436,165],{"class":134},[124,438,439,442,445,448,450,453,456,458,461,463,465],{"class":126,"line":230},[124,440,441],{"class":130},"    return",[124,443,444],{"class":138}," key",[124,446,447],{"class":189}," ?",[124,449,342],{"class":138},[124,451,452],{"class":134},"?.",[124,454,455],{"class":249},"[",[124,457,388],{"class":138},[124,459,460],{"class":249},"] ",[124,462,204],{"class":189},[124,464,342],{"class":138},[124,466,165],{"class":134},[124,468,469],{"class":126,"line":262},[124,470,471],{"class":134},"  },\n",[124,473,474,476],{"class":126,"line":282},[124,475,221],{"class":138},[124,477,165],{"class":134},[124,479,481],{"class":126,"line":480},7,[124,482,172],{"emptyLinePlaceholder":171},[124,484,486],{"class":126,"line":485},8,[124,487,488],{"class":278},"\u002F\u002F 使用\n",[124,490,492,494,496],{"class":126,"line":491},9,[124,493,302],{"class":134},[124,495,305],{"class":193},[124,497,498],{"class":138},"()\n",[124,500,502,505,507,509,511,513,516,518,521,524,527],{"class":126,"line":501},10,[124,503,504],{"class":193},"getUserEmail",[124,506,326],{"class":138},[124,508,329],{"class":193},[124,510,196],{"class":138},[124,512,310],{"class":154},[124,514,515],{"class":158},"email",[124,517,310],{"class":154},[124,519,520],{"class":138},") email: string) ",[124,522,523],{"class":134},"{",[124,525,526],{"class":189}," ...",[124,528,529],{"class":134}," }\n",[62,531,533],{"id":532},"二结合管道使用","二、结合管道使用",[23,535,536,537,41,539,542],{},"自定义参数装饰器可以像内置参数装饰器（",[27,538,47],{},[27,540,541],{},"@Query","）一样与管道配合使用。只需在参数声明时添加管道即可。",[115,544,546],{"className":117,"code":545,"language":119,"meta":120,"style":120},"@Get(':id')\nfindOne(\n  @Param('id', ParseIntPipe) id: number,\n  @CurrentUser(ValidationPipe) user: UserEntity,\n) { ... }\n",[27,547,548,565,572,597,608],{"__ignoreMap":120},[124,549,550,552,554,556,558,561,563],{"class":126,"line":127},[124,551,302],{"class":134},[124,553,305],{"class":193},[124,555,196],{"class":138},[124,557,310],{"class":154},[124,559,560],{"class":158},":id",[124,562,310],{"class":154},[124,564,318],{"class":138},[124,566,567,570],{"class":126,"line":168},[124,568,569],{"class":193},"findOne",[124,571,380],{"class":138},[124,573,574,577,580,582,584,587,589,591,594],{"class":126,"line":175},[124,575,576],{"class":138},"  @",[124,578,579],{"class":193},"Param",[124,581,196],{"class":138},[124,583,310],{"class":154},[124,585,586],{"class":158},"id",[124,588,310],{"class":154},[124,590,142],{"class":134},[124,592,593],{"class":138}," ParseIntPipe) id: number",[124,595,596],{"class":134},",\n",[124,598,599,601,603,606],{"class":126,"line":230},[124,600,576],{"class":138},[124,602,329],{"class":193},[124,604,605],{"class":138},"(ValidationPipe) user: UserEntity",[124,607,596],{"class":134},[124,609,610,613,615,617],{"class":126,"line":262},[124,611,612],{"class":138},") ",[124,614,523],{"class":134},[124,616,526],{"class":189},[124,618,529],{"class":134},[23,620,621],{},"管道会在装饰器提取数据之后执行，对提取的结果进行验证或转换。",[62,623,625,626,68],{"id":624},"三组合装饰器applydecorators","三、组合装饰器（",[27,627,628],{},"applyDecorators",[23,630,631,632,634],{},"当需要将多个装饰器组合成一个时，可以使用 ",[27,633,628],{}," 函数。它接受多个装饰器并返回一个组合装饰器。",[23,636,637],{},[53,638,639,640,643],{},"示例：创建一个 ",[27,641,642],{},"@Public()"," 装饰器，用于标记公开路由（跳过认证守卫）",[23,645,646,647,650],{},"通常我们会通过 ",[27,648,649],{},"@SetMetadata"," 设置元数据，然后守卫读取该元数据。可以封装成更语义化的装饰器：",[115,652,654],{"className":117,"code":653,"language":119,"meta":120,"style":120},"import { SetMetadata, applyDecorators } from \"@nestjs\u002Fcommon\";\n\nexport const Public = () =>\n  applyDecorators(\n    SetMetadata(\"isPublic\", true),\n    \u002F\u002F 还可以组合其他装饰器，如 @HttpCode(200) 等\n  );\n",[27,655,656,682,686,704,711,735,740],{"__ignoreMap":120},[124,657,658,660,662,665,667,670,672,674,676,678,680],{"class":126,"line":127},[124,659,131],{"class":130},[124,661,135],{"class":134},[124,663,664],{"class":138}," SetMetadata",[124,666,142],{"class":134},[124,668,669],{"class":138}," applyDecorators",[124,671,148],{"class":134},[124,673,151],{"class":130},[124,675,155],{"class":154},[124,677,159],{"class":158},[124,679,162],{"class":154},[124,681,165],{"class":134},[124,683,684],{"class":126,"line":168},[124,685,172],{"emptyLinePlaceholder":171},[124,687,688,690,692,696,698,701],{"class":126,"line":175},[124,689,178],{"class":130},[124,691,182],{"class":181},[124,693,695],{"class":694},"sfCm-"," Public",[124,697,190],{"class":189},[124,699,700],{"class":134}," ()",[124,702,703],{"class":181}," =>\n",[124,705,706,709],{"class":126,"line":230},[124,707,708],{"class":193},"  applyDecorators",[124,710,380],{"class":138},[124,712,713,716,718,720,723,725,727,731,733],{"class":126,"line":262},[124,714,715],{"class":193},"    SetMetadata",[124,717,196],{"class":138},[124,719,162],{"class":154},[124,721,722],{"class":158},"isPublic",[124,724,162],{"class":154},[124,726,142],{"class":134},[124,728,730],{"class":729},"syTEX"," true",[124,732,221],{"class":138},[124,734,596],{"class":134},[124,736,737],{"class":126,"line":282},[124,738,739],{"class":278},"    \u002F\u002F 还可以组合其他装饰器，如 @HttpCode(200) 等\n",[124,741,742,745],{"class":126,"line":480},[124,743,744],{"class":138},"  )",[124,746,165],{"class":134},[23,748,749,750,752,753,755],{},"然后在控制器方法上使用 ",[27,751,642],{},"，守卫中检查 ",[27,754,722],{}," 元数据即可。",[23,757,758,761,762,765],{},[53,759,760],{},"批量组合示例","：创建一个 ",[27,763,764],{},"@ApiPaginatedResponse"," 装饰器，组合 Swagger 装饰器。",[115,767,769],{"className":117,"code":768,"language":119,"meta":120,"style":120},"import { applyDecorators, Type } from \"@nestjs\u002Fcommon\";\nimport { ApiOkResponse, getSchemaPath } from \"@nestjs\u002Fswagger\";\n\nexport function ApiPaginatedResponse\u003CT extends Type\u003Cany>>(model: T) {\n  return applyDecorators(\n    ApiOkResponse({\n      schema: {\n        allOf: [\n          { properties: { data: { type: \"array\", items: { $ref: getSchemaPath(model) } } } },\n          { properties: { total: { type: \"number\" } } },\n        ],\n      },\n    }),\n  );\n}\n",[27,770,771,796,823,827,868,876,885,894,904,966,1000,1008,1014,1024,1031],{"__ignoreMap":120},[124,772,773,775,777,779,781,784,786,788,790,792,794],{"class":126,"line":127},[124,774,131],{"class":130},[124,776,135],{"class":134},[124,778,669],{"class":138},[124,780,142],{"class":134},[124,782,783],{"class":138}," Type",[124,785,148],{"class":134},[124,787,151],{"class":130},[124,789,155],{"class":154},[124,791,159],{"class":158},[124,793,162],{"class":154},[124,795,165],{"class":134},[124,797,798,800,802,805,807,810,812,814,816,819,821],{"class":126,"line":168},[124,799,131],{"class":130},[124,801,135],{"class":134},[124,803,804],{"class":138}," ApiOkResponse",[124,806,142],{"class":134},[124,808,809],{"class":138}," getSchemaPath",[124,811,148],{"class":134},[124,813,151],{"class":130},[124,815,155],{"class":154},[124,817,818],{"class":158},"@nestjs\u002Fswagger",[124,820,162],{"class":154},[124,822,165],{"class":134},[124,824,825],{"class":126,"line":175},[124,826,172],{"emptyLinePlaceholder":171},[124,828,829,831,834,837,840,843,846,848,850,853,856,859,861,864,866],{"class":126,"line":230},[124,830,178],{"class":130},[124,832,833],{"class":181}," function",[124,835,836],{"class":193}," ApiPaginatedResponse",[124,838,839],{"class":134},"\u003C",[124,841,842],{"class":218},"T",[124,844,845],{"class":181}," extends",[124,847,783],{"class":218},[124,849,839],{"class":134},[124,851,852],{"class":207},"any",[124,854,855],{"class":134},">>(",[124,857,858],{"class":201},"model",[124,860,204],{"class":189},[124,862,863],{"class":218}," T",[124,865,221],{"class":134},[124,867,227],{"class":134},[124,869,870,872,874],{"class":126,"line":262},[124,871,265],{"class":130},[124,873,669],{"class":193},[124,875,380],{"class":249},[124,877,878,881,883],{"class":126,"line":282},[124,879,880],{"class":193},"    ApiOkResponse",[124,882,196],{"class":249},[124,884,335],{"class":134},[124,886,887,890,892],{"class":126,"line":480},[124,888,889],{"class":249},"      schema",[124,891,204],{"class":134},[124,893,227],{"class":134},[124,895,896,899,901],{"class":126,"line":485},[124,897,898],{"class":249},"        allOf",[124,900,204],{"class":134},[124,902,903],{"class":249}," [\n",[124,905,906,909,912,914,916,919,921,923,926,928,930,933,935,937,940,942,944,947,949,951,953,955,957,959,961,963],{"class":126,"line":491},[124,907,908],{"class":134},"          {",[124,910,911],{"class":249}," properties",[124,913,204],{"class":134},[124,915,135],{"class":134},[124,917,918],{"class":249}," data",[124,920,204],{"class":134},[124,922,135],{"class":134},[124,924,925],{"class":249}," type",[124,927,204],{"class":134},[124,929,155],{"class":154},[124,931,932],{"class":158},"array",[124,934,162],{"class":154},[124,936,142],{"class":134},[124,938,939],{"class":249}," items",[124,941,204],{"class":134},[124,943,135],{"class":134},[124,945,946],{"class":249}," $ref",[124,948,204],{"class":134},[124,950,809],{"class":193},[124,952,196],{"class":249},[124,954,858],{"class":138},[124,956,612],{"class":249},[124,958,285],{"class":134},[124,960,148],{"class":134},[124,962,148],{"class":134},[124,964,965],{"class":134}," },\n",[124,967,968,970,972,974,976,979,981,983,985,987,989,992,994,996,998],{"class":126,"line":501},[124,969,908],{"class":134},[124,971,911],{"class":249},[124,973,204],{"class":134},[124,975,135],{"class":134},[124,977,978],{"class":249}," total",[124,980,204],{"class":134},[124,982,135],{"class":134},[124,984,925],{"class":249},[124,986,204],{"class":134},[124,988,155],{"class":154},[124,990,991],{"class":158},"number",[124,993,162],{"class":154},[124,995,148],{"class":134},[124,997,148],{"class":134},[124,999,965],{"class":134},[124,1001,1003,1006],{"class":126,"line":1002},11,[124,1004,1005],{"class":249},"        ]",[124,1007,596],{"class":134},[124,1009,1011],{"class":126,"line":1010},12,[124,1012,1013],{"class":134},"      },\n",[124,1015,1017,1020,1022],{"class":126,"line":1016},13,[124,1018,1019],{"class":134},"    }",[124,1021,221],{"class":249},[124,1023,596],{"class":134},[124,1025,1027,1029],{"class":126,"line":1026},14,[124,1028,744],{"class":249},[124,1030,165],{"class":134},[124,1032,1034],{"class":126,"line":1033},15,[124,1035,349],{"class":134},[62,1037,1039,1040,1042],{"id":1038},"四自定义方法类装饰器基于-setmetadata-与反射","四、自定义方法\u002F类装饰器（基于 ",[27,1041,29],{}," 与反射）",[23,1044,1045,1046,1048,1049,1052],{},"除了参数装饰器，还可以为方法或类创建装饰器，通常会结合 ",[27,1047,29],{}," 设置元数据，然后在守卫或拦截器中通过 ",[27,1050,1051],{},"Reflector"," 读取。",[1054,1055,1057,1058],"h4",{"id":1056},"_1-直接使用-setmetadata","1. 直接使用 ",[27,1059,29],{},[115,1061,1063],{"className":117,"code":1062,"language":119,"meta":120,"style":120},"@SetMetadata('roles', ['admin'])\n@Get()\nfindAll() { ... }\n",[27,1064,1065,1095,1103],{"__ignoreMap":120},[124,1066,1067,1069,1071,1073,1075,1078,1080,1082,1085,1087,1090,1092],{"class":126,"line":127},[124,1068,302],{"class":134},[124,1070,29],{"class":193},[124,1072,196],{"class":138},[124,1074,310],{"class":154},[124,1076,1077],{"class":158},"roles",[124,1079,310],{"class":154},[124,1081,142],{"class":134},[124,1083,1084],{"class":138}," [",[124,1086,310],{"class":154},[124,1088,1089],{"class":158},"admin",[124,1091,310],{"class":154},[124,1093,1094],{"class":138},"])\n",[124,1096,1097,1099,1101],{"class":126,"line":168},[124,1098,302],{"class":134},[124,1100,305],{"class":193},[124,1102,498],{"class":138},[124,1104,1105,1108,1111,1113,1115],{"class":126,"line":175},[124,1106,1107],{"class":193},"findAll",[124,1109,1110],{"class":138},"() ",[124,1112,523],{"class":134},[124,1114,526],{"class":189},[124,1116,529],{"class":134},[1054,1118,1120],{"id":1119},"_2-封装为自定义装饰器","2. 封装为自定义装饰器",[115,1122,1124],{"className":117,"code":1123,"language":119,"meta":120,"style":120},"import { SetMetadata } from \"@nestjs\u002Fcommon\";\n\nexport const Roles = (...roles: string[]) => SetMetadata(\"roles\", roles);\n",[27,1125,1126,1146,1150],{"__ignoreMap":120},[124,1127,1128,1130,1132,1134,1136,1138,1140,1142,1144],{"class":126,"line":127},[124,1129,131],{"class":130},[124,1131,135],{"class":134},[124,1133,664],{"class":138},[124,1135,148],{"class":134},[124,1137,151],{"class":130},[124,1139,155],{"class":154},[124,1141,159],{"class":158},[124,1143,162],{"class":154},[124,1145,165],{"class":134},[124,1147,1148],{"class":126,"line":168},[124,1149,172],{"emptyLinePlaceholder":171},[124,1151,1152,1154,1156,1159,1161,1164,1167,1169,1171,1173,1176,1178,1180,1182,1184,1186,1188,1190,1192,1195],{"class":126,"line":175},[124,1153,178],{"class":130},[124,1155,182],{"class":181},[124,1157,1158],{"class":694}," Roles",[124,1160,190],{"class":189},[124,1162,1163],{"class":134}," (",[124,1165,1166],{"class":189},"...",[124,1168,1077],{"class":201},[124,1170,204],{"class":189},[124,1172,393],{"class":207},[124,1174,1175],{"class":138},"[]",[124,1177,221],{"class":134},[124,1179,224],{"class":181},[124,1181,664],{"class":193},[124,1183,196],{"class":138},[124,1185,162],{"class":154},[124,1187,1077],{"class":158},[124,1189,162],{"class":154},[124,1191,142],{"class":134},[124,1193,1194],{"class":138}," roles)",[124,1196,165],{"class":134},[23,1198,1199],{},"使用：",[115,1201,1203],{"className":117,"code":1202,"language":119,"meta":120,"style":120},"@Roles('admin', 'moderator')\n@Delete(':id')\nremove(@Param('id') id: number) { ... }\n",[27,1204,1205,1232,1249],{"__ignoreMap":120},[124,1206,1207,1209,1212,1214,1216,1218,1220,1222,1225,1228,1230],{"class":126,"line":127},[124,1208,302],{"class":134},[124,1210,1211],{"class":193},"Roles",[124,1213,196],{"class":138},[124,1215,310],{"class":154},[124,1217,1089],{"class":158},[124,1219,310],{"class":154},[124,1221,142],{"class":134},[124,1223,1224],{"class":154}," '",[124,1226,1227],{"class":158},"moderator",[124,1229,310],{"class":154},[124,1231,318],{"class":138},[124,1233,1234,1236,1239,1241,1243,1245,1247],{"class":126,"line":168},[124,1235,302],{"class":134},[124,1237,1238],{"class":193},"Delete",[124,1240,196],{"class":138},[124,1242,310],{"class":154},[124,1244,560],{"class":158},[124,1246,310],{"class":154},[124,1248,318],{"class":138},[124,1250,1251,1254,1256,1258,1260,1262,1264,1266,1269,1271,1273],{"class":126,"line":175},[124,1252,1253],{"class":193},"remove",[124,1255,326],{"class":138},[124,1257,579],{"class":193},[124,1259,196],{"class":138},[124,1261,310],{"class":154},[124,1263,586],{"class":158},[124,1265,310],{"class":154},[124,1267,1268],{"class":138},") id: number) ",[124,1270,523],{"class":134},[124,1272,526],{"class":189},[124,1274,529],{"class":134},[1054,1276,1278],{"id":1277},"_3-组合多个元数据","3. 组合多个元数据",[115,1280,1282],{"className":117,"code":1281,"language":119,"meta":120,"style":120},"import { applyDecorators, SetMetadata } from \"@nestjs\u002Fcommon\";\n\nexport const Auth = (role: string) =>\n  applyDecorators(SetMetadata(\"role\", role), SetMetadata(\"requiresAuth\", true));\n",[27,1283,1284,1308,1312,1336],{"__ignoreMap":120},[124,1285,1286,1288,1290,1292,1294,1296,1298,1300,1302,1304,1306],{"class":126,"line":127},[124,1287,131],{"class":130},[124,1289,135],{"class":134},[124,1291,669],{"class":138},[124,1293,142],{"class":134},[124,1295,664],{"class":138},[124,1297,148],{"class":134},[124,1299,151],{"class":130},[124,1301,155],{"class":154},[124,1303,159],{"class":158},[124,1305,162],{"class":154},[124,1307,165],{"class":134},[124,1309,1310],{"class":126,"line":168},[124,1311,172],{"emptyLinePlaceholder":171},[124,1313,1314,1316,1318,1321,1323,1325,1328,1330,1332,1334],{"class":126,"line":175},[124,1315,178],{"class":130},[124,1317,182],{"class":181},[124,1319,1320],{"class":694}," Auth",[124,1322,190],{"class":189},[124,1324,1163],{"class":134},[124,1326,1327],{"class":201},"role",[124,1329,204],{"class":189},[124,1331,393],{"class":207},[124,1333,221],{"class":134},[124,1335,703],{"class":181},[124,1337,1338,1340,1342,1344,1346,1348,1350,1352,1354,1357,1359,1361,1363,1365,1368,1370,1372,1374,1377],{"class":126,"line":230},[124,1339,708],{"class":193},[124,1341,196],{"class":138},[124,1343,29],{"class":193},[124,1345,196],{"class":138},[124,1347,162],{"class":154},[124,1349,1327],{"class":158},[124,1351,162],{"class":154},[124,1353,142],{"class":134},[124,1355,1356],{"class":138}," role)",[124,1358,142],{"class":134},[124,1360,664],{"class":193},[124,1362,196],{"class":138},[124,1364,162],{"class":154},[124,1366,1367],{"class":158},"requiresAuth",[124,1369,162],{"class":154},[124,1371,142],{"class":134},[124,1373,730],{"class":729},[124,1375,1376],{"class":138},"))",[124,1378,165],{"class":134},[23,1380,1381,1382,1384],{},"然后在守卫中通过 ",[27,1383,1051],{}," 读取：",[115,1386,1388],{"className":117,"code":1387,"language":119,"meta":120,"style":120},"const role = this.reflector.get\u003Cstring>(\"role\", context.getHandler());\nconst requiresAuth = this.reflector.get\u003Cboolean>(\"requiresAuth\", context.getHandler());\n",[27,1389,1390,1445],{"__ignoreMap":120},[124,1391,1392,1395,1398,1400,1404,1406,1409,1411,1414,1416,1419,1422,1424,1426,1428,1430,1432,1435,1437,1440,1443],{"class":126,"line":127},[124,1393,1394],{"class":181},"const",[124,1396,1397],{"class":185}," role",[124,1399,190],{"class":189},[124,1401,1403],{"class":1402},"s39Yj"," this",[124,1405,243],{"class":134},[124,1407,1408],{"class":138},"reflector",[124,1410,243],{"class":134},[124,1412,1413],{"class":193},"get",[124,1415,839],{"class":134},[124,1417,1418],{"class":207},"string",[124,1420,1421],{"class":134},">",[124,1423,196],{"class":138},[124,1425,162],{"class":154},[124,1427,1327],{"class":158},[124,1429,162],{"class":154},[124,1431,142],{"class":134},[124,1433,1434],{"class":138}," context",[124,1436,243],{"class":134},[124,1438,1439],{"class":193},"getHandler",[124,1441,1442],{"class":138},"())",[124,1444,165],{"class":134},[124,1446,1447,1449,1452,1454,1456,1458,1460,1462,1464,1466,1469,1471,1473,1475,1477,1479,1481,1483,1485,1487,1489],{"class":126,"line":168},[124,1448,1394],{"class":181},[124,1450,1451],{"class":185}," requiresAuth",[124,1453,190],{"class":189},[124,1455,1403],{"class":1402},[124,1457,243],{"class":134},[124,1459,1408],{"class":138},[124,1461,243],{"class":134},[124,1463,1413],{"class":193},[124,1465,839],{"class":134},[124,1467,1468],{"class":207},"boolean",[124,1470,1421],{"class":134},[124,1472,196],{"class":138},[124,1474,162],{"class":154},[124,1476,1367],{"class":158},[124,1478,162],{"class":154},[124,1480,142],{"class":134},[124,1482,1434],{"class":138},[124,1484,243],{"class":134},[124,1486,1439],{"class":193},[124,1488,1442],{"class":138},[124,1490,165],{"class":134},[1054,1492,1494],{"id":1493},"_4-更复杂的类装饰器","4. 更复杂的类装饰器",[23,1496,1497,1498,1501],{},"可以通过 ",[27,1499,1500],{},"@Injectable()"," 或自定义类装饰器来实现类似继承的功能，但更常见的是用元数据 + 守卫。",[62,1503,1505],{"id":1504},"五总结","五、总结",[1507,1508,1509,1525],"table",{},[1510,1511,1512],"thead",{},[1513,1514,1515,1519,1522],"tr",{},[1516,1517,1518],"th",{},"类型",[1516,1520,1521],{},"创建方式",[1516,1523,1524],{},"典型用途",[1526,1527,1528,1542,1556],"tbody",{},[1513,1529,1530,1535,1539],{},[1531,1532,1533],"td",{},[53,1534,55],{},[1531,1536,1537],{},[27,1538,33],{},[1531,1540,1541],{},"提取请求数据（用户、分页、设备信息）",[1513,1543,1544,1549,1553],{},[1531,1545,1546],{},[53,1547,1548],{},"组合装饰器",[1531,1550,1551],{},[27,1552,628],{},[1531,1554,1555],{},"聚合多个装饰器，简化代码",[1513,1557,1558,1562,1569],{},[1531,1559,1560],{},[53,1561,59],{},[1531,1563,1564,1566,1567],{},[27,1565,29],{}," + ",[27,1568,1051],{},[1531,1570,1571],{},"设置权限、公开路由等元数据",[23,1573,1574],{},"自定义装饰器是 NestJS 中非常强大的扩展能力，能帮助你将横切关注点从控制器中抽离，使代码更简洁、可维护。",[1576,1577,1578],"h2",{"id":1578},"参考文献",[23,1580,1581],{},"以下链接在编写时均可正常访问：",[1507,1583,1584,1594],{},[1510,1585,1586],{},[1513,1587,1588,1591],{},[1516,1589,1590],{},"资料",[1516,1592,1593],{},"说明",[1526,1595,1596,1610,1622],{},[1513,1597,1598,1607],{},[1531,1599,1600],{},[1601,1602,1606],"a",{"href":1603,"rel":1604},"https:\u002F\u002Fdocs.nestjs.com\u002F",[1605],"nofollow","NestJS 文档",[1531,1608,1609],{},"官方",[1513,1611,1612,1619],{},[1531,1613,1614],{},[1601,1615,1618],{"href":1616,"rel":1617},"https:\u002F\u002Fdocs.nestjs.com\u002Fcustom-decorators",[1605],"Custom decorators",[1531,1620,1621],{},"本章主题",[1513,1623,1624,1631],{},[1531,1625,1626],{},[1601,1627,1630],{"href":1628,"rel":1629},"https:\u002F\u002Fdocs.nestjs.com\u002Ffaq\u002Frequest-lifecycle",[1605],"Request lifecycle",[1531,1632,1633],{},"执行顺序",[1635,1636,1637],"style",{},"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 .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}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 .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--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 .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 .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 .sfCm-, html code.shiki .sfCm-{--shiki-light:#90A4AE;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .syTEX, html code.shiki .syTEX{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":120,"searchDepth":168,"depth":168,"links":1639},[1640,1642,1643,1645,1647,1648],{"id":64,"depth":175,"text":1641},"一、自定义参数装饰器（createParamDecorator）",{"id":532,"depth":175,"text":533},{"id":624,"depth":175,"text":1644},"三、组合装饰器（applyDecorators）",{"id":1038,"depth":175,"text":1646},"四、自定义方法\u002F类装饰器（基于 SetMetadata 与反射）",{"id":1504,"depth":175,"text":1505},{"id":1578,"depth":168,"text":1578},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fend\u002FNestJS\u002Fnew\u002Fcustom-decorators","knowledge\u002Fknowledge\u002Fbooklet\u002Fend\u002FNestJS\u002Fnew\u002Fcustom-decorators.md","md",{"items":1654},[1655,1659,1663,1667,1671,1675],{"path":1656,"title":1657,"description":1658,"category":10,"score":282},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fauth","认证与授权","认证（Authentication）：确认「你是谁」（如 JWT、Session）。 - 授权（Authorization）：确认「你能做什么」（如 RBAC、策略检查）。",{"path":1660,"title":1661,"description":1662,"category":10,"score":282},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcache","缓存","@nestjs\u002Fcache-manager 统一缓存 API，存储实现可插拔。",{"path":1664,"title":1665,"description":1666,"category":10,"score":282},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fservice","提供者与服务","Service 是最常见的 Provider，封装业务逻辑。见 module。",{"path":1668,"title":1669,"description":1670,"category":10,"score":282},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fguards","守卫 (Guards) 与授权","守卫决定是否放行请求，常用于认证与授权。见 auth。",{"path":1672,"title":1673,"description":1674,"category":10,"score":282},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fconfig","配置管理（Config 模块）","@nestjs\u002Fconfig 加载 .env 并提供 ConfigService。见 工程化 env。",{"path":1676,"title":1677,"description":1678,"category":10,"score":282},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fdatabase","数据库集成（以 TypeORM 为例）","Nest 通过 @nestjs\u002Ftypeorm 等包集成 ORM；生产环境用 migration，慎用 synchronize。亦可选用 Prisma、MikroORM 等（见 官方 Database）。",{"series":1680,"title":1681,"items":1682,"index":282,"prev":1743,"next":1744},"booklet\u002Fend\u002Fnestjs\u002Fnew","new",[1683,1685,1689,1691,1693,1697,1701,1702,1704,1708,1712,1714,1718,1722,1726,1730,1733,1737,1739],{"path":1656,"title":1657,"date":1684,"pinned":15},"2025-12-23",{"path":1686,"title":1687,"date":1688,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fbase","NestJS 基础","2021-03-09",{"path":1660,"title":1661,"date":1690,"pinned":15},"2025-07-04",{"path":1672,"title":1673,"date":1692,"pinned":15},"2023-08-26",{"path":1694,"title":1695,"date":1696,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcontroller","控制器与路由","2020-07-18",{"path":1698,"title":1699,"date":1700,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcron","定时任务","2023-04-12",{"path":7,"title":8,"date":14,"pinned":15},{"path":1676,"title":1677,"date":1703,"pinned":15},"2023-05-26",{"path":1705,"title":1706,"date":1707,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fdeploy","部署与运维","2023-01-24",{"path":1709,"title":1710,"date":1711,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Ferror","异常处理","2020-01-28",{"path":1668,"title":1669,"date":1713,"pinned":15},"2023-09-09",{"path":1715,"title":1716,"date":1717,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fintercept","拦截器 (Interceptors)","2021-04-26",{"path":1719,"title":1720,"date":1721,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Flogger","日志记录","2022-10-10",{"path":1723,"title":1724,"date":1725,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fmiddleware","中间件 (Middleware)","2021-11-26",{"path":1727,"title":1728,"date":1729,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fmodule","模块系统与依赖注入","2022-06-15",{"path":1731,"title":1732,"date":1696,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fpipes","管道 (Pipes) 与数据验证",{"path":1734,"title":1735,"date":1736,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fqueue","队列（Bull）","2021-07-01",{"path":1664,"title":1665,"date":1738,"pinned":15},"2024-12-02",{"path":1740,"title":1741,"date":1742,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fwebsocket","WebSocket（网关）","2022-12-14",{"path":1698,"title":1699,"date":1700,"pinned":15},{"path":1676,"title":1677,"date":1703,"pinned":15}]