返回笔记首页

第 05 集:Rules 实战模板

主题配置

一、为什么要有模板

每次新开项目都从零写 .cursorrules 效率低,而且容易遗漏重要约束。

模板解决三个问题:

  1. 新项目快速启动:复制模板,改几个项目特定的参数就能用
  2. 约束不遗漏:模板包含了实际项目中反复踩坑总结出来的约束
  3. 团队统一:所有人用同一套模板,AI 生成的代码风格一致

二、NestJS 后端模板

文件名:node-nestjs.cursorrules

plain
# ===== NestJS 后端项目规范 =====

## 技术栈(必须遵守,不能替换)
- 运行时:Node.js 20+
- 框架:NestJS 10+
- 语言:TypeScript 5+,strict 模式开启
- ORM:Prisma 5+(不使用 TypeORM,不使用 Mongoose)
- 数据库:PostgreSQL 16+
- 鉴权:@nestjs/jwt + @nestjs/passport
- 文档:@nestjs/swagger(所有接口必须有 Swagger 注解)
- 验证:class-validator + class-transformer
- 配置:@nestjs/config(不允许硬编码任何配置值)

## 项目结构规范
src/
  ├── common/
  │   ├── decorators/
  │   ├── filters/
  │   ├── guards/
  │   ├── interceptors/
  │   └── dto/             # 共享 DTO(ResponseDto、PaginationDto)
  ├── modules/
  │   └── user/
  │       ├── dto/
  │       ├── user.controller.ts
  │       ├── user.service.ts
  │       └── user.module.ts
  ├── prisma/
  └── main.ts

## 命名规范
- 文件名:kebab-case(user-service.ts,不是 userService.ts)
- 类名:PascalCase(UserService)
- 方法名和变量:camelCase(findAll、userId)
- 常量:SCREAMING_SNAKE_CASE(MAX_LOGIN_ATTEMPTS)
- 数据库表和字段:snake_case(created_at)
- DTO 命名:动作+资源+Dto(CreateUserDto、UpdateUserDto、FindUsersDto)

## 接口返回格式(必须统一,不允许直接返回原始数据)
{
  "code": 0,
  "data": any,
  "message": "ok"
}

## Controller 规范
- 每个 Controller 必须有 @ApiTags('模块名')
- 每个接口必须有 @ApiOperation({ summary: '接口描述' })
- 每个接口必须有 @ApiResponse 定义成功和失败的返回格式
- 路径用 kebab-case(/api/user-profile,不是 /api/userProfile)

## Service 规范
- Service 不能引用任何 HTTP 相关对象(Request、Response、Headers)
- 所有 public 方法必须有 JSDoc 注释
- 复杂查询写在 Service 里,Controller 只做参数接收和返回

## 错误处理规范
- 使用 NestJS 内置异常类:NotFoundException、BadRequestException、
  UnauthorizedException、ForbiddenException、ConflictException
- 不允许空的 catch 块
- 错误消息要对用户友好,不能暴露技术细节

## TypeScript 规范
- 禁止使用 any 类型,用 unknown 替代或定义具体类型
- 所有函数参数和返回值必须有明确类型
- 用 interface 定义数据结构(除非需要联合类型才用 type)
- 可选字段用 ?(name?: string,不是 name: string | undefined)

## 不要做的事情
- 不要生成 .spec.ts 测试文件
- 不要在 Entity 或 DTO 里使用 TypeORM 装饰器
- 不要在 Service 层直接操作 HTTP 请求/响应
- 不要硬编码端口号、数据库 URL、Secret Key
- 不要用 console.log,用 NestJS 内置的 Logger
- 不要在单个函数里超过 30 行代码,超过就拆分

每条规则背后的决策逻辑

技术栈声明写"不使用 XX"的原因

单纯写"使用 Prisma"不够,AI 训练数据里 TypeORM 比重大,不明确否定偶尔会混入。

DTO 命名写"动作+资源+Dto"的原因

不规定时 AI 会随机命名:UserInputUserPayloadNewUserDtoCreateUserRequest 都有可能,同一项目里混用导致混乱。

