# 举报与屏蔽 API(V33) > 版本:V33 实现版 > 状态:已编码实现,待黑盒验收 > 适用范围:举报、屏蔽、ADMIN 举报处理;仅允许对 `MessagingService` 增加屏蔽校验。 > OpenAPI 117 → 125 paths(新增 8 path / 9 operation)。 ## 1. 定位与边界 | 接口 / 模块 | 是否改动 | 关系 | |---|---|---| | 投递、Offer、面试、评分、治理、看板、匹配 | 不改 | 屏蔽不影响系统通知、投递、Offer、面试、评分、评价 | | `MessagingService` | 仅改两方法 | `createOrGetDirectConversation`、`sendMessage` 增加屏蔽校验 | | `listMessages` / `getConversation` / `listConversations` | 不改 | 历史消息不删除,按现有权限读取 | | `NotificationService` | 不改语义 | 复用,处理完成后向举报人发送通知,不新增 NotificationType 枚举 | | AI 自动审核 / 自动封禁 / 自动警告 / 公平性算法 | 不实现 | 本轮明确排除 | ## 2. 数据库(V33,三张表) - `reports`:举报单。`(reporter_user_id, idempotency_key)` 唯一;`active_key` 唯一;`target_type` 仅 USER/MESSAGE;`status` 仅 PENDING/RESOLVED/REJECTED;`message_id` 外键 `ON DELETE SET NULL`;`reporter_user_id`/`reported_user_id` 外键关联 `users`;`@Version` 乐观锁;`evidence_urls` 使用 TEXT 存 JSON 字符串(不采用 MySQL JSON 列)。 - `block_relations`:`(blocker_user_id, blocked_user_id)` 唯一;禁止自己屏蔽自己;外键 `ON DELETE CASCADE`。 - `report_decision_history`:`report_id` 外键 `ON DELETE CASCADE`;记录 `from_status → to_status`、操作人、角色、note。 约束强校验:`target_type=USER` 时 `target_user_id` 非空且 `message_id` 为空;`target_type=MESSAGE` 反之。 ## 3. 举报接口 ``` POST /reports 提交举报 GET /reports/me 我的举报列表(status/page/size) GET /reports/{id} 举报详情(本人或 ADMIN) ``` - 身份全取 JWT,不接受 `userId`/`reporterId`/`reportedUserId` 注入。 - **USER 举报**:客户端传 `targetUserId`,服务端校验存在,`targetUserId ≠ 本人`,`reportedUserId = targetUserId`。 - **MESSAGE 举报**:仅传 `messageId`,服务端校验存在 + 本人是会话参与者(无权返回 403/2002,不泄露消息是否存在),`reportedUserId` 从消息真实 sender 推导,禁止客户端覆盖。 - 路由:`/reports/me` 必须在 `/reports/{id}` 之前注册,并有路由回归测试。 幂等与防重: | 场景 | 行为 | |---|---| | 同 reporter + idempotencyKey + 相同请求体 | 返回原举报,200(不新增) | | 同 reporter + idempotencyKey + 不同请求体 | 409 / 9606 | | 同 reporter + 同目标 + 同 category + 同 messageId/targetUserId + 已有 PENDING | 409 / 9603 | | 已 RESOLVED/REJECTED | 不占用 active_key,允许重新提交 | evidenceUrls 校验:最多 5 个、每个 ≤500 字符、仅 http/https;后端不主动请求/抓取 URL(防 SSRF)。 ## 4. 屏蔽接口 ``` PUT /blocks/{blockedUserId} 屏蔽 DELETE /blocks/{blockedUserId} 解除屏蔽(幂等) GET /blocks/me 我的屏蔽列表(page/size) ``` - 屏蔽自己 → 400 / 9608;目标不存在复用以有用户不存在错误码。 - PUT 已存在返回 200 不新增;DELETE 不存在返回 200 不报错;GET 仅返回本人主动屏蔽的用户。 - 响应不返回对方手机号/邮箱/密码/简历地址。 - 屏蔽不影响系统通知、投递、Offer、面试、评分、评价。 ## 5. MessagingService 屏蔽控制 - `createOrGetDirectConversation`:若 A 屏蔽 B 或 B 屏蔽 A 任一方向存在,禁止新建 DIRECT 会话。 - `sendMessage`:会话内任一方向存在屏蔽,禁止发送新消息。 - 对外统一 `403 / 2002 / "message cannot be sent"`,不返回“你已被屏蔽”,不泄露哪一方屏蔽了谁。 - 内部审计记录 `BLOCKED_SEND_PROHIBITED`。 - 屏蔽期间发送失败的消息直接丢弃,解除屏蔽后不补发。 - `listMessages`、`getConversation`、`listConversations` 保持现有语义。 ## 6. ADMIN 举报处理接口 ``` GET /admin/reports 举报列表(status/targetType/category/page/size) GET /admin/reports/{id} 举报详情(含处理历史、脱敏证据 URL、MESSAGE 脱敏内容) POST /admin/reports/{id}/decision 处理决定 ``` - 仅 ADMIN;COMPANY / STUDENT / 未登录分别 403 / 403 / 401。 - `decision` 仅 RESOLVED / REJECTED;REJECTED 必填 `note`。 - 仅 PENDING 可处理,重复 → 409 / 9604;非法流转 → 409 / 9605;乐观锁冲突 → 409 / 9605。 - 处理不自动封禁、不删除消息、不修改投递/面试/评分状态。 - 权限矩阵: | 身份/场景 | HTTP | code | |---|---|---| | 未登录 | 401 | 2001 | | COMPANY / STUDENT | 403 | 2002 | | ADMIN | 200 | — | | 非本人非 ADMIN 查详情 | 403 | 2002 | | 举报不存在 | 404 | 9601 | ## 7. 错误码 | code | 枚举 | HTTP | 说明 | |---|---|---|---| | 9601 | REPORT_NOT_FOUND | 404 | 举报不存在 | | 9602 | CANNOT_REPORT_SELF | 400 | 不能举报自己 | | 9603 | REPORT_DUPLICATE | 409 | 同目标同分类已有 PENDING | | 9604 | ALREADY_DECIDED | 409 | 重复决定 | | 9605 | INVALID_TRANSITION | 409 | 非法流转 / 乐观锁冲突 | | 9606 | IDEMPOTENCY_CONFLICT | 409 | 幂等键冲突(不同请求体) | | 9607 | REASON_REQUIRED | 400 | 理由缺失(注意复用 BAD_REQUEST 由校验层触发) | | 9608 | CANNOT_BLOCK_SELF | 400 | 不能屏蔽自己 | | 9609 | REPORT_RATE_LIMITED | 429 | 举报频率超限 | | 9610 | BLOCKED_SEND_PROHIBITED | 内部 | 仅内部审计/日志,HTTP 不直接暴露 | 复用:BAD_REQUEST(1001)、UNAUTHORIZED(2001)、FORBIDDEN(2002)、用户/消息不存在错误码。 ## 8. 举报限流 - 使用现有 Redis,不新增数据库表;仅限制 `POST /reports`。 - 每用户默认每小时最多 10 次;key:`report:rate:{userId}:{hour}`。 - 超限 → 429 / 9609,记录 `REPORT_RATE_LIMITED` 审计。 - 不影响屏蔽、查询、ADMIN 处理接口;不实现自动封禁。 ## 9. 通知闭环 - 处理完成后向举报人发送一条通知,复用 NotificationService,不新增枚举。 - `type=REPORT_DECISION`、`businessType=REPORT`、`businessId=reportId`、`idempotencyKey=report-decision-{reportId}`。 - RESOLVED / REJECTED 均可通知;同一举报最多一条处理结果通知(幂等)。 - 举报提交时不通知被举报人,不通知被举报人的联系方式。 ## 10. 审计 新增 action:`REPORT_CREATED`、`REPORT_DECIDED`、`BLOCK_CREATED`、`BLOCK_REMOVED`、`BLOCKED_SEND_PROHIBITED`、`REPORT_RATE_LIMITED`。 details 仅含:`reportId`、`userId`、`category`、`decision`、`targetType`、`messageId`、`requestId`、`timestamp`。 禁止写入:phone、email、password、JWT、resumeUrl、消息全文、文件绝对路径、evidence 文件内容、数据库连接信息。 ## 11. 并发与幂等 - 举报:`(reporter_user_id, idempotency_key)` 唯一 + `active_key` 唯一 + 创建事务;并发重复仅一条成功,另一条 9603 或同幂等返回原记录。 - 屏蔽:唯一约束保证并发 PUT 不产生重复关系;DELETE 幂等;屏蔽检查与发送消息在同一业务事务内。 - 处理:`@Version` 乐观锁,冲突 409/9605。 ## 12. 脱敏策略 - 响应永不返回 phone、email、passwordHash、resumeUrl、JWT、消息全文、举报人内部 ID(非 ADMIN)。 - MESSAGE 举报仅返回脱敏 `messagePreview`,不返回会话内其他敏感内容。 - 对外屏蔽发送统一模糊错误,不泄露屏蔽关系方向。 ## 13. OpenAPI 变化 - 新增 8 path / 9 operation,117 → 125 paths。 - 新 path:`/reports`、`/reports/me`、`/reports/{id}`、`/blocks/{blockedUserId}`(PUT+DELETE)、`/blocks/me`、`/admin/reports`、`/admin/reports/{id}`、`/admin/reports/{id}/decision`。 - 声明:bearerAuth、请求/响应 DTO、USER/MESSAGE 枚举、PENDING/RESOLVED/REJECTED 枚举、400/401/403/404/409/429、屏蔽模糊错误、MESSAGE 需 messageId、USER 需 targetUserId。 ## 14. 测试覆盖要点 1. USER 举报;2. MESSAGE 举报;3. MESSAGE 发送者服务端推导;4. 无权访问 messageId;5. 举报自己;6. 不存在 targetUserId;7. evidenceUrls 校验;8. 幂等同请求体;9. 幂等不同请求体;10. PENDING 重复;11. ADMIN 决定;12. 重复决定;13. 决定历史;14. 屏蔽/解除;15. 重复屏蔽/解除;16. 屏蔽后禁止 DIRECT;17. 屏蔽后禁止发送;18. 历史消息可读;19. 失败消息不补发;20. 频率限制;21. 通知幂等;22. 权限矩阵;23. 脱敏;24. 乐观锁并发;25. 业务数据不删除;26. V33 迁移 H2(MODE=MySQL)/MySQL 兼容。