从零新建一个 Springboot 2.7.1 项目搭配 Swagger 3.0 Knife4j MyBatisPlus 等

前言


本身应该是一个很简单的事情,新建项目,引入依赖,启动,结束。理论上不值得写笔记。
BUT!!!

最近发现,才大半年没新建项目,变化太快了,尤其是springboot 2.7.1,和一些框架依赖会有冲突,感觉需要重新学一学,
顺便把从零配置的过程全部详细记录一下,防止以后忘记。


开发环境如下
- Windows 11
- Intellij Idea 2020.3
- Maven 3.6.3
- Oracle Java 1.8.0_251
- Tomcat 8.5.56

折腾

新建项目

开头部分都是基础中的基础了,而且新版本和老版本也没什么区别,就用截图快速介绍下,不需要的可以跳到下一个部分

配置 Maven


先配置maven环境,改为开发环境本地的maven,否则会出现maven依赖引不进来的问题。

配置 Swagger 3.0


然后添加 swagger 3.0 相关依赖
在 pom.xml 中添加这段依赖,并刷新 maven

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>3.0.0</version>
</dependency>

XML

Copy


之后直接启动项目,就会看到这段报错

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-07-19 13:59:01.772 ERROR 1236 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.21.jar:5.3.21]
    at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.21.jar:5.3.21]
    at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.21.jar:5.3.21]
    at java.lang.Iterable.forEach(Iterable.java:75) ~[na:1.8.0_251]
    at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.21.jar:5.3.21]
    at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.21.jar:5.3.21]
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) ~[spring-context-5.3.21.jar:5.3.21]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.21.jar:5.3.21]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.1.jar:2.7.1]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) [spring-boot-2.7.1.jar:2.7.1]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) [spring-boot-2.7.1.jar:2.7.1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) [spring-boot-2.7.1.jar:2.7.1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) [spring-boot-2.7.1.jar:2.7.1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) [spring-boot-2.7.1.jar:2.7.1]
    at com.example.demo.DemoApplication.main(DemoApplication.java:10) [classes/:na]
    ...

Java

Copy


参考网上文章
https://blog.csdn.net/Java__EE/article/details/124808044


得知 Springboot 2.6.0 之后 SpringMVC 默认路径匹配策略从 AntPathMatcher 更改为 PathPatternParser ,导致出错,解决办法是切换为原先的 AntPathMatcher ,或者降低 Springboot 版本到 2.6.0 以下。


我这里直接选择最简单的改配置文件方法解决
配置文件中添加配置解决

spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

YAML

Copy


删除默认的 application.properties


新建3个文件分别是
application.yml

# 通用配置
spring:
  profiles:
    active: dev
  jackson:
    time-zone: GMT+8
    serialization:
      write-dates-as-timestamps: true
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

YAML

Copy


application-dev.yml

# 开发环境
server:
  port: 8080
  servlet:
    context-path: /demo
spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

YAML

Copy


application-prod.yml

# 生产环境
server:
  port: 8000
  servlet:
    context-path: /demo
spring:
  servlet:
    multipart:
      max-file-size: 20MB
      max-request-size: 20MB

YAML

Copy

再次启动项目就不报错了 (如果还报刚才的错可以刷新下项目 刷新下 maven 等)


这时候访问 http://localhost:8080/demo/swagger-ui/ 可以看到 swagger 3.0 的默认画面


接下来写一个最简单的接口模拟开发场景


LoginController.java

package com.example.demo.controller;


import com.example.demo.form.LoginForm;
import com.fasterxml.jackson.databind.util.JSONPObject;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/login")
@Api(tags = "登录接口")
public class LoginController {
    private Logger log = LoggerFactory.getLogger(getClass());


    @PostMapping("login")
    @ApiOperation("登录测试")
    public Map login(@RequestBody LoginForm form) {

        // 模拟传入数据
        System.out.println(form.toString());

        // 模拟返回数据
        return new HashMap() {{
            put("code", 200);
            put("message", "success");
        }};
    }
}

Java

Copy


LoginForm.java

package com.example.demo.form;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(value = "登录表单")
public class LoginForm {

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

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

    @ApiModelProperty(value = "手机号")
    private Integer mobile;

    @ApiModelProperty(value = "验证码")
    private Integer verifyCode;
}

Java

Copy


使用swagger调用效果如图


再加个简单的配置文件

package com.example.demo.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

