Base工程是用来放公共组件的
依赖信息
引入如下依赖:
pom.xml<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency>
|
实体类
在learning-online-base
工程下创建包com.swx.base.model
,并在该包下创建两个实体类
分页查询参数
PageParam
@Data @ApiModel("分页查询参数") public class PageParam {
@ApiModelProperty("当前页码") private Long pageNo = 1L; @ApiModelProperty("每页记录数") private Long pageSize = 10L;
public PageParam() { }
public PageParam(Long pageNo, Long pageSize) { this.pageNo = pageNo; this.pageSize = pageSize; } }
|
分页查询结果
PageResult
@Data public class PageResult<T> implements Serializable {
private List<T> items; private long counts; private long page; private long pageSize;
public PageResult() { }
public PageResult(List<T> items, long counts, long page, long pageSize) { this.items = items; this.counts = counts; this.page = page; this.pageSize = pageSize; }
public PageResult(List<T> items, long counts, PageParam pageParam) { this.items = items; this.counts = counts; this.page = pageParam.getPageNo(); this.pageSize = pageParam.getPageSize(); } }
|
JSON格式化
在com.swx.base.config
创建配置类JSONConvertConfig
JSONConvertConfig@Configuration public class JSONConvertConfig {
@Bean public LocalDateTimeSerializer localDateTimeSerializer() { return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); }
@Bean public LocalDateTimeDeserializer localDateTimeDeserializer() { return new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); }
@Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { return builder -> { builder.serializerByType(LocalDateTime.class, localDateTimeSerializer()); builder.deserializerByType(LocalDateTime.class, localDateTimeDeserializer()); }; }
@Bean public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); SimpleModule module = new SimpleModule(); module.addSerializer(Long.class, ToStringSerializer.instance); module.addSerializer(Long.TYPE, ToStringSerializer.instance); objectMapper.registerModule(module); return objectMapper; } }
|
Knife4j配置类
在com.swx.base.config
创建配置类Knife4jConfiguration
Knife4jConfiguration@Configuration @EnableSwagger2 @EnableKnife4j @Import(BeanValidatorPluginsConfiguration.class) public class Knife4jConfiguration {
@Bean(value = "defaultApi2") public Docket defaultApi2() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("1.0") .select() .apis(RequestHandlerSelectors.basePackage("com.swx")) .paths(PathSelectors.any()) .build(); }
private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("学成在线API文档") .description("学成在线API文档") .version("1.0") .build(); } }
|
统一返回结果
为了让前端接受到统一的返回结果,需要使用拦截器自动包装Controller返回结果,同时全局拦截异常处理,并一起包装返回统一结果。
{ code: 200, message: '成功', data: null, }
|
返回结果
在learning-online-base
工程的com.swx.base.model
包下创建如下几个实体类
返回结果
R
@Data public class R implements Serializable {
private static final long serialVersionUID = 1L;
private Integer code;
private String message;
private Object data;
public R() {
}
public R(ResultCodeEnum resultCode, Object data) { this.code = resultCode.code(); this.message = resultCode.message(); this.data = data; }
private void setResultCode(ResultCodeEnum resultCode) { this.code = resultCode.code(); this.message = resultCode.message(); }
public static R success() { R result = new R(); result.setResultCode(ResultCodeEnum.SUCCESS); return result; } public static R success(Object data) { R result = new R(); result.setResultCode(ResultCodeEnum.SUCCESS); result.setData(data); return result; }
public static R fail(Integer code, String message) { R result = new R(); result.setCode(code); result.setMessage(message); return result; } public static R fail(ResultCodeEnum resultCode) { R result = new R(); result.setResultCode(resultCode); return result; } }
|
异常结果
ErrorResult
public class ErrorResult {
private Integer code;
private String message;
private String exception;
public Integer getCode() { return code; }
public void setCode(Integer code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getException() { return exception; }
public void setException(String exception) { this.exception = exception; }
public static ErrorResult fail(ResultCodeEnum resultCode, Throwable e, String message) { ErrorResult errorResult = ErrorResult.fail(resultCode, e); errorResult.setMessage(message); return errorResult; }
public static ErrorResult fail(ResultCodeEnum resultCode, Throwable e) { ErrorResult errorResult = new ErrorResult(); errorResult.setCode(resultCode.code()); errorResult.setMessage(resultCode.message()); errorResult.setException(e.getClass().getName()); return errorResult; } public static ErrorResult fail(Integer code, String message) { ErrorResult errorResult = new ErrorResult(); errorResult.setCode(code); errorResult.setMessage(message); return errorResult; } }
|
异常枚举类
ResultCodeEnumpublic enum ResultCodeEnum {
SUCCESS(200, "成功"), TOKEN_INVALID(50, "无效TOKEN"), TOKEN_EXPIRE(51, "TOKEN已过期"), TOKEN_REQUIRE(50, "TOKEN是必须的"), SERVER_ERROR(500, "服务器内部错误"), PARAM_REQUIRE(501, "缺少参数"), PARAM_INVALID(502, "无效参数"), PARAM_TIMAGE_FORMAT_ERROR(503, "图片格式有误"), DATA_EXIST(1000, "数据已经存在"), AP_USER_DATA_NOT_EXIST(1001, "ApUser数据不存在"), DATA_NOT_EXIST(1002, "数据不存在"), NO_OPERATOR_AUTH(3000, "无权操作"), NEED_ADMIN(3001, "需要管理员权限");
private Integer code; private String message;
private ResultCodeEnum(Integer code, String message) { this.code = code; this.message = message; }
public Integer code() { return this.code; } public String message() { return this.message; } }
|
自定义异常
在learning-online-base
工程的com.swx.base.excpetion
包下创建自定义异常
BizExceptionpublic class BizException extends RuntimeException {
private Integer code;
private String message;
public BizException() { super(); }
public BizException(ResultCodeEnum resultCode) { super(resultCode.message()); this.code = resultCode.code(); this.message = resultCode.message(); }
public BizException(ResultCodeEnum resultCode, Throwable cause) { super(resultCode.message(), cause); this.code = resultCode.code(); this.message = resultCode.message(); }
public BizException(String message) { super(message); this.code = -1; this.message = message; }
public BizException(Integer code, String message) { super(message); this.code = code; this.message = message; }
public BizException(Integer code, String message, Throwable cause) { super(message, cause); this.code = code; this.message = message; }
@Override public synchronized Throwable fillInStackTrace() { return this; }
public Integer getCode() { return code; }
public void setCode(Integer code) { this.code = code; }
@Override public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; } }
|
自定义注解
添加了该注解的Controller的方法的返回结果将被我们统一包装
ResponseResult@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD}) @Documented public @interface ResponseResult {
}
|
注解拦截器
创建自定义的拦截器,判断方法是否使用了@ResponseResult
@Component public class ResponseResultInterceptor implements HandlerInterceptor { public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { final HandlerMethod handlerMethod = (HandlerMethod) handler; final Class<?> clazz = handlerMethod.getBeanType(); final Method method = handlerMethod.getMethod(); if (clazz.isAnnotationPresent(ResponseResult.class)) { request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class)); } else if (method.isAnnotationPresent(ResponseResult.class)) { request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class)); } } return true; } }
|
返回结果处理
使用@ControllerAdvice
可以拦截Controller方法返回参数,自动包装成为统一返回结果:
ResponseResultHandler
@ControllerAdvice public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
@Override public boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1) { ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = sra.getRequest(); ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN); return responseResultAnn == null ? false : true; }
@Override public Object beforeBodyWrite(Object body, MethodParameter arg1, MediaType arg2, Class<? extends HttpMessageConverter<?>> arg3, ServerHttpRequest arg4, ServerHttpResponse arg5) { if (body instanceof ErrorResult) { ErrorResult error = (ErrorResult) body; return R.fail(error.getCode(), error.getMessage()); } else if (body instanceof R) { return (R) body; } else if (body instanceof String) { return body; } return R.success(body); } }
|
全局异常处理
正常返回的结果R
被自动包装,那么异常结果也应该被自动包装,因此使用@RestControllerAdvice
注解做全局的异常处理,返回错误结果ErrorResult
GlobalExceptionHandler
@RestControllerAdvice public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger("GlobalExceptionHandler.class");
@ExceptionHandler(BizException.class) public ErrorResult bizExceptionHandler(BizException e, HttpServletRequest request) { logger.error("发生业务异常!原因是: {}", e.getMessage()); return ErrorResult.fail(e.getCode(), e.getMessage()); }
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Throwable.class) public ErrorResult handlerThrowable(Throwable e, HttpServletRequest request) { logger.error("发生未知异常!原因是: ", e); if (e.getMessage().equals("不允许访问")) { return ErrorResult.fail(ResultCodeEnum.NO_OPERATOR_AUTH, e); } return ErrorResult.fail(ResultCodeEnum.SERVER_ERROR, e); }
@ExceptionHandler(BindException.class) public ErrorResult handleBindExcpetion(BindException e, HttpServletRequest request) { logger.error("发生参数校验异常!原因是:",e); ErrorResult error = ErrorResult.fail(ResultCodeEnum.PARAM_INVALID, e, e.getAllErrors().get(0).getDefaultMessage()); return error; }
@ExceptionHandler(MethodArgumentNotValidException.class) public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) { logger.error("发生参数校验异常!原因是:",e); ErrorResult error = ErrorResult.fail(ResultCodeEnum.PARAM_INVALID,e,e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); return error; } }
|
系统如何处理异常?
使用控制器增强注解@RestControllerAdvice捕获处理不同的异常,可以返回一个自定义的异常结果。同时还可以使用增强注解@ControllerAdvice拦截Controller方法的返回,并在beforeBodyWrite方法包装对象和异常结果为统一结果。
自动装配
为了让引入base模块的项目能够自动装配配置类,需要在resources
下创建目录META-INF
并在目录下创建文件spring.factories
,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \ com.swx.base.config.LocalDateTimeConfig, \ com.swx.base.config.Knife4jConfiguration, \ com.swx.base.config.WebAppConfig, \ com.swx.base.exception.GlobalExceptionHandler, \ com.swx.base.exception.ResponseResultHandler
|
所有的配置类都要在这里声明一下。
JSR303校验
请求参数的合法校验如何做?
使用基于JSR303的校验框架实现,Spring Boot提供了JSR-303的支持,它就是spring-boot-starter-validation,它包括了很多校验规则,只需要在模型类中通过注解指定校验规则,在Controller方法上开启校验。
实现统一校验
引入依赖信息
pom.xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
|
在实体类中加入校验信息,如下:
@Data @ApiModel(value = "AddCourseDTO", description = "新增课程基本信息") public class AddCourseDTO {
@NotEmpty(message = "课程名称不能为空") @ApiModelProperty(value = "课程名称", required = true) private String name;
@NotEmpty(message = "适用人群不能为空") @Size(message = "适用人群内容过少", min = 10) @ApiModelProperty(value = "适用人群", required = true) private String users; }
|
分组校验
修改和新增的校验规则不同,此时可以使用分组来解决。
在base模块的com.swx.base.exception
包下创建分组类:
ValidationGroup
public class ValidationGroup { public interface Insert{}; public interface Update{}; public interface Delete{}; }
|
修改校验规则
@NotEmpty(message = "新增课程名称不能为空", groups = {ValidationGroup.Insert.class}) @NotEmpty(message = "修改课程名称不能为空", groups = {ValidationGroup.Update.class}) @ApiModelProperty(value = "课程名称", required = true) private String name;
|
增加分组信息
@ApiOperation("新增课程") @PostMapping("") public CourseBaseInfoVO createCourseBase(@RequestBody @Validated(ValidationGroup.Insert.class) AddCourseDTO dto) { Long companyId = 1232141425L; return courseBaseService.createCourseBase(companyId, dto); }
|