目录
前言
本文介绍如何搭建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>
<!-- <!–TODO freemarker模板引擎–>-->
<!-- <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>
<!-- <!–TODO freemarker模板引擎–>-->
<!-- <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>
<!-- <!– spring security 安全认证 –>-->
<!-- <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>
<!-- <!–Token生成与解析–>-->
<!-- <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>
<!-- <!– 解析客户端操作系统、浏览器等 –>-->
<!-- <dependency>-->
<!-- <groupId>eu.bitwalker</groupId>-->
<!-- <artifactId>UserAgentUtils</artifactId>-->
<!-- </dependency>-->
<!-- <!– 文件服务器–>-->
<!-- <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>
<!-- <!– 二维码支持包 –>-->
<!-- <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>-->
<!-- <!– https://mvnrepository.com/artifact/com.itextpdf/itextpdf –>-->
<!-- <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>-->
<!-- <!–中文支持–>-->
<!-- <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,PARAMETER | TYPE,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.6 | 2.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