接口管理工具

knife4j

  1. pom文件
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>2.0.2</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
    package com.itheima.config;

    import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;

    @Configuration
    @EnableSwagger2
    @EnableKnife4j
    public class Knife4jConfiguration {
    @Bean
    public Docket buildDocket() {
    //构建在线API概要对象
    return new Docket(DocumentationType.SWAGGER_2)
    .apiInfo(buildApiInfo())
    .select()
    // 要扫描的API(Controller)基础包
    .apis(RequestHandlerSelectors.basePackage("com.itheima.controller"))
    .paths(PathSelectors.any())
    .build();
    }
    private ApiInfo buildApiInfo() {
    //网站联系方式
    Contact contact = new Contact("GYM","https://www.itheima.com/","1984906301@qq.com");
    return new ApiInfoBuilder()
    .title("今日指数-在线接口API文档")//文档标题
    .description("这是一个方便前后端开发人员快速了解开发接口需求的在线接口API文档")//文档描述信息
    .contact(contact)//站点联系人相关信息
    .version("1.0.0")//文档版本
    .build();
    }
    }
  3. 在启动类添加@MapperScan(“com.itheima.mapper”)
  4. 访问地址:http://localhost:8091/doc.html
  5. 注解的使用
    @Api(tags= “01 通用模块”)
    @ApiOperation(“根据条件查询数据”)
    @ApiModel(“通用查询条件实体”)
    @ApiModelProperty(value = “页码”, required = true, example = “1”)

Yapi

Yapi安装流程

docker安装过程参考:https://www.jianshu.com/p/a97d2efb23c5
流程如下:

  1. 安装mongo数据库:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 拉取mongo镜像,当然一位内部包比较大,直接导入资料包中的镜像资源即可
    docker pull mongo
    # 安装mongo数据库服务
    # 创建存储卷
    docker volume create mongo-data
    # 启动 MongoDB
    docker run -d \
    --name mongo-yapi \
    -v mongo-data:/data/db \
    -p 27017:27017 \
    -e MONGO_INITDB_ROOT_USERNAME=anoyi \
    -e MONGO_INITDB_ROOT_PASSWORD=anoyi.com \
    mongo
  2. 初始化yaml的管理员账号和密码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # 拉取yapi镜像包
    docker pull registry.cn-hangzhou.aliyuncs.com/anoyi/yapi
    # 自定义名称为config.json的配置文件
    {
    "port": "3000",
    "adminAccount": "admin@anoyi.com",
    "timeout":120000,
    "db": {
    "servername": "mongo",
    "DATABASE": "yapi",
    "port": 27017,
    "user": "anoyi",
    "pass": "anoyi.com",
    "authSource": "admin"
    }
    }
    # 初始化管理员账户和密码
    docker run -it --rm \
    --link mongo-yapi:mongo \
    --entrypoint npm \
    --workdir /yapi/vendors \
    -v $PWD/config.json:/yapi/config.json \
    registry.cn-hangzhou.aliyuncs.com/anoyi/yapi \
    run install-server
    效果如下:
    code
  3. 最后初始化yaml容器:
    1
    2
    3
    4
    5
    6
    7
    8
    docker run -d \
    --name yapi \
    --link mongo-yapi:mongo \
    --workdir /yapi/vendors \
    -p 3000:3000 \
    -v $PWD/config.json:/yapi/config.json \
    registry.cn-hangzhou.aliyuncs.com/anoyi/yapi \
    server/app.js
    访问路径:
    1
    2
    3
    访问: http://192.168.200.130:3000
    登录账号:admin@anoyi.com
    密码:ymfe.org

    注意:
    **重启yapi服务时,需要同时启动mongo服务,可通过 docker start mongo-yapi yapi 启动

Yapi基本使用

登录到Yapi平台之后,我们可以创建项目,在项目下创建接口分类,在对应的分类中添加接口。

  1. 创建项目
    code
  2. 添加分类
    在当前项目中,有针对用户、股票、日志、权限等相关的操作,我们在进行接口维护时,可以针对接口进行分类,如果没有对应的分类,我们自己添加分类;
    code
    接口基本信息录入之后,点击提交按钮,就可以看到该接口的基本信息:
    code
  3. 运行接口
    code

    自动同步swagger

    code

实体类设计

  1. 实体类中的公共字段(例如idcreate_timeupdate_timeis_deleted)抽取到一个基类,进行统一管理,然后让各实体类继承该基类。
  2. 实体类中的状态字段(例如status)或类型字段(例如type),全部使用枚举类型。

    例如:订单状态(1:待支付,2:待发货,3:待收货,4:已收货,5:已完结)。
    定义枚举

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public enum Status {
    CANCEL(0, "已取消"),
    WAIT_PAY(1, "待支付"),
    WAIT_TRANSFER(2, "待发货"),
    WAIT_RECEIPT(3, "待收货"),
    RECEIVE(4, "已收货"),
    COMPLETE(5, "已完结");

    private final Integer value;
    private final String desc;

    public Integer value() {
    return value;
    }
    public String desc() {
    return desc;
    }
    }

    定义实体类

    1
    2
    3
    4
    5
    @Data
    public class Order{
    private Status status;
    ...
    }

    调用

    1
    order.setStatus(Status.WAIT_PAY);

配置类实体类

  1. yaml文件
    1
    2
    3
    4
    5
    minio:
    endpoint: http://<hostname>:<port>
    access-key: <access-key>
    secret-key: <secret-key>
    bucket-name: <bucket-name>
  2. 实体类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @ConfigurationProperties(prefix = "minio")
    @Data
    public class MinioProperties {

    private String endpoint;

    private String accessKey;

    private String secretKey;

    private String bucketName;
    }
  3. 使用 —-@AUtowired注入

mybatis-plus

入门

  1. 导入依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
    </dependency>
  2. application.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    server:
    port: 8800
    mybatis-plus:
    configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查看日志
    spring:
    datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/guigu-auth?characterEncoding=utf-8&useSSL=false
    username: root
    password: root
  3. 启动类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @MapperScan(basePackages = "com.atguigu.system.mapper")
    @SpringBootApplication
    public class ServiceAuthApplication {

    public static void main(String[] args) {
    SpringApplication.run(ServiceAuthApplication.class, args);
    }

    }
  4. 实体类

    @TableName:表名注解,标识实体类对应的表
    @TableId:主键注解,type = IdType.AUTO(数据库 ID 自增)
    @TableField:字段注解(非主键)
    @TableLogic:逻辑删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Data
    @TableName("sys_role")
    public class SysRole {

    private static final long serialVersionUID = 1L;

    //角色id
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    //角色名称
    @TableField("role_name")
    private String roleName;

    //逻辑删除
    @TableLogic
    @TableField("is_deleted")
    private Integer isDeleted;


    }
  5. 添加mapper

    1
    2
    3
    @Repository
    public interface SysRoleMapper extends BaseMapper<SysRole> {
    }
  6. 添加service

    1
    2
    3
    4
    5
    public interface SysRoleService();

    @Service
    public class SysRoleServiceImpl exantend IService<SysRoleMapper,SysRole> implements SysRoleService(){
    }

逻辑删除

  1. application.yml
    1
    2
    3
    4
    5
    6
    mybatis-plus:
    global-config:
    db-config:
    logic-delete-field: flag # 全局逻辑删除的实体字段名(配置后可以忽略不配置步骤二)
    logic-delete-value: 1 # 逻辑已删除值(默认为 1)
    logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  2. 在实体类中的删除标识字段上增加@TableLogic注解
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Data
    public class BaseEntity {

    @Schema(description = "主键")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @Schema(description = "创建时间")
    @JsonIgnore
    private Date createTime;

    @Schema(description = "更新时间")
    @JsonIgnore
    private Date updateTime;

    @Schema(description = "逻辑删除")
    @JsonIgnore
    @TableLogic
    @TableField("is_deleted")
    private Byte isDeleted;

    }

    逻辑删除功能只对Mybatis-Plus自动注入的sql起效,也就是说,对于手动在Mapper.xml文件配置的sql不会生效,需要单独考虑。

忽略特定字段

通常情况下接口响应的Json对象中并不需要create_time、update_time、is_deleted等字段,这时只需在实体类中的相应字段添加@JsonIgnore注解,该字段就会在序列化时被忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Data
public class BaseEntity {

@ApiModelProperty(value = "创建时间")
@JsonIgnore
@TableField(value = "create_time")
private Date createTime;

@ApiModelProperty(value = "更新时间")
@JsonIgnore
@TableField(value = "update_time")
private Date updateTime;

@ApiModelProperty(value = "逻辑删除")
@JsonIgnore
@TableField("is_deleted")
private Byte isDeleted;

}

字段自动填充

保存或更新数据时,前端通常不会传入isDeletedcreateTimeupdateTime这三个字段,因此我们需要手动赋值。但是数据库中几乎每张表都有上述字段,所以手动去赋值就显得有些繁琐。为简化上述操作,我们可采取以下措施。

  • is_deleted字段:可将数据库中该字段的默认值设置为0。
  • create_timeupdate_time:可使用mybatis-plus的自动填充功能,所谓自动填充,就是通过统一配置,在插入或更新数据时,自动为某些字段赋值
  1. 实体类 —-注意fill属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Data
    public class BaseEntity {

    @Schema(description = "创建时间")
    @JsonIgnore
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private Date createTime;

    @Schema(description = "更新时间")
    @JsonIgnore
    @TableField(value = "update_time", fill = FieldFill.UPDATE)
    private Date updateTime;

    @Schema(description = "逻辑删除")
    @JsonIgnore
    @TableLogic
    @TableField("is_deleted")
    private Byte isDeleted;

    }
  2. MybatisMetaObjectHandler

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Component
    public class MybatisMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
    this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
    this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
    }
    }

    在做完上述配置后,当写入数据时,Mybatis-Plus会自动将实体对象的create_time字段填充为当前时间,当更新数据时,则会自动将实体对象的update_time字段填充为当前时间。

业务类

三级菜单 —-自关联表

code
在实体类添加属性List<自身> children;

使用Stream流递归实现
  • 找到所有的分类
    • 根据分类过滤出所有根节点
    • 设置子节点为 — getchildren(过滤出的根节点,所有节点)
      • 根据所有节点进行过滤 — 所有节点的partentCid等于根节点的catid
      • 对过滤出的节点进行递归
      • 进行排序 — 注意返回结果为null
      • 进行收集
    • 进行排序
    • 进行收集
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
@Override
public List<CategoryEntity> listWithTree() {
//1.查出所有分类
List<CategoryEntity> entities = baseMapper.selectList(null);
//2.组装成父子的树型结构
List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity -> {
return categoryEntity.getParentCid() == 0;
}).map((menu)->{
menu.setChildren(getChildrens(menu,entities));
return menu;
}).sorted((menu1,menu2)->{
return menu1.getSort() - menu2.getSort();
}).collect(Collectors.toList());

return level1Menus;
}

//递归查找所有菜单的子菜单
private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all){
List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
return categoryEntity.getParentCid() == root.getCatId();
}).map((categoryEntity)->{
//1.找到子菜单
categoryEntity.setChildren(getChildrens(categoryEntity,all));
return categoryEntity;
}).sorted((menu1,menu2)->{
//2.菜单的排序
return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
}).collect(Collectors.toList());

return children;
}

直接递归实现
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
@Override
public List<SysMenu> findMenuNodes() {
//1. 查询所有的菜单
List<SysMenu> sysMenusAllList = sysMenuMapper.selectList(null);
//2. 组装成树(倒着的树)
List<SysMenu> treeList=MenuHelper.buildTreeList(sysMenusAllList);
return treeList;
}


public class MenuHelper {

public static List<SysMenu> buildTreeList(List<SysMenu> sysMenusAllList) {
//树形集合
List<SysMenu> treeList=new ArrayList<>();
for (SysMenu sysMenu : sysMenusAllList) {
//先找根节点
if(sysMenu.getParentId()==0){
treeList.add(findChildNode(sysMenu,sysMenusAllList));
}
}
return treeList;
}

//查找子节点
private static SysMenu findChildNode(SysMenu sysMenu, List<SysMenu> sysMenusAllList) {
sysMenu.setChildren(new ArrayList<>());
for (SysMenu childNode : sysMenusAllList) {
if(childNode.getParentId()==sysMenu.getId()){
//把子节点保存
if(sysMenu.getChildren()==null){
sysMenu.setChildren(new ArrayList<>());
}
sysMenu.getChildren().add(findChildNode(childNode,sysMenusAllList));
}
}
return sysMenu;
}
}

日志类

  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
    <!-- Spring Boot 集成 log4j2 -->  
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>

    如果上面出现错误--使用下面的
    <!-- 排除 Spring Boot 依赖的日志包冲突 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
    <exclusion>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
    </exclusions>
    </dependency>

    <!-- Spring Boot 集成 log4j2 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
  2. 创建log4j2-dev.xml
    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
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration monitorInterval="180" packages="">
    <properties>
    <property name="logdir">logs</property>
    <property name="PATTERN">%date{YYYY-MM-dd HH:mm:ss,SSS} %level [%thread][%file:%line] - %msg%n%throwable</property>
    </properties>
    <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
    <PatternLayout pattern="${PATTERN}"/>
    </Console>

    <RollingFile name="ErrorAppender" fileName="${logdir}/error.log"
    filePattern="${logdir}/$${date:yyyy-MM-dd}/error.%d{yyyy-MM-dd-HH}.log" append="true">
    <PatternLayout pattern="${PATTERN}"/>
    <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
    <Policies>
    <TimeBasedTriggeringPolicy interval="1" modulate="true" />
    </Policies>
    </RollingFile>

    <RollingFile name="DebugAppender" fileName="${logdir}/info.log"
    filePattern="${logdir}/$${date:yyyy-MM-dd}/info.%d{yyyy-MM-dd-HH}.log" append="true">
    <PatternLayout pattern="${PATTERN}"/>
    <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
    <Policies>
    <TimeBasedTriggeringPolicy interval="1" modulate="true" />
    </Policies>
    </RollingFile>

    <!--异步appender-->
    <Async name="AsyncAppender" includeLocation="true">
    <AppenderRef ref="ErrorAppender"/>
    <AppenderRef ref="DebugAppender"/>
    </Async>
    </Appenders>

    <Loggers>
    <!--过滤掉spring和mybatis的一些无用的debug信息
    <logger name="org.springframework" level="INFO">
    </logger>
    <logger name="org.mybatis" level="INFO">
    </logger>-->
    <logger name="cn.itcast.wanxinp2p.consumer.mapper" level="DEBUG">
    </logger>

    <logger name="springfox" level="INFO">
    </logger>
    <logger name="org.apache.http" level="INFO">
    </logger>
    <logger name="com.netflix.discovery" level="INFO">
    </logger>

    <logger name="RocketmqCommon" level="INFO" >
    </logger>

    <logger name="RocketmqRemoting" level="INFO" >
    </logger>

    <logger name="RocketmqClient" level="WARN">
    </logger>

    <logger name="org.dromara.hmily" level="WARN">
    </logger>

    <logger name="org.dromara.hmily.lottery" level="WARN">
    </logger>

    <logger name="org.dromara.hmily.bonuspoint" level="WARN">
    </logger>

    <!--OFF 0-->
    <!--FATAL 100-->
    <!--ERROR 200-->
    <!--WARN 300-->
    <!--INFO 400-->
    <!--DEBUG 500-->
    <!--TRACE 600-->
    <!--ALL Integer.MAX_VALUE-->
    <Root level="DEBUG" includeLocation="true">
    <AppenderRef ref="AsyncAppender"/>
    <AppenderRef ref="Console"/>
    <AppenderRef ref="DebugAppender"/>
    </Root>
    </Loggers>
    </Configuration>

  3. yaml配置
    1
    2
    3
    # 日志文件配置路径  
    logging:
    config: classpath:log4j2-dev.xml

常用的方法

对象复制

BeanUtils.copyProperties(被复制的对象,复制的对象)

  • 如果属性为null,则不复制

    判断字符串是否为空

    StringUtils.isBlank()
  • lang3包下的

hutool万能工具包

导入依赖

1
2
3
4
5
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.21</version>
</dependency>

生成图片验证码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        //1.生成图片验证码
//(图片宽度,图片高度,图片中包含的验证码的长度,干扰线的数量)
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(250, 40, 4, 5);
//设置背景图片
captcha.setBackground(Color.LIGHT_GRAY);
//自定义校验码生成方式
// captcha.setGenerator(new CodeGenerator() {
// @Override
// public String generate() {
// return RandomStringUtils.randomNumeric(4);
// }
// @Override
// public boolean verify(String code, String userInputCode) {
// return code.equalsIgnoreCase(userInputCode);
// }
// });
//2.获取校验码
String checkCode = captcha.getCode();
//3.获取经过base64编码处理的图片数据
String imageBase64 = captcha.getImageBase64();
//4.获取图片中的验证码,默认生成的校验码包含文字和数字,长度为4