纸鱼手机管理 App(pafish-app)通过一套独立的 Bearer 令牌访问站点,不需要浏览器 Cookie,也不依赖开放 API 的 X-API-Key。本文是客户端和服务端共同的接口契约。
与开放 API 的区别
项目 | 开放 API v1 | App API |
|---|---|---|
基础路径 |
|
|
凭据 |
|
|
身份 | 匿名只读 | 登录用户,权限随账号角色实时变化 |
数据范围 | 仅公开内容 | 包含草稿、定时、回收站和待审评论 |
写操作 | 无 | 受控白名单(文章保存/批量、评论审核) |
令牌只证明“这是哪个用户、哪台设备”,不固化权限:每次请求都会重新读取用户当前角色;管理员被降级为编辑后,下一次请求立即按编辑范围返回数据。
获取令牌
方式一:App 内登录
POST /api/app/auth/login
Content-Type: application/json
{"username": "admin", "password": "******", "deviceName": "我的手机"}成功返回:
{
"ok": true,
"tokenType": "Bearer",
"accessToken": "64 位十六进制字符串",
"expiresAt": "2027-01-17 10:00:00",
"user": {
"id": 1,
"username": "admin",
"nickname": "站长",
"role": "ADMIN",
"avatarUrl": "/uploads/avatar.jpg",
"capabilities": ["dashboard.view", "posts.manage"]
},
"site": {
"name": "纸鱼博客",
"subtitle": "副标题",
"version": "1.2.1",
"timezone": "Asia/Shanghai"
}
}用户名或密码错误返回
401;账号被禁用返回403;同一 IP 或同一账号失败次数过多返回429。已启用设备数达到上限时返回
409,需要先在 App 或网页个人资料中撤销旧设备。site.timezone决定客户端如何解释接口里的日期时间:App 各接口的日期一律是站点本地时间的墙上时钟字符串,不是 UTC。
方式二:网页个人资料创建
登录后台,进入“个人资料 → 设备授权”,填写设备名称和当前密码后创建。明文令牌只在创建成功时返回一次,之后只能看到设备名称、时间与撤销/删除操作。该入口有独立的频率限制(每账号 10 分钟 10 次)。
令牌规则
项目 | 规则 |
|---|---|
有效期 | 90 天,过期后需重新登录 |
存储 | 数据库只保存 SHA-256 哈希,不保存明文 |
设备上限 | 每账号最多 10 个有效设备 |
撤销 | 可单个撤销,也可一次撤销全部设备 |
删除 | 只允许删除“已撤销”的记录,活动令牌必须先撤销 |
失效条件 | 过期、已撤销、用户被禁用 |
请求头格式固定为:
Authorization: Bearer 64位十六进制令牌令牌无效、过期或已撤销时返回:
{"error": "访问令牌无效、已过期或已撤销"}状态码为 401。客户端收到 401 应清除本地令牌并回到连接页,不要反复重试。
令牌管理接口
方法 | 路径 | 说明 |
|---|---|---|
|
| 校验令牌并返回 |
|
| 当前账号的设备列表 |
|
| 撤销指定设备 |
|
| 撤销当前令牌并退出 |
设备列表每项包含 id、device_name、created_at、expires_at、last_used_at、revoked_at 和 current(是否为当前设备)。列表不会返回明文令牌或哈希。
内容接口
所有接口都需要 Bearer 令牌。编辑角色只能访问自己署名的文章和其下的评论;管理员可访问全站。
概览
GET /api/app/stats返回:
{
"stats": {
"posts": {"active": 12, "published": 8, "drafts": 3, "scheduled": 1, "trash": 0},
"pendingComments": 4
}
}文章列表
GET /api/app/posts?status=DRAFT&page=1&perPage=20&q=关键词参数 | 说明 |
|---|---|
| 空(全部未删除)、 |
| 从 |
| 默认 |
| 标题或 slug 模糊搜索; |
返回 posts、total、page、perPage、totalPages。列表项包含 id、title、slug、excerpt、coverUrl、status、publishedAt、viewCount、likeCount、favoriteCount、pinned、authorId、categoryId、createdAt 和 updatedAt。
文章详情
GET /api/app/posts/{id}返回文章全部可编辑字段,额外附带:
tags:[{id, name}]custom_fields:自定义字段数组is_pinned、category_pinned、allow_comments:布尔值不返回
password字段
访问他人文章返回 404(不暴露文章是否存在)。
评论审核
GET /api/app/comments?status=PENDING&page=1&perPage=20status 支持 PENDING(默认)、APPROVED、REJECTED、SPAM、TRASH、ALL;未知值回落到 PENDING。返回 comments、total、page、perPage、totalPages。
评论项包含 id、postId、postTitle、authorName、content、authorAvatar、status、pinned、parentId 和 createdAt。不返回评论者邮箱。
受控写操作
App 令牌可以调用以下已有的后台接口,但必须带 X-Requested-With: XMLHttpRequest 请求头,并遵守白名单:
路径 | 允许的操作 |
|---|---|
| 新建 / 更新文章 |
| 仅 |
| 审核状态流转 |
| 管理员回复 |
| 置顶评论 |
| 删除评论 |
| 仅 |
白名单之外的后台接口(站点设置、用户、插件、备份、升级、令牌创建等)一律返回 405;purge(永久删除文章)返回 403。App 令牌不能创建新的设备令牌,也不能读取后台 HTML 页面。
令牌写操作与开放 API 共用限流桶:每个设备令牌每分钟 120 次。
媒体与预览
App 编辑器复用后台接口:
POST /api/upload:上传文件,multipart/form-data,字段名filePOST /api/upload/chunk:大文件分片上传GET /api/uploads:媒体库列表POST /api/md-preview:Markdown / HTML 预览
这些接口接受 Bearer 令牌,并在没有浏览器 CSRF 令牌时按令牌身份放行。分片文件写入 storage/chunks/,需要该目录可写。
客户端实现建议
令牌保存在系统安全存储(
plus.storage等),不要写进普通配置或日志。每次冷启动先调用
/api/app/auth/me验证令牌并取站点身份;timezone必须随会话保存。请求统一带
Authorization头;不要在 URL 或表单里传令牌。遇到
401清理本地令牌并回到连接页;遇到429按提示等待,指数退避。文章保存时不要提交
password字段;App 不提供修改文章密码的入口。publishedAt、createdAt、updatedAt按站点时区的墙上时钟解析,展示时不要再次做 UTC 转换。