外观
AI 编码规则 Copilot / Cursor / Cline
本文档面向使用
@maxtan/nest-core构建业务系统的 AI 编码助手。 将本文档放入业务项目根目录(如.cursorrules、.github/copilot-instructions.md), 或在 AI 对话中引用,即可让 AI 按照框架规范自动生成高质量业务代码。
技术栈约束
AI 生成代码时 必须 使用以下技术栈,不得替换。
| 类别 | 技术选型 | 版本要求 |
|---|---|---|
| 运行时 | Node.js | >= 22 |
| 语言 | TypeScript | 5.9+ |
| 框架 | NestJS + Fastify | NestJS 11、Fastify 5 |
| ORM | Prisma | 7 |
| 验证 | Zod | 4 |
| 核心库 | @maxtan/nest-core | 2.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 = 36004. 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-cls。getCurrentUserId() / 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 NULL | trim + 长度约束,必填 |
zVarCharNull(max) | String? @db.VarChar(n) | trim + 空串 -> null |
zStr | String? | trim + 空串 -> undefined(可选) |
zStrNull | 可空文本字段 | trim + 空串 -> null |
zStrReq | String NOT NULL | trim + 必填 |
zInt / zIntReq | Int | 字符串 -> 整数预处理 |
zNum() / zNumReq | Float | 字符串 -> 数字预处理 |
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,适合列表筛选 |
zBool | Boolean | 智能转换('true'/'1'/'yes' -> true) |
zDate / zDateReq | DateTime | 字符串/时间戳 -> Date |
zJson() | Json | JSON 字符串解析 |
zId | - | 通用 ID(= zStrReq) |
zIds | - | ID 数组(逗号分隔字符串 -> 数组) |
zArr | - | 逗号分隔字符串 -> 数组 |
zPage() | - | 分页参数(current, size, sortBy, sortOrder) |
关键原则
- Prisma 非空列 -> 使用对应的必填预处理器(
zVarChar、zStrReq、zIntReq) - Prisma 可空列(
?)先判断语义:空串表示未传 ->.optional();空串表示清空 ->zStrNull/zVarCharNull - Update Schema =
CreateXxx.partial().extend({ id: zStrReq }) - List Schema =
zPage().extend({ keyword: zStr, ... }),可选筛选字段优先使用zQueryInt/zQueryId/zQueryEnumInt/zQueryEnumStr - 字段长度常量 -> 提取为
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 生成代码时必须遵守以下规则
- 禁止 Express API(
req/res/next/multer)-> 用 Fastify + 框架装饰器 - 禁止
new HttpException()-> 统一new AppException() - 禁止手动构造
{ code, data, message }响应 -> 直接return或resMessage() - 禁止
@PrismaTransactional内调用$transaction()-> 并行用Promise.all() - 禁止自行注册
ClsModule-> 已由AuthModule内置注册,直接用getCurrentUserId() - 禁止
import { Request } from 'express'-> 用getCurrentUserId()获取用户信息 - 禁止裸
@Query()/@Body()不加验证 -> 必须@Query(Zod(Schema)) - 禁止在 module 目录外放 controller / service -> 按功能模块组织
- 禁止手动编辑
generated/目录 -> 由prisma generate生成 - 禁止多数据源事务不指定
prismaField-> 多数据源时@PrismaTransactional({ prismaField: 'xxx' }) - 禁止单个事务跨多个数据源 -> 每个事务只操作一个
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.ts的imports中注册ProductModule。