# web-spring-boot-starter **Repository Path**: liuyangbox/web-spring-boot-starter ## Basic Information - **Project Name**: web-spring-boot-starter - **Description**: spring web 常用配置:异常链接,Response包装,跨域 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2020-09-29 - **Last Updated**: 2021-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # web-spring-boot-starter > 提供web相关的常用配置,开箱即用,也支持高度的自定义与功能开关 ## 1.controller-web-spring-boot-starter - 跨域自动配置`com.zhituanbox.web.controller.CorsAutoConfiguration` - Rest接口返回值包装`com.zhituanbox.web.controller.RestWrapperResponseBodyAutoConfiguration` - 异常处理`com.zhituanbox.web.controller.ExceptionAutoConfiguration` ### 1.1 跨域配置 ```properties # 默认开启 spring.zhituanbox.web.cors.enable=true spring.zhituanbox.web.cors.dynamic=true # 默认:* spring.zhituanbox.web.cors.allowed-headers=* # 默认:* spring.zhituanbox.web.cors.allowed-origins=* # 默认:* spring.zhituanbox.web.cors.allowed-methods=PUT, OPTIONS, GET, HEAD, PATCH, POST, DELETE spring.zhituanbox.web.cors.max-age=1H spring.zhituanbox.web.cors.with-credentials=true ``` **spring.zhituanbox.web.cors.dynamic** ```java /** * 是否动态响应 *

当CorsProperties#withCredentials设置为true时,也会采用动态模式

*

当允许的时候,显示的参数为客户端传过来的值;解决火狐中设置*跨域失败问题

*/ private Boolean dynamic = Boolean.FALSE; ``` **spring.zhituanbox.web.cors.max-age** 跨域缓存时间,默认为`null`,则使用`spring`跨域配置默认时间`30min` ```java //org.springframework.web.servlet.config.annotation.CorsRegistration#maxAge /** * Configure how long in seconds the response from a pre-flight request * can be cached by clients. *

By default this is set to 1800 seconds (30 minutes). */ public CorsRegistration maxAge(long maxAge) { this.config.setMaxAge(maxAge); return this; } ``` **spring.zhituanbox.web.cors.with-credentials** ```java /** * 表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器 * @see HttpHeaders#ACCESS_CONTROL_ALLOW_CREDENTIALS */ @NotNull private Boolean withCredentials = Boolean.FALSE; ``` #### 1.1.1.启动日志 > 服务启动之后,控制台打印相关配置信息 ```verilog -------------------------------------------------------------------- | 跨域: DynamicCorsFilter.class | -------------------------------------------------------------------- | dynamic : true | | maxAge : 1小时 | withCredentials : true | | allowedMethods : [GET, PATCH, PUT, DELETE, OPTIONS, HEAD, POST] | | allowedOrigins : [*] | | allowedHeaders : [*] | | enable : true | -------------------------------------------------------------------- ``` ### 1.2.Rest接口返回值包装 ``` # 是否开启返回值包装:默认开启 spring.zhituanbox.web.response.wrapper.enable=true spring.zhituanbox.web.response.wrapper.exclude-classes=java.io.File ``` **spring.zhituanbox.web.response.wrapper.exclude-classes** ```java /** * 需要排除的返回值类型 *

当接口返回的是此类型时,不进行包装

*

该类与该类的子类都不会进行包装

* @see cn.hutool.core.util.ClassUtil#isAssignable(Class, Class) * @see RestWrapperResponseBodyHandler * @see RestWrapperResponseBodyAdvice */ private Class[] excludeClasses; ``` #### 1.2.1.`@UnWrapper` `com.zhituanbox.web.controller.core.response.annotation.UnWrapper` 此注解可以使用在`Controller`类或者`Controller`方法 - 打在类上,则表示该类下所有方法都不进行包装 - 打在方法上,则表示该方法不进行包装 #### 1.2.2.启动日志 ``` ------------------------------------------------ | 全局返回值处理: RestWrapperResponseBodyAdvice.cla | ------------------------------------------------ | excludeClasses : [class java.io.File] | | enable : true | ------------------------------------------------ ``` #### 1.2.3.测试 ```java @RestController public class RestWrapperResponseBodyController { @GetMapping("/wrapper/string") public String stringWrapper() { return "wrapper"; } } ``` 访问[http://localhost:9003/wrapper/string](http://localhost:9003/wrapper/string) ```json // 请求返回值 { "code": "success", "message": null, "data": "wrapper" } ``` #### 1.2.4.返回值结构 默认实现返回的包装接口为:`com.zhituanbox.web.controller.support.response.DefaultRestWrapperResponseBodyHandler` 如果想自定义,则自定义实现`com.zhituanbox.web.controller.core.response.RestWrapperResponseBodyHandler`接口即可 **示例** 将返回值接口修改成`Map`,key:为返回值类名 ```java public class CustomRestWrapperResponseBodyHandler implements RestWrapperResponseBodyHandler> { @Override public Map wrap(Object body) { String key = Optional.ofNullable(body).map(Object::getClass).map(Class::getSimpleName).orElse("null"); return MapUtil.of(key, body); } } ``` ```json // 请求返回值 { "String": "wrapper" } ``` ### 1.3.异常处理 ```properties # 开启异常处理,默认开启 spring.zhituanbox.web.exception.enable=true # 是否开启debug模式,默认开启 spring.zhituanbox.web.exception.debug=true ``` **spring.zhituanbox.web.exception.debug** 返回异常返回值中加入堆栈信息,方便开发人员定位问题;生产中不建议开启 ```java /** * debug模式 *
    *
  • 接口异常返回数据中返回exceptionLog
  • *
  • 接口异常返回数据中返回异常的StackTrace
  • *