返回格式给了 JSON 示例而不只是文字描述

只写"统一格式"AI 理解各有不同,有人会生成 { success, result },有人会生成 { status, body }。给了具体示例,没有歧义空间。

不要超过 30 行的原因

AI 倾向于把所有逻辑写在一个大函数里,不主动拆分。30 行限制强制它在生成时就考虑单一职责。


三、Vue3 前端模板

文件名:vue3-frontend.cursorrules

plain
# ===== Vue3 前端项目规范 =====

## 技术栈(必须遵守)
- 框架:Vue 3.4+,使用 Composition API(不使用 Options API)
- 语言:TypeScript 5+,strict 模式
- 构建工具:Vite 5+
- 状态管理:Pinia(不使用 Vuex)
- 路由:Vue Router 4+
- 样式:TailwindCSS(优先使用原子类,减少自定义 CSS)
- 请求:axios(封装成统一的 request.ts,不直接用 axios)
- 图标:@iconify/vue

## 组件规范
- 所有组件使用 <script setup lang="ts"> 语法
- 组件文件名:PascalCase(UserProfile.vue)
- 页面组件放在 src/views/,通用组件放在 src/components/
- 单个组件不超过 200 行,超过就拆子组件
- Props 必须用 defineProps<{}>() 方式定义类型
- Emits 必须用 defineEmits<{}>() 方式定义

## 标准组件结构(生成组件时参照这个格式)
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import type { User } from '@/types/user'

interface Props {
  userId: number
  showAvatar?: boolean
}

const props = defineProps<Props>()
const emit = defineEmits<{
  update: [user: User]
  delete: [id: number]
}>()
</script>

<template>
  <div class="flex items-center gap-4">
  </div>
</template>

## Pinia Store 规范
- Store 文件放在 src/stores/,命名:useXxxStore.ts
- 每个 Store 只负责一个业务域的状态
- 使用 defineStore 的 setup 函数写法(不用 options 写法)
- 方法命名:动词开头(fetchUsers、updateUser、deleteUser)

## API 请求规范
- 所有 API 调用封装到 src/api/ 目录,按模块划分(user.ts、product.ts)
- 函数命名:动词+名词(getUserList、createUser)
- 统一使用 src/utils/request.ts 里封装的 axios 实例

## 路由规范
- 路由路径:kebab-case(/user-profile)
- 需要登录的路由加 meta: { requiresAuth: true }
- 所有页面组件懒加载:() => import('./views/User.vue')

## TypeScript 规范
- 接口类型定义放在 src/types/ 目录
- API 返回数据必须定义类型,不用 any
- 响应式数据:ref<具体类型>(),不要 ref<any>()

## 不要做的事情
- 不要写 Options API
- 不要使用 Vuex
- 不要在组件里直接写 axios.get()
- 不要写内联样式(style="..."),用 Tailwind 类
- 不要在 template 里写复杂逻辑,提取成 computed 或方法
- 不要在多处重复定义相同类型,放到 src/types/ 统一管理

四、团队共享方案

方案一:直接提交到 Git(推荐)

.cursorrules 提交到代码仓库,团队成员 git pull 后自动有这份规范。

注意.cursorrules 不要加进 .gitignore,它就是要共享的。

bash
git add .cursorrules
git commit -m "chore: 添加 Cursor Rules 项目规范"
git push

方案二:多 Rules 文件(Cursor 新版本支持)

对于全栈项目,可以把 Rules 按模块拆分,放在 .cursor/rules/ 目录下:

plain
.cursor/
  rules/
    backend.md    # NestJS 相关规范
    frontend.md   # Vue3 相关规范
    git.md        # Git commit 规范

创建目录结构:

bash
mkdir -p .cursor/rules
touch .cursor/rules/backend.md
touch .cursor/rules/frontend.md

好处:

  • 拆分管理,改后端规范不影响前端规范
  • 每个文件保持精简,不会触发 Token 超长导致后半段被忽略
  • 团队不同成员各自负责各自模块的 Rules 维护

