4、请求与响应格式:JSON格式规范、请求头设计、响应结构设计

好,咱们今天聊点实在的。请求和响应格式,说白了就是你的API跟客户端之间怎么「说话」。我见过太多项目,接口写得乱七八糟,前端同事天天跑来问「这个字段是啥意思」「为什么返回格式不一样」。嗯,今天咱们就把这事彻底理清楚。

4.1 为什么非要用JSON?

你可能要问:XML不行吗?Form-Data不行吗?当然行。但说实话,在RESTful API的世界里,JSON已经是事实标准了。我个人习惯,只要不是历史遗留项目,一律用JSON。

为什么?三个字:轻、快、爽。

  • :JSON的语法比XML简洁太多,同样的数据,JSON体积能小30%-50%。
  • :PHP的 json_encode()json_decode() 是C扩展实现的,性能极好。
  • :几乎所有语言都有原生支持,前端JavaScript直接 JSON.parse() 就能用。

核心原则:请求体用JSON,响应体也用JSON。保持一致性,别搞混搭。

4.2 请求头设计:别小看这些「元数据」

请求头,很多人觉得不就是传个Token吗?其实远不止这些。我在项目中踩过一个坑:客户端传了Content-Type为 application/x-www-form-urlencoded,但服务端死活解析不到JSON数据。后来发现,是前端忘了设请求头。

下面是我常用的请求头规范:

请求头 必填 说明 示例值
Content-Type 告诉服务端请求体的格式 application/json
Accept 告诉服务端你期望的响应格式 application/json
Authorization 是(需认证时) Bearer Token 或 JWT Bearer eyJhbGciOiJIUzI1NiIs...
X-Request-Id 推荐 用于链路追踪,方便排查问题 550e8400-e29b-41d4-a716-446655440000
X-Client-Version 推荐 客户端版本号,用于兼容性判断 2.1.0

小技巧:我习惯在中间件里统一校验 Content-Type。如果客户端传的不是 application/json,直接返回 415 Unsupported Media Type。这样能避免很多奇奇怪怪的解析错误。

4.3 响应结构设计:让前端不再骂人

响应结构,这是整个API设计里最容易被忽视的地方。我曾经接手过一个项目,同一个接口,成功时返回 { "data": {...} },失败时返回 { "error": "xxx" }。前端每次都要写两套解析逻辑,你说累不累?

所以,我强烈建议:统一响应结构。不管成功还是失败,格式必须一致。

4.3.1 标准响应结构

{
    "code": 0,          // 业务状态码,0表示成功
    "message": "success", // 提示信息
    "data": {           // 实际数据,失败时为null
        "id": 123,
        "name": "张三",
        "email": "zhangsan@example.com"
    },
    "meta": {           // 元数据,分页等信息放这里
        "current_page": 1,
        "per_page": 20,
        "total": 100
    },
    "request_id": "550e8400-e29b-41d4-a716-446655440000" // 方便排查
}

你可能会问:为什么不用HTTP状态码直接表示业务状态?嗯,这个问题我当年也纠结过。后来发现,HTTP状态码只有几十个,但业务错误可能有几百种。比如「用户余额不足」和「用户被冻结」,都是400,但业务含义完全不同。所以,HTTP状态码管传输,业务状态码管逻辑,各司其职。

4.3.2 业务状态码设计

范围 含义 示例
0 成功 一切正常
1000-1999 通用错误 1001: 参数错误, 1002: 未授权
2000-2999 用户相关 2001: 用户不存在, 2002: 密码错误
3000-3999 业务相关 3001: 余额不足, 3002: 订单已取消
4000-4999 系统错误 4001: 数据库异常, 4002: 第三方服务超时

避坑指南:我曾经犯过一个错——把业务状态码和HTTP状态码混着用。比如用户未登录,我返回了401,但业务code却是0。前端一看code=0,以为成功了,直接渲染页面,结果一片空白。后来我定了个死规矩:HTTP状态码只反映传输层状态,业务状态码只反映业务层状态。两者互不干扰。

4.3.3 错误响应示例

// 参数校验失败
{
    "code": 1001,
    "message": "参数校验失败",
    "data": {
        "errors": {
            "email": "邮箱格式不正确",
            "age": "年龄必须在1-150之间"
        }
    },
    "meta": null,
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
}

// 未授权
{
    "code": 1002,
    "message": "Token已过期,请重新登录",
    "data": null,
    "meta": null,
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
}

4.4 实战:PHP中如何统一处理响应

说了这么多理论,咱们来点实际的。我在项目中一般会封装一个 ApiResponse 类,所有控制器都通过它来返回数据。

<?php

namespace App\Http;

class ApiResponse
{
    // 成功响应
    public static function success($data = null, string $message = 'success', array $meta = [])
    {
        return response()->json([
            'code'       => 0,
            'message'    => $message,
            'data'       => $data,
            'meta'       => $meta,
            'request_id' => request()->header('X-Request-Id', uniqid()),
        ]);
    }

    // 错误响应
    public static function error(int $code, string $message, $data = null)
    {
        return response()->json([
            'code'       => $code,
            'message'    => $message,
            'data'       => $data,
            'meta'       => null,
            'request_id' => request()->header('X-Request-Id', uniqid()),
        ]);
    }

    // 分页响应
    public static function paginated($paginator, string $message = 'success')
    {
        return self::success(
            data: $paginator->items(),
            message: $message,
            meta: [
                'current_page' => $paginator->currentPage(),
                'per_page'     => $paginator->perPage(),
                'total'        => $paginator->total(),
                'last_page'    => $paginator->lastPage(),
            ]
        );
    }
}

使用起来也很简单:

// 在控制器里
public function show($id)
{
    $user = User::find($id);
    if (!$user) {
        return ApiResponse::error(2001, '用户不存在');
    }
    return ApiResponse::success($user);
}

个人经验:我建议在全局异常处理器里也统一捕获异常,自动转换成标准响应格式。这样即使代码里忘了处理异常,也不会返回乱七八糟的HTML或者堆栈信息给客户端。说白了,就是给API加一层「兜底」。

4.5 总结一下

请求和响应格式,看似简单,但决定了你的API好不好用。记住三点:

  • 请求头要规范:Content-Type、Accept、Authorization 一个都不能少。
  • 响应结构要统一:code、message、data、meta、request_id,固定这五个字段。
  • 状态码要分离:HTTP状态码管传输,业务状态码管逻辑,别混在一起。

嗯,今天就聊到这儿。下一章咱们讲讲认证与授权,那可是个重头戏。