首页 / 文章

教导Claude Code使用你的NestJS单仓库:路由、规则与技能。

教 Claude Code 使用 NestJS 单仓库架构:通过 CLAUDE.md、规则、技能及权限设置,将代码分配到合适的服务中,并确保其遵循团队规范。

发布者:REACTAPP.TOP阅读约 20 分钟4492 词

来源: Claude Code for NestJS Monorepos: A Practical Setup Guide

在大型 NestJS 单体仓库中,若向未配置好的 AI 助手发送类似“为预订添加用户可见的状态”这样的请求,结果往往是可预见的:更改会被应用到错误的微服务中,错误信息为硬编码的字符串,且访问权限由 user.role === 'admin' 控制。问题并不出在 TypeScript 本身,而在于该模型在猜测你们团队很久以前做出但从未记录下来的决策所在。本指南将展示如何利用 Claude Code 提供的四个核心组件——CLAUDE.md、规则、技能以及权限设置,用版本控制的上下文取代这种猜测行为。学习完成后,你将拥有可提交到仓库的架构模板,确保每位工程师以及每次 AI 会话都能遵循相同的规范。

如果您需要关于编写简报文件本身的通用建议,请参阅我们关于编写高效 CLAUDE.md 的指南。本文重点介绍当代码库为多服务 NestJS 后端时的变化。

简版内容

