PHP项目RESTful API设计规范

wen PHP项目 3

PHP RESTful API 设计规范

基础规范

1 协议与域名

  • 使用 HTTPS 协议
  • API 使用专用域名:api.example.com
  • 或保留在主域名下:example.com/api

2 版本控制

// URL 路径版本控制(推荐)
/API/v1/users
/API/v2/users
// 请求头版本控制
Accept: application/vnd.example.v1+json

路由设计

1 URL 命名规范

  • 使用名词复数形式
  • 不使用动词
  • 使用小写字母
  • 多个单词使用短横线(-)分隔
// ✅ 正确的设计
GET    /api/v1/users          // 用户列表
GET    /api/v1/users/123      // 指定用户
POST   /api/v1/users          // 创建用户
PUT    /api/v1/users/123      // 更新用户
DELETE /api/v1/users/123      // 删除用户
// ❌ 避免的设计
GET    /api/v1/getUsers       // 使用动词
GET    /api/v1/UserList       // 混合大小写
POST   /api/v1/getUserData    // 动词+连接词

2 资源嵌套

// 多级资源
GET    /api/v1/users/123/orders/456    // 用户123的订单456
GET    /api/v1/users/123/orders        // 用户123的所有订单
// 子资源关联
GET    /api/v1/users/123/profile       // 用户资料
GET    /api/v1/users/123/posts         // 用户的文章

HTTP 方法

1 方法使用规范

// 标准操作
GET      - 获取资源(幂等)
POST     - 创建资源(非幂等)
PUT      - 完整更新(幂等)
PATCH    - 部分更新(幂等)
DELETE   - 删除资源(幂等)
// 实际操作示例
GET    /api/v1/users         // 获取用户列表
POST   /api/v1/users         // 创建用户
GET    /api/v1/users/123    // 获取指定用户
PUT    /api/v1/users/123    // 完整更新用户
PATCH  /api/v1/users/123    // 部分更新用户
DELETE /api/v1/users/123    // 删除用户

请求设计

1 请求头规范

 header('Content-Type: application/json; charset=utf-8');
 header('Accept: application/json');
 header('Authorization: Bearer {token}');
 header('X-App-Version: 1.0.0');        // 客户端版本
 header('X-Device-Id: unique-device-id'); // 设备标识

2 查询参数

// 筛选
GET /api/v1/users?status=active&age_group=20-30
// 排序
GET /api/v1/users?sort=-created_at      // 降序
GET /api/v1/users?sort=created_at       // 升序
// 分页
GET /api/v1/users?page=1&limit=20
// 字段选择
GET /api/v1/users?fields=id,name,email

响应设计

1 统一响应格式

// 成功响应
{
    "code": 200,
    "message": "success",
    "data": {
        "id": 1,
        "name": "张三",
        "email": "zhangsan@example.com"
    },
    "meta": {
        "timestamp": 1634567890,
        "copyright": "2024 Company"
    }
}
// 列表响应
{
    "code": 200,
    "message": "success",
    "data": [
        {
            "id": 1,
            "name": "张三"
        },
        {
            "id": 2,
            "name": "李四"
        }
    ],
    "pagination": {
        "total": 100,
        "page": 1,
        "limit": 20,
        "pages": 5
    }
}

2 错误响应

// 4xx 客户端错误
{
    "code": 400,
    "message": "请求参数错误",
    "errors": {
        "email": ["邮箱格式不正确"],
        "password": ["密码长度不少于6位"]
    }
}
// 5xx 服务器错误
{
    "code": 500,
    "message": "服务器内部错误",
    "request_id": "a1b2c3d4-e5f6-g7h8"
}

PHP 实现示例

1 路由配置

// config/routes.php
<?php
use App\Controllers\UserController;
$routes = [
    // 用户路由
    'GET' => [
        '/api/v1/users' => [UserController::class, 'index'],
        '/api/v1/users/{id}' => [UserController::class, 'show'],
        '/api/v1/users/{id}/orders' => [OrderController::class, 'getUserOrders'],
    ],
    'POST' => [
        '/api/v1/users' => [UserController::class, 'store'],
        '/api/v1/users/login' => [AuthController::class, 'login'],
    ],
    'PUT' => [
        '/api/v1/users/{id}' => [UserController::class, 'update'],
    ],
    'PATCH' => [
        '/api/v1/users/{id}' => [UserController::class, 'partialUpdate'],
    ],
    'DELETE' => [
        '/api/v1/users/{id}' => [UserController::class, 'destroy'],
    ],
];

2 控制器基类