@EnableOpenApi
@Configuration
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.OAS_30)
                .enable(true)
                .apiInfo(new ApiInfoBuilder()
                        .title("DEMO测试系统")
                        .description("DEMO测试系统 API接口文档")
                        .version("1.0.0")
                        .contact(new Contact("admin", "httsp://www.xxx.com", "admin@xxx.com"))
                        .build())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
                .paths(PathSelectors.any())
                .build();
    }
}

Java

Copy


对应页面效果图

配置 knife4j


这个可以看做 swagger 3.0 web界面简单美化


添加 maven 依赖
pom.xml

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

XML

Copy


刷新 maven 以后直接重启项目即可
访问地址改为
http://localhost:8080/demo/doc.html


基础效果如下图



配置 Mybatis Plus 相关


官网
https://baomidou.com/


maven相关
pom.xml

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

XML

Copy


idea插件 MybatisX (用于对着数据库表逆向生成代码)


配置数据库连接
application-dev.yml

# 开发环境
server:
  port: 8080
  servlet:
    context-path: /demo
spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&tinyInt1isBit=false&allowPublicKeyRetrieval=true
    username: root
    password:
mybatis-plus:
  mapper-locations: classpath*:/mapper/*.xml
  type-aliases-package: com.example.demo.service
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0
    banner: false
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: true
    call-setters-on-nulls: true

YAML

Copy


随便开个库创建个简单的表


在 idea 中配置数据库连接













到这里已经生成完毕了
简单贴几个生成出来的文件代码


DbUser

package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;

/**
 * 用户表
 * @TableName db_user
 */
@TableName(value ="db_user")
@Data
public class DbUser implements Serializable {
    /**
     * 自增ID
     */
    @TableId(type = IdType.AUTO)
    private Integer id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 手机号
     */
    private Integer mobile;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;

    /**
     * 软删除 0 正常 1 删除
     */
    private Integer delFlag;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

Java

Copy

DbUserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.DbUserMapper">

    <resultMap id="BaseResultMap" type="com.example.demo.entity.DbUser">
            <id property="id" column="id" jdbcType="INTEGER"/>
            <result property="username" column="username" jdbcType="VARCHAR"/>
            <result property="password" column="password" jdbcType="VARCHAR"/>
            <result property="mobile" column="mobile" jdbcType="INTEGER"/>
            <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
            <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
            <result property="delFlag" column="del_flag" jdbcType="TINYINT"/>
    </resultMap>

    <sql id="Base_Column_List">
        id,username,password,
        mobile,create_time,update_time,
        del_flag
    </sql>
</mapper>

XML

Copy


之后需要在程序中指定 service 和 mapper 的位置才可以启动项目
service 在刚才的配置文件 application-dev.yml 中的 type-aliases-package 一栏中配置例如 com.example.demo.service


mapper 在启动类 DemoApplication 上面加一个注解 @MapperScan(basePackages = "")

@SpringBootApplication
@MapperScan(basePackages = "com.example.demo.mapper")
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Java

Copy



随便改造下刚才的示例
LoginController

@RestController
@RequestMapping("/login")
@Api(tags = "登录接口")
public class LoginController {
    private Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private DbUserService userService;

    @PostMapping("login")
    @ApiOperation(value = "登录测试")
    public Result login(@RequestBody LoginForm form) {
        DbUser user = new DbUser();
        // 复制同名参数 从form到entity
        BeanUtils.copyProperties(form, user);
        // 保存到数据库 (MybatisPlus自带方法)
        boolean save = userService.save(user);
        if(!save){
            throw new RuntimeException("系统错误: 保存到数据库发生异常!");
        }
        // 模拟返回数据
        return Result.success(user);
    }
}

Java

Copy



swagger在线调试效果


mysql中数据查看


(这里我发现mobile用int类型会超出范围,后全部改成string/varchar(20),与前文代码略有出入)

END

基本就是这样,可以快速实现增删改查,前端联调,只要建表生成,写接口逻辑即可

末尾再附上一些常用工具类



JSON工具类
JsonUtils.java

package com.skmagic.common.utils;


import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.List;
import java.util.Map;


/**
 * JSON 工具类
 * 基于Springboot自带的 Jackson
 *
 * @author zzzmh
 * @version 1.0.0
 * @email admin@zzzmh.cn
 * @date 2020/4/21 16:23
 */
public class JsonUtils {
    private static Logger logger = LoggerFactory.getLogger(JsonUtils.class);

    public static ObjectMapper mapper = new ObjectMapper();

    static {
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.registerModule(new JavaTimeModule());
    }


