# 题型查询接口 更新日期:2026-10-16。实现范围:公开题型列表、详情和组合筛选,供小程序及未来 Web 共用。题型管理预览及状态写入已在独立管理员接口补齐;尚未实现题型配置编辑、分龄推荐 Top3 混排或首页构建,doodle-mini 分类页现已通过全局环境配置接入新接口。 ## 接口与示例 统一前缀为 `/api/v1`,无需登录,固定要求自身 `status=active` 且所属分类及全部祖先已上线。客户端不能传 status、rawStats 等参数读取草稿、隐藏内容或管理统计;未识别参数返回 400。 | 用途 | 请求 | | -------------------- | --------------------------------------------------------------------------- | | 全部题型(分页) | `GET /worksheets?page=1&pageSize=20` | | 本批全部 51 条 | `GET /worksheets?pageSize=100` | | 按分类 | `GET /worksheets?categoryId=math` | | 按单个年级 | `GET /worksheets?grade=1` | | 按多个年级,任一匹配 | `GET /worksheets?grades=-1,0,1` | | 按年龄段 | `GET /worksheets?ageBand=3-4` | | 按单个年龄 | `GET /worksheets?age=5` | | 按 Tag | `GET /worksheets?tag=描红` | | 最新排序 | `GET /worksheets?sort=latest` | | 热门排序 | `GET /worksheets?sort=hot` | | 人工运营热门标记 | `GET /worksheets?isHot=true` | | 新品标记 | `GET /worksheets?isNew=true` | | 组合筛选 | `GET /worksheets?categoryId=math&ageBand=3-4&tag=数学&sort=hot&pageSize=20` | | 详情 | `GET /worksheets/number-find` | 中文 Tag 由 HTTP 客户端正常 URL 编码。查询参数与实际路由已加入开发 Swagger:`http://127.0.0.1:8800/api-docs`。 ## 筛选与排序规则 - 不同维度的条件按 AND 组合;多年级内部任一匹配。分类按原 ID 精确匹配,不自动包含子分类。 - 年级只使用审核后的 `worksheet_grades`,不读取旧 `worksheets.grade` 兜底。支持 -4~6,0 表示幼小衔接。`grade` 和逗号分隔的 `grades` 互斥;多重匹配不重复返回题型。 - `ageBand` 支持 3-4、4-5、5-6、6-7、7-8;题型适用范围须完整覆盖所选年龄段,即 ageMin ≤ 下限且 ageMax ≥ 上限。例如 3~8 岁题型可命中 3-4、4-5、5-6、6-7、7-8;3~5 岁题型不能命中 5-6,仅边界相交不足以匹配。公开列表与管理员列表共用此规则。 - `age` 是单年龄范围查询,要求 ageMin ≤ age ≤ ageMax,与 ageBand 互斥。年龄范围为非负 smallint。 - Tag 为字符串数组成员的精确匹配,区分大小写,不进行标题搜索、模糊匹配或推荐补齐。 - 默认排序:sortOrder 升序 → updatedAt 降序 → id 升序。 - 最新排序:firstPublishedAt 降序;旧数据未知时按 createdAt 降序 → id 升序。不使用会被计数变化刷新的 updatedAt 充当发布时间,也不改写原日期。 - 热门排序:downloads + downloadsSeed 降序 → likes + likesSeed 降序 → updatedAt 降序 → id 升序。数据库在完整筛选集排序后分页,不先取 100 条再排序,统计相加不会发生 bigint 溢出。 - `isHot` 独立筛选人工运营标记,不等同于下载热度排序,支持 true/false。 - `isNew` 依据真实 firstPublishedAt 及 `WORKSHEET_NEW_WINDOW_DAYS` 判断,默认 30 天;时间未知、未来或窗口外均为 false,旧 is_new 不作为新接口判断依据。最新排序与“仅新品”是两种查询,可分别使用。 ## 分页及公开响应 page 默认 1,范围 1~1000000;pageSize 默认 20,范围 1~100。超过 100 条可继续翻页,没有旧云函数固定截断 100 条的问题。超过最后一页仍返回正确 total、空 items 和 hasNext=false。 ```json { "code": 0, "message": "ok", "data": { "items": [ { "id": "number-find", "title": "找数字,涂一涂", "subtitle": "找出目标数字并涂色", "category": "math", "subcategory": "math-draw", "path": "/mathPages/mathDraw/mathDraw?id=number-find", "ageMin": 3, "ageMax": 5, "grades": [], "difficulty": 1, "previewImg": null, "tags": ["数学", "数字认知", "涂色"], "isNew": false, "isHot": false, "sortOrder": 101, "downloads": "335", "likes": "24", "createdAt": "2026-04-29T05:06:22.720Z", "updatedAt": "2026-10-15T07:17:05.132Z", "contentUpdatedAt": null, "firstPublishedAt": null } ], "total": 1, "page": 1, "pageSize": 20, "hasNext": false } } ``` 上述为响应结构示例,非默认第一页结果。downloads/likes 均为**含对应 seed 的展示值**,采用十进制字符串,避免 64 位计数在 JSON/JavaScript 中丢失精度。数据库原值不变;前端不要再次叠加 seed,不应把这些字段当作原始管理统计。 详情响应同样为 `{code:0,message:"ok",data:题型对象}`。不存在和未发布的题型均返回 404。公开白名单不含旧 grade、生成器配置、rendererKey、内部资产 ID、原 cloud:// 引用或对象存储 key。 列表与 total 在同一 Repeatable Read 事务、同一条件和时间点下读取,避免查询期间并发修改导致当次分页总量与内容使用不同快照。跨请求翻页仍可能受到新的业务写入影响,不承诺长时间固定快照。 ## 预览图与配置 已按用户确认采用“返回题型文字/元数据,未就绪预览留空”的方式。预览返回 URL 前,必须同时满足资产 ready、public、purpose=preview、无私有用户归属、图片 MIME。 - 旧源引用、pending/failed/deleting/deleted、私有文件或非图片均返回 previewImg=null。 - 七牛公共资产:配置 `QINIU_PUBLIC_DOMAIN` 为 HTTPS origin,按对象 key 逐段编码;缺域名不猜地址、不返回 key。 - 经核实的 external 资产:仅输出无凭据、无查询参数或 fragment 的稳定 HTTPS URL;不输出带签名的临时地址。 `QINIU_PUBLIC_DOMAIN` 只用于只读地址解析,不代表上传、私有签名或文件迁移已经接入。新配置均在 `apps/server/.env.example`;实际密钥不属于查询接口范围。 ## 当前 51 条数据的结果 2026-10-16 只读联调并逐项对照原附件: | 查询 | total | | ---------------------------------------- | ----- | | 全部 | 51 | | math | 22 | | english | 7 | | pinyin | 5 | | chinese | 6 | | puzzle | 11 | | ageBand=3-4 | 10 | | tag=字母 | 7 | | categoryId=math & ageBand=3-4 & tag=数学 | 5 | 热门前 3:word-coloring-grid(774)、number-find(335)、number-decompose(325);最新前 3:clock-connect、clock-reading、word-test-sheet。 当前 51 张预览全部 pending,因此 previewImg=null;所有真实首次发布时间未知,isNew=false;审核后的年级关系尚为空,所以带 grade/grades 的查询暂时没有结果。这些条件不会用旧 grade/is_new 或临时图片地址自动补齐。年级筛选功能已经用独立样本验证,后续审核并写入关系即可命中。 已通过 `pnpm check`、`pnpm test:db`,临时库覆盖公开状态隔离、组合筛选、多年级去重、Tag 精确匹配及注入文本、false/0/重复参数校验、121 条完整分页、稳定并列顺序、bigint 展示/排序、真实新品、预览访问策略和 Swagger 参数声明。开发库实测只读,未修改题型、年级、资产或审计。 ## 发布控制补充(2026-10-16) 公开列表、total、分页与详情统一过滤整条所属关系,不能通过热门、分类之外的查询或已知 ID 绕过。分类下架后其题型不再公开,恢复分类不改写题型自身状态。管理员通过 /admin/worksheets 查询草稿或下架内容;/admin/worksheet-resources 提供资源管理预览。状态更新、首发时间、页面快照失效与审计同事务。接口与客户端切换边界见 [内容发布与可见性](./内容发布与可见性.md)。 ## 年龄段匹配调整(2026-10-16) 用户确认将普通题型查询从“最小年龄等于年龄段下限”改为“适用范围完整覆盖所选年龄段”。本次仅修改 ageBand 匹配语义,不修改 grade/grades、age/ageBand 参数格式,不改数据库或小程序云函数。旧分龄推荐接口尚未迁移,其匹配和榜单规则不属于本次普通列表变更。 ### 独立作业纸绘制摘要(2026-10-23) 已登记的 `paper-tian`、`paper-mi` 等十个独立条目在配置匹配时额外返回可选 `rendering`,例如 `{"rendererKey":"paper-sheet","paperType":"mi"}`。这是服务端校验后的公开参数摘要,不包含原始 `renderConfig` 或其他生成器内部字段。未知 renderer、未登记条目或错配配置不返回摘要;新客户端遇到独立纸张摘要缺失或不匹配时提示不可用,不回退生成其他纸张。旧综合入口 `paper-sheet` 的响应保持兼容。