Skip to content

开发规范

接口与路由

  • 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/messageSSE 实时通信
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;
}

全局 ValidationPipeapp.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

  1. HTTP 语义:HTTP 200 表示服务器成功处理了请求,只是业务结果不符合预期
  2. 前端处理:前端可以根据响应体中的 code 字段判断具体错误类型,统一显示错误提示
  3. 日志记录:业务错误通常不需要触发 HTTP 错误日志,减少监控噪音
  4. 主流方案:大多数后台管理系统采用此方案,便于前后端协作

错误码规范

设计原则

参考《阿里巴巴 Java 开发手册》错误码规范:

  1. 快速溯源:通过错误码快速定位错误来源
  2. 简单易记:5 位字符串,易于记忆和比对
  3. 沟通标准化:脱离文档也能准确沟通

错误码结构

错误来源(1位)+ 数字编号(4位)
来源前缀说明
用户端错误A参数错误、认证失败、权限不足等
系统错误B系统超时、内部异常等
第三方服务C数据库、中间件、外部 API 等

号段划分

号段分类示例
A0001一级宏观用户端错误
A0100二级宏观用户注册错误
A01xx三级细分A0101 用户未同意协议
A0200二级宏观用户登录异常
A02xx三级细分A0230 Token 无效

常用错误码

错误码说明场景
00000成功正常执行
A0001用户端错误一级宏观
A0200用户登录异常登录失败
A0230Token 无效或过期认证失败
A0301访问未授权权限不足
A0400请求参数错误参数校验失败
A0506请勿重复提交防重复提交
B0001系统执行出错一级宏观
C0001第三方服务出错一级宏观

排序字段规范

字段命名

字段名类型说明
sortint排序号(升序,值越小越靠前)
createTimedatetime创建时间(降序)
updateTimedatetime更新时间(降序)

排序规则

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.mjsESLint 配置

基于 MIT 许可发布 · 由 ❤️ 和 ☕ 驱动 · 支持作者