package com.mydreamjob.safety; import com.mydreamjob.audit.AuditContext; import com.mydreamjob.auth.User; import com.mydreamjob.auth.UserRepository; import com.mydreamjob.common.api.ApiResponse; import com.mydreamjob.common.api.PageResponse; import com.mydreamjob.common.exception.BusinessException; import com.mydreamjob.common.exception.ErrorCode; import com.mydreamjob.safety.dto.CreateReportRequest; import com.mydreamjob.safety.dto.ReportResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; /** * 举报接口(V33)。 * *

身份仅取自 JWT,不接受 reporterUserId。举报人本人或 ADMIN 可查看详情; * 被举报人无法看到举报人身份。未登录 401。

*/ @Tag(name = "举报", description = "用户举报与查询") @RestController @RequiredArgsConstructor public class ReportController { /** R01-A3:身份只来自安全上下文,不接受请求参数传入的 userId。 */ private final com.mydreamjob.security.CurrentUserAccessor currentUserAccessor; /** R01-A3:requestId 只来自过滤器规范化后的唯一产出点。 */ private final com.mydreamjob.common.logging.RequestIdAccessor requestIdAccessor; private final ReportService reportService; private final UserRepository userRepository; @Operation(summary = "提交举报", description = "targetType=USER 需 targetUserId;targetType=MESSAGE 需 messageId(服务端推导发送者为被举报人)。" + "幂等键重放:同键同请求返回原单 200,同键不同请求 409/9606;待处理重复 409/9603;" + "举报自己 400/9602;限流 429/9609。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "参数错误(1001/9602/9607)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "未登录"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "无权访问该消息(2002)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "目标用户不存在(4001)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "重复举报/幂等冲突(9603/9606)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "429", description = "举报限流(9609)") }) @PostMapping("/reports") public ResponseEntity> create( @Valid @RequestBody CreateReportRequest req, HttpServletRequest request) { Long userId = currentUserAccessor.requireUserId(); ReportResponse data = reportService.create(userId, req, AuditContext.from(request)); return ResponseEntity.ok(ApiResponse.ok(data, requestIdAccessor.get(request))); } /** /reports/me 必须在 /reports/{id} 之前注册(路由回归测试覆盖) */ @Operation(summary = "我的举报列表", description = "仅返回当前用户创建的举报;支持 status/page/size;不含其他举报人信息、联系方式、消息全文。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "未登录") }) @GetMapping("/reports/me") public ResponseEntity>> listMine( @RequestParam(required = false) String status, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size, HttpServletRequest request) { Long userId = currentUserAccessor.requireUserId(); Page result = reportService.listMine(userId, status, page, size); return ResponseEntity.ok(ApiResponse.ok(PageResponse.of( result.getContent(), result.getTotalElements(), result.getNumber(), result.getSize()), requestIdAccessor.get(request))); } @Operation(summary = "我的举报详情", description = "当前举报人本人或 ADMIN 可查看;其他用户 403/2002。" + "ADMIN 可见处置历史与更完整画像;被举报人无法查看举报人身份。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "未登录"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "非举报人且非 ADMIN(2002)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "举报不存在(9601)") }) @GetMapping("/reports/{id}") public ResponseEntity> getDetail( @PathVariable Long id, HttpServletRequest request) { Long userId = currentUserAccessor.requireUserId(); ReportResponse data = reportService.getDetail(userId, id, isAdmin(userId)); return ResponseEntity.ok(ApiResponse.ok(data, requestIdAccessor.get(request))); } private boolean isAdmin(Long userId) { return userRepository.findById(userId) .map(u -> u.getRoles() != null && u.getRoles().stream().anyMatch(r -> r == User.Role.ADMIN)) .orElse(false); } }