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:
@@ -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)` 硬编码字符串**
|
||||
Reference in New Issue
Block a user