方案三:Rules 模板仓库

建一个专门的 Rules 模板仓库,里面按技术栈分门别类存放模板文件:

plain
company-cursor-rules/
  ├── nestjs/
  │   └── .cursorrules
  ├── vue3/
  │   └── .cursorrules
  ├── react/
  │   └── .cursorrules
  └── README.md

新项目开始时,去这个仓库复制对应的模板,根据项目特殊情况微调。


五、Rules 的迭代方式

Rules 是活文档,不是写完就不动的。

触发更新 Rules 的时机

发现 AI 生成了不符合预期的代码 → 思考这件事 Rules 里有没有说清楚 → 有就是 Rules 描述不够精确,改精确;没有就追加新条目。

一个具体例子

发现 AI 生成的 Service 方法直接 return prisma.user.findMany(),没有分页。

Rules 里加:

plain
- Service 的 findAll 类方法必须支持分页,参数通过 PaginationDto 传入(page、pageSize)

下次再生成,AI 会自动加分页逻辑。

Rules 迭代的节奏建议

  • 前两周:发现问题就追加,Rules 会增长较快
  • 两周后:趋于稳定,只有遇到新类型问题才追加
  • 每个月:回顾一次,删掉已经不适用的条目,保持精简

六、演示操作步骤

准备工作

Step 1:初始化两个演示项目

bash
# NestJS 后端
npx @nestjs/cli new nestjs-rules-demo
cd nestjs-rules-demo && npm install

# Vue3 前端
npm create vue@latest vue3-rules-demo
# 选项:TypeScript ✅  Vue Router ✅  Pinia ✅  ESLint ✅
cd vue3-rules-demo && npm install

Step 2:把两份模板文件提前准备好放在桌面(内容就是上面第二、三章的完整内容)


NestJS 模板演示

Step 1:用 Cursor 打开 nestjs-rules-demo

Step 2:在没有 Rules 的状态下,先在 Chat 里发送:

plain
帮我创建一个商品管理模块(ProductModule),包含 CRUD 接口

截图或记录输出结果(用了什么 ORM、有无 Swagger、有无 ResponseDto)

Step 3:在项目根目录创建 .cursorrules,把 NestJS 模板内容粘贴进去

Step 4:新建 Chat(必须新建,不能在原来的 Chat 里继续),发送同样的 Prompt:

plain
帮我创建一个商品管理模块(ProductModule),包含 CRUD 接口

Step 5:逐项对比两次的输出差异,重点检查:

  • ORM 是否为 Prisma
  • 是否有 @ApiTags@ApiOperation
  • 返回值是否是 { code, data, message } 格式
  • 是否有生成 .spec.ts 文件(不应该有)
  • 是否有 any 类型(不应该有)

Step 6:演示 Rules 迭代——假设发现 AI 生成的 Service 没有 JSDoc 注释

.cursorrules 里追加一行:

plain
- 所有 public 方法必须有 JSDoc 注释,格式:/** 方法描述 @param 参数 @returns 返回值 */

新建 Chat,再次发送 Prompt,观察这次的输出是否有 JSDoc 注释。


Vue3 模板演示

Step 1:用 Cursor 打开 vue3-rules-demo

Step 2:在项目根目录创建 .cursorrules,粘贴 Vue3 模板内容

Step 3:在 Chat 里发送:

plain
创建一个 UserCard 组件,展示用户的头像、名字、邮箱,
点击可以触发 select 事件,传递 userId

Step 4:检查生成的组件:

检查项 期望结果
脚本语法 <script setup lang="ts">
Props 定义方式 defineProps<{ userId: number }>()
Emits 定义方式 defineEmits<{ select: [userId: number] }>()
样式方式 Tailwind 原子类,无内联样式
有无 Options API 不应该有
有无 any 类型 不应该有

团队共享演示

Step 1:在 nestjs-rules-demo 里确认 .cursorrules 存在

Step 2:执行 Git 提交:

