开发规范
接口与路由
- API 前缀:
/api/v1 - Swagger:
/api-docs
项目结构
youlai-nest/
├── src/
│ ├── main.ts # 应用入口
│ ├── app.module.ts # 根模块
│ ├── auth/ # 认证与会话
│ ├── system/ # 系统管理
│ ├── codegen/ # 代码生成
│ ├── file/ # 文件上传
│ ├── message/ # 消息通知
│ ├── common/ # 公共能力(守卫/拦截器/异常/工具等)
│ ├── config/ # 配置模块
│ └── types/ # 类型定义
├── sql/ # 数据库脚本
├── .env # 基础配置
├── .env.dev # 开发环境配置
├── .env.prod # 生产环境配置
└── package.json # 项目配置目录约定
| 目录 | 说明 |
|---|---|
src/auth | 登录/验证码/会话模式(jwt/redis-token) |
src/system | 系统管理(用户/角色/菜单/部门/字典) |
src/codegen | 代码生成 |
src/file | 文件上传(OSS/MinIO/本地) |
src/message | SSE 实时通信 |
src/common | 守卫/拦截器/异常/Redis/工具等 |
src/config | 配置模块(TypeORM/Redis/OSS/JWT) |
开发规范
架构分层
- Controller:接收请求,调用 Service,返回响应
- Service:业务逻辑处理
- Repository:数据库操作(TypeORM)
重要:Controller 禁止直接注入 Repository,必须通过 Service 操作数据库。
路由顺序
静态路由在前,动态路由在后,避免 :id 抢占:
typescript
@Controller("users")
export class UserController {
@Put("read-all") // 静态路由在前
readAll() {}
@Put(":id") // 动态路由在后
update() {}
}新模块创建
建议沿用项目结构:
- 系统模块:
src/system/<module> - 公共模块:
src/common/<module>
命名规范
文件命名
- 小驼峰:
user.controller.ts,user.service.ts - 类名大驼峰:
UserController,UserService
接口命名
typescript
// RESTful 风格
@Controller("users")
export class UserController {
@Get() // GET /api/v1/users
findAll() {}
@Get(":id") // GET /api/v1/users/:id
findOne() {}
@Post() // POST /api/v1/users
create() {}
@Put(":id") // PUT /api/v1/users/:id
update() {}
@Delete(":id") // DELETE /api/v1/users/:id
remove() {}
}参数校验
使用 DTO + class-validator:
typescript
export class CreateUserDto {
@IsString() @MaxLength(50) username: string;
@IsString() @MinLength(6) password: string;
}全局 ValidationPipe:app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }))
统一响应
typescript
// 成功: { code: '00000', msg: '操作成功', data: user }
// 错误: throw new BusinessException('A0001', '认证失败')错误处理
typescript
export class BusinessException extends HttpException {
constructor(code: string, msg: string) {
super({ code, msg, data: null }, HttpStatus.OK);
}
}全局异常过滤器:src/common/filter/
接口幂等
- 优先使用数据库唯一约束兜底
- 对高风险写操作使用 Redis 分布式锁
typescript
const locked = await this.redisService.lock(`lock:order:${orderId}`, 30);
if (!locked) throw new BusinessException("A0004", "请勿重复提交");
try {
/* 业务处理 */
} finally {
await this.redisService.unlock(lockKey);
}HTTP 状态码规范
核心原则
HTTP 状态码表示 HTTP 请求是否成功到达并被处理,业务错误不属于 HTTP 层面的错误。
状态码对照表
| HTTP | 场景 | 说明 |
|---|---|---|
| 200 | 业务错误 | 验证码错误、密码错误、用户不存在、数据校验失败等 |
| 200 | 成功 | 请求成功,业务正常执行 |
| 401 | 未认证 | 未登录、Token 无效或过期 |
| 403 | 无权限 | 已登录但无接口访问权限(RBAC 检查失败) |
| 404 | 资源不存在 | 接口路径不存在 |
| 500 | 服务器错误 | 数据库异常、空指针、未捕获异常等 |
三种"无权限"的区分
| 场景 | HTTP | 说明 |
|---|---|---|
| 没登录 | 401 | 没有身份 |
| 登录了但没有接口权限 | 403 | 没有接口访问权 |
| 有权限访问接口,但业务校验不通过 | 200 | 业务失败 |
响应格式示例
json
// 成功 - HTTP 200
{
"code": "00000",
"msg": "成功",
"data": { ... }
}
// 业务错误 - HTTP 200
{
"code": "A0240",
"msg": "验证码错误",
"data": null
}
// 未认证 - HTTP 401
{
"code": "A0230",
"msg": "访问令牌无效",
"data": null
}
// 无权限 - HTTP 403
{
"code": "A0301",
"msg": "访问未授权",
"data": null
}
// 服务器错误 - HTTP 500
{
"code": "B0001",
"msg": "系统执行出错",
"data": null
}为什么业务错误返回 200
- HTTP 语义:HTTP 200 表示服务器成功处理了请求,只是业务结果不符合预期
- 前端处理:前端可以根据响应体中的
code字段判断具体错误类型,统一显示错误提示 - 日志记录:业务错误通常不需要触发 HTTP 错误日志,减少监控噪音
- 主流方案:大多数后台管理系统采用此方案,便于前后端协作
错误码规范
设计原则
参考《阿里巴巴 Java 开发手册》错误码规范:
- 快速溯源:通过错误码快速定位错误来源
- 简单易记:5 位字符串,易于记忆和比对
- 沟通标准化:脱离文档也能准确沟通
错误码结构
错误来源(1位)+ 数字编号(4位)| 来源 | 前缀 | 说明 |
|---|---|---|
| 用户端错误 | A | 参数错误、认证失败、权限不足等 |
| 系统错误 | B | 系统超时、内部异常等 |
| 第三方服务 | C | 数据库、中间件、外部 API 等 |
号段划分
| 号段 | 分类 | 示例 |
|---|---|---|
A0001 | 一级宏观 | 用户端错误 |
A0100 | 二级宏观 | 用户注册错误 |
A01xx | 三级细分 | A0101 用户未同意协议 |
A0200 | 二级宏观 | 用户登录异常 |
A02xx | 三级细分 | A0230 Token 无效 |
常用错误码
| 错误码 | 说明 | 场景 |
|---|---|---|
00000 | 成功 | 正常执行 |
A0001 | 用户端错误 | 一级宏观 |
A0200 | 用户登录异常 | 登录失败 |
A0230 | Token 无效或过期 | 认证失败 |
A0301 | 访问未授权 | 权限不足 |
A0400 | 请求参数错误 | 参数校验失败 |
A0506 | 请勿重复提交 | 防重复提交 |
B0001 | 系统执行出错 | 一级宏观 |
C0001 | 第三方服务出错 | 一级宏观 |
排序字段规范
字段命名
| 字段名 | 类型 | 说明 |
|---|---|---|
sort | int | 排序号(升序,值越小越靠前) |
createTime | datetime | 创建时间(降序) |
updateTime | datetime | 更新时间(降序) |
排序规则
typescript
// ✅ 推荐:先按 sort 升序,再按创建时间降序
await this.userRepository.find({
order: {
sort: 'ASC',
createTime: 'DESC',
},
});
// 示例:角色列表排序
await this.roleRepository.find({
order: {
sort: 'ASC',
createTime: 'DESC',
updateTime: 'DESC',
},
});
// 示例:菜单列表排序
await this.menuRepository.find({
order: {
sort: 'ASC',
},
});默认排序
| 业务场景 | 默认排序 |
|---|---|
| 列表查询 | sort ASC, createTime DESC |
| 树形结构 | sort ASC |
| 日志记录 | createTime DESC |
代码注释规范
类注释
typescript
/**
* 用户业务服务类
*
* @author Ray.Hao
* @since 2024/1/14
*/
@Injectable()
export class UserService {
// ...
}方法注释
typescript
/**
* 获取用户分页列表
*
* @param query 查询参数
* @returns 用户分页列表
*/
async getUserPage(query: UserQuery): Promise<PageResult<UserPageDto>> {
// ...
}
/**
* 新增用户
*
* @param dto 用户表单对象
* @returns 是否新增成功
*/
async saveUser(dto: CreateUserDto): Promise<boolean> {
// ...
}属性注释
typescript
export class User {
/** 用户ID */
id: number;
/** 用户名 */
username: string;
/** 用户状态:1-启用,0-禁用 */
status: number;
}日志规范
日志级别
| 级别 | 场景 |
|---|---|
error | 错误信息,需要立即处理 |
warn | 警告信息,可能存在问题 |
log | 关键流程信息 |
debug | 调试信息 |
日志格式
typescript
// ✅ 推荐:使用结构化日志
this.logger.log('用户登录成功', {
userId,
username,
ip: request.ip,
});
this.logger.error('用户登录失败', {
username,
reason: error.message,
stack: error.stack,
});
// ❌ 不推荐:字符串拼接
this.logger.log(`用户登录成功:userId=${userId}`);敏感信息脱敏
typescript
// ✅ 推荐:敏感信息脱敏
this.logger.log('用户登录', {
mobile: maskMobile(mobile),
});
// ❌ 不推荐:打印敏感信息
this.logger.log('用户登录', {
password, // ❌ 禁止打印密码
});最佳实践
避免魔法值
typescript
// ❌ 不推荐
if (user.status === 1) { }
// ✅ 推荐:使用枚举
if (user.status === UserStatus.ENABLED) { }
// ✅ 推荐:使用常量
export enum UserStatus {
ENABLED = 1,
DISABLED = 0,
}数组判空
typescript
// ❌ 不推荐
if (list.length > 0) { }
// ✅ 推荐
if (list && list.length > 0) { }字符串判空
typescript
// ❌ 不推荐
if (str !== null && str.length > 0) { }
// ✅ 推荐
if (str && str.trim()) { }避免空指针
typescript
// ❌ 不推荐
return user.username === 'admin';
// ✅ 推荐
return user?.username === 'admin';
// ✅ 推荐:使用可选链
return user?.roles?.some(role => role.code === 'ADMIN');代码风格
- ESLint + Prettier
- 使用项目脚本:
pnpm lint/pnpm lint:fix
相关文件
| 文件 | 说明 |
|---|---|
src/common/exceptions/ | 异常处理 |
src/common/interceptors/ | 响应拦截器 |
src/common/guards/ | 认证守卫 |
src/common/decorators/ | 自定义装饰器 |
eslint.config.mjs | ESLint 配置 |
