hmy 1 year ago
commit
71775f8cf0
51 changed files with 4119 additions and 0 deletions
  1. 35 0
      .gitignore
  2. 112 0
      pom.xml
  3. 47 0
      src/main/java/cn/iocoder/yudao/framework/common/exception/ErrorCode.java
  4. 78 0
      src/main/java/cn/iocoder/yudao/framework/common/exception/ServerException.java
  5. 77 0
      src/main/java/cn/iocoder/yudao/framework/common/exception/ServiceException.java
  6. 40 0
      src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java
  7. 43 0
      src/main/java/cn/iocoder/yudao/framework/common/exception/enums/ServiceErrorCodeRange.java
  8. 125 0
      src/main/java/cn/iocoder/yudao/framework/common/exception/util/ServiceExceptionUtil.java
  9. 134 0
      src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java
  10. 31 0
      src/main/java/cn/iocoder/yudao/framework/common/pojo/PageParam.java
  11. 51 0
      src/main/java/cn/iocoder/yudao/framework/common/pojo/PageResult.java
  12. 52 0
      src/main/java/cn/iocoder/yudao/framework/common/pojo/SortingField.java
  13. 40 0
      src/main/java/cn/iocoder/yudao/framework/datasource/config/YudaoDataSourceAutoConfiguration.java
  14. 22 0
      src/main/java/cn/iocoder/yudao/framework/datasource/core/enums/DataSourceEnum.java
  15. 38 0
      src/main/java/cn/iocoder/yudao/framework/datasource/core/filter/DruidAdRemoveFilter.java
  16. 5 0
      src/main/java/cn/iocoder/yudao/framework/datasource/package-info.java
  17. 115 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java
  18. 66 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java
  19. 130 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/collection/ArrayIter.java
  20. 91 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/dataobject/BaseDO.java
  21. 21 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/SqlConstants.java
  22. 64 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java
  23. 136 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
  24. 289 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/PlusBaseMapperX.java
  25. 28 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryEntity.java
  26. 55 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryKeyword.java
  27. 17 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryMap.java
  28. 17 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryWapper.java
  29. 20 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryWapperEnum.java
  30. 217 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryWrapperUtil.java
  31. 138 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperX.java
  32. 317 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java
  33. 169 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java
  34. 153 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/service/AbstractService.java
  35. 21 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/service/AbstractServiceImpl.java
  36. 75 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/EncryptTypeHandler.java
  37. 63 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/IntegerListTypeHandler.java
  38. 65 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/LongListTypeHandler.java
  39. 58 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringListTypeHandler.java
  40. 82 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/ArrayUtils.java
  41. 58 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/CollUtil.java
  42. 269 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/CollectionUtils.java
  43. 28 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/IterUtil.java
  44. 42 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/JdbcUtils.java
  45. 68 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MapUtils.java
  46. 89 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java
  47. 79 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/ObjectUtil.java
  48. 16 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/SetUtils.java
  49. 129 0
      src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/StrUtils.java
  50. 2 0
      src/main/resources/META-INF/spring.factories
  51. 2 0
      src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

+ 35 - 0
.gitignore

@@ -0,0 +1,35 @@
+# Compiled class file
+*.class
+
+.DS_Store
+.idea/
+*.iml
+target/
+ 
+# Log file
+*.log
+
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar
+*.tar.gz
+*.rar
+*.settings
+*.project
+.settings/
+*.settings 
+.classpath 
+.project 
+*.idea 
+logs 
+.factorypath 
+*.mvn 
+.mvn 
+*.iml 
+*.factorypath 
+*.node_modules 
+node_modules 
+.sts4-cache

+ 112 - 0
pom.xml

@@ -0,0 +1,112 @@
+<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">
+
+
+	<modelVersion>4.0.0</modelVersion>
+
+	<groupId>cn.iocoder.boot</groupId>
+	<artifactId>spring-boot-starter-mybatis</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<packaging>jar</packaging>
+
+	<name>spring-boot-starter-mybatis</name>
+	<url>http://maven.apache.org</url>
+
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<mybatis-plus.version>3.5.4</mybatis-plus.version>
+		<spring.boot.version>2.7.17</spring.boot.version>
+		<mybatis-plus-join.version>1.4.6</mybatis-plus-join.version>
+		<druid.version>1.2.20</druid.version>
+		<mybatis-plus.version>3.5.4</mybatis-plus.version>
+		<hutool.version>5.8.22</hutool.version>
+		<dynamic-datasource.version>3.6.1</dynamic-datasource.version>
+	</properties>
+
+	<dependencies>
+		<!-- DB 相关 -->
+		<dependency>
+			<groupId>com.mysql</groupId>
+			<artifactId>mysql-connector-j</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.baomidou</groupId>
+			<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
+			<version>${dynamic-datasource.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.oracle.database.jdbc</groupId>
+			<artifactId>ojdbc8</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.postgresql</groupId>
+			<artifactId>postgresql</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.microsoft.sqlserver</groupId>
+			<artifactId>mssql-jdbc</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.baomidou</groupId>
+			<artifactId>mybatis-plus-boot-starter</artifactId>
+			<version>3.5.4</version>
+		</dependency>
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>druid-spring-boot-starter</artifactId>
+			<version>${druid.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.hutool</groupId>
+			<artifactId>hutool-all</artifactId>
+			<version>${hutool.version}</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>com.github.yulichang</groupId>
+			<artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
+			<version>${mybatis-plus-join.version}</version>
+		</dependency>
+	</dependencies>
+	<dependencyManagement>
+		<dependencies>
+			<!-- 统一依赖管理 -->
+			<dependency>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-dependencies</artifactId>
+				<version>${spring.boot.version}</version>
+				<type>pom</type>
+				<scope>import</scope>
+			</dependency>
+		</dependencies>
+	</dependencyManagement>
+	<distributionManagement>
+		<repository>
+			<id>nexus</id>
+			<url>https://nexus.flowbb.cn/repository/maven-releases/</url>
+		</repository>
+		<snapshotRepository>
+			<id>nexus</id>
+			<url>https://nexus.flowbb.cn/repository/maven-snapshots/</url>
+		</snapshotRepository>
+	</distributionManagement>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<version>2.2.1</version>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar-no-fork</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>

+ 47 - 0
src/main/java/cn/iocoder/yudao/framework/common/exception/ErrorCode.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.framework.common.exception;
+
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.yudao.framework.common.exception.enums.ServiceErrorCodeRange;
+
+/**
+ * 错误码对象
+ *
+ * 全局错误码,占用 [0, 999], 参见 {@link GlobalErrorCodeConstants} 业务异常错误码,占用 [1 000 000
+ * 000, +∞),参见 {@link ServiceErrorCodeRange}
+ *
+ * TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
+ */
+
+public class ErrorCode {
+
+	public Integer getCode() {
+		return code;
+	}
+
+	public void setCode(Integer code) {
+		this.code = code;
+	}
+
+	public String getMsg() {
+		return msg;
+	}
+
+	public void setMsg(String msg) {
+		this.msg = msg;
+	}
+
+	/**
+	 * 错误码
+	 */
+	private Integer code;
+	/**
+	 * 错误提示
+	 */
+	private String msg;
+
+	public ErrorCode(Integer code, String message) {
+		this.code = code;
+		this.msg = message;
+	}
+
+}

+ 78 - 0
src/main/java/cn/iocoder/yudao/framework/common/exception/ServerException.java

@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.framework.common.exception;
+
+import java.util.Objects;
+
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+
+
+/**
+ * 服务器异常 Exception
+ */
+
+public final class ServerException extends RuntimeException {
+
+	private static final long serialVersionUID = 3092672595845521745L;
+	/**
+	 * 全局错误码
+	 *
+	 * @see GlobalErrorCodeConstants
+	 */
+	private Integer code;
+	/**
+	 * 错误提示
+	 */
+	private String message;
+
+	/**
+	 * 空构造方法,避免反序列化问题
+	 */
+	public ServerException() {
+	}
+
+	public ServerException(ErrorCode errorCode) {
+		this.code = errorCode.getCode();
+		this.message = errorCode.getMsg();
+	}
+
+	public ServerException(Integer code, String message) {
+		this.code = code;
+		this.message = message;
+	}
+
+	public Integer getCode() {
+		return code;
+	}
+
+	public ServerException setCode(Integer code) {
+		this.code = code;
+		return this;
+	}
+
+	@Override
+	public String getMessage() {
+		return message;
+	}
+
+	public ServerException setMessage(String message) {
+		this.message = message;
+		return this;
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(code, message);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		ServerException other = (ServerException) obj;
+		return Objects.equals(code, other.code) && Objects.equals(message, other.message);
+	}
+
+}

+ 77 - 0
src/main/java/cn/iocoder/yudao/framework/common/exception/ServiceException.java

@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.framework.common.exception;
+
+import java.util.Objects;
+
+import cn.iocoder.yudao.framework.common.exception.enums.ServiceErrorCodeRange;
+
+/**
+ * 业务逻辑异常 Exception
+ */
+
+public final class ServiceException extends RuntimeException {
+
+	private static final long serialVersionUID = 1L;
+	/**
+	 * 业务错误码
+	 *
+	 * @see ServiceErrorCodeRange
+	 */
+	private Integer code;
+
+	/**
+	 * 错误提示
+	 */
+	private String message;
+
+	/**
+	 * 空构造方法,避免反序列化问题
+	 */
+	public ServiceException() {
+	}
+
+	public ServiceException(ErrorCode errorCode) {
+		this.code = errorCode.getCode();
+		this.message = errorCode.getMsg();
+	}
+
+	public ServiceException(Integer code, String message) {
+		this.code = code;
+		this.message = message;
+	}
+
+	public Integer getCode() {
+		return code;
+	}
+
+	public ServiceException setCode(Integer code) {
+		this.code = code;
+		return this;
+	}
+
+	@Override
+	public String getMessage() {
+		return message;
+	}
+
+	public ServiceException setMessage(String message) {
+		this.message = message;
+		return this;
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(code, message);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		ServiceException other = (ServiceException) obj;
+		return Objects.equals(code, other.code) && Objects.equals(message, other.message);
+	}
+}

+ 40 - 0
src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.framework.common.exception.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * 全局错误码枚举
+ * 0-999 系统异常编码保留
+ *
+ * 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
+ * 虽然说,HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
+ * 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
+ *
+ * @author 芋道源码
+ */
+public interface GlobalErrorCodeConstants {
+
+    ErrorCode SUCCESS = new ErrorCode(0, "成功");
+
+    // ========== 客户端错误段 ==========
+
+    ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确");
+    ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录");
+    ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限");
+    ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到");
+    ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
+    ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求,不允许
+    ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试");
+
+    // ========== 服务端错误段 ==========
+
+    ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
+    ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启");
+
+    // ========== 自定义错误段 ==========
+    ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
+    ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作");
+
+    ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
+
+}

+ 43 - 0
src/main/java/cn/iocoder/yudao/framework/common/exception/enums/ServiceErrorCodeRange.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.framework.common.exception.enums;
+
+/**
+ * 业务异常的错误码区间,解决:解决各模块错误码定义,避免重复,在此只声明不做实际使用
+ *
+ * 一共 10 位,分成四段
+ *
+ * 第一段,1 位,类型
+ *      1 - 业务级别异常
+ *      x - 预留
+ * 第二段,3 位,系统类型
+ *      001 - 用户系统
+ *      002 - 商品系统
+ *      003 - 订单系统
+ *      004 - 支付系统
+ *      005 - 优惠劵系统
+ *      ... - ...
+ * 第三段,3 位,模块
+ *      不限制规则。
+ *      一般建议,每个系统里面,可能有多个模块,可以再去做分段。以用户系统为例子:
+ *          001 - OAuth2 模块
+ *          002 - User 模块
+ *          003 - MobileCode 模块
+ * 第四段,3 位,错误码
+ *       不限制规则。
+ *       一般建议,每个模块自增。
+ *
+ * @author 芋道源码
+ */
+public class ServiceErrorCodeRange {
+
+    // 模块 infra 错误码区间 [1-001-000-000 ~ 1-002-000-000)
+    // 模块 system 错误码区间 [1-002-000-000 ~ 1-003-000-000)
+    // 模块 report 错误码区间 [1-003-000-000 ~ 1-004-000-000)
+    // 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000)
+    // 模块 mp 错误码区间 [1-006-000-000 ~ 1-007-000-000)
+    // 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000)
+    // 模块 product 错误码区间 [1-008-000-000 ~ 1-009-000-000)
+    // 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000)
+    // 模块 trade 错误码区间 [1-011-000-000 ~ 1-012-000-000)
+    // 模块 promotion 错误码区间 [1-013-000-000 ~ 1-014-000-000)
+
+}

+ 125 - 0
src/main/java/cn/iocoder/yudao/framework/common/exception/util/ServiceExceptionUtil.java

@@ -0,0 +1,125 @@
+package cn.iocoder.yudao.framework.common.exception.util;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * {@link ServiceException} 工具类
+ *
+ * 目的在于,格式化异常信息提示。
+ * 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化
+ *
+ * 因为 {@link #MESSAGES} 里面默认是没有异常信息提示的模板的,所以需要使用方自己初始化进去。目前想到的有几种方式:
+ *
+ * 1. 异常提示信息,写在枚举类中,例如说,cn.iocoder.oceans.user.api.constants.ErrorCodeEnum 类 + ServiceExceptionConfiguration
+ * 2. 异常提示信息,写在 .properties 等等配置文件
+ * 3. 异常提示信息,写在 Apollo 等等配置中心中,从而实现可动态刷新
+ * 4. 异常提示信息,存储在 db 等等数据库中,从而实现可动态刷新
+ */
+
+public class ServiceExceptionUtil {
+	 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServiceExceptionUtil.class);
+    /**
+     * 错误码提示模板
+     */
+    private static final ConcurrentMap<Integer, String> MESSAGES = new ConcurrentHashMap<>();
+
+    public static void putAll(Map<Integer, String> messages) {
+        ServiceExceptionUtil.MESSAGES.putAll(messages);
+    }
+
+    public static void put(Integer code, String message) {
+        ServiceExceptionUtil.MESSAGES.put(code, message);
+    }
+
+    public static void delete(Integer code, String message) {
+        ServiceExceptionUtil.MESSAGES.remove(code, message);
+    }
+
+    // ========== 和 ServiceException 的集成 ==========
+
+    public static ServiceException exception(ErrorCode errorCode) {
+        String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg());
+        return exception0(errorCode.getCode(), messagePattern);
+    }
+
+    public static ServiceException exception(ErrorCode errorCode, Object... params) {
+        String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg());
+        return exception0(errorCode.getCode(), messagePattern, params);
+    }
+
+    /**
+     * 创建指定编号的 ServiceException 的异常
+     *
+     * @param code 编号
+     * @return 异常
+     */
+    public static ServiceException exception(Integer code) {
+        return exception0(code, MESSAGES.get(code));
+    }
+
+    /**
+     * 创建指定编号的 ServiceException 的异常
+     *
+     * @param code 编号
+     * @param params 消息提示的占位符对应的参数
+     * @return 异常
+     */
+    public static ServiceException exception(Integer code, Object... params) {
+        return exception0(code, MESSAGES.get(code), params);
+    }
+
+    public static ServiceException exception0(Integer code, String messagePattern, Object... params) {
+        String message = doFormat(code, messagePattern, params);
+        return new ServiceException(code, message);
+    }
+
+    public static ServiceException invalidParamException(String messagePattern, Object... params) {
+        return exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), messagePattern, params);
+    }
+
+    // ========== 格式化方法 ==========
+
+    /**
+     * 将错误编号对应的消息使用 params 进行格式化。
+     *
+     * @param code           错误编号
+     * @param messagePattern 消息模版
+     * @param params         参数
+     * @return 格式化后的提示
+     */
+    public static String doFormat(int code, String messagePattern, Object... params) {
+        StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
+        int i = 0;
+        int j;
+        int l;
+        for (l = 0; l < params.length; l++) {
+            j = messagePattern.indexOf("{}", i);
+            if (j == -1) {
+                log.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
+                if (i == 0) {
+                    return messagePattern;
+                } else {
+                    sbuf.append(messagePattern.substring(i));
+                    return sbuf.toString();
+                }
+            } else {
+                sbuf.append(messagePattern, i, j);
+                sbuf.append(params[l]);
+                i = j + 2;
+            }
+        }
+        if (messagePattern.indexOf("{}", i) != -1) {
+            log.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
+        }
+        sbuf.append(messagePattern.substring(i));
+        return sbuf.toString();
+    }
+
+}

