import{_ as i,o as a,c as t,a2 as h}from"./chunks/framework.BbmSSMjL.js";const g=JSON.parse('{"title":"8.1 统一响应格式","description":"","frontmatter":{},"headers":[],"relativePath":"zh/api-spec/response.md","filePath":"zh/api-spec/response.md"}'),n={name:"zh/api-spec/response.md"};function k(l,s,p,e,d,r){return a(),t("div",null,[...s[0]||(s[0]=[h(`

8.1 统一响应格式 ​

概述

所有接口返回统一的 JSON 结构,前端可据此判断请求结果。响应由 App\\Support\\Result 类构造,保证全系统格式一致;HTTP 状态码默认 200(仅 403 例外),结果一律以 body 的 code 判断。

响应结构 ​

typescript
interface ApiResponse {
    code: number;   // 状态码:0=成功,其他=失败
    ok: boolean;    // 是否成功
    msg: string;    // 提示信息
    data: any;      // 响应数据
}

状态码定义 ​

code含义okHTTP 状态说明
0成功true200业务成功
1业务失败false200通用业务错误(业务校验失败、FormRequest 格式错误等)
401未授权false200Token 缺失/无效/过期(前端据 body code=401 跳转登录)
403禁止访问false403权限不足 / 租户异常 / 演示环境拦截
404数据不存在false200查询的记录不存在 / 路由不存在
422参数验证失败false200代码内手动 Validator::validate() 校验不通过

HTTP 状态码约定

仅 forbidden() 同时设置 HTTP 403(便于网关按 HTTP 状态处理),其余响应 HTTP 恒为 200——前端以 code 为准,code=401 时跳转登录。

成功响应 ​

普通成功 ​

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,
        "current": 1,
        "size": 20,
        "pages": 5
    }
}

分页字段 ​

字段类型说明
recordsarray当前页数据列表
totalnumber总记录数
currentnumber当前页码
sizenumber每页条数
pagesnumber总页数

失败响应 ​

通用业务失败(BizException) ​

json
{
    "code": 1,
    "ok": false,
    "msg": "名称已存在",
    "data": null
}

FormRequest 参数验证失败 ​

json
{
    "code": 1,
    "ok": false,
    "msg": "参数验证失败",
    "data": {
        "username": ["用户名不能为空"],
        "password": ["密码不能为空"]
    }
}

data 为字段明细对象

FormRequest 失败时 data 为 {字段名: [错误消息数组]}(BaseRequest::failedValidation()),前端可按字段回显。

代码内手动校验失败(ValidationException) ​

json
{
    "code": 422,
    "ok": false,
    "msg": "参数验证失败",
    "data": { "name": ["案例名称不能为空"] }
}

未授权(Token 问题,code 401) ​

json
{
    "code": 401,
    "ok": false,
    "msg": "未提供认证令牌",
    "data": null
}

权限不足(HTTP 403) ​

json
{
    "code": 403,
    "ok": false,
    "msg": "无访问权限:sys:user:add",
    "data": null
}

数据不存在(404) ​

json
{
    "code": 404,
    "ok": false,
    "msg": "数据不存在",
    "data": null
}

未知异常(500) ​

json
{
    "code": 500,
    "ok": false,
    "msg": "操作失败",
    "data": null
}

(调试模式 APP_DEBUG=true 时 msg 为异常原文,生产环境固定"操作失败"。)

字段说明 ​

字段类型说明
codenumber状态码,0=成功
okboolean是否成功
msgstring提示信息
dataany响应数据,失败时通常为 null 或错误详情

后端 Result 类 ​

文件: app/Support/Result.php

方法清单 ​

方法说明codeHTTP
success($data, $msg, $raw)成功响应($raw=true 跳过驼峰转换)0200
page($records, $total, $current, $size, $pages = null)分页响应0200
fail($msg, $code, $data)失败响应(code 默认 1)自定义200
unauthorized($msg)未授权401200
forbidden($msg)禁止访问403403
notFound($msg)数据不存在404200
validateError($errors)验证失败422200

使用示例 ​

php
// Controller 中通过基类调用(推荐)
$this->success($data, '操作成功');
$this->success(['id' => $id], '添加成功');
$this->paginate($pageListResult);      // 分页(records/total/current/size/pages)
$this->fail('名称已存在');
$this->fail('缺少id参数', 1);

// 直接调用 Result 类
Result::success($data);
Result::fail('错误信息');
Result::unauthorized('未授权');
Result::forbidden('无权限');
Result::notFound('数据不存在');
Result::validateError(['name' => ['名称不能为空']]);

响应构造与异常渲染的完整机制见 5.16 统一响应与异常抛出。

驼峰自动转换 ​

响应数据自动从 snake_case 转为 camelCase(API_CAMEL_SNAKE_CONVERT=true 时):

数据库字段响应字段
create_timecreateTime
user_nameuserName
is_deleteisDelete
dept_iddeptId

请求方向同时自动转换(排除 pageNo/pageSize/orderField/orderType),可通过 config/api.php 的 camel_snake_convert 关闭。需要原样返回时用 Result::success($data, $msg, true)。

前端响应处理 ​

Axios 响应拦截器 ​

typescript
// ui/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 过期(code 401)
        if (code === 401 || response.status === 401) {
            localStorage.clear();
            router.push('/login');
            return Promise.reject(new Error(msg));
        }

        // 权限不足
        if (code === 403 || response.status === 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;
    // 总页数:res.pages
};

处理导入结果 ​

typescript
import { useMessage } from '@/hooks/web/useMessage';

const $message = useMessage();

const handleImport = async (file) => {
    const res = await importUsers(file);
    // res = { count: 8, errors: [{ row: 3, name: "...", reason: "..." }] }
    if (res.errors.length === 0) {
        $message.success(\`成功导入 \${res.count} 条\`);
    } else {
        $message.warning(\`成功 \${res.count} 条,失败 \${res.errors.length} 条\`);
    }
};

常见响应场景汇总 ​

场景codemsgdata
查询成功0操作成功数据对象/数组
分页成功0操作成功{records, total, current, size, pages}
新增成功0添加成功{id: 42}
修改成功0修改成功null
删除成功0删除成功null
业务失败1具体错误信息null
FormRequest 验证失败1参数验证失败{字段: [错误数组]}
手动校验失败422参数验证失败{字段: [错误数组]}
Token 过期401未提供认证令牌 / Expired tokennull
无权限403(HTTP 403)无访问权限:xxxnull
数据不存在404数据不存在null
未知异常500操作失败(生产)null
`,59)])])}const y=i(n,[["render",k]]);export{g as __pageData,y as default};