common-mybatis

This commit is contained in:
wol
2025-08-11 23:01:07 +08:00
parent b92f6a6f53
commit e04a663fdb
17 changed files with 555 additions and 224 deletions

View File

@@ -13,6 +13,7 @@
<module>wol-common-box</module>
<module>wol-common-core</module>
<module>wol-common-doc</module>
<module>wol-common-mybatis</module>
</modules>
<packaging>pom</packaging>

View File

@@ -27,6 +27,11 @@
<artifactId>wol-common-doc</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-common-mybatis</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>

View File

@@ -1,46 +0,0 @@
package com.agileboot.common.core.core.base;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Entity基类
*
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class BaseEntity<T extends Model<?>> extends Model<T> {
@ApiModelProperty("创建者ID")
@TableField(value = "creator_id", fill = FieldFill.INSERT)
private Long creatorId;
@ApiModelProperty("创建时间")
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
@ApiModelProperty("更新者ID")
@TableField(value = "updater_id", fill = FieldFill.UPDATE, updateStrategy = FieldStrategy.NOT_NULL)
private Long updaterId;
@ApiModelProperty("更新时间")
@TableField(value = "update_time", fill = FieldFill.UPDATE)
private Date updateTime;
/**
* deleted字段请在数据库中 设置为tinyInt 并且非null 默认值为0
*/
@ApiModelProperty("删除标志0代表存在 1代表删除")
@TableField("deleted")
@TableLogic
private Boolean deleted;
}

View File

@@ -1,44 +0,0 @@
package com.agileboot.common.core.core.page;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.validation.constraints.Max;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public abstract class AbstractPageQuery<T> extends AbstractQuery<T> {
/**
* 最大分页页数
*/
public static final int MAX_PAGE_NUM = 200;
/**
* 单页最大大小
*/
public static final int MAX_PAGE_SIZE = 500;
/**
* 默认分页页数
*/
public static final int DEFAULT_PAGE_NUM = 1;
/**
* 默认分页大小
*/
public static final int DEFAULT_PAGE_SIZE = 10;
@Max(MAX_PAGE_NUM)
protected Integer pageNum;
@Max(MAX_PAGE_SIZE)
protected Integer pageSize;
public Page<T> toPage() {
pageNum = ObjectUtil.defaultIfNull(pageNum, DEFAULT_PAGE_NUM);
pageSize = ObjectUtil.defaultIfNull(pageSize, DEFAULT_PAGE_SIZE);
return new Page<>(pageNum, pageSize);
}
}

View File

@@ -1,90 +0,0 @@
package com.agileboot.common.core.core.page;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.utils.time.DatePickUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import java.util.Date;
import lombok.Data;
/**
* 如果是简单的排序 和 时间范围筛选 可以使用内置的这几个字段
* @author valarchie
*/
@Data
public abstract class AbstractQuery<T> {
protected String orderColumn;
protected String orderDirection;
protected String timeRangeColumn;
@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd")
private Date beginTime;
@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd")
private Date endTime;
private static final String ASC = "ascending";
private static final String DESC = "descending";
/**
* 生成query conditions
*
* @return 添加条件后的QueryWrapper
*/
public QueryWrapper<T> toQueryWrapper() {
QueryWrapper<T> queryWrapper = addQueryCondition();
addSortCondition(queryWrapper);
addTimeCondition(queryWrapper);
return queryWrapper;
}
public abstract QueryWrapper<T> addQueryCondition();
public void addSortCondition(QueryWrapper<T> queryWrapper) {
if (queryWrapper == null || StrUtil.isEmpty(orderColumn)) {
return;
}
Boolean sortDirection = convertSortDirection();
if (sortDirection != null) {
queryWrapper.orderBy(StrUtil.isNotEmpty(orderColumn), sortDirection,
StrUtil.toUnderlineCase(orderColumn));
}
}
public void addTimeCondition(QueryWrapper<T> queryWrapper) {
if (queryWrapper != null
&& StrUtil.isNotEmpty(this.timeRangeColumn)) {
queryWrapper
.ge(beginTime != null, StrUtil.toUnderlineCase(timeRangeColumn),
DatePickUtil.getBeginOfTheDay(beginTime))
.le(endTime != null, StrUtil.toUnderlineCase(timeRangeColumn), DatePickUtil.getEndOfTheDay(endTime));
}
}
/**
* 获取前端传来的排序方向 转换成MyBatisPlus所需的排序参数 boolean=isAsc
* @return 排序顺序, null为无排序
*/
public Boolean convertSortDirection() {
Boolean isAsc = null;
if (StrUtil.isEmpty(this.orderDirection)) {
return isAsc;
}
if (ASC.equals(this.orderDirection)) {
isAsc = true;
}
if (DESC.equals(this.orderDirection)) {
isAsc = false;
}
return isAsc;
}
}

