【Java测开】初始化搭建springboot+mybatis-plus项目

本文详细介绍了如何初始化一个SpringBoot+Mybatis-Plus项目,包括父工程配置、公共工程的全局返回结果和异常处理、代码生成器的使用。在业务工程部分,讲解了数据库表设计、基础业务操作、复杂查询、业务异常校验以及分组和嵌套参数校验。最后,文章还涵盖了Swagger接口文档的集成和优化。
摘要由CSDN通过智能技术生成

目录


前言

本文介绍如何搭建springboot+mybatis-plus项目。


父工程(Parent)

如何创建springboot项目,参考前文:【Java测开】使用idea初始化创建springboot+vue项目

父工程主要用于组织子工程和子工程依赖管理,仅需配置pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.aegis</groupId>
    <artifactId>springboot-vue-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <name>springboot-vue-project</name>
    <modules>
        <module>frontend</module>
        <module>backend</module>
    </modules>

    <!-- 集中定义依赖版本号 -->
    <properties>
        <spring-boot.version>2.6.7</spring-boot.version>
        <fastjson.version>1.2.72</fastjson.version>
        <mybatis-plus.version>3.5.2</mybatis-plus.version>
        <freemarker.version>2.3.28</freemarker.version>
        <druid.version>1.1.10</druid.version>
        <mysql.version>8.0.28</mysql.version>
        <lombok.version>1.18.24</lombok.version>
    </properties>

    <!-- 依赖管理标签  必须加 -->
    <dependencyManagement>
        <dependencies>
            <!--spring-boot包-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>${spring-boot.version}</version>
<!--                <exclusions>-->
<!--                    <exclusion>-->
<!--                        <groupId>org.springframework.boot</groupId>-->
<!--                        <artifactId>spring-boot-starter-tomcat</artifactId>-->
<!--                    </exclusion>-->
<!--                </exclusions>-->
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <version>${spring-boot.version}</version>
                <scope>test</scope>
            </dependency>

            <!--  TODO lombok配置,简化POJO实体类开发和日志Slf4j-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
<!--                <optional>true</optional>-->
            </dependency>


            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>

            <!--TODO mybatis-plus的springboot支持-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!--TODO mybatis-plus代码生成器-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-generator</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!--TODO freemarker模板引擎-->
            <dependency>
                <groupId>org.freemarker</groupId>
                <artifactId>freemarker</artifactId>
                <version>${freemarker.version}</version>
            </dependency>

            <!--mysql驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
                <scope>runtime</scope>
            </dependency>

            <!--  TODO druid连接池配置-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>



        </dependencies>

    </dependencyManagement>



</project>

公共工程(common)

新建maven模块:common,封装公共处理模块

pom文件配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springboot-vue-demo</artifactId>
        <groupId>com.aegis</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>autotest-platform-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <description>common通用工具</description>

  <dependencies>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
      </dependency>
      <dependency>
          <groupId>io.swagger</groupId>
          <artifactId>swagger-annotations</artifactId>
      </dependency>


      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!--spring-boot 参数校验-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-validation</artifactId>
      </dependency>
      
      
      <!--mybatis-plus的springboot支持-->
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-boot-starter</artifactId>
      </dependency>
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-extension</artifactId>
      </dependency>

      
      <!--TODO mybatis-plus代码生成器-->
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-generator</artifactId>
      </dependency>
<!--      &lt;!&ndash;TODO freemarker模板引擎&ndash;&gt;-->
<!--      <dependency>-->
<!--          <groupId>org.freemarker</groupId>-->
<!--          <artifactId>freemarker</artifactId>-->
<!--      </dependency>-->
      <dependency>
          <groupId>org.apache.velocity</groupId>
          <artifactId>velocity-engine-core</artifactId>
      </dependency>
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
      </dependency>
      <!-- 连接池 -->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
      </dependency>


  </dependencies>

</project>

项目结构

在这里插入图片描述

全局统一返回结果

核心依赖

<!--mybatis-plus的springboot支持-->
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-boot-starter</artifactId>
      </dependency>
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-extension</artifactId>
      </dependency>

MybatisPlusConfig(配置MP的分页插件)

创建config包,包下新建MybatisPlusConfig类

package com.aegis.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


// TODO 配置MP的分页插件
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

CommonPage(分页数据封装)

package com.aegis.common.api;


import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.jetbrains.annotations.NotNull;

import java.util.List;

/**
 * 分页数据封装类
 */
@Data
@ApiModel(value = "分页数据封装")
public class CommonPage<T>{
    @ApiModelProperty("当前页")
    private long pageNum;

    @ApiModelProperty("页大小")
    private long pageSize;

    @ApiModelProperty("总记录数")
    private long totalCount;

    @ApiModelProperty("总页数")
    private long totalPage;

    @ApiModelProperty("查询结果列表")
    private List<T> list;


    /**
     * 将PageHelper分页后的list转为分页信息
     */

    @NotNull
    public static <T> CommonPage<T> restPage(@NotNull IPage<T> pageInfo) {
        CommonPage<T> result = new CommonPage<>();
        result.setTotalPage(pageInfo.getPages());
        result.setPageNum(pageInfo.getCurrent());
        result.setPageSize(pageInfo.getSize());
        result.setTotalCount(pageInfo.getTotal());
        result.setList(pageInfo.getRecords());
        return result;
    }
}

IErrorCode(API错误码接口)

package com.aegis.common.api;

/**
 * 封装API的错误码
 */
public interface IErrorCode {
     long getCode();
     String getMessage();
}

ResultCode(API错误码Enum类实现)

package com.aegis.common.api;

public enum ResultCode implements IErrorCode {
    SUCCESS(200,"请求成功"),
    FAILED(500, "操作失败"),
    VALIDATE_FAILED(501, "参数检验失败"),

    // 1xx Informational
    /**
     * {@code 100 Continue}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.2.1">HTTP/1.1:
     *      Semantics and Content, section 6.2.1</a>
     */
    CONTINUE(100, "继续"),

    /**
     * {@code 101 Switching Protocols}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.2.2">HTTP/1.1:
     *      Semantics and Content, section 6.2.2</a>
     */
    SWITCHING_PROTOCOLS(101, "切换协议"),

    /**
     * {@code 102 Processing}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc2518#section-10.1">WebDAV</a>
     */
    PROCESSING(102, "处理"),

    /**
     * {@code 103 Checkpoint}.
     *
     * @see <a href=
     *      "http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal">A
     *      proposal for supporting resumable POST/PUT HTTP requests in HTTP/1.0</a>
     */
    CHECKPOINT(103, "检查点"),


    // 2xx Success


    /**
     * {@code 200 OK}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.1">HTTP/1.1:
     *      Semantics and Content, section 6.3.1</a>
     */
    OK(200, "OK"),

    /**
     * {@code 201 Created}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.2">HTTP/1.1:
     *      Semantics and Content, section 6.3.2</a>
     */
    CREATED(201, "创建"),

    /**
     * {@code 202 Accepted}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.3">HTTP/1.1:
     *      Semantics and Content, section 6.3.3</a>
     */
    ACCEPTED(202, "接受"),

    /**
     * {@code 203 Non-Authoritative Information}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.4">HTTP/1.1:
     *      Semantics and Content, section 6.3.4</a>
     */
    NON_AUTHORITATIVE_INFORMATION(203, "非权威信息"),

    /**
     * {@code 204 No Content}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.5">HTTP/1.1:
     *      Semantics and Content, section 6.3.5</a>
     */
    NO_CONTENT(204, "无内容"),

    /**
     * {@code 205 Reset Content}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.6">HTTP/1.1:
     *      Semantics and Content, section 6.3.6</a>
     */
    RESET_CONTENT(205, "重置内容"),

    /**
     * {@code 206 Partial Content}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7233#section-4.1">HTTP/1.1: Range
     *      Requests, section 4.1</a>
     */
    PARTIAL_CONTENT(206, "部分内容"),

    /**
     * {@code 207 Multi-Status}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc4918#section-13">WebDAV</a>
     */
    MULTI_STATUS(207, "多状态"),

    /**
     * {@code 208 Already Reported}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc5842#section-7.1">WebDAV Binding
     *      Extensions</a>
     */
    ALREADY_REPORTED(208, "已经报告"),

    /**
     * {@code 226 IM Used}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc3229#section-10.4.1">Delta
     *      encoding in HTTP</a>
     */
    IM_USED(226, "IM 使用"),


    // 3xx Redirection


    /**
     * {@code 300 Multiple Choices}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.1">HTTP/1.1:
     *      Semantics and Content, section 6.4.1</a>
     */
    MULTIPLE_CHOICES(300, "多种选择"),

    /**
     * {@code 301 Moved Permanently}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.2">HTTP/1.1:
     *      Semantics and Content, section 6.4.2</a>
     */
    MOVED_PERMANENTLY(301, "永久移动"),

    /**
     * {@code 302 Found}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.3">HTTP/1.1:
     *      Semantics and Content, section 6.4.3</a>
     */
    FOUND(302, "找到"),

    /**
     * {@code 302 Moved Temporarily}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc1945#section-9.3">HTTP/1.0,
     *      section 9.3</a>
     * @deprecated in favor of {@link #FOUND} which will be returned from
     *             {@code HttpStatus.valueOf(302)}
     */
    @Deprecated
    MOVED_TEMPORARILY(302, "Moved Temporarily"),

