11、数据主体请求处理:删除账户功能实现、数据导出(JSON/CSV)功能实现、请求响应时限与流程设计。

数据主体请求处理,说白了就是用户找上门来,要你给个说法。要么是“把我的数据还给我”,要么是“把我的账号删了”。这可不是简单的功能开发,背后是 GDPR 第 17 条(被遗忘权)和第 20 条(数据可携带权)的硬性要求。我这些年处理过不少这类需求,踩过的坑比写过的代码还多。今天咱们就聊聊,怎么把这些合规要求落地成实实在在的代码。

11.1 删除账户功能实现

删除账户,听着简单,做起来全是细节。用户点一下“删除”,你后端可不能真的只删一条用户表记录。我见过最离谱的案例,是某 App 删了用户主表,但日志表里还躺着三年的操作记录——这要是被监管查到,罚单跑不了。

11.1.1 逻辑删除 vs 物理删除

我个人习惯,先做逻辑删除,再做物理删除。为什么?因为用户可能反悔。GDPR 给了用户 30 天的撤回期,你直接物理删了,人家反悔了怎么办?

类型 说明 适用场景
逻辑删除 标记 is_deleted=1,数据保留在库中 用户反悔、审计追溯、法律冻结期
物理删除 从数据库彻底抹除数据 冻结期结束、用户明确要求不可逆
注意: 逻辑删除不等于不删。你必须在用户可见的界面明确告知“删除后数据将不可恢复”,而不是偷偷留一手。

11.1.2 级联删除策略

用户数据不是孤岛。一个用户可能关联了订单、评论、聊天记录、设备信息。你删主表的时候,这些关联数据怎么处理?我建议分三类:

  • 必须删除: 个人身份信息(姓名、手机号、身份证)、登录凭证(token、密码哈希)
  • 匿名化保留: 订单记录(去掉用户标识,保留商品和金额用于统计)
  • 彻底删除: 用户行为日志、埋点数据、设备指纹

嗯,这里要注意。匿名化不是简单的把用户名置空。你得确保无法通过其他字段反推回具体用户。我曾经遇到一个项目,把用户名删了,但邮箱前缀还留在日志里——这跟没删一样。

11.1.3 代码示例:删除账户核心流程

// Kotlin 伪代码 - 删除账户服务
class AccountDeletionService(
    private val userRepo: UserRepository,
    private val orderRepo: OrderRepository,
    private val logRepo: LogRepository
) {
    fun deleteAccount(userId: String, isPhysical: Boolean = false) {
        if (isPhysical) {
            // 物理删除:先匿名化关联数据,再删主表
            orderRepo.anonymizeByUserId(userId)  // 去掉用户标识
            logRepo.deleteByUserId(userId)       // 日志直接删
            userRepo.physicalDelete(userId)      // 最后删主表
        } else {
            // 逻辑删除:标记 + 设置过期时间
            userRepo.softDelete(userId, expireAt = now() + 30.days)
        }
        // 记录审计日志
        auditLog.write("Account deleted", userId, type = isPhysical)
    }
}
避坑指南: 我曾经在物理删除时先删了主表,结果关联表的外键约束报错。正确的顺序是:先处理子表,再删主表。或者直接用数据库级联删除,但一定要在测试环境跑一遍所有场景。

11.2 数据导出功能实现(JSON/CSV)

数据可携带权,说白了就是用户要把数据带走。你得给人家一个机器可读的格式。JSON 和 CSV 是标配。我个人更推荐 JSON,结构清晰,嵌套数据也好处理。但有些用户就喜欢 CSV,说能直接拖进 Excel 看。

11.2.1 导出数据范围

不是所有数据都能导出。GDPR 规定,只能导出用户主动提供的数据和系统自动收集的关联数据。举个例子:

  • 可导出: 个人资料、订单记录、聊天记录、设备信息
  • 不可导出: 系统内部计算出的评分、推荐算法权重、其他用户的隐私数据

你想想看,如果用户导出了你的推荐算法模型,那你的商业机密就全暴露了。所以导出前一定要做数据脱敏和范围限制。

11.2.2 JSON 导出实现

// Kotlin 伪代码 - 数据导出为 JSON
fun exportAsJson(userId: String): String {
    val profile = userRepo.findProfile(userId)
    val orders = orderRepo.findByUserId(userId)
    val devices = deviceRepo.findByUserId(userId)

    val exportData = mapOf(
        "profile" to mapOf(
            "name" to profile.name,
            "email" to profile.email,
            "createdAt" to profile.createdAt
        ),
        "orders" to orders.map { order ->
            mapOf(
                "orderId" to order.id,
                "amount" to order.amount,
                "items" to order.items.map { it.name }
            )
        },
        "devices" to devices.map { it.model }
    )

    return ObjectMapper().writerWithDefaultPrettyPrinter()
        .writeValueAsString(exportData)
}