View File

@@ -1,38 +0,0 @@
package com.agileboot.common.core.core.page;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import lombok.Data;
/**
* 分页模型类
* @author valarchie
*/
@Data
public class PageDTO<T> {
/**
* 总记录数
*/
private Long total;
/**
* 列表数据
*/
private List<T> rows;
public PageDTO(List<T> list) {
this.rows = list;
this.total = (long) list.size();
}
public PageDTO(Page<T> page) {
this.rows = page.getRecords();
this.total = page.getTotal();
}
public PageDTO(List<T> list, Long count) {
this.rows = list;
this.total = count;
}
}

View File

@@ -0,0 +1,67 @@
package com.agileboot.common.core.exception;
import cn.hutool.core.text.StrFormatter;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* 业务异常(支持占位符 {}
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public final class ServiceException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细,内部调试错误
*/
private String detailMessage;
public ServiceException(String message) {
this.message = message;
}
public ServiceException(String message, Integer code) {
this.message = message;
this.code = code;
}
public ServiceException(String message, Object... args) {
this.message = StrFormatter.format(message, args);
}
@Override
public String getMessage() {
return message;
}
public ServiceException setMessage(String message) {
this.message = message;
return this;
}
public ServiceException setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
return this;
}
}

View File

@@ -0,0 +1,31 @@
package com.agileboot.common.core.factory;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
import java.io.IOException;
/**
* yml 配置源工厂
*
* @author Lion Li
*/
public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
String sourceName = resource.getResource().getFilename();
if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return new PropertiesPropertySource(sourceName, factory.getObject());
}
return super.createPropertySource(name, resource);
}
}

View File

@@ -0,0 +1,56 @@
package com.agileboot.common.core.utils.sql;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
/**
* sql操作工具类
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SqlUtil {
/**
* 定义常用的 sql关键字
*/
public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
/**
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
*/
public static final String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
/**
* 检查字符,防止注入绕过
*/
public static String escapeOrderBySql(String value) {
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
throw new IllegalArgumentException("参数不符合规范,不能进行查询");
}
return value;
}
/**
* 验证 order by 语法是否符合规范
*/
public static boolean isValidOrderBySql(String value) {
return value.matches(SQL_PATTERN);
}
/**
* SQL关键字检查
*/
public static void filterKeyword(String value) {
if (StringUtils.isEmpty(value)) {
return;
}
String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
for (String sqlKeyword : sqlKeywords) {
if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) {
throw new IllegalArgumentException("参数存在SQL注入风险");
}
}
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.agileboot</groupId>
<artifactId>agileboot-common</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>wol-common-mybatis</artifactId>
<dependencies>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,24 @@
package com.agileboot.common.mybatis.config;
import com.agileboot.common.core.factory.YmlPropertySourceFactory;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
@AutoConfiguration
@MapperScan("${mybatis-plus.mapperPackage}")
@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
public class MybatisPlusConfiguration {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}

View File

@@ -0,0 +1,70 @@
package com.agileboot.common.mybatis.core.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Entity基类
*
* @author Lion Li
*/
@Data
public class BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 搜索值
*/
@JsonIgnore
@TableField(exist = false)
private String searchValue;
/**
* 创建部门
*/
@TableField(fill = FieldFill.INSERT)
private Long createDept;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 请求参数
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@TableField(exist = false)
private Map<String, Object> params = new HashMap<>();
}

View File

@@ -0,0 +1,130 @@
package com.agileboot.common.mybatis.core.page;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.exception.ServiceException;
import com.agileboot.common.core.utils.sql.SqlUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 分页查询实体类
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class PageQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 分页大小
*/
private Integer pageSize;
/**
* 当前页数
*/
private Integer pageNum;
/**
* 排序列
*/
private String orderByColumn;
/**
* 排序的方向desc或者asc
*/
private String isAsc;
/**
* 当前记录起始索引 默认值
*/
public static final int DEFAULT_PAGE_NUM = 1;
/**
* 每页显示记录数 默认值 默认查全部
*/
public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
/**
* 构建分页对象
*/
public <T> Page<T> build() {
Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
if (pageNum <= 0) {
pageNum = DEFAULT_PAGE_NUM;
}
Page<T> page = new Page<>(pageNum, pageSize);
List<OrderItem> orderItems = buildOrderItem();
if (CollUtil.isNotEmpty(orderItems)) {
page.addOrder(orderItems);
}
return page;
}
/**
* 构建排序
* <p>
* 支持的用法如下:
* {isAsc:"asc",orderByColumn:"id"} order by id asc
* {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
* {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
* {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
*/
private List<OrderItem> buildOrderItem() {
if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) {
return null;
}
String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);
orderBy = StrUtil.toUnderlineCase(orderBy);
// 兼容前端排序类型
isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
String[] orderByArr = orderBy.split(",");
String[] isAscArr = isAsc.split(",");
if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
throw new ServiceException("排序参数有误");
}
List<OrderItem> list = new ArrayList<>();
// 每个字段各自排序
for (int i = 0; i < orderByArr.length; i++) {
String orderByStr = orderByArr[i];
String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];
if ("asc".equals(isAscStr)) {
list.add(OrderItem.asc(orderByStr));
} else if ("desc".equals(isAscStr)) {
list.add(OrderItem.desc(orderByStr));
} else {
throw new ServiceException("排序参数有误");
}
}
return list;
}
@JsonIgnore
public Integer getFirstNum() {
return (pageNum - 1) * pageSize;
}
public PageQuery(Integer pageSize, Integer pageNum) {
this.pageSize = pageSize;
this.pageNum = pageNum;
}
}

