之所以想写这一系列,是因为之前工作过程中有几次项目是从零开始搭建的,而且项目涉及的内容还不少。在这过程中,遇到了很多棘手的非业务问题,在不断实践过程中慢慢积累出一些基本的实践经验,认为这些与业务无关的基本的实践经验其实可以复刻到其它项目上,在行业内可能称为脚手架,因此决定将此java基础脚手架的搭建总结下来,分享给大家使用。
注意:由于框架不同版本改造会有些使用的不同,因此本次系列中主要使用基本框架是 spring-boo-2.3.12.RELEASE和spring-cloud.-Hoxton.SR12,所有代码都在commonFramework项目上:https://github.com/forever1986/commonFramework/tree/master
1 统一返回格式和异常处理
1.1 统一返回格式
由于现在项目越来越多都是前后端分离,因此后端对于前端来说就是一个提供数据查询的接口,那么这个时候,数据格式的统一就非常有必要,这样前端也能通过数据返回值统一处理。下面以springboot为框架,restful接口为规范的例子,实践中一般如何定义统一的数据返回值。
参考common-core子模块和manage-biz子模块
1)独立在common子模块下再建立common-core子模块。
2)定义一个泛型的Result,其中属性包括code(返回编码)、msg(返回消息)、data(数据)、total(数据总量)、page(当前页数)等等,下面以一个简单例子:
public class Result<T> implements Serializable {
private String code;
private T data;
private String msg;
private long total;
//构造方法和方法省略,参考项目完整代码
}
3)定义ResultCode为常见的错误编码集合,方便使用
package com.demo.common.core.result;
import java.io.Serializable;
public enum ResultCode implements IResultCode, Serializable {
SUCCESS("00000", "ok"),
USER_ERROR("A0001", "用户端错误"),
PARAM_IS_NULL("A0410", "请求必填参数为空"),
SYSTEM_EXECUTION_ERROR("B0001", "系统执行出错");
private String code;
private String msg;
public String getCode() {
return this.code;
}
public String getMsg() {
return this.msg;
}
public String toString() {
return "{\"code\":\"" + this.code + '"' + ", \"msg\":\"" + this.msg + '"' + '}';
}
public static ResultCode getValue(String code) {
ResultCode[] var1 = values();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
ResultCode value = var1[var3];
if (value.getCode().equals(code)) {
return value;
}
}
return SYSTEM_EXECUTION_ERROR;
}
private ResultCode(String code, String msg) {
this.code = code;
this.msg = msg;
}
private ResultCode() {
}
}
4)这样一个简单的统一返回值就出来,我们可以改造一下manage-biz子模块的controller方法,比如改造echo方法:
@SysLog(module= ModuleTypeEnum.MANAGE, description="测试echo")
@ApiOperation(value = "测试echo")
@GetMapping("/echo")
public Result<String> echo(@RequestParam String echo) {
log.info("echo======================"+echo);
log.info(nacosValueConstant.getValue());
log.info(nacosValueConstant.getTest());
return Result.success(demoService.echo(echo));
}
1.2 异常处理
异常的友好除了对于前端或者用户体验来说都是一种提升。以前都是通过拦截器方式做统一的异常处理,但处理起来还是有点麻烦,而基于springboot的restful风格的后端,springboot提供了@RestControllerAdvice(如果不是restful风格,则可以@ControllerAdvice)统一做全局异常处理,对于我们来说真的省了不少事,这里结合1.1统一返回格式,也将异常作为统一的返回格式。
参考common-exception子模块和manage-biz子模块
1)在common子模块下面新增common-exception子模块,以spring.factories方式发布
2)pom文件中引入以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>common-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
3)定义GlobalExceptionHandler,使用注解@RestControllerAdvice,然后定义各种异常处理
package com.demo.common.exception;
import com.demo.common.core.result.Result;
import com.demo.common.core.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
//兜底的异常处理
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler({RuntimeException.class})
public <T> Result<T> handleRuntimeException(RuntimeException e) {
log.error("兜底异常,异常原因:{}", e.getMessage(), e);
return Result.failed(ResultCode.SYSTEM_EXECUTION_ERROR);
}
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler({BizException.class})
public <T> Result<T> handleBizException(BizException e) {
log.error("业务异常,异常原因:{}", e.getMessage(), e);
return e.getResultCode() != null ? Result.failed(e.getResultCode()) : Result.failed(e.getMessage());
}
}
4)可以定义各类自定义异常,比如我们定义一个BizException
package com.demo.common.exception;
import com.demo.common.core.result.IResultCode;
public class BizException extends RuntimeException{
public IResultCode resultCode;
public BizException(IResultCode errorCode) {
super(errorCode.getMsg());
this.resultCode = errorCode;
}
public BizException(String message) {
super(message);
}
public BizException(String message, Throwable cause) {
super(message, cause);
}
public BizException(Throwable cause) {
super(cause);
}
public IResultCode getResultCode() {
return this.resultCode;
}
}
5)在spring.factories中注入GlobalExceptionHandler
6)在manage-biz中引入common-exception
7)在manage-biz中的DemoController写一个测试接口:
@SysLog(module= ModuleTypeEnum.MANAGE, description="测试全局异常处理")
@ApiOperation(value = "测试全局异常处理")
@GetMapping("/testexception")
public Result<String> testException(Boolean isNotNull) {
log.info("call testException");
if(isNotNull==null || !isNotNull){
throw new BizException(ResultCode.PARAM_IS_NULL);
}
return Result.success("ok");
}
2 swagger2
现在项目基本上都是前后端分离的,特别是后端提供Rest API接口,因为为了适用于不同端(PC、IOS、Android等),因此后端都是统一一个(可能会采用不同api分流或者区分)。所以后端写的api需要调试和文档就非常重要,调试可以使用postman,但是每个接口都要自己建立一个request,文档可以使用javadoc,但是更新也是一个问题。这个时候swagger2顺势而生,它同时解决了调试和文档的问题。
Swagger是一套围绕Open API 规范构建的开源工具,可以帮助设计,构建,记录和使用REST API。简单理解就是后端项目通过配置Swagger,则通过在发布的API中加入注解,自动就会生成一个可视化的调试和文档界面。
2.1 代码实践
Swagger是一个辅助功能,因此可以提炼为一个common子模块
请参考common-swagger子模块和manage-biz子模块
1)在common子模块下新建common-swagger子模块(以spring.factories方式发布)
2)在pom配置如下依赖:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!-- 只是为了引入Slf4j -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
3)设置SwaggerConfiguration
package com.demo.common.swagger;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@Slf4j
@Profile({"dev","test"})
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.demo.manage.biz.controller"))//这里填写项目package
.paths(PathSelectors.any())
.build();
}//springfox为我们提供了一个Docket(摘要的意思)类,我们需要把它做成一个Bean注入到spring中, 显然,我们需要一个配置文件,并通过一种方式(显然它会是一个注解)告诉程序,这是一个Swagger配置文件。
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("demo API")
.description("rest api 文档构建利器")
.version("1.0")
.build();
}
}
这里要说明一下,其中@Profile({“dev”,“test”})配置了dev、test环境才开启(生产环境不开启swagger);
4)在业务子模块manage-biz的pom中引入common-swagger子模块
<dependency>
<groupId>org.example</groupId>
<artifactId>common-swagger</artifactId>
<version>${project.version}</version>
</dependency>
5)在需要发布到swagger中的API接口(controller层),增加注解:@ApiOperation(value = “测试echo”)即可
2.2 swagger常见注解
1)@Api:可以放在类上面,对类的基本描述(可以作为分组使用)
2)@ApiOperation:可以放在某个方法上面,对方法的基本描述
3)@ApiImplicitParam:注解用在方法上,用于描述方法需要的参数
4)@ApiResponses:对返回值进行说明
5)@ApiModel和@ApiModelProperty:配置对象的描述信息
6)swagger还可以配置访问权限。但是总体来说,生产环境最好不要也没必要配置swagger。