diff --git a/README.md b/README.md index ff7104d41f172d2aab68d72d1817f70e32f79424..3fb94742ff03194566c6fed782b54fa102382d0c 100644 --- a/README.md +++ b/README.md @@ -599,6 +599,36 @@ public ParamHandler paramHandler(){ ApprovedFileValidator.mimeTypeMagic.put(MimeTypeUtils.parseMimeType("image/png"),"89504e47"); ``` - + ### update 2019/11/15 支持自定义异常处理 + 在此之前,都是框架帮忙把所有异常进行了拦截,但如果用户需要自己定义异常拦截时将没有任何办法,此次更新兼容了用户的异常处理,并将我的全局异常处理定义为最低优先级,如果你发现你的异常处理没有生效(大部分情况下会优先加载应用中的,虽然是同一优先级),请将你的异常处理优先级提高,设置如下 + + ```java + @Order(Ordered.LOWEST_PRECEDENCE - 20) // 数字越小,优先级越高 + public class CustomExceptionHandler + ``` + + 并且将返回类型定义为你自定义的返回类型 ,像这样 + + ```java + @ExceptionHandler(IllegalArgumentException.class) + public ResponseDto exception(IllegalArgumentException e){ + return ResponseDto.err("222").message(e.getMessage()); + } + ``` + + 如果你的异常处理没有拦截到异常,将继续进入我的全局异常处理 ,把异常类名做为 code,把消息放入 message + + + + **解决 bug** + + 支持将一些其它框架的返回忽略全局输出,像 swagger-ui 使用了自己的数据结构,在此之前,框架是照样拦截导致 swagger-ui 无法打开,目前已经支持 swaggerui 无需配置,如果有其它的框架类,你可以这样进行配置 + + ```properties + webui.advice.ignore.package=正则包路径过滤,忽略框架包 + ``` + + + diff --git a/src/main/java/com/sanri/web/configs/AbstractResponseBodyAdvice.java b/src/main/java/com/sanri/web/configs/AbstractResponseBodyAdvice.java new file mode 100644 index 0000000000000000000000000000000000000000..c38c726f04c969233e8cdb4e93356fe70006c07b --- /dev/null +++ b/src/main/java/com/sanri/web/configs/AbstractResponseBodyAdvice.java @@ -0,0 +1,34 @@ +package com.sanri.web.configs; + +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.MethodParameter; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import java.lang.reflect.Executable; + +public abstract class AbstractResponseBodyAdvice implements ResponseBodyAdvice { + /** + * 对于像 swaggerui 等,排除部分包的处理,使用它本身的映射 + * 这里写正则表达式 + */ + @Value("${webui.advice.ignore.package:}") + protected String adviceIgnorePackage; + + public boolean supports(MethodParameter returnType, Class converterType) { + Executable executable = returnType.getExecutable(); + Class declaringClass = executable.getDeclaringClass(); + String packageName = ClassUtils.getPackageName(declaringClass); + + //添加常用技术的过滤支持,如 swagger-ui + if(packageName.startsWith("springfox.documentation.swagger")){ + return false; + } + + if(StringUtils.isNotBlank(adviceIgnorePackage) && packageName.matches(adviceIgnorePackage)){ + return false; + } + return true; + } +} diff --git a/src/main/java/com/sanri/web/configs/CustomResponseBodyAdvice.java b/src/main/java/com/sanri/web/configs/CustomResponseBodyAdvice.java deleted file mode 100644 index 98d7386e96446578d3db7f588124781cad4f631c..0000000000000000000000000000000000000000 --- a/src/main/java/com/sanri/web/configs/CustomResponseBodyAdvice.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.sanri.web.configs; - -import com.sanri.web.dto.ResponseDto; -import com.sanri.web.dto.TreeResponseDto; -import com.sanri.web.helper.TreeResponseDtoHelper; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; -import org.springframework.cglib.core.ReflectUtils; -import org.springframework.core.MethodParameter; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; -import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; - -import java.lang.reflect.AnnotatedType; -import java.lang.reflect.Executable; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * 可以定义空返回的时候返回正确的信息,如成功信息 - */ -@RestControllerAdvice -public class CustomResponseBodyAdvice implements ResponseBodyAdvice { - @Autowired(required = false) - private ResponseHandler responseHandler; - - public boolean supports(MethodParameter returnType, Class converterType) { - return true; - } - - public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { - Executable executable = returnType.getExecutable(); - AnnotatedType annotatedReturnType = executable.getAnnotatedReturnType(); - Type type = annotatedReturnType.getType(); - - // 对于异常的处理 - ResponseDto errorResponse = null; - if(executable.getDeclaringClass() == BasicErrorController.class){ - // 如果是方法参数错误 ,被 BasicErrorController 拦截,则只需要取其中的 message 信息做返回 - ResponseEntity responseEntity = (ResponseEntity) body; - HttpStatus statusCode = responseEntity.getStatusCode(); - Map map = (Map) responseEntity.getBody(); - Object message = map.get("message"); - errorResponse = ResponseDto.err(statusCode.value() + "").message(Objects.toString(message)); - } - if(executable.getDeclaringClass() == GlobalExceptionHandler.class){ - //如果是出异常了,直接返回错误处理,如果在异常处理中没有包裹消息,则包裹 - if(type != ResponseDto.class){ //暂时只支持 String 类型,后面可以扩展成层级对象 - String errorInfo = Objects.toString(body); - errorResponse = ResponseDto.err().message(errorInfo); - } - errorResponse = (ResponseDto) body; - } - if(errorResponse != null){ - if(responseHandler != null) { - Object handlerError = responseHandler.handlerError(errorResponse); - return handlerError; - } - return errorResponse; - } - - // 对于正常输出的处理 - ResponseDto outResponse = null; - if(type == ResponseDto.class){ - // 如果本身就返回的是结果类型,则不用转换 - outResponse = (ResponseDto) body; - } - if(type == Void.TYPE){ - // 空返回直接返回成功 - outResponse = ResponseDto.ok(); - } - - if(type instanceof ParameterizedTypeImpl) { - // 如果是 List 类型,并明确说明结果要返回树结构,则做树结构返回 - if (((ParameterizedTypeImpl) type).getRawType() == List.class) { - TreeResponse treeResponse = executable.getAnnotation(TreeResponse.class); - if (treeResponse != null) { - List modifyList = new ArrayList(); - Class treeReposeDtoClass = treeResponse.type(); - String rootId = treeResponse.rootId(); - String rootParentId = treeResponse.rootParentId(); - - // 转换结构成可支持树结构 - List results = (List) body; - for (Object result : results) { - TreeResponseDto newInstance = (TreeResponseDto) ReflectUtils.newInstance(treeReposeDtoClass, new Class[]{result.getClass()}, new Object[]{result}); - modifyList.add(newInstance); - } - - //转换成树结构 - if (StringUtils.isNotBlank(rootId)) { - body = TreeResponseDtoHelper.fastConvertTree(modifyList, rootId); - } else if (StringUtils.isNotBlank(rootParentId)) { - body = TreeResponseDtoHelper.fastConvertForest(modifyList, rootParentId); - } - } - } - } - - // 否则使用成功消息包裹数据 - outResponse = ResponseDto.ok().data(body); - if(responseHandler != null){ - return responseHandler.handlerOut(outResponse); - } - return outResponse; - } -} diff --git a/src/main/java/com/sanri/web/configs/ExceptionResponseBodyAdvice.java b/src/main/java/com/sanri/web/configs/ExceptionResponseBodyAdvice.java new file mode 100644 index 0000000000000000000000000000000000000000..5ca205e6734c771501e7d2b27c79696a508071a3 --- /dev/null +++ b/src/main/java/com/sanri/web/configs/ExceptionResponseBodyAdvice.java @@ -0,0 +1,93 @@ +package com.sanri.web.configs; + +import com.sanri.web.dto.ResponseDto; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; +import org.springframework.core.MethodParameter; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Objects; + +/** + * 异常信息收集处理 + */ +@RestControllerAdvice +@Slf4j +@Order(Ordered.LOWEST_PRECEDENCE - 3) +public class ExceptionResponseBodyAdvice extends AbstractResponseBodyAdvice { + @Override + public boolean supports(MethodParameter returnType, Class converterType) { + boolean supports = super.supports(returnType, converterType); + if(!supports)return supports; + + Executable executable = returnType.getExecutable(); + Class declaringClass = executable.getDeclaringClass(); + RestControllerAdvice controllerAdvice = declaringClass.getAnnotation(RestControllerAdvice.class); + ExceptionHandler exceptionHandler = executable.getAnnotation(ExceptionHandler.class); + if(declaringClass == BasicErrorController.class){ + return true; + } + if(controllerAdvice != null && exceptionHandler != null){ + return true; + } + return false; + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { + Executable executable = returnType.getExecutable(); + Method method = returnType.getMethod(); + + AnnotatedType annotatedReturnType = executable.getAnnotatedReturnType(); + Type type = annotatedReturnType.getType(); + Class declaringClass = executable.getDeclaringClass(); + RestControllerAdvice controllerAdvice = declaringClass.getAnnotation(RestControllerAdvice.class); + + if(declaringClass == BasicErrorController.class){ + // 如果已经被 BasicErrorController 拦截,则只需要取其中的 message 信息做返回 + ResponseEntity responseEntity = (ResponseEntity) body; + HttpStatus statusCode = responseEntity.getStatusCode(); + Map map = (Map) responseEntity.getBody(); + Object message = map.get("message"); + return ResponseDto.err(statusCode.value() + "").message(Objects.toString(message)); + } + + // 经测试,这里的值只能有一个,方法可以返回 void ,表示不处理异常 + ExceptionHandler exceptionHandler = executable.getAnnotation(ExceptionHandler.class); + if(controllerAdvice != null && exceptionHandler != null){ //当前方法必须保证是 ControllerAdvice 并且是 ExceptionHandler + Class[] exceptions = exceptionHandler.value(); + Class exception = exceptions[0]; + //如果是进入全局异常处理,则处理全局异常中的返回 + if(type == Void.class){ + //如果返回空,不处理异常,返回前端成功,给出一个警告信息 + log.warn("异常未处理,异常处理方法为:{},异常类为:",method,exception); + return ResponseDto.ok(); + } + if(type == ResponseDto.class){ + return body; + } + + // 将会把返回值转 String,请事先实现 toString 接口 + String errorInfo = Objects.toString(body); + + return ResponseDto.err(exception.getSimpleName()).message(errorInfo); + } + + return body; + } +} diff --git a/src/main/java/com/sanri/web/configs/GlobalExceptionHandler.java b/src/main/java/com/sanri/web/configs/GlobalExceptionHandler.java index 1e4a1fccdb929cc347cb0da935eeff39a7c7c478..8a5081ea3d9be2d480ee1bf1aa842eded874be38 100644 --- a/src/main/java/com/sanri/web/configs/GlobalExceptionHandler.java +++ b/src/main/java/com/sanri/web/configs/GlobalExceptionHandler.java @@ -8,6 +8,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.MethodParameter; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; @@ -21,8 +23,12 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +/** + * 全局异常处理具有较低的优先级 + */ @RestControllerAdvice @Slf4j +@Order(Ordered.LOWEST_PRECEDENCE) public class GlobalExceptionHandler { @Value("${sanri.webui.package.prefix:com.sanri}") @@ -101,7 +107,7 @@ public class GlobalExceptionHandler { } /** - * 异常处理,可以绑定多个 + * 异常处理,如果子类没有覆盖特定异常的话,将使用默认处理 * @return */ @ExceptionHandler(Exception.class) diff --git a/src/main/java/com/sanri/web/configs/NormalResponseBodyAdvice.java b/src/main/java/com/sanri/web/configs/NormalResponseBodyAdvice.java new file mode 100644 index 0000000000000000000000000000000000000000..a1a70178ec842ae27645c2b48add48263d9845cc --- /dev/null +++ b/src/main/java/com/sanri/web/configs/NormalResponseBodyAdvice.java @@ -0,0 +1,97 @@ +package com.sanri.web.configs; + +import com.sanri.web.dto.ResponseDto; +import com.sanri.web.dto.TreeResponseDto; +import com.sanri.web.helper.TreeResponseDtoHelper; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.core.MethodParameter; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; +import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; + +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Executable; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * 正常数据收集处理 + */ +@RestControllerAdvice +@Slf4j +@Order(Ordered.LOWEST_PRECEDENCE - 2) +public class NormalResponseBodyAdvice extends AbstractResponseBodyAdvice { + + + public boolean supports(MethodParameter returnType, Class converterType) { + boolean supports = super.supports(returnType, converterType); + if(!supports)return supports; + + Executable executable = returnType.getExecutable(); + Class declaringClass = executable.getDeclaringClass(); + RestControllerAdvice controllerAdvice = declaringClass.getAnnotation(RestControllerAdvice.class); + ExceptionHandler exceptionHandler = executable.getAnnotation(ExceptionHandler.class); + if(controllerAdvice != null && exceptionHandler != null){ + return false; + } + + return true; + } + + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { + Executable executable = returnType.getExecutable(); + AnnotatedType annotatedReturnType = executable.getAnnotatedReturnType(); + Type type = annotatedReturnType.getType(); + + // 如果空,则为成功 + if(type == Void.TYPE){ + // 空返回直接返回成功 + return ResponseDto.ok(); + } + //如果本身返回 ResponseDto 则不处理 + if(type == ResponseDto.class){ + return body; + } + //检查是否需要树状结构返回 + TreeResponse treeResponse = executable.getAnnotation(TreeResponse.class); + if(treeResponse != null){ + //检查原始返回类型是否为 List 类型,否则给出异常 + if((type instanceof ParameterizedTypeImpl) && ((ParameterizedTypeImpl) type).getRawType() == List.class) { + List modifyList = new ArrayList(); + Class treeReposeDtoClass = treeResponse.type(); + String rootId = treeResponse.rootId(); + String rootParentId = treeResponse.rootParentId(); + + // 转换结构成可支持树结构 + List results = (List) body; + for (Object result : results) { + TreeResponseDto newInstance = (TreeResponseDto) ReflectUtils.newInstance(treeReposeDtoClass, new Class[]{result.getClass()}, new Object[]{result}); + modifyList.add(newInstance); + } + + //转换成树结构,根据需要转成森林或树结构 + if (StringUtils.isNotBlank(rootId)) { + body = TreeResponseDtoHelper.fastConvertTree(modifyList, rootId); + } else if (StringUtils.isNotBlank(rootParentId)) { + body = TreeResponseDtoHelper.fastConvertForest(modifyList, rootParentId); + } + + return ResponseDto.ok().data(body); + } + throw new IllegalArgumentException("树类型返回要求原始数据为 List 类型"); + } + + return ResponseDto.ok().data(body); + } +} diff --git a/src/main/java/com/sanri/web/configs/SupportOldProjectAdvice.java b/src/main/java/com/sanri/web/configs/SupportOldProjectAdvice.java new file mode 100644 index 0000000000000000000000000000000000000000..0d2f44fee2ffcc1deed60f5912196240b16fd087 --- /dev/null +++ b/src/main/java/com/sanri/web/configs/SupportOldProjectAdvice.java @@ -0,0 +1,54 @@ +package com.sanri.web.configs; + +import com.sanri.web.dto.ResponseDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.MethodParameter; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import java.lang.reflect.Executable; + +@RestControllerAdvice +@Order(Ordered.LOWEST_PRECEDENCE) +public class SupportOldProjectAdvice extends AbstractResponseBodyAdvice { + @Autowired(required = false) + private ResponseHandler responseHandler; + + @Override + public boolean supports(MethodParameter returnType, Class converterType) { + boolean supports = super.supports(returnType, converterType); + if(!supports)return supports; + + Executable executable = returnType.getExecutable(); + Class declaringClass = executable.getDeclaringClass(); + RestControllerAdvice controllerAdvice = declaringClass.getAnnotation(RestControllerAdvice.class); + ExceptionHandler exceptionHandler = executable.getAnnotation(ExceptionHandler.class); + if(controllerAdvice != null && exceptionHandler != null){ + return false; + } + return true; + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { + if((body instanceof ResponseDto) && responseHandler != null){ + ResponseDto responseDto = (ResponseDto) body; + String code = responseDto.getCode(); + Object newBody = null; + if("0".equals(code)){ + newBody = responseHandler.handlerError(responseDto); + }else{ + newBody = responseHandler.handlerOut(responseDto); + } + + return newBody; + } + return body; + } +} diff --git a/src/main/java/com/sanri/web/configs/WebConfig.java b/src/main/java/com/sanri/web/configs/WebConfig.java index c86326560a2aa4e2cab2b425008b61f748845d14..1b58547f28d8253bf47196b47377ae783ec23454 100644 --- a/src/main/java/com/sanri/web/configs/WebConfig.java +++ b/src/main/java/com/sanri/web/configs/WebConfig.java @@ -43,8 +43,16 @@ public class WebConfig { * @return */ @Bean - public CustomResponseBodyAdvice customResponseBodyAdvice(){ - return new CustomResponseBodyAdvice(); + public ExceptionResponseBodyAdvice exceptionResponseBodyAdvice(){ + return new ExceptionResponseBodyAdvice(); + } + @Bean + public NormalResponseBodyAdvice normalResponseBodyAdvice(){ + return new NormalResponseBodyAdvice(); + } + @Bean + public SupportOldProjectAdvice supportOldProjectAdvice(){ + return new SupportOldProjectAdvice(); } /**