View File

@@ -0,0 +1,107 @@
package com.agileboot.common.mybatis.core.page;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 表格分页数据对象
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class TableDataInfo<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 总记录数
*/
private long total;
/**
* 列表数据
*/
private List<T> rows;
/**
* 消息状态码
*/
private int code;
/**
* 消息内容
*/
private String msg;
/**
* 分页
*
* @param list 列表数据
* @param total 总记录数
*/
public TableDataInfo(List<T> list, long total) {
this.rows = list;
this.total = total;
this.code = HttpStatus.HTTP_OK;
this.msg = "查询成功";
}
/**
* 根据分页对象构建表格分页数据对象
*/
public static <T> TableDataInfo<T> build(IPage<T> page) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(page.getRecords());
rspData.setTotal(page.getTotal());
return rspData;
}
/**
* 根据数据列表构建表格分页数据对象
*/
public static <T> TableDataInfo<T> build(List<T> list) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(list.size());
return rspData;
}
/**
* 构建表格分页数据对象
*/
public static <T> TableDataInfo<T> build() {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
return rspData;
}
/**
* 根据原始数据列表和分页参数,构建表格分页数据对象(用于假分页)
*
* @param list 原始数据列表(全部数据)
* @param page 分页参数对象(包含当前页码、每页大小等)
* @return 构造好的分页结果 TableDataInfo<T>
*/
public static <T> TableDataInfo<T> build(List<T> list, IPage<T> page) {
if (CollUtil.isEmpty(list)) {
return TableDataInfo.build();
}
List<T> pageList = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), list);
return new TableDataInfo<>(pageList, list.size());
}
}

View File

@@ -0,0 +1 @@
com.agileboot.common.mybatis.config.MybatisPlusConfiguration

View File

@@ -0,0 +1,28 @@
# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
# 启动时是否检查 MyBatis XML 文件的存在,默认不检查
checkConfigLocation: false
configuration:
# 自动驼峰命名规则camel case映射
mapUnderscoreToCamelCase: true
# MyBatis 自动映射策略
# NONE不启用 PARTIAL只对非嵌套 resultMap 自动映射 FULL对所有 resultMap 自动映射
autoMappingBehavior: FULL
# MyBatis 自动映射时未知列或未知属性处理策
# NONE不做处理 WARNING打印相关警告 FAILING抛出异常和详细信息
autoMappingUnknownColumnBehavior: NONE
# 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
# 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
global-config:
banner: true # 是否打印 Logo banner
dbConfig:
idType: ASSIGN_ID # 主键类型: AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
logicDeleteValue: 1 # 逻辑已删除值(框架表均使用此值 禁止随意修改)
logicNotDeleteValue: 0 # 逻辑未删除值
insertStrategy: NOT_NULL
updateStrategy: NOT_NULL
whereStrategy: NOT_NULL

16
pom.xml
View File

@@ -39,7 +39,6 @@
<org.lionsoul.version>2.6.5</org.lionsoul.version>
<poi.version>4.1.2</poi.version>
<io.swagger.version>1.6.8</io.swagger.version>
<mybatis-plus.version>3.5.2</mybatis-plus.version>
<!-- 插件版本 -->
@@ -95,9 +94,14 @@
<!-- </dependency>-->
<!-- mybatis plus 主依赖 -->
<!-- <dependency>-->
<!-- <groupId>com.baomidou</groupId>-->
<!-- <artifactId>mybatis-plus-boot-starter</artifactId>-->
<!-- <version>${mybatis-plus.version}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- &lt;!&ndash; mybatis plus 代码生成器依赖 &ndash;&gt;-->
@@ -293,10 +297,10 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.baomidou</groupId>-->
<!-- <artifactId>mybatis-plus-boot-starter</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>