# HTTP API 参考 Base URL:部署域名(如 `https://footpr.example.com`)。除 `/health` 与 `/auth/register|login` 外, 所有接口都鉴权。登录限速:同一 IP + 用户名连续失败 5 次会收到 429,冷却 10 分钟。 统一响应格式: ```json { "ok": true, "data": { ... } } { "ok": false, "error": "错误原因" } ``` ## 健康检查 | 方法 | 路径 | 说明 | |---|---|---| | GET | `/health` | 存活探针,无需鉴权 | ## 认证 | 方法 | 路径 | 说明 | |---|---|---| | POST | `/auth/register` | 注册(`REGISTER_ENABLED`/邀请码/`MAX_USERS` 门控;首个非 demo 账号自动成为管理员)。请求体里的 `seed` 标志一律被服务端剥离,仅供内部播种 | | POST | `/auth/login` | 登录,返回 Bearer Token(365 天) | | POST | `/auth/logout` | 吊销当前 Bearer Token(服务端删除 token 行) | | GET | `/auth/me` | 当前用户信息(`user_id` / `username` / `is_admin` / `device_id`) | ```bash TOKEN=$(curl -s https://footpr.example.com/auth/login \ -H 'content-type: application/json' \ -d '{"username":"demo","password":"demo1234","device_name":"cli"}' | jq -r .data.token) ``` ## 管理(仅 is_admin 会话) | 方法 | 路径 | 说明 | |---|---|---| | GET | `/admin/users` | 用户列表(设备数/记录数/附件用量),元数据不含档案内容 | | POST | `/admin/users/delete` | 删除用户 `{"user_id":"..."}`,级联清理图床/档案库/附件/会话;不能删自己、不能删最后一位管理员 | | GET | `/admin/invites` | 邀请码列表(remark、创建者/使用者、valid、holder_gone) | | POST | `/admin/invites/generate` | 批量生成随机码 `{"count": 5}`(1-50) | | POST | `/admin/invites/create` | 新建自定义码 `{"code":"XIONG-2025","remark":"备注"}`(4-32 位字母数字-_) | | POST | `/admin/invites/update` | 改备注 / 启停 `{"id":1,"remark":"...","valid":false}`(已使用的码不可再启用) | | POST | `/admin/invites/delete` | 删除 `{"id":1}` | | POST | `/admin/invites/revoke` | 作废(等价 update valid=false,兼容保留) | | POST | `/admin/demo-rebuild` | 级联删除现有 demo 并重建、重播种演示数据 | | POST | `/admin/demo-seed` | 给 demo 播种演示数据(幂等;档案库非空时返回 seeded:false) | | POST | `/admin/attachments/sync-lsky` | 把当前用户全部未上图床的附件批量推送到 Lsky(需配置 `LSKY_API`/`LSKY_TOKEN`) | ## 同步 | 方法 | 路径 | 说明 | |---|---|---| | GET | `/sync/status` | 当前用户同步账本统计 | | POST | `/sync/push` | 推送 wire 变更(LWW 上/删除,立即生效) | | GET | `/sync/pull?cursor=...&limit=...` | 按 seq 游标增量拉取 | | POST | `/sync/attachment` | 上传附件 `{"sha256","size","mime","file_name","data_b64"}`(内容寻址去重,哈希校验) | | GET | `/sync/attachment/` | 下载附件(`Cache-Control: private, immutable`) | 附件另有两个业务命令通道:`POST /api/attachment_read`(base64 全文,含缩略图通道 `attachment_thumb`) 与 `POST /api/attachment_download`(直接落盘,`Content-Disposition` 带原始文件名)。 ## 业务命令层 `POST /api/` 与桌面端共用同一命令层(`app/server/db.mjs`),按用户隔离。44 个命令: | 分组 | 命令 | |---|---| | 应用 | `app_info` · `sync_status` | | 记录类型 | `type_list` · `type_upsert` · `type_delete` | | 字典/维度 | `taxonomy_list` · `taxonomy_upsert` · `taxonomy_delete` · `dimension_list` · `dimension_upsert` · `dimension_delete` | | 足迹 | `footprint_list` · `footprint_get` · `footprint_upsert` · `footprint_delete` · `timeline` | | 统计 | `stats_overview` · `metric_list` · `metric_upsert` · `metric_delete` | | 成绩 | `grades_overview` · `semester_upsert` · `semester_delete` · `course_upsert` · `course_delete` · `band_upsert` · `band_delete` · `gpa_settings_get` · `gpa_settings_upsert` | | 附件 | `attachment_add` · `attachment_list` · `attachment_delete` · `attachment_read` · `attachment_thumb` · `attachment_set_remote` · `attachment_set_thumb` | | 档案 | `profile_get` · `profile_upsert` | | 保存视图 | `view_list` · `view_upsert` · `view_delete` | | 播种/同步内核 | `demo_seed` · `sync_apply_wire` · `sync_pull_wire` | ### `profile_upsert` 语义 顶层 key 合并:`info` / `resume` / `zongce` 是互相独立的子文档,只更所提交的 key, 其余原样保留(避免三张页面互相覆盖): ```json { "info": { "name": "...", "phone": "...", "eduStart": "2023-09" } } ``` ### `footprint_list` 的 `query` | 字段 | 说明 | |---|---| | `q` | 全文检索(≥3 字符走 FTS5 trigram 子串匹配,短词回退 LIKE) | | `year` | 起始年份过滤 | | `types` / `levels` / `roles` / `statuses` | 数组多选过滤 | | `dims` | 维度多选 | | `polarity` | `positive` / `negative` | | `has_attachment` | 仅看有附件记录 | | `tag` | 标签匹配 | | `sort` | `date_desc`(默认)/ `date_asc` / `updated_desc` / `weight_desc` | | `page` /`page_size` | 分页(`page_size` 上限 2000) | ### 分页约定 `page_size` 上限 2000;传非 `{query:{...}}` 形状的载荷会按默认 20 条处理(客户端请按此约定传参)。 示例(检索记录): ```bash curl -s https://footpr.example.com/api/footprint_list \ -H 'content-type: application/json' -H "authorization: Bearer $TOKEN" \ -d '{"query":{"page":1,"page_size":20,"year":2025}}' ```