// app/Controllers/BaseController.php
<?php
class BaseController
{
    protected function success($data = null, $code = 200, $message = 'success')
    {
        http_response_code($code);
        header('Content-Type: application/json; charset=utf-8');
        $response = [
            'code' => $code,
            'message' => $message,
            'data' => $data,
            'meta' => [
                'timestamp' => time()
            ]
        ];
        echo json_encode($response);
        exit;
    }
    protected function error($message, $code = 400, $errors = null)
    {
        http_response_code($code);
        header('Content-Type: application/json; charset=utf-8');
        $response = [
            'code' => $code,
            'message' => $message,
            'errors' => $errors
        ];
        echo json_encode($response);
        exit;
    }
    protected function paginate($data, $page, $limit, $total)
    {
        http_response_code(200);
        header('Content-Type: application/json; charset=utf-8');
        $response = [
            'code' => 200,
            'message' => 'success',
            'data' => $data,
            'pagination' => [
                'total' => $total,
                'page' => $page,
                'limit' => $limit,
                'pages' => ceil($total / $limit)
            ]
        ];
        echo json_encode($response);
        exit;
    }
    protected function validate($data, $rules)
    {
        // 实现验证逻辑
        $errors = [];
        foreach ($rules as $field => $rule) {
            if (!isset($data[$field]) || !preg_match($rule, $data[$field])) {
                $errors[$field] = "字段:{$field} 验证失败";
            }
        }
        if (!empty($errors)) {
            $this->error('验证失败', 422, $errors);
        }
        return true;
    }
    protected function getRequestData()
    {
        // 获取JSON请求体
        $json = file_get_contents('php://input');
        $data = json_decode($json, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            $this->error('无效的JSON格式', 400);
        }
        return $data ?: [];
    }
    protected function getAuthUser()
    {
        // 从请求头获取Authorization
        $headers = getallheaders();
        $auth = $headers['Authorization'] ?? '';
        if (preg_match('/Bearer\s+(.*)/', $auth, $matches)) {
            // 解析token并返回用户
            return JWT::decode($matches[1]);
        }
        return null;
    }
}

3 用户控制器

// app/Controllers/UserController.php
<?php
class UserController extends BaseController
{
    private $userModel;
    public function __construct()
    {
        $this->userModel = new UserModel();
    }
    // 获取用户列表
    public function index(Request $request)
    {
        // 参数校验
        $page = $request->query('page', 1);
        $limit = $request->query('limit', 20);
        $limit = min($limit, 100); // 限制最大条数
        // 构建查询条件
        $conditions = [];
        if ($request->query('status')) {
            $conditions[] = "status = '{$request->query('status')}'";
        }
        if ($request->query('fields')) {
            $fields = explode(',', $request->query('fields'));
            $select = implode(',', $fields);
        } else {
            $select = '*';
        }
        // 排序
        $sort = $request->query('sort', '-created_at');
        $sortDirection = str_starts_with($sort, '-') ? 'DESC' : 'ASC';
        $sortField = ltrim($sort, '-');
        // 查询数据
        $users = $this->userModel->getUsers(
            $conditions, 
            $sortField, 
            $sortDirection, 
            $page, 
            $limit,
            $select
        );
        $total = $this->userModel->countUsers($conditions);
        return $this->paginate($users, $page, $limit, $total);
    }
    // 获取指定用户
    public function show($id)
    {
        // 参数验证
        if (!is_numeric($id)) {
            return $this->error('无效的用户ID', 400);
        }
        $user = $this->userModel->find($id);
        if (!$user) {
            return $this->error('用户不存在', 404);
        }
        // 去除敏感字段(如密码)
        unset($user['password']);
        return $this->success($user);
    }
    // 创建用户
    public function store()
    {
        $data = $this->getRequestData();
        // 数据验证
        $rules = [
            'name' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]{2,30}$/u',
            'email' => '/^[\w\.-]+@[\w\.-]+\.\w+$/',
            'password' => '/^.{6,32}$/'
        ];
        $this->validate($data, $rules);
        // 检查邮箱唯一性
        if ($this->userModel->findByEmail($data['email'])) {
            return $this->error('邮箱已被注册', 409);
        }
        // 密码加密
        $data['password'] = password_hash($data['password'], PASSWORD_BCRYPT);
        // 创建用户
        $userId = $this->userModel->create($data);
        if (!$userId) {
            return $this->error('用户创建失败', 500);
        }
        // 返回创建成功的用户
        $user = $this->userModel->find($userId);
        unset($user['password']);
        return $this->success($user, 201, '用户创建成功');
    }
    // 完整更新用户
    public function update($id)
    {
        $user = $this->userModel->find($id);
        if (!$user) {
            return $this->error('用户不存在', 404);
        }
        $data = $this->getRequestData();
        // 验证必需字段
        $rules = [
            'name' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]{2,30}$/u',
            'email' => '/^[\w\.-]+@[\w\.-]+\.\w+$/'
        ];
        $this->validate($data, $rules);
        // 更新用户
        $result = $this->userModel->update($id, $data);
        if (!$result) {
            return $this->error('用户更新失败', 500);
        }
        $updatedUser = $this->userModel->find($id);
        unset($updatedUser['password']);
        return $this->success($updatedUser);
    }
    // 删除用户
    public function destroy($id)
    {
        $user = $this->userModel->find($id);
        if (!$user) {
            return $this->error('用户不存在', 404);
        }
        $result = $this->userModel->delete($id);
        if (!$result) {
            return $this->error('用户删除失败', 500);
        }
        return $this->success(null, 204, '用户已删除');
    }
}

