# CandidateFeedback 响应脱敏与契约修复设计 > 状态:已批准实施 > > 目标:修复候选人反馈响应中的内部身份字段泄露,不改变既有接口语义。 ## 1. 背景 `candidate_feedback` 模块已经提供企业创建/修改/删除反馈、企业和学生查看反馈的能力。当前服务层使用 `Map` 拼装响应,其中包含 `companyUserId` 和 `studentUserId`。这两个字段是内部身份标识,不属于前端页面所需的业务字段,也不应出现在学生或企业响应中。 本模块只做响应层脱敏,不修改数据库结构、请求格式、权限模型或业务流程。 ## 2. 范围 ### 2.1 本次实施 - 新增 `CandidateFeedbackResponse` 白名单 DTO; - `POST/GET/PUT /applications/{applicationId}/feedback` 改为返回该 DTO; - 从响应中移除内部用户 ID 和其他敏感字段; - 更新 Controller 测试、Service 测试和 OpenAPI response schema; - 更新前端联调文档; - 执行专项测试、全量测试和 OpenAPI 校验。 ### 2.2 明确不做 - 不修改 `candidate_feedback` 表; - 不新增 Flyway; - 不修改请求 DTO; - 不修改 URL、HTTP 方法、权限、错误码和状态机; - 不把 CandidateFeedback 改造成结构化面试评分; - 不修改评分引擎对 `candidate_feedback` 的读取; - 不启动或停止 Docker; - 不删除业务数据。 ## 3. 现有接口契约 | 方法 | 路径 | 权限 | 说明 | |---|---|---|---| | POST | `/applications/{applicationId}/feedback` | 岗位所属 COMPANY | 创建反馈,同一投递只能创建一次 | | GET | `/applications/{applicationId}/feedback` | 岗位所属 COMPANY 或投递本人 STUDENT | 查询反馈 | | PUT | `/applications/{applicationId}/feedback` | 岗位所属 COMPANY | 修改反馈 | | DELETE | `/applications/{applicationId}/feedback` | 岗位所属 COMPANY | 删除反馈 | 身份始终从 JWT/SecurityContext 获取,不接受 `userId`、`studentUserId` 或 `companyUserId` 请求参数。 ## 4. 新响应 DTO 类名:`com.mydreamjob.feedback.dto.CandidateFeedbackResponse` 字段: | 字段 | 类型 | 是否返回 | 说明 | |---|---|---:|---| | `id` | Long | 是 | 反馈 ID | | `applicationId` | Long | 是 | 投递 ID | | `rating` | Integer | 是 | 1–5 分 | | `content` | String | 是 | 反馈内容 | | `tags` | List<String> | 是 | 标签数组,无标签时返回空数组 | | `createdAt` | LocalDateTime | 否 | 创建时间,未生成时省略 | | `updatedAt` | LocalDateTime | 否 | 更新时间,未生成时省略 | 响应示例: ```json { "code": 200, "message": "ok", "data": { "id": 1, "applicationId": 31, "rating": 5, "content": "沟通良好", "tags": ["沟通良好"], "createdAt": "2026-08-25T10:00:00", "updatedAt": "2026-08-25T10:00:00" } } ``` 响应禁止出现: ```text companyUserId studentUserId phone email passwordHash resumeUrl ``` DTO 使用 `@JsonInclude(JsonInclude.Include.NON_NULL)`;`tags` 由服务层解析为数组,非法或空 JSON 返回空数组,不向客户端暴露原始存储内容。 ## 5. 权限与错误码 | 场景 | HTTP | 业务码 | |---|---:|---:| | 企业正常创建/修改/删除 | 200 | 200 | | 企业或本人学生正常查询 | 200 | 200 | | 未登录 | 401 | 401 | | 非岗位所属企业写入 | 403 | 2002 | | 非本人学生查询 | 403 | 2002 | | COMPANY/ADMIN 无企业归属或跨企业访问 | 403 | 2002 | | 投递不存在 | 404 | 8002 | | 反馈不存在 | 404 | 9104 | | 重复创建 | 409 | 9103 | | 参数校验失败 | 400 | 1001 | 本次不新增错误码,也不调整既有错误码映射。 ## 6. 实现约束 1. `CandidateFeedbackService` 的创建、查询、修改方法返回 `CandidateFeedbackResponse`; 2. Service 内新增安全的 `toResponse` 映射方法; 3. 不再使用包含内部 ID 的 `Map` 作为接口响应; 4. Controller 使用 `ApiResponse`; 5. Entity 继续保留 `companyUserId`、`studentUserId`,因为它们是数据库和评分聚合所需字段; 6. 评分引擎和 Candidate Score Card 只读仓储,不受本次响应 DTO 变化影响。 ## 7. 测试矩阵 ### Service 测试 - 创建成功时 DTO 含业务字段; - DTO 不含内部用户 ID; - 查询成功时 DTO 脱敏; - 修改成功时 DTO 脱敏; - 标签 JSON 正常解析; - 标签为空或非法 JSON 时返回空数组; - 企业、学生、其他学生、其他企业权限保持原语义。 ### MockMvc 测试 - POST/GET/PUT 返回 200; - DELETE 原有行为不变; - 响应中断言禁止字段不存在; - 未登录返回 401; - 参数非法返回 400; - 越权访问返回 403; - 反馈不存在返回 404; - 重复创建返回 409。 ### 回归测试 - Candidate Score Card 读取反馈摘要不受影响; - `mvn test` 全量通过; - `mvn clean verify` 全量通过; - `python docs/verify_openapi.py` 通过; - OpenAPI 路径数量保持 107。 ## 8. 完成标准 只有以下条件全部满足,才允许进入隐私设置模块: 1. 代码编译通过; 2. CandidateFeedback 专项测试通过; 3. 全量测试通过; 4. OpenAPI 静态校验通过; 5. 响应脱敏断言通过; 6. 既有权限和错误码回归通过; 7. 未新增数据库迁移; 8. 未修改已验收接口语义。