package com.talentcard.server.common; import java.util.stream.Collectors; import jakarta.validation.ConstraintViolationException; import org.springframework.dao.DuplicateKeyException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.server.ResponseStatusException; import org.springframework.validation.BindException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(BusinessException.class) public ResponseEntity> handleBusinessException(BusinessException exception) { HttpStatus status = HttpStatus.valueOf(exception.errorCode().code()); return ResponseEntity.status(status) .body(ApiResponse.failure(exception.errorCode().code(), exception.getMessage())); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity> handleMethodArgumentNotValid(MethodArgumentNotValidException exception) { String message = exception.getBindingResult().getFieldErrors().stream() .map(error -> error.getField() + " " + error.getDefaultMessage()) .collect(Collectors.joining("; ")); return badRequest(message); } @ExceptionHandler(BindException.class) public ResponseEntity> handleBindException(BindException exception) { String message = exception.getBindingResult().getFieldErrors().stream() .map(error -> error.getField() + " " + error.getDefaultMessage()) .collect(Collectors.joining("; ")); return badRequest(message); } @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity> handleConstraintViolation(ConstraintViolationException exception) { String message = exception.getConstraintViolations().stream() .map(violation -> violation.getPropertyPath() + " " + violation.getMessage()) .collect(Collectors.joining("; ")); return badRequest(message); } @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity> handleUnreadableMessage(HttpMessageNotReadableException exception) { return badRequest("请求内容格式不正确"); } /** * 权限拦截器用 {@link ResponseStatusException} 表达 403;不显式处理会被下面的兜底分支包成 500, * 前端只看到「服务器内部错误」,看不出是权限问题。这里原样透出状态码和原因。 */ @ExceptionHandler(ResponseStatusException.class) public ResponseEntity> handleResponseStatus(ResponseStatusException exception) { int status = exception.getStatusCode().value(); String reason = exception.getReason() == null || exception.getReason().isBlank() ? "请求被拒绝" : exception.getReason(); return ResponseEntity.status(status).body(ApiResponse.failure(status, reason)); } /** * 唯一键冲突(例如同一微信换绑并发、卡号/证件号重复)以前会掉进下面的兜底分支,前端只看到 * 「服务器内部错误」,用户除了反复重试没有别的办法。这里按约束名给出可读提示,返回 409。 */ @ExceptionHandler(DuplicateKeyException.class) public ResponseEntity> handleDuplicateKey(DuplicateKeyException exception) { Throwable cause = exception.getMostSpecificCause(); log.warn("Duplicate key conflict: {}", cause == null ? exception.getMessage() : cause.getMessage()); return ResponseEntity.status(HttpStatus.CONFLICT) .body(ApiResponse.failure(HttpStatus.CONFLICT.value(), duplicateKeyMessage(exception))); } private String duplicateKeyMessage(DuplicateKeyException exception) { Throwable cause = exception.getMostSpecificCause(); String detail = cause == null ? "" : String.valueOf(cause.getMessage()); if (detail.contains("uk_openid")) return "该微信已绑定到其它账号,请稍后重试或联系管理员处理"; if (detail.contains("uk_mobile")) return "该手机号已存在账号,请改用原手机号登录或联系管理员"; // 先判 id_card:'uk_id_card_no' 里也含 'card_no',顺序反了会把它当成卡号。 if (detail.contains("id_card")) return "该证件号已存在账号,请核对后重试"; if (detail.contains("card_no")) return "该人才卡号已被使用,请核对后重试"; return "数据已存在,请核对后重试"; } @ExceptionHandler(Exception.class) public ResponseEntity> handleException(Exception exception) { log.error("Unhandled request exception", exception); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(ApiResponse.failure(ErrorCode.INTERNAL_ERROR.code(), ErrorCode.INTERNAL_ERROR.message())); } private ResponseEntity> badRequest(String message) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(ApiResponse.failure(ErrorCode.BAD_REQUEST.code(), message)); } }