对于希望先看到检查清单再了解原理的读者:

  1. 安装 Claude Code(例如使用 npm install -g @anthropic-ai/claude-code),并通过运行 claude 进行一次身份验证。
  2. 创建一个根级的 .claude/CLAUDE.md 文件,说明哪些服务负责处理哪些数据、请求处理流程的顺序,以及每个接口返回的响应结构。
  3. 创建一个 .claude/rules/ 目录,列出相关限制:禁止在循环中执行查询、禁止根据角色名称进行判断、禁止相对导入。
  • 添加一个 .claude/skills/ 目录,其中存放针对重复性任务(如创建接口端点)的逐步操作流程及代码模板。
  • 添加 .claude/settings.json 文件,用于预先批准安全命令并阻止具有破坏性的命令。
  • 将整个 .claude/ 目录进行版本控制,这样配置就能像代码一样被共享和审查。
  • 为何 NestJS 单体仓库会让 AI 助手感到困惑

    在庞大的 NestJS 代码库中,代码是否正确往往取决于某些无法从单个文件中了解的约定。数十个模块、数百个 DTO、自定义守卫、拦截器、异常过滤器以及 HTTP 适配器都会带来极大的模糊性。当这些模糊点无法得到解决时,模型就会采用通用的默认设置,而这类通用默认设置很少能符合特定团队的需求。

    未进行配置时的典型症状包括:

    • 服务方法直接与数据库通信,而非通过存储层处理。
    • 错误信息为字符串字面量,而非来自ErrorMessages常量文件中的条目。
    • 尽管预订逻辑属于api-service,却在operator-service中创建了新文件。
    • 授权检查采用user.role === 'admin'的方式,而非使用PermissionCodes。
    • 调试输出使用的是console.log,而非结构化日志记录器。
    • 导入语句通过../../common/utils的方式遍历目录,而非使用src/路径别名。

    根本原因在于缺乏团队内部知识

    这些都不是模型缺陷。在会话进行期间,助手仅知晓它已打开的文件。它无法得知团队故意删除了RolesGuard,也无法知道TypeORM的synchronize选项必须保持为false,或是服务之间只能通过HTTP适配器进行通信。这些知识都存在于工程师的头脑中以及旧的拉取请求讨论帖里。

    解决办法是将这些知识记录在工具会加载的文件中,并将每种类型的知识对应到合适的机制:

    • CLAUDE.md用于记载架构设计、服务归属及规范,会在每次会话开始时自动加载。
    • 规则则用于规定各种禁止事项及不变约束,以此指导所有生成的代码。
  • Skills功能支持多步骤工作流与模板,只有在出现匹配任务时才会被调用。
  • 本指南所假设的仓库结构

    在编写任何配置之前,先确定一个具体的结构会很有帮助。示例后端包含四个服务,以及位于仓库根目录下的一个.claude/目录:

    my-backend/
    ├── .claude/                  # Everything Claude needs to operate well
    │   ├── CLAUDE.md             # Root-level instructions (always loaded)
    │   ├── rules/                # Enforced coding constraints
    │   ├── skills/               # Custom slash commands with deep context
    │   └── settings.json         # Command permissions
    ├── services/
    │   ├── api-service/          # NestJS consumer API (port 5001)
    │   ├── operator-service/     # NestJS operator/vendor API (port 5002)
    │   ├── chat-service/         # Express + Socket.IO (JavaScript, port 5005)
    │   └── job-service/          # Scheduled jobs, no HTTP server
    ├── db/migration/             # Flyway migrations per schema
    └── package.json              # Root: only husky + lint-staged
    

    每个服务都是一个独立的应用程序,拥有自己的package.json、tsconfig.json以及构建输出文件。需要注意的是,其中的chat-service是基于Express和Socket.IO的纯JavaScript实现,而job-service则完全没有HTTP服务器。正是这种多样性使得助手无法仅通过单个文件就推断出相关信息。

    在每个NestJS服务中,功能模块都采用相同的三层结构:

    src/modules/booking/
    ├── booking.module.ts
    ├── controllers/    # HTTP routing only, no logic
    ├── services/       # Business logic and orchestration
    ├── repositories/   # TypeORM queries only
    └── dtos/           # Request/response shapes, validation
    

    控制器仅负责路由HTTP请求,服务层包含业务逻辑与协调功能,而仓库层则存放TypeORM查询语句。如果从未有人向助手解释过这种分层结构,它很可能会混淆这些职责——比如误以为服务层负责发起查询,或是控制器会直接调用仓库层。将各层的功能明确写下来,才能确保其遵守相应的边界划分。

    第一步:安装并登录

    只需安装一个全局包,然后进行一次浏览器登录即可:

    npm install -g @anthropic-ai/claude-code
    claude   # opens a browser for one-time OAuth
    

    完成登录后,即可在任意目录下启动claude。每位开发者都需在自己的设备上完成安装和登录,无需共享团队密钥。安装方式会随时间变化,如果npm安装路径已不再被推荐,请查阅最新的Claude Code文档。

    对于单仓库项目而言,有一个细节尤为重要。项目配置是相对于仓库根目录来确定的,因此根目录下的 .claude/ 文件中的指令在任何会话启动位置都适用。如果你的工作仅限于某个服务内,那就从该服务目录(如 services/api-service/)中启动 Claude,这样搜索和命令才能针对正确的包执行。

    步骤 2:编写 CLAUDE.md 说明文档

    CLAUDE.md 是影响最大的文件,因为助手会在处理其他任何内容之前先读取它。撰写时可以像为新入职的资深工程师做培训那样:假设对方具备基本能力,只需解释与你的系统相关的特定内容。在单仓库项目中,采用两级结构更为合理:

    1. 根目录下的 .claude/CLAUDE.md,用于说明服务归属、共享规范以及请求处理流程。
  • 每个服务都有一份 services/{service}/CLAUDE.md,其中记载了仅适用于该服务的模块模式、认证及依赖注入相关细节。
  • 根文件:让服务归属成为明确决策,而非猜测

    在多服务仓库中,最严重的错误就是将代码放在错误的位置,因为这类代码最初能通过本地审查,后来才会被发现存在边界违规问题。根文件通过路由表和明确的禁止模式列表来避免这种情况。以下是该文件可能包含的内容示例:

    ## Monorepo StructureFour services under `services/`, each independently runnable.
    Run all commands from inside the service directory.| Service            | Port | Stack                    | Purpose              |
    | ------------------ | ---- | ------------------------ | -------------------- |
    | `api-service`      | 5001 | NestJS + TypeScript      | Consumer-facing API  |
    | `operator-service` | 5002 | NestJS + TypeScript      | Operator/vendor mgmt |
    | `chat-service`     | 5005 | Express + Socket.IO + JS | Real-time messaging  |
    | `job-service`      | —    | Node.js + TypeScript     | Scheduled jobs       |## Which Service Owns What- **operator-service** — if the data describes _what an experience is_:
      package definitions, pricing, taxes, availability, operator accounts.
    - **api-service** — if the data describes _a consumer's interaction_:
      bookings, users, auth, trip planning, payments, notifications.| Task involves...                   | Service                            |
    | ---------------------------------- | ---------------------------------- |
    | Package categories, pricing, slots | operator-service                   |
    | Booking status, user profiles      | api-service                        |
    | Stripe payments (consumer side)    | api-service                        |
    | Cross-service booking flow         | Start in api-service → IMS adapter |## Global Request Pipeline (in order)1. `TraceContextMiddleware` — sets `correlationId` in `AsyncLocalStorage`
    2. `ValidationPipe` — whitelist mode, flattens DTO errors
    3. `LoggerInterceptor` — logs request/response
    4. `TransformInterceptor` — wraps return value in the standard shape
    5. Exception filters (`HttpExceptionFilter`, `TypeOrmExceptionFilter`, …)## Standard Response ShapeControllers return `{ data, meta }`; `TransformInterceptor` wraps it:
    return { data: { items }, meta: { path: request.path } };
    // → { success: true, data: { items }, meta: { path }, error: null }## Cross-Service Communicationapi-service NEVER touches operator-service's database. All reads/writes go
    through HTTP adapters in `src/common/adapters/` (they handle OAuth refresh and
    trace propagation). Need operator data? Call the adapter, never the DB.## What Claude Must Never Do- Use `console.log` → use `logMessage()` from `src/common/utils/logger`
    - Check `user.role` → use `PermissionCodes` + `@Permissions()`
    - Query the DB inside a loop → use bulk queries (`IN`, `JOIN`)
    - Use relative `../../` imports → use the `src/` alias
    - Set TypeORM `synchronize: true` → Flyway manages all schema changes
    

    该文件的几个部分值得关注。所有权部分的表述采用了一种测试形式(“这些数据描述的是某种体验本身,还是用户与它的交互?”),而非目录列表形式,这样就能让助手对从未见过的请求进行分类。流程部分按执行顺序列出了中间件、管道、拦截器和过滤器,从而避免生成的代码重复实现TransformInterceptor已有的功能。跨服务部分明确指出api-service绝不能直接访问操作员数据库,必须通过src/common/adapters/中的适配器来操作。

    一份有用的CLAUDE.md与一份劣质的CLAUDE.md的区别在于:

    • 决策规则优于文件结构。 像“描述体验内容的数据应放在operator-service中”这样的规则无需通过提问即可消除歧义。
    • 明确响应格式。 若不规定,每个控制器都会自行封装响应,导致结构混乱。
    • 详细列出禁止事项。 “绝不要使用console.log”比“请使用我们的日志记录工具”更易于执行。
    • 记录有意移除的内容。 如果删除了RolesGuard或关闭了synchronize,需说明原因。否则助手可能会重新引入这些功能,因为它们看起来像是NestJS中的常规选择。

    服务级文件:仅一个服务所需的深度

    仅在一个服务内部才重要的细节应放在该服务自身的文件中。对于api-service而言,这些细节包括分层结构、身份验证、DI令牌以及后台任务:

    ## Module Architecture — never mix layerscontrollers/ HTTP routing only — call service, return data
    services/ Business logic — orchestrate repos, no direct DB calls
    repositories/ TypeORM queries only — accept EntityManager for transactions## Authentication- `JwtAuthGuard` validates Bearer tokens, attaches `UserRequest`
    - `PermissionsGuard` reads `@Permissions([PermissionCodes.X])`
    - Never use RolesGuard or check role names — removed intentionally## Dependency InjectionRegister services by interface token, and inject by token, not class:
    { provide: SERVICE_INTERFACE.BOOKING_SERVICE, useClass: BookingService }## Background JobsBullMQ + Redis. Processors live in `src/modules/{module}/processors/`.
    Enqueue via the module's service — never from a controller.
    

    在没有指导的情况下,这里有两点很容易出错。首先,提供者是通过诸如SERVICE_INTERFACE.BOOKING_SERVICE这样的接口令牌来注册和注入的,而非通过类名,因此直接注入BookingService的生成构造函数虽然可以编译通过,但会违背团队的设计规范。其次,BullMQ任务只能从模块的服务中提交,绝不能从控制器中提交,而处理程序则位于src/modules/{module}/processors/目录下。将这些信息放在根文件之外,可以确保在处理chat-service时不会受到它们的影响。

    步骤3:为绝不能发生的情况添加规则

    CLAUDE.md用于描述系统的运作方式,而规则则规定了生成的代码不得包含哪些内容。一个实用的思维模型是针对 AI 输出设计的 ESLint 配置。这些规则位于 .claude/rules/ 目录下的 Markdown 文件中。

    确定内容归属的简单方法如下:

    • 架构、模块结构、服务归属、约定以及命令引用应放在 CLAUDE.md 中。
    • 严格的禁止性规定、绝不能出现的模式,以及与安全或性能相关的恒定要求则应放在 rules/ 中。

    请记住,规则是加载到模型上下文中的指令,而非静态分析工具。它们能大幅降低违规概率,但对于真正重要的恒定要求,仍需通过 lint 规则、测试或 CI 检查来加以保障。

    规则:禁止 N+1 查询

    N+1 查询的代码写起来很简单,但在审查时也容易被忽略,因为每一行看起来都很正常。在 .claude/rules/database-queries.md 中的规则,若能同时展示被禁止的模式及替代方案,则效果最佳。采用被禁止的方式时,每个预订都会触发一次查询:

    // Wrong — N queries
    for (const b of bookings) {
      b.user = await this.userRepo.findOne(b.userId);
    }
    

    而替代方案则使用 TypeORM 的 In 操作符通过单次查询加载所有用户,然后通过以 id 为键的 Map 在内存中存储这些用户:

    // Correct — one query
    const users = await this.userRepo.find({ where: { id: In(userIds) } });
    const userMap = new Map(users.map((u) => [u.id, u]));
    bookings.forEach((b) => {
      b.user = userMap.get(b.userId);
    });
    

    这样做的好处是批量查询在生成代码时就已经写好,因此性能问题根本不会出现在拉取请求中。记得先从预订记录中收集 userIds,如果多个预订使用了同一用户,则需考虑去重处理。

    规则:使用权限而非角色名称

    第二条规则 .claude/rules/permissions.md 禁止类似这样的临时角色检查:

    // Wrong
    if (user.roles.includes('admin')) { ... }
    

    相反,端点应声明其所需的权限,由守卫机制来强制执行:

    // Correct
    @Permissions([PermissionCodes.MANAGE_BOOKINGS])
    @UseGuards(JwtAuthGuard, PermissionsGuard)
    

    相关理由也应记录在规则文件中,因为这有助于模型进行泛化:随着产品的发展,角色会被合并、拆分或重命名,而基于角色名称编写的所有检查在这种情况发生时都会悄然失效。权限代码则是稳定的常量,能够经受此类重组。规则还应说明这些权限代码的存放位置,在本例中为 src/common/constants/permissions.constant.ts。

    规则:使用路径别名而非相对导入

    第三条规则 .claude/rules/imports.md 负责规范导入方式:

    // Wrong
    import { UserService } from "../../services/user.service";
    // Correct
    import { UserService } from "src/modules/user/services/user.service";
    

    该别名之所以有效,是因为 tsconfig.json 将 baseUrl 设置为 "./",而 Jest 的 moduleNameMapper 也会映射相同的前缀。同时提及这两点很重要:如果仅了解 TypeScript 的相关规则,可能会误认为该别名在测试中不安全。

    其他值得记录的规则

    同样的“禁止并给出替代方案”格式适用于许多常见的 NestJS 问题。针对这些问题的自定义文件规则如下:

    • 错误处理:应使用由 ErrorMessages 或 ResponseCodeKeys 常量构建的 NestJS 异常;绝不能硬编码错误信息或直接返回原始错误对象。
    • 日志记录:需使用结构化的 logMessage() 辅助函数并按相应级别记录日志;严禁使用 console.log。
  • 事务处理:多次写入的操作必须共享同一个EntityManager事务,这样一旦失败就不会导致数据处于半提交状态。
  • DTO验证:每个请求DTO都需要使用class-validator装饰器。由于ValidationPipe以白名单模式运行,没有装饰器的属性会被静默忽略,这看起来像是数据丢失而非错误。
  • 实体与迁移的对应关系:任何实体的更改都需要有对应的Flyway迁移文件,且synchronize参数应保持为false。
  • Swagger文档覆盖:每个接口都会添加@ApiSpec注解以及带类型的响应DTO,从而确保生成的API文档准确无误。
  • 第4步:将重复的工作流程转化为技能

    技能是针对特定项目的命令,其中包含了操作流程、模板以及参考资料。对于任何需要处理多个文件的任务——无论是添加接口端点、编写迁移脚本、构建模块框架还是审查拉取请求——它们都是理想的工具。与 CLAUDE.md 不同,技能的内容仅在实际使用时才会被加载,因此复杂的模板不会在每次会话中占用过多资源。

    技能目录的结构

    用于构建接口端点的技能通常采用如下结构:工作流程记载在 SKILL.md 中,模板存放在 assets/ 目录下,而背景资料则位于 references/ 目录中:

    .claude/skills/api-development/
    ├── SKILL.md                    # Workflow, trigger, steps
    ├── assets/
    │   ├── controller-template.md
    │   ├── service-template.md
    │   ├── repository-template.md
    │   └── dto-template.md
    └── references/
        ├── nestjs-conventions.md
        └── swagger-documentation.md
    

    SKILL.md 中的工作流程

    该文件以一个简单的标题开头:

    API Development Skill
    

    随后在正文中会说明何时调用该技能,并按固定顺序详细阐述各个步骤:

    ## TriggerInvoke with `/api-development` when building or modifying a NestJS endpoint.## Workflow1. **Determine service & module** — check CLAUDE.md routing; ask if ambiguous.
    2. **Read what exists** — entities, repositories, DTOs already in the module.
    3. **Generate the DTO first** — it defines the contract. class-validator
       decorators; extend `PaginationRequestDTO` for lists.
    4. **Generate the repository** — inject `DataSource`; accept an optional
       `EntityManager` for transactions; queries only, no logic.
    5. **Generate the service** — inject the repository interface token; use
       `ErrorMessages` constants; throw `NotFoundException` etc.
    6. **Generate the controller** — `@ApiSpec` + `@ApiOkResponse`; return
       `{ data, meta }`; no business logic.
    7. **Wire the module** — add to `providers`, export by interface token.
    

    这种排序是有意为之的。首先检查路由表可以防止该功能被错误地接入到不合适的服务中。读取现有的实体、仓库和DTO可以避免重复创建它们。在其他任何操作之前先生成DTO,这样就能确定接口规范,后续的每一层都会基于此规范进行构建:仓库接受一个可选的EntityManager以便参与事务处理,服务通过接口令牌注入,并使用带有ErrorMessages常量的标准异常进行错误处理,而控制器则仅负责记录操作、委托任务并返回{ data, meta }。最后一步是将各个提供者连接到模块中,并通过令牌将其导出。

    一个实用提示:当前版本的 Claude Code 通过 SKILL.md 文件顶部的 YAML 前置信息来识别技能(包括 name 和说明使用场景的 description)。上面的示例未包含这些内容,因此在自己创建技能时请查阅最新文档以了解确切格式。

    模板必须是真实代码

    位于 assets/ 目录中的模板只有能够成功编译才有用。伪代码会让模型用自己的默认值填补空白。控制器模板以类声明和基于令牌的注入开始:

    @ApiTags("bookings")
    @Controller("bookings")
    export class BookingController {
      constructor(
        @Inject(SERVICE_INTERFACE.BOOKING_SERVICE)
        private readonly bookingService: IBookingService,
      ) {}
    

    接着是一个完整的端点示例,一次性展示了守卫顺序、权限装饰器、Swagger 装饰器以及返回数据结构:

      @Post()
      @UseGuards(JwtAuthGuard, PermissionsGuard)
      @Permissions([PermissionCodes.CREATE_BOOKING])
      @ApiSpec({ summary: "Create a new booking" })
      @ApiCreatedResponse({ type: CreateBookingSuccessDto })
      async createBooking(
        @Body() dto: CreateBookingDto,
        @Req() req: UserRequest,
      ): Promise<{ data: BookingEntity }> {
        const booking = await this.bookingService.createBooking(dto, req.user.id);
        return { data: booking };
      }
    }
    

    注意这个方法所体现的要点:JwtAuthGuard会在PermissionsGuard之前执行,用户ID来自类型化的UserRequest对象,控制器不包含任何业务逻辑,并且会返回{ data }以便由TransformInterceptor进行封装。具备这样的能力后,按照团队的工作方式,描述中的场景能够在三分钟内完成整个端点的开发(包括控制器、服务层、数据访问层以及DTO类,并将它们整合到模块中),而非最初尝试时所需的约二十分钟。实际所需时间还会取决于模板的大小以及后续需要进行的审查工作量。

    其他值得考虑的技能

    任何遵循团队特定规范的重复性工作流程都是合适的应用对象:

    • 迁移生成器:根据您设定的版本控制和命名规则,在相应的架构文件夹中创建 Flyway 迁移文件,并同步生成对应的实体变更内容。
    • 模块脚手架工具:自动生成模块文件以及已配置好连接关系的三层架构和依赖注入组件。
    • 拉取请求审核:检查代码差异,识别是否存在 N+1 查询、权限使用不当、架构违规以及测试缺失等问题。
    • 测试生成器:按照服务层的模拟与测试 fixture 规范编写 *.spec.ts 测试文件。
    • 跨服务适配器:为相关服务添加 IMS 适配器方法,以及对应的响应接口和数据转换辅助工具。
    • 用例文档生成:将需求转化为包含“给定/当...时/然后”验收标准的用户故事。

    第 5 步:配置权限

    .claude/settings.json 决定了助手可以在无需事先询问的情况下运行哪些shell命令。合理的默认设置会预先批准代码检查、测试和构建操作,同时明确禁止那些可能破坏工作成果或将其公开的命令:

    {
      "permissions": {
        "allow": [
          "Bash(npm run lint:*)",
          "Bash(npm run test:*)",
          "Bash(npm run build)",
          "Bash(npx jest *)"
        ],
        "deny": ["Bash(rm -rf *)", "Bash(git push *)", "Bash(git reset --hard *)"]
      }
    }
    

    设置分为两层:

    • .claude/settings.json 适用于整个团队,并会被提交到版本控制中。
    • .claude/settings.local.json 用于存储个人自定义设置,仅对你本人有效,应被加入gitignored文件。

    禁止列表是一种安全保障,而非沙箱环境。这些规则通过模式匹配命令字符串,因此应将其视为防止意外行为的防护措施,而非安全边界。

    保持设置健康的实践

    应将 .claude/ 视为生产级代码:它会将你的规范传递给每位工程师及每次会话,因此需要经过审查、保留历史记录并定期清理。

    • 保持CLAUDE.md内容简短,最多约300行。其中的所有信息都会与助手需要阅读的代码争夺上下文空间。如果某项信息可以从代码中得出,就无需写入。
    • 将规则表述为禁止性要求。实际上,“永远不要在循环中查询数据库”比“优先使用批量查询”更能有效应对实际问题。
    • 将模板保存在技能文件中。它们应仅在技能运行时加载,而非每次会话都加载。
    • 在git中版本控制.claude/目录。新规则通过拉取请求添加,过时的规则则被删除。
    • 在存在两种合理选择的情况下使用决策表,比如DTO的放置位置、实体的存放位置或服务边界等。在助手使用的Markdown文件中,表格比段落更清晰。

    本指南所述配置的完整布局:

    .claude/
    ├── CLAUDE.md                  # Root guide (<300 lines)
    ├── settings.json              # Team permissions (committed)
    ├── rules/
    │   ├── database-queries.md    # No N+1
    │   ├── permissions.md         # PermissionCodes, no role checks
    │   └── imports.md             # src/ alias only
    └── skills/
        └── api-development/
            ├── SKILL.md
            ├── assets/            # controller/service/repository/dto templates
            └── references/        # conventions, swagger
    

    那些会悄悄降低输出质量的错误

    这些错误都会生成表面上看似正常但实际上违反既定规范的代码,正因如此才会带来严重后果。

    1. 将CLAUDE.md写成NestJS教程。模型已经了解该框架,只需记录与自己相关的内容:模块名称、错误常量、响应格式以及权限系统。
    2. 遗漏负面规则。那些看似正确的错误其实代价高昂;明确告知助手哪些行为不可取,就能避免整类错误。
    3. 将所有内容都放在根文件中。根文件会在每次会话中加载,因此将特定于服务的代码放在其中会在其他地方工作时污染上下文。
  • 跳过路由表。服务放置错误源于歧义而非无知,而路由表能让决策更加确定。
  • 未记录适配器模式。在通过HTTP连接的服务中,否则助手会尝试跨服务导入数据或查询其他服务的数据库。
  • 未提及synchronize: false。模型会默认TypeORM负责管理架构。若使用Flyway,则未生成迁移文件的实体变更会默默失败,因此需明确说明这一点。
  • 详细演示:一个请求的处理前后过程

    假设产品经理要求添加预订功能,以便在用户包裹上显示相应状态。

    在未配置的仓库中,助手错误地认为包定义在operator-service中,于是在该服务处修改包实体。它将状态字符串硬编码,并通过if (user.roles.includes('admin'))来限制该修改的权限。

    按照本指南中的配置,请求会逐步得到处理:

    1. 路由表将消费者可见的状态视为消费者交互的一部分,因此相关处理工作应在api-service中完成。
    2. 跨服务规则提示它,操作员包数据是通过IMS适配器读取的,因此该字段会被添加到适配器的转换逻辑中,而无需查询其他数据库。
    3. 运行/api-development会按顺序生成四层结构,从DTO开始,最终形成控制器。
  • 权限规则生成的是 @Permissions([PermissionCodes.VIEW_BOOKING_STATUS]),而非角色检查。
  • 错误处理规则则根据 ErrorMessages 常量生成 NotFoundException。
  • 原本一句含糊的描述,如今只需在四个文件中进行符合规范的修改,无需任何进一步澄清。

    日常需要做的更改

    初始化只需花费几小时时间。由此带来的变更包括:

    • 审查重点发生变化。 审核人员不再需要检查每个生成的文件中的硬编码字符串、错误的导入语句以及角色检查,而是将注意力集中在逻辑层面。
    • 接口响应速度更快。 在原有的配置下,手动连接四层架构需要大约二十分钟;而借助这项技能后,时间可缩短至三分钟以内。
  • 首次定位即准确无误。路由表会将文件放到正确的服务中,无需在后期审核时才发现问题。
  • 从源头避免常见错误。在代码生成阶段就能防止N+1查询以及多余的console.log调用,而无需在拉取请求中才发现这些问题。
  • 新人上手更便捷。由于相关规范已被明确记载并应用于他们的第一个AI辅助功能开发中,新工程师无需再猜测模块边界。
  • 有些好处需要数月时间才能显现。由于源自相同的模板和规则,间隔较长时间生成的代码看起来依然一致。审查人员也不必再重复撰写相同的评论。路由表和规则则充当了实际被使用的文档。再加上精简版的 CLAUDE.md 以及按需提供的技能,就能让精力集中在阅读代码上,而非重新学习各种规范。

    常见问题

    在单仓库中,文件应放在哪里? 将通用的 CLAUDE.md 放在 git 根目录下的 .claude/ 文件夹中,用于处理跨服务的问题;而在每个服务内部则放置该服务特有的 CLAUDE.md。根目录下的文件会始终被加载,而在该服务中工作时则会加载对应的服务文件。

    CLAUDE.md与规则有何不同? CLAUDE.md用于说明系统的运作方式;而规则则规定哪些内容绝不能出现,比如循环中的查询或角色名称检查。

    技能是如何运行的?像/api-development这样的技能由SKILL.md工作流程以及相关模板和参考资料支撑。调用该技能时,助手会遵循固定的多步骤程序,而非即兴应对。

    是否应该将.claude/提交到版本控制中?是的:CLAUDE.md、rules/、skills/和settings.json都应放入git中,以便团队共享统一的配置。个人自定义设置则放在gitignored目录下的settings.local.json中。

    核心要点

    • 在 NestJS 单体仓库中,正确性的保障依赖于一些无法通过单个文件体现的约定,因此没有上下文的辅助工具只能靠猜测。
    • 按类型划分这些知识:用 CLAUDE.md 说明事物的运作方式,用规则规定禁止的内容,用技能文档记录可重复执行的流程,用设置说明可无人值守运行的内容。
    • 服务归属决策表是影响最大的要素,应优先编写。
    • 需记录那些经过刻意设计的移除操作以及非默认选项,如 synchronize: false,因为这些正是模型否则会试图“修正”的内容。
    • 规则主要用于引导生成过程,而非强制执行;对于那些绝不能出错的规范,仍需依靠代码检查、测试和持续集成机制来保障。
    • 只要这些文件能像代码一样得到审查和优化,这项只需投入几小时的一次性工作,就能为每一个功能和新员工带来收益。