    /**
     * {@code 303 See Other}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.4">HTTP/1.1:
     *      Semantics and Content, section 6.4.4</a>
     */
    SEE_OTHER(303, "见其他"),

    /**
     * {@code 304 Not Modified}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7232#section-4.1">HTTP/1.1:
     *      Conditional Requests, section 4.1</a>
     */
    NOT_MODIFIED(304, "未修改"),

    /**
     * {@code 305 Use Proxy}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.5">HTTP/1.1:
     *      Semantics and Content, section 6.4.5</a>
     * @deprecated due to security concerns regarding in-band configuration of a
     *             proxy
     */
    @Deprecated
    USE_PROXY(305, "使用代理"),

    /**
     * {@code 307 Temporary Redirect}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.7">HTTP/1.1:
     *      Semantics and Content, section 6.4.7</a>
     */
    TEMPORARY_REDIRECT(307, "临时重定向"),

    /**
     * {@code 308 Permanent Redirect}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7238">RFC 7238</a>
     */
    PERMANENT_REDIRECT(308, "永久重定向"),


    // --- 4xx Client Error ---


    /**
     * {@code 400 Bad Request}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.1">HTTP/1.1:
     *      Semantics and Content, section 6.5.1</a>
     */
    BAD_REQUEST(400, "错误请求"),

    /**
     * {@code 401 Unauthorized}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7235#section-3.1">HTTP/1.1:
     *      Authentication, section 3.1</a>
     */
    UNAUTHORIZED(401, "未登录或登录已过期,请重新登录~"),

    /**
     * {@code 402 Payment Required}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.2">HTTP/1.1:
     *      Semantics and Content, section 6.5.2</a>
     */
    PAYMENT_REQUIRED(402, "需要付款"),

    /**
     * {@code 403 Forbidden}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.3">HTTP/1.1:
     *      Semantics and Content, section 6.5.3</a>
     */
    FORBIDDEN(403, "没有相关权限"),

    /**
     * {@code 404 Not Found}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.4">HTTP/1.1:
     *      Semantics and Content, section 6.5.4</a>
     */
    NOT_FOUND(404, "未找到"),

    /**
     * {@code 405 Method Not Allowed}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.5">HTTP/1.1:
     *      Semantics and Content, section 6.5.5</a>
     */
    METHOD_NOT_ALLOWED(405, "方法不允许"),

    /**
     * {@code 406 Not Acceptable}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.6">HTTP/1.1:
     *      Semantics and Content, section 6.5.6</a>
     */
    NOT_ACCEPTABLE(406, "不可接受"),
    /**
     * {@code 407 Proxy Authentication Required}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7235#section-3.2">HTTP/1.1:
     *      Authentication, section 3.2</a>
     */
    PROXY_AUTHENTICATION_REQUIRED(407, "需要代理验证"),

    /**
     * {@code 408 Request Timeout}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.7">HTTP/1.1:
     *      Semantics and Content, section 6.5.7</a>
     */
    REQUEST_TIMEOUT(408, "请求超时"),

    /**
     * {@code 409 Conflict}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.8">HTTP/1.1:
     *      Semantics and Content, section 6.5.8</a>
     */
    CONFLICT(409, "冲突"),

    /**
     * {@code 410 Gone}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.9">HTTP/1.1:
     *      Semantics and Content, section 6.5.9</a>
     */
    GONE(410, "走了"),

    /**
     * {@code 411 Length Required}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.10">HTTP/1.1:
     *      Semantics and Content, section 6.5.10</a>
     */
    LENGTH_REQUIRED(411, "所需长度"),

    /**
     * {@code 412 Precondition failed}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7232#section-4.2">HTTP/1.1:
     *      Conditional Requests, section 4.2</a>
     */
    PRECONDITION_FAILED(412, "先决条件失败"),

    /**
     * {@code 413 Payload Too Large}.
     *
     * @since 4.1
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.11">HTTP/1.1:
     *      Semantics and Content, section 6.5.11</a>
     */
    PAYLOAD_TOO_LARGE(413, "请求实体太大"),

    /**
     * {@code 413 Request Entity Too Large}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.14">HTTP/1.1,
     *      section 10.4.14</a>
     * @deprecated in favor of {@link #PAYLOAD_TOO_LARGE} which will be returned
     *             from {@code HttpStatus.valueOf(413)}
     */
    @Deprecated
    REQUEST_ENTITY_TOO_LARGE(413, "请求实体太大"),

    /**
     * {@code 414 URI Too Long}.
     *
     * @since 4.1
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.12">HTTP/1.1:
     *      Semantics and Content, section 6.5.12</a>
     */
    URI_TOO_LONG(414, "请求URI太长"),

    /**
     * {@code 414 Request-URI Too Long}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.15">HTTP/1.1,
     *      section 10.4.15</a>
     * @deprecated in favor of {@link #URI_TOO_LONG} which will be returned from
     *             {@code HttpStatus.valueOf(414)}
     */
    @Deprecated
    REQUEST_URI_TOO_LONG(414, "请求URI太长"),

    /**
     * {@code 415 Unsupported Media Type}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.13">HTTP/1.1:
     *      Semantics and Content, section 6.5.13</a>
     */
    UNSUPPORTED_MEDIA_TYPE(415, "不支持的媒体类型"),

    /**
     * {@code 416 Requested Range Not Satisfiable}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7233#section-4.4">HTTP/1.1: Range
     *      Requests, section 4.4</a>
     */
    REQUESTED_RANGE_NOT_SATISFIABLE(416, "请求范围不满足"),

    /**
     * {@code 417 Expectation Failed}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.14">HTTP/1.1:
     *      Semantics and Content, section 6.5.14</a>
     */
    EXPECTATION_FAILED(417, "期望失败"),

    /**
     * {@code 418 I'm a teapot}.
     *
     * @see <a href=
     *      "http://tools.ietf.org/html/rfc2324#section-2.3.2">HTCPCP/1.0</a>
     */
    I_AM_A_TEAPOT(418, "我是茶壶"),

    /**
     * @deprecated See <a href=
     *             "http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV
     *             Draft Changes</a>
     */
    @Deprecated
    INSUFFICIENT_SPACE_ON_RESOURCE(419, "资源空间不足"),

    /**
     * @deprecated See <a href=
     *             "http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV
     *             Draft Changes</a>
     */
    @Deprecated
    METHOD_FAILURE(420, "方法失效"),

    /**
     * @deprecated See <a href=
     *             "http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV
     *             Draft Changes</a>
     */
    @Deprecated
    DESTINATION_LOCKED(421, "目的地锁定"),

    /**
     * {@code 422 Unprocessable Entity}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc4918#section-11.2">WebDAV</a>
     */
    UNPROCESSABLE_ENTITY(422, "无法处理的实体"),

    /**
     * {@code 423 Locked}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc4918#section-11.3">WebDAV</a>
     */
    LOCKED(423, "锁定的"),

    /**
     * {@code 424 Failed Dependency}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc4918#section-11.4">WebDAV</a>
     */
    FAILED_DEPENDENCY(424, "依赖失败"),

    /**
     * {@code 426 Upgrade Required}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc2817#section-6">Upgrading to TLS
     *      Within HTTP/1.1</a>
     */
    UPGRADE_REQUIRED(426, "需要升级"),

    /**
     * {@code 428 Precondition Required}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc6585#section-3">Additional HTTP
     *      Status Codes</a>
     */
    PRECONDITION_REQUIRED(428, "要求先决条件"),

    /**
     * {@code 429 Too Many Requests}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc6585#section-4">Additional HTTP
     *      Status Codes</a>
     */
    TOO_MANY_REQUESTS(429, "请求太多"),

    /**
     * {@code 431 Request Header Fields Too Large}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc6585#section-5">Additional HTTP
     *      Status Codes</a>
     */
    REQUEST_HEADER_FIELDS_TOO_LARGE(431, "请求头字段太大"),

    /**
     * {@code 451 Unavailable For Legal Reasons}.
     *
     * @see <a href=
     *      "https://tools.ietf.org/html/draft-ietf-httpbis-legally-restricted-status-04">
     *      An HTTP Status Code to Report Legal Obstacles</a>
     * @since 4.3
     */
    UNAVAILABLE_FOR_LEGAL_REASONS(451, "因法律原因无法获得"),


    // --- 5xx Server Error ---


    /**
     * {@code 500 Internal Server Error}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.1">HTTP/1.1:
     *      Semantics and Content, section 6.6.1</a>
     */
    INTERNAL_SERVER_ERROR(500, "内部服务器错误"),

    /**
     * {@code 501 Not Implemented}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.2">HTTP/1.1:
     *      Semantics and Content, section 6.6.2</a>
     */
    NOT_IMPLEMENTED(501, "未实施"),

    /**
     * {@code 502 Bad Gateway}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.3">HTTP/1.1:
     *      Semantics and Content, section 6.6.3</a>
     */
    BAD_GATEWAY(502, "错误网关"),

    /**
     * {@code 503 Service Unavailable}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.4">HTTP/1.1:
     *      Semantics and Content, section 6.6.4</a>
     */
    SERVICE_UNAVAILABLE(503, "服务不可用"),

    /**
     * {@code 504 Gateway Timeout}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.5">HTTP/1.1:
     *      Semantics and Content, section 6.6.5</a>
     */
    GATEWAY_TIMEOUT(504, "网关超时"),

