API v1 重构与完善 Spec
Why
当前 API v1 存在 URL 设计反 RESTful(?action=login)、分页格式不统一、缺少 access_token/refresh_token 双令牌机制、缺少帖子互动端点(收藏/点赞/举报)、无字段过滤与批量操作、错误响应结构不一致等问题,需要全面重构以达到可直接用于开发的标准。
What Changes
BREAKING URL 规范化
POST /user?action=login → POST /auth/login
POST /user?action=register → POST /auth/register
POST /notify?action=read → PUT /notify/{id}/read
POST /notify?action=read-all → PUT /notify/read-all
GET /thread?tid=1 → GET /thread/1(统一路径参数)
GET /post?pid=1 → GET /post/1(统一路径参数)
GET /forum?fid=1 → GET /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 客户端传递
page 和 pagesize 查询参数
- 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 返回该版块的帖子列表(含分页),支持
orderby、order、keyword 参数
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-Limit、X-RateLimit-Remaining、X-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 规范 |
数据库变更
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;
XIUNOX版本发布地址:https://xiuno.xcxgy.cn
最后于 1月前
被贰先生编辑
,原因: