开发工具箱

Spring配置

统一异常处理

统一结果返回对象

MyBatis-Plus配置

ResultMap配置

SpringBoot配置

Docker配置

Vue配置

Swagger配置


那些难记又经常用的代码片段-待补充

Spring配置

Spring核心配置文件

AOP最佳实战

统一结果返回对象

一般的返回结果结构:

1
2
3
4
5
6
7
8
9
10
11
{
"code": 200,
"message": "成功",
"data": [
{
"id": 2,
"roleName": "系统管理员"
}
],
"ok": true
}
  1. 首先创建一个枚举类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package com.zzmr.common.result;

    import lombok.Getter;

    @Getter
    public enum ResultCodeEnum {

    SUCCESS(200, "成功"),
    FAIL(201, "失败");
    /*SERVICE_ERROR(2012, "服务异常"),
    DATA_ERROR(204, "数据异常"),

    LOGIN_AUTH(208, "未登录"),
    PERMISSION(209, "没有权限");*/

    private Integer code;

    private String message;

    private ResultCodeEnum(Integer code, String message) {
    this.code = code;
    this.message = message;
    }

    }
  2. 创建返回结果类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    package com.zzmr.common.result;

    import lombok.Data;

    @Data
    public class Result<T> {

    // 状态码
    private Integer code;

    // 信息
    private String message;

    // 数据
    private T data;

    // 构造私有化,
    private Result() {

    }

    // 封装返回的是数据
    public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
    Result<T> result = new Result<>();

    if (body != null) {
    result.setData(body);
    }
    result.setCode(resultCodeEnum.getCode());
    result.setMessage(resultCodeEnum.getMessage());

    return result;
    }

    // 成功
    public static <T> Result<T> ok() {
    return build(null, ResultCodeEnum.SUCCESS);
    }

    // 成功-有数据
    public static <T> Result<T> ok(T data) {
    return build(data, ResultCodeEnum.SUCCESS);
    }

    // 失败
    public static <T> Result<T> fail() {
    return build(null, ResultCodeEnum.FAIL);
    }

    // 失败-有数据
    public static <T> Result<T> fail(T data) {
    return build(data, ResultCodeEnum.FAIL);
    }

    public Result<T> message(String msg) {
    this.setMessage(msg);
    return this;
    }

    public Result<T> code(Integer code) {
    this.setCode(code);
    return this;
    }

    }
  3. 此时修改Controller

    1
    2
    3
    4
    @GetMapping("/findAll")
    public Result findAll() {
    return Result.ok(sysRoleService.list());
    }

如果返回的数据为空,就可以不在ok()中放入数据,这样也是可以进行返回的

统一异常处理

当控制器方法出现异常时,我们希望都返回相同格式的数据,就算是有异常,然后仍是Result格式的结果

异常处理可以分为:

  1. 全局异常处理
  2. 特定异常处理
  3. 自定义异常处理

全局异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.zzmr.common.config.exception;

import com.zzmr.common.result.Result;
import com.zzmr.common.result.ResultCodeEnum;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;


@ControllerAdvice
public class GlobalExceptionHandler {

/**
* spring security异常
*
* @param e
* @return
*/
@ExceptionHandler(AccessDeniedException.class)
@ResponseBody
public Result error(AccessDeniedException e) throws AccessDeniedException {
return Result.fail().code(205).message("没有操作权限");
}


// 全局异常处理,执行的方法
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e) {
e.printStackTrace();
return Result.fail().message("执行了全局异常处理....");
}

// 特定异常处理
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public Result error(ArithmeticException e) {
e.printStackTrace();
return Result.fail().message("执行了特定异常处理....");
}

// 自定义异常处理
@ExceptionHandler(ZzmrException.class)
@ResponseBody
public Result error(ZzmrException e) {
e.printStackTrace();
return Result.fail().code(e.getCode()).message(e.getMsg());
}

}

特定异常处理

1
2
3
4
5
6
7
// 特定异常处理
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public Result error(ArithmeticException e) {
e.printStackTrace();
return Result.fail().message("执行了特定异常处理....");
}

如果加了特定异常处理,同时也有全局,那么程序会默认找特定的,没有特定的才会使用全局的

自定义异常处理

  1. 创建异常类,继承RuntimeException
  2. 在异常类中添加上相关的属性,状态码,描述信息
  3. 在出现异常的地方手动抛出异常
  4. 在之前创建异常类中,添加上执行方法(就是上面的方法)

异常类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.zzmr.common.config.exception;

import com.zzmr.common.result.ResultCodeEnum;
import lombok.Data;

@Data
public class ZzmrException extends RuntimeException {
private Integer code; // 异常状态码
private String msg; // 描述信息

/**
* 通过状态码和错误消息创建异常对象
*
* @param code
* @param msg
*/
public ZzmrException(Integer code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}

/**
* 接收枚举类型对象
*
* @param resultCodeEnum
*/
public ZzmrException(ResultCodeEnum resultCodeEnum) {
super(resultCodeEnum.getMessage());
this.code = resultCodeEnum.getCode();
this.msg = resultCodeEnum.getMessage();
}

@Override
public String toString() {
return "ZzmrException{" +
"code=" + code +
", msg=" + this.getMsg() +
'}';
}

}

手动抛出异常

1
2
3
4
5
6
7
// 模拟异常效果
try {
int i = 10 / 0;
} catch (Exception e) {
// 抛出自定义异常
throw new ZzmrException(20001,"执行了自定义异常处理");
}

添加执行方法

1
2
3
4
5
6
7
// 自定义异常处理
@ExceptionHandler(ZzmrException.class)
@ResponseBody
public Result error(ZzmrException e) {
e.printStackTrace();
return Result.fail().code(e.getCode()).message(e.getMsg());
}

MyBatis-Plus配置

逻辑删除

在配置文件中加入:

1
2
3
4
5
6
# 此为默认值,不需要修改  意思就是1为删除了,0为未删除
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1
logic-note-delete-value: 0

此时再调用删除方法时,就会自动进行逻辑删除

代码生成器

  1. 引入依赖:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
    </dependency>

    <dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.0</version>
    </dependency>
  2. 编写工具类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    package com.zzmr.code;

    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.generator.AutoGenerator;
    import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
    import com.baomidou.mybatisplus.generator.config.GlobalConfig;
    import com.baomidou.mybatisplus.generator.config.PackageConfig;
    import com.baomidou.mybatisplus.generator.config.StrategyConfig;
    import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

    public class CodeGet {

    public static void main(String[] args) {

    // 1、创建代码生成器
    AutoGenerator mpg = new AutoGenerator();

    // 2、全局配置
    // 全局配置
    GlobalConfig gc = new GlobalConfig();
    gc.setOutputDir("D:\\Codefield\\guigu-oa-parent\\service-oa"+"/src/main/java");

    gc.setServiceName("%sService"); //去掉Service接口的首字母I
    gc.setAuthor("zzmr");
    gc.setOpen(false);
    mpg.setGlobalConfig(gc);

    // 3、数据源配置
    DataSourceConfig dsc = new DataSourceConfig();
    dsc.setUrl("jdbc:mysql://localhost:3306/guigu-oa?serverTimezone=GMT%2B8&useSSL=false");
    dsc.setDriverName("com.mysql.cj.jdbc.Driver");
    dsc.setUsername("root");
    dsc.setPassword("010203");
    dsc.setDbType(DbType.MYSQL);
    mpg.setDataSource(dsc);

    // 4、包配置
    PackageConfig pc = new PackageConfig();
    pc.setParent("com.zzmr");
    pc.setModuleName("auth"); //模块名
    pc.setController("controller");
    pc.setService("service");
    pc.setMapper("mapper");
    mpg.setPackageInfo(pc);

    // 5、策略配置
    StrategyConfig strategy = new StrategyConfig();

    // 指定表名
    strategy.setInclude("sys_user");

    strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略

    strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
    strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

    strategy.setRestControllerStyle(true); //restful api风格控制器
    strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

    mpg.setStrategy(strategy);

    // 6、执行
    mpg.execute();
    }
    }
  3. 同时会生成一个实体类,如果事先已经创建好了,可以删除,然后把service和mapper中引入的实体类换成自己的就行了.