    /**
     * {@code 505 HTTP Version Not Supported}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.6">HTTP/1.1:
     *      Semantics and Content, section 6.6.6</a>
     */
    HTTP_VERSION_NOT_SUPPORTED(505, "不支持HTTP版本"),

    /**
     * {@code 506 Variant Also Negotiates}
     *
     * @see <a href="http://tools.ietf.org/html/rfc2295#section-8.1">Transparent
     *      Content Negotiation</a>
     */
    VARIANT_ALSO_NEGOTIATES(506, "变型也谈判"),

    /**
     * {@code 507 Insufficient Storage}
     *
     * @see <a href="http://tools.ietf.org/html/rfc4918#section-11.5">WebDAV</a>
     */
    INSUFFICIENT_STORAGE(507, "存储不足"),

    /**
     * {@code 508 Loop Detected}
     *
     * @see <a href="http://tools.ietf.org/html/rfc5842#section-7.2">WebDAV Binding
     *      Extensions</a>
     */
    LOOP_DETECTED(508, "回路检测"),

    /**
     * {@code 509 Bandwidth Limit Exceeded}
     */
    BANDWIDTH_LIMIT_EXCEEDED(509, "超出带宽限制"),

    /**
     * {@code 510 Not Extended}
     *
     * @see <a href="http://tools.ietf.org/html/rfc2774#section-7">HTTP Extension
     *      Framework</a>
     */
    NOT_EXTENDED(510, "未扩展"),

    /**
     * {@code 511 Network Authentication Required}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc6585#section-6">Additional HTTP
     *      Status Codes</a>
     */
    NETWORK_AUTHENTICATION_REQUIRED(511, "需要网络认证");


    ResultCode(int code, String message) {
        this.code = code;
        this.message = message;
    }

    private final int code;
    private final String message;

    @Override
    public int getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }
}

R(API响应封装)

package com.aegis.common.api;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;


@Data
@ApiModel(value = "全局统一返回结果")
public class R<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "状态码", required = true)
    private int code;

    @ApiModelProperty(value = "返回消息",required = true)
    private String message;

    @ApiModelProperty(value = "返回数据",required = true)
    private T data;

    private R() {

    }

    public R(int code, String message, T data) {
        this.code = code;
        this.message = message;
        if (data instanceof Page<?>) {
            Page<?> page = (Page<?>) data;
            this.data = (T) CommonPage.restPage(page);
        } else {
            this.data = data;
        }
    }


    /**
     * 成功返回结果(默认成功返回数据)
     *
     */
    public static <T> R<T> success(){
        return new R<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(),null);
    }

    /**
     * 成功返回结果(自定义提示信息)
     *
     */
    public static <T> R<T> success(String message){
        return new R<T>(ResultCode.SUCCESS.getCode(), message,null);
    }

    /**
     * 成功返回结果(自定义成功业务返回数据)
     *
     */
    public static <T> R<T> success(T data){
        return new R<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(),data);
    }

    /**
     * 成功返回结果(自定义成功提示信息)
     *
     */
    public static <T> R<T>  success(T data, String message){
        return new R<T>(ResultCode.SUCCESS.getCode(), message, data);
    }

    /**
     * 失败返回结果(返回所有失败类型默认信息)
     * @param errorCode 错误码
     */
    public static  <T> R<T>  fail(IErrorCode errorCode) {
        return new R<T>(errorCode.getCode(), errorCode.getMessage(), null);
    }

    /**
     * 失败返回结果(返回所有失败类型自定义提示信息)
     * @param errorCode 错误码
     * @param message 错误信息
     */
    public static  <T> R<T> fail(IErrorCode errorCode, String message) {
        return new R<T>(errorCode.getCode(), message, null);
    }

    /**
     * 失败返回结果(返回默认FAILED失败信息)
     */
    public static  <T> R<T>  fail() {
        return fail(ResultCode.FAILED);
    }

    /**
     * 失败返回结果(返回FAILED业务操作失败原因)
     * @param message 提示信息
     */
    public static  <T> R<T>  fail(String message) {
        return new R<T>(ResultCode.FAILED.getCode(), message, null);
    }


    /**
     * 失败返回结果(返回FAILED业务操作失败原因和业务数据)
     * @param message 提示信息
     */
    public static  <T> R<T>  fail(T data, String message) {
        return new R<T>(ResultCode.FAILED.getCode(), message, data);
    }



    /**
     * 参数验证失败返回结果(默认参数错误提示信息)
     */
    public static  <T> R<T>  validateFailed() {
        return fail(ResultCode.VALIDATE_FAILED);
    }


    /**
     * 参数验证失败返回结果(自定义参数错误提示信息)
     * @param message 提示信息
     */
    public static  <T> R<T>  validateFailed(String message) {
        return new R<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null);
    }

    /**
     * 未登录返回结果
     */
    public static  <T> R<T>  unauthorized(T data) {
        return new R<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
    }

    /**
     * 未授权返回结果
     */
    public static  <T> R<T>  forbidden(T data) {
        return new R<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
    }

    /**
     * 状态设置
     */
    public static <T> R<T> status(boolean flag) {
        return flag ? success("操作成功") : fail("操作失败");
    }


}

全局统一异常处理

核心依赖

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!--spring-boot 参数校验-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-validation</artifactId>
      </dependency>

BusinessException(自定义业务异常)

package com.aegis.common.exception;


public class BusinessException extends RuntimeException{
    /**
     * 返回code
     */
    private Integer code;
    /**
     * 返回msg
     */
    private String message;

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }

}

CommonExceptionHandler(异常处理封装)

package com.aegis.common.exception;

import com.aegis.common.api.R;
import com.aegis.common.api.ResultCode;
import io.swagger.annotations.ApiModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.NotReadablePropertyException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolationException;
import java.sql.SQLIntegrityConstraintViolationException;

@ApiModel(value = "全局统一异常处理")
@RestControllerAdvice
@ResponseBody
@Slf4j
public class CommonExceptionHandler {
    /*
    业务异常处理
     */
    @ExceptionHandler(BusinessException.class)
    public R<String> handleBusinessExceptionHandler(BusinessException ex){
        log.error("BusinessException异常:{}", ex.getMessage());
        return R.fail(ResultCode.FAILED,ex.getMessage());
    }

    /**
     * 请求参数异常处理
     * @param ex
     * @return
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.OK)
    public R<MethodArgumentNotValidException> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        StringBuilder sb = new StringBuilder("");
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage());
        }
        String msg = sb.toString();
        return R.fail(ResultCode.VALIDATE_FAILED, msg);
    }


    /**
     * 集合类型的请求参数异常处理
     * @param ex
     * @return
     */
    @ExceptionHandler({NotReadablePropertyException.class})
    @ResponseStatus(HttpStatus.OK)
    public R<NotReadablePropertyException> handleNotReadablePropertyException(NotReadablePropertyException ex) {
        StringBuilder sb =new StringBuilder(ex.getPropertyName());
        String propertyName = sb.substring(sb.indexOf("[") + 1,sb.indexOf("]"));
        int index = Integer.parseInt(propertyName)+1;
        return R.fail(ResultCode.VALIDATE_FAILED,"第"+index+"条数据存在问题");
    }


    @ExceptionHandler({ConstraintViolationException.class})
    @ResponseStatus(HttpStatus.OK)
    public R<ConstraintViolationException> handleConstraintViolationException(ConstraintViolationException ex) {
        return R.fail(ResultCode.VALIDATE_FAILED, ex.getMessage());
    }


    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException ex){
        log.error(ex.getMessage());
        if(ex.getMessage().contains("Duplicate entry")){
            String[] split = ex.getMessage().split(" ");
            String msg = split[2] + "已存在";
            return R.fail(msg);
        }
        return R.fail("未知错误");
    }
}

代码生成器

核心依赖

 <!--TODO mybatis-plus代码生成器-->
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-generator</artifactId>
      </dependency>
<!--      &lt;!&ndash;TODO freemarker模板引擎&ndash;&gt;-->
<!--      <dependency>-->
<!--          <groupId>org.freemarker</groupId>-->
<!--          <artifactId>freemarker</artifactId>-->
<!--      </dependency>-->
      <dependency>
          <groupId>org.apache.velocity</groupId>
          <artifactId>velocity-engine-core</artifactId>
      </dependency>
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
      </dependency>
      <!-- 连接池 -->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
      </dependency>

自定义模板(vm文件)

controller.java.vm
package ${package.Controller};


import com.aegis.common.api.R;
import io.swagger.annotations.*;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.web.bind.annotation.*;
import ${package.Service}.${table.serviceName};
import ${package.Entity}.${entity};
import javax.annotation.Resource;
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end

