You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

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/deletion
    • GET /profile/me/deletion
    • POST /profile/me/deletion/cancel
    • OPTIONS /**

以下写接口会被拦截(示例,不止于此):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_INITIATED
  • ACCOUNT_DELETION_CANCELLED
  • ACCOUNT_DELETION_COMPLETED
  • ACCOUNT_DATA_DESENSITIZED

细节(details)仅记录:userId、状态变化、时间、操作类型。禁止写入 phone / email / password / passwordHash / token / jwt / resumeUrl / 验证码。 同一次完成流程不会重复写完成审计与脱敏审计。

12. 并发与幂等

  • user_id 唯一约束保证同一用户仅一条注销记录。
  • POST deletion 在 PENDING 时幂等返回 200。
  • cancel 在 NONE/COMPLETED 时幂等返回当前状态或明确错误。
  • 使用 @Version 乐观锁;并发状态迁移只有一个事务成功,后到者捕获 OptimisticLockingFailureException 映射为 9304。