那个不会真的删除的删除按钮

2026年9月7日 gdprgoprivacy

博客现在有用户账户了,这意味着它有用户数据,这意味着欧盟法律和我自己的良心都认为,用户应该有两个按钮:一个用来下载我这里保存的关于他们的一切,一个用来让这一切彻底消失。这篇文章讲的就是这两个按钮是怎么做出来的,以及为什么删除按钮其实并不会真的删除数据。

下载按钮是比较容易的那一半。一个接口把和你账户相关的所有东西(来自 auth 服务的账户信息,来自 blog 服务的评论、点赞和举报)收集起来,打包成一个 JSON 文件返回给你。我本来以为这就完事了。结果代码审查发现我把它放在登录后面了,却忘了加限流,所以任何有账户的人都能对着一个开销不小的接口狂刷。加上一层中间件之后,这才算真正做完。

删除按钮才是真正有意思的地方。我的第一反应是最直接的那种:删除账户,删除评论,全部清空。但这样会把对话搞断。如果你写了一条评论,有三个人回复了它,删掉你的评论就会让他们的回复悬在半空中,回应着一个已经不存在的人。对你以外的所有人来说,这个评论串就再也说不通了。

个人设置里的数据标签页,带有下载和删除按钮

于是博客在第一次启动时会创建一个共享账户,叫 anonymous,删除你的账户时,会把你写的所有内容都转交给这个账户。你的名字会被拿掉,文字留下来。评论串读起来还是通顺的,只是原来显示你用户名的地方,现在写着 anonymous:

-- name: AnonymizeComments :execrows
-- Reattributes a deleted user's comments to the shared anonymous account.
UPDATE comments
SET author_id = sqlc.arg(anonymous_id)::uuid, author_username = 'anonymous'
WHERE author_id = sqlc.arg(user_id)::uuid;

这部分一次就成功了。点赞没有那么顺利。一条点赞记录说的是"这个用户点赞了这条评论",而这一对数据必须是唯一的。如果你点赞的一条评论,恰好也被某个更早被删除的用户点赞过,那这两条点赞现在都想变成"anonymous 点赞了这条评论",数据库理所当然地拒绝了这个重复。解决办法是先删掉冲突的那条点赞(评论上的点赞数还是准确的,因为 anonymous 已经点过赞了),然后再转移剩下的。

顺序也很重要。blog 服务先做匿名化处理,然后 auth 服务再删除账户。如果中途崩溃了,你就会变成一个内容已经匿名化的用户,这个状态是安全的,直接重试就行。反过来的顺序则可能先删掉你的账户,让你的评论挂在一个已经不存在的用户 id 下面,变成孤儿数据。

最后加了两道保险:anonymous 账户不能删除自己(那会是个挺有意思的 bug),最后一个管理员也不能删除自己,因为一个谁都登不进管理后台的博客,未免也太安静了。

我用一个用完即弃的账户测试了整个流程:注册、评论、下载数据、点删除。评论还在,署名 anonymous,登录也确实用不了了。跟按钮承诺的一模一样,虽然跟它字面写的不完全是一回事。

0 条评论

登录 后即可评论。

登录

忘记密码?

还没有账号?