174 lines
4.4 KiB
Markdown
174 lines
4.4 KiB
Markdown
# 接口响应规范
|
||
|
||
<!-- Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 -->
|
||
|
||
:::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": {
|
||
"records": [
|
||
{ "id": 1, "name": "案例1", "status": 1 },
|
||
{ "id": 2, "name": "案例2", "status": 2 }
|
||
],
|
||
"total": 100,
|
||
"size": 10,
|
||
"current": 1,
|
||
"pages": 10
|
||
},
|
||
"msg": "操作成功",
|
||
"ok": true
|
||
}
|
||
```
|
||
|
||
## 后端实现
|
||
|
||
位于 `utils/R.py`:
|
||
|
||
```python
|
||
from django.http import JsonResponse
|
||
|
||
def ok(data=None, msg="操作成功", code=0, **kwargs):
|
||
response_body = {"code": code, "data": data, "msg": msg, "ok": True}
|
||
if kwargs:
|
||
response_body.update(kwargs)
|
||
return JsonResponse(data=response_body)
|
||
|
||
def failed(msg="操作失败", code=1, data=None, **kwargs):
|
||
response_body = {"code": code, "data": data, "msg": msg, "ok": False}
|
||
if kwargs:
|
||
response_body.update(kwargs)
|
||
return JsonResponse(data=response_body)
|
||
```
|
||
|
||
### 使用示例
|
||
|
||
```python
|
||
# 成功
|
||
return R.ok(data=user_info)
|
||
return R.ok(data=page_data) # 分页
|
||
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 exampleAdd(formData);
|
||
// result = undefined (add 接口无返回数据)
|
||
|
||
// 分页接口:data 包含 records、total 等
|
||
const result = await getExamplePage(params);
|
||
// result = { records: [...], total: 100, size: 10, current: 1, pages: 10 }
|
||
```
|
||
|
||
## 字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `code` | number | 业务码:0=成功,1=失败,401=登录超时 |
|
||
| `data` | any | 业务数据 |
|
||
| `total` | 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` 拦截器统一解包,调用方直接使用业务数据。
|