/**
 * <p>
 * $!{table.comment} 前端控制器
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
#if(${restControllerStyle})
@RestController
#else
@Controller
#end
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
@Api(value = "${table.controllerName}",tags = "${table.controllerName}")
#if(${kotlin})
class ${table.controllerName}#if(${superControllerClass}) : ${superControllerClass}()#end

#else
#if(${superControllerClass})
public class ${table.controllerName} extends ${superControllerClass} {
#else
public class ${table.controllerName} {
#end

    @Resource
    private ${table.serviceName} ${table.entityPath}Service;

    @GetMapping("/detail/{id}")
    @ApiOperation("查询单条数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    @ApiResponses(@ApiResponse(code = 200, message = "成功", response = ${entity}.class))
    public R<${entity}> detail(@PathVariable("id") Long id){
        ${entity} ${table.entityPath} = ${table.entityPath}Service.getById(id);
        if (${table.entityPath} != null) {
            return R.success(${table.entityPath});
        } else {
            return R.fail("id不存在");
        }
    }

    @PostMapping("/list")
    @ApiOperation("查询列表")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    @ApiResponses(@ApiResponse(code = 200, message = "成功", response = ${entity}.class))
    public R<IPage<${entity}>> search(@RequestParam(required = false, defaultValue = "1") Integer pageNum,
                                      @RequestParam(required = false, defaultValue = "10") Integer pageSize,
                                      @RequestBody ${entity} ${table.entityPath}){
            return R.success(${table.entityPath}Service.getAllByPage(pageNum,pageSize,${table.entityPath}));
    }

    @PostMapping("/save")
    @ApiOperation("添加数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<${entity}> save(@RequestBody ${entity} ${table.entityPath}){
        boolean flag = ${table.entityPath}Service.save(${table.entityPath});
        if(flag){
            return R.success(${table.entityPath});
        }else{
            return R.fail("添加数据失败");
        }
    }

    @PostMapping("/update")
    @ApiOperation("修改数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<${entity}> update(@RequestBody ${entity} ${table.entityPath}){
            return R.status( ${table.entityPath}Service.updateById(${table.entityPath}));
    }

    @PostMapping("/delete/{id}")
    @ApiOperation("删除数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public R<${entity}> delete(@PathVariable("id") Long id){
        boolean flag = ${table.entityPath}Service.removeById(id);
        if(flag){
            return R.success();
        }else{
            return R.fail("添加数据失败");
        }
    }

}

#end

entity.java.vm
package ${package.Entity};


#foreach($pkg in ${table.importPackages})
import ${pkg};
#end
#if(${swagger})
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
#end
#if(${entityLombokModel})
import lombok.Data;
import lombok.ToString;
#if(${chainModel})
import lombok.experimental.Accessors;
#end
#end

/**
 * <p>
 * $!{table.comment}
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
#if(${entityLombokModel})
@Data
@ToString
  #if(${chainModel})
@Accessors(chain = true)
  #end
#end
#if(${table.convert})
@TableName("${schemaName}${table.name}")
#end
#if(${superEntityClass})
public class ${entity} extends ${superEntityClass}#if(${activeRecord})<${entity}>#end {
#elseif(${activeRecord})
public class ${entity} extends Model<${entity}> {
#elseif(${entitySerialVersionUID})
public class ${entity} implements Serializable {
#else
public class ${entity} {
#end
#if(${entitySerialVersionUID})

    private static final long serialVersionUID = 1L;
#end
## ----------  BEGIN 字段循环遍历  ----------
#foreach($field in ${table.fields})

#if(${field.keyFlag})
#set($keyPropertyName=${field.propertyName})
#end
#if("$!field.comment" != "")
    /**
     * ${field.comment}
     */
#end
#if(${field.keyFlag})
## 主键
  #if(${field.keyIdentityFlag})
    @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
  #elseif(!$null.isNull(${idType}) && "$!idType" != "")
    @TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
  #elseif(${field.convert})
    @TableId("${field.annotationColumnName}")
  #end
## 普通字段
#elseif(${field.fill})
## -----   存在字段填充设置   -----
  #if(${field.convert})
    @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
  #else
    @TableField(fill = FieldFill.${field.fill})
  #end
#elseif(${field.convert})
    @TableField("${field.annotationColumnName}")
#end
## 乐观锁注解
#if(${field.versionField})
    @Version
#end
## 逻辑删除注解
#if(${field.logicDeleteField})
    @TableLogic
#end
    private ${field.propertyType} ${field.propertyName};
#end
## ----------  END 字段循环遍历  ----------

#if(!${entityLombokModel})
#foreach($field in ${table.fields})
  #if(${field.propertyType.equals("boolean")})
    #set($getprefix="is")
  #else
    #set($getprefix="get")
  #end

    public ${field.propertyType} ${getprefix}${field.capitalName}() {
        return ${field.propertyName};
    }

  #if(${chainModel})
    public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  #else
    public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  #end
        this.${field.propertyName} = ${field.propertyName};
  #if(${chainModel})
        return this;
  #end
    }
#end
## --foreach end---
#end
## --end of #if(!${entityLombokModel})--

#if(${entityColumnConstant})
  #foreach($field in ${table.fields})
    public static final String ${field.name.toUpperCase()} = "${field.name}";

  #end
#end
#if(${activeRecord})
    @Override
    public Serializable pkVal() {
  #if(${keyPropertyName})
        return this.${keyPropertyName};
  #else
        return null;
  #end
    }

#end
#if(!${entityLombokModel})
    @Override
    public String toString() {
        return "${entity}{" +
  #foreach($field in ${table.fields})
    #if($!{foreach.index}==0)
        "${field.propertyName}=" + ${field.propertyName} +
    #else
        ", ${field.propertyName}=" + ${field.propertyName} +
    #end
  #end
        "}";
    }
#end
}

mapper.java.vm
package ${package.Mapper};

import ${package.Entity}.${entity};
import ${superMapperClassPackage};
#if(${mapperAnnotation})
import org.apache.ibatis.annotations.Mapper;
#end

