# 8.1 统一响应格式 :::tip 概述 所有接口返回统一的 JSON 结构,前端可据此判断请求结果。响应由 `response\Result` 类构造,保证全系统格式一致。 ::: ## 响应结构 ```typescript interface ApiResponse { code: number; // 状态码:0=成功,其他=失败 ok: boolean; // 是否成功 msg: string; // 提示信息 data: any; // 响应数据 } ``` ## 状态码定义 | code | 含义 | ok | 说明 | |------|------|-----|------| | `0` | 成功 | `true` | 业务成功 | | `1` | 业务失败 | `false` | 通用业务错误(参数错误、业务校验失败等) | | `401` | 未授权 | `false` | Token 缺失/无效/过期 | | `403` | 禁止访问 | `false` | 权限不足 | | `404` | 数据不存在 | `false` | 查询的记录不存在 | | `422` | 参数验证失败 | `false` | Validate 校验不通过 | ## 成功响应 ### 普通成功 ```json { "code": 0, "ok": true, "msg": "操作成功", "data": { "id": 1, "username": "admin", "realname": "管理员", "createTime": "2026-01-01 00:00:00" } } ``` ### 带自定义消息 ```json { "code": 0, "ok": true, "msg": "添加成功", "data": { "id": 42 } } ``` ### data 为 null ```json { "code": 0, "ok": true, "msg": "修改成功", "data": null } ``` ### data 为数组 ```json { "code": 0, "ok": true, "msg": "操作成功", "data": [ { "id": 1, "name": "选项一" }, { "id": 2, "name": "选项二" } ] } ``` ## 分页响应 ```json { "code": 0, "ok": true, "msg": "操作成功", "data": { "records": [ { "id": 1, "username": "admin", "createTime": "2026-01-01 00:00:00" }, { "id": 2, "username": "user1", "createTime": "2026-01-02 00:00:00" } ], "total": 100, "size": 20, "current": 1, "pages": 5 } } ``` ### 分页参数 | 字段 | 类型 | 说明 | |------|------|------| | `records` | array | 当前页数据列表 | | `total` | number | 总记录数 | | `size` | number | 每页条数 | | `current` | number | 当前页码 | | `pages` | number | 总页数(`ceil(total / size)`) | ## 失败响应 ### 通用业务失败 ```json { "code": 1, "ok": false, "msg": "用户名已存在", "data": null } ``` ### 参数验证失败 ```json { "code": 1, "ok": false, "msg": "参数验证失败", "data": "用户名不能为空" } ``` ### 批量验证失败 ```json { "code": 1, "ok": false, "msg": "参数验证失败", "data": [ "用户名不能为空", "姓名不能为空" ] } ``` ### 数据不存在 ```json { "code": 1, "ok": false, "msg": "数据不存在", "data": null } ``` ### 未授权(Token 问题) ```json { "code": 401, "ok": false, "msg": "token已过期", "data": null } ``` ### 权限不足 ```json { "code": 403, "ok": false, "msg": "无访问权限", "data": null } ``` ## 字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `code` | number | 状态码,0=成功 | | `ok` | boolean | 是否成功 | | `msg` | string | 提示信息 | | `data` | any | 响应数据,失败时通常为 null 或错误详情 | ### 分页字段 | 字段 | 类型 | 说明 | |------|------|------| | `data.records` | array | 分页数据列表 | | `data.total` | number | 总记录数 | | `data.size` | number | 每页条数 | | `data.current` | number | 当前页码 | | `data.pages` | number | 总页数 | ## 后端 Result 类 **文件:** `extend/response/Result.php` ### 方法清单 | 方法 | 说明 | code | ok | |------|------|------|-----| | `success($data, $msg)` | 成功响应 | 0 | true | | `page($records, $total, $current, $size)` | 分页响应 | 0 | true | | `fail($msg, $code, $data)` | 失败响应 | 1 | false | | `unauthorized($msg)` | 未授权 | 401 | false | | `forbidden($msg)` | 禁止访问 | 403 | false | | `notFound($msg)` | 数据不存在 | 404 | false | | `validateError($errors)` | 验证失败 | 422 | false | ### 使用示例 ```php // Controller 中通过 BaseController 调用 $this->success($data, '操作成功'); $this->success(['id' => $id], '添加成功'); $this->fail('用户名已存在'); $this->fail('缺少id参数', 1); // 直接调用 Result 类 \response\Result::success($data); \response\Result::fail('错误信息'); \response\Result::unauthorized('未授权'); \response\Result::forbidden('无权限'); \response\Result::notFound('数据不存在'); \response\Result::validateError('用户名不能为空'); ``` ## 驼峰自动转换 响应数据自动从 snake_case 转为 camelCase: | 数据库字段 | 响应字段 | |-----------|---------| | `create_time` | `createTime` | | `user_name` | `userName` | | `is_delete` | `isDelete` | | `dept_id` | `deptId` | 可通过 `config/api.php` 的 `camel_snake_convert` 配置关闭。 ## 前端响应处理 ### Axios 响应拦截器 ```typescript // src/utils/http/axios/index.ts import { useMessage } from '@/hooks/web/useMessage'; const $message = useMessage(); axios.interceptors.response.use( response => { const { code, data, msg } = response.data; // 成功 if (code === 0) { return data; } // Token 过期 if (code === 401) { localStorage.clear(); router.push('/login'); return Promise.reject(new Error(msg)); } // 权限不足 if (code === 403) { $message.error(msg || '无访问权限'); return Promise.reject(new Error(msg)); } // 其他业务错误 $message.error(msg || '操作失败'); return Promise.reject(new Error(msg)); }, error => { $message.error('网络错误'); return Promise.reject(error); } ); ``` ### 处理分页数据 ```typescript const fetchData = async () => { const res = await getUserPage({ pageNo: 1, pageSize: 20 }); // res 已经是 data 部分(拦截器已解包) tableData.value = res.records; total.value = res.total; }; ``` ### 处理导入结果 ```typescript import { useMessage } from 'naive-ui'; const message = useMessage(); const handleImport = async (file) => { const res = await importUsers(file); // res = { count: 8, errors: [{ row: 3, reason: "..." }] } if (res.errors.length === 0) { message.success(`成功导入 ${res.count} 条`); } else { message.warning(`成功 ${res.count} 条,失败 ${res.errors.length} 条`); } }; ``` ## 常见响应场景汇总 | 场景 | code | msg | data | |------|------|-----|------| | 查询成功 | 0 | 操作成功 | 数据对象/数组 | | 分页成功 | 0 | 操作成功 | {records, total, size, current, pages} | | 新增成功 | 0 | 添加成功 | {id: 42} | | 修改成功 | 0 | 修改成功 | null | | 删除成功 | 0 | 删除成功 | null | | 业务失败 | 1 | 具体错误信息 | null | | 参数验证失败 | 1 | 参数验证失败 | 错误字符串或数组 | | Token 过期 | 401 | token已过期 | null | | 无权限 | 403 | 无访问权限 | null | | 数据不存在 | 404 | 数据不存在 | null |