177 lines
4.4 KiB
Markdown
177 lines
4.4 KiB
Markdown
# 接口响应规范
|
||
|
||
:::tip 说明
|
||
后端所有接口统一返回 `R.ok()` / `R.failed()` 格式的 JSON 响应,前端通过 `VAxios` 的 `transformRequestData` 拦截器统一解包处理。
|
||
:::
|
||
|
||
## 响应格式
|
||
|
||
### 成功响应
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": { "id": 1, "name": "测试岗位", "status": 1 },
|
||
"msg": "操作成功",
|
||
"ok": true
|
||
}
|
||
```
|
||
|
||
### 失败响应
|
||
|
||
```json
|
||
{
|
||
"code": 1,
|
||
"data": null,
|
||
"msg": "岗位名称不能重复",
|
||
"ok": false
|
||
}
|
||
```
|
||
|
||
### 分页响应
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": [
|
||
{ "id": 1, "name": "岗位1", "status": 1, "status_name": "正常" },
|
||
{ "id": 2, "name": "岗位2", "status": 2, "status_name": "停用" }
|
||
],
|
||
"count": 100,
|
||
"msg": "操作成功",
|
||
"ok": true
|
||
}
|
||
```
|
||
|
||
## 后端实现
|
||
|
||
位于 `src/core/response.py`:
|
||
|
||
```python
|
||
from flask import jsonify
|
||
|
||
class R:
|
||
@staticmethod
|
||
def ok(data=None, msg="操作成功", **kwargs):
|
||
return jsonify({
|
||
"code": 0,
|
||
"data": data,
|
||
"msg": msg,
|
||
"ok": True,
|
||
**kwargs, # 支持额外字段,如 count
|
||
})
|
||
|
||
@staticmethod
|
||
def failed(msg="操作失败", data=None):
|
||
return jsonify({
|
||
"code": 1,
|
||
"data": data,
|
||
"msg": msg,
|
||
"ok": False,
|
||
})
|
||
```
|
||
|
||
### 使用示例
|
||
|
||
```python
|
||
from src.core import response as R
|
||
|
||
# 成功
|
||
return R.ok(data=user_info)
|
||
return R.ok(data=list, count=total) # 分页
|
||
return R.ok(msg="添加成功")
|
||
|
||
# 失败
|
||
return R.failed(msg="用户名已存在")
|
||
return R.failed("参数错误")
|
||
```
|
||
|
||
## 前端拦截器
|
||
|
||
`VAxios` 的 `transformRequestData` 统一解包响应:
|
||
|
||
```typescript
|
||
// src/utils/http/axios/index.ts
|
||
transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
|
||
const { code, data, msg } = res.data;
|
||
|
||
// 业务成功(code === 0):直接返回 data
|
||
if (code === ResultEnum.SUCCESS) {
|
||
return data;
|
||
}
|
||
|
||
// 业务失败(code === 1):弹出错误提示
|
||
if (code === ResultEnum.ERROR) {
|
||
ElMessage.error(msg);
|
||
throw new Error(msg);
|
||
}
|
||
|
||
// 登录超时(code === 401):弹出确认框,跳转登录页
|
||
if (code === ResultEnum.TIMEOUT) {
|
||
ElMessageBox.confirm('登录身份已失效,请重新登录!', '提示', {
|
||
confirmButtonText: '确定',
|
||
showCancelButton: false,
|
||
type: 'warning',
|
||
}).then(() => {
|
||
storage.clear();
|
||
window.location.href = '/login';
|
||
});
|
||
throw new Error(msg);
|
||
}
|
||
},
|
||
```
|
||
|
||
解包后调用方拿到的是 `data` 字段内容:
|
||
|
||
```typescript
|
||
// 普通接口:data 就是业务数据
|
||
const result = await positionAdd(formData);
|
||
// result = { id: 1, name: "测试岗位", ... }
|
||
|
||
// 分页接口:data 包含 list 和 count
|
||
const result = await getPositionList(params);
|
||
// result = { data: [...], count: 100, msg: "操作成功", ok: true, code: 0 }
|
||
```
|
||
|
||
## 字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `code` | number | 业务码:0=成功,1=失败,401=登录超时 |
|
||
| `data` | any | 业务数据 |
|
||
| `count` | number | 分页总数(仅分页接口) |
|
||
| `msg` | string | 提示消息 |
|
||
| `ok` | boolean | 业务是否成功 |
|
||
|
||
## 错误码规范
|
||
|
||
| code | 说明 | 前端行为 |
|
||
|------|------|----------|
|
||
| 0 | 成功 | 返回 `data` |
|
||
| 1 | 业务失败 | `ElMessage.error(msg)` + `throw` |
|
||
| 401 | 未登录或 Token 过期 | 弹出确认框 → 跳转登录页 |
|
||
|
||
HTTP 状态码始终为 200,业务成功/失败通过 `code` 字段区分。
|
||
|
||
## 前端错误展示
|
||
|
||
业务错误通过 `ElMessage.error` 弹出提示,无需组件手动处理:
|
||
|
||
```typescript
|
||
// 组件中直接调用,错误由拦截器处理
|
||
try {
|
||
await positionAdd(formData);
|
||
message('操作成功');
|
||
} catch (e) {
|
||
// 拦截器已弹出 ElMessage.error,此处可选处理
|
||
}
|
||
```
|
||
|
||
:::tip 温馨提示
|
||
HTTP 状态码始终为 200,业务成功/失败通过 `code` 字段区分。这避免了 HTTP 状态码与业务状态码的混淆。前端拦截器统一处理错误提示,业务代码只需处理成功逻辑。
|
||
:::
|
||
|
||
## 总结
|
||
|
||
接口响应规范统一使用 `R.ok()`/`R.failed()` 格式,`code=0` 表示成功返回 `data`,`code=1` 表示失败弹出 `msg`,`code=401` 表示登录超时跳转登录页。前端 `VAxios` 的 `transformRequestData` 拦截器统一解包,调用方直接使用业务数据。
|