/**
 * <p>
 * $!{table.comment} Mapper 接口
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
#if(${mapperAnnotation})
@Mapper
#end
#if(${kotlin})
interface ${table.mapperName} : ${superMapperClass}<${entity}>
#else
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {

}
#end

mapper.xml.vm
<?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="${package.Mapper}.${table.mapperName}">

#if(${enableCache})
    <!-- 开启二级缓存 -->
    <cache type="${cacheClassName}"/>

#end
#if(${baseResultMap})
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
#foreach($field in ${table.fields})
#if(${field.keyFlag})##生成主键排在第一位
        <id column="${field.name}" property="${field.propertyName}" />
#end
#end
#foreach($field in ${table.commonFields})##生成公共字段
        <result column="${field.name}" property="${field.propertyName}" />
#end
#foreach($field in ${table.fields})
#if(!${field.keyFlag})##生成普通字段
        <result column="${field.name}" property="${field.propertyName}" />
#end
#end
    </resultMap>

#end
#if(${baseColumnList})
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
#foreach($field in ${table.commonFields})
        ${field.columnName},
#end
        ${table.fieldNames}
    </sql>

#end
</mapper>

service.java.vm
package ${package.Service};

import com.baomidou.mybatisplus.core.metadata.IPage;
import ${package.Entity}.${entity};
import ${superServiceClassPackage};

/**
 * <p>
 * $!{table.comment} 服务类
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
#if(${kotlin})
interface ${table.serviceName} : ${superServiceClass}<${entity}>
#else
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {

        IPage<${entity}> getAllByPage(Integer pageNum, Integer pageSize,${entity} ${table.entityPath});


}
#end


serviceImpl.java.vm
package ${package.ServiceImpl};

import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName};
import ${superServiceImplClassPackage};
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;

/**
 * <p>
 * $!{table.comment} 服务实现类
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
@Service
#if(${kotlin})
open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>(), ${table.serviceName} {

        }
#else
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}> implements ${table.serviceName} {

    @Override
    public IPage<${entity}> getAllByPage(Integer pageNum, Integer pageSize,${entity} ${table.entityPath}){
        //分页构造器
        IPage<${entity}> pageInfo = new Page<>(pageNum,pageSize);
        //条件构造器
        LambdaQueryWrapper<${entity}> queryWrapper = new LambdaQueryWrapper<>();

        //编写具体查询代码和排序代码


        return this.page(pageInfo,queryWrapper);
    }
}
#end


生成器(MybatisPlusGenerator)

根据需求,修改配置,执行即可生成代码

package com.aegis.common.generator;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;
import lombok.extern.slf4j.Slf4j;

import java.util.*;


@Slf4j
public class MybatisPlusGenerator {

    // 配置数据库信息
    private static final String URL = "jdbc:mysql://192.168.11.204:13360/demo?useSSL=false";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root";
    // 项目根目录
    private static final String PROJECT_ROOT_PATH = System.getProperty("user.dir");
    private static final String BASE_PATH = PROJECT_ROOT_PATH + "/springboot-vue-demo/backend/src/main/java/com/aegis";
    private static final String PARENT_PACKAGE_NAME = "com.aegis";

    // 执行main方法
    public static void main(String[] args) {
        codeGenerator();
    }

    // 代码生成方法
    @SuppressWarnings("all")
    private static void codeGenerator() {
        // 六个文件的路径
        String entityPath = BASE_PATH + "/entity";
        String mapperPath = BASE_PATH + "/mapper";
        String mapperXmlPath = PROJECT_ROOT_PATH + "/springboot-vue-demo/backend/src/main/resources/mapper";
        String servicePath = BASE_PATH + "/service";
        String serviceImplPath = BASE_PATH + "/service/impl";
        String controllerPath = BASE_PATH + "/controller";


        FastAutoGenerator.create(URL, USERNAME, PASSWORD)
                // 全局配置
                .globalConfig(builder -> builder
                        // 作者名称
                        .author("chenjing")
                        // 开启覆盖已生成的文件
                        .fileOverride()
                        // 禁止打开输出目录
                        .disableOpenDir()
                        // 指定输出目录
                        // .outputDir(packagePath)
                        // 开启swagger2。注释掉则默认关闭。
//                        .enableSwagger()
                        // 时间策略
                        .dateType(DateType.TIME_PACK)
                        // 时间格式,注释日期
                        .commentDate("yyyy-MM-dd HH:mm:ss")
                )

                // 包配置
                .packageConfig((scanner, builder) -> builder
                        // 阶段1:各个文件的包名设置,用来拼接每个java文件的第一句:package com.XXX.XXX.XXX.xxx;
                        // 父包名配置
                        .parent(PARENT_PACKAGE_NAME)
//                        .moduleName(scanner.apply("请输入模块名称")) // 模块包名
                        // 实体类包名
                        .entity("entity")
                        // mapper包名
                        .mapper("mapper")
                        // service包名
                        .service("service")
                        // serviceImpl包名
                        .serviceImpl("service.impl")
                        // controller包名
                        .controller("controller")
                        // 自定义包名
                        .other("other")
                        // 阶段2:所有文件的生成路径配置
                        .pathInfo(
                                new HashMap<OutputFile, String>() {{
                                    // 实体类的保存路径
                                    put(OutputFile.entity, entityPath);
                                    // mapper接口的保存路径
                                    put(OutputFile.mapper, mapperPath);
                                    // mapper.xml文件的保存路径
//                                    put(OutputFile.xml, mapperXmlPath);
                                    // service层接口的保存路径
                                    put(OutputFile.service, servicePath);
                                    // service层接口实现类的保存路径
                                    put(OutputFile.serviceImpl, serviceImplPath);
                                    // 控制类的保存路径
                                    put(OutputFile.controller, controllerPath);
                                }}
                        )
                )

                 策略配置
                .strategyConfig((scanner, builder) -> builder
                        .addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?生成所有表,请输入[all]:")))
                        // 增加过滤表前缀
                        .addTablePrefix("tbl_")
//                        // 增加过滤表后缀
//                        .addTableSuffix(scanner.apply("请输入所要过滤的表后缀"))
//                        // 增加过滤字段前缀
//                        .addFieldPrefix(scanner.apply("请输入所要过滤字段前缀"))
//                        // 增加过滤字段后缀
//                        .addFieldSuffix(scanner.apply("请输入所要过滤字段后缀"))

                        // 阶段1:Entity实体类策略配置
                        .entityBuilder()
                        // 设置父类。会在生成的实体类名后:extends BaseEntity
                        // .superClass(BaseEntity.class)
                        // 禁用生成 serialVersionUID。(不推荐禁用)
                        // .disableSerialVersionUID()
                        // 开启生成字段常量。
                        // 会在实体类末尾生成一系列 [public static final String NICKNAME = "nickname";] 的语句。(一般在写wapper时,会用到)
                        // .enableColumnConstant()
                        // 开启链式模型。
                        // 会在实体类前添加 [@Accessors(chain = true)] 注解。用法如 [User user=new User().setAge(31).setName("snzl");](这是Lombok的注解,需要添加Lombok依赖)
                        .enableChainModel()
                        // 开启 lombok 模型。
                        // 会在实体类前添加 [@Getter] 和 [@Setter] 注解。(这是Lombok的注解,需要添加Lombok依赖)
                        .enableLombok()
                        // 开启 Boolean 类型字段移除 is 前缀。
                        // .enableRemoveIsPrefix()
                        // 开启生成实体时生成字段注解。
                        // 会在实体类的属性前,添加[@TableField("nickname")]
                        .enableTableFieldAnnotation()
//                        // 乐观锁数据库字段
//                        .versionColumnName(scanner.apply("请输入数据库中的乐观锁字段"))
//                        // 乐观锁实体类名称
//                        .versionPropertyName(scanner.apply("请输入字段中的乐观锁名称"))
                        // 逻辑删除字段名(数据库)。
                        .logicDeleteColumnName("is_delete")
                        // 逻辑删除属性名(实体)。
                        // 会在实体类的该字段属性前加注解[@TableLogic]
                        .logicDeletePropertyName("isDelete")
                        // 数据库表映射到实体的命名策略(默认下划线转驼峰)。一般不用设置
                        // .naming(NamingStrategy.underline_to_camel)
                        // 数据库表字段映射到实体的命名策略(默认为 null,未指定按照 naming 执行)。一般不用设置
                        // .columnNaming(NamingStrategy.underline_to_camel)
                        // 添加父类公共字段。
                        // 这些字段不会出现在新增的实体类中。
//                        .addSuperEntityColumns("id", "delete_time")
                        // 添加忽略字段。
                        // 这些字段不会出现在新增的实体类中。
//                         .addIgnoreColumns("password")
                        // 添加表字段填充
                        // 会在实体类的该字段上追加注解[@TableField(value = "create_time", fill = FieldFill.INSERT)]
                        .addTableFills(new Column("created_time", FieldFill.INSERT))
                        // 会在实体类的该字段上追加注解[@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)]
                        .addTableFills(new Column("updated_time", FieldFill.INSERT_UPDATE))
                        // 全局主键类型。如果MySQL主键设置为自增,则不需要设置此项。
//                         .idType(IdType.ASSIGN_ID)
                        // 格式化文件名称。
                        // 如果不设置,如表[sys_user]的实体类名是[SysUser]。设置成下面这样,将是[SysUserEntity]
//                        .formatFileName("%sEntity")

                        // 阶段2:Mapper策略配置
                        .mapperBuilder()
                        // 设置父类
                        // 会在mapper接口方法集成[extends BaseMapper<XXXEntity>]
                        .superClass(BaseMapper.class)
                        // 开启 @Mapper 注解。
                        // 会在mapper接口上添加注解[@Mapper]
                        .enableMapperAnnotation()
                        // 启用 BaseResultMap 生成。
                        // 会在mapper.xml文件生成[通用查询映射结果]配置。
                        .enableBaseResultMap()
                        // 启用 BaseColumnList。
                        // 会在mapper.xml文件生成[通用查询结果列 ]配置
                        .enableBaseColumnList()
                        // 设置缓存实现类
                        // .cache(MyMapperCache.class)
                        // 格式化 mapper 文件名称。
                        // 如果不设置,如表[sys_user],默认的文件名是[SysUserMapper]。写成下面这种形式后,将变成[SysUserDao]。
                        // .formatMapperFileName("%sDao")
                        // 格式化 xml 实现类文件名称。
                        // 如果不设置,如表[sys_user],默认的文件名是[SysUserMapper.xml],写成下面这种形式后,将变成[SysUserXml.xml]。
                        // .formatXmlFileName("%sXml")

                        // 阶段3:Service策略配置
                        // .serviceBuilder()
                        // 设置 service 接口父类
                        // .superServiceClass(BaseService.class)
                        // 设置 service 实现类父类
                        // .superServiceImplClass(BaseServiceImpl.class)
                        // 格式化 service 接口文件名称
                        // 如果不设置,如表[sys_user],默认的是[ISysUserService]。写成下面这种形式后,将变成[SysUserService]。
                        // .formatServiceFileName("%sService")
                        // 格式化 service 实现类文件名称
                        // 如果不设置,如表[sys_user],默认的是[SysUserServiceImpl]。
                        // .formatServiceImplFileName("%sServiceImpl")

                        // 阶段4:Controller策略配置
                        .controllerBuilder()
                        // 设置父类。
                        // 会集成此父类。
                        // .superClass(BaseController.class)
                        // 开启生成 @RestController 控制器
                        // 会在控制类中加[@RestController]注解。
                        .enableRestStyle()
                        // 开启驼峰转连字符
                        .enableHyphenStyle()
                        // Controller 文件名称
//                        .formatFileName("%sController")

                        // 最后:构建
                        .build()
                )
                //模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker
                //.templateEngine(new BeetlTemplateEngine())
//                .templateEngine(new FreemarkerTemplateEngine())
//                // 自定义模板配置
//                .templateEngine(new EnhanceFreemarkerTemplateEngine())
                .templateEngine(new VelocityTemplateEngine())

                //设置自定义模板路径
                .templateConfig(builder -> builder
                            .entity("/templates/entity.java.vm")
                            .service("/templates/service.java.vm")
                            .serviceImpl("/templates/serviceImpl.java.vm")
                            .mapper("/templates/mapper.java")
                            .xml("/templates/mapper.xml")
                            .controller("/templates/controller.java.vm")
                )

//                 // 注入配置
//                .injectionConfig((scanner, builder) -> {
//                    // 自定义vo,ro,qo等数据模型
//                    Map<String, String> customFile = new HashMap<>();
//                    customFile.put("VO.java", "templates/model/vo.java.vm");
//                    customFile.put("RO.java", "templates/model/ro.java.vm");
//                    customFile.put("QO.java", "templates/model/qo.java.vm");
//                    customFile.put("URO.java", "templates/model/uro.java.vm");
//                    // 自定义MapStruct
//                    customFile.put("Converter.java", "templates/converter/converter.java.vm");
//
//                    // 自定义配置对象
//                    Map<String, Object> customMap = new HashMap<>();
//                    customMap.put("vo", "VO");
//                    customMap.put("ro", "RO");
//                    customMap.put("qo", "QO");
//                    customMap.put("uro", "URO");
//                    builder
//                            .customFile(customFile) // 自定义配置模板文件
//                            .customMap(customMap); // 自定义配置Map对象
//                })


                // 执行
                .execute();
    }


    /**
     * @Description 处理控制台输入all情况
     * @Params [tables]
     * @Return java.util.List<java.lang.String>
     */
    private static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }

    /**
     * @Description 读取控制台内容
     * @Params [tip]
     * @Return java.lang.String
     */
    private static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入").append(tip).append(":");
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

}