bash
git init
git add .cursorrules
git status   # 确认 .cursorrules 在暂存区里
git commit -m "chore: 添加 NestJS 项目 Cursor Rules 规范"

Step 3:演示多 Rules 文件结构(新版 Cursor)

bash
mkdir -p .cursor/rules
# 把 .cursorrules 的内容拆分成两个文件
cp .cursorrules .cursor/rules/backend.md
touch .cursor/rules/git.md

git.md 里写入 Git commit 相关规范(后续第 11 集会详细讲):

plain
## Git Commit 规范
- 使用 Conventional Commits 格式:type(scope): subject
- type:feat / fix / refactor / test / docs / chore
- subject 不超过 50 字,动词开头

展示拆分后的目录结构:

bash
ls -la .cursor/rules/

Spec Coding 实战补充:05 Rules 实战模板

来源:Spec Coding实战/05 Rules 实战模板.md,已合并到本章节。

1. 模板的价值

好的 .cursorrules 不是随手写几句话,而是把团队实践中踩过的坑沉淀成约束。

这一节给出三套可以直接使用的模板:

  • NODE + NESTJS 后端模板(本课重点)
  • 前端模板(Vue3 + TypeScript)
  • 全栈模板(NestJS + Vue3)

每套模板都可以直接复制到项目根目录,按实际情况调整。


2. NODE + NESTJS 后端模板(完整版)

markdown
# ============================================================

# NestJS Backend Cursor Rules

# 适用:Node.js 20 + NestJS 10 + Prisma 5 + PostgreSQL

# ============================================================

# 技术栈

- Runtime: Node.js 20 LTS
- Framework: NestJS 10.x
- Language: TypeScript 5.x(启用严格模式:strict: true)
- ORM: Prisma 5(禁止引入 TypeORM、Sequelize、Mongoose)
- Database: PostgreSQL 16
- 认证: @nestjs/jwt + passport-jwt
- 校验: class-validator + class-transformer
- 日志: nestjs-pino(禁止 console.log,禁止 winston)
- 配置: @nestjs/config(.env 文件)
- HTTP 客户端: axios(如需外部请求)

# 代码风格(与 prettier.config.js 保持一致)

- semi: false(无分号)
- singleQuote: true(单引号)
- tabWidth: 2(2 空格缩进)
- printWidth: 100
- trailingComma: 'all'
- endOfLine: 'lf'

# 目录结构

src/ ├── common/ │ ├── constants/ # 常量(错误码、枚举等) │ ├── decorators/ # 自定义装饰器 │ ├── dto/ # 公共 DTO(分页、排序等) │ │ └── page-query.dto.ts │ ├── filters/ # 全局异常过滤器 │ │ └── all-exceptions.filter.ts │ ├── guards/ # 全局守卫 │ ├── interceptors/ # 全局拦截器 │ │ └── transform.interceptor.ts # 统一响应格式 │ └── utils/ # 工具函数 ├── config/ # 配置 │ └── configuration.ts # 配置工厂函数 ├── modules/ # 业务模块 │ └── [feature]/ │ ├── [feature].controller.ts │ ├── [feature].service.ts │ ├── [feature].module.ts │ └── dto/ │ ├── create-[feature].dto.ts │ ├── update-[feature].dto.ts │ └── query-[feature].dto.ts └── prisma/ └── prisma.service.ts

plain
# 统一响应格式
所有接口通过 TransformInterceptor 自动包装,无需在 Controller 手动包装:

```typescript
// 成功响应结构
{
  "code": 0,
  "message": "ok",
  "data": T | null
}

// 分页响应结构
{
  "code": 0,
  "message": "ok",
  "data": {
    "list": T[],
    "total": number,
    "page": number,
    "pageSize": number
  }
}
```text

Controller 直接 return 数据,interceptor 自动包装。 异常通过 BusinessException 抛出,filter 自动格式化为统一错误格式。

# 异常处理
使用 BusinessException 统一抛出业务异常:

```typescript
// 不要这样写
throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)

// 要这样写
throw new BusinessException(ErrorCode.USER_NOT_FOUND)
```text

