跳转到内容

AI 编码规则 Copilot / Cursor / Cline

本文档面向使用 @maxtan/nest-core 构建业务系统的 AI 编码助手。 将本文档放入业务项目根目录(如 .cursorrules.github/copilot-instructions.md), 或在 AI 对话中引用,即可让 AI 按照框架规范自动生成高质量业务代码。


技术栈约束

AI 生成代码时 必须 使用以下技术栈,不得替换。

类别技术选型版本要求
运行时Node.js>= 22
语言TypeScript5.9+
框架NestJS + FastifyNestJS 11、Fastify 5
ORMPrisma7
验证Zod4
核心库@maxtan/nest-core2.x
包管理pnpm-

注释与代码风格规范

强制约束

AI 生成代码时 必须 遵守以下注释与风格规则,保持整个项目注释风格统一。

注释规则

1. 方法 / 函数 - 必须使用 JSDoc 块注释

所有方法(包括 Controller、Service、Repository 的公共和私有方法)必须 在方法声明前使用 /** */ 风格的 JSDoc 注释,说明方法作用:

typescript
/**
 * 根据用户 ID 查询详情
 */
async findById(id: string) {
  // ...
}

/**
 * 分页查询商品列表
 */
async list(query: ListProduct) {
  // ...
}

对于参数或返回值需要额外说明的复杂方法,可补充 @param / @returns

typescript
/**
 * 批量导入用户
 * @param list 用户数据数组
 * @param skipDuplicate 是否跳过重复记录
 * @returns 成功导入的数量
 */
async batchImport(list: CreateUser[], skipDuplicate = true): Promise<number> {
  // ...
}

2. 方法体内部 - 必须使用单行注释

方法体内的逻辑说明 只用 // 单行注释,禁止 在方法体内使用 /** */ 块注释:

typescript
/**
 * 创建用户
 */
async create(dto: CreateUser) {
  // 检查用户名是否已存在
  const exists = await this.prisma.user.findUnique({
    where: { username: dto.username },
  })
  if (exists) throw new AppException('用户名已存在')

  // 加密密码
  const hashed = await hashPassword(dto.password)

  // 写入数据库
  const user = await this.prisma.user.create({
    data: { ...dto, password: hashed },
  })
  return resMessage(user, '创建成功')
}

3. 常量 / 变量 - 使用 JSDoc 块注释

常量和变量 必须 使用 /** */ 风格注释,便于 IDE 悬停提示:

typescript
/** 用户名最大长度(对齐 schema.prisma) */
const USERNAME_MAX = 50

/** Redis 缓存 TTL(秒) */
const CACHE_TTL = 3600

4. Schema 字段 - 使用行尾注释

typescript
export const CreateUser = z.object({
  username: zVarChar(USERNAME_MAX, 2),         // 用户名,必填
  nickname: zVarChar(NICKNAME_MAX).optional(), // 昵称,可选
  status: zEnumInt(0, 1).optional(),           // 状态:0=禁用 1=启用
})

列表查询字段注释建议明确“查询过滤”语义:

typescript
export const ListUser = zPage().extend({
  status: zQueryEnumInt(0, 1),                 // 查询状态,不传/null/空串 -> 不过滤
  roleId: zQueryId(),                          // 查询角色 ID,不传/null/空串 -> 不过滤
})

其他风格规范