业务工程(backend)

业务工程通过导入common公共配置工程jar包,可使用通用功能和继承common工程的依赖

pom文件配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <artifactId>springboot-vue-demo</artifactId>
        <groupId>com.aegis</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>


    <artifactId>backend</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <description>后端项目工程</description>


    <dependencies>

        <dependency>
            <groupId>com.aegis</groupId>
            <artifactId>autotest-platform-common</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
<!--        &lt;!&ndash; spring security 安全认证 &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-security</artifactId>-->
<!--        </dependency>-->

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>



        <!-- io常用工具类 -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
        </dependency>

        <!--常用工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>


        <!-- 文件上传工具类 -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
        </dependency>

        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.4</version>
        </dependency>

        <!-- JSON工具类 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

        <!-- 阿里JSON解析器 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

        <!-- excel工具 -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
        </dependency>

        <!-- yml解析器 -->
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
        </dependency>

        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
        </dependency>

<!--        &lt;!&ndash;Token生成与解析&ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>io.jsonwebtoken</groupId>-->
<!--            <artifactId>jjwt</artifactId>-->
<!--        </dependency>-->

        <!-- redis 缓存操作 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- pool 对象池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

<!--        &lt;!&ndash; 解析客户端操作系统、浏览器等 &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>eu.bitwalker</groupId>-->
<!--            <artifactId>UserAgentUtils</artifactId>-->
<!--        </dependency>-->

<!--        &lt;!&ndash; 文件服务器&ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>com.aegis</groupId>-->
<!--            <artifactId>file-server</artifactId>-->
<!--            <version>1.0.12</version>-->
<!--            <exclusions>-->
<!--                <exclusion>-->
<!--                    <groupId>com.qcloud</groupId>-->
<!--                    <artifactId>vod_api</artifactId>-->
<!--                </exclusion>-->
<!--            </exclusions>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>com.qcloud</groupId>-->
<!--            <artifactId>cos_api</artifactId>-->
<!--            <version>5.6.38</version>-->
<!--        </dependency>-->


        <!-- websocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

<!--        &lt;!&ndash; 二维码支持包 &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>com.google.zxing</groupId>-->
<!--            <artifactId>core</artifactId>-->
<!--            <version>3.2.0</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>com.google.zxing</groupId>-->
<!--            <artifactId>javase</artifactId>-->
<!--            <version>3.2.0</version>-->
<!--        </dependency>-->

<!--        &lt;!&ndash; https://mvnrepository.com/artifact/com.itextpdf/itextpdf &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>com.itextpdf</groupId>-->
<!--            <artifactId>itextpdf</artifactId>-->
<!--            <version>5.5.13.2</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>com.itextpdf.tool</groupId>-->
<!--            <artifactId>xmlworker</artifactId>-->
<!--            <version>5.5.13.2</version>-->
<!--        </dependency>-->
<!--        &lt;!&ndash;中文支持&ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>com.itextpdf</groupId>-->
<!--            <artifactId>itext-asian</artifactId>-->
<!--            <version>5.2.0</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.bouncycastle</groupId>-->
<!--            <artifactId>bcprov-jdk15on</artifactId>-->
<!--            <version>1.47</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.bouncycastle</groupId>-->
<!--            <artifactId>bcmail-jdk15on</artifactId>-->
<!--            <version>1.47</version>-->
<!--        </dependency>-->
<!--        -->
<!--        -->



    </dependencies>


    <build>
        <plugins>
            <!--TODO 测试方法存在问题,打包时需要设置跳过测试忽略问题,直接打包(不影响jar包的部署使用)-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <testFailureIgnore>true</testFailureIgnore>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

修改配置文件

application.properties改为application.yml,修改端口为80

# TODO 配置启动端口
server:
  port: 80

# TODO 配置数据源相关信息
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.11.204:13360/demo?useSSL=false  #?serverTimezone=UTC&characterEncoding=UTF-8
    username: root
    password: root

# TODO 配置mybatis-plus相关信息
mybatis-plus:
  configuration:
    # 在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    # 数据库操作(sql)调试使用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
  	# 自动生成主键
    db-config:
      id-type: assign_id

准备数据库表

设计创建数据库表,方式有2种:sql脚本创建、Navicat图形界面中设计表

sql脚本创建

在resource目录下创建db/migration目录,新建V1__init.sql脚本,关联对应的数据库,并执行
在这里插入图片描述
在这里插入图片描述

Navicat中设计表

Navicat连接服务器,新建表,开始数据表的设计
在这里插入图片描述

生成业务代码

根据业务实际需求,修改XML、Service、Controller 等各个模块的代码;
在这里插入图片描述

基础业务操作

当无特别的业务逻辑限制时,通过生成的代码,即可完成,无需修改代码

    @PostMapping("/update")
    @ApiOperation("修改数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<Account> update(@RequestBody Account account){
            return R.status( accountService.updateById(account));
    }

    @PostMapping("/delete/{id}")
    @ApiOperation("删除数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public R<Account> delete(@PathVariable("id") Long id){
        boolean flag = accountService.removeById(id);
        if(flag){
            return R.success();
        }else{
            return R.fail("添加数据失败");
        }
    }

效果
在这里插入图片描述

复杂查询(分页查询+条件查询)

业务接口(Service)

添加自定义方法(getAllByPage)

package com.aegis.service;

import com.aegis.entity.Account;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author chenjing
 * @since 2022-11-17 12:24:59
 */
public interface IAccountService extends IService<Account> {
        IPage<Account> getAllByPage(Integer pageNum, Integer pageSize,Account account);
}


业务实现类(ServiceImpl)

实现自定义方法(getAllByPage),并通过LambdaQueryWrapper实现复杂链路查询

package com.aegis.service.impl;

import com.aegis.entity.Account;
import com.aegis.mapper.AccountMapper;
import com.aegis.service.IAccountService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author chenjing
 * @since 2022-11-17 12:24:59
 */
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements IAccountService {

    @Override
    public IPage<Account> getAllByPage(Integer pageNum, Integer pageSize,Account account){
        //分页构造器
        IPage<Account> pageInfo = new Page<>(pageNum,pageSize);
        //条件构造器
        LambdaQueryWrapper<Account> queryWrapper = new LambdaQueryWrapper<>();

        //编写具体查询代码和排序代码
        queryWrapper.like(StringUtils.isNotBlank(account.getName()), Account::getName, account.getName());

        return this.page(pageInfo,queryWrapper);
    }
}

业务控制类(Controller)

    @PostMapping("/list")
    @ApiOperation("查询列表")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    @ApiResponses(@ApiResponse(code = 200, message = "成功", response = Account.class))
    public R<IPage<Account>> search(@RequestParam(required = false, defaultValue = "1") Integer pageNum,
                                      @RequestParam(required = false, defaultValue = "10") Integer pageSize,
                                      @RequestBody Account account){
            return R.success(accountService.getAllByPage(pageNum,pageSize,account));
    }

效果

在这里插入图片描述

业务异常校验

@GetMapping("/detail/{id}")
    @ApiOperation("查询单条数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    @ApiResponses(@ApiResponse(code = 200, message = "成功", response = Account.class))
    public R<Account> detail(@PathVariable("id") Long id){
        Account account = accountService.getById(id);
        if(null == account){
            throw new BusinessException(ResultCode.VALIDATE_FAILED.getCode(),"用户不存在");
        }
        return R.success();
    }

效果
在这里插入图片描述

参数校验(requestParam/PathVariable参数校验)

GET请求一般会使用requestParam/PathVariable传参。如果参数比较多 (比如超过 6 个),还是推荐使用DTO对象接收。否则,推荐将一个个参数平铺到方法入参中。在这种情况下,必须在Controller类上标注@Validated注解,并在入参上声明约束注解 (如@Min等)。如果校验失败,会抛出ConstraintViolationException异常

@Validated
@RestController
@RequestMapping("/user")
@Api(value = "UserController",tags = "UserController")
public class UserController {

    @Resource
    private IUserService userService;

    /**
     * 登录:
     * 多个终端登录可以写不同的登录接口,分别设置登录的设备。
     * 也可以写一个统一的接口,在 request 中设置登录终端标识,根据这个标识来设置登录的设备。
     * 退出登录同理
     * */
    @ApiOperation(value = "登录接口", notes = "使用账号密码登录用户")
    @PostMapping("/login")
    public R<HashMap<Object,Object>> login(@NotNull String name, String password, String code, HttpServletRequest request){
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(StringUtils.isNotBlank(name),User::getUsername,name);
//        queryWrapper.eq(name != null ,User::getUsername,name);

        User user = userService.getOne(queryWrapper);
        if(user==null){
            return R.fail("用户不存在!");
        }
        if (!"1".equals(user.getIsEnable())) {
            return R.fail("用户未激活,请联系管理员!");
        }
        if(!password.equals(user.getPassword())){
            return R.fail("用户名密码不正确!");
        }

        // sa-token登录
        StpUtil.login(user.getId());
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        String token = tokenInfo.getTokenValue();
        HashMap<Object,Object> res = new HashMap<>();
        res.put("info",user);
        res.put("token",token);
        return R.success(res);
    }
}

在这里插入图片描述

requestBody参数校验(初阶)

业务控制类(Controller)

在需要的校验参数前面加 @Validated

    @PostMapping("/save")
    @ApiOperation("添加数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<Account> save(@Validated @RequestBody Account account){
        boolean flag = accountService.save(account);
        if(flag){
            return R.success(account);
        }else{
            return R.fail("添加数据失败");
        }
    }

实体类(entity)

需要校验的参数对应的实体类上,根据需求在需要校验的字段添加限制(比如@NotBlank,@Max,@Min)

package com.aegis.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 lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.math.BigDecimal;

/**
 * <p>
 * 
 * </p>
 *
 * @author chenjing
 * @since 2022-11-17 12:24:59
 */
@Data
@ToString
@Accessors(chain = true)
@TableName("tbl_account")
public class Account implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 账号ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 账号名
     */
    @NotBlank(message = "姓名不能为空")
    @TableField("name")
    private String name;

    /**
     * 账号金额
     */
    @Max(value = 200,message = "最大值不能超过200")
    @Min(value = 100,message = "最小值不能低于100")
    @TableField("money")
    private BigDecimal money;


}

