PHP API 设计实战:RESTful、认证、版本控制与 OpenAPI

系统覆盖 PHP API 开发:RESTful 设计原则与资源建模、状态码语义、JWT/OAuth2 认证、速率限制、版本控制策略、OpenAPI 契约驱动、Laravel API 资源与测试。

引言

API 是前后端与第三方的「公共契约」——设计得好,改起来毫不费劲;设计得差,每次迭代都在破坏别人。RESTful 的价值不是「标准答案」,而是用 HTTP 语义自描述资源操作。本文覆盖资源建模、状态码、认证、版本控制、契约驱动测试,给出 Laravel 落地写法与一套「先契约后实现」的工作流。

前置:/php-laravel-internals/(路由/中间件)、/php-security-hardening/(认证安全)。数据库见 /php-mysql-database/。


目录


1. REST 资源建模:名词而非动词

REST 核心:把业务抽象成资源,用 HTTP 动词表达操作——URL 里是名词,不是动词:

❌ 动词式(RPC 味)          ✅ 资源式(REST 味)
GET  /getUsers              GET   /users
POST /createUser            POST  /users
POST /deleteUser?id=1       DELETE /users/1
POST /updateUser            PATCH  /users/1
POST /login                 POST   /auth/token   ← 认证是「换取凭证」,算资源操作

资源层级:

/users                     用户集合
/users/1                   单个用户
/users/1/orders            用户 1 的订单(子资源)
/orders/5/items            订单 5 的明细

子资源 vs 扁平:深嵌套过多(>2 层)就扁平化,用查询参数表达关联。

记忆:URL 描述「是什么资源」,动词描述「做什么」,查询参数描述「怎么筛选」。


2. HTTP 动词与状态码语义

动词语义幂等返回
GET读✅200
POST创建(不确定 URI)❌201 + Location
PUT整体替换✅200
PATCH部分更新可200
DELETE删除✅204
HEAD读头✅200(无体)

状态码语义(别乱用 200):

200 OK / 201 Created / 202 Accepted(异步)
204 No Content
400 参数错 / 401 未认证 / 403 无权限 / 404 不存在
409 冲突(如已存在)/ 422 校验失败(表单)
429 限流 / 5xx 服务端

反模式:全部返回 200 + {"code":0}——丢掉了 HTTP 的语义,客户端判断更累。

记忆:2xx 成功、4xx 客户端错、5xx 服务端错;422 专门给字段校验失败。


3. 响应结构约定:统一封装与错误

统一成功结构(分页必带 meta):

{
  "data": [
    {"id": 1, "name": "Alice"}
  ],
  "meta": {
    "page": 1, "per_page": 20, "total": 157, "last_page": 8
  }
}

统一错误结构:

{
  "error": {
    "code": "validation_failed",
    "message": "请求参数校验失败",
    "details": {"email": ["邮箱格式不正确"]},
    "traceId": "abc-123"
  }
}

错误码约定:机器可判(code)+ 人可读(message)+ 细节(details)+ 排查(traceId)。

traceId 贯穿日志是排查命根子,见 [[observability]]。


4. 认证方案:JWT、OAuth2 与 API Key

JWT(无状态 Bearer Token):

// 生成(示例,生产用成熟库如 tymon/jwt-auth 或 Sanctum)
$payload = [
    'sub'  => $user->id,
    'exp'  => time() + 3600,       // 1 小时
    'iat'  => time(),
];
$token = jwt_encode($payload, $secret, 'HS256');

// 校验中间件
// 解析 → 验签 → 查 sub → 注入当前用户

OAuth2(授权码流程,第三方登录):用户授权 → 换 token → 访问资源。适合「授权他人访问你的资源」。

API Key(服务间/简单):

// 头或查询参数传 key,查表校验
$key = $request->header('X-API-Key');
$client = ApiClient::where('key_hash', hash('sha256', $key))->first();
方案适用特点
JWT自家 App/SPA无状态、可跨服务
Sanctum(Laravel)自家 API内置、简单
OAuth2第三方授权授权码 + 令牌交换
API Key服务间简单、需限流

Laravel 推荐:内置 Sanctum(token 认证)起步,复杂授权再上 OAuth。


5. 速率限制与防滥用

Laravel RateLimiter:

// 按 IP 限流
RateLimiter::for('api', fn($job) =>
    Limit::perMinute(60)->by($job->user?->id ?: $job->ip()));

// 路由组应用
Route::middleware(['auth:sanctum', 'throttle:api'])->group(...);

自定义限制维度:

// 登录接口更严:按 IP + 邮箱
RateLimiter::for('login', fn($job) =>
    Limit::perMinute(5)->by($job->ip() . '|' . $job->input('email')));

429 响应:返回 Retry-After 头,客户端可退避重试。

