docs(api): 补附件契约、Markdown 存储理由与富文本路由
CI / backend (push) Canceled after 0s

This commit is contained in:
dadao
2026-10-29 01:53:47 +08:00
parent a24a493403
commit 800c2726e2
3 changed files with 85 additions and 2 deletions
+52 -1
View File
@@ -389,7 +389,58 @@ Query:`doc_type`(默认 `postmortem`)。来源可以是任务或工单;
---
## 6. 健康与版本
## 6. 附件与富文本
### 6.1 存储格式:Markdown + 附件表
正文类字段(任务 `body_md`、工单说明、知识正文、评论、验收结论)**一律存 Markdown**,不存 HTML。
理由:能导出、能全文检索、能直接喂给 LLM;HTML 每次渲染都要重新清洗一遍,是隐患。
图片不是 base64 塞进正文,而是走附件接口拿一个稳定地址:
```markdown
![票据截图.png](/api/v1/workspaces/{ws_id}/attachments/{attachment_id}/raw?token=...)
```
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/workspaces/{ws_id}/items/{item_id}/attachments` | multipart,字段名 `file`;返回 201 + 附件对象 |
| GET | `/workspaces/{ws_id}/items/{item_id}/attachments` | `{items: [...]}` |
| GET | `/workspaces/{ws_id}/attachments/{id}/download` | 下载(`Content-Disposition: attachment`) |
| GET | `/workspaces/{ws_id}/attachments/{id}/raw?token=<access>` | 直链,给 `<img src>` 用 |
| DELETE | `/workspaces/{ws_id}/attachments/{id}?confirm=true` | 上传者或管理员可删;不传 confirm 报 `8004` |
附件对象的字段:`id / item_id / filename / content_type / size / sha256 / uploader_id / created_at / can_delete`。
事项与知识的列表接口会附带 `attachment_count`,详情接口附带 `attachments` 全量。
### 6.2 为什么图片直链要带 token
`<img>` 发不出 `Authorization` 头。两种解法:把图片读成 base64 内联(正文会膨胀几十倍,且破坏检索),
或者给一个生命周期短的签名地址。
当前实现取的是"query 里带 access token",**并且只允许本工作区成员访问**(非成员一律 `8001`,
不区分"不存在"与"没权限",避免探测他人附件)。这是本地/内网阶段的折中,上生产前应换成
**带过期时间的一次性签名 URL**,见 `ROADMAP` 的 M7 安全清单。
### 6.3 附件错误码
| 码 | HTTP | 含义 |
|---|---|---|
| 8001 | 404 | 附件不存在,或不属于这个工作区 |
| 8002 | 413 | 超过 `MAX_UPLOAD_MB`(默认 20) |
| 8003 | 403 | 不是上传者也不是管理员 |
| 8004 | 409 | 需要 `?confirm=true`(删除是破坏性动作) |
### 6.4 前端约定
- 编辑器写 Markdown,工具栏/快捷键 `Ctrl+B`、`Ctrl+I` 只管插入语法;预览用 `renderMarkdown()`
渲染(先转义再上标签,链接协议白名单 `http(s)/mailto//`)。
- 粘贴或拖入图片时:先上传 → 拿到 `id` → 在光标处插入 `![文件名](attachmentUrl(...))`。
- 上传接口自己带 `Idempotency-Key`,**不要**手动设 `Content-Type`(浏览器要自己带 boundary)。
---
## 7. 健康与版本
| 方法 | 路径 | 说明 |
|---|---|---|