ErrorCode 枚举定义在 src/common/constants/error-code.ts

# 命名约定
+ 文件名:kebab-case(create-user.dto.ts,user.service.ts)
+ 类名:PascalCase(CreateUserDto,UserService)
+ 接口/类型:PascalCase,Interface 不加 I 前缀
+ 变量、方法:camelCase
+ 常量:UPPER_SNAKE_CASE
+ 环境变量:UPPER_SNAKE_CASE(DB_HOST,JWT_SECRET)

# DTO 规范
```typescript
// create DTO:所有字段必填,明确类型校验
export class CreateUserDto {
  @ApiProperty({ description: '邮箱地址' })
  @IsEmail({}, { message: '邮箱格式不正确' })
  @IsNotEmpty()
  email: string

  @ApiProperty({ description: '密码,8-32位含大小写和数字' })
  @IsString()
  @MinLength(8)
  @MaxLength(32)
  @Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/, {
    message: '密码必须包含大小写字母和数字',
  })
  password: string
}

// update DTO:继承 create DTO 的 Partial
export class UpdateUserDto extends PartialType(CreateUserDto) {}

// query DTO:继承公共分页 DTO
export class QueryUserDto extends PageQueryDto {
  @IsOptional()
  @IsString()
  search?: string
}
```text

# Prisma 使用规范
+ 查询时必须过滤软删除:where: { isDeleted: false, ...其他条件 }
+ 密码等敏感字段必须在 select/omit 中排除
+ 分页查询用 findMany + count 组合,禁止用 cursor 分页(除非明确需要)
+ Prisma 模型字段使用 camelCase,数据库列使用 snake_case(通过 @map 映射)
+ 禁止在 Service 外部直接使用 PrismaService,业务 module 通过自己的 Service 操作数据

```typescript
// 正确:分页查询写法
const [list, total] = await this.prisma.$transaction([
  this.prisma.user.findMany({
    where: { isDeleted: false, ...whereCondition },
    skip: (page - 1) * pageSize,
    take: pageSize,
    orderBy: { [sortBy]: order },
    omit: { password: true },
  }),
  this.prisma.user.count({ where: { isDeleted: false, ...whereCondition } }),
])
```text

# 日志规范
```typescript
// 注入 logger
constructor(
  private readonly logger: Logger,
) {}

// 结构化日志,不要用字符串拼接
this.logger.log({ userId: user.id, email: user.email }, 'User created successfully')
this.logger.error({ userId, error: err.message }, 'Failed to create user')

// 禁止
console.log('user created:', user)
this.logger.log('user ' + user.id + ' created')
```text

# 安全规范
+ JWT 认证接口统一使用 @Auth() 自定义装饰器(内含 @UseGuards + @ApiBearerAuth)
+ 密码统一用 bcrypt,rounds=10,从配置读取:this.configService.get('BCRYPT_ROUNDS')
+ 禁止在日志、响应、错误信息中暴露 password、secret、token 完整内容
+ 禁止在代码中硬编码任何密钥或敏感配置

# Swagger 文档
+ 所有 Controller 加 @ApiTags()
+ 所有接口加 @ApiOperation({ summary: '...' })
+ 所有 DTO 字段加 @ApiProperty()
+ 需要认证的 Controller 加 @ApiBearerAuth()

# 测试要求
+ 单元测试文件命名:[feature].service.spec.ts
+ 使用 Jest,Mock 所有外部依赖(PrismaService、MailService 等)
+ 测试方法命名:should + 动词 + 场景(should_create_user_successfully)
+ 每个 service 方法至少覆盖:正常流程 + 关键异常场景

# AI 生成代码要求
+ 直接输出完整代码,不需要前置解释
+ 禁止使用 any 类型,遇到类型不确定的地方使用 unknown 或具体类型
+ 生成的代码必须可以直接运行,不留 TODO、FIXME 占位
+ 生成多个文件时,每个文件必须包含完整的 import 语句
+ 如果需要新增环境变量,在代码注释里标注:// 需要添加 ENV: VARIABLE_NAME

