package quant.rich.emoney.component; import lombok.extern.slf4j.Slf4j; import quant.rich.emoney.exception.PageNotFoundException; import quant.rich.emoney.exception.RException; import quant.rich.emoney.pojo.dto.LayPageResp; import quant.rich.emoney.pojo.dto.R; import java.lang.reflect.Method; import java.util.Collections; import java.util.stream.Collectors; import javax.security.auth.login.LoginException; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.ConstraintViolationException; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.resource.NoResourceFoundException; import com.fasterxml.jackson.databind.JsonNode; /** * 异常处理 */ @ControllerAdvice @Slf4j public class EmoneyAutoPlatformExceptionHandler { @Autowired HttpServletResponse response; @Autowired HttpServletRequest request; @ExceptionHandler({ BindException.class, ConstraintViolationException.class, MethodArgumentNotValidException.class }) @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public R handleBindException(Ex ex) { StringBuilder messageSb = new StringBuilder(); ex.getBindingResult().getAllErrors().forEach(error -> messageSb.append(error.getDefaultMessage()).append("\n")); log.warn("Resolved Exception {}", messageSb.substring(0, messageSb.length() - 1)); log.warn(httpServletRequestToString(request)); return bodyOrPage(HttpStatus.BAD_REQUEST, messageSb.substring(0, messageSb.length() - 1)); } @ExceptionHandler(LoginException.class) @ResponseBody @ResponseStatus(HttpStatus.NOT_ACCEPTABLE) public R handleLoginException(LoginException ex) { return bodyOrPage(HttpStatus.NOT_ACCEPTABLE, ex); } @ExceptionHandler(RException.class) @ResponseBody public R handleRException(RException ex) { response.setStatus(ex.getHttpStatus().value()); if (ex.getLogRequest()) { log.warn(httpServletRequestToString(request)); } return bodyOrPage(ex.getHttpStatus(), ex); } @ExceptionHandler(ServletException.class) @ResponseBody public R handleServletException(ServletException ex) { HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; if (ex instanceof HttpRequestMethodNotSupportedException) httpStatus = HttpStatus.METHOD_NOT_ALLOWED; response.setStatus(httpStatus.value()); return bodyOrPage(httpStatus, ex); } @ExceptionHandler(value = Exception.class) @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public R handleException(Exception ex) { if (ex instanceof PageNotFoundException) { throw (PageNotFoundException) ex; } String message = null; if (ex.getMessage() != null) { message = ex.getMessage(); } else if (ex.getCause() != null) { message = ex.getCause().getMessage(); } ex.printStackTrace(); log.warn("Resolved exception {}", message); log.warn(httpServletRequestToString(request)); return bodyOrPage(HttpStatus.INTERNAL_SERVER_ERROR, ex); } /** * 根据 @Autowired request 等自动判断当前请求是 text/html 还是 application/json * @return */ private boolean isPage() { boolean isPage = true; String accept; // 匹配当前 request 在控制层的方法,根据是否是 // @ResponseBody 注解、@RestController 注解、 // 已知的返回类型来判断返回的是否是页面 Object handler = request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE); if (handler instanceof HandlerMethod handlerMethod) { Method method = handlerMethod.getMethod(); Class controllerClass = handlerMethod.getBeanType(); boolean hasResponseBody = method.isAnnotationPresent(ResponseBody.class) || controllerClass.isAnnotationPresent(ResponseBody.class) || controllerClass.isAnnotationPresent(RestController.class); Class returnType = method.getReturnType(); boolean isDtoReturnType = returnType.equals(R.class) || returnType.equals(LayPageResp.class) || JsonNode.class.isAssignableFrom(returnType); if (hasResponseBody || isDtoReturnType) { isPage = false; } } if (isPage && (accept = request.getHeader("Accept")) != null) { int indexOfHtml = accept.indexOf("text/html"), indexOfJson = accept.indexOf("application/json"); if (indexOfHtml == -1 && indexOfJson != -1) { isPage = false; } else if (indexOfHtml != -1 && indexOfJson != -1) { isPage = indexOfHtml < indexOfJson; } } return isPage; } private R bodyOrPage(HttpStatus httpStatus, String message) { boolean isPage = isPage(); if (isPage) { throw message == null ? new RuntimeException("Page exception raised") : new RuntimeException(message); } R r = message != null ? R.status(httpStatus).setMessage(message).setData(message) : R.status(httpStatus); return r; } private R bodyOrPage(HttpStatus httpStatus, Exception ex) { boolean isPage = true; String message = null; if (ex instanceof RException || ex instanceof LoginException) { isPage = false; message = ex.getMessage(); } else { isPage = isPage(); } if (isPage) { if (ex instanceof NoResourceFoundException nrfe) { if (StringUtils.isNotEmpty(nrfe.getMessage()) && nrfe.getMessage().endsWith(" .well-known/appspecific/com.chrome.devtools.json.")) { // 傻逼 Chrome 开发工具在本地调试时默认调用该地址 // see: https://blog.ni18.in/well-known-appspecific-com-chrome-devtools-json-request/ return null; } } throw ex == null ? new RuntimeException("Page exception raised") : new RuntimeException(ex); } R r = message != null ? R.status(httpStatus).setMessage(message).setData(message) : R.status(httpStatus); return r; } private static String httpServletRequestToString(HttpServletRequest request) { StringBuilder sb = new StringBuilder(); sb.append("Request Method = \"" + request.getMethod() + "\", "); sb.append("Request URL Path = \"" + request.getRequestURL() + "\", "); String headers = Collections.list(request.getHeaderNames()).stream() .map(headerName -> headerName + " : " + Collections.list(request.getHeaders(headerName))) .collect(Collectors.joining(", ")); if (headers.isEmpty()) { sb.append("Request headers: NONE,"); } else { sb.append("Request headers: [" + headers + "],"); } String parameters = Collections.list(request.getParameterNames()).stream().map(p -> { String[] values = request.getParameterValues(p); return p + ": [" + StringUtils.join(values, ", ") + "]"; }).collect(Collectors.joining(", ")); if (parameters.isEmpty()) { sb.append("Request parameters: NONE."); } else { sb.append("Request parameters: [" + parameters + "]."); } return sb.toString(); } }