7.3 KiB
AI 学习项目目录服务端协作契约
状态:Makelore 客户端与 Electron Main 已按本契约实现;运营后台、数据库、对象存储与生产数据不在本仓库内,需在 Works Square 侧实现并部署后联调。
1. 产品与权限边界
AI 学习是登录后可用的精选项目目录。V1 只包含项目列表、README 详情和 ZIP 下载,不包含课程生成、课程播放器、本地课程库、学习进度、Agent、ASR、评分或课堂 runtime,也不兼容旧课程接口。
沿用现有账号与 module_access.learning 策略:客户端入口策略在页面初始化前拦截无权限账号,Works Square 的列表、详情、媒体和下载接口仍必须独立校验登录态及 Learning 权限,不能依赖客户端置灰。
Renderer 的固定链路为 Renderer -> Host API -> Electron Main -> Works Square。Renderer 不得获得 Works Token、对象存储凭据、内部存储 key、任意下载 URL或本地保存路径。
2. 运营后台“学习项目管理”
运营后台新增一级菜单“学习项目管理”,至少支持新建、编辑、预览、发布、下架、排序和查看发布记录。项目字段如下:
| 字段 | 规则 |
|---|---|
项目名称 name |
必填,1–200 字符 |
摘要 summary |
必填,1–2,000 字符,用于卡片 |
封面 cover |
必填,PNG/JPEG/WebP,最大 10 MiB;服务端读取真实 MIME、尺寸并生成受控媒体 |
项目压缩包 archive |
必填 ZIP,最大 512 MiB;服务端流式计算字节数与 SHA-256,并验证 ZIP 签名 |
README readme |
必填 .md,UTF-8,最大 500 KiB;不接受可执行 HTML 作为发布内容 |
标签 tags |
0–16 个,每项 1–64 字符,去重 |
版本 version |
可空,最大 64 字符,仅作展示 |
排序 sort_order |
有界整数;列表默认按运营排序,再按发布时间稳定排序 |
状态 status |
draft、published、archived |
上传中的对象不能直接进入公开目录。发布必须在一个事务/发布代际中冻结元数据、README、封面、镜像图片和 ZIP 摘要;任一校验失败则整个发布失败,旧的已发布版本继续可读。下架后列表和详情立即不可见,但已有审计记录不能物理删除。
后台必须记录操作人、时间、发布代际、变更摘要、归档 SHA-256/字节数和远程图片抓取结果。客户端不提供任何运营上传或发布入口。
3. README 远程图片发布规则
README 中的远程图片在“发布”时由服务端解析 Markdown AST 并镜像,客户端不直接使用原始远程图片 URL。处理要求:
- 只接受无用户名/密码的 HTTPS URL;拒绝
http:、data:、file:、本地路径和协议相对地址。 - 每一跳重新解析 DNS,并拒绝 loopback、私网、链路本地、保留地址、云元数据地址和非公网目标;最多 5 次重定向。
- 单图最大 10 MiB,同时限制超时、并发数和 README 图片总量;响应必须是实际可解码的 PNG/JPEG/WebP/GIF/AVIF,拒绝 SVG、HTML、XML 和 MIME 欺骗。
- 将通过校验的字节写入受控对象存储,以内容摘要去重;发布记录引用不可变对象。
- 把 Markdown 图片地址改写为
/api/learning/projects/:projectId/media/:mediaId。详情接口只返回改写后的 Markdown,不返回原始远程 URL 或对象 key。 - Markdown 原始 HTML在客户端被禁用;服务端也应在预览与发布时提示被忽略的 HTML,避免运营误判展示效果。
封面同样优先返回固定媒体路径。若返回 HTTPS CDN 地址,该地址必须无凭据、由 Works Square 控制且不包含用户隐私。
4. 客户端公开接口
| 方法 | Works Square 路径 | 用途 |
|---|---|---|
GET |
/api/learning/projects?cursor=&limit= |
已发布项目分页列表 |
GET |
/api/learning/projects/:id |
项目详情和 README |
GET |
/api/learning/projects/:id/media/:mediaId |
受控封面/README raster 图片 |
GET |
/api/learning/projects/:id/archive |
ZIP 字节流或同 Works origin 重定向 |
limit 为 1–48,默认 24;cursor 是不透明游标。空列表返回 200,不要用 404。列表不返回 readmeMarkdown、archiveSha256、archiveFileName 或任何存储字段。
列表响应
{
"success": true,
"data": {
"items": [
{
"id": "robot-arm",
"name": "桌面机械臂",
"summary": "从零搭建一个可以抓取积木的桌面机械臂。",
"cover": {
"url": "/api/learning/projects/robot-arm/media/cover",
"alt": "桌面机械臂成品",
"width": 1600,
"height": 900
},
"tags": ["机器人", "Python"],
"version": "1.2.0",
"archiveBytes": 12582912,
"publishedAt": "2026-08-01T00:00:00Z",
"updatedAt": "2026-08-18T00:00:00Z"
}
],
"nextCursor": null,
"total": 1
}
}
详情响应
详情复用全部列表字段,并增加:
{
"success": true,
"data": {
"id": "robot-arm",
"name": "桌面机械臂",
"summary": "从零搭建一个可以抓取积木的桌面机械臂。",
"cover": {
"url": "/api/learning/projects/robot-arm/media/cover",
"alt": "桌面机械臂成品",
"width": 1600,
"height": 900
},
"tags": ["机器人", "Python"],
"version": "1.2.0",
"archiveBytes": 12582912,
"publishedAt": "2026-08-01T00:00:00Z",
"updatedAt": "2026-08-18T00:00:00Z",
"readmeMarkdown": "# 桌面机械臂\n\n",
"archiveFileName": "makelore-robot-arm-1.2.0.zip",
"archiveSha256": "64位小写十六进制SHA-256"
}
}
TypeScript 权威字段定义位于 shared/learning.ts。未列出的内部字段会被 Main 丢弃。
媒体与归档响应
- 媒体接口只返回受控 raster 内容,必须设置准确
Content-Type与Content-Length;Main 限制 10 MiB,并转换为 data URL给 Renderer。 - 归档接口返回
application/zip、application/x-zip-compressed或application/octet-stream,设置准确Content-Length。如需重定向,只能跳转到与 Works API 相同 origin 的 HTTP(S) 地址,最多 5 跳;Main 不向重定向目标转发 Bearer。 - 归档字节必须与详情中的
archiveBytes和archiveSha256精确一致。客户端验证失败时删除临时文件,不留下部分下载。
5. 错误、缓存与上线顺序
统一使用现有 { success, status, code, error } 错误封装。至少支持:
LEARNING_AUTH_REQUIRED(401);LEARNING_FORBIDDEN(403);LEARNING_PROJECT_NOT_FOUND(404);LEARNING_CONFLICT(409,发布代际变化);LEARNING_UNAVAILABLE(429/502/503)。
列表/详情可对发布代际生成 ETag;媒体和归档按内容摘要设置不可变缓存,但不得缓存带用户私有授权的响应到公共共享缓存。日志不得记录 Bearer、签名 URL、README 原始私有 URL或对象存储 key。
上线顺序:先部署数据库/对象存储、运营后台、远程图片镜像和四个公开接口,并用真实账号完成发布/下架/图片/ZIP 联调;再发布新客户端。旧 /api/learning/courses、generation/progress/runtime 接口不在新客户端兼容范围内,可按服务端消费者盘点结果独立退役。