[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fwebsocket":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fwebsocket":1811,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fwebsocket":1836},{"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":1807,"stem":1808,"id":1809,"extension":1810},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fwebsocket","WebSocket（网关）","WebSocket 通过 @WebSocketGateway 实现，常配合 Socket.IO。","技能小册",[12,13],"nestjs","websocket","2022-12-14",false,{"type":17,"value":18,"toc":1797},"minimark",[19,23,32,52,56,64,75,335,341,412,419,422,452,466,473,480,611,616,654,658,661,730,734,738,743,767,773,792,796,823,827,847,948,954,958,965,993,1011,1015,1018,1023,1235,1262,1265,1269,1273,1286,1477,1480,1487,1595,1598,1635,1638,1715,1737,1741,1744,1793],[20,21,8],"h1",{"id":22},"websocket网关",[24,25,26,27,31],"p",{},"WebSocket 通过 ",[28,29,30],"code",{},"@WebSocketGateway"," 实现，常配合 Socket.IO。",[24,33,34,35,39,40,43,44,47,48,51],{},"在 NestJS 中，WebSocket 功能通过 ",[36,37,38],"strong",{},"网关（Gateway）"," 来实现。网关是一个使用 ",[28,41,42],{},"@WebSocketGateway()"," 装饰器标记的类，提供了与 HTTP 控制器（Controller）类似的、基于装饰器的 WebSocket 服务器开发体验，是目前构建实时服务的主流选择。得益于其良好的平台抽象，NestJS 的网关可以兼容 ",[28,45,46],{},"socket.io"," 和原生 ",[28,49,50],{},"ws"," 等多个 WebSocket 库。",[53,54,55],"h3",{"id":55},"核心构件",[57,58,60,61,63],"h4",{"id":59},"_1-websocketgateway-装饰器","1. ",[28,62,42],{}," 装饰器",[24,65,66,67,70,71,74],{},"用于将一个类标记为 WebSocket 网关。它可以接收一个配置对象来指定端口、命名空间和跨域资源共享（CORS）等选项。从功能上看，它类似 HTTP 中的 ",[28,68,69],{},"@Controller()","，但与之并列，是与 HTTP 层平行的 WebSocket 独立网关，通常不建议通过注入 ",[28,72,73],{},"HttpService"," 等方式将其与 HTTP 处理器组在一起工作。",[76,77,82],"pre",{"className":78,"code":79,"language":80,"meta":81,"style":81},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark","import {\n  WebSocketGateway,\n  WebSocketServer,\n  SubscribeMessage,\n  OnGatewayInit,\n  OnGatewayConnection,\n  OnGatewayDisconnect,\n} from \"@nestjs\u002Fwebsockets\";\nimport { Server, Socket } from \"socket.io\";\n\n@WebSocketGateway({\n  cors: { origin: \"*\" }, \u002F\u002F 允许跨域（开发环境），生产环境需细化\n  namespace: \"chat\", \u002F\u002F 可选命名空间\n  \u002F\u002F port: 3001,         \u002F\u002F 不指定则使用 HTTP 服务器端口\n})\nexport class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {\n  \u002F\u002F ... 实现生命周期方法\n}\n","typescript","",[28,83,84,97,107,115,123,131,139,147,170,200,207,223,254,274,283,291,323,329],{"__ignoreMap":81},[85,86,89,93],"span",{"class":87,"line":88},"line",1,[85,90,92],{"class":91},"sVHd0","import",[85,94,96],{"class":95},"sP7_E"," {\n",[85,98,100,104],{"class":87,"line":99},2,[85,101,103],{"class":102},"su5hD","  WebSocketGateway",[85,105,106],{"class":95},",\n",[85,108,110,113],{"class":87,"line":109},3,[85,111,112],{"class":102},"  WebSocketServer",[85,114,106],{"class":95},[85,116,118,121],{"class":87,"line":117},4,[85,119,120],{"class":102},"  SubscribeMessage",[85,122,106],{"class":95},[85,124,126,129],{"class":87,"line":125},5,[85,127,128],{"class":102},"  OnGatewayInit",[85,130,106],{"class":95},[85,132,134,137],{"class":87,"line":133},6,[85,135,136],{"class":102},"  OnGatewayConnection",[85,138,106],{"class":95},[85,140,142,145],{"class":87,"line":141},7,[85,143,144],{"class":102},"  OnGatewayDisconnect",[85,146,106],{"class":95},[85,148,150,153,156,160,164,167],{"class":87,"line":149},8,[85,151,152],{"class":95},"}",[85,154,155],{"class":91}," from",[85,157,159],{"class":158},"sjJ54"," \"",[85,161,163],{"class":162},"s_sjI","@nestjs\u002Fwebsockets",[85,165,166],{"class":158},"\"",[85,168,169],{"class":95},";\n",[85,171,173,175,178,181,184,187,190,192,194,196,198],{"class":87,"line":172},9,[85,174,92],{"class":91},[85,176,177],{"class":95}," {",[85,179,180],{"class":102}," Server",[85,182,183],{"class":95},",",[85,185,186],{"class":102}," Socket",[85,188,189],{"class":95}," }",[85,191,155],{"class":91},[85,193,159],{"class":158},[85,195,46],{"class":162},[85,197,166],{"class":158},[85,199,169],{"class":95},[85,201,203],{"class":87,"line":202},10,[85,204,206],{"emptyLinePlaceholder":205},true,"\n",[85,208,210,213,217,220],{"class":87,"line":209},11,[85,211,212],{"class":95},"@",[85,214,216],{"class":215},"sGLFI","WebSocketGateway",[85,218,219],{"class":102},"(",[85,221,222],{"class":95},"{\n",[85,224,226,230,233,235,238,240,242,245,247,250],{"class":87,"line":225},12,[85,227,229],{"class":228},"skxfh","  cors",[85,231,232],{"class":95},":",[85,234,177],{"class":95},[85,236,237],{"class":228}," origin",[85,239,232],{"class":95},[85,241,159],{"class":158},[85,243,244],{"class":162},"*",[85,246,166],{"class":158},[85,248,249],{"class":95}," },",[85,251,253],{"class":252},"sutJx"," \u002F\u002F 允许跨域（开发环境），生产环境需细化\n",[85,255,257,260,262,264,267,269,271],{"class":87,"line":256},13,[85,258,259],{"class":228},"  namespace",[85,261,232],{"class":95},[85,263,159],{"class":158},[85,265,266],{"class":162},"chat",[85,268,166],{"class":158},[85,270,183],{"class":95},[85,272,273],{"class":252}," \u002F\u002F 可选命名空间\n",[85,275,277,280],{"class":87,"line":276},14,[85,278,279],{"class":252},"  \u002F\u002F port: 3001,",[85,281,282],{"class":252},"         \u002F\u002F 不指定则使用 HTTP 服务器端口\n",[85,284,286,288],{"class":87,"line":285},15,[85,287,152],{"class":95},[85,289,290],{"class":102},")\n",[85,292,294,297,301,305,308,311,313,316,318,321],{"class":87,"line":293},16,[85,295,296],{"class":91},"export",[85,298,300],{"class":299},"sbsja"," class",[85,302,304],{"class":303},"sbgvK"," ChatGateway",[85,306,307],{"class":299}," implements",[85,309,310],{"class":303}," OnGatewayInit",[85,312,183],{"class":95},[85,314,315],{"class":303}," OnGatewayConnection",[85,317,183],{"class":95},[85,319,320],{"class":303}," OnGatewayDisconnect",[85,322,96],{"class":95},[85,324,326],{"class":87,"line":325},17,[85,327,328],{"class":252},"  \u002F\u002F ... 实现生命周期方法\n",[85,330,332],{"class":87,"line":331},18,[85,333,334],{"class":95},"}\n",[24,336,337,340],{},[36,338,339],{},"常见的配置选项","：",[342,343,344,363,385],"ul",{},[345,346,347,350,351,354,355,358,359,362],"li",{},[36,348,349],{},"port","：指定 WebSocket 服务器的端口。默认为与 HTTP 服务器相同的端口（例如 ",[28,352,353],{},"3000","）。如果显式指定使用 ",[28,356,357],{},"port: 3001","，但同端口 ",[28,360,361],{},"3001"," 已被其他服务占用，就会导致冲突并报错。故额外指定端口时，需确保其空闲。",[345,364,365,368,369,372,373,376,377,380,381,384],{},[36,366,367],{},"namespace","：命名空间用于将连接分组。默认根命名空间是 ",[28,370,371],{},"\u002F","（空字符串）。通过 ",[28,374,375],{},"namespace: 'chat'"," 可以让客户端连接到 ",[28,378,379],{},"ws:\u002F\u002Flocalhost\u002Fchat"," 或 ",[28,382,383],{},"https:\u002F\u002Fdomain\u002Fchat","（Socket.IO）。",[345,386,387,390,391],{},[36,388,389],{},"cors","：跨域资源共享配置，对于 WebSocket 环境尤为重要。\n",[342,392,393,402],{},[345,394,395,396,398,399,401],{},"Socket.IO 下直接在 ",[28,397,42],{}," 中配置 ",[28,400,389],{}," 对象。",[345,403,404,405,407,408,411],{},"使用原生 ",[28,406,50],{}," 库时，CORS 通常由上层 HTTP 服务器通过 ",[28,409,410],{},"app.enableCors()"," 统一处理（需格外留意中间件的顺序）。",[57,413,415,416,63],{"id":414},"_2-websocketserver-装饰器","2. ",[28,417,418],{},"@WebSocketServer()",[24,420,421],{},"用于获取底层的 WebSocket 服务器实例，以便向所有连接到该网关的客户端广播消息。",[76,423,425],{"className":78,"code":424,"language":80,"meta":81,"style":81},"@WebSocketServer()\nserver: Server;  \u002F\u002F 对于 socket.io，类型为 Server；对于 ws 库，可能是 WebSocketServer\n",[28,426,427,437],{"__ignoreMap":81},[85,428,429,431,434],{"class":87,"line":88},[85,430,212],{"class":95},[85,432,433],{"class":215},"WebSocketServer",[85,435,436],{"class":102},"()\n",[85,438,439,442,444,446,449],{"class":87,"line":99},[85,440,441],{"class":303},"server",[85,443,232],{"class":95},[85,445,180],{"class":102},[85,447,448],{"class":95},";",[85,450,451],{"class":252},"  \u002F\u002F 对于 socket.io，类型为 Server；对于 ws 库，可能是 WebSocketServer\n",[24,453,454,455,457,458,461,462,465],{},"获取到 ",[28,456,441],{}," 实例后，即可调用 ",[28,459,460],{},"emit"," \u002F ",[28,463,464],{},"send"," 等方法主动推送消息。",[57,467,469,470,63],{"id":468},"_3-subscribemessage-装饰器","3. ",[28,471,472],{},"@SubscribeMessage()",[24,474,475,476,479],{},"用于监听客户端发送的特定事件，是一个",[36,477,478],{},"被动","的消息处理器，需要等待客户端触发事件。",[76,481,483],{"className":78,"code":482,"language":80,"meta":81,"style":81},"@SubscribeMessage('message')\nhandleMessage(client: Socket, payload: any): void {\n  console.log(`Received message: ${payload.text}`);\n  \u002F\u002F 直接返回数据会向当前客户端发送一个以方法名为事件的响应\n  \u002F\u002F 注意：如果返回 `WsResponse`，其 `event` 字段默认与方法名相同；如不特殊关注响应事件名称，可保持简单返回对象。\n  return { event: 'message', data: 'Message received!' };\n}\n",[28,484,485,504,523,561,566,571,607],{"__ignoreMap":81},[85,486,487,489,492,494,497,500,502],{"class":87,"line":88},[85,488,212],{"class":95},[85,490,491],{"class":215},"SubscribeMessage",[85,493,219],{"class":102},[85,495,496],{"class":158},"'",[85,498,499],{"class":162},"message",[85,501,496],{"class":158},[85,503,290],{"class":102},[85,505,506,509,512,514,517,521],{"class":87,"line":99},[85,507,508],{"class":215},"handleMessage",[85,510,511],{"class":102},"(client: Socket",[85,513,183],{"class":95},[85,515,516],{"class":102}," payload: any): ",[85,518,520],{"class":519},"smGrS","void",[85,522,96],{"class":95},[85,524,525,528,531,534,536,539,542,545,548,550,553,556,559],{"class":87,"line":109},[85,526,527],{"class":102},"  console",[85,529,530],{"class":95},".",[85,532,533],{"class":215},"log",[85,535,219],{"class":228},[85,537,538],{"class":158},"`",[85,540,541],{"class":162},"Received message: ",[85,543,544],{"class":158},"${",[85,546,547],{"class":102},"payload",[85,549,530],{"class":158},[85,551,552],{"class":102},"text",[85,554,555],{"class":158},"}`",[85,557,558],{"class":228},")",[85,560,169],{"class":95},[85,562,563],{"class":87,"line":117},[85,564,565],{"class":252},"  \u002F\u002F 直接返回数据会向当前客户端发送一个以方法名为事件的响应\n",[85,567,568],{"class":87,"line":125},[85,569,570],{"class":252},"  \u002F\u002F 注意：如果返回 `WsResponse`，其 `event` 字段默认与方法名相同；如不特殊关注响应事件名称，可保持简单返回对象。\n",[85,572,573,576,578,581,583,586,588,590,592,595,597,599,602,604],{"class":87,"line":133},[85,574,575],{"class":91},"  return",[85,577,177],{"class":95},[85,579,580],{"class":228}," event",[85,582,232],{"class":95},[85,584,585],{"class":158}," '",[85,587,499],{"class":162},[85,589,496],{"class":158},[85,591,183],{"class":95},[85,593,594],{"class":228}," data",[85,596,232],{"class":95},[85,598,585],{"class":158},[85,600,601],{"class":162},"Message received!",[85,603,496],{"class":158},[85,605,606],{"class":95}," };\n",[85,608,609],{"class":87,"line":141},[85,610,334],{"class":95},[24,612,613,340],{},[36,614,615],{},"@SubscribeMessage 支持复杂的返回场景",[342,617,618,629,636,651],{},[345,619,620,621,624,625,628],{},"直接返回一个对象：将对象作为 ",[28,622,623],{},"data","，并自动使用 ",[28,626,627],{},"'message'"," 作为响应事件名。",[345,630,631,632,635],{},"完全手动控制：返回 ",[28,633,634],{},"Observable","。",[345,637,638,639,642,643,646,647,650],{},"发送到不同客户端：既不依赖 ",[28,640,641],{},"@WebSocketServer"," 也不返回数据，可以注入 ",[28,644,645],{},"@WebSocketServer() server: Server"," 并通过 ",[28,648,649],{},"server.to(room).emit(...)"," 任意控制消息的分发。",[345,652,653],{},"你还可以在网关中注入自定义服务，并与数据库、队列、业务逻辑等进行更深度的集成。",[57,655,657],{"id":656},"_4-生命周期钩子","4. 生命周期钩子",[24,659,660],{},"通过实现相应接口，可以监控 WebSocket 连接的生命周期事件，适用于鉴权、清理资源等场景：",[662,663,664,681],"table",{},[665,666,667],"thead",{},[668,669,670,675,678],"tr",{},[671,672,674],"th",{"align":673},"left","接口",[671,676,677],{"align":673},"方法",[671,679,680],{"align":673},"触发时机",[682,683,684,700,715],"tbody",{},[668,685,686,692,697],{},[687,688,689],"td",{"align":673},[28,690,691],{},"OnGatewayInit",[687,693,694],{"align":673},[28,695,696],{},"afterInit(server: Server)",[687,698,699],{"align":673},"WebSocket 服务器初始化后",[668,701,702,707,712],{},[687,703,704],{"align":673},[28,705,706],{},"OnGatewayConnection",[687,708,709],{"align":673},[28,710,711],{},"handleConnection(client: Socket, ...args: any[])",[687,713,714],{"align":673},"新客户端连接时",[668,716,717,722,727],{},[687,718,719],{"align":673},[28,720,721],{},"OnGatewayDisconnect",[687,723,724],{"align":673},[28,725,726],{},"handleDisconnect(client: Socket)",[687,728,729],{"align":673},"客户端断开连接时",[53,731,733],{"id":732},"项目集成","📦 项目集成",[57,735,737],{"id":736},"第一步安装依赖","第一步：安装依赖",[24,739,740,741,340],{},"如果要使用 ",[36,742,46],{},[76,744,748],{"className":745,"code":746,"language":747,"meta":81,"style":81},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","npm install @nestjs\u002Fwebsockets @nestjs\u002Fplatform-socket.io socket.io\n","bash",[28,749,750],{"__ignoreMap":81},[85,751,752,755,758,761,764],{"class":87,"line":88},[85,753,754],{"class":303},"npm",[85,756,757],{"class":162}," install",[85,759,760],{"class":162}," @nestjs\u002Fwebsockets",[85,762,763],{"class":162}," @nestjs\u002Fplatform-socket.io",[85,765,766],{"class":162}," socket.io\n",[24,768,769,770,772],{},"如果要使用原生库 ",[36,771,50],{},"，可安装：",[76,774,776],{"className":745,"code":775,"language":747,"meta":81,"style":81},"npm install @nestjs\u002Fwebsockets @nestjs\u002Fplatform-ws ws\n",[28,777,778],{"__ignoreMap":81},[85,779,780,782,784,786,789],{"class":87,"line":88},[85,781,754],{"class":303},[85,783,757],{"class":162},[85,785,760],{"class":162},[85,787,788],{"class":162}," @nestjs\u002Fplatform-ws",[85,790,791],{"class":162}," ws\n",[57,793,795],{"id":794},"第二步定义网关","第二步：定义网关",[24,797,798,799,802,803,807,808,811,812,815,816,818,819,822],{},"将核心逻辑写在网关类里。一般情况下，网关自身也是 ",[28,800,801],{},"@Injectable()"," 的，可依赖注入其他服务。\n",[804,805,806],"em",{},"注意","：通常情况下不需要在模块的 ",[28,809,810],{},"providers"," 中重复注册网关；默认情况下，NestJS 的 ",[28,813,814],{},"WebSocketsModule"," 会自动识别并实例化使用 ",[28,817,42],{}," 装饰器的类（该行为适用于全局模式）。但如果要对网关进行显式的模块管理，依然推荐在某些模块的 ",[28,820,821],{},"providers: [ChatGateway]"," 里注册，这能确保在被其他模块引用时，网关实例是受控的。",[57,824,826],{"id":825},"第三步注册模块","第三步：注册模块",[24,828,829,831,832,837,838,840,841,843,844,846],{},[28,830,163],{}," ",[36,833,834,835],{},"并不导出 ",[28,836,814],{},"；网关本质上只是一个普通的 ",[28,839,801],{}," 类，只需像其他 Provider 一样注册到某个模块的 ",[28,842,810],{}," 中即可，NestJS 会自动识别其 ",[28,845,42],{}," 元数据并接管连接：",[76,848,850],{"className":78,"code":849,"language":80,"meta":81,"style":81},"\u002F\u002F app.module.ts（或对应的功能模块）\nimport { Module } from \"@nestjs\u002Fcommon\";\nimport { ChatGateway } from \".\u002Fchat.gateway\";\n\n@Module({\n  providers: [ChatGateway], \u002F\u002F 直接注册即可，无需额外导入某个 \"WebSocket 模块\"\n})\nexport class AppModule {}\n",[28,851,852,857,879,900,904,915,930,936],{"__ignoreMap":81},[85,853,854],{"class":87,"line":88},[85,855,856],{"class":252},"\u002F\u002F app.module.ts（或对应的功能模块）\n",[85,858,859,861,863,866,868,870,872,875,877],{"class":87,"line":99},[85,860,92],{"class":91},[85,862,177],{"class":95},[85,864,865],{"class":102}," Module",[85,867,189],{"class":95},[85,869,155],{"class":91},[85,871,159],{"class":158},[85,873,874],{"class":162},"@nestjs\u002Fcommon",[85,876,166],{"class":158},[85,878,169],{"class":95},[85,880,881,883,885,887,889,891,893,896,898],{"class":87,"line":109},[85,882,92],{"class":91},[85,884,177],{"class":95},[85,886,304],{"class":102},[85,888,189],{"class":95},[85,890,155],{"class":91},[85,892,159],{"class":158},[85,894,895],{"class":162},".\u002Fchat.gateway",[85,897,166],{"class":158},[85,899,169],{"class":95},[85,901,902],{"class":87,"line":117},[85,903,206],{"emptyLinePlaceholder":205},[85,905,906,908,911,913],{"class":87,"line":125},[85,907,212],{"class":95},[85,909,910],{"class":215},"Module",[85,912,219],{"class":102},[85,914,222],{"class":95},[85,916,917,920,922,925,927],{"class":87,"line":133},[85,918,919],{"class":228},"  providers",[85,921,232],{"class":95},[85,923,924],{"class":102}," [ChatGateway]",[85,926,183],{"class":95},[85,928,929],{"class":252}," \u002F\u002F 直接注册即可，无需额外导入某个 \"WebSocket 模块\"\n",[85,931,932,934],{"class":87,"line":141},[85,933,152],{"class":95},[85,935,290],{"class":102},[85,937,938,940,942,945],{"class":87,"line":149},[85,939,296],{"class":91},[85,941,300],{"class":299},[85,943,944],{"class":303}," AppModule",[85,946,947],{"class":95}," {}\n",[24,949,950,951,953],{},"如果希望网关只属于某个特定功能模块（不在全局范围共享实例），将其 ",[28,952,810],{}," 声明在该功能模块中即可，无需其他配置。",[53,955,957],{"id":956},"适配器adapter选择","🧩 适配器（Adapter）选择",[24,959,960,961,964],{},"NestJS 的 ",[28,962,963],{},"WebSocketAdapter"," 接口抽象了不同 WebSocket 库之间的差异，让网关代码可以平滑切换底层实现。",[342,966,967,977],{},[345,968,969,972,973,976],{},[36,970,971],{},"Socket.IO 适配器","（",[28,974,975],{},"@nestjs\u002Fplatform-socket.io","）：\n提供更丰富的功能，如自动降级（轮询+xhr）、房间（Rooms）支持、命名空间（Namespaces）和中间件。官方示例文档详尽，适合大部分实时通信场景。",[345,978,979,972,982,985,986,988,989,992],{},[36,980,981],{},"原生 WebSocket（ws）适配器",[28,983,984],{},"@nestjs\u002Fplatform-ws","）：\n原生库 ",[28,987,50],{}," 更为轻量，性能更高，适合对传输体积和原始吞吐量极其敏感的场景。不提供自动降级和房间等内置高级特性，需要手动实现分组逻辑。两种适配器都支持使用 ",[28,990,991],{},"app.useWebSocketAdapter(new WsAdapter(app))"," 来指定。",[994,995,996],"blockquote",{},[24,997,998,999,1001,1002,1004,1005,1007,1008,1010],{},"在实际项目里，",[28,1000,163],{}," 是所有 WebSocket 功能的基础包，而 ",[28,1003,975],{}," 是 socket.io 的专属适配器实现，",[28,1006,984],{}," 则是 ",[28,1009,50],{}," 的专属适配器。",[53,1012,1014],{"id":1013},"️-安全认证authentication","️ 安全认证（Authentication）",[24,1016,1017],{},"WebSocket 认证主要通过在连接建立阶段验证 Token 来实现，例如提取 Socket.IO 握手时的查询参数或请求头。",[24,1019,1020,340],{},[36,1021,1022],{},"Socket.IO 示例",[76,1024,1026],{"className":78,"code":1025,"language":80,"meta":81,"style":81},"import { WebSocketGateway, OnGatewayConnection } from \"@nestjs\u002Fwebsockets\";\nimport { Socket } from \"socket.io\";\n\n@WebSocketGateway()\nexport class AuthGateway implements OnGatewayConnection {\n  handleConnection(client: Socket) {\n    const token = client.handshake.auth.token;\n    \u002F\u002F 验证 token，若无效则断开连接\n    if (!token || !this.validateToken(token)) {\n      client.disconnect();\n      return;\n    }\n    \u002F\u002F 认证通过后可执行额外逻辑\n  }\n}\n",[28,1027,1028,1053,1073,1077,1085,1100,1120,1152,1157,1194,1209,1216,1221,1226,1231],{"__ignoreMap":81},[85,1029,1030,1032,1034,1037,1039,1041,1043,1045,1047,1049,1051],{"class":87,"line":88},[85,1031,92],{"class":91},[85,1033,177],{"class":95},[85,1035,1036],{"class":102}," WebSocketGateway",[85,1038,183],{"class":95},[85,1040,315],{"class":102},[85,1042,189],{"class":95},[85,1044,155],{"class":91},[85,1046,159],{"class":158},[85,1048,163],{"class":162},[85,1050,166],{"class":158},[85,1052,169],{"class":95},[85,1054,1055,1057,1059,1061,1063,1065,1067,1069,1071],{"class":87,"line":99},[85,1056,92],{"class":91},[85,1058,177],{"class":95},[85,1060,186],{"class":102},[85,1062,189],{"class":95},[85,1064,155],{"class":91},[85,1066,159],{"class":158},[85,1068,46],{"class":162},[85,1070,166],{"class":158},[85,1072,169],{"class":95},[85,1074,1075],{"class":87,"line":109},[85,1076,206],{"emptyLinePlaceholder":205},[85,1078,1079,1081,1083],{"class":87,"line":117},[85,1080,212],{"class":95},[85,1082,216],{"class":215},[85,1084,436],{"class":102},[85,1086,1087,1089,1091,1094,1096,1098],{"class":87,"line":125},[85,1088,296],{"class":91},[85,1090,300],{"class":299},[85,1092,1093],{"class":303}," AuthGateway",[85,1095,307],{"class":299},[85,1097,315],{"class":303},[85,1099,96],{"class":95},[85,1101,1102,1106,1108,1112,1114,1116,1118],{"class":87,"line":133},[85,1103,1105],{"class":1104},"sVXei","  handleConnection",[85,1107,219],{"class":95},[85,1109,1111],{"class":1110},"s99_P","client",[85,1113,232],{"class":519},[85,1115,186],{"class":303},[85,1117,558],{"class":95},[85,1119,96],{"class":95},[85,1121,1122,1125,1129,1132,1135,1137,1140,1142,1145,1147,1150],{"class":87,"line":141},[85,1123,1124],{"class":299},"    const",[85,1126,1128],{"class":1127},"s_hVV"," token",[85,1130,1131],{"class":519}," =",[85,1133,1134],{"class":102}," client",[85,1136,530],{"class":95},[85,1138,1139],{"class":102},"handshake",[85,1141,530],{"class":95},[85,1143,1144],{"class":102},"auth",[85,1146,530],{"class":95},[85,1148,1149],{"class":102},"token",[85,1151,169],{"class":95},[85,1153,1154],{"class":87,"line":149},[85,1155,1156],{"class":252},"    \u002F\u002F 验证 token，若无效则断开连接\n",[85,1158,1159,1162,1165,1168,1170,1173,1176,1180,1182,1185,1187,1189,1192],{"class":87,"line":172},[85,1160,1161],{"class":91},"    if",[85,1163,1164],{"class":228}," (",[85,1166,1167],{"class":519},"!",[85,1169,1149],{"class":102},[85,1171,1172],{"class":519}," ||",[85,1174,1175],{"class":519}," !",[85,1177,1179],{"class":1178},"s39Yj","this",[85,1181,530],{"class":95},[85,1183,1184],{"class":215},"validateToken",[85,1186,219],{"class":228},[85,1188,1149],{"class":102},[85,1190,1191],{"class":228},")) ",[85,1193,222],{"class":95},[85,1195,1196,1199,1201,1204,1207],{"class":87,"line":202},[85,1197,1198],{"class":102},"      client",[85,1200,530],{"class":95},[85,1202,1203],{"class":215},"disconnect",[85,1205,1206],{"class":228},"()",[85,1208,169],{"class":95},[85,1210,1211,1214],{"class":87,"line":209},[85,1212,1213],{"class":91},"      return",[85,1215,169],{"class":95},[85,1217,1218],{"class":87,"line":225},[85,1219,1220],{"class":95},"    }\n",[85,1222,1223],{"class":87,"line":256},[85,1224,1225],{"class":252},"    \u002F\u002F 认证通过后可执行额外逻辑\n",[85,1227,1228],{"class":87,"line":276},[85,1229,1230],{"class":95},"  }\n",[85,1232,1233],{"class":87,"line":285},[85,1234,334],{"class":95},[24,1236,1237,1240,1241,1243,1244,1247,1248,1251,1252,1255,1256,1258,1259,1261],{},[36,1238,1239],{},"原生 ws 示例","：\n原生 ",[28,1242,50],{}," 适配器下，可通过 ",[28,1245,1246],{},"client.upgradeReq","（或 Node.js 的 IncomingMessage）获取 ",[28,1249,1250],{},"headers"," 中的 ",[28,1253,1254],{},"authorization"," 字段。所以在实际集成时，建议优先考虑使用官方推荐的 ",[28,1257,984],{},"，而不要直接依赖 ",[28,1260,50],{}," 的原始回调。",[24,1263,1264],{},"对于生产环境，建议将认证逻辑独立为一个**守卫（Guard）**或中间件，以提高代码的可维护性。",[53,1266,1268],{"id":1267},"高级技巧与最佳实践","📈 高级技巧与最佳实践",[57,1270,1272],{"id":1271},"房间rooms管理","房间（Rooms）管理",[24,1274,1275,1276,461,1279,1282,1283,635],{},"对于聊天室这类多房间的场景，可以利用 ",[28,1277,1278],{},"socket.join()",[28,1280,1281],{},"socket.leave()"," 以及 ",[28,1284,1285],{},"server.to(room).emit()",[76,1287,1289],{"className":78,"code":1288,"language":80,"meta":81,"style":81},"@SubscribeMessage('joinRoom')\nhandleJoinRoom(client: Socket, roomName: string) {\n  client.join(roomName);\n  client.emit('joinedRoom', roomName);\n}\n\n@SubscribeMessage('messageToRoom')\nhandleMessageToRoom(client: Socket, payload: { room: string; message: string }) {\n  this.server.to(payload.room).emit('roomMessage', payload.message);\n}\n",[28,1290,1291,1308,1322,1341,1367,1371,1375,1392,1422,1473],{"__ignoreMap":81},[85,1292,1293,1295,1297,1299,1301,1304,1306],{"class":87,"line":88},[85,1294,212],{"class":95},[85,1296,491],{"class":215},[85,1298,219],{"class":102},[85,1300,496],{"class":158},[85,1302,1303],{"class":162},"joinRoom",[85,1305,496],{"class":158},[85,1307,290],{"class":102},[85,1309,1310,1313,1315,1317,1320],{"class":87,"line":99},[85,1311,1312],{"class":215},"handleJoinRoom",[85,1314,511],{"class":102},[85,1316,183],{"class":95},[85,1318,1319],{"class":102}," roomName: string) ",[85,1321,222],{"class":95},[85,1323,1324,1327,1329,1332,1334,1337,1339],{"class":87,"line":109},[85,1325,1326],{"class":102},"  client",[85,1328,530],{"class":95},[85,1330,1331],{"class":215},"join",[85,1333,219],{"class":228},[85,1335,1336],{"class":102},"roomName",[85,1338,558],{"class":228},[85,1340,169],{"class":95},[85,1342,1343,1345,1347,1349,1351,1353,1356,1358,1360,1363,1365],{"class":87,"line":117},[85,1344,1326],{"class":102},[85,1346,530],{"class":95},[85,1348,460],{"class":215},[85,1350,219],{"class":228},[85,1352,496],{"class":158},[85,1354,1355],{"class":162},"joinedRoom",[85,1357,496],{"class":158},[85,1359,183],{"class":95},[85,1361,1362],{"class":102}," roomName",[85,1364,558],{"class":228},[85,1366,169],{"class":95},[85,1368,1369],{"class":87,"line":125},[85,1370,334],{"class":95},[85,1372,1373],{"class":87,"line":133},[85,1374,206],{"emptyLinePlaceholder":205},[85,1376,1377,1379,1381,1383,1385,1388,1390],{"class":87,"line":141},[85,1378,212],{"class":95},[85,1380,491],{"class":215},[85,1382,219],{"class":102},[85,1384,496],{"class":158},[85,1386,1387],{"class":162},"messageToRoom",[85,1389,496],{"class":158},[85,1391,290],{"class":102},[85,1393,1394,1397,1399,1401,1404,1407,1410,1412,1415,1417,1420],{"class":87,"line":149},[85,1395,1396],{"class":215},"handleMessageToRoom",[85,1398,511],{"class":102},[85,1400,183],{"class":95},[85,1402,1403],{"class":102}," payload: ",[85,1405,1406],{"class":95},"{",[85,1408,1409],{"class":228}," room",[85,1411,232],{"class":95},[85,1413,1414],{"class":102}," string; message: string ",[85,1416,152],{"class":95},[85,1418,1419],{"class":102},") ",[85,1421,222],{"class":95},[85,1423,1424,1427,1429,1431,1433,1436,1438,1440,1442,1445,1447,1449,1451,1453,1455,1458,1460,1462,1465,1467,1469,1471],{"class":87,"line":172},[85,1425,1426],{"class":1178},"  this",[85,1428,530],{"class":95},[85,1430,441],{"class":102},[85,1432,530],{"class":95},[85,1434,1435],{"class":215},"to",[85,1437,219],{"class":228},[85,1439,547],{"class":102},[85,1441,530],{"class":95},[85,1443,1444],{"class":102},"room",[85,1446,558],{"class":228},[85,1448,530],{"class":95},[85,1450,460],{"class":215},[85,1452,219],{"class":228},[85,1454,496],{"class":158},[85,1456,1457],{"class":162},"roomMessage",[85,1459,496],{"class":158},[85,1461,183],{"class":95},[85,1463,1464],{"class":102}," payload",[85,1466,530],{"class":95},[85,1468,499],{"class":102},[85,1470,558],{"class":228},[85,1472,169],{"class":95},[85,1474,1475],{"class":87,"line":202},[85,1476,334],{"class":95},[57,1478,1479],{"id":1479},"网关设计与可测试性",[24,1481,1482,1483,1486],{},"推荐将网关仅作为",[36,1484,1485],{},"轻量级消息路由层","，具体的业务逻辑（如消息持久化、推送通知等）应委托给独立的服务（Service）处理。这样可以保持网关代码的简洁，并使其更易于单元测试。",[76,1488,1490],{"className":78,"code":1489,"language":80,"meta":81,"style":81},"\u002F\u002F 单一职责的网关示例：只负责收发，业务逻辑交给 service\nconstructor(private messageService: MessageService) {}\n\n@SubscribeMessage('message')\nasync handleMessage(client: Socket, payload: any) {\n  const response = await this.messageService.processMessage(payload);\n  \u002F\u002F 返回给当前客户端或广播\n  return response;\n}\n",[28,1491,1492,1497,1508,1512,1528,1544,1578,1583,1591],{"__ignoreMap":81},[85,1493,1494],{"class":87,"line":88},[85,1495,1496],{"class":252},"\u002F\u002F 单一职责的网关示例：只负责收发，业务逻辑交给 service\n",[85,1498,1499,1502,1505],{"class":87,"line":99},[85,1500,1501],{"class":215},"constructor",[85,1503,1504],{"class":102},"(private messageService: MessageService) ",[85,1506,1507],{"class":95},"{}\n",[85,1509,1510],{"class":87,"line":109},[85,1511,206],{"emptyLinePlaceholder":205},[85,1513,1514,1516,1518,1520,1522,1524,1526],{"class":87,"line":117},[85,1515,212],{"class":95},[85,1517,491],{"class":215},[85,1519,219],{"class":102},[85,1521,496],{"class":158},[85,1523,499],{"class":162},[85,1525,496],{"class":158},[85,1527,290],{"class":102},[85,1529,1530,1533,1535,1537,1539,1542],{"class":87,"line":125},[85,1531,1532],{"class":102},"async ",[85,1534,508],{"class":215},[85,1536,511],{"class":102},[85,1538,183],{"class":95},[85,1540,1541],{"class":102}," payload: any) ",[85,1543,222],{"class":95},[85,1545,1546,1549,1552,1554,1557,1560,1562,1565,1567,1570,1572,1574,1576],{"class":87,"line":133},[85,1547,1548],{"class":299},"  const",[85,1550,1551],{"class":1127}," response",[85,1553,1131],{"class":519},[85,1555,1556],{"class":91}," await",[85,1558,1559],{"class":1178}," this",[85,1561,530],{"class":95},[85,1563,1564],{"class":102},"messageService",[85,1566,530],{"class":95},[85,1568,1569],{"class":215},"processMessage",[85,1571,219],{"class":228},[85,1573,547],{"class":102},[85,1575,558],{"class":228},[85,1577,169],{"class":95},[85,1579,1580],{"class":87,"line":141},[85,1581,1582],{"class":252},"  \u002F\u002F 返回给当前客户端或广播\n",[85,1584,1585,1587,1589],{"class":87,"line":149},[85,1586,575],{"class":91},[85,1588,1551],{"class":102},[85,1590,169],{"class":95},[85,1592,1593],{"class":87,"line":172},[85,1594,334],{"class":95},[53,1596,1597],{"id":1597},"部署与性能",[342,1599,1600,1618],{},[345,1601,1602,1605,1606,1609,1610,1613,1614,1617],{},[36,1603,1604],{},"扩展性","：Socket.IO 和 WebSocket 都支持使用 ",[36,1607,1608],{},"Redis Adapter"," 进行水平扩展，允许多个 Node.js 进程之间共享连接信息和广播消息。基本思路是先安装 ",[28,1611,1612],{},"socket.io-redis"," 包，再通过 ",[28,1615,1616],{},"IoAdapter"," 启用 Redis 适配器。官方文档提供了现成的集成步骤。",[345,1619,1620,1623,1624,1627,1628,380,1631,1634],{},[36,1621,1622],{},"负载均衡","：如使用 Nginx 或 HAProxy，务必启用 ",[28,1625,1626],{},"proxy_pass"," 并配置负载均衡策略（例如基于 ",[28,1629,1630],{},"ip_hash",[28,1632,1633],{},"sticky session","），确保同一客户端始终路由到同一后端服务，避免长连接中断或认证失败。在实际生产环境里，还需要额外验证 WebSocket 连接升级（Upgrade）头是否被防火墙或代理正确转发。",[53,1636,1637],{"id":1637},"总结",[662,1639,1640,1650],{},[665,1641,1642],{},[668,1643,1644,1647],{},[671,1645,1646],{"align":673},"概念",[671,1648,1649],{"align":673},"核心作用",[682,1651,1652,1661,1670,1679,1687,1695,1707],{},[668,1653,1654,1658],{},[687,1655,1656],{"align":673},[28,1657,42],{},[687,1659,1660],{"align":673},"声明一个类为 WebSocket 网关，并定义端口、命名空间、CORS 等",[668,1662,1663,1667],{},[687,1664,1665],{"align":673},[28,1666,418],{},[687,1668,1669],{"align":673},"获取底层服务器实例，用于主动广播消息",[668,1671,1672,1676],{},[687,1673,1674],{"align":673},[28,1675,472],{},[687,1677,1678],{"align":673},"定义消息监听器，处理客户端发送的特定事件",[668,1680,1681,1684],{},[687,1682,1683],{"align":673},"生命周期钩子",[687,1685,1686],{"align":673},"监听连接、断开、初始化等事件，处理认证\u002F资源清理",[668,1688,1689,1692],{},[687,1690,1691],{"align":673},"适配器（Adapter）",[687,1693,1694],{"align":673},"抽象底层 WebSocket 库（Socket.IO \u002F ws），灵活切换",[668,1696,1697,1700],{},[687,1698,1699],{"align":673},"认证与授权",[687,1701,1702,1703,1706],{"align":673},"在 ",[28,1704,1705],{},"handleConnection"," 或自定义守卫中验证客户端身份",[668,1708,1709,1712],{},[687,1710,1711],{"align":673},"房间与命名空间",[687,1713,1714],{"align":673},"逻辑隔离，实现群组通信等功能",[24,1716,1717,1718,1721,1722,1728,1729,1736],{},"对于绝大多数实时通信场景，",[36,1719,1720],{},"Socket.IO"," 提供了最丰富的开箱即用功能，开发效率很高。若对性能和包体积有极致要求，可考虑 ",[36,1723,1724,1725,1727],{},"原生 ",[28,1726,50],{}," 库","进行定制开发。官方文档的示例都比较简略，建议结合官方教程和社区实战示例来进一步完善鉴权，也可多阅读 NestJS 的 ",[1730,1731,1735],"a",{"href":1732,"rel":1733},"https:\u002F\u002Fdocs.nestjs.com\u002Fwebsockets\u002Fgateways",[1734],"nofollow","WebSocket 官方文档"," 来获得更全面的配置信息。",[1738,1739,1740],"h2",{"id":1740},"参考文献",[24,1742,1743],{},"以下链接在编写时均可正常访问：",[662,1745,1746,1756],{},[665,1747,1748],{},[668,1749,1750,1753],{},[671,1751,1752],{},"资料",[671,1754,1755],{},"说明",[682,1757,1758,1770,1781],{},[668,1759,1760,1767],{},[687,1761,1762],{},[1730,1763,1766],{"href":1764,"rel":1765},"https:\u002F\u002Fdocs.nestjs.com\u002F",[1734],"NestJS 文档",[687,1768,1769],{},"官方",[668,1771,1772,1778],{},[687,1773,1774],{},[1730,1775,1777],{"href":1732,"rel":1776},[1734],"Gateways",[687,1779,1780],{},"本章主题",[668,1782,1783,1790],{},[687,1784,1785],{},[1730,1786,1789],{"href":1787,"rel":1788},"https:\u002F\u002Fdocs.nestjs.com\u002Ffaq\u002Frequest-lifecycle",[1734],"Request lifecycle",[687,1791,1792],{},"执行顺序",[1794,1795,1796],"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 .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 .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 .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--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 .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 .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVXei, html code.shiki .sVXei{--shiki-light:#E53935;--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 .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--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":81,"searchDepth":99,"depth":99,"links":1798},[1799,1800,1801,1802,1803,1804,1805,1806],{"id":55,"depth":109,"text":55},{"id":732,"depth":109,"text":733},{"id":956,"depth":109,"text":957},{"id":1013,"depth":109,"text":1014},{"id":1267,"depth":109,"text":1268},{"id":1597,"depth":109,"text":1597},{"id":1637,"depth":109,"text":1637},{"id":1740,"depth":99,"text":1740},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fend\u002FNestJS\u002Fnew\u002FwebSocket","knowledge\u002Fknowledge\u002Fbooklet\u002Fend\u002FNestJS\u002Fnew\u002FwebSocket.md","md",{"items":1812},[1813,1816,1820,1824,1828,1832],{"path":1814,"title":1699,"description":1815,"category":10,"score":133},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fauth","认证（Authentication）：确认「你是谁」（如 JWT、Session）。 - 授权（Authorization）：确认「你能做什么」（如 RBAC、策略检查）。",{"path":1817,"title":1818,"description":1819,"category":10,"score":133},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcache","缓存","@nestjs\u002Fcache-manager 统一缓存 API，存储实现可插拔。",{"path":1821,"title":1822,"description":1823,"category":10,"score":133},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fservice","提供者与服务","Service 是最常见的 Provider，封装业务逻辑。见 module。",{"path":1825,"title":1826,"description":1827,"category":10,"score":133},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fguards","守卫 (Guards) 与授权","守卫决定是否放行请求，常用于认证与授权。见 auth。",{"path":1829,"title":1830,"description":1831,"category":10,"score":133},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fconfig","配置管理（Config 模块）","@nestjs\u002Fconfig 加载 .env 并提供 ConfigService。见 工程化 env。",{"path":1833,"title":1834,"description":1835,"category":10,"score":133},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fdatabase","数据库集成（以 TypeORM 为例）","Nest 通过 @nestjs\u002Ftypeorm 等包集成 ORM；生产环境用 migration，慎用 synchronize。亦可选用 Prisma、MikroORM 等（见 官方 Database）。",{"series":1837,"title":1838,"items":1839,"index":331,"prev":1900,"next":1901},"booklet\u002Fend\u002Fnestjs\u002Fnew","new",[1840,1842,1846,1848,1850,1854,1858,1862,1864,1868,1872,1874,1878,1882,1886,1890,1893,1897,1899],{"path":1814,"title":1699,"date":1841,"pinned":15},"2025-12-23",{"path":1843,"title":1844,"date":1845,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fbase","NestJS 基础","2021-03-09",{"path":1817,"title":1818,"date":1847,"pinned":15},"2025-07-04",{"path":1829,"title":1830,"date":1849,"pinned":15},"2023-08-26",{"path":1851,"title":1852,"date":1853,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcontroller","控制器与路由","2020-07-18",{"path":1855,"title":1856,"date":1857,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcron","定时任务","2023-04-12",{"path":1859,"title":1860,"date":1861,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fcustom-decorators","自定义装饰器","2021-09-19",{"path":1833,"title":1834,"date":1863,"pinned":15},"2023-05-26",{"path":1865,"title":1866,"date":1867,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fdeploy","部署与运维","2023-01-24",{"path":1869,"title":1870,"date":1871,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Ferror","异常处理","2020-01-28",{"path":1825,"title":1826,"date":1873,"pinned":15},"2023-09-09",{"path":1875,"title":1876,"date":1877,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fintercept","拦截器 (Interceptors)","2021-04-26",{"path":1879,"title":1880,"date":1881,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Flogger","日志记录","2022-10-10",{"path":1883,"title":1884,"date":1885,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fmiddleware","中间件 (Middleware)","2021-11-26",{"path":1887,"title":1888,"date":1889,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fmodule","模块系统与依赖注入","2022-06-15",{"path":1891,"title":1892,"date":1853,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fpipes","管道 (Pipes) 与数据验证",{"path":1894,"title":1895,"date":1896,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fend\u002Fnestjs\u002Fnew\u002Fqueue","队列（Bull）","2021-07-01",{"path":1821,"title":1822,"date":1898,"pinned":15},"2024-12-02",{"path":7,"title":8,"date":14,"pinned":15},{"path":1821,"title":1822,"date":1898,"pinned":15},null]