```plain
---

### 3. 前端模板(Vue3 + TypeScript)

```markdown
# ============================================================
# Vue3 Frontend Cursor Rules
# 适用:Vue3 + TypeScript + Vite + Pinia
# ============================================================

# 技术栈
- 框架:Vue 3.x(Composition API,禁止 Options API)
- 构建:Vite 5.x
- 语言:TypeScript 5.x(严格模式)
- 状态管理:Pinia(禁止 Vuex)
- 路由:Vue Router 4
- HTTP:axios(封装在 src/api/request.ts)
- UI:[根据项目选择:Element Plus / Naive UI / Ant Design Vue]
- 样式:SCSS(禁止在 <style> 中写无 scoped 的全局样式,全局样式放 src/styles/)
- 包管理:pnpm

# 代码风格
- semi: false
- singleQuote: true
- tabWidth: 2
- printWidth: 100

# 目录结构

src/ ├── api/ # 接口层 │ ├── request.ts # axios 实例和拦截器 │ ├── user.ts # 用户相关接口 │ └── types/ # 接口入参/返回类型定义 ├── assets/ # 静态资源 ├── components/ # 公共组件 │ └── [ComponentName]/ │ ├── index.vue │ └── types.ts # 组件 Props 类型(可选) ├── composables/ # 组合式函数(useXxx 命名) ├── router/ │ └── index.ts ├── stores/ # Pinia stores │ └── useUserStore.ts # use + 名词 + Store 命名 ├── styles/ # 全局样式 ├── utils/ # 工具函数 └── views/ # 页面组件 └── user/ ├── UserList.vue └── UserDetail.vue

plain
# Vue 组件规范
- 使用 <script setup lang="ts"> 语法(禁止 Options API)
- Props 用 defineProps<{...}>() 泛型写法,不用 withDefaults(除非有默认值)
- Emits 用 defineEmits<{...}>() 类型声明写法
- 组件文件名:PascalCase(UserCard.vue)
- 单文件组件顺序:<script setup> → <template> → <style scoped>

```vue
<!-- 正确写法示例 -->
<script setup lang="ts">
interface Props {
  userId: string
  showAvatar?: boolean
}

interface Emits {
  (e: 'update', user: User): void
}

const props = defineProps<Props>()
const emit = defineEmits<Emits>()
</script>
```text

# Pinia Store 规范
使用 Setup Store 写法(函数式,不用 Options Store):

```typescript
// stores/useUserStore.ts
export const useUserStore = defineStore('user', () => {
  const currentUser = ref<User | null>(null)
  const isLoggedIn = computed(() => !!currentUser.value)

  async function fetchCurrentUser() {
    currentUser.value = await userApi.getMe()
  }

  return { currentUser, isLoggedIn, fetchCurrentUser }
})
```text

# API 请求规范
+ 所有接口定义在 src/api/ 对应模块文件
+ 接口函数返回 Promise<T>,类型明确
+ 不在组件/store 里直接调用 axios,通过 api 函数调用
+ 响应拦截器统一处理错误,业务代码不需要每次判断 res.code

# 命名约定
+ 组件:PascalCase(UserCard,OrderList)
+ 组合式函数:use 前缀,camelCase(useUserList,usePageQuery)
+ Store:use 前缀 + Store 后缀(useUserStore)
+ 页面路由:kebab-case(/user-list,/order-detail)
+ CSS 类名:BEM 风格(.user-card__avatar--active)

# TypeScript 规范
+ 禁止使用 any,用 unknown 或具体类型
+ 接口用 interface,简单类型别名用 type
+ 不用 namespace
+ 类型文件放在 types/ 目录或与使用文件同目录

# AI 生成代码要求
+ 直接输出完整代码
+ Vue 组件必须包含完整的 script/template/style 三个块
+ 生成的 TypeScript 类型要完整,不留 any 占位
+ 如果用到了尚未安装的包,在注释里注明:// 需要安装:pnpm add xxx

