From 1324a52049d1ea806ef42fcb6a8494d5effe7684 Mon Sep 17 00:00:00 2001 From: pigeon Date: Mon, 8 Jun 2026 16:01:03 +0800 Subject: [PATCH] =?UTF-8?q?docs(standards):=20=E8=A1=A5=E5=85=85=20Result=20=E7=BB=9F=E4=B8=80=E5=93=8D=E5=BA=94=E7=B1=BB=E6=96=87?= =?UTF-8?q?=E6=A1=A3=EF=BC=8C=E4=BF=AE=E6=AD=A3=20API=20=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E8=A7=84=E8=8C=83=E5=93=8D=E5=BA=94=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 Result统一响应类.md 完整文档 - 修正 API设计规范.md 中响应字段与实际代码不一致的问题 - 错误码规范表按实际 ResultCode 枚举对齐 --- standards/API设计规范.md | 34 ++-- standards/Result统一响应类.md | 332 ++++++++++++++++++++++++++++++++++ 2 files changed, 352 insertions(+), 14 deletions(-) create mode 100644 standards/Result统一响应类.md diff --git a/standards/API设计规范.md b/standards/API设计规范.md index 2838bb7..fd536e7 100644 --- a/standards/API设计规范.md +++ b/standards/API设计规范.md @@ -116,10 +116,12 @@ GET /v1/user/users?username=admin&status=1&createdAt_start=2024-01-01&createdAt_ ### 4.1 统一响应格式 +> 详细规范见 [Result 统一响应类](Result统一响应类.md) + ```json { - "code": 200, - "msg": "操作成功", + "error": 0, + "message": "操作成功", "data": {} } ``` @@ -128,8 +130,8 @@ GET /v1/user/users?username=admin&status=1&createdAt_start=2024-01-01&createdAt_ ```json { - "code": 200, - "msg": "操作成功", + "error": 0, + "message": "操作成功", "data": { "records": [], "total": 100, @@ -159,19 +161,23 @@ GET /v1/user/users?username=admin&status=1&createdAt_start=2024-01-01&createdAt_ | 区间 | 模块 | 示例 | |------|------|------| -| 1000-1999 | 通用 | 1001: 参数校验失败, 1002: 资源不存在 | -| 2000-2999 | 用户模块 | 2001: 用户名已存在, 2002: 密码错误 | -| 3000-3999 | 系统模块 | 3001: 字典不存在, 3002: 配置错误 | -| 4000-4999 | 认证模块 | 4001: Token 过期, 4002: 无权访问 | -| 5000-5999 | 文件模块 | 5001: 上传失败, 5002: 文件过大 | -| 6000-6999 | 消息模块 | 6001: 发送失败, 6002: 模板不存在 | +| 0 | 通用成功 | `0: 操作成功` | +| 1 | 通用失败 | `1: 操作失败` | +| 400-499 | HTTP 标准 | `401: 未授权, 404: 资源不存在` | +| 4000-4099 | 认证模块 | `4001: Token 已过期, 4002: Token 无效` | +| 4100-4199 | 用户信息 | `4101: 用户不存在, 4102: 用户名已存在` | +| 4200-4299 | 用户等级 | `4201: 等级编码已存在` | +| 5000-5999 | 文件模块(预留) | `5001: 上传失败, 5002: 文件大小超限` | +| 6000-6999 | 消息模块(预留) | `6001: 发送失败, 6002: 模板不存在` | + +> 完整枚举值见 [Result 统一响应类 → ResultCode 枚举](Result统一响应类.md#四resultcode-枚举) **错误响应示例**: ```json { - "code": 2001, - "msg": "用户名已存在", - "data": null + "error": 4102, + "message": "用户名已存在", + "code": "USER_INFO_USERNAME_EXISTS" } ``` @@ -321,7 +327,7 @@ public class UserRemoteService { if (result.isSuccess()) { return result.getData(); } - throw new BizException(result.getCode(), result.getMsg()); + throw new BizException(result.getError(), result.getMessage()); } } ``` diff --git a/standards/Result统一响应类.md b/standards/Result统一响应类.md new file mode 100644 index 0000000..9802dc6 --- /dev/null +++ b/standards/Result统一响应类.md @@ -0,0 +1,332 @@ +# Result 统一响应类规范 + +> RUI 框架所有 API 接口的统一返回包装 + +--- + +## 一、类信息 + +| 属性 | 值 | +|------|-----| +| 包路径 | `com.rui.common.core.result.Result` | +| 所在模块 | `rui-common-core` | +| 泛型参数 | `` — data 字段的具体类型 | +| 序列化 | 实现 `Serializable` | +| JSON 策略 | `@JsonInclude(NON_NULL)` — 值为 `null` 的字段自动忽略 | + +--- + +## 二、响应结构 + +### 2.1 字段定义 + +| 字段 | 类型 | 说明 | 成功时 | 失败时 | +|------|------|------|--------|--------| +| `error` | `int` | 状态码(0 = 成功) | `0` | 非 0 | +| `message` | `String` | 提示信息 | `"操作成功"` | 具体错误描述 | +| `code` | `String` | 业务错误码 | `null`(不输出) | 如 `"AUTH_UNAUTHORIZED"` | +| `data` | `T` | 业务数据 | 实际数据 | 通常为 `null`(不输出) | + +### 2.2 成功响应示例 + +**无数据返回:** + +```json +{ + "error": 0, + "message": "操作成功" +} +``` + +**带数据返回:** + +```json +{ + "error": 0, + "message": "操作成功", + "data": { + "id": 1001, + "username": "admin" + } +} +``` + +**分页数据:** + +```json +{ + "error": 0, + "message": "操作成功", + "data": { + "records": [], + "total": 100, + "size": 10, + "current": 1, + "pages": 10 + } +} +``` + +### 2.3 失败响应示例 + +**通用失败:** + +```json +{ + "error": 1, + "message": "操作失败" +} +``` + +**带业务错误码:** + +```json +{ + "error": 401, + "message": "未授权", + "code": "AUTH_UNAUTHORIZED" +} +``` + +**带数据(未找到场景):** + +```json +{ + "error": 404, + "message": "数据不存在", + "code": "DATA_NOT_FOUND", + "data": "dictCode_001" +} +``` + +> `data` 字段在 `failNotFound` 场景下用于传递资源 key,便于前端做国际化模板替换。 + +--- + +## 三、静态工厂方法 + +### 3.1 成功系列 + +| 方法签名 | 说明 | 使用场景 | +|----------|------|----------| +| `Result.ok()` | 无数据成功 | 删除、更新等不需要返回数据的操作 | +| `Result.ok(T data)` | 带数据成功 | 查询详情、列表、新增返回实体 | + +### 3.2 失败系列 + +| 方法签名 | 说明 | 使用场景 | +|----------|------|----------| +| `Result.fail()` | 通用失败 | 兜底异常、未知错误 | +| `Result.fail(String message)` | 自定义提示 | 需要特定提示信息的业务异常 | +| `Result.fail(ResultCode resultCode)` | 枚举驱动 | 标准业务错误,推荐使用 | +| `Result.fail(int error, String message)` | 自定义错误码+提示 | 非标准错误场景 | +| `Result.fail(int error, String message, String code)` | 完全自定义 | 需要同时指定三个字段 | +| `Result.fail(ResultCode resultCode, T data)` | 枚举+数据 | 失败时需携带部分数据 | +| `Result.failNotFound(ResultCode resultCode, String key)` | 404 未找到 | 数据不存在,key 放入 data | + +### 3.3 判断方法 + +| 方法 | 说明 | +|------|------| +| `result.isSuccess()` | 判断是否成功(`error == 0`) | +| `result.toJsonString()` | 序列化为 JSON 字符串 | + +--- + +## 四、ResultCode 枚举 + +### 4.1 枚举结构 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `error` | `int` | HTTP 风格状态码,0 为成功 | +| `message` | `String` | 默认提示文本 | +| `code` | `String` | 业务错误码(大写蛇形,模块前缀) | + +### 4.2 枚举值一览 + +| 枚举值 | error | message | code | 说明 | +|--------|-------|---------|------|------| +| `SUCCESS` | 0 | 操作成功 | `null` | 成功 | +| `FAILURE` | 1 | 操作失败 | `null` | 通用失败 | +| `UNAUTHORIZED` | 401 | 未授权 | `AUTH_UNAUTHORIZED` | 未登录 | +| `FORBIDDEN` | 403 | 无权限 | `AUTH_FORBIDDEN` | 无权限 | +| `NOT_FOUND` | 404 | 资源不存在 | `COMMON_NOT_FOUND` | 资源未找到 | +| `DATA_NOT_FOUND` | 404 | 数据不存在 | `DATA_NOT_FOUND` | 数据未找到 | +| `VALIDATE_FAILED` | 400 | 参数校验失败 | `COMMON_VALIDATE_FAILED` | 参数错误 | +| `TOKEN_EXPIRED` | 4001 | Token 已过期 | `AUTH_TOKEN_EXPIRED` | Token 过期 | +| `TOKEN_INVALID` | 4002 | Token 无效 | `AUTH_TOKEN_INVALID` | Token 无效 | +| `TENANT_NOT_FOUND` | 4003 | 租户不存在 | `AUTH_TENANT_NOT_FOUND` | 租户不存在 | +| `TENANT_DISABLED` | 4004 | 租户已禁用 | `AUTH_TENANT_DISABLED` | 租户禁用 | +| `USER_NOT_FOUND` | 4101 | 用户不存在 | `USER_INFO_NOT_FOUND` | 用户不存在 | +| `USERNAME_EXISTS` | 4102 | 用户名已存在 | `USER_INFO_USERNAME_EXISTS` | 用户名重复 | +| `LEVEL_CODE_EXISTS` | 4201 | 等级编码已存在 | `USER_LEVEL_CODE_EXISTS` | 等级编码重复 | + +### 4.3 错误码规划规则 + +| 区间 | 模块 | code 前缀 | +|------|------|-----------| +| 0 | 通用成功 | — | +| 1 | 通用失败 | — | +| 400-499 | HTTP 标准错误 | `COMMON_*` / `AUTH_*` | +| 4000-4099 | 认证错误 | `AUTH_*` | +| 4100-4199 | 用户信息错误 | `USER_INFO_*` | +| 4200-4299 | 用户等级错误 | `USER_LEVEL_*` | +| 5000-5999 | 文件模块(预留) | `FILE_*` | +| 6000-6999 | 消息模块(预留) | `MSG_*` | + +**新增规则**:新模块取 100 的整数倍区间,code 格式为 `{模块}_{业务}_{具体}`。 + +--- + +## 五、Controller 使用示例 + +### 5.1 标准 CRUD + +```java +@RestController +@RequestMapping("/v1/system/roles") +@RequiredArgsConstructor +public class SysRoleController { + + private final SysRoleService roleService; + + @GetMapping + public Result> list(SysRoleQuery query) { + return Result.ok(roleService.page(query)); + } + + @GetMapping("/{id}") + public Result getById(@PathVariable Long id) { + SysRoleVO role = roleService.getById(id); + if (role == null) { + return Result.failNotFound(ResultCode.DATA_NOT_FOUND, String.valueOf(id)); + } + return Result.ok(role); + } + + @PostMapping + public Result create(@RequestBody @Valid SysRoleDTO dto) { + return Result.ok(roleService.create(dto)); + } + + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @RequestBody @Valid SysRoleDTO dto) { + return Result.ok(roleService.update(id, dto)); + } + + @DeleteMapping("/{id}") + public Result delete(@PathVariable Long id) { + roleService.delete(id); + return Result.ok(); + } +} +``` + +### 5.2 异常处理中返回 + +```java +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(BizException.class) + public Result handleBizException(BizException e) { + return Result.fail(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public Result handleValidation(MethodArgumentNotValidException e) { + String message = e.getBindingResult().getFieldErrors().stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining(", ")); + return Result.fail(ResultCode.VALIDATE_FAILED.getError(), message, ResultCode.VALIDATE_FAILED.getCode()); + } +} +``` + +### 5.3 Feign 远程调用中处理 Result + +```java +@Service +@RequiredArgsConstructor +public class UserRemoteService { + + private final RemoteUserService remoteUserService; + + public UserVO getUserById(Long userId) { + Result result = remoteUserService.getById(userId); + if (result.isSuccess()) { + return result.getData(); + } + throw new BizException(result.getError(), result.getMessage()); + } +} +``` + +--- + +## 六、前端对接指南 + +### 6.1 判断成功 + +```typescript +// response.data 为 Result 结构 +const isSuccess = response.data.error === 0; +``` + +### 6.2 错误处理 + +```typescript +if (result.error !== 0) { + // 优先用 code 做国际化 + if (result.code) { + showI18nMessage(result.code, { key: result.data }); + } else { + // 降级显示 message + showMessage(result.message); + } +} +``` + +### 6.3 TypeScript 类型定义 + +```typescript +interface Result { + error: number; + message: string; + code?: string; // 失败时存在 + data?: T; // 成功时或 failNotFound 时存在 +} +``` + +--- + +## 七、扩展指南 + +### 7.1 新增 ResultCode + +在 `ResultCode` 枚举中添加新值,遵循以下规则: + +1. **error 取值**:按模块区间分配(见 4.3 节) +2. **code 命名**:`{模块}_{业务}_{具体}`,全大写蛇形 +3. **向后兼容**:禁止修改已有枚举值的 error 或 code + +```java +// 示例:文件模块新增 +FILE_UPLOAD_FAILED(5001, "文件上传失败", "FILE_UPLOAD_FAILED"), +FILE_SIZE_EXCEEDED(5002, "文件大小超限", "FILE_SIZE_EXCEEDED"), +``` + +### 7.2 禁止事项 + +- ❌ 不要在 Controller 中直接 `new Result<>()`,必须使用静态工厂方法 +- ❌ 不要修改 `Result` 类的字段名(`error`/`message`/`code`/`data`),影响序列化兼容 +- ❌ 不要用 `error` 字段传递 HTTP 状态码(它只是业务状态码,HTTP 状态码由框架控制) +- ❌ 不要在 `code` 字段中使用小写或特殊字符 + +--- + +> **文档版本**: v1.0 +> **创建日期**: 2026-06-08 +> **源码位置**: `rui-common/rui-common-core/src/main/java/com/rui/common/core/result/` +> **适用范围**: RUI 框架所有模块的 API 响应