Files
doodle-store/docs/v3.5/分类接口与数据导入.md

100 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 分类接口与数据导入
分类服务基于 v3.5 `categories` 表实现,供小程序及后续 Web 共用。保留原分类 ID 和父子关系,年级不属于分类树。当前服务端已实现,`doodle-mini` 分类页已通过统一环境配置切换至新后端,其他页面仍按迁移进度保留旧云服务。
## 接口
前缀为 `/api/v1`,成功响应统一为 `{ code: 0, message: "ok", data: ... }`。
| 方法 | 路径 | 权限 | 结果 |
| ------ | ----------------------- | ------------------- | --------------------------------------------------------------------------- |
| GET | `/categories` | 公开,无需登录 | 自身及全部祖先已上线分类的扁平数组,依次按 `sortOrder ASC, id ASC` 稳定排序 |
| GET | `/categories/:id` | 公开,无需登录 | 单个已上线分类;不存在或所属链未上线返回 404 |
| POST | `/admin/categories` | 当前 active + admin | 创建分类,返回 201;ID 重复返回 409 |
| PATCH | `/admin/categories/:id` | 当前 active + admin | 局部更新,返回 200;不存在返回 404 |
| DELETE | `/admin/categories/:id` | 当前 active + admin | 删除无引用分类,返回 200;不存在 404,有引用 409 |
列表没有分页、筛选或自动展开后代的参数,不沿用 CloudBase 单次查询的 100 条限制。分类表没有发布状态,不套用题型的 active 过滤规则。
公开分类结构示例:
```json
{
"id": "math",
"name": "数感启蒙",
"icon": "📐",
"color": "#CDB4DB",
"sortOrder": 0,
"parentId": null,
"status": "active",
"createdAt": "2026-10-15T05:35:43.575Z",
"updatedAt": "2026-10-15T05:35:43.575Z"
}
```
新版返回 `id`,不是旧云函数的 `_id`。小程序切换时应在接口适配层转换;不要据此修改数据库主键。公开响应不含 `iconAssetId`、对象存储 key 或内部关联资料。公共 TypeScript 契约位于 `packages/contracts/src/index.ts`,开发 Swagger 可查看实际注册接口。
创建请求包含必填 `id/name/icon/color`,可选 `status`(默认 draft)、`sortOrder`(默认 0)和 `parentId`(默认 null)。PATCH 只接受 `name/icon/color/sortOrder/parentId/status`,省略字段保持原值;不允许空对象、修改 ID、未知字段或将数字字符串隐式转换为排序值。`parentId` 为 null 或空串时规范化为根分类。
ID 区分大小写,最长 128 个字符,不含空白、控制字符或斜杠;名称/图标/颜色长度分别不超过 100/1024/32,排序必须为 PostgreSQL integer。名称、图标和颜色会去除首尾空白。
父分类必须存在,禁止自引用及多层循环。分类写入先锁定并复核管理员账户,再串行锁定分类树、读取最新状态并校验;导入也使用同一个分类表锁。公开读取不被该表锁阻塞。成功管理修改与前后快照审计在同一事务提交,重复提交相同值不刷新更新时间或新增审计。当前未开放 HTTP 导入接口。
新增、修改、删除均使用 `Authorization: Bearer <accessToken>`,以服务端当前账户的 `active + admin` 为准。未登录返回 401,普通用户或已降权管理员返回 403;禁用账户或凭证撤销返回 401。事务内再次复核账户,不能用 token 自报角色或旧身份绕过限制。
删除不接收请求体,成功响应为 `{ code: 0, message: "ok", data: { id: "分类ID" } }`。存在子分类或任何状态(draft/active/hidden)的题型引用时返回 409,须先调整这些引用;不级联删除子分类、题型或图标资产。删除记录保留 beforeData,afterData 为 NULL;审计失败则删除回滚。同一分类并发删除仅一次成功,其余返回 404。
本批图标均为文字/Emoji。文件图标的资产迁移、访问地址解析尚待七牛模块接入,当前创建/导入拒绝文件 URL;遇到已有文件资产图标时不下发原始地址。文件资产能力由迁移清单 S2-F04 跟进。
## 本地导入
在 `doodle-store` 根目录执行,文件参数使用绝对路径:
```bash
# 构建后执行预演,不写分类或审计
pnpm categories:import --file "/绝对路径/categories.json"
# 明确写入;整批成功或整批回滚
pnpm categories:import --file "/绝对路径/categories.json" --apply
# 可选报告路径也使用绝对路径;报告文件必须尚不存在
pnpm categories:import --file "/绝对路径/categories.json" --apply --report "/绝对路径/import-report.json"
```
工具读取 `apps/server/.env` 的 DATABASE_URL,允许显式环境覆盖,但仅允许本机 `doodle_dev`。若服务端已经构建,可用 `pnpm --filter @doodle/server categories:import ...` 避免重复构建。pnpm 的工作目录会切换到 server 包,使用绝对文件/报告路径可避免歧义。报告路径会在写库前检查并独占创建;不覆盖已有报告。
导入支持 JSON 数组与 CloudBase JSON Lines(本次附件格式),最多 10000 行、10 MiB;只接受已知分类字段以及 `_id/id/createdAt/updatedAt`。未知字段、重复 ID、冲突的 `_id/id`、类型错误、未知日期格式或无效父子树会整批拒绝。日期可选,存在时须为带时区的 ISO 字符串;尚不支持 CloudBase 特殊日期对象。
导入按合并后的完整分类树检查并按父先子后写入,不要求源文件按层级排序。新增行保留源 ID;已有行按业务字段比较,真正变化时才更新,并保留已有 `createdAt`,不会删除附件中没有的分类。文件缺时间时,新记录的两个时间使用同一导入批次时间;有变化的已有行只补更新时间。完全相同的重复导入不改变数据、时间或审计。
每条实际变化写入 `actor_type=migration` 审计,包含批次 requestId、源文件 SHA-256、前后快照及时间补齐说明。报告列出新增、更新、未变化及缺时间字段的数量;正式导入前应查看预演结果。
## 2026-10-15 本地导入结果
源文件:`database_export-_oMVCNYtUGVT.json`;SHA-256:`02a5a2d26c1801e7e77e8dc2449aebd5b74e11d49a358ed957f3cd11ca290d11`。
已向本地 `doodle_dev` 新增 6 条分类及 6 条迁移审计,未修改其他业务表。6 行均缺少原始创建/更新时间,统一使用 `2026-10-15T05:35:43.575Z`(北京时间 13:35:43.575),这是本次导入时间,并非历史创建时间。
| ID | 名称 | 图标 | sortOrder | parentId |
| ------- | -------- | ---- | --------- | -------- |
| math | 数感启蒙 | 📐 | 0 | null |
| puzzle | 益智游戏 | 🧩 | 1 | null |
| pinyin | 汉语拼音 | ǎ | 2 | null |
| chinese | 识字练字 | ✏️ | 3 | null |
| english | 英语启蒙 | 🔤 | 4 | null |
| craft | 创意手工 | 🌈 | 5 | null |
批次及时间补齐记录见 [首次导入报告](categories-import-2026-10-15.json)。原附件未改动。首次报告因相对路径无效未能保存,现已按数据库已提交记录补记,并修正了工具的报告路径预检查。
再次正式执行同一附件的结果为新增 0、更新 0、未变化 6,见 [重复导入复核报告](categories-reimport-2026-10-15.json)。实际 HTTP 列表/6 个详情已逐字段与附件核对一致,创建及更新时间保持首次批次值,审计仍为 6 条。开发库未新增任何测试用户;匿名读取成功,匿名管理写入返回 401。
## 验证
`pnpm check` 覆盖格式、静态检查、类型检查、构建及常规测试;`pnpm test:db` 在独立临时数据库验证登录鉴权和分类业务,结束后删除临时库,不向开发库写入测试账户。
分类验证涵盖公开读取、增删改的管理员权限及禁用/降权/撤销、非法字段、ID 大小写、稳定排序、局部更新、审计快照、无变化更新、事务内权限复核、两个管理员并发互设父级、子分类及各状态题型引用保护、删除审计失败回滚、并发/重复删除、子先父后导入、dry-run、缺失时间补齐、重复导入、保留创建时间、半批失败回滚及跨现有数据的循环校验。
## 发布控制(2026-10-16)
增加 GET /admin/categories 及 /admin/categories/:id,可读取所有状态;公开接口不接受草稿预览开关。新增分类默认 draft,旧分类增量升级保留 active。旧导出省略 status 时重导保留当前状态,新导入默认 draft;显式状态才更改。状态或父级修改同事务撤销新后端数据库页面快照并记录审计。详见 [内容发布与可见性](./内容发布与可见性.md)。