import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.util.WebUtils;
import javax.security.auth.login.FailedLoginException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.List;
import java.util.Objects;
import static org.springframework.http.HttpHeaders.WWW_AUTHENTICATE;
/**
* 全局异常处理类
*/
@Slf4j
@ControllerAdvice
@RestController
public class GlobalExceptionHandler implements ErrorController {
private static final String PATH = "/error";
private static final String JSON_ERROR_INFO = "JSON parse error:";
private static final String SEMICOLON = ";";
private static final char BLANK = ' ';
private static final int CUT_LENGTH = 100;
@RequestMapping(value = PATH, produces = {MediaType.APPLICATION_JSON_VALUE})
@ResponseBody
public ResponseEntity<Body<Void>> errorHandler(HttpServletResponse response, HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE);
String message = String.valueOf(request.getAttribute(WebUtils.ERROR_MESSAGE_ATTRIBUTE));
log.warn("[Handle_Error] - status:{}, message:{}", statusCode, message);
response.setStatus(statusCode);
return ResponseUtil.of(HttpStatus.valueOf(statusCode), message);
}
@Override
public String getErrorPath() {
return PATH;
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(MissingServletRequestParameterException e) {
StringBuilder sb = new StringBuilder().append("未提供必选请求参数 '")
.append(e.getParameterName()).append("'(").append(e.getParameterType()).append(")");
log.warn("[Handle_MissingServletRequestParameterException] - {}", sb.toString());
return ResponseUtil.badRequest(sb.toString());
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(IllegalArgumentException e) {
log.warn("[Handle_IllegalArgumentException] - {}", e.getMessage());
return ResponseUtil.badRequest(e.getMessage());
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(MethodArgumentTypeMismatchException e) {
StringBuilder sb = new StringBuilder().append("参数 '").append(e.getName())
.append("'(").append(e.getRequiredType().getSimpleName()).append(")类型不匹配: ").append(e.getValue());
log.warn("[Handle_MethodArgumentTypeMismatchException] - {}", sb.toString());
return ResponseUtil.badRequest(sb.toString());
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(MethodArgumentNotValidException e) {
return ResponseUtil.badRequest(getErrorMsg(e.getBindingResult()));
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(BindException e) {
return ResponseUtil.badRequest(getErrorMsg(e.getBindingResult()));
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(ConstraintViolationException e) {
log.warn("[Handle_ConstraintViolationException] - {}", e.getMessage());
final var violations = e.getConstraintViolations();
if (CollectionUtils.isEmpty(violations)) {
return ResponseUtil.badRequest("未知错误");
}
final var first = violations.stream().findFirst();
final ConstraintViolation<?> violation = first.get();
if (Objects.isNull(violation)) {
}
return ResponseUtil.badRequest(violation.getMessage());
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(ResourceNotFoundException e) {
log.warn("[Handle_ResourceNotFoundException] - {}", e.getMessage());
return ResponseUtil.serverError(e.getMessage());
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(HttpMessageNotReadableException e) {
log.error("[Handle_HttpMessageNotReadableException] - {}", e.getMessage());
String jsonErrorMsg;
if (e.getLocalizedMessage().contains(JSON_ERROR_INFO) &&
Objects.nonNull(jsonErrorMsg =
dealWithJsonExceptionError(e.getLocalizedMessage()))) {
return ResponseUtil.badRequest("格式转换错误,请检查" + jsonErrorMsg + "字段");
}
return ResponseUtil.serverError("消息不可读:" + StringUtils.substring(e.getMessage(), 0, CUT_LENGTH));
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(FailedLoginException e) {
String msg = e.getMessage();
log.warn("[Handle_FailedLoginException] - {}", msg);
return ResponseUtil.badRequest("登录失败:" + msg);
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(HttpServletResponse response, AccessDeniedException e) {
log.warn("[Handle_AccessDeniedException] - {}", e.getMessage());
StringBuilder wwwAuthenticateBuilder = new StringBuilder();
wwwAuthenticateBuilder.append("Bearer").append(" ")
.append("realm").append("=").append("xiaohoucode.com").append(", ")
.append("error").append("=").append("insufficient_scope");
response.setHeader(WWW_AUTHENTICATE, wwwAuthenticateBuilder.toString());
return ResponseUtil.forbidden(e.getMessage());
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(DataAccessException e) {
log.warn("[Handle_DataAccessException] - 数据访问异常 {}", ExceptionUtils.getStackTrace(e));
return ResponseUtil.serverError("数据访问异常,请联系技术支持!");
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(MyAppCustomException e) {
log.error("[Handle_MyAppCustomException] - {}", getSimpleStackTrace(e));
return ResponseUtil.serverError(StringUtils.substring(e.getMessage(), 0, CUT_LENGTH));
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<Body<Void>> exceptionHandler(Exception e) {
log.error("[Handle_Exception] - {}", ExceptionUtils.getStackTrace(e));
return ResponseUtil.serverError("抱歉,系统发生意外了。["
+ StringUtils.substring(e.getMessage(), 0, CUT_LENGTH) + "]");
}
private String getErrorMsg(BindingResult bindingResult) {
final var defaultMsg = "未知错误";
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
if (CollectionUtils.isEmpty(fieldErrors)) {
return defaultMsg;
}
FieldError error = fieldErrors.get(0);
if (Objects.isNull(error)) {
return defaultMsg;
}
log.warn("Binding error - {}: {}", error.getField(), error.getDefaultMessage());
return error.getDefaultMessage();
}
/**
* 针对性处理JsonException,友好返回异常信息
*
* @param errorMsg
* @return
*/
private String dealWithJsonExceptionError(String errorMsg) {
int lastSemicolon = errorMsg.lastIndexOf(SEMICOLON);
if (lastSemicolon != -1) {
int startIndex = lastSemicolon - 1;
while (startIndex > 0 && errorMsg.charAt(startIndex) != BLANK) {
startIndex--;
}
return errorMsg.substring(++startIndex, lastSemicolon);
}
return null;
}
private String getSimpleStackTrace(Exception e) {
StringBuilder simpleStackTraceBuilder = new StringBuilder();
try (BufferedReader bufferedReader = new BufferedReader(new StringReader(
ExceptionUtils.getStackTrace(e)))) {
String line = bufferedReader.readLine();
simpleStackTraceBuilder.append(line).append("\n");
while ((line = bufferedReader.readLine()) != null) {
if (StringUtils.contains(line, "com.tal.")) {
simpleStackTraceBuilder.append(line).append("\n");
}
}
} catch (Exception ex) {
// do nothing
}
return simpleStackTraceBuilder.toString();
}
}