+ 134 - 0
src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java

@@ -0,0 +1,134 @@
+package cn.iocoder.yudao.framework.common.pojo;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+
+import org.springframework.util.Assert;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * 通用返回
+ *
+ * @param <T> 数据泛型
+ */
+
+public class CommonResult<T> implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+	/**
+	 * 错误码
+	 *
+	 * @see ErrorCode#getCode()
+	 */
+	private Integer code;
+
+	public Integer getCode() {
+		return code;
+	}
+
+	public void setCode(Integer code) {
+		this.code = code;
+	}
+
+	public T getData() {
+		return data;
+	}
+
+	public void setData(T data) {
+		this.data = data;
+	}
+
+	public String getMsg() {
+		return msg;
+	}
+
+	public void setMsg(String msg) {
+		this.msg = msg;
+	}
+
+	/**
+	 * 返回数据
+	 */
+	private T data;
+	/**
+	 * 错误提示,用户可阅读
+	 *
+	 * @see ErrorCode#getMsg() ()
+	 */
+	private String msg;
+
+	/**
+	 * 将传入的 result 对象,转换成另外一个泛型结果的对象
+	 *
+	 * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
+	 *
+	 * @param result 传入的 result 对象
+	 * @param <T>    返回的泛型
+	 * @return 新的 CommonResult 对象
+	 */
+	public static <T> CommonResult<T> error(CommonResult<?> result) {
+		return error(result.getCode(), result.getMsg());
+	}
+
+	public static <T> CommonResult<T> error(Integer code, String message) {
+		Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), "code 必须是错误的!");
+		CommonResult<T> result = new CommonResult<>();
+		result.code = code;
+		result.msg = message;
+		return result;
+	}
+
+	public static <T> CommonResult<T> error(ErrorCode errorCode) {
+		return error(errorCode.getCode(), errorCode.getMsg());
+	}
+
+	public static <T> CommonResult<T> success(T data) {
+		CommonResult<T> result = new CommonResult<>();
+		result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
+		result.data = data;
+		result.msg = "";
+		return result;
+	}
+
+	public static boolean isSuccess(Integer code) {
+		return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
+	}
+
+	public boolean isSuccess() {
+		return isSuccess(code);
+	}
+
+	public boolean isError() {
+		return !isSuccess();
+	}
+
+	// ========= 和 Exception 异常体系集成 =========
+
+	/**
+	 * 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
+	 */
+	public void checkError() throws ServiceException {
+		if (isSuccess()) {
+			return;
+		}
+		// 业务异常
+		throw new ServiceException(code, msg);
+	}
+
+	/**
+	 * 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常 如果没有,则返回 {@link #data} 数据
+	 */
+
+	public T getCheckedData() {
+		checkError();
+		return data;
+	}
+
+	public static <T> CommonResult<T> error(ServiceException serviceException) {
+		return error(serviceException.getCode(), serviceException.getMessage());
+	}
+
+}

+ 31 - 0
src/main/java/cn/iocoder/yudao/framework/common/pojo/PageParam.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.framework.common.pojo;
+
+import java.io.Serializable;
+
+public class PageParam implements Serializable {
+
+	private static final long serialVersionUID = 3358233066786644726L;
+	private static final Integer PAGE_NO = 1;
+	private static final Integer PAGE_SIZE = 10;
+
+	public Integer getPageNo() {
+		return pageNo;
+	}
+
+	public void setPageNo(Integer pageNo) {
+		this.pageNo = pageNo;
+	}
+
+	public Integer getPageSize() {
+		return pageSize;
+	}
+
+	public void setPageSize(Integer pageSize) {
+		this.pageSize = pageSize;
+	}
+
+	private Integer pageNo = PAGE_NO;
+
+	private Integer pageSize = PAGE_SIZE;
+
+}

+ 51 - 0
src/main/java/cn/iocoder/yudao/framework/common/pojo/PageResult.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.framework.common.pojo;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class PageResult<T> implements Serializable {
+
+	private static final long serialVersionUID = 3579555243967668992L;
+	private List<T> list;
+
+	public List<T> getList() {
+		return list;
+	}
+
+	public void setList(List<T> list) {
+		this.list = list;
+	}
+
+	public Long getTotal() {
+		return total;
+	}
+
+	public void setTotal(Long total) {
+		this.total = total;
+	}
+
+	private Long total;
+
+	public PageResult() {
+	}
+
+	public PageResult(List<T> list, Long total) {
+		this.list = list;
+		this.total = total;
+	}
+
+	public PageResult(Long total) {
+		this.list = new ArrayList<>();
+		this.total = total;
+	}
+
+	public static <T> PageResult<T> empty() {
+		return new PageResult<>(0L);
+	}
+
+	public static <T> PageResult<T> empty(Long total) {
+		return new PageResult<>(total);
+	}
+
+}

+ 52 - 0
src/main/java/cn/iocoder/yudao/framework/common/pojo/SortingField.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.framework.common.pojo;
+
+import java.io.Serializable;
+
+/**
+ * 排序字段 DTO
+ *
+ * 类名加了 ing 的原因是,避免和 ES SortField 重名。
+ */
+
+public class SortingField implements Serializable {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -4503964252773297406L;
+	/**
+	 * 顺序 - 升序
+	 */
+	public static final String ORDER_ASC = "asc";
+	/**
+	 * 顺序 - 降序
+	 */
+	public static final String ORDER_DESC = "desc";
+
+	/**
+	 * 字段
+	 */
+	private String field;
+
+	public String getField() {
+		return field;
+	}
+
+	public void setField(String field) {
+		this.field = field;
+	}
+
+	public String getOrder() {
+		return order;
+	}
+
+	public void setOrder(String order) {
+		this.order = order;
+	}
+
+	/**
+	 * 顺序
+	 */
+	private String order;
+
+}

+ 40 - 0
src/main/java/cn/iocoder/yudao/framework/datasource/config/YudaoDataSourceAutoConfiguration.java

@@ -0,0 +1,40 @@
+//package cn.iocoder.yudao.framework.datasource.config;
+//
+//import cn.iocoder.yudao.framework.datasource.core.filter.DruidAdRemoveFilter;
+//import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
+//import org.springframework.boot.autoconfigure.AutoConfiguration;
+//import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+//import org.springframework.boot.context.properties.EnableConfigurationProperties;
+//import org.springframework.boot.web.servlet.FilterRegistrationBean;
+//import org.springframework.context.annotation.Bean;
+//import org.springframework.transaction.annotation.EnableTransactionManagement;
+//
+///**
+// * 数据库配置类
+// *
+// * @author 芋道源码
+// */
+////@AutoConfiguration
+////@EnableTransactionManagement(proxyTargetClass = true) // 启动事务管理
+////@EnableConfigurationProperties(DruidStatProperties.class)
+//public class YudaoDataSourceAutoConfiguration {
+//
+//    /**
+//     * 创建 DruidAdRemoveFilter 过滤器,过滤 common.js 的广告
+//     */
+//    @Bean
+//    @ConditionalOnProperty(name = "spring.datasource.druid.web-stat-filter.enabled", havingValue = "true")
+//    public FilterRegistrationBean<DruidAdRemoveFilter> druidAdRemoveFilterFilter(DruidStatProperties properties) {
+//        // 获取 druid web 监控页面的参数
+//        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
+//        // 提取 common.js 的配置路径
+//        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
+//        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
+//        // 创建 DruidAdRemoveFilter Bean
+//        FilterRegistrationBean<DruidAdRemoveFilter> registrationBean = new FilterRegistrationBean<>();
+//        registrationBean.setFilter(new DruidAdRemoveFilter());
+//        registrationBean.addUrlPatterns(commonJsPattern);
+//        return registrationBean;
+//    }
+//
+//}

+ 22 - 0
src/main/java/cn/iocoder/yudao/framework/datasource/core/enums/DataSourceEnum.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.framework.datasource.core.enums;
+
+/**
+ * 对应于多数据源中不同数据源配置
+ *
+ * 通过在方法上,使用 {@link com.baomidou.dynamic.datasource.annotation.DS} 注解,设置使用的数据源。
+ * 注意,默认是 {@link #MASTER} 数据源
+ *
+ * 对应官方文档为 http://dynamic-datasource.com/guide/customize/Annotation.html
+ */
+public interface DataSourceEnum {
+
+    /**
+     * 主库,推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Master} 注解
+     */
+    String MASTER = "master";
+    /**
+     * 从库,推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Slave} 注解
+     */
+    String SLAVE = "slave";
+
+}

+ 38 - 0
src/main/java/cn/iocoder/yudao/framework/datasource/core/filter/DruidAdRemoveFilter.java

@@ -0,0 +1,38 @@
+//package cn.iocoder.yudao.framework.datasource.core.filter;
+//
+//import com.alibaba.druid.util.Utils;
+//import org.springframework.web.filter.OncePerRequestFilter;
+//
+//import javax.servlet.FilterChain;
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//
+///**
+// * Druid 底部广告过滤器
+// *
+// * @author 芋道源码
+// */
+//public class DruidAdRemoveFilter extends OncePerRequestFilter {
+//
+//    /**
+//     * common.js 的路径
+//     */
+//    private static final String COMMON_JS_ILE_PATH = "support/http/resources/js/common.js";
+//
+//    @Override
+//    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+//            throws ServletException, IOException {
+//        chain.doFilter(request, response);
+//        // 重置缓冲区,响应头不会被重置
+//        response.resetBuffer();
+//        // 获取 common.js
+//        String text = Utils.readFromResource(COMMON_JS_ILE_PATH);
+//        // 正则替换 banner, 除去底部的广告信息
+//        text = text.replaceAll("<a.*?banner\"></a><br/>", "");
+//        text = text.replaceAll("powered.*?shrek.wang</a>", "");
+//        response.getWriter().write(text);
+//    }
+//
+//}

+ 5 - 0
src/main/java/cn/iocoder/yudao/framework/datasource/package-info.java

@@ -0,0 +1,5 @@
+/**
+ * 数据库连接池,采用 Druid
+ * 多数据源,采用爆米花
+ */
+package cn.iocoder.yudao.framework.datasource;

+ 115 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java

