Skip to content
bitzorcas
中EN

Reference

DocumentStructure 收藏、回收站与对象安全

深入说明用户收藏关系、唯一与软删除、悬空引用、owner 资源授权,以及 Documents 回收站查询、恢复、Purge 和级联安全边界。

Last updated

收藏和回收站都引用 Documents 资源,却有完全不同的风险:收藏是用户快捷关系,不能授予访问;Purge 是不可逆高风险操作,必须由 owner 的对象策略、保留和审计共同保护。

1. 收藏数据模型

DocsFavorite 保存 TenantId、UserId、TargetType、TargetId、GroupName,支持软删除。唯一索引覆盖 (TenantId, UserId, TargetType, TargetId),同一用户不能把同一目标收藏两次;GroupName 不在唯一键中,因此不能用分组复制收藏。

当前未回源

Current user

DocsFavorite relation

TargetType + TargetId

Documents / other owner

existence + current authorization

收藏表不建立跨 bounded context 导航,方向正确;但应用必须有 owner resolver 才能把弱引用解析为安全视图。

2. 新增收藏

新增一个文档收藏
// 关系中的目标类型和标识来自调用方,当前没有回源验证。
var result = await mediator.Send(new ManageFavorites.AddCommand(
TargetType: "Document",
TargetId: "doc-100",
GroupName: "本周处理"), cancellationToken);
if (result.IsFailure)
return result.Error;
// 成功只代表关系已保存,不代表 doc-100 存在或调用者可读取。
return result.Value!;

Handler 只检查 TargetType/TargetId 非空。它不限制 Document/Category,不验证 owner,也使用 UserId?.ToString() ?? "0"。HTTP 认证不能替代应用用例的 RequireUserId。

3. 重复收藏

应用层没有先查或 upsert,重复依赖数据库唯一索引失败。软删除后的相同自然键能否重新新增取决于 ORM 的软删除过滤与唯一索引是否包含 IsDeleted;当前唯一键不包含 IsDeleted,数据库通常仍会阻止新增。

GA 应定义:重复 active 收藏返回原项;已软删项应恢复原行还是允许新行;并发重复必须在两 ORM 上得到同一结果。

若选择恢复旧行,应更新 GroupName、恢复时间和操作者,并保留原始创建审计;若选择新行,唯一索引必须显式纳入活动状态或使用过滤索引。两种 ORM 和支持的数据库需要给出一致迁移策略。

4. 收藏列表

列表按 TenantId、UserId、可选 GroupName 查询,按 CreateTime 倒序。没有分页;收藏很多时会一次加载全部。没有 owner 批量解析,因此返回只有类型/ID/分组/时间,且包含目标已删除或用户已失权的关系。

安全 API 可以返回 ResolvedFavorite:对支持类型批量回源,当前无权项不泄漏详情,并按策略标记 Unavailable 或清理。不能由前端拿 ID 后绕过授权直接请求目标。

5. 删除本人收藏

Remove 先在当前有效租户按主键读取,再比较 favorite.UserId 与当前用户,最后软删。这里对象所有者检查是明确的,但仍使用 UserId=0 fallback。并发重复删除第二次可能得到 NotFound,尚未定义幂等删除语义。

删除收藏并处理稳定错误
var removed = await mediator.Send(
new ManageFavorites.RemoveCommand(favoriteId),
cancellationToken);
// 非 owner 返回 DocsErrors.Favorite.NotOwner;不存在返回 NotFound。
if (removed.IsFailure)
return removed.Error;
return Result.Success();

6. 收藏权限目录缺口

DocumentStructurePermissions 声明模板与回收站权限,没有 FavoriteView/Create/Delete。请求没有显式 Resource,依赖类型约定推导。即使路由要求认证,也要确认授权 Pipeline 实际使用哪个 resource/action,避免所有认证用户默认通过或命中错误权限。

7. 回收站端口归属

端口位于 DocumentStructure Application,Store 位于 Documents Infrastructure,操作 Documents 的软删除记录。这种 adapter 允许协调层不直接访问 Documents 表,但端口本身仍需表达 owner 语义,而不仅是 tenantId + id。

8. 查询回收站

