feat(learning): replace courses with project catalog

This commit is contained in:
2026-08-20 00:08:04 +08:00
parent 2cb8a7aef4
commit 38db1589e2
56 changed files with 1741 additions and 8725 deletions

View File

@@ -0,0 +1,136 @@
# 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` | 必填1200 字符 |
| 摘要 `summary` | 必填12,000 字符,用于卡片 |
| 封面 `cover` | 必填PNG/JPEG/WebP最大 10 MiB服务端读取真实 MIME、尺寸并生成受控媒体 |
| 项目压缩包 `archive` | 必填 ZIP最大 512 MiB服务端流式计算字节数与 SHA-256并验证 ZIP 签名 |
| README `readme` | 必填 `.md`UTF-8最大 500 KiB不接受可执行 HTML 作为发布内容 |
| 标签 `tags` | 016 个,每项 164 字符,去重 |
| 版本 `version` | 可空,最大 64 字符,仅作展示 |
| 排序 `sort_order` | 有界整数;列表默认按运营排序,再按发布时间稳定排序 |
| 状态 `status` | `draft``published``archived` |
上传中的对象不能直接进入公开目录。发布必须在一个事务/发布代际中冻结元数据、README、封面、镜像图片和 ZIP 摘要;任一校验失败则整个发布失败,旧的已发布版本继续可读。下架后列表和详情立即不可见,但已有审计记录不能物理删除。
后台必须记录操作人、时间、发布代际、变更摘要、归档 SHA-256/字节数和远程图片抓取结果。客户端不提供任何运营上传或发布入口。
## 3. README 远程图片发布规则
README 中的远程图片在“发布”时由服务端解析 Markdown AST 并镜像,客户端不直接使用原始远程图片 URL。处理要求
1. 只接受无用户名/密码的 HTTPS URL拒绝 `http:``data:``file:`、本地路径和协议相对地址。
2. 每一跳重新解析 DNS并拒绝 loopback、私网、链路本地、保留地址、云元数据地址和非公网目标最多 5 次重定向。
3. 单图最大 10 MiB同时限制超时、并发数和 README 图片总量;响应必须是实际可解码的 PNG/JPEG/WebP/GIF/AVIF拒绝 SVG、HTML、XML 和 MIME 欺骗。
4. 将通过校验的字节写入受控对象存储,以内容摘要去重;发布记录引用不可变对象。
5. 把 Markdown 图片地址改写为 `/api/learning/projects/:projectId/media/:mediaId`。详情接口只返回改写后的 Markdown不返回原始远程 URL或对象 key。
6. 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` 为 148默认 24`cursor` 是不透明游标。空列表返回 `200`,不要用 `404`。列表不返回 `readmeMarkdown``archiveSha256``archiveFileName` 或任何存储字段。
### 列表响应
```json
{
"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
}
}
```
### 详情响应
详情复用全部列表字段,并增加:
```json
{
"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![接线图](/api/learning/projects/robot-arm/media/wiring)",
"archiveFileName": "makelore-robot-arm-1.2.0.zip",
"archiveSha256": "64位小写十六进制SHA-256"
}
}
```
TypeScript 权威字段定义位于 [`shared/learning.ts`](../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 接口不在新客户端兼容范围内,可按服务端消费者盘点结果独立退役。