GDPR 模块管理同意历史、数据主体访问请求(SAR)和擦除请求。它已经具备双 ORM 持久化、30 天擦除冷静期、租户谓词和 Identity 用户匿名化,但还不是完整的商业合规编排平台。
1. 当前端到端路径
所有 [GenerateEndpoint] 路由默认 RequireAuthorization()。实现了 IAuthorizedRequest 的命令还会进入框架授权决策;普通查询和 RecordConsent 只受 HTTP 认证保护。
2. 当前能力矩阵
| 能力 | 当前实现 | 不能推断出的保证 |
|---|---|---|
| 同意授权 | 追加 SysConsent 历史行 | 没有目的目录、文本快照、唯一活动同意或下游传播 |
| 同意撤回 | 本人或角色名 admin 设置 RevokedAt | 没有撤回事件和处理方确认 |
| SAR 创建 | 租户内按 RequestId 先查后插入 | 没有数据库唯一约束,并发不保证幂等 |
| SAR 处理 | 保存 UserId 与最多 1000 条审计项 | 没有业务模块收集、文件、下载、加密或过期 |
| 擦除创建 | 创建 30 天 CoolingOff 请求 | 没有身份再验证、审批或法律保全 |
| 擦除执行 | 匿名化并软删除当前租户的 SysUser 行 | 不覆盖凭据、会话、业务模块和外部处理方 |
| 持久化 | ORM 中立 IEntitySet<T>,SqlSugar/EF Core 契约测试 | 不等于完整工作流语义已验证 |
| 活动审计 | 通用 Activity 管道记录命令结果 | 是 best-effort,不是不可变合规证据账本 |
3. HTTP 用例与实际授权
| 方法与路由 | 用例 | 额外授权决策 |
|---|---|---|
POST /api/privacy/consents | RecordConsent | 无 IAuthorizedRequest |
GET /api/privacy/consents | GetConsents | 无 IAuthorizedRequest |
DELETE /api/privacy/consents/{id} | RevokeConsent | gdpr.own-data.delete |
POST /api/privacy/sar | CreateSar | gdpr.own-data.create |
GET /api/privacy/sar | GetSarRequests | 无 IAuthorizedRequest |
POST /api/privacy/sar/{id}/process | ProcessSar | privacy.sar.update |
POST /api/privacy/erasure | CreateErasure | gdpr.own-data.create |
DELETE /api/privacy/erasure/{id} | CancelErasure | gdpr.own-data.delete |
POST /api/privacy/erasure/{id}/execute | ExecuteErasure | privacy.erasure.delete |
表中的决策码由 ResourceDescriptor + AuthorizationAction 推导。源码目录声明的是 privacy.consent.view、privacy.consent.manage、privacy.sar.manage 和 privacy.erasure.manage,两组并不一致。这不是命名偏好,而是可能导致权限无法分配或请求始终不获 Allow 的交付缺口。
4. 两个状态机,共用一张表
这是业务意图,不是当前强约束。两个 SmartEnum 都以整数保存到 StatusCode,而按 ID 读取和处理器没有验证 RequestType。错误端点拿到另一类请求时,同一个整数会被解释成另一套状态,必须在生产启用前修复并加入数据库/应用层防线。
5. 租户数据面
GdprStore 使用 ICurrentTenant.Tenant.EffectiveTenantId 作为最终谓词,这是正确的存储防线。应用处理器却普遍把 currentUser.User.TenantId 作为查询参数和请求上下文:
var actorTenant = currentUser.User.TenantId;
// Store 同时要求调用方租户和当前有效租户相等。var rows = await store.GetConsentsByUserAsync( actorTenant, currentUser.User.UserId?.ToString() ?? "0", cancellationToken);
// operate-as 时 actorTenant 与 EffectiveTenantId 可能不同,因此结果为空;// 创建路径又会以 EffectiveTenantId 落库,形成调用上下文分歧。普通用户请求下两者通常一致;平台代操作、后台任务和租户模拟必须单独测试,不能据此宣称全场景租户正确。
6. 持久化与组合
模块拥有两张软删除租户表:
SysConsent:同意目的、版本、授予/撤回时间,以及当前没有写入的 IP、User-Agent 字段。SysDataSubjectRequest:请求类型、状态、冷静期、拒绝原因、DetailsJson和预留ExportFileId。
GdprStore 不依赖 SqlSugar 或 EF Core,而依赖 IEntitySet<T>。生产配置满足数据库与 RabbitMQ 前置条件时,生成式注册选择当前 ORM Adapter;Shell 模式保留显式 Unavailable Port,调用时失败关闭。
7. 当前最重要的交付缺口
- SAR 与 Erasure 的处理器不校验请求类型,状态整数可以被交叉解释。
RequestId查询不包含请求类型,表上也没有唯一索引;跨类型复用和并发创建都可能出错。- SAR 的
Failed更新随后返回Result.Failure,会被事务管道回滚,失败状态并不会持久化。 ExportFileId只有字段和测试赋值,业务代码从未生成或交付文件。- 擦除只处理
SysUser的部分字段,不会编排其他数据归属模块或外部处理方。 - 权限目录、Feature
privacy.operations与实际请求决策没有闭环证据。 - 没有主体身份再验证、法律保全、截止日期、升级通知、下载授权和逐步骤不可变证据。
8. 推荐阅读路径
- 同意与证据:授权、撤回、版本和证据字段。
- SAR 与导出:当前数据内容、分页、失败语义和目标收集器。
- 擦除工作流:冷静期、Identity 匿名化和归属模块编排。
- 存储、安全与权限:表结构、租户、权限和事务。
- 测试与运营:契约测试、指标、排障和 GA 门禁。
关联基础章节:Auditing、Identity、Multitenancy 和 Authorization。
9. 最小源码审查
# 列出全部 GDPR 用例和路由。rg -n "GenerateEndpoint|class Handler" src/Platform/Gdpr -g '*.cs'
# 找出声明后没有形成业务写入的导出字段;生产代码应只有读取/映射命中。rg -n "ExportFileId|DetailsJson" src/Platform/Gdpr tests -g '*.cs'
# 检查权限资源是否仍混用 gdpr 与 privacy。rg -n 'new\("gdpr"|GdprPermissions\.' src/Platform/Gdpr -g '*.cs'