docs(standards): 增加 Result 返回规范章节

按用户反馈(i18n key 用 code 字符串,key 放 data 字段):
- 字段语义:error/Http、code/i18n key、message/默认中文、data/业务数据
- 调用规范表:成功/校验失败/未授权/无权限/数据不存在/降级/通用失败
- 重点:数据不存在用 failNotFound(ResultCode.DATA_NOT_FOUND, key)
- 禁止写法:Result.ok(null) 表示'未找到'(反直觉)
- i18n 配合示例:前端用 code 字段路由 i18n,data 字段做模板替换
This commit is contained in:
2026-06-07 17:49:19 +08:00
parent 22889afedc
commit 3395f69b42
+83 -1
View File
@@ -298,4 +298,86 @@ Closes #123
---
> **最后提醒**:编码规范是为了团队协作,请务必遵守!
> **最后提醒**:编码规范是为了团队协作,请务必遵守!
---
## 📦 Result 返回规范
`com.rui.common.core.result.Result` 是统一的 API 响应封装。所有 controller 必须遵守:
### 字段语义
| 字段 | 类型 | 用途 |
|---|---|---|
| `error` | int | HTTP 风格状态码(200/400/401/403/404/500/503 等) |
| `code` | String | **业务编码,前端 i18n key**(如 `DATA_NOT_FOUND` |
| `message` | String | 默认中文提示,可由前端 i18n 覆盖 |
| `data` | T | 业务数据 |
### 调用规范
| 场景 | 调用方式 | 备注 |
|---|---|---|
| 成功 | `Result.ok(data)` | data 可为 null,但**列表场景应返回 `emptyList`** |
| 业务校验失败 | `Result.fail(400, "msg")``Result.fail(ResultCode.X, "msg")` | 优先用枚举 |
| 未授权 | `Result.fail(401, "msg")` | 框架层 `GlobalExceptionHandler` 统一处理 |
| 无权限 | `Result.fail(403, "msg")` | 同上 |
| **数据不存在** | **`Result.failNotFound(ResultCode.DATA_NOT_FOUND, key)`** | **推荐写法**:key 放 data 字段便于前端模板替换 |
| 资源不存在(泛指) | `Result.fail(ResultCode.NOT_FOUND)` | 不带 key 的场景 |
| 服务降级 | `Result.fail(503, "服务降级: msg")` | Feign fallback 等场景 |
| 通用失败 | `Result.fail("msg")` | 兜底 |
### ❌ 禁止写法
- **`Result.ok(null)` 表示"未找到"** —— 反直觉(HTTP 200 + null),且与 `fail(404)` 语义冲突
- **message 中拼接 key** —— 如 `"字典不存在: " + dictCode`,应该用 `failNotFound(DATA_NOT_FOUND, dictCode)` 让前端用 i18n 模板 `"字典[${data}]不存在"`
- **数字 code 字符串比较** —— 应该用 `ResultCode` 枚举的 `getCode()` 字符串
### ✅ 正确示例
```java
// 查询接口 - 数据不存在
@GetMapping("/dict/getByCode/{dictCode}")
public Result<Map<String, Object>> getDictByCode(@PathVariable String dictCode) {
SysDictType dict = dictTypeService.findByCode(dictCode);
if (dict == null) {
return Result.failNotFound(ResultCode.DATA_NOT_FOUND, dictCode);
}
return Result.ok(buildDictResult(dict));
}
// 业务校验失败
@PostMapping("/save")
public Result<Void> save(@RequestBody @Valid SysDictDTO dto) {
if (dictService.isCodeExists(dto.getCode())) {
return Result.fail(400, "字典编码已存在: " + dto.getCode());
}
return Result.ok();
}
// 列表查询(即使是空也要返回空集合)
@GetMapping("/list")
public Result<List<SysDict>> list() {
return Result.ok(dictService.list()); // 不要 Result.ok(null)
}
```
### i18n 配合示例
前端拿到 `Result` 后:
```javascript
const i18nMap = {
'DATA_NOT_FOUND': '数据[{0}]不存在', // 占位符 {0} 用 data 字段填充
'AUTH_UNAUTHORIZED': '请先登录',
};
if (result.code === 'DATA_NOT_FOUND') {
showError(i18nMap['DATA_NOT_FOUND'].replace('{0}', result.data));
}
```
### 相关枚举
- `com.rui.common.core.result.ResultCode` —— 业务 code 枚举(404 用 `DATA_NOT_FOUND`401 用 `UNAUTHORIZED` 等)
- 新增业务 code 时在 `ResultCode` 加枚举值,**不要直接 `Result.fail(int, String, String)` 硬编码字符串**