9.0 KiB
账号注销(Account Deletion)API 联调说明
模块:账号注销。当前登录用户可对自己的账号发起注销申请、查询状态、在冷静期内取消注销。 适用于 STUDENT / COMPANY / ADMIN 所有登录角色;未登录一律返回
HTTP 401。
1. 接口概览
| 方法 | 路径 | 角色 | 说明 |
|---|---|---|---|
| POST | /profile/me/deletion |
所有登录角色 | 申请注销当前账号(NONE → PENDING;PENDING 重复提交幂等返回 200;COMPLETED 再申请返回 409/9301) |
| GET | /profile/me/deletion |
所有登录角色 | 查询当前用户注销状态;无记录返回默认对象 NONE,不写库 |
| POST | /profile/me/deletion/cancel |
所有登录角色 | 仅在 PENDING 且未超过 effectiveAt 时可取消(PENDING → NONE) |
身份一律从 JWT / SecurityContext 获取,不接受 userId、candidateId 等身份参数。
2. 注销状态机
状态仅有三种:
NONE —— 正常态(未注销)
PENDING —— 冷静期(可取消;业务写操作被限制)
COMPLETED —— 终态(账号已脱敏,登录被阻止,不可恢复)
转换规则:
NONE ----发起注销----> PENDING
PENDING ----冷静期内取消-> NONE
PENDING ----30天到期------> COMPLETED
COMPLETED(终态,不可恢复)
3. 冷静期
- 默认 30 天,可通过
app.deletion.cooling-days(环境变量DELETION_COOLING_DAYS)配置。 - 发起注销时
effectiveAt = requestedAt + 冷静期。 - 冷静期内允许登录、允许 GET 查询,但业务写操作被拦截。
- 超过
effectiveAt后,PENDING 会转换为 COMPLETED;此后不可取消。
4. 默认 NONE 行为
GET /profile/me/deletion 在用户从未发起注销时返回默认对象,且不会自动向数据库插入记录:
{
"code": 200,
"message": "ok",
"data": {
"userId": 2,
"status": "NONE",
"version": 0
},
"timestamp": "2026-08-25T10:00:00Z"
}
requestedAt / effectiveAt / cancelledAt / completedAt 仅在对应状态下返回(无值不输出)。
5. 请求与响应示例
5.1 发起注销
POST /api/v1/profile/me/deletion
Authorization: Bearer <token>
{
"code": 200,
"message": "ok",
"data": {
"userId": 2,
"status": "PENDING",
"requestedAt": "2026-08-25T10:00:00",
"effectiveAt": "2026-09-24T10:00:00",
"version": 0
},
"timestamp": "2026-08-25T10:00:00Z"
}
PENDING 状态下重复提交:返回 200 当前状态,不重复写入ACCOUNT_DELETION_INITIATED 审计。
5.2 查询状态
GET /api/v1/profile/me/deletion
Authorization: Bearer <token>
响应结构同默认对象;已在 PENDING 时返回上述 PENDING 结构。
5.3 取消注销(冷静期内)
POST /api/v1/profile/me/deletion/cancel
Authorization: Bearer <token>
{
"code": 200,
"message": "ok",
"data": {
"userId": 2,
"status": "NONE",
"cancelledAt": "2026-08-26T09:00:00",
"version": 1
},
"timestamp": "2026-08-26T09:00:00Z"
}
非 PENDING(如 NONE)重复取消返回当前状态,保持幂等,不重复写审计。
6. PENDING 写操作限制
当当前用户状态为 PENDING 时,DeletionGuardInterceptor 会拦截所有业务写操作
(POST / PUT / PATCH / DELETE),返回:
HTTP/1.1 409 Conflict
Content-Type: application/json
{ "code": 9302, "message": "account deletion pending, business write is not allowed" }
GET、OPTIONS默认放行。- 白名单放行:
POST /profile/me/deletionGET /profile/me/deletionPOST /profile/me/deletion/cancelOPTIONS /**
以下写接口会被拦截(示例,不止于此):PUT /profile/me、PUT /profile/me/preferences、
POST /applications、POST /offers、POST /conversations/messages、PUT /profile/me/privacy。
未注销用户、未认证请求、GET 请求不会被误拦截。
7. COMPLETED 脱敏规则与旧 JWT 处理
7.1 触发完成
- 定时任务:每日扫描
PENDING 且 effective_at <= 当前时间的记录,转为 COMPLETED 并脱敏。 - 懒转换:登录、已认证请求、注销查询或写操作时,若发现 PENDING 已过期,先转换为 COMPLETED 再按 COMPLETED 规则处理。
转换与脱敏事务化、幂等、并发安全(@Version 乐观锁 + REQUIRES_NEW 独立事务)。
7.2 脱敏规则(只脱敏 users 表 PII,不删除任何业务数据)
| 字段 | 处理 |
|---|---|
phone |
改为 deleted_{userId},保证唯一 |
email |
置空 |
nickname |
置为 deleted |
avatarUrl |
置空 |
passwordHash |
置空 |
is_active |
false |
7.3 旧 JWT
COMPLETED 状态属于终态,即使旧 JWT 尚未过期也会被拒绝:
- 登录时校验到 COMPLETED → 阻止登录(
ACCOUNT_DELETED)。 - 普通业务请求(即使带有效 JWT)→
409 / 9303。 - 注销接口专用于查询终态或返回正确错误:
GET /profile/me/deletion→ 可查看COMPLETED;POST /profile/me/deletion→409 / 9301;POST /profile/me/deletion/cancel→409 / 9301(或 9305)。
未注销用户的 JWT 登录流程不受影响。
7.4 懒转换首次查询即返回 COMPLETED
懒转换(到期 PENDING → COMPLETED)在独立事务(REQUIRES_NEW)中完成并提交。触发懒转换后,
GET /profile/me/deletion 会在独立只读事务(新快照)中重读最新记录,因此第一次 GET 就返回
status=COMPLETED 与 completedAt,不会返回过期的 PENDING。随后任何 GET 均保持一致状态,
不会出现"第一次 PENDING、第二次 COMPLETED"的不一致。
7.5 原手机号禁止重新注册(墓碑)
- 用户完成注销(COMPLETED)时,在将
users.phone改为deleted_{userId}之前, 使用服务端密钥(配置:app.deletion.phone-hash-secret/ 环境变量DELETION_PHONE_HASH_SECRET) 对原手机号做 HMAC-SHA256,仅把摘要写入account_deletions.deleted_phone_hash(唯一索引)。 - 不保存原手机号明文到注销表,不写入审计日志。
- 登录/注册流程对输入手机号计算同样 HMAC 并查询
deleted_phone_hash:命中即拒绝 ——409 / 9303 ACCOUNT_DELETED,不自动创建新账号。 - 已注销账号仍以
deleted_<id>作为users.phone(原脱敏规则不变)。 - 既不允许通过原手机号恢复原账号,也不允许通过原手机号重新注册新账号。
- 数据库变更由 V29 迁移完成(仅新增
deleted_phone_hash字段,不改动 V1-V28 及状态机)。
8. 不删除业务数据
脱敏仅修改 users 表。以下业务数据一律保留:
applications、jobs、offers、interviews、messages、notifications、reviews、
candidate_dimension_scores、audit_logs 及其他业务数据。不物理删除任何业务数据。
9. 错误的角色权限矩阵
| 状态/角色 | STUDENT | COMPANY | ADMIN | 未登录 |
|---|---|---|---|---|
| 申请注销(POST deletion) | 200 / 409(9301) | 200 / 409(9301) | 200 / 409(9301/9306) | 401 |
| 查询状态(GET deletion) | 200 | 200 | 200 | 401 |
| 取消注销(POST cancel) | 200 / 409(9305) | 200 / 409(9305) | 200 / 409(9305) | 401 |
- ADMIN:若为系统中最后一个有效 ADMIN(
role=ADMIN 且 is_active=true计数 ≤ 1),不允许注销,返回409 / 9306 LAST_ADMIN_CANNOT_DELETE;存在多个有效 ADMIN 时允许其中一个注销。
10. 错误码(9301-9306)
| 业务码 | HTTP | 枚举 | 说明 |
|---|---|---|---|
| 9301 | 409 | INVALID_DELETION_STATE |
状态不允许该操作(如 COMPLETED 再申请 / COMPLETED 取消恢复) |
| 9302 | 409 | ACCOUNT_DELETION_PENDING |
冷静期内业务写操作被拦截 |
| 9303 | 409 | ACCOUNT_DELETED |
账号已注销(COMPLETED),业务请求被拒 |
| 9304 | 409 | ACCOUNT_DELETION_CONCURRENT_UPDATE |
乐观锁并发冲突 |
| 9305 | 409 | ACCOUNT_DELETION_CANCEL_DEADLINE_PASSED |
已超过冷静期,无法取消 |
| 9306 | 409 | LAST_ADMIN_CANNOT_DELETE |
最后一个有效 ADMIN 不能注销 |
11. 审计日志
写入以下 action:
ACCOUNT_DELETION_INITIATEDACCOUNT_DELETION_CANCELLEDACCOUNT_DELETION_COMPLETEDACCOUNT_DATA_DESENSITIZED
细节(details)仅记录:userId、状态变化、时间、操作类型。禁止写入
phone / email / password / passwordHash / token / jwt / resumeUrl / 验证码。
同一次完成流程不会重复写完成审计与脱敏审计。
12. 并发与幂等
user_id唯一约束保证同一用户仅一条注销记录。- POST deletion 在 PENDING 时幂等返回 200。
- cancel 在 NONE/COMPLETED 时幂等返回当前状态或明确错误。
- 使用
@Version乐观锁;并发状态迁移只有一个事务成功,后到者捕获OptimisticLockingFailureException映射为9304。