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());
    }
}
复制代码

效果图

image.png

项目同一返回值

该方法我们提供两种方法进行处理。
一种是自定义返回类型,在每一个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);
}
复制代码

image.png

image.png

image.png

拦截器封装返回值

定义执行器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;
    }
}
复制代码

定义一个全局异常,该类可以捕捉系统中所有抛出的异常信息,并对异常信息进行相应的格式化和持久化处理。

  1. 在类上添加@RestControllerAdvice注解
  2. 在方法上添加拦截器注解,该注解可定义拦截哪些异常@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());
    }

}
复制代码