Skip to content
bitzorcas
中EN

Guide

GDPR SAR 与数据导出

深入解释 SAR 创建、处理、审计查询、失败事务、导出交付缺口与目标收集器协议。

Last updated

数据主体访问请求(SAR)应回答“平台持有哪些与我有关的数据、来源、用途和接收方是什么,并如何安全交付”。当前实现是一条可演示的最小路径,还没有达到可交付导出包的完整度。

1. 路由与状态

路由调用者行为
POST /api/privacy/sar已认证用户 + gdpr.own-data.create按 RequestId 创建 Submitted
GET /api/privacy/sar已认证用户返回本人 SAR 列表
POST /api/privacy/sar/{id}/process拥有 privacy.sar.update拒绝或同步处理

GdprSarStatus 包含 Submitted(0)、Processing(1)、Completed(2)、Failed(3) 和 Rejected(4)。

2. 创建与幂等

CreateSar 只校验 RequestId 非空,然后按 TenantId + RequestId 查询;命中就返回旧记录,否则插入。

创建 SAR 的客户端调用
var idempotencyKey = Guid.NewGuid().ToString("N");
using var response = await http.PostAsJsonAsync(
"/api/privacy/sar",
new
{
// 网络重试必须复用这个值,而不是每次重新生成。
requestId = idempotencyKey
},
cancellationToken);
var summary = await response.Content
.ReadFromJsonAsync<DataSubjectRequestSummary>(cancellationToken);
// 业务数据库的唯一约束仍是并发幂等的最终防线。

当前表没有 TenantId + RequestId 唯一索引,两个并发请求仍可能同时通过查询并插入。查询也不包含 RequestType;如果相同 RequestId 已被 Erasure 使用,CreateSar 会返回那条 Erasure 记录。

3. 处理路径

SysDataSubjectRequestIAuditQueryPortIGdprStoreProcessSar.HandlerPrivacy operatorSysDataSubjectRequestIAuditQueryPortIGdprStoreProcessSar.HandlerPrivacy operatoralt[rejection reason supplied][process]id + optional rejection reasonGetDsrById(id)Rejected + reason + completed timeProcessingtenant + user + page 1 + size 1000AuditPageserialize UserId and audit itemsDetailsJson + Completed

处理是同步 HTTP Command,使用 StandardCommand 超时策略。没有后台队列、截止时间扫描、恢复游标或进度端点。

4. 当前导出内容

SarExportData 只有四个字段:

  • UserId;
  • ExportedAt;
  • Profile:实际只是 { UserId } 的 JSON;
  • AuditRecords:AuditPage Items 再序列化成字符串。

处理器固定请求 PageIndex: 1, PageSize: 1000,没有继续翻页。审计查询本身还存在多 Provider 全局分页和类别映射差异,详见 审计存储与查询。

DetailsJson 最终是“外层对象 + 两个 JSON 字符串”,调用者需要二次反序列化。它不是稳定、版本化的导出 Manifest。

5. 当前没有导出交付

虽然模型包含 ExportFileId,业务源码没有给它赋值。没有实现:

  • 调用 IFileStorage 或 Files 模块生成包;
  • 下载端点、一次性授权或下载次数限制;
  • 文件加密、密钥封装、校验和或病毒扫描;
  • 包过期与物理清理;
  • 导出完成通知;
  • 把 DetailsJson 返回给主体的详情端点。

因此 Completed 当前只表示 JSON 已写进工作流表,不表示数据主体已经获得可访问副本。

6. 失败状态与事务

ProcessSar 捕获非取消异常,设置 Failed 和 RejectionReason = ex.Message,然后返回 Result.Failure。框架事务管道对失败 Result 执行回滚,所以这次 Failed 更新也会回滚。

审计查询或序列化异常

内存对象设为 Failed
写 RejectionReason

返回 Result.Failure

TransactionPipeline Rollback

数据库仍保持处理前状态

原始 ex.Message 还可能包含连接、查询或数据细节。正确做法是提交脱敏失败证据和可重试状态,向 API 只返回稳定错误码。

7. 请求类型混用

GetDsrByIdAsync 只按 ID 和租户查询。ProcessSar 没有检查 entity.RequestType == SarRequestType。Erasure 的状态值 1 会被 SAR 解释为 Processing,随后可能被处理成 Completed 并写入 SAR Details。

必须先固定请求类型和允许迁移
if (request.RequestType != GdprRequestType.Sar)
{
// 不泄露另一类请求是否存在,返回稳定的类型不匹配错误。
return Result.Failure<SarSummary>(
GdprErrors.RequestTypeMismatch);
}
var transition = SarTransitions.TryStart(request.Status);
// 迁移守卫同时检查来源状态和并发版本。
if (transition.IsFailure)
return Result.Failure<SarSummary>(transition.Error);

以上为目标实现形状。当前源码没有 GdprRequestType 和集中状态迁移守卫。

8. 生产级收集器协议

每个数据归属模块应通过窄合同提供数据,而不是由 GDPR 直接查询对方数据库。

目标合同:可恢复的数据归属收集器
public interface IDataSubjectExportContributor
{
// OwnerCode 是 Manifest、指标和幂等键中的稳定所有者代码。
string OwnerCode { get; }
int SchemaVersion { get; }
// Cursor 由 Owner 解释;编排器只负责原样持久化和回传。
Task<ExportContribution> CollectAsync(
VerifiedSubject subject,
ExportCursor? cursor,
CancellationToken cancellationToken);
}
public sealed record ExportContribution(
IReadOnlyList<ExportArtifact> Artifacts,
ExportCursor? NextCursor,
string ContentHash,
bool Completed,
IReadOnlyList<RetentionNotice> OmittedData);

协议必须支持稳定游标、大小限制、版本、哈希、合法保留说明和重复执行。Contributor 只返回本模块拥有的数据。

9. 推荐导出流水线

主体再验证

创建 Job + Deadline

按 Owner 收集
游标 / Checkpoint

生成版本化 Manifest

加密打包 + Hash

Files 私有对象

一次性下载票据

到期销毁 + 证据

Manifest 应明确列出已收集 Owner、Schema 版本、文件哈希、被合法排除的数据、生成时间和完成状态;不能只凭一个 Completed 枚举判断完整。

10. 下载安全

生产交付至少需要:

  • 下载前进行近期身份再验证或 Step-up MFA;
  • 下载票据绑定 Tenant、Subject、FileId、过期时间和一次性消费;
  • 对象存储私有,不能把永久 URL 写入日志或通知;
  • 响应使用正确 Content-Type、Content-Disposition 和禁止缓存 Header;
  • 文件内容和元数据经过敏感数据扫描;
  • 下载与销毁都产生耐久证据。

11. 测试与审查命令

当前测试只验证 SAR 列表 Handler 和 Store 的双 ORM 读写,不覆盖 Create/Process、分页、文件或失败事务。

Terminal window
# 证明 SAR 当前只读取审计第一页。
rg -n "PageIndex: 1|PageSize: 1000" src/Platform/Gdpr -g '*.cs'
# 证明没有文件写入或下载端点。
rg -n "IFileStorage|ExportFileId =|download|Download" src/Platform/Gdpr -g '*.cs'
# 查找请求类型校验;修复前预期处理器中没有对应判断。
rg -n "RequestType.*SarRequestType" src/Platform/Gdpr/BitzOrcas.Platform.Gdpr.Application/Commands

新增测试必须覆盖并发幂等、跨类型 RequestId、错误 ID 类型、超过 1000 条数据、Provider 分页差异、失败状态持久化、包完整性、票据重放和过期销毁。

返回 GDPR 总览

100%

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