    public static String toJSONString(Object object) {
        String result = "";
        try {
            result = mapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return result;
    }

    public static <T> T toObject(String json, Class<T> clazz) {
        T result = null;
        try {
            result = mapper.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return result;
    }

    public static <T> List toArray(String json, Class<T> clazz) {
        try {
            return (List) mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, clazz));
        } catch (JsonParseException e) {
            logger.error("decode(String, JsonTypeReference<T>)", e);
            e.printStackTrace();
        } catch (JsonMappingException e) {
            logger.error("decode(String, JsonTypeReference<T>)", e);
            e.printStackTrace();
        } catch (IOException e) {
            logger.error("decode(String, JsonTypeReference<T>)", e);
            e.printStackTrace();
        }
        return null;
    }

    public static Map<String, Object> toObject(String json) {
        return toObject(json, Map.class);
    }

    public static List<Map<String, Object>> toArray(String json) {
        return toArray(json, Map.class);
    }

    public static void main(String[] args) {
        String test = "[{\"key\":1,\"value\":1},{\"key\":1,\"value\":2},{\"key\":2,\"value\":1},{\"key\":2,\"value\":2}]";
        List<Map<String, Object>> array = toArray(test);
        System.out.println(array);
    }
}

Java

Copy



Redis相关
pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

XML

Copy


RedisConfig.java

@Configuration
public class RedisConfig {
    @Autowired
    private RedisConnectionFactory factory;

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericToStringSerializer(Object.class));
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }

    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    @Bean
    public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }
}

Java

Copy


RedisUtils.java

@Component
public class RedisUtils {
    @Autowired
    private RedisTemplate redisTemplate;
    @Resource(name = "redisTemplate")
    private ValueOperations<String, String> valueOperations;
    @Resource(name = "redisTemplate")
    private HashOperations<String, String, Object> hashOperations;
    @Resource(name = "redisTemplate")
    private ListOperations<String, Object> listOperations;
    @Resource(name = "redisTemplate")
    private SetOperations<String, Object> setOperations;
    @Resource(name = "redisTemplate")
    private ZSetOperations<String, Object> zSetOperations;
    /**
     * 默认过期时长,单位:秒
     */
    public final static long DEFAULT_EXPIRE = 60 * 60 * 12;
    /**
     * 不设置过期时长
     */
    public final static long NOT_EXPIRE = -1;

    /**
     * 计数器
     */
    public Long increment(String key, long expire) {
        Long increment = valueOperations.increment(key);
        // 单位时间内首次计数才设置过期时间
        if (increment == 1) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return increment;
    }

    /**
     * redis计数器 默认每次加一
     */
    public Long increment(String key) {
        return valueOperations.increment(key, 1);
    }

    public Long getIncrement(final String key) {
        return (Long) redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] rowkey = serializer.serialize(key);
                byte[] rowval = connection.get(rowkey);
                try {
                    String val = serializer.deserialize(rowval);
                    return Long.parseLong(val);
                } catch (Exception e) {
                    return 0L;
                }
            }
        });
    }

    public void set(String key, Object value, long expire) {
        valueOperations.set(key, toJson(value));
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    public void set(String key, Object value) {
        set(key, value, DEFAULT_EXPIRE);
    }

    public <T> T get(String key, Class<T> clazz, long expire) {
        String value = valueOperations.get(key);
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value == null ? null : fromJson(value, clazz);
    }

    public <T> T get(String key, Class<T> clazz) {
        return get(key, clazz, NOT_EXPIRE);
    }

    public String get(String key, long expire) {
        String value = valueOperations.get(key);
        if (expire != NOT_EXPIRE) {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value;
    }

    public String get(String key) {
        return get(key, NOT_EXPIRE);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /**
     * Object转成JSON数据
     */
    private String toJson(Object object) {
        if (object instanceof Integer || object instanceof Long || object instanceof Float ||
                object instanceof Double || object instanceof Boolean || object instanceof String) {
            return String.valueOf(object);
        }
        return JsonUtils.toJSONString(object);
    }

    /**
     * JSON数据,转成Object
     */
    private <T> T fromJson(String json, Class<T> clazz) {
        return JsonUtils.toObject(json, clazz);
    }

    public Map<String, String> getAllKV() {
        Map<String, String> result = new HashMap<>();
        Set<String> set = redisTemplate.keys("*");
        for (String k : set) {
            result.put(k, get(k));
        }
        return result;
    }
}

来源:https://www.zzzmh.cn/post/6bc7c6496cf24fd8830563254b02de08

腾讯云推出云产品限时特惠抢购活动:2C2G云服务器7.9元/月起
本文链接:https://www.jhelp.net/p/i1B3jVYYB0LqyTti (转载请保留)。
关注下面的标签,发现更多相似文章