@@ -0,0 +1,115 @@
+package cn.iocoder.yudao.framework.mybatis.config;
+
+//import cn.hutool.core.util.StrUtil;
+
+//import cn.hutool.core.util.StrUtil;
+
+import cn.iocoder.yudao.framework.mybatis.core.enums.SqlConstants;
+import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
+import cn.iocoder.yudao.framework.mybatis.core.util.SetUtils;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+import java.util.Set;
+
+/**
+ * 当 IdType 为 {@link IdType#NONE} 时,根据 PRIMARY 数据源所使用的数据库,自动设置
+ *
+ * @author 芋道源码
+ */
+
+public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor {
+
+	private static final String ID_TYPE_KEY = "mybatis-plus.global-config.db-config.id-type";
+
+	private static final String DATASOURCE_DYNAMIC_KEY = "spring.datasource.dynamic";
+
+	private static final String QUARTZ_JOB_STORE_DRIVER_KEY = "spring.quartz.properties.org.quartz.jobStore.driverDelegateClass";
+
+	private static final Set<DbType> INPUT_ID_TYPES = SetUtils.asSet(DbType.ORACLE, DbType.ORACLE_12C,
+			DbType.POSTGRE_SQL, DbType.KINGBASE_ES, DbType.DB2, DbType.H2);
+
+	@Override
+	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
+		// 如果获取不到 DbType,则不进行处理
+		DbType dbType = getDbType(environment);
+		if (dbType == null) {
+			return;
+		}
+
+		// 设置 Quartz JobStore 对应的 Driver
+		// TODO 芋艿:暂时没有找到特别合适的地方,先放在这里
+		setJobStoreDriverIfPresent(environment, dbType);
+
+		// 初始化 SQL 静态变量
+		SqlConstants.init(dbType);
+
+		// 如果非 NONE,则不进行处理
+		IdType idType = getIdType(environment);
+		if (idType != IdType.NONE) {
+			return;
+		}
+		// 情况一,用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
+		if (INPUT_ID_TYPES.contains(dbType)) {
+			setIdType(environment, IdType.INPUT);
+			return;
+		}
+		// 情况二,自增 ID,适合 MySQL 等直接自增的数据库
+		setIdType(environment, IdType.AUTO);
+	}
+
+	public IdType getIdType(ConfigurableEnvironment environment) {
+		return environment.getProperty(ID_TYPE_KEY, IdType.class);
+	}
+
+	public void setIdType(ConfigurableEnvironment environment, IdType idType) {
+		environment.getSystemProperties().put(ID_TYPE_KEY, idType);
+		// log.info("[setIdType][修改 MyBatis Plus 的 idType 为({})]", idType);
+	}
+
+	public void setJobStoreDriverIfPresent(ConfigurableEnvironment environment, DbType dbType) {
+		String driverClass = environment.getProperty(QUARTZ_JOB_STORE_DRIVER_KEY);
+		if (StringUtils.isNotEmpty(driverClass)) {
+			return;
+		}
+		// 根据 dbType 类型,获取对应的 driverClass
+		switch (dbType) {
+		case POSTGRE_SQL:
+			driverClass = "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate";
+			break;
+		case ORACLE:
+		case ORACLE_12C:
+			driverClass = "org.quartz.impl.jdbcjobstore.oracle.OracleDelegate";
+			break;
+		case SQL_SERVER:
+		case SQL_SERVER2005:
+			driverClass = "org.quartz.impl.jdbcjobstore.MSSQLDelegate";
+			break;
+		default:
+			break;
+		}
+		// 设置 driverClass 变量
+		if (StringUtils.isNotEmpty(driverClass)) {
+			environment.getSystemProperties().put(QUARTZ_JOB_STORE_DRIVER_KEY, driverClass);
+		}
+	}
+
+	public static DbType getDbType(ConfigurableEnvironment environment) {
+		String primary = environment.getProperty(DATASOURCE_DYNAMIC_KEY + "." + "primary");
+		if (StringUtils.isEmpty(primary)) {
+			return null;
+		}
+		String url = environment.getProperty(DATASOURCE_DYNAMIC_KEY + ".datasource." + primary + ".url");
+		if (StringUtils.isEmpty(url)) {
+			return null;
+		}
+		return JdbcUtils.getDbType(url);
+	}
+
+}

+ 66 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java

@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.framework.mybatis.config;
+
+import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler;
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
+import com.baomidou.mybatisplus.extension.incrementer.*;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.apache.ibatis.annotations.Mapper;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+/**
+ * MyBaits 配置类
+ *
+ * @author 芋道源码
+ */
+@AutoConfiguration
+@MapperScan(value = "${yudao.info.base-package}", annotationClass = Mapper.class, lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper																												// 懒加载,目前仅用于单元测试
+public class YudaoMybatisAutoConfiguration {
+
+//	@Bean
+//	public MybatisPlusInterceptor mybatisPlusInterceptor() {
+//		MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
+//		mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页插件
+//		return mybatisPlusInterceptor;
+//	}
+
+//	@Bean
+//	@ConditionalOnMissingBean 
+//	public MetaObjectHandler defaultMetaObjectHandler() {
+//		return new DefaultDBFieldHandler(); // 自动填充参数类
+//	}
+
+	@Bean
+	@ConditionalOnProperty(prefix = "mybatis-plus.global-config.db-config", name = "id-type", havingValue = "INPUT")
+	public IKeyGenerator keyGenerator(ConfigurableEnvironment environment) {
+		DbType dbType = IdTypeEnvironmentPostProcessor.getDbType(environment);
+		if (dbType != null) {
+			switch (dbType) {
+			case POSTGRE_SQL:
+				return new PostgreKeyGenerator();
+			case ORACLE:
+			case ORACLE_12C:
+				return new OracleKeyGenerator();
+			case H2:
+				return new H2KeyGenerator();
+			case KINGBASE_ES:
+				return new KingbaseKeyGenerator();
+			case DM:
+				return new DmKeyGenerator();
+			default:
+				break;
+			}
+		}
+		// 找不到合适的 IKeyGenerator 实现类
+		throw new IllegalArgumentException(String.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType));
+	}
+
+}

+ 130 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/collection/ArrayIter.java

@@ -0,0 +1,130 @@
+package cn.iocoder.yudao.framework.mybatis.core.collection;
+
+
+import java.lang.reflect.Array;
+import java.util.NoSuchElementException;
+
+/**
+ * 数组Iterator对象
+ *
+ * @param <E> 元素类型
+ * @author Looly
+ * @since 4.1.1
+ */
+public class ArrayIter<E> {
+
+	/**
+	 * 数组
+	 */
+	private final Object array;
+	/**
+	 * 起始位置
+	 */
+	private int startIndex;
+	/**
+	 * 结束位置
+	 */
+	private int endIndex;
+	/**
+	 * 当前位置
+	 */
+	private int index;
+
+	/**
+	 * 构造
+	 *
+	 * @param array 数组
+	 * @throws IllegalArgumentException array对象不为数组抛出此异常
+	 * @throws NullPointerException     array对象为null
+	 */
+	public ArrayIter(E[] array) {
+		this((Object) array);
+	}
+
+	/**
+	 * 构造
+	 *
+	 * @param array 数组
+	 * @throws IllegalArgumentException array对象不为数组抛出此异常
+	 * @throws NullPointerException     array对象为null
+	 */
+	public ArrayIter(Object array) {
+		this(array, 0);
+	}
+
+	/**
+	 * 构造
+	 *
+	 * @param array      数组
+	 * @param startIndex 起始位置,当起始位置小于0或者大于结束位置,置为0。
+	 * @throws IllegalArgumentException array对象不为数组抛出此异常
+	 * @throws NullPointerException     array对象为null
+	 */
+	public ArrayIter(Object array, int startIndex) {
+		this(array, startIndex, -1);
+	}
+
+	/**
+	 * 构造
+	 *
+	 * @param array      数组
+	 * @param startIndex 起始位置,当起始位置小于0或者大于结束位置,置为0。
+	 * @param endIndex   结束位置,当结束位置小于0或者大于数组长度,置为数组长度。
+	 * @throws IllegalArgumentException array对象不为数组抛出此异常
+	 * @throws NullPointerException     array对象为null
+	 */
+	public ArrayIter(final Object array, final int startIndex, final int endIndex) {
+		this.endIndex = Array.getLength(array);
+		if (endIndex > 0 && endIndex < this.endIndex) {
+			this.endIndex = endIndex;
+		}
+
+		if (startIndex >= 0 && startIndex < this.endIndex) {
+			this.startIndex = startIndex;
+		}
+		this.array = array;
+		this.index = this.startIndex;
+	}
+
+	public boolean hasNext() {
+		return (index < endIndex);
+	}
+
+	@SuppressWarnings("unchecked")
+	public E next() {
+		if (hasNext() == false) {
+			throw new NoSuchElementException();
+		}
+		return (E) Array.get(array, index++);
+	}
+
+	/**
+	 * 不允许操作数组元素
+	 *
+	 * @throws UnsupportedOperationException always
+	 */
+
+	public void remove() {
+		throw new UnsupportedOperationException("remove() method is not supported");
+	}
+
+	// Properties
+	// -----------------------------------------------------------------------
+
+	/**
+	 * 获得原始数组对象
+	 *
+	 * @return 原始数组对象
+	 */
+	public Object getArray() {
+		return array;
+	}
+
+	/**
+	 * 重置数组位置
+	 */
+
+	public void reset() {
+		this.index = this.startIndex;
+	}
+}

+ 91 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/dataobject/BaseDO.java

@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.framework.mybatis.core.dataobject;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+
+import org.apache.ibatis.type.JdbcType;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 基础实体对象
+ *
+ * @author 芋道源码
+ */
+
+public abstract class BaseDO implements Serializable {
+
+	private static final long serialVersionUID = -3248020874153395137L;
+	public LocalDateTime getCreateTime() {
+		return createTime;
+	}
+
+	public void setCreateTime(LocalDateTime createTime) {
+		this.createTime = createTime;
+	}
+
+	public LocalDateTime getUpdateTime() {
+		return updateTime;
+	}
+
+	public void setUpdateTime(LocalDateTime updateTime) {
+		this.updateTime = updateTime;
+	}
+
+	public String getCreator() {
+		return creator;
+	}
+
+	public void setCreator(String creator) {
+		this.creator = creator;
+	}
+
+	public String getUpdater() {
+		return updater;
+	}
+
+	public void setUpdater(String updater) {
+		this.updater = updater;
+	}
+
+	public Boolean getDeleted() {
+		return deleted;
+	}
+
+	public void setDeleted(Boolean deleted) {
+		this.deleted = deleted;
+	}
+
+	/**
+	 * 创建时间
+	 */
+	@TableField(fill = FieldFill.INSERT)
+	private LocalDateTime createTime;
+	/**
+	 * 最后更新时间
+	 */
+	@TableField(fill = FieldFill.INSERT_UPDATE)
+	private LocalDateTime updateTime;
+	/**
+	 * 创建者,目前使用 SysUser 的 id 编号
+	 *
+	 * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
+	 */
+	@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
+	private String creator;
+	/**
+	 * 更新者,目前使用 SysUser 的 id 编号
+	 *
+	 * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
+	 */
+	@TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
+	private String updater;
+	/**
+	 * 是否删除
+	 */
+	@TableLogic
+	private Boolean deleted;
+
+}

+ 21 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/SqlConstants.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.framework.mybatis.core.enums;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+
+/**
+ * SQL相关常量类
+ *
+ * @author 芋道源码
+ */
+public class SqlConstants {
+
+    /**
+     * 数据库的类型
+     */
+    public static DbType DB_TYPE;
+
+    public static void init(DbType dbType) {
+        DB_TYPE = dbType;
+    }
+
+}

+ 64 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java

@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.framework.mybatis.core.handler;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+/**
+ * 通用参数填充实现类
+ *
+ * 如果没有显式的对通用参数进行赋值,这里会对通用参数进行填充、赋值
+ *
+ * @author hexiaowu
+ */
+public class DefaultDBFieldHandler implements MetaObjectHandler {
+
+	@Override
+	public void insertFill(MetaObject metaObject) {
+		if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
+			BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();
+
+			LocalDateTime current = LocalDateTime.now();
+			// 创建时间为空,则以当前时间为插入时间
+			if (Objects.isNull(baseDO.getCreateTime())) {
+				baseDO.setCreateTime(current);
+			}
+			// 更新时间为空,则以当前时间为更新时间
+			if (Objects.isNull(baseDO.getUpdateTime())) {
+				baseDO.setUpdateTime(current);
+			}
+
+//            Long userId = WebFrameworkUtils.getLoginUserId();
+			Long userId = null;
+			// 当前登录用户不为空,创建人为空,则当前登录用户为创建人
+			if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) {
+				baseDO.setCreator(userId.toString());
+			}
+			// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
+			if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) {
+				baseDO.setUpdater(userId.toString());
+			}
+		}
+	}
+
+	@Override
+	public void updateFill(MetaObject metaObject) {
+		// 更新时间为空,则以当前时间为更新时间
+		Object modifyTime = getFieldValByName("updateTime", metaObject);
+		if (Objects.isNull(modifyTime)) {
+			setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
+		}
+
+		// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
+		Object modifier = getFieldValByName("updater", metaObject);
+//		Long userId = WebFrameworkUtils.getLoginUserId();
+		Long userId = 0l;
+		if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
+			setFieldValByName("updater", userId.toString(), metaObject);
+		}
+	}
+}

+ 136 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java

