# 个人数据导出 API(V30) 本文档描述「个人数据导出」模块。**这与简历导出(`/resume-export-tasks`)是两套完全独立的机制**,请勿混淆: - 简历导出:面向单个简历文件/快照的导出,走既有 `/resume-export-tasks` 接口,语义保持不变。 - 个人数据导出:把当前登录用户的全量业务数据打包成一个异步 ZIP 文件下载,走下面的新接口。 只实现当前登录用户「本人」数据的导出;任务创建是**异步**的:先返回一个 `PENDING` 任务,后台 Worker 收集并打包,完成后才能下载。 --- ## 一、接口总览 | 操作 | 路径 | 说明 | | --- | --- | --- | | POST | `/profile/me/data-export-tasks` | 创建导出任务(异步 ZIP) | | GET | `/profile/me/data-export-tasks` | 查询本人任务列表(分页) | | GET | `/profile/me/data-export-tasks/{taskId}` | 查询本人某个任务详情 | | GET | `/profile/me/data-export-tasks/{taskId}/download` | 下载已完成 ZIP | 新增 3 个 path、4 个 operation。OpenAPI 路径总数 110 → **113**(详情接口已覆盖状态查询,未单独新增 `/status`)。 ### 身份与权限 - 所有接口都需要登录(JWT);身份只从 JWT/SecurityContext 获取,**不接受 `userId`/`candidateId` 参数**。 - `STUDENT`、`COMPANY`、`ADMIN` 均可导出**自己的**任务。 - 只能查询/下载自己的任务;访问他人任务返回 `403/2002`,任务不存在返回 `404/9401`。 --- ## 二、创建任务 `POST /profile/me/data-export-tasks` 请求体(均可选): ```json { "format": "ZIP", "idempotencyKey": "optional-client-key" } ``` 约束: - `format` 当前仅允许 `ZIP`(非法值返回 `400/1001`)。 - `idempotencyKey` 可选,最大 64 字符;用于创建幂等重放。 - 同一用户存在未完成任务(`PENDING`/`PROCESSING`)时: - 相同 `idempotencyKey` 重放 → 返回原任务; - 不同 key → 返回 `409/9402`。 - 已完成且未过期的旧任务 → 直接返回该任务;若旧任务已过期则可创建新任务。 - 账号注销 `PENDING`/`COMPLETED` 时禁止创建(见第八节)。 成功响应: ```json { "code": 200, "message": "ok", "data": { "taskId": 1, "status": "PENDING", "format": "ZIP", "requestedAt": "2026-08-25T10:00:00", "startedAt": null, "completedAt": null, "expiresAt": null, "sizeBytes": null, "downloadAvailable": false } } ``` > 响应**绝不**包含 `storageKey`、服务器绝对路径、内部异常堆栈等字段。 --- ## 三、查询任务列表 / 详情 `GET /profile/me/data-export-tasks?page=0&size=20&status=` - `page` 默认 0,不得小于 0。 - `size` 默认 20,范围 1–100(越界返回 `400/1001`)。 - `status` 可选:`PENDING` / `PROCESSING` / `COMPLETED` / `FAILED` / `EXPIRED` / `CANCELLED`。 - 按 `createdAt` **倒序**,返回标准分页结构。 `GET /profile/me/data-export-tasks/{taskId}` - 任务不存在 → `404/9401`。 - 任务属于他人 → `403/2002`。 - 响应不返回 `storageKey`、绝对路径、内部字段。 --- ## 四、下载 `GET /profile/me/data-export-tasks/{taskId}/download` - 仅任务本人可下载。 - 仅 `COMPLETED` **且未过期**可下载。 - `PENDING`/`PROCESSING` → `409/9403`(文件尚未准备好了)。 - `FAILED` → `409/9405`。 - `EXPIRED`/`CANCELLED` → `409/9404`。 - 文件不存在 → `409/9406`(不会返回 500)。 - 返回 `Content-Disposition: attachment`,`Content-Type: application/octet-stream`(内容为 ZIP)。 - **下载凭证不可脱离 JWT**;不返回任何公开 URL。 - 下载成功会记录 `downloadedAt` 并写审计事件 `DATA_EXPORT_DOWNLOADED`(留有接口;服务记录 downloadedAt)。 --- ## 五、任务状态机 ``` PENDING ─→ PROCESSING ─→ COMPLETED PENDING ─→ PROCESSING ─→ FAILED PENDING ─→ FAILED PROCESSING ─→ FAILED COMPLETED ─→ EXPIRED COMPLETED/CANCELLED(账号注销清理) ``` - 创建后默认 `PENDING`;后台 Worker 用 `@Version` 乐观锁抢占(只有一个 Worker 能把 `PENDING→PROCESSING`,并发后到者跳过)。 - 完成后写入 `completedAt`、`expiresAt`、`sizeBytes`、`checksumSha256`。 - 默认文件有效期 **24 小时**,过期由定时任务把 `COMPLETED→EXPIRED` 并删除本地导出文件;**不删除任何业务数据库数据**。 - 失败仅记录安全的 `errorCode/errorMessage`,不记录密码、Token、手机号等敏感内容。 --- ## 六、导出数据范围 ZIP 内部结构(`personal-data/` 根目录): ``` personal-data/ manifest.json # schemaVersion/format/requestedAt/fileCount/sections profile.json # 基本资料 + 学生资料 preferences.json # 求职偏好 resumes/<文件> # 用户自己上传的简历文件 + resumes.json 元数据 applications.json # 投递记录 application-timelines.json interviews.json # 本人参与/发起的面试 interview-timelines.json offers.json # 本人收到或创建(COMPANY)的 Offer offer-timelines.json feedback.json # 本人候选人反馈 favorites.json # 本人岗位收藏 notifications.json # 本人通知 conversations.json # 本人会话 messages.json # 本人会话消息 candidate-scoring.json # 本人评分快照/趋势 scoring-appeals.json # 本人评分申诉及历史 ``` > 以上为当前实现实际包含的 section(`manifest.json` 的 `sections` 字段会列出全部 section 名称);`resumes/` 目录下同时附带用户自己上传的简历文件二进制。 ### 允许导出(本人) - 基本资料:`userId / nickname / avatarUrl / email`(如业务允许)/ 账号创建时间等;**不含 passwordHash**。 - 学生资料与求职偏好。 - 简历版本及用户自己上传的简历文件。 - `COMPANY` 用户自己创建的企业/岗位资料。 - 自己的投递记录与状态时间线、自己参与的面试、自己收到/创建的 Offer、自己的候选人反馈、岗位收藏、通知、会话与消息、评分快照/趋势/申诉及历史。 ### 脱敏与禁止导出 - 禁止:`passwordHash`、JWT、短信验证码、JWT_SECRET、`deletedPhoneHash`、数据库密码、管理员内部审核备注、内部风控规则、系统异常堆栈、服务器绝对路径、RabbitMQ/Redis/MySQL 连接信息。 - 不得导出**其他用户**的手机号、邮箱、密码、简历地址。 - 会话消息:只导出当前用户参与的消息;对方**仅保留 `participantId`(脱敏显示名)**,不导出对方完整资料。 --- ## 七、错误码 | 错误码 | 含义 | HTTP | | --- | --- | --- | | 1001 | 参数错误(非法 format、page/size 越界) | 400 | | 2001 | 未认证 | 401 | | 2002 | 无权限(访问他人任务) | 403 | | 9401 | 任务不存在 | 404 | | 9402 | 存在未完成任务 | 409 | | 9403 | 文件尚未生成 | 409 | | 9404 | 导出文件已过期 | 409 | | 9405 | 导出失败 | 409 | | 9406 | 导出文件不存在 | 409 | | 9302 | 注销进行中禁止操作 | 409 | | 9303 | 账号已注销 | 409 | 错误响应统一格式(`ApiResponse`):`{ "code": <码>, "message": "...", "requestId": "..." }`。 --- ## 八、账号注销兼容 - **注销 PENDING 用户**:不允许创建新导出任务;已创建但未执行的任务会被 Worker 阻止并标记 `FAILED`(不再生成含个人信息的新文件)。 - **注销 COMPLETED 用户**:所有导出接口返回 `409/9303`(创建/查询/详情/下载均被拦截),不能看到或下载新任务。 - **注销完成清理**:账号注销完成(脱敏)流程会删除该用户的导出文件,将未完成任务标记为 `CANCELLED` 或 `EXPIRED`,写入审计事件;**不删除任何原业务数据库数据**。 --- ## 九、审计 复用 `AuditService`,新增 action: - `DATA_EXPORT_REQUESTED` / `DATA_EXPORT_PROCESSING` / `DATA_EXPORT_COMPLETED` - `DATA_EXPORT_DOWNLOADED` / `DATA_EXPORT_FAILED` / `DATA_EXPORT_EXPIRED` details 仅记录:`taskId / userId / format / status / sizeBytes` 等;禁止写入文件内容、`storageKey`、手机号、邮箱、密码、Token、绝对路径。