防滥用清单:限流(频控)、验证码(注册/登录)、账号锁定(多次失败)、WAF 层防护。


6. 版本控制策略

为什么要版本:契约变化(加必填字段、改语义、删资源)会破坏既有客户端。

三种主流策略:

策略方式优缺点
URI 版本/api/v1/users显式、简单,膨胀 URL
查询参数/users?version=1不推荐(可缓存性问题)
Header 版本Accept: application/vnd.app.v1+json干净但隐藏

实践建议:

  • 默认 /api/v1/,破坏性变更升 v2(/api/v2/)
  • 向后兼容优先:加可选字段不升版本
  • 废弃周期:标记 deprecated 头 + 通知期
  • URL 语义不破坏(改字段值格式也应升版本)
Route::prefix('v1')->group(function () {
    Route::get('/users', ...);
});
Route::prefix('v2')->group(function () {
    Route::get('/users', ...);   // 新语义
});

记忆:加字段向后兼容,改语义就升版本;v1/v2 并行跑,给客户端留迁移时间。


7. OpenAPI:契约驱动开发

OpenAPI(Swagger):用一份 YAML/JSON 描述整个 API——文档、测试、客户端生成同源。

openapi: 3.0.0
info: { title: User API, version: "1.0.0" }
paths:
  /users:
    get:
      summary: 用户列表
      parameters:
        - name: page
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: 成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserList"
components:
  schemas:
    User:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }

契约驱动工作流:

写 OpenAPI 契约 → 生成 mock/文档 → 前后端并行开发
→ 契约校验(请求/响应符合 schema)→ 交付

Laravel 配合:scramble 或 l5-swagger 从代码生成/校验契约;测试断言响应符合 schema。

记忆:契约先行 = 接口文档不再是「事后补」,mock 让前后端解耦并行。


8. Laravel API 工程化

API 资源(API Resource)——统一输出层,数据库结构不与响应耦合:

// app/Http/Resources/UserResource.php
class UserResource extends JsonResource {
    public function toArray($request): array {
        return [
            'id'    => $this->id,
            'name'  => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at->toISOString(),
        ];
    }
}

// 控制器返回资源
Route::get('/users/{user}', fn(User $user) => new UserResource($user));

工程化清单:

项做法
校验FormRequest(类型化校验)
资源层API Resource 统一输出
错误ApiException 统一结构
认证Sanctum / JWT
限流RateLimiter
文档OpenAPI
日志traceId + 请求上下文
// FormRequest 示例:校验与授权
class StoreUserRequest extends FormRequest {
    public function authorize(): bool { return true; }
    public function rules(): array {
        return [
            'name'  => ['required', 'string', 'max:100'],
            'email' => ['required', 'email', 'unique:users'],
        ];
    }
}

9. API 测试与契约测试

集成测试(Laravel Pest/PHPUnit):

test('用户列表返回分页结构', function () {
    $response = $this->getJson('/api/v1/users?page=1');
    $response->assertOk()
        ->assertJsonStructure([
            'data'  => [['id' => 'integer', 'name' => 'string']],
            'meta'  => ['total' => 'integer'],
        ]);
});

test('未认证访问返回 401', function () {
    $this->getJson('/api/v1/users/me')->assertUnauthorized();
});

契约测试(防止前后端契约漂移):

// 用 OpenAPI schema 校验响应
$schema = loadSchema('components/schemas/User');
$response->assertValidAgainst($schema);

API 测试清单:正常路径、认证、校验失败、404/409、限流 429、分页边界、超时。


10. 速查表

需求做法
资源建模URL 名词 + 动词 + 查询参数
状态码语义化(201/204/400/401/403/404/409/422/429)
响应统一 data/meta + 错误结构
认证Sanctum / JWT / OAuth2 按需
限流RateLimiter + Retry-After
版本/api/v1/,破坏性变更升版
文档OpenAPI 契约
校验FormRequest
输出API Resource
测试集成 + 契约校验

一句话记忆:URL 名词、动词表操作、状态码说结果;认证 Sanctum 起步、限流必须有、破坏性改动升版本;契约先行,文档同源。


延伸阅读

  • /php-laravel-internals/ — 路由与中间件管道
  • /php-security-hardening/ — 认证与会话安全
  • /php-testing-practice/ — API 集成测试
  • /php-microservices-message-queue/ — API 背后的异步处理
  • [[nodejs]] — 服务端 API 的跨语言对照

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「php」更多文章

  1. PHP 面向对象与设计模式:SOLID、常用模式与 Laravel 实践
  2. PHP 静态分析与代码质量:PHPStan、Psalm、Rector 与 CI 门禁
  3. PHP 部署运维实战:Nginx、PHP-FPM、Docker 与 CI/CD