@@ -0,0 +1,136 @@
+package cn.iocoder.yudao.framework.mybatis.core.mapper;
+
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.util.CollUtil;
+import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.baomidou.mybatisplus.extension.toolkit.Db;
+import com.github.yulichang.base.MPJBaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力
+ *
+ * 1. {@link BaseMapper} 为 MyBatis Plus 的基础接口,提供基础的 CRUD 能力
+ * 2. {@link MPJBaseMapper} 为 MyBatis Plus Join 的基础接口,提供连表 Join 能力
+ */
+public interface BaseMapperX<T> extends MPJBaseMapper<T> {
+
+    default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
+        // MyBatis Plus 查询
+        IPage<T> mpPage = MyBatisUtils.buildPage(pageParam);
+        selectPage(mpPage, queryWrapper);
+        // 转换返回
+        return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
+    }
+
+    default T selectOne(String field, Object value) {
+        return selectOne(new QueryWrapper<T>().eq(field, value));
+    }
+
+    default T selectOne(SFunction<T, ?> field, Object value) {
+        return selectOne(new LambdaQueryWrapper<T>().eq(field, value));
+    }
+
+    default T selectOne(String field1, Object value1, String field2, Object value2) {
+        return selectOne(new QueryWrapper<T>().eq(field1, value1).eq(field2, value2));
+    }
+
+    default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {
+        return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
+    }
+
+    default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
+                        SFunction<T, ?> field3, Object value3) {
+        return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
+                .eq(field3, value3));
+    }
+
+    default Long selectCount() {
+        return selectCount(new QueryWrapper<>());
+    }
+
+    default Long selectCount(String field, Object value) {
+        return selectCount(new QueryWrapper<T>().eq(field, value));
+    }
+
+    default Long selectCount(SFunction<T, ?> field, Object value) {
+        return selectCount(new LambdaQueryWrapper<T>().eq(field, value));
+    }
+
+    default List<T> selectList() {
+        return selectList(new QueryWrapper<>());
+    }
+
+    default List<T> selectList(String field, Object value) {
+        return selectList(new QueryWrapper<T>().eq(field, value));
+    }
+
+    default List<T> selectList(SFunction<T, ?> field, Object value) {
+        return selectList(new LambdaQueryWrapper<T>().eq(field, value));
+    }
+
+    default List<T> selectList(String field, Collection<?> values) {
+        if (CollUtil.isEmpty(values)) {
+            return CollUtil.newArrayList();
+        }
+        return selectList(new QueryWrapper<T>().in(field, values));
+    }
+
+    default List<T> selectList(SFunction<T, ?> field, Collection<?> values) {
+        if (CollUtil.isEmpty(values)) {
+            return CollUtil.newArrayList();
+        }
+        return selectList(new LambdaQueryWrapper<T>().in(field, values));
+    }
+
+    default List<T> selectList(SFunction<T, ?> leField, SFunction<T, ?> geField, Object value) {
+        return selectList(new LambdaQueryWrapper<T>().le(leField, value).ge(geField, value));
+    }
+
+    /**
+     * 批量插入,适合大量数据插入
+     *
+     * @param entities 实体们
+     */
+    default void insertBatch(Collection<T> entities) {
+        Db.saveBatch(entities);
+    }
+
+    /**
+     * 批量插入,适合大量数据插入
+     *
+     * @param entities 实体们
+     * @param size     插入数量 Db.saveBatch 默认为 1000
+     */
+    default void insertBatch(Collection<T> entities, int size) {
+        Db.saveBatch(entities, size);
+    }
+
+    default void updateBatch(T update) {
+        update(update, new QueryWrapper<>());
+    }
+
+    default void updateBatch(Collection<T> entities) {
+        Db.updateBatchById(entities);
+    }
+
+    default void updateBatch(Collection<T> entities, int size) {
+        Db.updateBatchById(entities, size);
+    }
+
+    default void saveOrUpdateBatch(Collection<T> collection) {
+        Db.saveOrUpdateBatch(collection);
+    }
+
+}

+ 289 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/PlusBaseMapperX.java