规则说明
语言注释统一使用 中文,专有名词保留英文(如 Prisma、Zod、JWT)
空行JSDoc 注释与上方代码之间保留 一个空行,与下方声明之间 不留空行
冗余注释禁止 复述代码本身(如 // 返回 user -> 不推荐),只注释业务意图
TODO待办事项用 // TODO: 描述 格式,必须说明具体内容
FIXME已知问题用 // FIXME: 描述 格式
注释掉的代码禁止 提交被注释掉的代码,直接删除

项目结构

业务项目遵循以下目录规范,AI 生成新文件时 必须 按此结构放置:

text
src/
├── main.ts                    # Fastify 启动入口
├── app.module.ts              # 根模块(框架模块 + 业务模块注册)
├── config/
│   └── config.ts              # JWT、Redis、数据库等配置
├── decorator/
│   └── auth.decorator.ts      # 自定义认证装饰器(可选)
├── prisma/
│   └── prisma.service.ts      # 继承 PrismaBaseService
├── module/
│   └── <模块名>/                      # 业务大模块(分组目录)
│       ├── schema/                    # 模块级 Schema(所有子模块共享)
│       │   └── <模块名>.schema.ts
│       ├── <子模块A>/                 # 子模块(最细粒度的功能单元)
│       │   ├── <子模块A>.module.ts
│       │   ├── <子模块A>.controller.ts
│       │   ├── <子模块A>.service.ts
│       │   └── <子模块A>.repository.ts # 可选:需复用查询逻辑时继承 PrismaRepository
│       └── <子模块B>/
│           ├── <子模块B>.module.ts
│           ├── <子模块B>.controller.ts
│           └── <子模块B>.service.ts
├── generated/
│   └── prisma/                # prisma generate 输出(勿手动编辑)
└── prisma/
    └── schema.prisma          # 数据模型定义

注意: 所有从 @maxtan/nest-core 可用的功能,直接 import 即可,无需自行实现。


根模块注册模板

AI 修改 app.module.ts 时参考此标准结构:

typescript
import { Module } from '@nestjs/common'
import {
  AuthModule, AuthGuard, ZodPipe, AllExceptionsFilter,
  PrismaModule, CacheModule, OperationModule,
  MultipartModule, TransformModule, getCurrentUserId
} from '@maxtan/nest-core'
import { APP_GUARD, APP_PIPE, APP_FILTER } from '@nestjs/core'
import { PrismaService } from '@/prisma/prisma.service'
import { JwtOptions, RedisConfig } from '@/config/config'
// ...业务模块 imports

@Module({
  imports: [
    // AuthModule 内置 ClsModule(nestjs-cls),自动创建请求上下文
    AuthModule.register(JwtOptions),
    PrismaModule.forRoot({
      service: PrismaService,
      middlewares: {
        softDelete: { enabled: true, models: 'auto' },
        audit: { enabled: true, getUserId: getCurrentUserId, fallbackUserId: 'system' }
      }
    }),
    CacheModule.register(RedisConfig),
    OperationModule.forRoot(),
    MultipartModule.forRoot(),
    TransformModule.forRoot(),
    // 业务模块在此注册
  ],
  providers: [
    { provide: APP_GUARD, useClass: AuthGuard },
    { provide: APP_PIPE, useClass: ZodPipe },
    { provide: APP_FILTER, useClass: AllExceptionsFilter },
  ]
})
export class AppModule {}

ClsModule 自动注册

AuthModule.register() 内部已注册 ClsModule.forRoot({ global: true, middleware: { mount: true } }), 无需单独导入 nestjs-clsgetCurrentUserId() / getCurrentUser() 基于 CLS 上下文工作,开箱即用。


Schema 编写规则(Zod)

强制约束

本框架 只用 Zod 进行参数验证。

基本范式

每个 Schema 文件导出 Zod Schema 常量同名 Type

typescript
import { z } from 'zod'
import {
  zPage,
  zStr,
  zStrReq,
  zStrNull,
  zVarChar,
  zVarCharNull,
  zEnumInt,
  zEnumStr,
  zQueryEnumInt,
  zQueryEnumStr
} from '@maxtan/nest-core'

// ─── 常量提取(对齐 schema.prisma 字段长度) ───
const USERNAME_MAX = 50
const NICKNAME_MAX = 50

// ─── 创建 ───
export const CreateUser = z.object({
  username: zVarChar(USERNAME_MAX, 2),          // String @db.VarChar(50) - 必填
  password: z.string().min(6).max(255),         // String @db.VarChar(255)
  nickname: zVarCharNull(NICKNAME_MAX),         // String? - 空串表示清空 -> null
  intro: zStrNull,                              // Text? / Json 内部普通可空文本
  status: zEnumInt(0, 1).optional(),            // Int @db.TinyInt @default(1) - 有默认 -> .optional()
  role: zEnumStr('ADMIN', 'USER', 'GUEST').optional(),
})
export type CreateUser = z.infer<typeof CreateUser>

// ─── 更新(全部可选 + 必填 id) ───
export const UpdateUser = CreateUser.partial().extend({ id: zStrReq })
export type UpdateUser = z.infer<typeof UpdateUser>

// ─── 列表查询(必须基于 zPage) ───
export const ListUser = zPage().extend({
  keyword: zStr,                                // 搜索关键词(可选、自动 trim)
  status: zQueryEnumInt(0, 1),
  role: zQueryEnumStr('ADMIN', 'USER', 'GUEST'),
})
export type ListUser = z.infer<typeof ListUser>

Zod 预处理器与 Prisma 字段对齐表

预处理器对齐 Prisma 类型行为
zVarChar(max, min?)@db.VarChar(n) NOT NULLtrim + 长度约束,必填
zVarCharNull(max)String? @db.VarChar(n)trim + 空串 -> null
zStrString?trim + 空串 -> undefined(可选)
zStrNull可空文本字段trim + 空串 -> null
zStrReqString NOT NULLtrim + 必填
zInt / zIntReqInt字符串 -> 整数预处理
zNum() / zNumReqFloat字符串 -> 数字预处理
zDecimal(p, s)Decimal(p,s)精确小数
zEnumInt(...values)@db.TinyInt限定整数集合
zEnumStr(...values)enum Xxx限定字符串集合
zQueryInt(options?)查询整数条件null / 空串 -> undefined,其它非法值报错
zQueryId(options?)查询 ID 条件null / 空串 -> undefined,其它非法值报错
zQueryEnumInt(...values)查询整数枚举null / 空串 -> undefined,适合列表筛选
zQueryEnumStr(...values)查询字符串枚举null / 空串 -> undefined,适合列表筛选
zBoolBoolean智能转换('true'/'1'/'yes' -> true)
zDate / zDateReqDateTime字符串/时间戳 -> Date
zJson()JsonJSON 字符串解析
zId-通用 ID(= zStrReq
zIds-ID 数组(逗号分隔字符串 -> 数组)
zArr-逗号分隔字符串 -> 数组
zPage()-分页参数(current, size, sortBy, sortOrder)

关键原则

  1. Prisma 非空列 -> 使用对应的必填预处理器(zVarCharzStrReqzIntReq
  2. Prisma 可空列?)先判断语义:空串表示未传 -> .optional();空串表示清空 -> zStrNull / zVarCharNull
  3. Update Schema = CreateXxx.partial().extend({ id: zStrReq })
  4. List Schema = zPage().extend({ keyword: zStr, ... }),可选筛选字段优先使用 zQueryInt / zQueryId / zQueryEnumInt / zQueryEnumStr
  5. 字段长度常量 -> 提取为 const XXX_MAX = n,与 schema.prisma 保持同步

字符串字段补充规则:

  • 不要默认生成 z.string()z.string().optional()z.string().nullable().optional()
  • 普通可选字符串优先 zStr
  • 必填字符串优先 zStrReq
  • 可空字符串优先 zStrNull
  • 可空 VarChar 字段优先 zVarCharNull

查询 helper 使用规则

当字段出现在 @Query() 的列表、筛选、统计场景时,优先按下面规则生成:

场景推荐写法原因
可选整数过滤zQueryInt()兼容前端 null / '' 初始值
可选 ID 过滤zQueryId()不需要在 Service 手动写 `id
可选整数枚举过滤zQueryEnumInt()保留合法值校验,同时允许“不筛选”
可选字符串枚举过滤zQueryEnumStr()适合下拉框、单选枚举筛选

不要这样生成列表查询:

typescript
export const ListUser = zPage().extend({
  status: zEnumInt(0, 1).optional(),
  role: zEnumStr('ADMIN', 'USER').optional()
})

推荐这样生成:

typescript
export const ListUser = zPage().extend({
  status: zQueryEnumInt(0, 1),
  role: zQueryEnumStr('ADMIN', 'USER')
})

Controller 编写规则

typescript
import { Controller, Get, Post, Delete, Query, Body, Param } from '@nestjs/common'
import { AuthPublic, Zod, Operation, Multipart, MultipartData, ParsedMultipart } from '@maxtan/nest-core'

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  /** 分页列表 */
  @Get('list')
  @Operation({ description: '查询用户列表' })
  getList(@Query(Zod(ListUser)) query: ListUser) {
    return this.userService.list(query)
  }

  /** 创建 */
  @Post('create')
  @Operation({ description: '创建用户', request: true, response: true })
  create(@Body(Zod(CreateUser)) dto: CreateUser) {
    return this.userService.create(dto)
  }

  /** 更新 */
  @Post('update')
  @Operation({ description: '更新用户' })
  update(@Body(Zod(UpdateUser)) dto: UpdateUser) {
    return this.userService.update(dto)
  }

  /** 删除 */
  @Delete(':id')
  @Operation({ description: '删除用户' })
  delete(@Param('id') id: string) {
    return this.userService.delete(id)
  }

  /** 公开接口(无需登录) */
  @Get('public-data')
  @AuthPublic()
  publicData() {
    return { hello: 'world' }
  }

  /** 文件上传 */
  @Post('avatar')
  @Multipart({ schema: UploadSchema, maxFileSize: 5 * 1024 * 1024, maxFiles: 1 })
  upload(@MultipartData() data: ParsedMultipart<UploadSchema>) {
    return { files: data.files.length, fields: data.fields }
  }
}

规则总结

规则说明
参数验证必须 @Query(Zod(Schema))@Body(Zod(Schema)),不要裸用 @Query()
操作日志建议 每个接口加 @Operation({ description })
公开接口必须 @AuthPublic() 装饰,否则全局 Guard 拒绝无 Token 请求
文件上传使用 @Multipart() + @MultipartData()禁止 multer
HTTP 方法查询用 @Get,写操作用 @Post,删除用 @Delete
返回值直接 return,框架自动包装为统一格式(无需手动构造 { code, data, message }

Service 编写规则

typescript
import { Injectable } from '@nestjs/common'
import {
  AppException, MSG, resMessage,
  prismaPage, prismaKeyword, prismaSoftRemove, prismaExists
} from '@maxtan/nest-core'
import { PrismaService } from '@/prisma/prisma.service'
import { CreateUser, UpdateUser, ListUser } from './schema/user.schema'

@Injectable()
export class UserService {
  constructor(private readonly prisma: PrismaService) {}

  /** 分页列表 */
  async list(query: ListUser) {
    const { current, size, keyword, status, role } = query
    return prismaPage(this.prisma.user, {
      where: {
        ...prismaKeyword(keyword, ['username', 'nickname']),
        ...(status !== undefined && { status }),
        ...(role !== undefined && { role }),
      },
      current,
      size,
      orderBy: { createdAt: 'desc' },
    })
  }

  /** 创建 */
  async create(dto: CreateUser) {
    if (await prismaExists(this.prisma.user, { username: dto.username })) {
      throw new AppException('用户名已存在')
    }

    const user = await this.prisma.user.create({ data: dto })
    return resMessage(user, '创建成功')
  }

  /** 更新 */
  async update(dto: UpdateUser) {
    const { id, ...data } = dto
    if (!await prismaExists(this.prisma.user, { id })) {
      throw new AppException(MSG.NOT_FOUND_DATA)
    }

    return this.prisma.user.update({ where: { id }, data })
  }

  /** 删除(软删除) */
  async delete(id: string) {
    if (!await prismaExists(this.prisma.user, { id })) {
      throw new AppException(MSG.NOT_FOUND_DATA)
    }

    await prismaSoftRemove(this.prisma.user, id)
    return resMessage({ id }, '删除成功')
  }

  /** 批量删除(软删除) */
  async batchDelete(ids: string[]) {
    return prismaSoftRemove(this.prisma.user, ids)
  }
}

规则总结

规则说明
异常throw new AppException('消息')throw new AppException(MSG.XXX)禁止 new HttpException()
自定义消息return resMessage(data, '操作成功'),不要手动构造 { code, data, message }
分页查询使用 prismaPage(model, params) 返回 Page<T>
模糊搜索prismaKeyword(keyword, ['field1', 'field2']) 生成 OR 条件
存在性检查prismaExists(model, where) 快捷检测
软删除prismaSoftRemove(model, id)prismaSoftRemove(model, ids)
条件过滤...(value !== undefined && { field: value }) 展开模式
获取当前用户getCurrentUserId() / getCurrentUser()不要 注入 Request
数据不存在先查后抛 AppException(MSG.NOT_FOUND_DATA)

Repository 编写规则(可选层)

简单 CRUD 在 Service 中直接操作 this.prisma。当多个 Service 需复用相同查询逻辑时,创建 Repository:

typescript
import { Injectable } from '@nestjs/common'
import { PrismaRepository } from '@maxtan/nest-core'
import { PrismaService } from '@/prisma/prisma.service'
import type { User } from '@prisma/client'

@Injectable()
export class UserRepository extends PrismaRepository<User> {
  constructor(private prisma: PrismaService) {
    super(prisma, 'user')
    // 默认:softDelete: false(由中间件处理),defaultOrderBy: { createdAt: 'desc' }
  }
}

事务安全

Repository 内部通过 usePrismaClient 动态获取 model,在 @PrismaTransactional 事务内自动参与事务。

Repository 继承后 自动拥有 以下方法(不要重新实现):

方法返回值说明
findById(id)T | null按 ID 查询
findByIds(ids)T[]按 ID 数组批量查询
findOne(where)T | null条件查单条
findList(where?)T[]条件列表
findPage({ where, current, size })Page<T>分页查询
findInBatches(handler, where?, options?){ count }游标分批遍历,大表安全迭代
count(where?)number计数
exists(where)boolean存在检测
create(data)T创建
createMany(data[], skip?){ count }批量创建
updateById(id, data)T按 ID 更新
updateMany(where, data){ count }批量更新
upsert(where, create, update)T存在更新 / 不存在创建
upsertMany(items, uniqueWhere, getUpdate?, options?){ count }批量 Upsert(逐条 upsert,适合中小批量)
bulkUpsert(items, uniqueWhere, getUpdate?, options?){ count }MySQL/MariaDB 原生批量 Upsert(高性能,绕过 Prisma 扩展)
removeById(id)T删除(自动走软删除)
removeByIds(ids){ count }批量删除
removeMany(where){ count }条件删除

事务编写规则

PrismaBaseService 已自动感知事务上下文,PrismaService 内置 Proxy,在 @PrismaTransactional 内部可直接使用 this.prisma.xxx 访问事务客户端,外部则访问原始客户端。

typescript
import { PrismaTransactional } from '@maxtan/nest-core'

@Injectable()
export class OrderService {
  constructor(private readonly prisma: PrismaService) {}

  @PrismaTransactional({ timeout: 10000 })
  async placeOrder(dto: CreateOrder) {
    // this.prisma 在事务内自动返回 tx,无需手动 usePrismaClient
    const order = await this.prisma.order.create({ data: { ... } })
    await this.prisma.inventory.update({
      where: { id: dto.productId },
      data: { stock: { decrement: dto.quantity } }
    })
    // 方法抛异常自动回滚,正常返回自动提交
    return order
  }
}
规则说明
获取客户端直接用 this.prisma(自动事务感知),或显式 usePrismaClient()
禁止嵌套严禁 调用 prisma.$transaction(),并行用 Promise.all()
自动传播嵌套调用其他 @PrismaTransactional 方法自动复用同一事务
跨类传播其他 Service 中的 @PrismaTransactional 方法自动检测外层事务并复用

事务内并行查询

typescript
@PrismaTransactional()
async listWithCount(where: any) {
  // ❌ 事务内禁止调用 $transaction() — 会抛出错误
  // const [records, total] = await this.prisma.$transaction([...])

  // ✅ 改用 Promise.all — 已在同一事务中
  const [records, total] = await Promise.all([
    this.prisma.user.findMany({ where }),
    this.prisma.user.count({ where })
  ])
  return { records, total }
}

事务隔离级别

typescript
// 金融场景:防止并发余额不一致
@PrismaTransactional({ isolationLevel: 'RepeatableRead' })
async transfer(fromId: string, toId: string, amount: number) {
  const from = await this.prisma.account.findUnique({ where: { id: fromId } })
  if (from.balance < amount) throw new AppException('余额不足')
  // ...异常自动回滚
}

事务 + Repository 配合

typescript
@PrismaTransactional()
async createUserWithProfile(dto: CreateUser) {
  // ✅ Repository 方法自动参与事务
  const user = await this.userRepo.create(dto)
  // ✅ 跨类调用自动复用事务
  await this.profileService.createProfile(user.id)
  return user
}

多数据源编写规则

定义多个 PrismaService

每个数据源对应一个独立的 PrismaBaseService 子类,各自导入自己的 PrismaClient

typescript
// prisma/primary.service.ts
import { PrismaClient } from '@/generated/primary/client'
import { PrismaBaseService, PrismaModelProxy } from '@maxtan/nest-core'

export interface PrimaryPrismaService extends PrismaModelProxy<PrismaClient> {}

@Injectable()
export class PrimaryPrismaService extends PrismaBaseService<PrismaClient> {
  constructor() {
    super()
    this.setClient(new PrismaClient())
  }
}

// prisma/secondary.service.ts
import { PrismaClient } from '@/generated/secondary/client'
import { PrismaBaseService, PrismaModelProxy } from '@maxtan/nest-core'

export interface SecondaryPrismaService extends PrismaModelProxy<PrismaClient> {}

@Injectable()
export class SecondaryPrismaService extends PrismaBaseService<PrismaClient> {
  constructor() {
    super()
    this.setClient(new PrismaClient())
  }
}

注册多个数据源

typescript
@Module({
  imports: [
    // 主库:全局注册,开启软删除 + 审计
    PrismaModule.forRoot({
      service: PrimaryPrismaService,
      token: 'PRIMARY_PRISMA',
      middlewares: {
        softDelete: { enabled: true, models: 'auto' },
        audit: { enabled: true, getUserId: getCurrentUserId }
      }
    }),
    // 从库:非全局,按需导入
    PrismaModule.forRoot({
      service: SecondaryPrismaService,
      token: 'SECONDARY_PRISMA',
      global: false,
      middlewares: {
        softDelete: { enabled: true, models: ['LogEntry'] }
      }
    })
  ]
})
export class AppModule {}

Service 中操作多数据源

typescript
@Injectable()
export class AnalyticsService {
  constructor(
    private readonly primaryDb: PrimaryPrismaService,
    @Inject('SECONDARY_PRISMA')
    private readonly secondaryDb: SecondaryPrismaService
  ) {}

  async logAction(userId: string, action: string) {
    const user = await this.primaryDb.user.findUnique({ where: { id: userId } })
    await this.secondaryDb.actionLog.create({
      data: { userId, action, username: user?.username }
    })
  }
}

多数据源事务

多数据源时 @PrismaTransactional 必须 通过 prismaField 指定目标属性名:

typescript
@Injectable()
export class OrderService {
  constructor(
    private readonly primaryDb: PrimaryPrismaService,
    private readonly secondaryDb: SecondaryPrismaService
  ) {}

  // ✅ 指定主库事务
  @PrismaTransactional({ prismaField: 'primaryDb' })
  async createOrder(dto: CreateOrder) {
    const order = await this.primaryDb.order.create({ data: dto })
    await this.primaryDb.inventory.update({
      where: { id: dto.productId },
      data: { stock: { decrement: dto.quantity } }
    })
    return order
  }
}
规则说明
一个数据源一个 Service 子类各自持有独立的连接和中间件
主库全局注册,从库按需注册从库设 global: false
事务指定 prismaField必须 明确指定属性名,不能用默认的 'prisma'
不跨库事务单次事务只能操作一个数据库,跨库用补偿机制
推荐按类注入按类注入有完整类型提示,按 token 注入适合动态切换

统一响应格式

框架 ResponseInterceptor 自动包装,AI 不需要 手动构造响应结构:

typescript
// 直接返回数据
return user                        // -> { code: 200, data: user, message: 'success', success: true, ... }
return resMessage(user, '创建成功') // -> { code: 200, data: user, message: '创建成功', ... }

// 抛异常自动处理
throw new AppException('用户不存在') // -> { code: 500, data: null, message: '用户不存在', success: false, ... }

预定义消息枚举

typescript
MSG.SUCCESS          // '操作成功'
MSG.FAIL             // '操作失败'
MSG.PARAMETER_ERROR  // '参数校验失败'
MSG.NOT_FOUND_DATA   // '数据不存在'
MSG.NOT_TOKEN        // '未提供认证令牌'
MSG.INVALID_TOKEN    // '无效的认证令牌'
MSG.EXPIRED_TOKEN    // '认证令牌已过期'
MSG.FORBIDDEN        // '无权限访问'

常用工具函数速查

AI 生成代码时可直接使用,全部从 @maxtan/nest-core 导入:

Prisma 查询

typescript
// 分页查询(最常用)
prismaPage(prisma.user, { where, current, size, orderBy })   // -> Page<User>

// 关键词模糊搜索(多字段 OR)
prismaKeyword(keyword, ['name', 'phone'])                    // -> { OR: [{ name: { contains } }, ...] }

// 单字段模糊
prismaContains({ name: '张', status: 1 }, ['name'])          // -> { name: { contains: '张' }, status: 1 }

// 排除敏感字段
prismaExclude(prisma, 'User', ['password'])                  // -> { id: true, username: true, ... }

// 软删除(单条 / 批量)
prismaSoftRemove(prisma.user, id)                  // 单条软删除
prismaSoftRemove(prisma.user, ids)                 // 批量软删除
prismaSoftRemove(prisma.user, id, { hard: true })  // 物理删除

// 存在性检查
prismaExists(prisma.user, { email: 'a@b.com' })    // -> boolean

// 树形构建(菜单、部门)
const tree = buildTree(flatMenus)                  // parentId 扁平 -> children 嵌套
const path = getTreePath(tree, n => n.id === id)   // 面包屑导航

通用工具

typescript
generateSnowflakeId()                    // 雪花 ID(字符串)
Guid()                                   // UUID v4
formatDate(new Date(), 'YYYY-MM-DD')     // 日期格式化
encryptAES(data, key)                    // AES-256-GCM 加密
cleanObject({ a: '', b: null, c: 1 })    // -> { c: 1 }(清理空值)
isEmpty(value) / isNotEmpty(value)       // 空值检测
getCurrentUserId()                       // 当前登录用户 ID(基于 nestjs-cls,任意位置可调用)
getCurrentUser()                         // 当前用户完整信息(AuthPayload)

禁止清单(Anti-Patterns)

AI 生成代码时必须遵守以下规则

  1. 禁止 Express APIreq/res/next/multer)-> 用 Fastify + 框架装饰器
  2. 禁止 new HttpException() -> 统一 new AppException()
  3. 禁止手动构造 { code, data, message } 响应 -> 直接 returnresMessage()
  4. 禁止 @PrismaTransactional 内调用 $transaction() -> 并行用 Promise.all()
  5. 禁止自行注册 ClsModule -> 已由 AuthModule 内置注册,直接用 getCurrentUserId()
  6. 禁止 import { Request } from 'express' -> 用 getCurrentUserId() 获取用户信息
  7. 禁止裸 @Query() / @Body() 不加验证 -> 必须 @Query(Zod(Schema))
  8. 禁止在 module 目录外放 controller / service -> 按功能模块组织
  9. 禁止手动编辑 generated/ 目录 -> 由 prisma generate 生成
  10. 禁止多数据源事务不指定 prismaField -> 多数据源时 @PrismaTransactional({ prismaField: 'xxx' })
  11. 禁止单个事务跨多个数据源 -> 每个事务只操作一个 PrismaClient,跨库用补偿机制

新建功能模块模板

当需要新增业务功能时,AI 应按此模板生成完整模块(以 product 为例):

1. schema/product.schema.ts

路径:src/module/product/schema/product.schema.ts

typescript
import { z } from 'zod'
import { zPage, zStr, zStrReq, zVarChar, zInt, zDecimal, zEnumInt, zQueryEnumInt } from '@maxtan/nest-core'

const NAME_MAX = 100

export const CreateProduct = z.object({
  name: zVarChar(NAME_MAX, 2),
  price: zDecimal(10, 2),
  stock: zInt.pipe(z.number().int().min(0)),
  status: zEnumInt(0, 1).optional(),
})
export type CreateProduct = z.infer<typeof CreateProduct>

export const UpdateProduct = CreateProduct.partial().extend({ id: zStrReq })
export type UpdateProduct = z.infer<typeof UpdateProduct>

export const ListProduct = zPage().extend({
  keyword: zStr,
  status: zQueryEnumInt(0, 1),
})
export type ListProduct = z.infer<typeof ListProduct>

2. product.service.ts

typescript
import { Injectable } from '@nestjs/common'
import {
  AppException, MSG, resMessage,
  prismaPage, prismaKeyword, prismaSoftRemove, prismaExists
} from '@maxtan/nest-core'
import { PrismaService } from '@/prisma/prisma.service'
import { CreateProduct, UpdateProduct, ListProduct } from './schema/product.schema'

@Injectable()
export class ProductService {
  constructor(private readonly prisma: PrismaService) {}

  async list(query: ListProduct) {
    const { current, size, keyword, status } = query
    return prismaPage(this.prisma.product, {
      where: {
        ...prismaKeyword(keyword, ['name']),
        ...(status !== undefined && { status }),
      },
      current,
      size,
      orderBy: { createdAt: 'desc' },
    })
  }

  async create(dto: CreateProduct) {
    const product = await this.prisma.product.create({ data: dto })
    return resMessage(product, '创建成功')
  }

  async update(dto: UpdateProduct) {
    const { id, ...data } = dto
    if (!await prismaExists(this.prisma.product, { id })) {
      throw new AppException(MSG.NOT_FOUND_DATA)
    }
    return this.prisma.product.update({ where: { id }, data })
  }

  async remove(id: string) {
    if (!await prismaExists(this.prisma.product, { id })) {
      throw new AppException(MSG.NOT_FOUND_DATA)
    }
    await prismaSoftRemove(this.prisma.product, id)
    return resMessage({ id }, '删除成功')
  }
}

3. product.controller.ts

typescript
import { Controller, Get, Post, Delete, Query, Body, Param } from '@nestjs/common'
import { Zod, Operation } from '@maxtan/nest-core'
import { ProductService } from './product.service'
import { CreateProduct, UpdateProduct, ListProduct } from './schema/product.schema'

@Controller('product')
export class ProductController {
  constructor(private readonly productService: ProductService) {}

  @Get('list')
  @Operation({ description: '商品列表' })
  list(@Query(Zod(ListProduct)) query: ListProduct) {
    return this.productService.list(query)
  }

  @Post('create')
  @Operation({ description: '创建商品', request: true })
  create(@Body(Zod(CreateProduct)) dto: CreateProduct) {
    return this.productService.create(dto)
  }

  @Post('update')
  @Operation({ description: '更新商品' })
  update(@Body(Zod(UpdateProduct)) dto: UpdateProduct) {
    return this.productService.update(dto)
  }

  @Delete(':id')
  @Operation({ description: '删除商品' })
  remove(@Param('id') id: string) {
    return this.productService.remove(id)
  }
}

4. product.module.ts

typescript
import { Module } from '@nestjs/common'
import { ProductController } from './product.controller'
import { ProductService } from './product.service'

@Module({
  controllers: [ProductController],
  providers: [ProductService],
  exports: [ProductService],
})
export class ProductModule {}

最后一步:在 app.module.tsimports 中注册 ProductModule


相关文档