1 整体说明
在前后端分离的Web开发中,为便于开发,有必要统一后端接口的返回结果格式以及统一处理全局异常。常见的统一格式如下:
{"status":"100","message":"操作成功","data":"hello,world"}
为了避免各个微服务都自行实现返回结果的封装和全局异常的处理,可将上述功能抽取为公共服务,其他服务引入公共服务直接使用。
本文就逐步说明如何搭建公共服务实现统一后端接口的返回结果格式以及统一处理全局异常,并在其他Web业务服务中引用该公共服务。
2 公共服务搭建
2.1 服务自建自用
参考资料:SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!
2.1.1 定义返回结果格式
@DatapublicclassResultData<T>{privateintstatus;privateStringmessage;privateTdata;privatelongtimestamp;publicResultData(){this.timestamp=System.currentTimeMillis();}publicstatic<T>ResultData<T>success(Tdata){ResultData<T>resultData=newResultData<>();resultData.setStatus(ReturnCode.RC100.getCode());resultData.setMessage(ReturnCode.RC100.getMessage());resultData.setData(data);returnresultData;}publicstatic<T>ResultData<T>fail(intcode,Stringmessage){ResultData<T>resultData=newResultData<>();resultData.setStatus(code);resultData.setMessage(message);returnresultData;}}
/*定义状态码*/publicenumReturnCode{/**操作成功**/RC100(100,"操作成功"),/**操作失败**/RC999(999,"操作失败"),/**服务限流**/RC200(200,"服务开启限流保护,请稍后再试!"),/**服务降级**/RC201(201,"服务开启降级保护,请稍后再试!"),/**热点参数限流**/RC202(202,"热点参数限流,请稍后再试!"),/**系统规则不满足**/RC203(203,"系统规则不满足要求,请稍后再试!"),/**授权规则不通过**/RC204(204,"授权规则不通过,请稍后再试!"),/**access_denied**/RC403(403,"无访问权限,请联系管理员授予权限"),/**access_denied**/RC401(401,"匿名用户访问无权限资源时的异常"),/**服务异常**/RC500(500,"系统异常,请稍后重试"),INVALID_TOKEN(2001,"访问令牌不合法"),ACCESS_DENIED(2003,"没有权限访问该资源"),CLIENT_AUTHENTICATION_FAILED(1001,"客户端认证失败"),USERNAME_OR_PASSWORD_ERROR(1002,"用户名或密码错误"),UNSUPPORTED_GRANT_TYPE(1003,"不支持的认证模式");/**自定义状态码**/privatefinalintcode;/**自定义描述**/privatefinalStringmessage;ReturnCode(intcode,Stringmessage){this.code=code;this.message=message;}publicintgetCode(){returncode;}publicStringgetMessage(){returnmessage;}}
2.1.2 封装接口异常的返回结果
@Slf4j@RestControllerAdvicepublicclassRestExceptionHandler{/***默认全局异常处理。*/@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)publicResultData<String>exception(Exceptione){log.error("全局异常信息ex={}",e.getMessage(),e);returnResultData.fail(ReturnCode.RC500.getCode(),e.getMessage());}}
2.1.3 封装接口正常的返回结果
借助@RestControllerAdvice
注解和ResponseBodyAdvice
接口实现。
@RestControllerAdvice
是@RestController
注解的增强,可以实现三个方面的功能:全局异常处理、全局数据绑定、全局数据预处理。
ResponseBodyAdvice
接口的作用:拦截Controller
方法的返回值,统一处理返回值/响应体,一般用来统一返回格式,加解密,签名等等。
@RestControllerAdvice//拦截Controller方法的返回值,统一处理返回值/响应体,一般用于统一返回格式、加解密、签名等等publicclassRestResponseAdviceimplementsResponseBodyAdvice<Object>{@AutowiredprivateObjectMapperobjectMapper;/**是否支持advice功能*true支持,false不支持*/@Overridepublicbooleansupports(MethodParametermethodParameter,Class<?extendsHttpMessageConverter<?>>aClass){returntrue;}/**处理返回的结果数据*/@SneakyThrows@OverridepublicObjectbeforeBodyWrite(Objecto,MethodParametermethodParameter,MediaTypemediaType,Class<?extendsHttpMessageConverter<?>>aClass,ServerHttpRequestserverHttpRequest,ServerHttpResponseserverHttpResponse){if(oinstanceofString){returnobjectMapper.writeValueAsString(ResultData.success(o));}if(oinstanceofResultData){//对于已经是ResultData类型的结果不再封装,直接返回returno;}returnResultData.success(o);}}
2.1.4 在当前服务中验证
经过2.1.1~2.1.3节的处理,已经完成统一接口的返回数据格式和封装全局异常,这里直接在当前服务中创建2个接口进行验证。
@RestController@RequestMapping("/api/demo")publicclassRestDemo{@GetMapping("/hello")publicStringHello(){return"hello,world!";}@GetMapping("/wrong")publicintWrong(){return3/0;}}
项目结构:
接口调用正常的结果:
接口调用异常的结果:
2.2 公共服务安装
参考资料:
Maven 构建生命周期
springboot项目如何打包给其他项目引用
为了在其他服务中能以maven依赖项的方式引用2.1节搭建的公共服务,需要把公共服务安装到本地maven仓库或者部署到远程仓库。这里采用安装到本地仓库的方式进行演示。
Spring Boot打包的是Spring Boot特有格式的jar包,即可以运行的fat jar(生成jar包中源码对应的class文件在BOOT-INF目录),并不是传统的maven的JAR包,为此需要修改公共服务的pom.xml
配置文件,修改如下:
<build><plugins><!--<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin>--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target></configuration></plugin></plugins></build>
在IDEA的Terminal中执行maven项目安装命令:mvn clean install -Dmaven.test.skip=true
。
3 公共服务引用
搭建另一个SpringBoot Web业务服务,在这个服务中引用第2节搭建的公共服务以实现统一接口的返回结果格式的目的。
3.1 引入公共服务依赖项
公共服务的pom.xml
部分配置如下:
<groupId>com.example</groupId><artifactId>common-advice</artifactId><version>0.0.1-SNAPSHOT</version><name>common-advice</name><description>Publicservicethatwrapsinterfaceresults</description><properties><java.version>1.8</java.version></properties>
在业务服务的pom.xml
中添加依赖项:
<dependency><groupId>com.example</groupId><artifactId>common-advice</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
3.2 组件扫描路径添加公共服务
在业务服务的组件扫描路径(在服务入口增加@ComponentScan
配置)中增加公共服务的包路径,否则无法使@RestControllerAdvice
注解修饰的配置类生效。
查看@RestControllerAdvic
源码可知,它是封装了@ControllerAdvice
注解,而@ControllerAdvice
封装了@Component
注解,根据SpringBoot自动装配的规定,通过@ComponentScan
配置可以将@RestControllerAdvic
注解标记的组件加载。
@ComponentScan({"com.example.demorest","com.example.common.advice"})@SpringBootApplicationpublicclassDemorestApplication{publicstaticvoidmain(String[]args){SpringApplication.run(DemorestApplication.class,args);}}
3.3 编写接口验证
@DatapublicclassResultData<T>{privateintstatus;privateStringmessage;privateTdata;privatelongtimestamp;publicResultData(){this.timestamp=System.currentTimeMillis();}publicstatic<T>ResultData<T>success(Tdata){ResultData<T>resultData=newResultData<>();resultData.setStatus(ReturnCode.RC100.getCode());resultData.setMessage(ReturnCode.RC100.getMessage());resultData.setData(data);returnresultData;}publicstatic<T>ResultData<T>fail(intcode,Stringmessage){ResultData<T>resultData=newResultData<>();resultData.setStatus(code);resultData.setMessage(message);returnresultData;}}0
项目结构:
接口访问正常的结果:
接口访问异常的结果: