Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_NaiveVue/zh/api-spec/response.md
T
2026-10-08 17:50:29 +08:00

8.4 KiB
Raw Blame History

8.1 统一响应格式

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

响应结构

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

状态码定义

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

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

成功响应

普通成功

{
    "code": 0,
    "ok": true,
    "msg": "操作成功",
    "data": {
        "id": 1,
        "username": "admin",
        "realname": "管理员",
        "createTime": "2026-01-01 00:00:00"
    }
}

带自定义消息

{
    "code": 0,
    "ok": true,
    "msg": "添加成功",
    "data": { "id": 42 }
}

data 为 null

{
    "code": 0,
    "ok": true,
    "msg": "修改成功",
    "data": null
}

data 为数组

{
    "code": 0,
    "ok": true,
    "msg": "操作成功",
    "data": [
        { "id": 1, "name": "选项一" },
        { "id": 2, "name": "选项二" }
    ]
}

分页响应

{
    "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
    }
}

分页字段

字段 类型 说明
records array 当前页数据列表
total number 总记录数
current number 当前页码
size number 每页条数
pages number 总页数

失败响应

通用业务失败(BizException)

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

FormRequest 参数验证失败

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

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

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

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

未授权(Token 问题,code 401)

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

权限不足(HTTP 403)

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

数据不存在(404)

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

未知异常(500)

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

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

字段说明

字段 类型 说明
code number 状态码,0=成功
ok boolean 是否成功
msg string 提示信息
data any 响应数据,失败时通常为 null 或错误详情

后端 Result 类

文件: app/Support/Result.php

方法清单

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

使用示例

// 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_time createTime
user_name userName
is_delete isDelete
dept_id deptId

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

前端响应处理

Axios 响应拦截器

// ui/src/utils/http/axios/index.ts

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) {
            ElMessage.error(msg || '无访问权限');
            return Promise.reject(new Error(msg));
        }

        // 其他业务错误
        ElMessage.error(msg || '操作失败');
        return Promise.reject(new Error(msg));
    },
    error => {
        ElMessage.error('网络错误');
        return Promise.reject(error);
    }
);

处理分页数据

const fetchData = async () => {
    const res = await getUserPage({ pageNo: 1, pageSize: 20 });
    // res 已经是 data 部分(拦截器已解包)
    tableData.value = res.records;
    total.value = res.total;
    // 总页数:res.pages
};

处理导入结果

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

常见响应场景汇总

场景 code msg data
查询成功 0 操作成功 数据对象/数组
分页成功 0 操作成功 {records, total, current, size, pages}
新增成功 0 添加成功 {id: 42}
修改成功 0 修改成功 null
删除成功 0 删除成功 null
业务失败 1 具体错误信息 null
FormRequest 验证失败 1 参数验证失败 {字段: [错误数组]}
手动校验失败 422 参数验证失败 {字段: [错误数组]}
Token 过期 401 未提供认证令牌 / Expired token null
无权限 403(HTTP 403) 无访问权限:xxx null
数据不存在 404 数据不存在 null
未知异常 500 操作失败(生产) null