100 lines
8.8 KiB
Markdown
100 lines
8.8 KiB
Markdown
# 分类接口与数据导入
|
||
|
||
分类服务基于 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)。
|