Query 接收 KnowledgeBaseId、PageIndex、PageSize,Handler 原样传入 Store。当前 Application 不规范化分页、不确认 KB 存在/授权。Store 必须同时查询软删 Document/Category 并按时间形成统一页;分页合并语义要从实现和测试固定。

查询知识库回收站
GET /api/v1/document-structures/recycle-bin?knowledgeBaseId=kb-100&pageIndex=1&pageSize=20
Authorization: Bearer <token>

返回含 Name、ParentId、DeletedBy,属于敏感元数据;跨 KB 或无权用户必须在 Store 调用前被拒绝,不能只依赖 tenant filter。

9. Restore

ItemType 精确区分 Document/Category,其他值返回 InvalidItemType。Handler 不读取对象、不确认已删除、不返回恢复数量。分类恢复被端口注释为递归恢复子分类和文档,但父级是否恢复、名称冲突如何处理、附件/版本/索引是否恢复要看 Documents Store。

重复 Restore 当前可能无操作仍返回成功;需要定义幂等语义和审计。恢复到已删除父节点或已被占用的名称需要明确策略。

10. Purge

Purge 同样按类型分派。它是永久删除,不应仅凭普通 Delete action 和一个 ID 执行。最低要求包括保留期、法律留存、二次确认/强认证、理由、审批或高权限、影响预览、不可抵赖审计和后台受控执行。

分类级联可能删除大量子分类与文档;同步 HTTP 请求容易超时并形成大事务。商业系统通常先创建 PurgeJob,扫描影响、锁定范围、分批执行、产生报告并允许运维恢复未完成任务。

11. 跨模块副作用

Document/Category 恢复或 Purge 还影响 Files、Search、Comments、Chat 附件引用、Guidance/RAG 索引和收藏。当前端口只返回 Task,无受影响 ID、事件或报告,协调层无法证明所有消费者同步。

应由 Documents owner 发布版本化事实,经 Outbox 让索引/引用消费者幂等处理。Purge 不应直接从 DocumentStructure 调用多个模块实现。

事件至少携带 owner 资源标识、租户、操作、发生时间、schema version、correlation/causation 和受影响范围摘要。消费者仍需按自身数据边界执行删除或重建,不能把事件当作绕过权限的查询凭证。

12. 安全矩阵

场景GA 预期
收藏不存在目标Validation/NotFound,不写关系
收藏后来失权列表不泄漏详情
跨租户 FavoriteIdNotFound
删除他人收藏Forbidden
查询无权 KB 回收站Forbidden/NotFound
用 Category ID 冒充 Document类型/owner 校验拒绝
保留期内 PurgePolicy denied
Legal Hold 对象 Purge强制拒绝并审计
并发 Restore/Purge单一稳定终态

13. 运维指标

收藏:add/conflict/restore/remove、unresolved/denied target、list size。回收站:items by age/type、restore/purge count、cascade size、duration、failure、legal-hold denial、event lag。日志不记录文档正文,只记录 tenant、KB、item、operation、actor、reason、correlation。

审计保留期应长于可逆恢复窗口,并把普通 Restore 与永久 Purge 分开检索。Purge 报告需要记录策略判定、影响计数和失败批次,但不能复制正文或敏感附件元数据。

14. 降级与应急

owner resolver 不可用时,收藏列表应返回关系不可解析或整体失败,不能沿用缓存详情冒充当前授权。回收站 Store、保留策略或审计不可用时,Restore/Purge 必须失败关闭,尤其不能把 Purge 排入无法追踪的本地后台任务。

应急解锁需要双人审批、限时授权和独立审计;它只能恢复运维能力,不能跳过 Legal Hold 或跨租户边界。

15. 检查命令

Terminal window
# 收藏当前输入、owner 与唯一边界。
rg -n "AddFavoriteAsync|ListFavoritesAsync|NotOwner|UX_DocsFavorite" \
src/Platform/DocumentStructure -g '*.cs'
# 回收站端口与 Documents 实现位置。
rg -n "IDocumentRecycleBinStore|DocumentRecycleBinStore|RestoreCategoryAsync|PurgeCategoryAsync" \
src/Platform/DocumentStructure src/Platform/Documents -g '*.cs'

DocumentStructure 总览 · 模板应用与预览 · 测试与 GA

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%