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

8.8 KiB
Raw Blame History

分类接口与数据导入

分类服务基于 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 过滤规则。

公开分类结构示例:

{
    "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 根目录执行,文件参数使用绝对路径:

# 构建后执行预演,不写分类或审计
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

批次及时间补齐记录见 首次导入报告。原附件未改动。首次报告因相对路径无效未能保存,现已按数据库已提交记录补记,并修正了工具的报告路径预检查。

再次正式执行同一附件的结果为新增 0、更新 0、未变化 6,见 重复导入复核报告。实际 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;显式状态才更改。状态或父级修改同事务撤销新后端数据库页面快照并记录审计。详见 内容发布与可见性。