ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Nestjs框架: 微服务项目创建与 gRPC 配置规范指南(TaoToken 统一 Key 接入版)

Nestjs框架: 微服务项目创建与 gRPC 配置规范指南(TaoToken 统一 Key 接入版) 1. 从零搭建 Nestjs gRPC 微服务为什么你的第一个服务总是跑不起来如果你正在搜「Nestjs 微服务 gRPC 配置」大概率已经踩过这几个坑nest new出来的项目默认是 HTTP 应用直接加nestjs/microservices后启动报No transport definedproto 文件路径写相对路径本地能跑、打包后ENOENTSWC 编译加速开了之后GrpcMethod装饰器元数据丢失服务端注册不上方法。这些问题不是 Nestjs 本身的问题而是微服务项目的初始化顺序和配置规范没定好。这篇内容聚焦一条完整链路从空目录开始用 pnpm workspace 隔离多个微服务用 SWC 做编译加速用 proto 文件约定服务间接口最后通过 TaoToken 统一 Key 完成一次端到端的 gRPC 调用验证。适合已经会写 Nestjs 控制器、但第一次把单体拆成微服务的开发者。整套流程我按「能复制、能跑通、能排错」的标准写每一步都给出可粘贴的配置和命令。先说结论性的结构一个 user-service 负责用户注册登录一个 gateway 负责对外 HTTP 并调用 user-service 的 gRPC 接口。proto 文件独立放在/proto目录两个服务共享同一份接口定义。鉴权相关的模型调用走 TaoToken 的 OpenAI 兼容通道Key 通过环境变量注入不硬编码。下面从项目结构开始。2. TaoToken 前置准备统一 Key 与 API 通道配置在写 gRPC 代码之前先把模型调用的鉴权通道准备好。微服务里经常需要在用户注册时做一次内容合规检查或者在登录时调用模型做风控判断这类调用如果每个服务各自维护一套 Key后期轮换会非常痛苦。TaoToken 提供的是 OpenAI 兼容的统一入口一个 Key 可以覆盖多个模型适合放在微服务的公共配置层。你需要先拿到 Key。访问 API Keys 管理页创建https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。创建后复制sk-开头的字符串只显示一次建议直接写进.env而不是代码里。Base URL 统一用https://taotoken.net/api注意这个地址不带任何查询参数。模型 ID 按你实际要用的填比如gpt-4o-mini或claude-3-5-sonnet具体可用列表在模型对话页可以查到https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。在 user-service 根目录建.env# user-service/.env TAOTOKEN_API_KEYsk-你的实际Key TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODELgpt-4o-mini GRPC_USER_URLlocalhost:4001然后在app.module.ts里用nestjs/config加载确保微服务启动时环境变量已就绪。这里有个细节ConfigModule.forRoot({ isGlobal: true })要放在ClientsModule.register之前否则 gRPC 客户端注册时读不到GRPC_USER_URL会拿到 undefined 然后连接失败。如果你打算长期跑多个微服务、频繁调用模型做 Agent 类任务可以看下 Coding Plan 的额度方案https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。单次验证用按量 Key 就够了不用一上来就上套餐。3. 可复制配置nest-cli.json、proto 目录与 gRPC 注册代码这一节是全文的核心所有配置都可以直接复制。先建目录结构mkdir -p nest-grpc-demo/proto cd nest-grpc-demo pnpm initpnpm-workspace.yaml内容packages: - user-service - gatewayproto 文件放在根目录/proto/user.proto两个服务通过相对路径引用。注意 proto 的 package 名和 Nestjs 里GrpcMethod的第一个参数必须一致否则报method not found。// proto/user.proto syntax proto3; package user; service UserService { rpc FindOne (FindOneRequest) returns (FindOneResponse); rpc Create (CreateRequest) returns (CreateResponse); } message FindOneRequest { string username 1; string password 2; } message FindOneResponse { uint32 code 1; string message 2; uint32 id 3; string username 4; string accessToken 5; } message CreateRequest { string username 1; string password 2; } message CreateResponse { uint32 code 1; string message 2; uint32 id 3; string username 4; repeated UserRole userRole 5; } message UserRole { uint32 roleId 1; uint32 userId 2; }user-service 的nest-cli.json启用 SWC{ $schema: https://json.schemastore.org/nest-cli, collection: nestjs/schematics, sourceRoot: src, compilerOptions: { builder: swc, typeCheck: true, deleteOutDir: true, assets: [ { include: ../proto/**/*.proto, outDir: dist/proto } ] } }assets这段是关键SWC 编译不会自动拷贝 proto 文件不配的话dist里没有 proto运行时报ENOENT: no such file or directory。outDir设成dist/proto代码里用join(__dirname, proto/user.proto)就能找到。main.ts启动 gRPC 微服务// user-service/src/main.ts import { NestFactory } from nestjs/core; import { MicroserviceOptions, Transport } from nestjs/microservices; import { join } from path; import { AppModule } from ./app.module; async function bootstrap() { const app await NestFactory.create(AppModule); app.connectMicroserviceMicroserviceOptions({ transport: Transport.GRPC, options: { url: process.env.GRPC_USER_URL || localhost:4001, package: user, protoPath: join(__dirname, proto/user.proto), loader: { keepCase: true, longs: Number, enums: String, defaults: false, }, }, }); await app.startAllMicroservices(); await app.listen(3000); console.log(user-service gRPC on 4001, HTTP on 3000); } bootstrap();控制器里注册方法注意GrpcMethod(UserService, FindOne)两个参数分别是 service 名和方法名和 proto 完全对应// user-service/src/user.controller.ts import { Controller } from nestjs/common; import { GrpcMethod } from nestjs/microservices; Controller() export class UserController { GrpcMethod(UserService, FindOne) findOne(data: { username: string; password: string }) { if (!data.username) { return { code: 10001, message: username required }; } return { code: 0, message: ok, id: 1, username: data.username, accessToken: mock-token- Date.now(), }; } GrpcMethod(UserService, Create) create(data: { username: string; password: string }) { return { code: 0, message: created, id: 2, username: data.username, userRole: [{ roleId: 1, userId: 2 }], }; } }gateway 侧注册 gRPC 客户端ClientsModule.register里的name后面注入时要用// gateway/src/app.module.ts import { Module } from nestjs/common; import { ClientsModule, Transport } from nestjs/microservices; import { ConfigModule } from nestjs/config; import { join } from path; import { GatewayController } from ./gateway.controller; Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), ClientsModule.register([ { name: USER_PACKAGE, transport: Transport.GRPC, options: { url: process.env.GRPC_USER_URL || localhost:4001, package: user, protoPath: join(__dirname, proto/user.proto), }, }, ]), ], controllers: [GatewayController], }) export class AppModule {}gateway 控制器调用// gateway/src/gateway.controller.ts import { Controller, Get, Inject, OnModuleInit } from nestjs/common; import { ClientGrpc } from nestjs/microservices; import { Observable } from rxjs; interface UserService { findOne(data: { username: string; password: string }): Observableany; } Controller(user) export class GatewayController implements OnModuleInit { private userService: UserService; constructor(Inject(USER_PACKAGE) private client: ClientGrpc) {} onModuleInit() { this.userService this.client.getServiceUserService(UserService); } Get(find) find() { return this.userService.findOne({ username: test, password: 123 }); } }到这里配置部分结束。SWC 版本建议锁swc/core1.6.7新版本在某些 Node 版本下和nestjs/microservices的元数据反射有兼容问题表现为getService返回 undefined。4. 验证请求grpcurl 与 HTTP 网关双通道确认成功配置写完必须验证否则你不知道是 proto 路径错了还是端口没通。分两步先用 grpcurl 直连 gRPC 端口确认服务端本身正常再通过 gateway 的 HTTP 接口确认客户端注册正常。启动 user-servicecd user-service pnpm add nestjs/microservices grpc/grpc-js grpc/proto-loader nestjs/config pnpm add -D swc/cli1.6.7 swc/core1.6.7 pnpm start:dev看到user-service gRPC on 4001说明微服务已监听。另开终端用 grpcurl 测试grpcurl -plaintext -import-path ./proto -proto user.proto \ -d {username:test,password:123} \ localhost:4001 user.UserService/FindOne预期返回{ code: 0, message: ok, id: 1, username: test, accessToken: mock-token-1720000000000 }如果返回Failed to dial target host检查 4001 是否被占用返回method not found检查 proto 里 service 名和GrpcMethod是否一致返回code: 10001说明请求体没传进去检查-d的 JSON 字段名和 proto 是否匹配。再启动 gatewaycd gateway pnpm start:dev curl http://localhost:3000/user/find返回同样的 JSON 就说明客户端注册成功。这一步能过说明 proto 路径、package 名、服务名、方法名四者全部对齐。如果你在 gateway 里还要调用 TaoToken 做一次模型校验可以在find方法里加一段const res await fetch(${process.env.TAOTOKEN_BASE_URL}/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${process.env.TAOTOKEN_API_KEY}, }, body: JSON.stringify({ model: process.env.TAOTOKEN_MODEL, messages: [{ role: user, content: ping }], }), });返回 200 且 body 里有choices数组说明统一 Key 通道正常。这一步的报错排查见下一节。5. 本篇常见错排查401、local proxy failed 与 reading choices微服务 模型调用组合起来报错会集中在几个固定位置。下面按真实报错对照处理。401 UnauthorizedTaoToken 返回 401九成是 Key 没读到。检查.env是否在 gateway 根目录、ConfigModule.forRoot是否在ClientsModule之前、process.env.TAOTOKEN_API_KEY打印出来是不是 undefined。注意.env不要提交到 git.gitignore加上。如果 Key 确认正确仍 401去 API Keys 页确认这个 Key 是否被禁用或额度耗尽。local proxy failed / connection refusedgateway 调 user-service 时报这个说明 gRPC 客户端连不上 4001。先lsof -i :4001看端口是否被占用再确认 user-service 的startAllMicroservices()是否在listen()之前调用。顺序反了会导致微服务没起来但 HTTP 端口已监听客户端连接被拒。Cannot read properties of undefined (reading choices)这是模型返回体解析错误。常见原因是 Base URL 写成了https://taotoken.net/api/带尾斜杠拼接后变成//chat/completions部分网关会 404 返回 HTMLres.json()解析后没有choices。统一用https://taotoken.net/api不带尾斜杠。另一个原因是模型 ID 写错返回体里是error字段而不是choices打印完整 response 就能看到。OAuth / token expired 类报错如果你用的是 Claude Code 或 Codex 这类工具接入报 OAuth 相关错误通常是本地凭证缓存过期。这类工具建议直接走 API Key 模式而不是 OAuth配置三件套Base URL 填https://taotoken.net/apiKey 填sk-开头字符串Model ID 填实际模型名。三者在同一处配置缺一个都会鉴权失败。proto 路径 ENOENT打包后运行报找不到 proto检查nest-cli.json的assets是否包含../proto/**/*.protooutDir是否和代码里join(__dirname, ...)的相对层级一致。SWC 模式下__dirname指向dist所以 proto 要拷到dist/proto下。字段 ID 重复导致序列化异常proto 里两个字段用了同一个编号编译不报错但运行时数据错乱。用 VS Code 的 protobuf-helper 插件执行Reset Field IDs自动排序或者手动检查每个 message 内编号唯一。排障时优先看服务端日志gRPC 的错误信息比 HTTP 详细得多。nestjs/microservices默认会把异常转成 gRPC status codeUNKNOWN通常是控制器抛了未捕获异常NOT_FOUND是方法没注册。6. 语义一致 CTA把统一 Key 接进你的微服务链路整套流程跑通后你手上有一个可独立运行的 user-service、一个 gateway、一份共享 proto以及一条通过 TaoToken 统一 Key 调模型的通道。后续扩展新服务时复制 user-service 目录、改 proto 里的 service 名、在 gateway 的ClientsModule.register里加一个客户端即可不需要重新设计鉴权层。需要继续深入的话接入文档里有完整的参数说明和错误码对照https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。想先在网页上验证模型 ID 和返回格式用模型对话页最快https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。如果你的微服务里要跑长链路的 Agent 任务Coding Plan 的额度比按量更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。最后留一个实操建议proto 文件用 git submodule 或者独立 npm 包管理不要让两个服务各自复制一份。接口一改两边不同步gRPC 报的错会非常难定位。我试过在 gateway 里手动改 proto 字段名而 user-service 没改结果客户端一直超时查了两小时才发现是字段编号对不上。proto 单一来源是微服务接口约定里最省事的一条规则。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表