Files
shanxia2.0/docs/superpowers/specs/2026-11-12-doclib-batch-share-design.md
T
wuzilongandCursor bf89a1f301 docs: 记录文档库多选批量分享设计(#25)。
明确整份覆盖、交集预选、batch-shares 事务与 Web 弹窗扩展范围,供实现对照。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-12 17:36:51 +08:00

116 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 文档库多选批量设置分享(工单 #25)
工单:https://gitea.aichengyan.com/cykj/shanxia2.0/issues/25
## 背景与问题
Web 文档库管理(`/#/doclib/enter`)工具栏「设置分享」当前仅在恰好选中 1 份且可管理的文档时可用(`actionsFor.share`),弹窗与接口均为单文档。用户需要多选文档后统一设置分享人员。
## 已确认决策
| 项 | 结论 |
| --- | --- |
| 保存语义 | 整份覆盖:弹窗所选 `userIds` 写入每一份所选文档,替换原分享人;空列表表示全部收回 |
| 弹窗初始勾选 | 所选文档现有分享人的交集;无共同人员则为空 |
| 修改端 | Web 管理端 + 后端批量 API |
| 失败策略 | 整批事务:全部成功或全部回滚 |
| 实现方案 | 新增 `batch-shares` + 扩展现有 `ShareDialog`(方案 A) |
## 范围
**包含**
- `web-admin`:`doclib/enter` 列表工具栏、`ShareDialog`、`actionsFor`、API 封装与单测
- `backend`:批量分享路由、schema、服务层事务与单文件写入逻辑复用
**不包含**
- 小程序 / 移动 H5
- 「只追加不收回」、部分成功返回清单
- 文件夹级分享
## 后端设计
### 接口
`POST /web/doclib/files/batch-shares`
请求体(`extra=forbid`):
```json
{
"ids": ["file-id-1", "file-id-2"],
"userIds": ["user-id-a", "user-id-b"]
}
```
约束:
- `ids`:`min_length=1`,`max_length=100`(对齐 `DeleteFilesBody`)
- `userIds`:`max_length=500`(对齐 `ShareBody`)
- 去重后的 `ids` 均须当前用户可管理;任一份不可管理则 4xx,整批不写
- 人员须同租户且启用;自动排除各文档上传人(与现 `set_shares` 一致:`requested - {create_by}`)
- 非法 / 停用 / 跨租户人员 → 400,整批回滚
响应:成功后返回更新后的文档列表(字段与现有单文档分享成功返回一致的序列化)。
### 事务与复用
- 现有 `DoclibService.set_shares` 在单文件路径末尾 `commit`。批量路径须在**全部文件写入完成前不 commit**。
- 抽取内部方法(名称示例:`_apply_shares(file_id, user_ids)`)完成单文件差量更新(含 lock);`set_shares` = `_apply_shares` + `commit` + 返回序列化;`set_shares_batch` = 校验全部可管理 → 校验人员 → 对每个 id 调用 `_apply_shares` → 一次 `commit` → 返回列表。
- 任一步抛错依赖会话回滚,不留下部分文档已改的状态。
路由注册位置:与现有 `PUT /web/doclib/files/{file_id}/shares`、`POST .../batch-delete` 同模块(`backend/app/api/doclib.py`)。
## 前端设计
### 工具栏与权限
- `actionsFor.share`:`rows.length > 0 && rows.every((row) => row.canManage)`(与删除多选门槛一致,取消「仅 1 条」限制)。
- 工具栏「设置分享」对 `selected` 全量打开弹窗,不再只取 `selected[0]`。
- 详情抽屉单文档「设置分享」仍传入单元素数组或单文档,行为不变。
### ShareDialog
- Props 改为支持多文档(例如 `documents: DoclibDocument[]`);打开时:
- 摘要列出所选文档名称(数量较多时可截断并注明总数)
- `userIds` 初始 = 分享人 id **交集**
- 候选人员:交集中的人员 ∪ 远程搜索结果;排除各文档上传人(多文档时排除所有相关 `ownerId`)
- 保存:统一调用批量接口 `saveDoclibBatchShares(ids, userIds)`(单文档也走批量,少分支);成功提示后关闭并刷新列表 / 更新详情。
### 纯函数(便于单测)
- `shareUserIntersection(documents): string[]` — 交集;空文档列表返回 `[]`
- 可选:`canShareSelection(rows)` 与 `actionsFor` 共用语义
### API
在 `web-admin/src/api/doclib.ts` 增加:
`saveDoclibBatchShares(ids: string[], userIds: string[])` → `POST .../files/batch-shares`
单文档 `saveDoclibShares` 可保留供兼容/测试,页面路径以批量为准。
## UI 规范
- 沿用现有 `doclib-dialog dialog-form-layout`、Fresh Mint、P12 类弹窗;不新造布局与主色。
- 按钮图标规则与现页一致(Close / Check 等已有用法)。
## 验收
1. 多选可管理文档后可打开「设置分享」;含不可管理项时按钮禁用。
2. 打开时勾选为交集;无共同分享人时为空。
3. 保存后所选文档分享人员均等于弹窗所选集合(含清空)。
4. 故意混入无权限文档(若后端被直接调用)→ 整批不改。
5. 详情页单文档分享仍可用。
6. 单测覆盖交集函数与 `actionsFor.share`;后端/服务层覆盖批量成功与权限失败回滚(按项目既有测试方式)。
## 风险
- `set_shares` 重构时须保证单文档路径回归(commit 时机、上传人排除、软删恢复历史 share 行)。
- 批量上限 100 与删除一致;超大选区依赖现有表格多选,不另做分片 UI。
## 状态
设计已获用户确认(会话中「按照推荐」方案 A,「OK,请继续」批准落盘)。待用户审阅本文件后进入实现计划。