@@ -0,0 +1,289 @@
+package cn.iocoder.yudao.framework.mybatis.core.mapper;
+
+import java.beans.PropertyDescriptor;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+import org.apache.ibatis.reflection.property.PropertyNamer;
+import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Assert;
+import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
+import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
+import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public interface PlusBaseMapperX<T> extends BaseMapperX<T> {
+	static final Log logger = LogFactory.getLog(PlusBaseMapperX.class);
+
+	/**
+	 * 博客地址:https://blog.csdn.net/qq_19266669/article/details/114369195
+	 * 查看AbstractLambdaWrapper通过方法得到字段名称
+	 * 
+	 * @param column
+	 * @return
+	 */
+	default String columnToString(SFunction<T, ?> column) {
+		return columnToString(column, true);
+	}
+
+	default String columnToString(SFunction<T, ?> column, boolean onlyColumn) {
+		ColumnCache cache = getColumnCache(column);
+		return onlyColumn ? cache.getColumn() : cache.getColumnSelect();
+	}
+
+	/**
+	 * 获取 SerializedLambda 对应的列信息,从 lambda 表达式中推测实体类
+	 * <p>
+	 * 如果获取不到列信息,那么本次条件组装将会失败
+	 *
+	 * @return 列
+	 * @throws com.baomidou.mybatisplus.core.exceptions.MybatisPlusException 获取不到列信息时抛出异常
+	 */
+	default ColumnCache getColumnCache(SFunction<T, ?> column) {
+		LambdaMeta meta = LambdaUtils.extract(column);
+		String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName());
+		Class<?> instantiatedClass = meta.getInstantiatedClass();
+		Map<String, ColumnCache> columnMap = tryInitCache(instantiatedClass);
+		return getColumnCache(fieldName, instantiatedClass, columnMap);
+	}
+
+	default ColumnCache getColumnCache(String fieldName, Class<?> lambdaClass, Map<String, ColumnCache> columnMap) {
+		ColumnCache columnCache = columnMap.get(LambdaUtils.formatKey(fieldName));
+		Assert.notNull(columnCache, "can not find lambda cache for this property [%s] of entity [%s]", fieldName,
+				lambdaClass.getName());
+		return columnCache;
+	}
+
+	@SuppressWarnings("unchecked")
+	default Class<T> getEntityClass() {
+		return (Class<T>)ReflectionKit.getSuperClassGenericType(this.getClass(), PlusBaseMapperX.class, 0) ;
+	}
+	@SuppressWarnings("unchecked")
+	default Class<T> currentMapperClass() {
+        return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), PlusBaseMapperX.class, 0);
+    }
+	default Map<String, ColumnCache> tryInitCache(Class<?> lambdaClass) {
+
+		final Class<T> entityClass = getEntityClass();
+		if (entityClass != null) {
+			lambdaClass = entityClass;
+		}
+		Map<String, ColumnCache> columnMap = LambdaUtils.getColumnMap(lambdaClass);
+		return columnMap;
+	}
+
+	/**
+	 * 根据 entity 条件,删除记录
+	 *
+	 * @param queryWrapper 实体包装类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default boolean remove(T t) {
+		Wrapper<T> queryWrapper = QueryWrapperUtil.convertQuery(t);
+		return SqlHelper.retBool(delete(queryWrapper));
+	}
+
+	/**
+	 * 根据 whereEntity 条件,更新记录
+	 *
+	 * @param entity        实体对象
+	 * @param updateWrapper 实体对象封装操作类
+	 *                      {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
+	 * @throws SecurityException
+	 * @throws NoSuchFieldException
+	 */
+	default boolean update(T entity, SFunction<T, ?> column, Object value) {
+
+		UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
+		// 这里使用反射得到字段,
+		String conditions = columnToString(column);
+		updateWrapper.eq(conditions, value);
+		return SqlHelper.retBool(update(entity, updateWrapper));
+
+	}
+
+	/**
+	 * 根据 whereEntity 条件,更新记录
+	 *
+	 * @param entity        实体对象
+	 * @param updateWrapper 实体对象封装操作类
+	 *                      {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
+	 * @throws SecurityException
+	 * @throws NoSuchFieldException
+	 */
+	default boolean update(T entity, Map<String, Object> columns) {
+
+		UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
+		for (String key : columns.keySet()) {
+			updateWrapper.eq(key, columns.get(key));
+		}
+		return SqlHelper.retBool(update(entity, updateWrapper));
+
+	}
+
+	/**
+	 * 将
+	 * 
+	 * @param entity
+	 * @param columns
+	 * @return
+	 */
+	@SuppressWarnings("unchecked")
+	default boolean update(T entity, SFunction<T, ?>... columns) {
+		// 这里是反射,通过反射来得到
+		try {
+			Map<String, Object> columnVal = new HashMap<>();
+			for (SFunction<T, ?> column : columns) {
+				String columnName = columnToString(column);
+				String fieldName = getFieldName(column);
+				Class<?> clazz = entity.getClass();
+				PropertyDescriptor p = new PropertyDescriptor(fieldName, clazz);
+				Method readMethod = p.getReadMethod();
+				// 得到跟新的值
+				Object value = readMethod.invoke(entity);
+				// 把查询条件的值设置为null
+				Method writeMethod = p.getWriteMethod();
+				// 将相等的值设置为控制
+				Object obj = null;
+				writeMethod.invoke(entity, obj);
+				columnVal.put(columnName, value);
+			}
+			return update(entity, columnVal);
+		} catch (Exception e) {
+			logger.error("update class" + entity.getClass() + " exception :" + e.getMessage());
+			return false;
+		}
+
+	}
+
+	default String getFieldName(SFunction<T, ?> column) {
+		LambdaMeta meta = LambdaUtils.extract(column);
+		String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName());
+		return fieldName;
+
+	}
+
+	default boolean update(T entity, SFunction<T, ?> column, Collection<?> coll) {
+		UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
+		// 这里使用反射得到字段,
+		String conditions = columnToString(column);
+		updateWrapper.in(conditions, coll);
+		return SqlHelper.retBool(update(entity, updateWrapper));
+
+	}
+
+	default boolean update(T entity, String conditions, Object... values) {
+		UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
+		updateWrapper.in(conditions, values);
+		return SqlHelper.retBool(update(entity, updateWrapper));
+	}
+
+	/**
+	 * 根据 Wrapper,查询一条记录 <br/>
+	 * <p>
+	 * 结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
+	 * </p>
+	 *
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default T getOne(T t) {
+		Wrapper<T> queryWrapper = QueryWrapperUtil.convertQuery(t);
+		return selectOne(queryWrapper, true);
+	}
+
+	default Map<String, Object> getMap(T t) {
+		Wrapper<T> queryWrapper = QueryWrapperUtil.convertQuery(t);
+		return SqlHelper.getObject(logger, selectMaps(queryWrapper));
+
+	}
+
+	/**
+	 * 根据 Wrapper 条件,查询总记录数
+	 *
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default long count(T t) {
+		Wrapper<T> queryWrapper = QueryWrapperUtil.convertQuery(t);
+		return SqlHelper.retCount(selectCount(queryWrapper));
+
+	}
+
+	/**
+	 * 查询列表
+	 *
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default List<T> list(T t) {
+		Wrapper<T> queryWrapper = QueryWrapperUtil.convertQuery(t);
+		return selectList(queryWrapper);
+	}
+
+	/**
+	 * 查询列表
+	 *
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default List<Map<String, Object>> listMaps(T t) {
+		Wrapper<T> queryWrapper = QueryWrapperUtil.convertQuery(t);
+		return selectMaps(queryWrapper);
+	}
+
+	/**
+	 * 根据 Wrapper 条件,查询全部记录
+	 *
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default List<Object> listObjs(T t) {
+		Wrapper<T> queryWrapper = QueryWrapperUtil.convertQuery(t);
+		return listObjs(queryWrapper, Function.identity());
+	}
+
+	/**
+	 * 根据 Wrapper 条件,查询全部记录
+	 *
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 * @param mapper       转换函数
+	 */
+	default <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper) {
+		return selectObjs(queryWrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
+	}
+
+	/**
+	 * 翻页查询
+	 *
+	 * @param page         翻页对象
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default <E extends IPage<T>> E page(E page, T t) {
+		Wrapper<T> queryWrapper = QueryWrapperUtil.convertQuery(t);
+		try {
+			return selectPage(page, queryWrapper);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+	public static void main(String[] args) {
+		
+	}
+}

+ 28 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryEntity.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.framework.mybatis.core.mapper;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+
+public class QueryEntity {
+	public String getStartDate() {
+		return startDate;
+	}
+
+	public void setStartDate(String startDate) {
+		this.startDate = startDate;
+	}
+
+	public String getEndDate() {
+		return endDate;
+	}
+
+	public void setEndDate(String endDate) {
+		this.endDate = endDate;
+	}
+
+	// 开始时间
+	@TableField(exist = false)
+	private String startDate;
+	// 结束时间
+	@TableField(exist = false)
+	private String endDate;
+}

+ 55 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryKeyword.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.framework.mybatis.core.mapper;
+
+
+import com.baomidou.mybatisplus.core.conditions.ISqlSegment;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+
+
+/**
+ * SQL 保留关键字枚举,{@link SqlKeyword}
+ *
+ * @author hubin
+ * @since 2018-05-28
+ */
+
+public enum QueryKeyword implements ISqlSegment {
+
+    AND("AND"),
+    OR("OR"),
+    NOT("NOT"),
+    IN("IN"),
+    NOT_IN("NOT IN"),
+    LIKE("LIKE"),
+    NOT_LIKE("NOT LIKE"),
+    EQ(StringPool.EQUALS),
+    NE("<>"),
+    GT(StringPool.RIGHT_CHEV),
+    GE(">="),
+    LT(StringPool.LEFT_CHEV),
+    LE("<="),
+    IS_NULL("IS NULL"),
+    IS_NOT_NULL("IS NOT NULL"),
+    GROUP_BY("GROUP BY"),
+    HAVING("HAVING"),
+    ORDER_BY("ORDER BY"),
+    EXISTS("EXISTS"),
+    NOT_EXISTS("NOT EXISTS"),
+    BETWEEN("BETWEEN"),
+    NOT_BETWEEN("NOT BETWEEN"),
+    ASC("ASC"),
+    DESC("DESC"),
+	LIKE_LEFT("likeLeft"),
+	LIKE_RIGHT("likeRight"),
+	RANGE("RANGE"),
+	;
+	private QueryKeyword(String string) {
+		this.keyword = string;
+		
+	};
+    private final String keyword;
+
+    @Override
+    public String getSqlSegment() {
+        return this.keyword;
+    }
+}

+ 17 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryMap.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.framework.mybatis.core.mapper;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public class QueryMap {
+	private ConcurrentMap<String, Object> param = new ConcurrentHashMap<String, Object>();
+
+	public Object get(String key) {
+		return param.get(key);
+	}
+
+	public void setParam(String key, String value) {
+		param.put(key, value);
+	}
+
+}

+ 17 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryWapper.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.framework.mybatis.core.mapper;
+
+import org.springframework.stereotype.Indexed;
+
+import java.lang.annotation.*;
+
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Indexed
+public @interface QueryWapper {
+
+	QueryKeyword[] value();
+
+	// 查询时间的范围
+	String[] attribute() default "";
+}

+ 20 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryWapperEnum.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.framework.mybatis.core.mapper;
+
+/**
+ * @Description 查询枚举类
+ * @Date 2020-07-16 16:10
+ */
+public enum QueryWapperEnum {
+
+	LIKE(1), EQ(2);
+
+	private final int value;
+
+	QueryWapperEnum(int value) {
+		this.value = value;
+	}
+
+	public int value() {
+		return this.value;
+	}
+}

+ 217 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/QueryWrapperUtil.java

@@ -0,0 +1,217 @@
+package cn.iocoder.yudao.framework.mybatis.core.mapper;
+
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.ibatis.ognl.OgnlRuntime;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.ObjectUtils;
+
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+
+
+/**
+ * @Description 拼接查询条件工具类
+ */
+public class QueryWrapperUtil {
+	/**
+	 * 得到父类的对象
+	 * 
+	 * @param <T>   查询的泛型
+	 * @param clazz
+	 * @return
+	 */
+	public static <T> List<Field> reflectForField(Class<T> clazz) {
+		Class<?> tmpClazz = clazz;
+		List<Field> fieldList = new ArrayList<>();
+		while (tmpClazz != null) {
+			//处理去掉静态属性
+			for (Field f:tmpClazz.getDeclaredFields()) {
+				if(!Modifier.isStatic(f.getModifiers())) {
+					 fieldList.add(f);
+				}
+			}
+			tmpClazz = tmpClazz.getSuperclass();
+		}
+		return fieldList;
+	}
+
+	/**
+	 * 拼接查询条件
+	 *
+	 * @param obj 数据实体
+	 * @return void 返回参数说明
+	 * @exception/throws
+	 */
+	public static <T> QueryWrapper<T> convertQuery(Object obj, QueryMap... jsonObj) {
+		QueryMap param = new QueryMap();
+		if (ArrayUtils.isNotEmpty(jsonObj)) {
+			param = jsonObj[0];
+		}
+		QueryWrapper<T> queryWrapper = new QueryWrapper<>();
+		Class<?> clazz = obj.getClass();
+		try {
+			// TODO 这里应该通过共有方法来进行反射
+			for (Field field : reflectForField(clazz)) {
+
+				// 抑制Java对修饰符的检查
+//				field.setAccessible(true);
+				// 获取属性值
+//				Object fieldValue = field.get(obj);
+//            String fieldValue = getFieldValue(obj ,field.getName()).toString();
+				TableField tableField = AnnotationUtils.getAnnotation(field, TableField.class);
+				// 字段没有TableField这个注解和有这个主机上false的
+				// 这里有可能没有注解,是使用了骆驼ming'm
+				if (tableField != null && !tableField.exist()) {
+					continue;
+				}
+				// String methodName = "set" + filedName.substring(0, 1).toUpperCase() +
+				// filedName.substring(1);
+				// 获取属性名
+				String name = field.getName();
+				// 声明属性描述对象
+//				PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, clazz);
+//				Method method = clazz.getDeclaredMethod(propertyName);
+				// 获取getter方法
+//				Method method = propertyDescriptor.getReadMethod();
+				//String getMethod = getMethod(name);
+				Method method = getMethod(clazz, name);
+				if(method==null) {
+					continue;
+				}
+				Object fieldValue = method.invoke(obj);
+				String fieldName;
+				if (tableField == null) {
+					fieldName = StringUtils.camelToUnderline(name);
+				} else {
+					fieldName = tableField.value();
+				}
+				// 默认是相等
+				QueryWapper queryWapperAnnotation = AnnotationUtils.getAnnotation(field, QueryWapper.class);
+				if (ObjectUtils.isEmpty(queryWapperAnnotation)) {
+					queryWrapper.eq(!ObjectUtils.isEmpty(fieldValue), fieldName, fieldValue);
+				} else {
+					// 获取枚举
+					QueryKeyword[] queryWapperEnums = queryWapperAnnotation.value();
+					for (QueryKeyword queryWapperEnum : queryWapperEnums) {
+						// 拼接查询条件
+						switch (queryWapperEnum) {
+						case NE:
+							queryWrapper.ne(!ObjectUtils.isEmpty(fieldValue), fieldName, fieldValue);
+							break;
+						// 这里需要,修改为集合
+						case IN:
+							if (fieldValue != null && fieldValue.toString().contains(",")) {
+								List<String> result = Arrays.asList(fieldValue.toString().split(","));
+								queryWrapper.in(!ObjectUtils.isEmpty(fieldValue), fieldName, result);
+							}
+							break;
+						case GT:
+							queryWrapper.gt(!ObjectUtils.isEmpty(fieldValue), fieldName, fieldValue);
+							break;
+						case GE:
+							queryWrapper.ge(!ObjectUtils.isEmpty(fieldValue), fieldName, fieldValue);
+							break;
+						case LT:
+							queryWrapper.lt(!ObjectUtils.isEmpty(fieldValue), fieldName, fieldValue);
+							break;
+						case LE:
+							queryWrapper.le(!ObjectUtils.isEmpty(fieldValue), fieldName, fieldValue);
+							break;
+						case LIKE:
+							queryWrapper.like(!ObjectUtils.isEmpty(fieldValue), fieldName, fieldValue);
+							break;
+						case NOT_LIKE:
+							queryWrapper.notLike(!ObjectUtils.isEmpty(fieldValue), fieldName, fieldValue);
+							break;
+						case LIKE_LEFT:
+							queryWrapper.likeLeft(!ObjectUtils.isEmpty(fieldValue), fieldName, fieldValue);
+							break;
+						case LIKE_RIGHT:
+							queryWrapper.likeRight(!ObjectUtils.isEmpty(fieldValue), fieldName, fieldValue);
+							break;
+						case DESC:
+							queryWrapper.orderByDesc(fieldName);
+							break;
+						case ASC:
+							queryWrapper.orderByAsc(fieldName);
+							break;
+						case RANGE:
+							// 设置开始的值和结束的值
+							String[] attribute = queryWapperAnnotation.attribute();
+							String startRange = attribute[0];
+							// 声明属性描述对象
+//							getMethod = getMethod(startRange);
+//							method = ReflectionUtils.findMethod(clazz, getMethod);
+							 method = getMethod(clazz, startRange);
+							if (method != null) {
+								Object val = method.invoke(obj);
+								if (!ObjectUtils.isEmpty(val)) {
+									queryWrapper.ge(fieldName, val);
+								}
+							}
+							// 得到开始的属性值,通过反射设置属性值
+//							Field startRange = clazz.getDeclaredField(attribute[0]);
+//							startRange.setAccessible(true);
+//							Object val = startRange.get(obj);
+							// 结束的属性值,通过反射设置属性值
+							String endRange = attribute[1];
+							//getMethod = getMethod(endRange);
+							// 获取getter方法
+							//method = ReflectionUtils.findMethod(clazz, getMethod);
+							method = getMethod(clazz, endRange);
+							if (method != null) {
+								Object val = method.invoke(obj);
+								if (!ObjectUtils.isEmpty(val)) {
+									queryWrapper.le(fieldName, val);
+								}
+							}
+							// ---属性值查询结束的值----
+							// 通过传入的参数查询值
+							// 然后通过参数查询
+							Object val = param.get(attribute[0]);
+							if (!ObjectUtils.isEmpty(val)) {
+								queryWrapper.ge(fieldName, val);
+							}
+							// 然后通过参数查询
+							val = param.get(attribute[1]);
+							if (!ObjectUtils.isEmpty(val)) {
+								queryWrapper.le(fieldName, val);
+							}
+							break;
+						default:
+							break;
+						}
+					}
+
+				}
+
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return queryWrapper;
+
+	}
+ 
+
+	// 获取get方法
+	public static Method getMethod(Class<?> clazz,String filedName) throws Exception {
+//		String alpha = filedName.substring(0, 1).toUpperCase();
+//		String methodName = "get" + alpha + filedName.substring(1);
+//		PropertyDescriptor p = new PropertyDescriptor(filedName, beanClass);
+//		Method readMethod = p.getReadMethod();
+		
+		return OgnlRuntime.getReadMethod(clazz, filedName);
+	}
+
+}

+ 138 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperX.java

@@ -0,0 +1,138 @@
+package cn.iocoder.yudao.framework.mybatis.core.query;
+
+
+import cn.iocoder.yudao.framework.mybatis.core.util.ArrayUtils;
+import cn.iocoder.yudao.framework.mybatis.core.util.ObjectUtil;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+
+/**
+ * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能:
+ * <p>
+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
+ *
+ * @param <T> 数据类型
+ */
+public class LambdaQueryWrapperX<T> extends LambdaQueryWrapper<T> {
+
+    private static final long serialVersionUID = -8898497335185097393L;
+
+	public LambdaQueryWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {
+        if (StringUtils.hasText(val)) {
+            return (LambdaQueryWrapperX<T>) super.like(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtils.isEmpty(values)) {
+            return (LambdaQueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtils.isEmpty(values)) {
+            return (LambdaQueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (LambdaQueryWrapperX<T>) super.eq(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (LambdaQueryWrapperX<T>) super.ne(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.gt(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.ge(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.lt(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.le(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {
+        if (val1 != null && val2 != null) {
+            return (LambdaQueryWrapperX<T>) super.between(column, val1, val2);
+        }
+        if (val1 != null) {
+            return (LambdaQueryWrapperX<T>) ge(column, val1);
+        }
+        if (val2 != null) {
+            return (LambdaQueryWrapperX<T>) le(column, val2);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {
+        Object val1 = ArrayUtils.get(values, 0);
+        Object val2 = ArrayUtils.get(values, 1);
+        return betweenIfPresent(column, val1, val2);
+    }
+
+    // ========== 重写父类方法,方便链式调用 ==========
+
+    @Override
+    public LambdaQueryWrapperX<T> eq(boolean condition, SFunction<T, ?> column, Object val) {
+        super.eq(condition, column, val);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> eq(SFunction<T, ?> column, Object val) {
+        super.eq(column, val);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> orderByDesc(SFunction<T, ?> column) {
+        super.orderByDesc(true, column);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> last(String lastSql) {
+        super.last(lastSql);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> in(SFunction<T, ?> column, Collection<?> coll) {
+        super.in(column, coll);
+        return this;
+    }
+
+}

+ 317 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java

@@ -0,0 +1,317 @@
+package cn.iocoder.yudao.framework.mybatis.core.query;
+
+
+import cn.iocoder.yudao.framework.mybatis.core.util.ArrayUtils;
+import cn.iocoder.yudao.framework.mybatis.core.util.ObjectUtil;
+
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.github.yulichang.toolkit.MPJWrappers;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.function.Consumer;
+
+/**
+ * 拓展 MyBatis Plus Join QueryWrapper 类,主要增加如下功能:
+ * <p>
+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
+ *
+ * @param <T> 数据类型
+ */
+public class MPJLambdaWrapperX<T> extends MPJLambdaWrapper<T> {
+
+    private static final long serialVersionUID = 1L;
+
+	public MPJLambdaWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {
+        MPJWrappers.lambdaJoin().like(column, val);
+        if (StringUtils.hasText(val)) {
+            return (MPJLambdaWrapperX<T>) super.like(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtils.isEmpty(values)) {
+            return (MPJLambdaWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtils.isEmpty(values)) {
+            return (MPJLambdaWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (MPJLambdaWrapperX<T>) super.eq(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (MPJLambdaWrapperX<T>) super.ne(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.gt(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.ge(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.lt(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.le(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {
+        if (val1 != null && val2 != null) {
+            return (MPJLambdaWrapperX<T>) super.between(column, val1, val2);
+        }
+        if (val1 != null) {
+            return (MPJLambdaWrapperX<T>) ge(column, val1);
+        }
+        if (val2 != null) {
+            return (MPJLambdaWrapperX<T>) le(column, val2);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {
+        Object val1 = ArrayUtils.get(values, 0);
+        Object val2 = ArrayUtils.get(values, 1);
+        return betweenIfPresent(column, val1, val2);
+    }
+
+    // ========== 重写父类方法,方便链式调用 ==========
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> eq(boolean condition, SFunction<X, ?> column, Object val) {
+        super.eq(condition, column, val);
+        return this;
+    }
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> eq(SFunction<X, ?> column, Object val) {
+        super.eq(column, val);
+        return this;
+    }
+
+    @SuppressWarnings("unchecked")
+	@Override
+    public <X> MPJLambdaWrapperX<T> orderByDesc(SFunction<X, ?> column) {
+        //noinspection unchecked
+        super.orderByDesc(true, column);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> last(String lastSql) {
+        super.last(lastSql);
+        return this;
+    }
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> in(SFunction<X, ?> column, Collection<?> coll) {
+        super.in(column, coll);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> selectAll(Class<?> clazz) {
+        super.selectAll(clazz);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> selectAll(Class<?> clazz, String prefix) {
+        super.selectAll(clazz, prefix);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectAs(SFunction<S, ?> column, String alias) {
+        super.selectAs(column, alias);
+        return this;
+    }
+
+    @Override
+    public <E> MPJLambdaWrapperX<T> selectAs(String column, SFunction<E, ?> alias) {
+        super.selectAs(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectAs(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectAs(column, alias);
+        return this;
+    }
+
+    @Override
+    public <E, X> MPJLambdaWrapperX<T> selectAs(String index, SFunction<E, ?> column, SFunction<X, ?> alias) {
+        super.selectAs(index, column, alias);
+        return this;
+    }
+
+    @Override
+    public <E> MPJLambdaWrapperX<T> selectAsClass(Class<E> source, Class<?> tag) {
+        super.selectAsClass(source, tag);
+        return this;
+    }
+
+    @Override
+    public <E, F> MPJLambdaWrapperX<T> selectSub(Class<E> clazz, Consumer<MPJLambdaWrapper<E>> consumer, SFunction<F, ?> alias) {
+        super.selectSub(clazz, consumer, alias);
+        return this;
+    }
+
+    @Override
+    public <E, F> MPJLambdaWrapperX<T> selectSub(Class<E> clazz, String st, Consumer<MPJLambdaWrapper<E>> consumer, SFunction<F, ?> alias) {
+        super.selectSub(clazz, st, consumer, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column) {
+        super.selectCount(column);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> selectCount(Object column, String alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> selectCount(Object column, SFunction<X, ?> alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, String alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column) {
+        super.selectSum(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, String alias) {
+        super.selectSum(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectSum(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column) {
+        super.selectMax(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, String alias) {
+        super.selectMax(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectMax(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column) {
+        super.selectMin(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, String alias) {
+        super.selectMin(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectMin(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column) {
+        super.selectAvg(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, String alias) {
+        super.selectAvg(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectAvg(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column) {
+        super.selectLen(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, String alias) {
+        super.selectLen(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectLen(column, alias);
+        return this;
+    }
+
+}

+ 169 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java

@@ -0,0 +1,169 @@
+package cn.iocoder.yudao.framework.mybatis.core.query;
+
+
+import cn.iocoder.yudao.framework.mybatis.core.enums.SqlConstants;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
+import com.baomidou.mybatisplus.core.toolkit.Assert;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+
+/**
+ * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能:
+ *
+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
+ *
+ * @param <T> 数据类型
+ */
+public class QueryWrapperX<T> extends QueryWrapper<T> {
+
+    private static final long serialVersionUID = 8674033674276527963L;
+
+	public QueryWrapperX<T> likeIfPresent(String column, String val) {
+        if (StringUtils.hasText(val)) {
+            return (QueryWrapperX<T>) super.like(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> inIfPresent(String column, Collection<?> values) {
+        if (!CollectionUtils.isEmpty(values)) {
+            return (QueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> inIfPresent(String column, Object... values) {
+        if (!ArrayUtils.isEmpty(values)) {
+            return (QueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> eqIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.eq(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> neIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.ne(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> gtIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.gt(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> geIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.ge(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> ltIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.lt(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> leIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.le(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> betweenIfPresent(String column, Object val1, Object val2) {
+        if (val1 != null && val2 != null) {
+            return (QueryWrapperX<T>) super.between(column, val1, val2);
+        }
+        if (val1 != null) {
+            return (QueryWrapperX<T>) ge(column, val1);
+        }
+        if (val2 != null) {
+            return (QueryWrapperX<T>) le(column, val2);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> betweenIfPresent(String column, Object[] values) {
+        if (values!= null && values.length != 0 && values[0] != null && values[1] != null) {
+            return (QueryWrapperX<T>) super.between(column, values[0], values[1]);
+        }
+        if (values!= null && values.length != 0 && values[0] != null) {
+            return (QueryWrapperX<T>) ge(column, values[0]);
+        }
+        if (values!= null && values.length != 0 && values[1] != null) {
+            return (QueryWrapperX<T>) le(column, values[1]);
+        }
+        return this;
+    }
+
+    // ========== 重写父类方法,方便链式调用 ==========
+
+    @Override
+    public QueryWrapperX<T> eq(boolean condition, String column, Object val) {
+        super.eq(condition, column, val);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> eq(String column, Object val) {
+        super.eq(column, val);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> orderByDesc(String column) {
+        super.orderByDesc(true, column);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> last(String lastSql) {
+        super.last(lastSql);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> in(String column, Collection<?> coll) {
+        super.in(column, coll);
+        return this;
+    }
+
+    /**
+     * 设置只返回最后一条
+     *
+     * TODO 芋艿:不是完美解,需要在思考下。如果使用多数据源,并且数据源是多种类型时,可能会存在问题:实现之返回一条的语法不同
+     *
+     * @return this
+     */
+    public QueryWrapperX<T> limitN(int n) {
+        Assert.notNull(SqlConstants.DB_TYPE, "获取不到数据库的类型");
+        switch (SqlConstants.DB_TYPE) {
+            case ORACLE:
+            case ORACLE_12C:
+                super.eq("ROWNUM", n);
+                break;
+            case SQL_SERVER:
+            case SQL_SERVER2005:
+                super.select("TOP " + n + " *"); // 由于 SQL Server 是通过 SELECT TOP 1 实现限制一条,所以只好使用 * 查询剩余字段
+                break;
+            default:
+                super.last("LIMIT " + n);
+        }
+        return this;
+    }
+
+}

+ 153 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/service/AbstractService.java

@@ -0,0 +1,153 @@
+package cn.iocoder.yudao.framework.mybatis.core.service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.github.yulichang.base.MPJBaseService;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.PlusBaseMapperX;
+public interface AbstractService<T> extends MPJBaseService<T> {
+
+	/**
+	 * 获取对应 entity 的 BaseMapper
+	 *
+	 * @return BaseMapper
+	 */
+	PlusBaseMapperX<T> getBaseMapper();
+
+	/**
+	 * 根据 entity 条件,删除记录
+	 *
+	 * @param queryWrapper 实体包装类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default boolean remove(T t) {
+		return getBaseMapper().remove(t);
+	}
+
+	/**
+	 * 根据 whereEntity 条件,更新记录
+	 *
+	 * @param entity        实体对象
+	 * @param updateWrapper 实体对象封装操作类
+	 *                      {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
+	 * @throws SecurityException
+	 * @throws NoSuchFieldException
+	 */
+	default boolean update(T entity, SFunction<T, ?> column, Object value) {
+
+		return getBaseMapper().update(entity, column, value);
+
+	}
+
+	/**
+	 * 根据 whereEntity 条件,更新记录
+	 *
+	 * @param entity        实体对象
+	 * @param updateWrapper 实体对象封装操作类
+	 *                      {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
+	 * @throws SecurityException
+	 * @throws NoSuchFieldException
+	 */
+	default boolean update(T entity, String column, Object value) {
+
+		return getBaseMapper().update(entity, column, value);
+
+	}
+	/**
+	 * 
+	 * @param entity
+	 * @param column 
+	 * @return
+	 */
+	@SuppressWarnings("unchecked")
+	default boolean update(T entity, SFunction<T, ?> column) {
+		// 这里是反射,通过反射来得到
+		return getBaseMapper().update(entity, column);
+
+	}
+
+	default boolean update(T entity, SFunction<T, ?> column, Collection<?> coll) {
+
+		return getBaseMapper().update(entity, column, coll);
+
+	}
+
+	default boolean update(T entity, String conditions, Object... values) {
+		return getBaseMapper().update(entity, conditions, values);
+	}
+
+	/**
+	 * 根据 Wrapper,查询一条记录 <br/>
+	 * <p>
+	 * 结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
+	 * </p>
+	 *
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default T getOne(T t) {
+		return getBaseMapper().getOne(t);
+	}
+
+	default Map<String, Object> getMap(T t) {
+		return getBaseMapper().getMap(t);
+
+	}
+
+	/**
+	 * 根据 Wrapper 条件,查询总记录数
+	 *
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default long count(T t) {
+		return getBaseMapper().count(t);
+
+	}
+
+	/**
+	 * 查询列表
+	 *
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default List<T> list(T t) {
+		return getBaseMapper().list(t);
+	}
+
+	/**
+	 * 查询列表
+	 *
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default List<Map<String, Object>> listMaps(T t) {
+		return getBaseMapper().listMaps(t);
+	}
+
+	/**
+	 * 根据 Wrapper 条件,查询全部记录
+	 *
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default List<Object> listObjs(T t) {
+		return getBaseMapper().listObjs(t);
+	}
+
+	/**
+	 * 翻页查询
+	 *
+	 * @param page         翻页对象
+	 * @param queryWrapper 实体对象封装操作类
+	 *                     {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+	 */
+	default <E extends IPage<T>> E page(E page, T t) {
+
+		return getBaseMapper().page(page, t);
+
+	}
+
+}

+ 21 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/service/AbstractServiceImpl.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.framework.mybatis.core.service;
+
+import com.github.yulichang.base.MPJBaseServiceImpl;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.PlusBaseMapperX;
+
+/**
+ * public class AbstractServiceImpl<M , T> extends ServiceImpl<BaseMapper<T>, T>
+ * implements AbstractService<T> IService 实现类( 泛型:M 是 mapper 对象,T 是实体 )
+ * 实现的时候要注意和父类有相同的方式
+ * 
+ * 
+ * 
+ * @author hubin
+ * @since 2018-06-23
+ */
+
+public class AbstractServiceImpl<M extends PlusBaseMapperX<T>, T> extends MPJBaseServiceImpl<M, T>
+		implements AbstractService<T> {
+
+}

+ 75 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/EncryptTypeHandler.java

@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.framework.mybatis.core.type;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.symmetric.AES;
+import cn.hutool.extra.spring.SpringUtil;
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * 字段字段的 TypeHandler 实现类,基于 {@link cn.hutool.crypto.symmetric.AES} 实现
+ * 可通过 jasypt.encryptor.password 配置项,设置密钥
+ *
+ * @author 芋道源码
+ */
+public class EncryptTypeHandler extends BaseTypeHandler<String> {
+
+    private static final String ENCRYPTOR_PROPERTY_NAME = "mybatis-plus.encryptor.password";
+
+    private static AES aes;
+
+    @Override
+    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
+        ps.setString(i, encrypt(parameter));
+    }
+
+    @Override
+    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
+        String value = rs.getString(columnName);
+        return decrypt(value);
+    }
+
+    @Override
+    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+        String value = rs.getString(columnIndex);
+        return decrypt(value);
+    }
+
+    @Override
+    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+        String value = cs.getString(columnIndex);
+        return decrypt(value);
+    }
+
+    private static String decrypt(String value) {
+        if (value == null) {
+            return null;
+        }
+        return getEncryptor().decryptStr(value);
+    }
+
+    public static String encrypt(String rawValue) {
+        if (rawValue == null) {
+            return null;
+        }
+        return getEncryptor().encryptBase64(rawValue);
+    }
+
+    private static AES getEncryptor() {
+        if (aes != null) {
+            return aes;
+        }
+        // 构建 AES
+        String password = SpringUtil.getProperty(ENCRYPTOR_PROPERTY_NAME);
+        Assert.notEmpty(password, "配置项({}) 不能为空", ENCRYPTOR_PROPERTY_NAME);
+        aes = SecureUtil.aes(password.getBytes());
+        return aes;
+    }
+
+}

+ 63 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/IntegerListTypeHandler.java

@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.framework.mybatis.core.type;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * List<Integer> 的类型转换器实现类,对应数据库的 varchar 类型
+ *
+ * @author jason
+ */
+@MappedJdbcTypes(JdbcType.VARCHAR)
+@MappedTypes(List.class)
+public class IntegerListTypeHandler implements TypeHandler<List<Integer>> {
+
+    private static final String COMMA = ",";
+
+    @Override
+    public void setParameter(PreparedStatement ps, int i, List<Integer> strings, JdbcType jdbcType) throws SQLException {
+        ps.setString(i, CollUtil.join(strings, COMMA));
+    }
+
+    @Override
+    public List<Integer> getResult(ResultSet rs, String columnName) throws SQLException {
+        String value = rs.getString(columnName);
+        return getResult(value);
+    }
+
+    @Override
+    public List<Integer> getResult(ResultSet rs, int columnIndex) throws SQLException {
+        String value = rs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    @Override
+    public List<Integer> getResult(CallableStatement cs, int columnIndex) throws SQLException {
+        String value = cs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    private List<Integer> getResult(String value) {
+        if (value == null) {
+            return null;
+        }
+        return splitToInteger(value, COMMA);
+    }
+    public static List<Integer> splitToInteger(String value, CharSequence separator) {
+        int[] integers = StrUtil.splitToInt(value, separator);
+        return Arrays.stream(integers).boxed().collect(Collectors.toList());
+    }
+}

+ 65 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/LongListTypeHandler.java

@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.framework.mybatis.core.type;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * List<Long> 的类型转换器实现类,对应数据库的 varchar 类型
+ *
+ * @author 芋道源码
+ */
+@MappedJdbcTypes(JdbcType.VARCHAR)
+@MappedTypes(List.class)
+public class LongListTypeHandler implements TypeHandler<List<Long>> {
+
+	private static final String COMMA = ",";
+
+	@Override
+	public void setParameter(PreparedStatement ps, int i, List<Long> strings, JdbcType jdbcType) throws SQLException {
+		// 设置占位符
+		ps.setString(i, CollUtil.join(strings, COMMA));
+	}
+
+	@Override
+	public List<Long> getResult(ResultSet rs, String columnName) throws SQLException {
+		String value = rs.getString(columnName);
+		return getResult(value);
+	}
+
+	@Override
+	public List<Long> getResult(ResultSet rs, int columnIndex) throws SQLException {
+		String value = rs.getString(columnIndex);
+		return getResult(value);
+	}
+
+	@Override
+	public List<Long> getResult(CallableStatement cs, int columnIndex) throws SQLException {
+		String value = cs.getString(columnIndex);
+		return getResult(value);
+	}
+
+	private List<Long> getResult(String value) {
+		if (value == null) {
+			return null;
+		}
+		return splitToLong(value, COMMA);
+	}
+
+	private List<Long> splitToLong(String value, CharSequence separator) {
+		long[] longs = StrUtil.splitToLong(value, separator);
+		return Arrays.stream(longs).boxed().collect(Collectors.toList());
+	}
+}

+ 58 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringListTypeHandler.java

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.framework.mybatis.core.type;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * List<String> 的类型转换器实现类,对应数据库的 varchar 类型
+ *
+ * @author 永不言败
+ * @since 2022 3/23 12:50:15
+ */
+@MappedJdbcTypes(JdbcType.VARCHAR)
+@MappedTypes(List.class)
+public class StringListTypeHandler implements TypeHandler<List<String>> {
+
+    private static final String COMMA = ",";
+
+    @Override
+    public void setParameter(PreparedStatement ps, int i, List<String> strings, JdbcType jdbcType) throws SQLException {
+        // 设置占位符
+        ps.setString(i, CollUtil.join(strings, COMMA));
+    }
+
+    @Override
+    public List<String> getResult(ResultSet rs, String columnName) throws SQLException {
+        String value = rs.getString(columnName);
+        return getResult(value);
+    }
+
+    @Override
+    public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
+        String value = rs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    @Override
+    public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
+        String value = cs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    private List<String> getResult(String value) {
+        if (value == null) {
+            return null;
+        }
+        return StrUtil.splitTrim(value, COMMA);
+    }
+}

+ 82 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/ArrayUtils.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.framework.mybatis.core.util;
+
+import java.lang.reflect.Array;
+
+/**
+ * Array 工具类
+ *
+ * @author 芋道源码
+ */
+public class ArrayUtils {
+	/**
+	 * 数组是否为非空
+	 *
+	 * @param <T>   数组元素类型
+	 * @param array 数组
+	 * @return 是否为非空
+	 */
+	public static <T> boolean isNotEmpty(T[] array) {
+		return (null != array && array.length != 0);
+	}
+	/**
+	 * 是否存都不为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素
+	 *
+	 * @param args 被检查的对象,一个或者多个
+	 * @return 是否都不为空
+	 * @since 4.5.18
+	 */
+	public static boolean isAllNotEmpty(Object... args) {
+		return false == hasEmpty(args);
+	}
+	/**
+	 * 是否存在{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素
+	 *
+	 * @param args 被检查对象
+	 * @return 是否存在
+	 * @since 4.5.18
+	 */
+	public static boolean hasEmpty(Object... args) {
+		if (isNotEmpty(args)) {
+			for (Object element : args) {
+				if (ObjectUtil.isEmpty(element)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+	/**
+	 * 对象是否为数组对象
+	 *
+	 * @param obj 对象
+	 * @return 是否为数组对象,如果为{@code null} 返回false
+	 */
+	public static boolean isArray(Object obj) {
+		return null != obj && obj.getClass().isArray();
+	}
+	/**
+	 * 数组是否为空<br>
+	 * 此方法会匹配单一对象,如果此对象为{@code null}则返回true<br>
+	 * 如果此对象为非数组,理解为此对象为数组的第一个元素,则返回false<br>
+	 * 如果此对象为数组对象,数组长度大于0情况下返回false,否则返回true
+	 *
+	 * @param array 数组
+	 * @return 是否为空
+	 */
+	public static boolean isEmpty(Object array) {
+		if (array != null) {
+			if (isArray(array)) {
+				return 0 == Array.getLength(array);
+			}
+			return false;
+		}
+		return true;
+	}
+
+    public static <T> T get(T[] array, int index) {
+        if (null == array || index >= array.length) {
+            return null;
+        }
+        return array[index];
+    }
+}

+ 58 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/CollUtil.java

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.framework.mybatis.core.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
+
+
+
+public class CollUtil {
+	/**
+	 * 集合是否为空
+	 *
+	 * @param collection 集合
+	 * @return 是否为空
+	 */
+	public static boolean isEmpty(Collection<?> collection) {
+		return collection == null || collection.isEmpty();
+	}
+	/**
+	 * Map是否为空
+	 *
+	 * @param map 集合
+	 * @return 是否为空
+	 */
+	public static boolean isEmpty(Map<?, ?> map) {
+		return null == map || map.isEmpty();
+	}
+	/**
+	 * 新建一个ArrayList
+	 *
+	 * @param <T>    集合元素类型
+	 * @param values 数组
+	 * @return ArrayList对象
+	 * @see #toList(Object[])
+	 */
+	
+	public static <T> ArrayList<T> newArrayList() {
+		return new ArrayList<T>();
+	}
+	/**
+	 * 新建一个HashSet
+	 *
+	 * @param <T>      集合元素类型
+	 * @param isSorted 是否有序,有序返回 {@link LinkedHashSet},否则返回 {@link HashSet}
+	 * @param ts       元素数组
+	 * @return HashSet对象
+	 */
+	@SafeVarargs
+	public static <T> HashSet<T> newHashSet(T... ts) {
+		final HashSet<T> set =  new HashSet<>();
+		Collections.addAll(set, ts);
+		return set;
+	}
+	
+}

+ 269 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/CollectionUtils.java

@@ -0,0 +1,269 @@
+package cn.iocoder.yudao.framework.mybatis.core.util;
+
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.Arrays.asList;
+
+/**
+ * Collection 工具类
+ *
+ * @author 芋道源码
+ */
+public class CollectionUtils {
+
+	public static boolean containsAny(Object source, Object... targets) {
+		return asList(targets).contains(source);
+	}
+
+	public static <T> boolean anyMatch(Collection<T> from, Predicate<T> predicate) {
+		return from.stream().anyMatch(predicate);
+	}
+
+	public static <T> List<T> filterList(Collection<T> from, Predicate<T> predicate) {
+		if (CollUtil.isEmpty(from)) {
+			return new ArrayList<>();
+		}
+		return from.stream().filter(predicate).collect(Collectors.toList());
+	}
+
+	public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper) {
+		if (CollUtil.isEmpty(from)) {
+			return new ArrayList<>();
+		}
+		return distinct(from, keyMapper, (t1, t2) -> t1);
+	}
+
+	public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper, BinaryOperator<T> cover) {
+		if (CollUtil.isEmpty(from)) {
+			return new ArrayList<>();
+		}
+		return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());
+	}
+
+	public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
+		if (CollUtil.isEmpty(from)) {
+			return new ArrayList<>();
+		}
+		return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
+	}
+
+	public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
+		if (CollUtil.isEmpty(from)) {
+			return new ArrayList<>();
+		}
+		return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
+	}
+
+	public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
+		return map.values().stream().flatMap(List::stream).collect(Collectors.toList());
+	}
+
+	public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
+		if (CollUtil.isEmpty(from)) {
+			return new HashSet<>();
+		}
+		return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());
+	}
+
+	public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
+		if (CollUtil.isEmpty(from)) {
+			return new HashSet<>();
+		}
+		return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
+	}
+
+	public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) {
+		if (CollUtil.isEmpty(from)) {
+			return new HashMap<>();
+		}
+		return from.stream().filter(filter).collect(Collectors.toMap(keyFunc, v -> v));
+	}
+
+	public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
+		if (CollUtil.isEmpty(from)) {
+			return new HashMap<>();
+		}
+		return convertMap(from, keyFunc, Function.identity());
+	}
+
+	public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc,
+			Supplier<? extends Map<K, T>> supplier) {
+		if (CollUtil.isEmpty(from)) {
+			return supplier.get();
+		}
+		return convertMap(from, keyFunc, Function.identity(), supplier);
+	}
+
+	public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
+		if (CollUtil.isEmpty(from)) {
+			return new HashMap<>();
+		}
+		return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1);
+	}
+
+	public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc,
+			BinaryOperator<V> mergeFunction) {
+		if (CollUtil.isEmpty(from)) {
+			return new HashMap<>();
+		}
+		return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new);
+	}
+
+	public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc,
+			Supplier<? extends Map<K, V>> supplier) {
+		if (CollUtil.isEmpty(from)) {
+			return supplier.get();
+		}
+		return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier);
+	}
+
+	public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc,
+			BinaryOperator<V> mergeFunction, Supplier<? extends Map<K, V>> supplier) {
+		if (CollUtil.isEmpty(from)) {
+			return new HashMap<>();
+		}
+		return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier));
+	}
+
+	public static <T, K> Map<K, List<T>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc) {
+		if (CollUtil.isEmpty(from)) {
+			return new HashMap<>();
+		}
+		return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList())));
+	}
+
+	public static <T, K, V> Map<K, List<V>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc,
+			Function<T, V> valueFunc) {
+		if (CollUtil.isEmpty(from)) {
+			return new HashMap<>();
+		}
+		return from.stream()
+				.collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
+	}
+
+	// 暂时没想好名字,先以 2 结尾噶
+	public static <T, K, V> Map<K, Set<V>> convertMultiMap2(Collection<T> from, Function<T, K> keyFunc,
+			Function<T, V> valueFunc) {
+		if (CollUtil.isEmpty(from)) {
+			return new HashMap<>();
+		}
+		return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
+	}
+
+	/**
+	 * 对比老、新两个列表,找出新增、修改、删除的数据
+	 *
+	 * @param oldList  老列表
+	 * @param newList  新列表
+	 * @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同 注意,same
+	 *                 是通过每个元素的“标识”,判断它们是不是同一个数据
+	 * @return [新增列表、修改列表、删除列表]
+	 */
+	public static <T> List<List<T>> diffList(Collection<T> oldList, Collection<T> newList,
+			BiFunction<T, T, Boolean> sameFunc) {
+		List<T> createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除
+		List<T> updateList = new ArrayList<>();
+		List<T> deleteList = new ArrayList<>();
+
+		// 通过以 oldList 为主遍历,找出 updateList 和 deleteList
+		for (T oldObj : oldList) {
+			// 1. 寻找是否有匹配的
+			T foundObj = null;
+			for (Iterator<T> iterator = createList.iterator(); iterator.hasNext();) {
+				T newObj = iterator.next();
+				// 1.1 不匹配,则直接跳过
+				if (!sameFunc.apply(oldObj, newObj)) {
+					continue;
+				}
+				// 1.2 匹配,则移除,并结束寻找
+				iterator.remove();
+				foundObj = newObj;
+				break;
+			}
+			// 2. 匹配添加到 updateList;不匹配则添加到 deleteList 中
+			if (foundObj != null) {
+				updateList.add(foundObj);
+			} else {
+				deleteList.add(oldObj);
+			}
+		}
+		return asList(createList, updateList, deleteList);
+	}
+
+	public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
+		return org.springframework.util.CollectionUtils.containsAny(source, candidates);
+	}
+
+	public static <T> T findFirst(List<T> from, Predicate<T> predicate) {
+		if (CollUtil.isEmpty(from)) {
+			return null;
+		}
+		return from.stream().filter(predicate).findFirst().orElse(null);
+	}
+
+	public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) {
+		if (CollUtil.isEmpty(from)) {
+			return null;
+		}
+		assert from.size() > 0; // 断言,避免告警
+		T t = from.stream().max(Comparator.comparing(valueFunc)).get();
+		return valueFunc.apply(t);
+	}
+
+	public static <T, V extends Comparable<? super V>> V getMinValue(List<T> from, Function<T, V> valueFunc) {
+		if (CollUtil.isEmpty(from)) {
+			return null;
+		}
+		assert from.size() > 0; // 断言,避免告警
+		T t = from.stream().min(Comparator.comparing(valueFunc)).get();
+		return valueFunc.apply(t);
+	}
+
+	public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
+			BinaryOperator<V> accumulator) {
+		if (CollUtil.isEmpty(from)) {
+			return null;
+		}
+		assert from.size() > 0; // 断言,避免告警
+		return from.stream().map(valueFunc).reduce(accumulator).get();
+	}
+
+	public static <T> void addIfNotNull(Collection<T> coll, T item) {
+		if (item == null) {
+			return;
+		}
+		coll.add(item);
+	}
+
+	public static <T> Collection<T> singleton(T deptId) {
+		return deptId == null ? Collections.emptyList() : Collections.singleton(deptId);
+	}
+
+	public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
+			Function<T, ? extends Stream<? extends U>> func) {
+		if (CollUtil.isEmpty(from)) {
+			return new ArrayList<>();
+		}
+		return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
+	}
+
+	public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,
+			Function<T, ? extends Stream<? extends U>> func) {
+		if (CollUtil.isEmpty(from)) {
+			return new HashSet<>();
+		}
+		return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
+	}
+	/**
+	 * 集合是否为空
+	 *
+	 * @param collection 集合
+	 * @return 是否为空
+	 */
+	public static boolean isEmpty(Collection<?> collection) {
+		return collection == null || collection.isEmpty();
+	}
+}

+ 28 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/IterUtil.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.framework.mybatis.core.util;
+
+import java.util.Iterator;
+
+
+
+public class IterUtil {
+	/**
+	 * Iterable是否为空
+	 *
+	 * @param iterable Iterable对象
+	 * @return 是否为空
+	 */
+	public static boolean isEmpty(Iterable<?> iterable) {
+		return null == iterable || isEmpty(iterable.iterator());
+	}
+
+	/**
+	 * Iterator是否为空
+	 *
+	 * @param Iterator Iterator对象
+	 * @return 是否为空
+	 */
+	public static boolean isEmpty(Iterator<?> Iterator) {
+		return null == Iterator || false == Iterator.hasNext();
+	}
+	
+}

+ 42 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/JdbcUtils.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.framework.mybatis.core.util;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+
+/**
+ * JDBC 工具类
+ *
+ * @author 芋道源码
+ */
+public class JdbcUtils {
+
+    /**
+     * 判断连接是否正确
+     *
+     * @param url      数据源连接
+     * @param username 账号
+     * @param password 密码
+     * @return 是否正确
+     */
+    public static boolean isConnectionOK(String url, String username, String password) {
+        try (Connection ignored = DriverManager.getConnection(url, username, password)) {
+            return true;
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+    /**
+     * 获得 URL 对应的 DB 类型
+     *
+     * @param url URL
+     * @return DB 类型
+     */
+    public static DbType getDbType(String url) {
+        String name = com.alibaba.druid.util.JdbcUtils.getDbType(url, null);
+        return DbType.getDbType(name);
+    }
+
+}

+ 68 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MapUtils.java

@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.framework.mybatis.core.util;
+
+
+
+
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Map 工具类
+ *
+ * @author 芋道源码
+ */
+public class MapUtils {
+	/**
+	 * Map是否为空
+	 *
+	 * @param map 集合
+	 * @return 是否为空
+	 */
+	public static boolean isEmpty(Map<?, ?> map) {
+		return null == map || map.isEmpty();
+	}
+//    /**
+//     * 从哈希表表中,获得 keys 对应的所有 value 数组
+//     *
+//     * @param multimap 哈希表
+//     * @param keys keys
+//     * @return value 数组
+//     */
+//    public static <K, V> List<V> getList(Multimap<K, V> multimap, Collection<K> keys) {
+//        List<V> result = new ArrayList<>();
+//        keys.forEach(k -> {
+//            Collection<V> values = multimap.get(k);
+//            if (CollectionUtil.isEmpty(values)) {
+//                return;
+//            }
+//            result.addAll(values);
+//        });
+//        return result;
+//    }
+
+    /**
+     * 从哈希表查找到 key 对应的 value,然后进一步处理
+     * 注意,如果查找到的 value 为 null 时,不进行处理
+     *
+     * @param map 哈希表
+     * @param key key
+     * @param consumer 进一步处理的逻辑
+     */
+    public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) {
+        if (CollUtil.isEmpty(map)) {
+            return;
+        }
+        V value = map.get(key);
+        if (value == null) {
+            return;
+        }
+        consumer.accept(value);
+    }
+
+//    public static <K, V> Map<K, V> convertMap(List<KeyValue<K, V>> keyValues) {
+//        Map<K, V> map = Maps.newLinkedHashMapWithExpectedSize(keyValues.size());
+//        keyValues.forEach(keyValue -> map.put(keyValue.getKey(), keyValue.getValue()));
+//        return map;
+//    }
+
+}

+ 89 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java

@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.framework.mybatis.core.util;
+
+
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.SortingField;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import net.sf.jsqlparser.expression.Alias;
+import net.sf.jsqlparser.schema.Column;
+import net.sf.jsqlparser.schema.Table;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * MyBatis 工具类
+ */
+public class MyBatisUtils {
+
+    private static final String MYSQL_ESCAPE_CHARACTER = "`";
+
+    public static <T> Page<T> buildPage(PageParam pageParam) {
+        return buildPage(pageParam, null);
+    }
+
+    public static <T> Page<T> buildPage(PageParam pageParam, Collection<SortingField> sortingFields) {
+        // 页码 + 数量
+        Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
+        // 排序字段
+        if (!CollectionUtils.isEmpty(sortingFields)) {
+            page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) ?
+                    OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))
+                    .collect(Collectors.toList()));
+        }
+        return page;
+    }
+
+    /**
+     * 将拦截器添加到链中
+     * 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
+     *
+     * @param interceptor 链
+     * @param inner 拦截器
+     * @param index 位置
+     */
+    public static void addInterceptor(MybatisPlusInterceptor interceptor, InnerInterceptor inner, int index) {
+        List<InnerInterceptor> inners = new ArrayList<>(interceptor.getInterceptors());
+        inners.add(index, inner);
+        interceptor.setInterceptors(inners);
+    }
+
+    /**
+     * 获得 Table 对应的表名
+     *
+     * 兼容 MySQL 转义表名 `t_xxx`
+     *
+     * @param table 表
+     * @return 去除转移字符后的表名
+     */
+    public static String getTableName(Table table) {
+        String tableName = table.getName();
+        if (tableName.startsWith(MYSQL_ESCAPE_CHARACTER) && tableName.endsWith(MYSQL_ESCAPE_CHARACTER)) {
+            tableName = tableName.substring(1, tableName.length() - 1);
+        }
+        return tableName;
+    }
+
+    /**
+     * 构建 Column 对象
+     *
+     * @param tableName 表名
+     * @param tableAlias 别名
+     * @param column 字段名
+     * @return Column 对象
+     */
+    public static Column buildColumn(String tableName, Alias tableAlias, String column) {
+        if (tableAlias != null) {
+            tableName = tableAlias.getName();
+        }
+        return new Column(tableName + StringPool.DOT + column);
+    }
+
+}

+ 79 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/ObjectUtil.java

@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.framework.mybatis.core.util;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * 对象工具类,包括判空、克隆、序列化等操作
+ *
+ * @author Looly
+ */
+public class ObjectUtil {
+	/**
+	 * 是否全都不为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素
+	 *
+	 * @param objs 被检查的对象,一个或者多个
+	 * @return 是否都不为空
+	 */
+	public static boolean isAllNotEmpty(Object... objs) {
+		return ArrayUtils.isAllNotEmpty(objs);
+	}
+
+	
+
+	/**
+	 * 判断指定对象是否为非空,支持:
+	 *
+	 * <pre>
+	 * 1. CharSequence
+	 * 2. Map
+	 * 3. Iterable
+	 * 4. Iterator
+	 * 5. Array
+	 * </pre>
+	 *
+	 * @param obj 被判断的对象
+	 * @return 是否为空,如果类型不支持,返回true
+	 * @since 4.5.7
+	 */
+	public static boolean isNotEmpty(Object obj) {
+		return false == isEmpty(obj);
+	}
+
+	/**
+	 * 判断指定对象是否为空,支持:
+	 *
+	 * <pre>
+	 * 1. CharSequence
+	 * 2. Map
+	 * 3. Iterable
+	 * 4. Iterator
+	 * 5. Array
+	 * </pre>
+	 *
+	 * @param obj 被判断的对象
+	 * @return 是否为空,如果类型不支持,返回false
+	 * @since 4.5.7
+	 */
+	@SuppressWarnings("rawtypes")
+	public static boolean isEmpty(Object obj) {
+		if (null == obj) {
+			return true;
+		}
+
+		if (obj instanceof CharSequence) {
+			return StrUtils.isEmpty((CharSequence) obj);
+		} else if (obj instanceof Map) {
+			return MapUtils.isEmpty((Map) obj);
+		} else if (obj instanceof Iterable) {
+			return IterUtil.isEmpty((Iterable) obj);
+		} else if (obj instanceof Iterator) {
+			return IterUtil.isEmpty((Iterator) obj);
+		} else if (ArrayUtils.isArray(obj)) {
+			return ArrayUtils.isEmpty(obj);
+		}
+
+		return false;
+	}
+
+}

+ 16 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/SetUtils.java

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.framework.mybatis.core.util;
+
+import java.util.Set;
+
+/**
+ * Set 工具类
+ *
+ * @author 芋道源码
+ */
+public class SetUtils {
+	
+	@SafeVarargs
+	public static <T> Set<T> asSet(T... objs) {
+		return CollUtil.newHashSet(objs);
+	}
+}

+ 129 - 0
src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/StrUtils.java

@@ -0,0 +1,129 @@
+package cn.iocoder.yudao.framework.mybatis.core.util;
+
+/**
+ * 字符串工具类
+ *
+ * @author 芋道源码
+ */
+public class StrUtils {
+	public static final int INDEX_NOT_FOUND = -1;
+
+	/**
+	 * 字符串常量:{@code "null"} <br>
+	 * 注意:{@code "null" != null}
+	 */
+	public static final String NULL = "null";
+
+	/**
+	 * 字符串常量:空字符串 {@code ""}
+	 */
+	public static final String EMPTY = "";
+
+	/**
+	 * 字符串常量:空格符 {@code " "}
+	 */
+	public static final String SPACE = " ";
+
+	public static boolean isEmpty(CharSequence str) {
+		return str == null || str.length() == 0;
+	}
+
+	public static boolean isNotEmpty(CharSequence str) {
+		return false == isEmpty(str);
+	}
+
+	/**
+	 * 获取字符串的长度,如果为null返回0
+	 *
+	 * @param cs a 字符串
+	 * @return 字符串的长度,如果为null返回0
+	 * @since 4.3.2
+	 */
+	public static int length(CharSequence cs) {
+		return cs == null ? 0 : cs.length();
+	}
+
+	/**
+	 * 是否以指定字符串结尾
+	 *
+	 * @param str    被监测字符串
+	 * @param suffix 结尾字符串
+	 * @return 是否以指定字符串结尾
+	 */
+	public static boolean endWith(CharSequence str, CharSequence suffix) {
+		return endWith(str, suffix, false);
+	}
+
+	/**
+	 * 是否以指定字符串结尾<br>
+	 * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false
+	 *
+	 * @param str        被监测字符串
+	 * @param suffix     结尾字符串
+	 * @param ignoreCase 是否忽略大小写
+	 * @return 是否以指定字符串结尾
+	 */
+	public static boolean endWith(CharSequence str, CharSequence suffix, boolean ignoreCase) {
+		return endWith(str, suffix, ignoreCase, false);
+	}
+
+	/**
+	 * 是否以指定字符串结尾<br>
+	 * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false
+	 *
+	 * @param str          被监测字符串
+	 * @param suffix       结尾字符串
+	 * @param ignoreCase   是否忽略大小写
+	 * @param ignoreEquals 是否忽略字符串相等的情况
+	 * @return 是否以指定字符串结尾
+	 * @since 5.8.0
+	 */
+	public static boolean endWith(CharSequence str, CharSequence suffix, boolean ignoreCase, boolean ignoreEquals) {
+		if (null == str || null == suffix) {
+			if (ignoreEquals) {
+				return false;
+			}
+			return null == str && null == suffix;
+		}
+
+		final int strOffset = str.length() - suffix.length();
+		boolean isEndWith = str.toString().regionMatches(ignoreCase, strOffset, suffix.toString(), 0, suffix.length());
+
+		if (isEndWith) {
+			return (false == ignoreEquals) || (false == equals(str, suffix, ignoreCase));
+		}
+		return false;
+	}
+
+	/**
+	 * 比较两个字符串是否相等,规则如下
+	 * <ul>
+	 * <li>str1和str2都为{@code null}</li>
+	 * <li>忽略大小写使用{@link String#equalsIgnoreCase(String)}判断相等</li>
+	 * <li>不忽略大小写使用{@link String#contentEquals(CharSequence)}判断相等</li>
+	 * </ul>
+	 *
+	 * @param str1       要比较的字符串1
+	 * @param str2       要比较的字符串2
+	 * @param ignoreCase 是否忽略大小写
+	 * @return 如果两个字符串相同,或者都是{@code null},则返回{@code true}
+	 * @since 3.2.0
+	 */
+	public static boolean equals(CharSequence str1, CharSequence str2, boolean ignoreCase) {
+		if (null == str1) {
+			// 只有两个都为null才判断相等
+			return str2 == null;
+		}
+		if (null == str2) {
+			// 字符串2空,字符串1非空,直接false
+			return false;
+		}
+
+		if (ignoreCase) {
+			return str1.toString().equalsIgnoreCase(str2.toString());
+		} else {
+			return str1.toString().contentEquals(str2);
+		}
+	}
+
+}

+ 2 - 0
src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.env.EnvironmentPostProcessor=\
+  cn.iocoder.yudao.framework.mybatis.config.IdTypeEnvironmentPostProcessor

+ 2 - 0
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,2 @@
+cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration
+cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration