Xiuno BBS 重构记录贴(四)API v1 重构与完善
贰先生 3小时前

API v1 重构与完善 Spec

Why

当前 API v1 存在 URL 设计反 RESTful(?action=login)、分页格式不统一、缺少 access_token/refresh_token 双令牌机制、缺少帖子互动端点(收藏/点赞/举报)、无字段过滤与批量操作、错误响应结构不一致等问题,需要全面重构以达到可直接用于开发的标准。

What Changes

BREAKING URL 规范化

  • POST /user?action=loginPOST /auth/login
  • POST /user?action=registerPOST /auth/register
  • POST /notify?action=readPUT /notify/{id}/read
  • POST /notify?action=read-allPUT /notify/read-all
  • GET /thread?tid=1GET /thread/1(统一路径参数)
  • GET /post?pid=1GET /post/1(统一路径参数)
  • GET /forum?fid=1GET /forum/1(统一路径参数)

BREAKING 分页响应统一

  • 所有列表端点统一返回 pagination 对象:{page, pagesize, total, total_pages}
  • 列表数据统一放在 list 字段

BREAKING 错误响应结构增强

  • 错误响应增加 errors 字段(验证错误时返回字段级详情)
  • code 字段与 HTTP 状态码对齐(成功为 0,其余与 HTTP 状态码一致)

Token 机制升级

  • 引入 access_token(短期,默认 2 小时)+ refresh_token(长期,默认 30 天)双令牌
  • 新增 POST /auth/refresh 端点
  • 新增 POST /auth/logout 端点(撤销 refresh_token)
  • api_token 表增加 type 字段区分 access/refresh

新增资源端点

  • POST /thread/{tid}/like / DELETE /thread/{tid}/like — 帖子点赞
  • POST /thread/{tid}/favorite / DELETE /thread/{tid}/favorite — 帖子收藏
  • POST /thread/{tid}/report — 帖子举报
  • GET /user/{uid}/threads — 用户帖子列表
  • GET /user/{uid}/posts — 用户回复列表
  • GET /user/{uid}/favorites — 用户收藏列表
  • GET /forum/{fid}/threads — 版块帖子列表(替代 /thread?fid=
  • POST /auth/logout — 退出登录

字段过滤

  • 所有 GET 列表/详情端点支持 fields 查询参数
  • 示例:GET /thread/1?fields=tid,subject,uid,create_date

批量操作

  • DELETE /thread/batch — 批量删除帖子(body: {tids: [1,2,3]},需管理员权限)
  • DELETE /post/batch — 批量删除回复(body: {pids: [1,2,3]},需管理员权限)
  • PUT /thread/batch — 批量更新帖子类型/置顶/关闭(body: {tids: [1,2,3], update: {top: 1}},需管理员权限)

API 版本策略

  • URL 路径版本:/api/v1/
  • 响应头包含 X-API-Version: 1.0
  • 版本变更规则:补丁版本向后兼容,主版本可引入 BREAKING 变更

限流增强

  • 限流响应增加 Retry-After
  • 认证用户与匿名用户分别限流
  • 管理员豁免限流

文档自动生成

  • ApiDocService 从硬编码改为基于注解/配置自动生成
  • 新增 GET /api/v1/openapi.json 端点返回 OpenAPI 3.0 规范

Impact

  • Affected specs: api-optimization-and-admin-fix(前序 spec,已完成)
  • Affected code:
    • api/v1/bootstrap.php — 路由分发重构
    • api/v1/user.php → 拆分为 api/v1/auth.php + api/v1/user.php
    • api/v1/thread.php — 增加互动端点、路径参数、字段过滤
    • api/v1/post.php — 路径参数、字段过滤
    • api/v1/forum.php — 增加 /forum/{fid}/threads 子资源
    • api/v1/notify.php — URL 规范化
    • api/v1/attach.php — 路径参数
    • api/v1/search.php — 分页格式统一
    • api/v1/site.php — 无变更
    • lib/ApiAuthService.php — 双令牌机制
    • lib/ApiResponse.php — 错误响应结构增强
    • lib/ApiDocService.php — 改为自动生成
    • lib/RateLimitService.php — 限流增强
    • service/UserService.php — 增加 getUserList/getUserCount
    • service/ThreadService.php — 增加互动相关方法
    • docs/refactor/phase3/migration.sql — 增加 thread_like/thread_favorite/thread_report 表
    • admin/view/htm/api_doc.htm — 适配新端点
    • admin/view/htm/api_debug.htm — 适配新端点

ADDED Requirements

Requirement: RESTful URL 规范

所有 API 端点 SHALL 遵循 RESTful 资源路径设计,禁止使用 ?action= 查询参数表达操作语义

Scenario: 用户登录

  • WHEN 客户端发送 POST /api/v1/auth/login,body 包含 {email, password}
  • THEN 返回 {code: 0, msg: "ok", data: {access_token, refresh_token, expires_in, user}}

Scenario: 用户注册

  • WHEN 客户端发送 POST /api/v1/auth/register,body 包含 {email, username, password}
  • THEN 返回 {code: 0, msg: "ok", data: {uid, access_token, refresh_token, expires_in}}

Scenario: 刷新令牌

  • WHEN 客户端发送 POST /api/v1/auth/refresh,body 包含 {refresh_token}
  • THEN 返回 {code: 0, msg: "ok", data: {access_token, refresh_token, expires_in}}

Scenario: 退出登录

  • WHEN 客户端发送 POST /api/v1/auth/logout,Header 包含 Bearer access_token,body 包含 {refresh_token}
  • THEN 撤销该 refresh_token 及其关联的 access_token,返回 {code: 0, msg: "ok", data: null}

Scenario: 标记通知已读

  • WHEN 客户端发送 PUT /api/v1/notify/{id}/read
  • THEN 标记该通知已读,返回 {code: 0, msg: "ok", data: null}

Scenario: 全部标记已读

  • WHEN 客户端发送 PUT /api/v1/notify/read-all
  • THEN 标记当前用户所有通知已读,返回 {code: 0, msg: "ok", data: null}

Requirement: 统一分页响应格式

所有列表端点 SHALL 返回统一的分页结构

Scenario: 列表响应

  • WHEN 客户端请求任何列表端点
  • THEN 响应 data 包含 {list: [...], pagination: {page, pagesize, total, total_pages}}

Scenario: 分页参数

  • WHEN 客户端传递 pagepagesize 查询参数
  • THEN pagesize 上限为 100,默认 20;page 默认 1

Requirement: 双令牌认证机制

系统 SHALL 实现 access_token + refresh_token 双令牌机制

Scenario: 登录获取令牌

  • WHEN 用户登录成功
  • THEN 返回 access_token(有效期 2 小时)和 refresh_token(有效期 30 天)

Scenario: access_token 过期

  • WHEN access_token 过期后客户端使用 refresh_token 调用 /auth/refresh
  • THEN 返回新的 access_token 和 refresh_token,旧 refresh_token 失效(轮转机制)

Scenario: refresh_token 过期

  • WHEN refresh_token 也过期
  • THEN 返回 401,客户端需重新登录

Scenario: Token 表结构

  • WHEN 数据库迁移执行
  • THEN bbs_api_token 表增加 type 字段(枚举 access/refresh)和 related_token 字段(关联 token ID)

Requirement: 增强错误响应

错误响应 SHALL 包含结构化的错误详情

Scenario: 验证错误

  • WHEN 请求参数验证失败
  • THEN 返回 {code: 422, msg: "Validation Error", data: null, errors: [{field: "email", message: "Email is required"}, {field: "password", message: "Password is required"}]}

Scenario: 通用错误

  • WHEN 请求发生非验证类错误
  • THEN 返回 {code: <http_status>, msg: "<描述>", data: null}

Requirement: 帖子互动端点

系统 SHALL 提供帖子点赞、收藏、举报端点

Scenario: 点赞帖子

  • WHEN 客户端发送 POST /api/v1/thread/{tid}/like(需认证)
  • THEN 记录点赞,返回 {code: 0, msg: "ok", data: {liked: true}}

Scenario: 取消点赞

  • WHEN 客户端发送 DELETE /api/v1/thread/{tid}/like(需认证)
  • THEN 取消点赞,返回 {code: 0, msg: "ok", data: {liked: false}}

Scenario: 收藏帖子

  • WHEN 客户端发送 POST /api/v1/thread/{tid}/favorite(需认证)
  • THEN 记录收藏,返回 {code: 0, msg: "ok", data: {favorited: true}}

Scenario: 取消收藏

  • WHEN 客户端发送 DELETE /api/v1/thread/{tid}/favorite(需认证)
  • THEN 取消收藏,返回 {code: 0, msg: "ok", data: {favorited: false}}

Scenario: 举报帖子

  • WHEN 客户端发送 POST /api/v1/thread/{tid}/report(需认证),body 包含 {reason}
  • THEN 记录举报,返回 {code: 0, msg: "ok", data: null}

Requirement: 用户子资源端点

系统 SHALL 提供用户维度的子资源查询

Scenario: 用户帖子列表

  • WHEN 客户端发送 GET /api/v1/user/{uid}/threads
  • THEN 返回该用户的帖子列表(含分页)

Scenario: 用户回复列表

  • WHEN 客户端发送 GET /api/v1/user/{uid}/posts
  • THEN 返回该用户的回复列表(含分页)

Scenario: 用户收藏列表

  • WHEN 客户端发送 GET /api/v1/user/{uid}/favorites(需认证,仅可查看自己的)
  • THEN 返回该用户的收藏帖子列表(含分页)

Requirement: 版块帖子子资源

系统 SHALL 提供版块下的帖子列表端点

Scenario: 版块帖子列表

  • WHEN 客户端发送 GET /api/v1/forum/{fid}/threads
  • THEN 返回该版块的帖子列表(含分页),支持 orderbyorderkeyword 参数

Requirement: 字段过滤

GET 端点 SHALL 支持 fields 查询参数

Scenario: 指定返回字段

  • WHEN 客户端发送 GET /api/v1/thread/1?fields=tid,subject,uid
  • THEN 仅返回 tid、subject、uid 三个字段

Scenario: 无效字段名

  • WHEN 客户端请求的 fields 包含不存在的字段
  • THEN 忽略无效字段,仅返回有效字段

Requirement: 批量操作

系统 SHALL 提供管理员批量操作端点

Scenario: 批量删除帖子

  • WHEN 管理员发送 DELETE /api/v1/thread/batch,body 包含 {tids: [1,2,3]}
  • THEN 删除指定帖子,返回 {code: 0, msg: "ok", data: {deleted: 3}}

Scenario: 批量删除回复

  • WHEN 管理员发送 DELETE /api/v1/post/batch,body 包含 {pids: [1,2,3]}
  • THEN 删除指定回复,返回 {code: 0, msg: "ok", data: {deleted: 3}}

Scenario: 批量更新帖子

  • WHEN 管理员发送 PUT /api/v1/thread/batch,body 包含 {tids: [1,2,3], update: {top: 1}}
  • THEN 批量更新帖子属性,返回 {code: 0, msg: "ok", data: {updated: 3}}

Scenario: 非管理员批量操作

  • WHEN 非管理员用户尝试批量操作
  • THEN 返回 403 Forbidden

Requirement: 限流增强

限流机制 SHALL 区分认证/匿名用户,并返回标准头

Scenario: 限流响应头

  • WHEN 任何 API 请求
  • THEN 响应包含 X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset

Scenario: 触发限流

  • WHEN 请求超过限流阈值
  • THEN 返回 429 状态码,包含 Retry-After

Scenario: 认证用户限流

  • WHEN 认证用户请求 API
  • THEN 限流阈值高于匿名用户(认证 120/min,匿名 60/min)

Scenario: 管理员豁免

  • WHEN 管理员(gid=1)请求 API
  • THEN 不受限流限制

Requirement: OpenAPI 3.0 规范端点

系统 SHALL 提供 OpenAPI 3.0 JSON 规范

Scenario: 获取 OpenAPI 规范

  • WHEN 客户端发送 GET /api/v1/openapi.json
  • THEN 返回符合 OpenAPI 3.0 规范的 JSON 文档

Requirement: API 版本头

所有 API 响应 SHALL 包含版本信息

Scenario: 版本响应头

  • WHEN 任何 API 请求
  • THEN 响应包含 X-API-Version: 1.0

MODIFIED Requirements

Requirement: ApiResponse 输出格式

ApiResponse 类 SHALL 支持增强的错误响应结构

  • success() 方法保持不变:{code: 0, msg: "ok", data: ...}
  • error() 方法增加可选 errors 参数:{code: <int>, msg: <string>, data: null, errors: [...]}
  • 新增 validationError() 方法支持字段级错误:validationError(string $msg, array $errors = [])

Requirement: ApiAuthService 令牌管理

ApiAuthService SHALL 支持双令牌机制

  • generateTokens(int $uid): array — 同时生成 access_token 和 refresh_token
  • validateAccessToken(string $token): ?array — 验证 access_token
  • validateRefreshToken(string $token): ?array — 验证 refresh_token
  • refreshTokens(string $refreshToken): ?array — 轮转刷新令牌
  • revokeTokens(string $refreshToken): bool — 撤销令牌对
  • 保留 getBearerToken() 静态方法

Requirement: bootstrap.php 路由分发

bootstrap.php SHALL 支持更细粒度的路由匹配

  • 支持路径参数提取:/thread/{tid}/like$segments[0]='thread', $segments[1]={tid}, $segments[2]='like'
  • 支持 auth 资源路由到 auth.php
  • 支持 batch 子路径路由到对应资源的批量操作

REMOVED Requirements

Requirement: ?action= 查询参数路由

Reason: 违反 RESTful 设计原则,改为资源路径 Migration: 客户端需将 ?action=login 改为 POST /auth/login?action=read 改为 PUT /notify/{id}/read

Requirement: 查询参数获取单个资源

Reason: GET /thread?tid=1 不符合 REST 规范,应使用路径参数 GET /thread/1 Migration: 客户端需将 ?tid=1 改为路径参数 /thread/1


完整端点清单

认证 Auth

方法 路径 认证 说明
POST /auth/login 登录
POST /auth/register 注册
POST /auth/refresh 刷新令牌
POST /auth/logout 退出登录

用户 User

方法 路径 认证 说明
GET /user 用户列表
GET /user/me 当前用户
GET /user/{uid} 用户详情
PUT /user/{uid} 更新用户
GET /user/{uid}/threads 用户帖子
GET /user/{uid}/posts 用户回复
GET /user/{uid}/favorites 用户收藏

帖子 Thread

方法 路径 认证 说明
GET /thread 帖子列表
POST /thread 创建帖子
GET /thread/{tid} 帖子详情
PUT /thread/{tid} 更新帖子
DELETE /thread/{tid} 删除帖子
POST /thread/{tid}/like 点赞
DELETE /thread/{tid}/like 取消点赞
POST /thread/{tid}/favorite 收藏
DELETE /thread/{tid}/favorite 取消收藏
POST /thread/{tid}/report 举报
DELETE /thread/batch 是(管理员) 批量删除
PUT /thread/batch 是(管理员) 批量更新

回复 Post

方法 路径 认证 说明
GET /post 回复列表
POST /post 创建回复
GET /post/{pid} 回复详情
PUT /post/{pid} 更新回复
DELETE /post/{pid} 删除回复
DELETE /post/batch 是(管理员) 批量删除

版块 Forum

方法 路径 认证 说明
GET /forum 版块列表
GET /forum/{fid} 版块详情
GET /forum/{fid}/threads 版块帖子

附件 Attach

方法 路径 认证 说明
GET /attach/{aid} 附件详情
POST /attach 上传附件
DELETE /attach/{aid} 删除附件

通知 Notify

方法 路径 认证 说明
GET /notify 通知列表
GET /notify/unread 未读数
PUT /notify/{id}/read 标记已读
PUT /notify/read-all 全部已读

搜索 Search

方法 路径 认证 说明
GET /search 全文搜索

站点 Site

方法 路径 认证 说明
GET /site 站点信息
GET /site/stats 站点统计

元数据

方法 路径 认证 说明
GET /openapi.json OpenAPI 3.0 规范

数据库变更

-- 修改 api_token 表:增加 type 和 related_id 字段
ALTER TABLE `bbs_api_token` ADD COLUMN `type` enum('access','refresh') NOT NULL DEFAULT 'access' AFTER `uid`;
ALTER TABLE `bbs_api_token` ADD COLUMN `related_id` bigint(16) unsigned NOT NULL DEFAULT 0 AFTER `type`;
ALTER TABLE `bbs_api_token` ADD INDEX `uid_type` (`uid`, `type`);

-- 帖子点赞表
CREATE TABLE IF NOT EXISTS `bbs_thread_like` (
  `id` bigint(16) unsigned NOT NULL AUTO_INCREMENT,
  `tid` int(11) unsigned NOT NULL DEFAULT 0,
  `uid` int(11) unsigned NOT NULL DEFAULT 0,
  `create_date` int(11) unsigned NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `tid_uid` (`tid`, `uid`),
  KEY `uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 帖子收藏表
CREATE TABLE IF NOT EXISTS `bbs_thread_favorite` (
  `id` bigint(16) unsigned NOT NULL AUTO_INCREMENT,
  `tid` int(11) unsigned NOT NULL DEFAULT 0,
  `uid` int(11) unsigned NOT NULL DEFAULT 0,
  `create_date` int(11) unsigned NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `tid_uid` (`tid`, `uid`),
  KEY `uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 帖子举报表
CREATE TABLE IF NOT EXISTS `bbs_thread_report` (
  `id` bigint(16) unsigned NOT NULL AUTO_INCREMENT,
  `tid` int(11) unsigned NOT NULL DEFAULT 0,
  `uid` int(11) unsigned NOT NULL DEFAULT 0,
  `reason` varchar(500) NOT NULL DEFAULT '',
  `create_date` int(11) unsigned NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `tid` (`tid`),
  KEY `uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
最新回复 (0)
全部楼主
返回