效果

在这里插入图片描述

在这里插入图片描述

参数校验(requestBody参数校验进阶)

分组校验

在实际项目中,可能多个方法需要使用同一个DTO类来接收参数,而不同方法的校验规则很可能是不一样的。这个时候,简单地在DTO类的字段上加约束注解无法解决这个问题。因此,spring-validation支持了分组校验的功能,专门用来解决这类问题

新建分组类(ValidationGroups)
package com.aegis.vaildation;

public class ValidationGroups {
    public interface CommonAccount{}

    public interface Register extends CommonAccount{}

    public interface UpdateAccount extends CommonAccount{}

}

比如说姓名在新增和更新操作的时候,都需校验必填不能为空,则可定义公共接口CommonAccount,新增接口 Register 和更新接口 UpdateAccount 继承公共接口

实体类上声明分组

约束注解上声明适用的分组信息groups

package com.aegis.entity;


import com.aegis.vaildation.ValidationGroups;
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 lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Null;
import java.io.Serializable;
import java.math.BigDecimal;

/**
 * <p>
 * 
 * </p>
 *
 * @author chenjing
 * @since 2022-11-17 18:02:35
 */
@Data
@ToString
@Accessors(chain = true)
@TableName("tbl_account")
public class Account implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 账号ID
     */
    @NotBlank(message = "更新操作,id不能为空",groups = {ValidationGroups.UpdateAccount.class})
    @Null(message = "新增操作,id应为空",groups = {ValidationGroups.Register.class})
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 账号名
     */
    @NotBlank(message = "姓名不能为空",groups = {ValidationGroups.CommonAccount.class})
    @TableField("name")
    private String name;

    /**
     * 账号金额
     */
    @TableField("money")
    private BigDecimal money;


}

含义:新增时id要为空,而更新时id不能为空;新增和更新时,姓名必填

控制类指定校验分组

@Validated注解上指定校验分组

 @PostMapping("/save")
    @ApiOperation("添加数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<Account> save(@Validated(ValidationGroups.Register.class) @RequestBody Account account){
        boolean flag = accountService.save(account);
        if(flag){
            return R.success(account);
        }else{
            return R.fail("添加数据失败");
        }
    }

    @PostMapping("/update")
    @ApiOperation("修改数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<Account> update(@Validated(ValidationGroups.UpdateAccount.class) @RequestBody Account account){
            return R.status( accountService.updateById(account));
    }

注:多个分组形式 @Validated({ValidationGroups.UpdateAccount.class,ValidationGroups.Register.class})

验证效果

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

嵌套校验

在实际场景中,某个字段可能是一个对象,这种情况可以使用嵌套校验。

比如,更新Account信息的时候同时还带有Job信息。此时DTO类型的字段必须标记@Valid注解。

package com.aegis.entity;


import com.aegis.vaildation.ValidationGroups;
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 lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.Length;

import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import java.io.Serializable;
import java.math.BigDecimal;

/**
 * <p>
 * 
 * </p>
 *
 * @author chenjing
 * @since 2022-11-17 18:02:35
 */
@Data
@ToString
@Accessors(chain = true)
@TableName("tbl_account")
public class Account implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 账号ID
     */
    @NotNull(message = "更新操作,id不能为空",groups = {ValidationGroups.UpdateAccount.class})
    @Null(message = "新增操作,id应为空",groups = {ValidationGroups.Register.class})
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 账号名
     */
    @NotBlank(message = "姓名不能为空",groups = {ValidationGroups.CommonAccount.class})
    @TableField("name")
    private String name;

    /**
     * 账号金额
     */
    @TableField("money")
    private BigDecimal money;

    @NotNull(groups = {ValidationGroups.UpdateAccount.class})
    @Valid
    private Job job;

    @Data
    public static class Job {

        @Min(value = 1, groups = ValidationGroups.UpdateAccount.class)
        private Long jobId;

        @Length(min = 2, max = 10, groups = {ValidationGroups.Register.class, ValidationGroups.UpdateAccount.class})
        private String jobName;

        private String position;
    }

}

注:嵌套集合校验会对集合里面的每一项都进行校验,例如List字段会对这个list里面的每一个Job对象都进行校验

在这里插入图片描述

集合校验

如果请求体直接传递了json数组给后台,并希望对数组中的每一项都进行参数校验。此时,如果我们直接使用java.util.Collection下的list或者set来接收数据,参数校验并不会生效!我们可以使用自定义list集合来接收参数:包装List类型,并声明@Valid注解

案例:批量新增账号

包装List类型(ValidationList)
package com.aegis.vaildation;

import lombok.experimental.Delegate;

import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;

public class ValidationList<E> implements List<E> {

    @Delegate  // @Delegate是lombok注解
    @Valid // 一定要加@Valid注解
    public List<E> list = new ArrayList<>();

    @Override
    public String toString() {
        return list.toString();
    }
}

@Delegate注解受lombok版本限制,1.18.6以上版本可支持。如果校验不通过,会抛出NotReadablePropertyException,可以使用全局统一异常进行处理。

NotReadablePropertyException异常处理
 /**
     * 集合类型的请求参数异常处理
     * @param ex
     * @return
     */
    @ExceptionHandler({NotReadablePropertyException.class})
    @ResponseStatus(HttpStatus.OK)
    public R<NotReadablePropertyException> handleNotReadablePropertyException(NotReadablePropertyException ex) {
        StringBuilder sb =new StringBuilder(ex.getPropertyName());
        String propertyName = sb.substring(sb.indexOf("[") + 1,sb.indexOf("]"));
        int index = Integer.parseInt(propertyName)+1;
        return R.fail(ResultCode.VALIDATE_FAILED,"第"+index+"条数据存在问题");
    }

Controller控制类使用包装集合类接收参数
    @PostMapping("/saveList")
    @ApiOperation("添加数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<Account> saveList (@Validated(ValidationGroups.Register.class) @RequestBody ValidationList<Account> accountList){
        // 校验通过,才会执行业务逻辑处理
        return R.success();
    }

自定义参数校验

自定义约束注解
package com.aegis.custom.constraints;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;

@Documented
@Target({FIELD,METHOD,PARAMETER,CONSTRUCTOR,ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {AccountNameValidator.class})
public @interface AccountName {
    // 默认错误消息
    String message() default "姓名格式错误";

    // 分组
    Class<?>[] groups() default { };

    // 负载
    Class<? extends Payload>[] payload() default { };

}

具体含义参考自定义注解annotation详解

编写约束校验器(Validator)

账号姓名需是数字+字母组成,3-10位

package com.aegis.custom.constraints;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AccountNameValidator implements ConstraintValidator<AccountName,String> {
    private static final Pattern PATTERN = Pattern.compile("^[a-fA-F\\d]{3,10}$");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(null!=value){
            Matcher matcher = PATTERN.matcher(value);
            return matcher.find();
        }
        return false;
    }
}

添加自定义约束注解

可以通过使用@AccountName进行参数校验

    /**
     * 账号名
     */
    @AccountName(message = "姓名格式错误啦啦啦",groups = {ValidationGroups.CommonAccount.class})
    @NotBlank(message = "姓名不能为空",groups = {ValidationGroups.CommonAccount.class})
    @TableField("name")
    private String name;

效果

在这里插入图片描述

编程校验

适用场景:当我们并不是参数失败就退出时,需要这种方式

步骤

  • 定义javax.validation.Validator类型的私有成员变量
  • 指定校验的分组,获取校验信息
  • 根据校验结果进行具体编程处理
package com.aegis.controller;


import com.aegis.common.api.R;
import com.aegis.entity.Account;
import com.aegis.service.IAccountService;
import com.aegis.vaildation.ValidationGroups;
import com.aegis.vaildation.ValidationList;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import java.util.Set;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author chenjing
 * @since 2022-11-17 18:08:06
 */
@RestController
@RequestMapping("/account")
@Api(value = "AccountController",tags = "AccountController")
public class AccountController {
    @Resource
    private javax.validation.Validator globalValidator;