分页插件

  1. 配置分页插件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.zzmr.common.config.mp;

    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
    import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    @Configuration
    @MapperScan("com.zzmr.auth.mapper")
    public class MybatisPlusConfig {
    /**
    * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
    */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
    }

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
    return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
    }
  2. 编写controller的分页方法
    • 当前页,每页显示的记录数
    • 条件参数
  3. 调用service的方法实现条件分页查询
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 条件分页查询
    // page 当前页 limit 每页显示的记录数
    @ApiOperation("条件分页查询")
    @GetMapping("/{page}/{limit}")
    public Result pageQueryRole(@PathVariable Long page, @PathVariable Long limit,
    SysRoleQueryVo sysRoleQueryVo) {
    // 调用service的方法实现
    // 1. 创建page对象,传递分页相关参数
    Page<SysRole> pageParam = new Page<>(page, limit);
    // 2. 封装条件,判断条件是否为空,不为空进行封装
    LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();
    String roleName = sysRoleQueryVo.getRoleName();
    if (!StringUtils.isEmpty(roleName)) {
    // 不为空,封装
    // 下面的lambda表达式相当于取数据库中roleName的字段名和roleName进行模糊搜索
    wrapper.like(SysRole::getRoleName, roleName);
    }
    // 3. 调用方法实现
    IPage<SysRole> pageModel = sysRoleService.page(pageParam, wrapper);
    return Result.ok(pageModel);
    }

ResultMap配置

SpringBoot配置

Docker配置

安装tomcat

安装redis

安装mysql

Vue配置

Swagger配置

项目配置

使用Swagger只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面,Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案

使用方式

  1. 导入依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.2</version>
    </dependency>
  2. 在配置类中加入knife4j相关配置(某个带有@Configuration的配置类中即可)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * 通过knife4j生成接口文档
    * @return
    */
    @Bean
    public Docket docket() {
    ApiInfo apiInfo = new ApiInfoBuilder()
    .title("苍穹外卖项目接口文档")
    .version("2.0")
    .description("苍穹外卖项目接口文档")
    .build();
    Docket docket = new Docket(DocumentationType.SWAGGER_2)
    .apiInfo(apiInfo)
    .select()
    .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
    .paths(PathSelectors.any())
    .build();
    return docket;
    }
  3. 设置静态资源映射,否则接口文档页面无法访问(要放在某个继承自WebMvcConfigurationSupport类中),这个方法其实就是重写的父类的方法
    20230826201101
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 设置静态资源映射
    * @param registry
    */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
    registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

顺便把这个配置类也放着:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.sky.config;

import com.sky.interceptor.JwtTokenAdminInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
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;

/**
* 配置类,注册web层相关组件
*/
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

@Autowired
private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;

/**
* 注册自定义拦截器
*
* @param registry
*/
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/employee/login");
}

/**
* 通过knife4j生成接口文档
* @return
*/
@Bean
public Docket docket() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}

/**
* 设置静态资源映射
* @param registry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}

这就配置好了,启动项目进入浏览器访问localhost:8080/doc.html,即可进入接口文档页面
20230826201533


Swagger和yapi的区别

  1. yapi是设计阶段使用的工具,管理和维护接口
  2. Swagger是在开发阶段使用的框架,帮助后端开发人员做后端的接口测试

Swagger常用注解

20230826202517

具体使用案例

  1. @Api(tags = "员工相关接口"),给Controller上加,在页面上表示一级目录,**tags=**不能省略,否则无效,
    1
    2
    3
    @Api(tags = "员工相关接口")
    public class EmployeeController {
    }
  2. @ApiOperation(value = "员工登录接口"),给接口上加,表示具体接口的名称
    1
    2
    3
    @ApiOperation(value = "员工登录接口")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
    }
  3. @ApiModel@ApiModelProperty(),给实体类和属性上加
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @ApiModel(description = "员工登录时传递的数据模型")
    public class EmployeeLoginDTO implements Serializable {

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("密码")
    private String password;

    }