11.2.3 CSV 导出实现

CSV 的问题在于,它天生不适合嵌套数据。我建议把复杂结构拍平。比如订单里的商品列表,用分号分隔塞到一个单元格里。

// Kotlin 伪代码 - 导出为 CSV
fun exportAsCsv(userId: String): String {
    val orders = orderRepo.findByUserId(userId)
    val header = "orderId,amount,items,date"
    val rows = orders.map { order ->
        "${order.id},${order.amount},\"${order.items.joinToString(";")}\",${order.date}"
    }
    return listOf(header).plus(rows).joinToString("\n")
}
注意: CSV 导出时,如果字段里包含逗号或换行符,一定要用双引号包裹。否则 Excel 打开会错列。我吃过这个亏,用户投诉说数据乱码,排查了半天才发现是某个商品名称里带了逗号。

11.3 请求响应时限与流程设计

GDPR 规定,数据主体请求必须在 一个月内 响应。特殊情况下可以延长两个月,但必须通知用户原因。这个时限是从你收到请求那天开始算的,不是你开始处理那天。

11.3.1 流程设计

我建议把流程拆成四个阶段:

  1. 接收与验证: 用户提交请求,系统验证身份(防止冒用)
  2. 分类与分派: 判断是删除、导出还是修改,分派给对应服务
  3. 处理与审核: 执行操作,必要时人工审核(比如删除涉及法律冻结的数据)
  4. 反馈与归档: 通知用户结果,记录审计日志
核心逻辑: 整个流程必须可追溯。每个环节都要记录时间戳和操作人。监管来查的时候,你得能拿出完整的处理链路。

11.3.2 时限控制

一个月听起来很长,但实际处理起来经常超时。为什么?因为人工审核环节卡住了。我建议设置自动提醒:

  • 第 20 天:系统自动发提醒给审核人
  • 第 25 天:升级提醒给上级主管
  • 第 28 天:如果还没处理完,自动通知用户“需要延期”

嗯,这里要注意。延期通知必须写明原因和预计完成时间。不能只说“我们正在处理”,用户会投诉的。

11.3.3 状态机设计

我习惯用状态机来管理请求生命周期。每个请求都有明确的状态,不会出现“处理中”卡死的情况。

// Kotlin 伪代码 - 请求状态机
enum class RequestState {
    PENDING,        // 待验证
    VERIFIED,       // 已验证
    PROCESSING,     // 处理中
    COMPLETED,      // 已完成
    REJECTED,       // 已拒绝
    EXPIRED         // 超时
}

class RequestStateMachine {
    fun transition(current: RequestState, action: Action): RequestState {
        return when (current) {
            PENDING -> when (action) {
                VERIFY -> VERIFIED
                REJECT -> REJECTED
                else -> throw IllegalStateException()
            }
            VERIFIED -> when (action) {
                START_PROCESS -> PROCESSING
                else -> throw IllegalStateException()
            }
            // ... 其他状态转换
        }
    }
}

11.4 知识体系总览

下面这张图,是我梳理的数据主体请求处理的核心逻辑。从用户发起请求,到最终反馈结果,每个环节都不能少。

数据主体请求处理核心流程 1. 接收与验证 身份验证 + 请求登记 2. 分类与分派 删除 / 导出 / 修改 3. 处理与审核 执行操作 + 人工审核 4. 反馈与归档 通知用户 + 审计日志 超时或失败时回退 ⏱ 响应时限:30天(可延至60天) 第20天自动提醒 · 第28天通知延期 📄 导出格式 JSON(推荐) / CSV 🗑 删除策略 逻辑删除 → 物理删除 每个环节必须记录时间戳和操作人,确保可追溯 监管审计时需提供完整处理链路

这张图把整个流程串起来了。从接收验证到反馈归档,每个环节都有明确的时限和输出。你照着这个框架去设计,基本不会漏掉关键节点。

个人经验: 我建议在开发阶段就把审计日志埋好。不要等上线了再补。因为一旦用户投诉到监管机构,你需要在 72 小时内提供处理记录。没有日志,你连用户什么时候提的请求都说不清楚。

好了,数据主体请求处理这块,核心就是三个点:删除要分级、导出要脱敏、流程要可追溯。你把这些落地了,GDPR 的合规要求基本就稳了。剩下的就是根据业务场景微调,比如有些行业有特殊的数据保留期限,这个需要法务同事一起确认。