Springcloud(二)、整合MybatisPlus、统一返回类型、定义全局异常
项目整合MybatisPlus
添加依赖
Springboot项目添加Web项目启动依赖,MYbatisPlus启动依赖,数据库连接依赖等
<!-- Web项目启动类--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MybatisPlus启动类--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <!-- 数据库连接--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- MybatisPlus代码生成器--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> </dependency> <!-- MybatisPlus代码生成器模板引擎--> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> </dependency>复制代码
添加代码生成器
// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中 public class CodeGenerator { /** * <p> * 读取控制台内容 * </p> */ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotBlank(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("YU.TAN"); gc.setOpen(false); // gc.setSwagger2(true); 实体属性 Swagger2 注解 mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/springcloud_demo?serverTimezone=UTC&useUnicode=true&useSSL=false&characterEncoding=utf8"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("root"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); // pc.setModuleName(scanner("admin")); pc.setParent("com.future.admin"); mpg.setPackageInfo(pc); // 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 freemarker // String templatePath = "/templates/mapper.xml.ftl"; // 如果模板引擎是 velocity String templatePath = "/templates/mapper.xml.vm"; // 自定义输出配置 List<FileOutConfig> focList = new ArrayList<>(); // 自定义配置会被优先输出 focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! return projectPath + "/src/main/resources/mapper/" + pc.getModuleName() + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); // 配置自定义输出模板 //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 templateConfig.setXml(null); mpg.setTemplate(templateConfig); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); // strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!"); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); // 公共父类 // strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!"); // 写于父类中的公共字段 strategy.setSuperEntityColumns("id"); strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(pc.getModuleName() + "_"); mpg.setStrategy(strategy); // mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.setTemplateEngine(new VelocityTemplateEngine()); mpg.execute(); } }复制代码
配置文件添加数据库链接
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/springcloud_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true username: root password: root application: name: future-admin复制代码
项目整合SWagger
添加Swagger依赖
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency>复制代码
添加Swagger配置
@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .pathMapping("/") .select() .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class)) .paths(PathSelectors.any()) .build().apiInfo(new ApiInfoBuilder() .title("FUTURE-admin-基础用户信息") .description("FUTURE-基础模块,详细信息") .version("1.0") .contact(new Contact("邮件", "http://www.baidu.com", "aaaa.email.com")) .license("The Apache License") .licenseUrl("http://www.baidu.com") .build()); } }复制代码
效果图
项目同一返回值
该方法我们提供两种方法进行处理。
一种是自定义返回类型,在每一个Controller中的方法都对结果进行该类型的包装处理,即在方法使用时对方法返回值进行手动包装。
另一种是利用拦截器的方式,对所用Controller中的方法进行拦截,并对其返回值进行统一格式化处理。
两种方法比较来看,第一种可以定义更加具体的返回类型和返回码;但是第二种对代码的侵入性更低,降低了代码的耦合性,对于代码的格式重构和修改更加方便。
不论是手动封装返回值还是拦截器实现,我们首先都需要先定义返回格式以及返回状态码。
返回体格式
@JsonIgnoreProperties(ignoreUnknown = true):这个注解写在类上之后,就会忽略类中不存在的字段。这个注解还可以指定要忽略的字段,例如@JsonIgnoreProperties({ “password”, “secretKey” })
@sonInclude(JsonInclude.Include.NON_NULL):在实体类序列化成json的时候在某些策略下,加了该注解的字段不去序列化该字段。
@Data @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @ApiModel(value = "基础API返回对象") @JsonInclude(JsonInclude.Include.NON_NULL) @AllArgsConstructor public class BaseFxResponse<T> { @ApiModelProperty(value = "处理结果code", required = true) private int code = ResultCode.SUCCESS.getCode(); @ApiModelProperty(value = "处理结果描述信息") private String msg = ResultCode.SUCCESS.getMessage(); @ApiModelProperty(value = "请求结果生成时间戳") private String timestamp; @ApiModelProperty(value = "处理结果数据信息") private T data; public BaseFxResponse(int code, String msg) { this.code = code; this.msg = msg; } /** * 1、内部使用,用于构造成功的结果 * 2、对接外部系统错误码及消息 * * @param code * @param msg * @param data */ public BaseFxResponse(int code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } /** * 快速创建成功结果并返回结果数据 * * @param data * @return ApiResult */ public static <T> BaseFxResponse<T> success(T data) { return new BaseFxResponse<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data); } /** * 快速创建成功结果 * * @return ApiResult */ public static <T> BaseFxResponse<T> success() { return success(null); } /** * 系统异常类没有返回数据 * * @return BaseFxResponse */ public static <T> BaseFxResponse<T> fail() { return new BaseFxResponse(ResultCode.EXCEPTION.getCode(), ResultCode.EXCEPTION.getMessage(), null); } /** * 快速创建失败结果并返回结果数据 * * @return BaseFxResponse */ public static <T> BaseFxResponse<T> fail(T data) { return new BaseFxResponse(ResultCode.FAIL.getCode(), ResultCode.FAIL.getMessage(), data); } /** * 成功code=000000 * * @return true/false */ @JsonIgnore public static boolean isSuccess(int code) { return ResultCode.SUCCESS.getCode() == code; } /** * 快速创建成功结果并返回结果数据 * * @param msg * @return ApiResult */ public static <T> BaseFxResponse<T> failMsg(String msg) { return new BaseFxResponse<>(ResultCode.FAIL.getCode(), msg, null); } public static <T> BaseFxResponse<T> fail(ResultCode resultCode) { return new BaseFxResponse(resultCode.getCode(), resultCode.getMessage(), null); } public static BaseFxResponse<Object> result(int code, String msg) { return new BaseFxResponse<>(code, msg, null); } }复制代码
定义返回状态码的枚举
@AllArgsConstructor @Getter public enum ResultCode { /** * */ //成功状态码 SUCCESS(200,"成功"), FAIL(201,"失败"), EXCEPTION(400,"系统异常"), EXCEPTION_NO_PERMISSION(401,"权限不足,无法访问"), EXCEPTION_NO_PARAMETER(402,"方法参数异常"); private final int code; private final String message; }复制代码
手动封装返回值
该方式使用时所有方法都返回BaseFxResponse类型的数据,将具体的返回结果赋值给属性data,可以对具体场景使用不同的状态码,例如:
@ApiOperation("模拟返回成功") @RequestMapping(value = "/test/result/success",method = RequestMethod.GET) public BaseFxResponse<String> testResultSuccess() { return BaseFxResponse.success("Hello,World!"); } @ApiOperation("模拟返回失败") @RequestMapping(value = "/test/result/fail",method = RequestMethod.GET) public BaseFxResponse<String> testResultFail() { return BaseFxResponse.fail("Sorry,Fail!"); } @ApiOperation("模拟返回异常") @RequestMapping(value = "/test/result/exception",method = RequestMethod.GET) public BaseFxResponse<String> testResultException() { return BaseFxResponse.fail(ResultCode.EXCEPTION); }复制代码
拦截器封装返回值
定义执行器ResultResponseHandler
在该执行器上方添加@ControllerAdvice("com.response.controller")注解,标记该在哪些类的方法放格式化返回值。并且要实现ResponseBodyAdvice接口,实现接口中的supports()方法和beforeBodyWrite()方法。
supports():该方法作用是定义哪些方法需要格式化返回值。
beforeBodyWrite():对返回值进行具体的格式化操作。
@Slf4j @ControllerAdvice("com.response.controller") public class ResultResponseHandler implements ResponseBodyAdvice<Object> { public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { return true; } @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { log.info("进入返回体,格式重写中"); if (o instanceof Result){ return o; } if (o instanceof String) { return JSON.toJSONString(Result.success(o)); } return Result.success(o); } }复制代码
也可以对具体哪些方法需要封装进行更细致的划分,例如在前面的基础上只对某些的方法进行封装。 首先定义一个自定义注解,用于标记方法是否需要格式化返回值。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) @Documented public @interface ResponseResult { }复制代码
定义一个拦截器,可以拦截具体的请求,判断该请求是否需要格式化。 我们在该方法或者该类上添加该注解,拦截到方法后,利用反射,判断该类或者该方法是否具有@ResponseResult注解,如果存在该注解,则在请求中添加一个标记。然后方法执行完成后,在ResultResponseHandler类中的supports()方法中判断该方法的请求是否含有该标记,如果存在标记,则返回true,对返回值进行封装,不存在则返回false。具体实现如下:
Slf4j @Component public class ResponseResultInterceptor implements HandlerInterceptor { //标记名称 public static final String RESPONSE_RESULT_ANN="RESPONSE-RESULT-ANN"; /** * 拦截请求,是否此请求返回的值需要包装,其实就是运行的时候,解析@ResponseResult注解 * @param request * @param response * @param handler * @return * @throws Exception */ @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,clazz.getAnnotation(ResponseResult.class)); } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }复制代码
继承WebMvcConfigurerAdapter并对该拦截器进行注册。
项目公共模块定义全局异常
构建一个自定义异常类,在使用时直接抛出该异常类
public class BusinessException extends RuntimeException{ /** * 自定义异常Code */ private int exceptionCode; /** * 自定义异常内容 */ private String exception; public BusinessException(){ } public BusinessException(String exception){ this.exceptionCode= ResultCode.EXCEPTION.getCode(); this.exception=exception; } public BusinessException(int exceptionCode,String exception){ this.exceptionCode=exceptionCode; this.exception=exception; } public int getExceptionCode() { return exceptionCode; } public void setExceptionCode(int exceptionCode) { this.exceptionCode = exceptionCode; } public String getException() { return exception; } public void setException(String exception) { this.exception = exception; } }复制代码
定义一个全局异常,该类可以捕捉系统中所有抛出的异常信息,并对异常信息进行相应的格式化和持久化处理。
在类上添加@RestControllerAdvice注解
在方法上添加拦截器注解,该注解可定义拦截哪些异常@ExceptionHandler(Exception.class)
我们统一拦截Exception类型的异常,及所有异常信息,在方法中再对异常信息类型进行判断和识别,并对具体类型进行我们需要的格式化及持久化处理。
@RestControllerAdvice @Slf4j @Order(1) public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseBody public BaseFxResponse<Object> resolveException(Exception ex) { log.error("系统异常", ex); //判断异常类型 if (ex instanceof BusinessException) { BusinessException exception = (BusinessException) ex; return BaseFxResponse.result(exception.getExceptionCode(), exception.getException()); } if (ex instanceof MethodArgumentNotValidException) { return BaseFxResponse.fail(ResultCode.EXCEPTION_NO_PARAMETER); } return BaseFxResponse.fail(ResultCode.EXCEPTION.getCode()); } }
作者:览山河
链接:https://juejin.cn/post/7038981346986819615