安全规范

1 认证与授权

// JWT实现基础认证
class AuthController extends BaseController
{
    public function login()
    {
        $data = $this->getRequestData();
        // 验证用户名密码
        $user = $this->userModel->findByEmail($data['email']);
        if (!$user || !password_verify($data['password'], $user['password'])) {
            return $this->error('邮箱或密码错误', 401);
        }
        // 生成JWT Token
        $payload = [
            'user_id' => $user['id'],
            'exp' => time() + 7200 // 2小时过期
        ];
        $token = JWT::encode($payload, getenv('JWT_SECRET'), 'HS256');
        return $this->success([
            'token' => $token,
            'token_type' => 'Bearer',
            'expires_in' => 7200
        ]);
    }
}

2 限流

class RateLimiter
{
    public static function check($userId = null, $limit = 100, $window = 60)
    {
        $key = 'rate_limit:' . ($userId ?: getClientIp());
        // 使用Redis实现
        $current = Redis::get($key);
        if ($current === false) {
            Redis::setex($key, $window, 1);
        } elseif ($current >= $limit) {
            throw new ApiException('请求过于频繁', 429);
        } else {
            Redis::incr($key);
        }
    }
}

最佳实践

1 缓存策略

// HTTP缓存头
header('Cache-Control: private, max-age=300');
header('ETag: "' . md5(json_encode($data)) . '"');
// 条件请求
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
    $etag = trim($_SERVER['HTTP_IF_NONE_MATCH'], '"');
    if ($etag == md5(json_encode($data))) {
        http_response_code(304);
        exit;
    }
}

2 日志记录

class ApiLogger
{
    public static function log($request, $response, $duration)
    {
        $logData = [
            'request_time' => date('Y-m-d H:i:s'),
            'duration' => $duration,
            'ip' => getClientIp(),
            'method' => $request['REQUEST_METHOD'],
            'uri' => $request['REQUEST_URI'],
            'params' => json_encode($request['QUERY_STRING']),
            'response_code' => $response['code'],
            'launch_client' => $request['HTTP_USER_AGENT'] ?? ''
        ];
        // 写入日志文件
        file_put_contents(
            LOG_PATH . '/api_' . date('Y-m-d') . '.log',
            json_encode($logData) . PHP_EOL,
            FILE_APPEND
        );
    }
}

3 异常处理

// 全局异常处理
class ApiException extends Exception
{
    private $errors;
    public function __construct($message, $code = 400, $errors = null)
    {
        parent::__construct($message, $code);
        $this->errors = $errors;
    }
    public function getErrors()
    {
        return $this->errors;
    }
}
// 自定义错误处理函数
set_exception_handler(function ($exception) {
    if ($exception instanceof ApiException) {
        $response = [
            'code' => $exception->getCode(),
            'message' => $exception->getMessage(),
            'errors' => $exception->getErrors()
        ];
    } else {
        $response = [
            'code' => 500,
            'message' => '服务器内部错误',
            'request_id' => uniqid()
        ];
    }
    http_response_code($response['code']);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($response);
});

文档与测试

1 API文档规范

  • 使用OpenAPI/Swagger规范
  • 记录所有请求参数
  • 提供完整示例
# api-docs.yaml
/users:
  get:
    summary: 获取用户列表
    parameters:
      - name: page
        in: query
        description: 页码
        schema:
          type: integer
      - name: limit
        in: query
        description: 每页数量
        schema:
          type: integer
    responses:
      '200':
        description: 成功
        content:
          application/json:
            schema:
              type: object
              properties:
                code:
                  type: integer
                data:
                  type: array

2 测试规范

// 使用PHPUnit进行API测试
class UserApiTest extends PHPUnit\Framework\TestCase
{
    protected $client;
    protected function setUp(): void
    {
        $this->client = new GuzzleHttp\Client([
            'base_uri' => 'https://api.example.com/v1/',
            'headers' => [
                'Authorization' => 'Bearer ' . $this->getToken()
            ]
        ]);
    }
    public function testCreateUser()
    {
        $response = $this->client->post('users', [
            'json' => [
                'name' => 'Test User',
                'email' => 'test@gmail.com',
                'password' => 'test123456'
            ]
        ]);
        $this->assertEquals(200, $response->getStatusCode());
        $data = json_decode($response->getBody(), true);
        $this->assertEquals('success', $data['message']);
    }
}

验收清单

  • [ ] 使用HTTPS协议
  • [ ] URL使用复数名词
  • [ ] 正确使用HTTP方法
  • [ ] 统一响应格式
  • [ ] 包含分页元数据
  • [ ] 实现请求验证
  • [ ] 返回明确错误信息
  • [ ] 实现JWT认证
  • [ ] 配置CORS
  • [ ] 实现限流机制
  • [ ] 编写API文档
  • [ ] 添加单元测试

通过遵循这些规范,可以构建出可维护、可扩展且安全的RESTful API,为前端和其他系统提供清晰、一致的接口服务。

PHP项目RESTful API设计规范

抱歉,评论功能暂时关闭!