    @PostMapping("/save2")
    @ApiOperation("编程校验")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<Account> saveWithCodingValidate (@RequestBody Account account){
        Set<ConstraintViolation<Account>> validate = globalValidator.validate(account, ValidationGroups.Register.class);
        // 如果校验通过,validate为空;否则,validate包含未校验通过项
        if(validate.isEmpty()){
            // 校验通过,才会执行业务逻辑处理
        }else{
            for (ConstraintViolation<Account> accountConstraintViolation : validate) {
                // 校验失败,做其它逻辑
                System.out.println(accountConstraintViolation);
            }
        }
        return R.success();
    }

}


效果
在这里插入图片描述

快速失败(Fail Fast)

Spring Validation默认会校验完所有字段,然后才抛出异常。可以通过添加配置,开启Fali Fast模式,一旦校验失败就立即返回。

package com.aegis.common.config;

import org.hibernate.validator.HibernateValidator;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

@Configuration
public class ValidatorConfiguration {

    @Bean
    public Validator validator(AutowireCapableBeanFactory springFactory) {
        try (ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 快速失败
                .failFast(true)
                // 解决 SpringBoot 依赖注入问题
                .constraintValidatorFactory(new SpringConstraintValidatorFactory(springFactory))
                .buildValidatorFactory()) {
            return factory.getValidator();
        }
    }
}


在这里插入图片描述

@Valid和@Validated区别

区别@Valid@Validated
提供者javax(标准JSR-303规范Spring Validation 验证框架
分组不支持支持
循环嵌套支持不支持
标注位置FIELD,CONTRUSTOR,TYPE_USE,METHOD,PARAMETERTYPE,METHOD,PARAMETER

参数校验实现原理:参考 Springboot @Validated参数校验

集成Swagger接口文档

Swagger2 作为一个规范和完整的框架,可以用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。

  • 接口文档在线自动生成,文档随接口变动实时更新,节省维护成本
  • 支持在线接口测试,不依赖第三方工具

添加核心依赖

        <!-- 读取配置文件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!--Swagger-UI API文档生产工具-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <!-- swagger2-UI-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>

        <!--解决 1.5.20的AbstractSerializableParameter异常(这是由于实体类使用@ApiModelProperty时,example属性没有赋值导致的)-->
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
        </dependency>

新建项目配置类(ProjectConfig)

@ConfigurationProperties是springboot提供读取配置文件的一个注解

package com.aegis.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "project")
@Data
public class ProjectConfig {
    /**
     * 项目名称
     */
    private String name;
    /**
     * 版本
     */
    private String version;

}

新建Swagger配置类(SwaggerConfig)

package com.aegis.config;

import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * Swagger2的接口配置
 *
 * @author admin
 */
//表示这个类是一个配置类,会把这个类注入到ioc容器中
@Configuration
//开启swagger2的功能
@EnableSwagger2
public class SwaggerConfig {

    /**
     * 系统基础配置
     */
    @Resource
    private ProjectConfig projectConfig;
    /** 是否开启swagger */
    @Value("${swagger.enabled}")
    private boolean enabled;
    /** 设置请求的统一前缀 */
    @Value("${swagger.pathMapping}")
    private String pathMapping;

    /**
     * 创建API
     */
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                // 是否启用Swagger
                .enable(enabled)
                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
                .apiInfo(apiInfo())
                // 设置哪些接口暴露给Swagger展示
                .select()
                // 扫描所有有注解的api,用这种方式更灵活
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                // 扫描指定包中的swagger注解
                // .apis(RequestHandlerSelectors.basePackage("com.aegis.project.tool.swagger"))
                // 扫描所有 .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                .pathMapping(pathMapping)
                /* 设置安全模式,swagger可以设置访问token */
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts());
    }

    /**
     * 添加摘要信息
     */
    private ApiInfo apiInfo() {
        // 用ApiInfoBuilder进行定制
        return new ApiInfoBuilder()
                // 设置标题
                .title("标题:自动化测试平台系统_接口文档")
                // 描述
                .description("描述:用于管理自动化测试平台系统信息,具体包括XXX,XXX模块...")
                // 作者信息
                .contact(new Contact(projectConfig.getName(), null, null))
                // 版本
                .version("版本号:" + projectConfig.getVersion())
                .build();
    }

    /**
     * 安全模式,这里指定token通过Authorization头请求头传递
     */
    private List<ApiKey> securitySchemes() {
        List<ApiKey> apiKeyList = new ArrayList<ApiKey>();
        apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
        return apiKeyList;
    }

    /**
     * 安全上下文
     */
    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(
                SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .forPaths(PathSelectors.regex("^(?!auth).*$"))
                        .build());
        return securityContexts;
    }

    /**
     * 默认的安全上引用
     */
    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }

}

修改配置文件(application.yml)

# Swagger配置
swagger:
  # 是否开启swagger
  enabled: true
  pathMapping: /


# 项目相关配置
project:
  # 名称
  name: autotestPlatform
  # 版本
  version: 1.0.0

为方法添加Api注解

注解类型:

  • @Api:修饰整个类,描述Controller的作用
  • @ApiOperation:描述一个类的一个方法,或者说一个接口
  • @ApiParam:单个参数描述
  • @ApiModel:用对象来接收参数
  • @ApiProperty:用对象接收参数时,描述对象的一个字段
  • @ApiResponse:HTTP响应其中1个描述
  • @ApiResponses:HTTP响应整体描述
  • @ApiIgnore:使用该注解忽略这个API
  • @ApiError :发生错误返回的信息
  • @ApiImplicitParam:描述一个请求参数,可以配置参数的中文含义,还可以给参数设置默认值
  • @ApiImplicitParams:描述由多个 @ApiImplicitParam 注解的参数组成的请求参数列表
package com.aegis.controller;


import com.aegis.common.api.R;
import com.aegis.entity.Account;
import com.aegis.service.IAccountService;
import com.aegis.vaildation.ValidationGroups;
import com.aegis.vaildation.ValidationList;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import java.util.Set;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author chenjing
 * @since 2022-11-17 18:08:06
 */
@RestController
@RequestMapping("/account")
@Api(value = "AccountController",tags = "账号管理")
public class AccountController {
    @Resource
    private javax.validation.Validator globalValidator;

    @Resource
    private IAccountService accountService;

    @GetMapping("/detail/{id}")
    @ApiOperation("查询单条数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    @ApiResponses(@ApiResponse(code = 200, message = "成功", response = Account.class))
    public R<Account> detail(@PathVariable("id") Long id){
        Account account = accountService.getById(id);
        if (account != null) {
            return R.success(account);
        } else {
            return R.fail("id不存在");
        }
    }

    @PostMapping("/list")
    @ApiOperation("查询列表")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    @ApiResponses(@ApiResponse(code = 200, message = "成功", response = Account.class))
    public R<IPage<Account>> search(@RequestParam(required = false, defaultValue = "1") Integer pageNum,
                                      @RequestParam(required = false, defaultValue = "10") Integer pageSize,
                                      @RequestBody Account account){
            return R.success(accountService.getAllByPage(pageNum,pageSize,account));
    }

    @PostMapping("/save")
    @ApiOperation("添加数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<Account> save(@Validated(ValidationGroups.Register.class) @RequestBody Account account){
        boolean flag = accountService.save(account);
        if(flag){
            return R.success(account);
        }else{
            return R.fail("添加数据失败");
        }
    }

    @PostMapping("/update")
    @ApiOperation("修改数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<Account> update(@Validated(ValidationGroups.UpdateAccount.class) @RequestBody Account account){
            return R.status( accountService.updateById(account));
    }

    @PostMapping("/delete/{id}")
    @ApiOperation("删除数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public R<Account> delete(@PathVariable("id") Long id){
        boolean flag = accountService.removeById(id);
        if(flag){
            return R.success();
        }else{
            return R.fail("添加数据失败");
        }
    }

    @PostMapping("/saveList")
    @ApiOperation("批量添加数据")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<Account> saveList (@Validated(ValidationGroups.Register.class) @RequestBody ValidationList<Account> accountList){
        // 校验通过,才会执行业务逻辑处理
        return R.success();
    }

    @PostMapping("/save2")
    @ApiOperation("编程校验")
    @ApiImplicitParams({@ApiImplicitParam(value = "token", paramType = "header", name = "token", required = true)})
    public  R<Account> saveWithCodingValidate (@RequestBody Account account){
        Set<ConstraintViolation<Account>> validate = globalValidator.validate(account, ValidationGroups.Register.class);
        // 如果校验通过,validate为空;否则,validate包含未校验通过项
        if(validate.isEmpty()){
            // 校验通过,才会执行业务逻辑处理
        }else{
            for (ConstraintViolation<Account> accountConstraintViolation : validate) {
                // 校验失败,做其它逻辑
                System.out.println(accountConstraintViolation);
            }
        }
        return R.success();
    }

}

注意事项

如果出现documentationPluginsBootstrapper相关的报错,问题在于Springfox 使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher。
即Spring Boot和Swagger存在版本兼容问题。
比如下面版本组合是兼容的

Spring Boot版本Swagger 版本
2.5.62.9.2

验证效果

启动项目,打开地址http://localhost/swagger-ui.html
如果端口非默认80,则需加上端口号
在这里插入图片描述

优化界面效果

基本的Swagger2界面还不够好用,所以第三方有基于bootstrap的扩展,添加依赖

<!--基本的Swagger2界面还不够好用,所以第三方有基于bootstrap的扩展-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>

访问地址:http://localhost/doc.html

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值