*/ private boolean debug = true; ``` #### 1.3.1.测试 ```java @GetMapping("/exception/null") public String nullPointException() { throw new NullPointerException("测试为空"); } ``` 请求接口:[GET http://localhost:9003/exception/null](http://localhost:9003/exception/null) **未开启异常处理时** ```verilog Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Tue Mar 30 00:41:21 GMT+08:00 2021 There was an unexpected error (type=Internal Server Error, status=500). 测试为空 java.lang.NullPointerException: 测试为空 at com.zhituanbox.guide.ExceptionController.nullPointException(ExceptionController.java:19) ...... ``` **开启异常处理** ```json //未开启debug { "code": "java.lang.NullPointerException", "message": "空指针异常", "data": null, "businessException": false } ``` ```json //开启debug { "code": "java.lang.NullPointerException", "message": "空指针异常", "data": [ "com.zhituanbox.guide.ExceptionController.nullPointException(ExceptionController.java:19)", "com.zhituanbox.guide.ExceptionController$$FastClassBySpringCGLIB$$140d21da.invoke()", "org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)", "org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)", "org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)", "org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)", "......" ], "exceptionLog": "测试为空", "businessException": false } ``` ### 1.3.2.返回值结构 默认异常返回的结构 `com.zhituanbox.web.controller.support.response.ExceptionResponseData` > 从上述结果可知 | code | 异常码 | | ----------------- | ------------------------------------------------------------ | | message | 错误消息,供前端展示的友好消息 | | data | debug模式下,为异常堆栈 | | exceptionLog | debug模式下,供开发人员观看,可记录具体的异常数据,比如包含用户ID等 | | businessException | 是否为业务异常 | ### 1.3.3.业务异常 业务异常基类:`com.zhituanbox.core.exception.BusinessException` 当系统抛出的异常为`BusinessException`或其子类是,则`businessException=true` > 因此所有的业务异常都从`BusinessException`开始,或继承实现特定的业务异常,例如`UserBusinessException`、`AuthBusinessException` #### 1.3.3. 返回值`code` 当抛出的异常为`BusinessException`;`code`码为填写的异常码,对应`message`从`messageSource(messages.properties)`中读取对应国际化内容 其余都将异常的`classname`设置为`code`,也将从`messageSource`中读取 > 此处需要注意,默认配置的`messageSource`默认优先从已经配置的messages当中读取,当读取不到时,读取`classpath:messages-controller.properties` ### 1.3.4.扩展异常返回值 扩展异常返回值只需要实现`com.zhituanbox.web.controller.core.execption.IExceptionHandler` 已经为你提供好了便于扩展的抽象类`com.zhituanbox.web.controller.core.execption.AbstractExceptionHandler` 常用异常扩展已经实现完毕 - BusinessExceptionExceptionHandler - MissingServletRequestParameterExceptionHandler - HttpRequestMethodNotSupportedExceptionHandler - MissingPathVariableExceptionHandler - MissingServletRequestPartExceptionHandler - MaxUploadSizeExceededExceptionHandler - FinalExceptionHandler > 此处的异常匹配排序,按照打分排序,如果异常抛出异常距离定义异常越远,则分越高 > > 源码 > > `com.zhituanbox.web.controller.core.execption.ExceptionHandlerAdvice#matchBestExceptionHandler` 对于使用者来说,只需要在`messages.properties`定义messages ```properties java.lang.NullPointerException=发生了一个空指针 business.code.1=业务异常一 ``` 此时,当发生空指针异常时,响应 ```json //未开启debug { "code": "java.lang.NullPointerException", "message": "发生了一个空指针", "data": null, "businessException": false } ``` 发生业务异常时 ```java @GetMapping("/exception/business") public String business(@RequestParam("message") @Name String message) { throw new BusinessException("因为测试业务异常", "business.code.1"); } ``` ```json //未开启debug { "code": "business.code.1", "message": "业务异常一", "data": null, "businessException": true } ``` > messageSource建议配合`extensions-message-source-spring-boot-starter`一起使用,更能实现高度自定义