```plain
---

### 4. 全栈模板(NestJS + Vue3)

全栈模板在后端和前端规则的基础上,增加联调和共享类型的约定:

```markdown
# ============================================================
# Full-Stack Rules(NestJS + Vue3)
# ============================================================

# [继承后端模板的所有规则]
# [继承前端模板的所有规则]

# 前后端联调约定

## 共享类型
- 后端 DTO 和前端请求/响应类型保持一致
- 如果有 API 文档生成(Swagger),前端从 swagger.json 生成类型(openapi-typescript)
- 否则手动在 src/api/types/ 维护类型,与后端 DTO 字段名保持完全一致

## 接口路径约定
- 后端接口前缀:/api/v1/
- 前端 axios baseURL:http://localhost:3000(开发),通过 Vite proxy 转发

## 开发环境
- 后端运行在 3000 端口
- 前端运行在 5173 端口
- Vite proxy 配置:/api/* → http://localhost:3000

## 错误处理一致性
前端拦截器处理统一响应格式:
- code === 0:正常,返回 data
- code === 40100(未认证):跳转到登录页
- code !== 0:Toast 提示 message,抛出错误

# 目录结构(Monorepo)

project/ ├── apps/ │ ├── backend/ # NestJS 后端 │ └── frontend/ # Vue3 前端 ├── packages/ │ └── shared/ # 共享类型(可选) ├── package.json # 根 package.json └── pnpm-workspace.yaml

plain

5. 团队协作:共享与版本管理

把 rules 纳入版本控制

bash
# 提交到 git,让所有人共享
git add .cursorrules .cursorignore
git commit -m "chore: init cursor rules for backend development"

团队讨论 rules 变更

rules 文件不应该由一个人随便修改。建议:

plain
1. 提 PR 修改 .cursorrules
2. PR 描述里说明:为什么要加这条规则(踩了什么坑)
3. 团队 review 后合并

rules 不是一次性的

随着项目演进,rules 需要更新:

  • 升级了 Prisma 版本,有 Breaking Change?更新 Prisma 使用规范
  • 新引入了一个日志库?更新日志规范,删除旧的
  • 发现 AI 总是在某个地方犯同样的错?加一条明确的禁止项

6. 自定义模板的最佳实践

原则一:用否定约束效果往往好于正面说明

markdown
# 差

使用 Prisma 操作数据库

# 好

使用 Prisma 操作数据库(禁止引入 TypeORM、mongoose、knex)

AI 面对"你可以用任何东西"时,会随机选择。禁止具体的替代项,AI 就没有歧义了。

原则二:代码示例比文字描述有效

markdown
# 差

异常处理要统一

# 好

异常统一通过 BusinessException 抛出:

````typescript
throw new BusinessException(ErrorCode.USER_NOT_FOUND)
// 禁止:throw new HttpException('用户不存在', 404)
// 禁止:throw new Error('User not found')
```text

```plain
#### 原则三:分清"必须"和"建议"

```markdown
# 必须(不能违反)
- 所有密码必须 bcrypt 加密
- 禁止在接口返回密码字段

# 建议(有理由可以例外)
- Controller 方法尽量保持简洁,复杂逻辑放 Service
text

在 rules 里用"必须"、"禁止"等强制词表达约束;用"建议"、"尽量"表达偏好。

#### 原则四:保持简洁,不超过 500 行

超过 500 行的 rules,AI 对后半部分的遵守程度会显著下降。 如果内容很多,拆成多个文件(用 .cursor/rules/ 目录),按场景激活。

---

### 7. 小结

Rules 模板是团队工程规范的数字化。它做的事情,实际上就是把团队 Code Review 中反复提出的意见,提前告诉 AI,让 AI 一次就写对。

三套模板(NestJS 后端 / Vue3 前端 / 全栈)覆盖了大部分场景,可以直接复制使用,按项目实际情况裁剪。

**记住**:最好的 rules 不是在项目开始前一次性写完的,而是在开发过程中不断发现问题、不断往里加约束沉淀出来的。