refactor(data): mybatis-plus => mp,mybatis-flex => mf

This commit is contained in:
2024-08-29 19:47:56 +08:00
parent 784a56fd4f
commit 5e0eea2ed8
47 changed files with 64 additions and 64 deletions

View File

@@ -0,0 +1,47 @@
<?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>top.continew</groupId>
<artifactId>continew-starter-data</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-data-mp</artifactId>
<description>ContiNew Starter 数据访问模块 - MyBatis Plus</description>
<dependencies>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- Dynamic Datasource基于 Spring Boot 的快速集成多数据源的启动器) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
</dependency>
<!-- P6SpySQL 性能分析组件) -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
</dependency>
<!-- 数据访问模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-data-core</artifactId>
</dependency>
<!-- CosId通用、灵活、高性能的分布式 ID 生成器) -->
<dependency>
<groupId>me.ahoo.cosid</groupId>
<artifactId>cosid-spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import top.continew.starter.core.constant.PropertiesConstants;
import java.lang.annotation.*;
/**
* 是否启用数据权限注解
*
* @author Charles7c
* @since 1.1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@ConditionalOnProperty(prefix = "mybatis-plus.extension.data-permission", name = PropertiesConstants.ENABLED, havingValue = "true")
public @interface ConditionalOnEnabledDataPermission {
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.autoconfigure;
import com.baomidou.mybatisplus.annotation.DbType;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import top.continew.starter.data.mp.autoconfigure.idgenerator.MyBatisPlusIdGeneratorProperties;
/**
* MyBatis Plus 扩展配置属性
*
* @author Charles7c
* @since 1.0.0
*/
@ConfigurationProperties("mybatis-plus.extension")
public class MyBatisPlusExtensionProperties {
/**
* 是否启用扩展
*/
private boolean enabled = false;
/**
* Mapper 接口扫描包配置时必须使用mapper-package 键名)
* <p>
* e.g. com.example.**.mapper
* </p>
*/
private String mapperPackage;
/**
* ID 生成器
*/
@NestedConfigurationProperty
private MyBatisPlusIdGeneratorProperties idGenerator;
/**
* 数据权限插件配置
*/
private DataPermissionProperties dataPermission;
/**
* 分页插件配置
*/
private PaginationProperties pagination;
/**
* 启用防全表更新与删除插件
*/
private boolean blockAttackPluginEnabled = true;
/**
* 数据权限插件配置属性
*/
public static class DataPermissionProperties {
/**
* 是否启用数据权限插件
*/
private boolean enabled = false;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
/**
* 分页插件配置属性
*/
public static class PaginationProperties {
/**
* 是否启用分页插件
*/
private boolean enabled = true;
/**
* 数据库类型
*/
private DbType dbType;
/**
* 是否溢出处理
*/
private boolean overflow = false;
/**
* 单页分页条数限制(默认:-1 表示无限制)
*/
private Long maxLimit = -1L;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public DbType getDbType() {
return dbType;
}
public void setDbType(DbType dbType) {
this.dbType = dbType;
}
public boolean isOverflow() {
return overflow;
}
public void setOverflow(boolean overflow) {
this.overflow = overflow;
}
public Long getMaxLimit() {
return maxLimit;
}
public void setMaxLimit(Long maxLimit) {
this.maxLimit = maxLimit;
}
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getMapperPackage() {
return mapperPackage;
}
public void setMapperPackage(String mapperPackage) {
this.mapperPackage = mapperPackage;
}
public MyBatisPlusIdGeneratorProperties getIdGenerator() {
return idGenerator;
}
public void setIdGenerator(MyBatisPlusIdGeneratorProperties idGenerator) {
this.idGenerator = idGenerator;
}
public DataPermissionProperties getDataPermission() {
return dataPermission;
}
public void setDataPermission(DataPermissionProperties dataPermission) {
this.dataPermission = dataPermission;
}
public PaginationProperties getPagination() {
return pagination;
}
public void setPagination(PaginationProperties pagination) {
this.pagination = pagination;
}
public boolean isBlockAttackPluginEnabled() {
return blockAttackPluginEnabled;
}
public void setBlockAttackPluginEnabled(boolean blockAttackPluginEnabled) {
this.blockAttackPluginEnabled = blockAttackPluginEnabled;
}
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.autoconfigure;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import jakarta.annotation.PostConstruct;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.GeneralPropertySourceFactory;
import top.continew.starter.data.mp.autoconfigure.idgenerator.MyBatisPlusIdGeneratorConfiguration;
import top.continew.starter.data.mp.datapermission.DataPermissionFilter;
import top.continew.starter.data.mp.datapermission.DataPermissionHandlerImpl;
import top.continew.starter.data.mp.handler.MybatisBaseEnumTypeHandler;
/**
* MyBatis Plus 自动配置
*
* @author Charles7c
* @since 1.0.0
*/
@AutoConfiguration
@MapperScan("${mybatis-plus.extension.mapper-package}")
@EnableTransactionManagement(proxyTargetClass = true)
@EnableConfigurationProperties(MyBatisPlusExtensionProperties.class)
@ConditionalOnProperty(prefix = "mybatis-plus.extension", name = PropertiesConstants.ENABLED, havingValue = "true")
@PropertySource(value = "classpath:default-data-mybatis-plus.yml", factory = GeneralPropertySourceFactory.class)
public class MybatisPlusAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(MybatisPlusAutoConfiguration.class);
/**
* MyBatis Plus 配置
*
* @since 2.4.0
*/
@Bean
public MybatisPlusPropertiesCustomizer mybatisPlusPropertiesCustomizer() {
return properties -> properties.getConfiguration().setDefaultEnumTypeHandler(MybatisBaseEnumTypeHandler.class);
}
/**
* MyBatis Plus 插件配置
*/
@Bean
@ConditionalOnMissingBean
public MybatisPlusInterceptor mybatisPlusInterceptor(MyBatisPlusExtensionProperties properties) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 数据权限插件
MyBatisPlusExtensionProperties.DataPermissionProperties dataPermissionProperties = properties
.getDataPermission();
if (null != dataPermissionProperties && dataPermissionProperties.isEnabled()) {
interceptor.addInnerInterceptor(new DataPermissionInterceptor(SpringUtil
.getBean(DataPermissionHandler.class)));
}
// 分页插件
MyBatisPlusExtensionProperties.PaginationProperties paginationProperties = properties.getPagination();
if (null != paginationProperties && paginationProperties.isEnabled()) {
interceptor.addInnerInterceptor(this.paginationInnerInterceptor(paginationProperties));
}
// 防全表更新与删除插件
if (properties.isBlockAttackPluginEnabled()) {
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
}
return interceptor;
}
/**
* ID 生成器配置
*/
@Configuration
@Import({MyBatisPlusIdGeneratorConfiguration.Default.class, MyBatisPlusIdGeneratorConfiguration.CosId.class,
MyBatisPlusIdGeneratorConfiguration.Custom.class})
protected static class MyBatisPlusIdGeneratorAutoConfiguration {}
/**
* 数据权限处理器
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledDataPermission
public DataPermissionHandler dataPermissionHandler(DataPermissionFilter dataPermissionFilter) {
return new DataPermissionHandlerImpl(dataPermissionFilter);
}
/**
* 分页插件配置(<a href="https://baomidou.com/pages/97710a/#paginationinnerinterceptor">PaginationInnerInterceptor</a>
*/
private PaginationInnerInterceptor paginationInnerInterceptor(MyBatisPlusExtensionProperties.PaginationProperties paginationProperties) {
// 对于单一数据库类型来说,都建议配置该值,避免每次分页都去抓取数据库类型
PaginationInnerInterceptor paginationInnerInterceptor = null != paginationProperties.getDbType()
? new PaginationInnerInterceptor(paginationProperties.getDbType())
: new PaginationInnerInterceptor();
paginationInnerInterceptor.setOverflow(paginationProperties.isOverflow());
paginationInnerInterceptor.setMaxLimit(paginationProperties.getMaxLimit());
return paginationInnerInterceptor;
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'MyBatis Plus' completed initialization.");
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.autoconfigure.idgenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import me.ahoo.cosid.snowflake.SnowflakeId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
/**
* MyBatis Plus ID 生成器 - CosId
*
* @author Charles7c
* @since 1.4.0
*/
public class MyBatisPlusCosIdIdentifierGenerator implements IdentifierGenerator {
@Qualifier("__share__SnowflakeId")
@Lazy
@Autowired
private SnowflakeId snowflakeId;
@Override
public Number nextId(Object entity) {
return snowflakeId.generate();
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.autoconfigure.idgenerator;
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import me.ahoo.cosid.IdGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ResolvableType;
/**
* MyBatis ID 生成器配置
*
* @author Charles7c
* @since 1.4.0
*/
public class MyBatisPlusIdGeneratorConfiguration {
private static final Logger log = LoggerFactory.getLogger(MyBatisPlusIdGeneratorConfiguration.class);
private MyBatisPlusIdGeneratorConfiguration() {
}
/**
* 自定义 ID 生成器-默认(雪花算法,使用网卡信息绑定雪花生成器,防止集群雪花 ID 重复)
*/
@ConditionalOnMissingBean(IdentifierGenerator.class)
@ConditionalOnProperty(name = "mybatis-plus.extension.id-generator.type", havingValue = "default", matchIfMissing = true)
public static class Default {
static {
log.debug("[ContiNew Starter] - Auto Configuration 'MyBatis Plus-IdGenerator-Default' completed initialization.");
}
@Bean
public IdentifierGenerator identifierGenerator() {
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
}
}
/**
* 自定义 ID 生成器-CosId
*/
@ConditionalOnMissingBean(IdentifierGenerator.class)
@ConditionalOnClass(IdGenerator.class)
@ConditionalOnProperty(name = "mybatis-plus.extension.id-generator.type", havingValue = "cosid")
public static class CosId {
static {
log.debug("[ContiNew Starter] - Auto Configuration 'MyBatis Plus-IdGenerator-CosId' completed initialization.");
}
@Bean
public IdentifierGenerator identifierGenerator() {
return new MyBatisPlusCosIdIdentifierGenerator();
}
}
/**
* 自定义 ID 生成器
*/
@ConditionalOnProperty(name = "mybatis-plus.extension.id-generator.type", havingValue = "custom")
public static class Custom {
@Bean
@ConditionalOnMissingBean
public IdentifierGenerator identifierGenerator() {
if (log.isErrorEnabled()) {
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
.forClass(IdentifierGenerator.class));
}
throw new NoSuchBeanDefinitionException(IdentifierGenerator.class);
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.autoconfigure.idgenerator;
import top.continew.starter.data.mp.enums.MyBatisPlusIdGeneratorType;
/**
* MyBatis ID 生成器配置属性
*
* @author Charles7c
* @since 1.4.0
*/
public class MyBatisPlusIdGeneratorProperties {
/**
* ID 生成器类型
*/
private MyBatisPlusIdGeneratorType type = MyBatisPlusIdGeneratorType.DEFAULT;
public MyBatisPlusIdGeneratorType getType() {
return type;
}
public void setType(MyBatisPlusIdGeneratorType type) {
this.type = type;
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.base;
import cn.hutool.core.util.ClassUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import java.util.Collection;
/**
* Mapper 基类
*
* @param <T> 实体类
* @author Charles7c
* @since 1.0.0
*/
public interface BaseMapper<T> extends com.baomidou.mybatisplus.core.mapper.BaseMapper<T> {
/**
* 批量插入记录
*
* @param entityList 实体列表
* @return 是否成功
*/
default boolean insertBatch(Collection<T> entityList) {
return Db.saveBatch(entityList);
}
/**
* 批量更新记录
*
* @param entityList 实体列表
* @return 是否成功
*/
default boolean updateBatchById(Collection<T> entityList) {
return Db.updateBatchById(entityList);
}
/**
* 链式查询
*
* @return QueryWrapper 的包装类
*/
default QueryChainWrapper<T> query() {
return ChainWrappers.queryChain(this);
}
/**
* 链式查询lambda 式)
*
* @return LambdaQueryWrapper 的包装类
*/
default LambdaQueryChainWrapper<T> lambdaQuery() {
return ChainWrappers.lambdaQueryChain(this, this.currentEntityClass());
}
/**
* 链式查询lambda 式)
*
* @param entity 实体对象
* @return LambdaQueryWrapper 的包装类
*/
default LambdaQueryChainWrapper<T> lambdaQuery(T entity) {
return ChainWrappers.lambdaQueryChain(this, entity);
}
/**
* 链式更改
*
* @return UpdateWrapper 的包装类
*/
default UpdateChainWrapper<T> update() {
return ChainWrappers.updateChain(this);
}
/**
* 链式更改lambda 式)
*
* @return LambdaUpdateWrapper 的包装类
*/
default LambdaUpdateChainWrapper<T> lambdaUpdate() {
return ChainWrappers.lambdaUpdateChain(this);
}
/**
* 获取实体类 Class 对象
*
* @return 实体类 Class 对象
*/
default Class<T> currentEntityClass() {
return (Class<T>)ClassUtil.getTypeArgument(this.getClass(), 0);
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.datapermission;
import java.lang.annotation.*;
/**
* 数据权限注解
*
* @author Charles7c
* @since 1.1.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {
/**
* 表别名
*/
String tableAlias() default "";
/**
* ID
*/
String id() default "id";
/**
* 部门 ID
*/
String deptId() default "dept_id";
/**
* 用户 ID
*/
String userId() default "create_user";
/**
* 角色 ID角色和部门关联表
*/
String roleId() default "role_id";
/**
* 部门表别名
*/
String deptTableAlias() default "sys_dept";
/**
* 角色和部门关联表别名
*/
String roleDeptTableAlias() default "sys_role_dept";
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.datapermission;
import java.util.Set;
/**
* 当前用户信息
*
* @author Charles7c
* @since 1.1.0
*/
public class DataPermissionCurrentUser {
/**
* 用户 ID
*/
private String userId;
/**
* 角色列表
*/
private Set<CurrentUserRole> roles;
/**
* 部门 ID
*/
private String deptId;
/**
* 当前用户角色信息
*/
public static class CurrentUserRole {
/**
* 角色 ID
*/
private String roleId;
/**
* 数据权限
*/
private DataScope dataScope;
public CurrentUserRole() {
}
public CurrentUserRole(String roleId, DataScope dataScope) {
this.roleId = roleId;
this.dataScope = dataScope;
}
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
public DataScope getDataScope() {
return dataScope;
}
public void setDataScope(DataScope dataScope) {
this.dataScope = dataScope;
}
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Set<CurrentUserRole> getRoles() {
return roles;
}
public void setRoles(Set<CurrentUserRole> roles) {
this.roles = roles;
}
public String getDeptId() {
return deptId;
}
public void setDeptId(String deptId) {
this.deptId = deptId;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.datapermission;
/**
* 数据权限过滤器接口
*
* @author Charles7c
* @since 1.1.0
*/
public interface DataPermissionFilter {
/**
* 是否过滤
*
* @return true过滤false不过滤
*/
boolean isFilter();
/**
* 获取当前用户信息
*
* @return 当前用户信息
*/
DataPermissionCurrentUser getCurrentUser();
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.datapermission;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.core.constant.StringConstants;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
/**
* 数据权限处理器实现类
*
* @author <a href="https://gitee.com/baomidou/mybatis-plus/issues/I37I90">DataPermissionInterceptor 如何使用?</a>
* @author Charles7c
* @since 1.1.0
*/
public class DataPermissionHandlerImpl implements DataPermissionHandler {
private static final Logger log = LoggerFactory.getLogger(DataPermissionHandlerImpl.class);
private final DataPermissionFilter dataPermissionFilter;
public DataPermissionHandlerImpl(DataPermissionFilter dataPermissionFilter) {
this.dataPermissionFilter = dataPermissionFilter;
}
@Override
public Expression getSqlSegment(Expression where, String mappedStatementId) {
try {
Class<?> clazz = Class.forName(mappedStatementId.substring(0, mappedStatementId
.lastIndexOf(StringConstants.DOT)));
String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringConstants.DOT) + 1);
Method[] methodArr = clazz.getMethods();
for (Method method : methodArr) {
DataPermission dataPermission = method.getAnnotation(DataPermission.class);
String name = method.getName();
if (null == dataPermission || !CharSequenceUtil.equalsAny(methodName, name, name + "_COUNT")) {
continue;
}
if (dataPermissionFilter.isFilter()) {
return buildDataScopeFilter(dataPermission, where);
}
}
} catch (ClassNotFoundException e) {
log.error("Data permission handler build data scope filter occurred an error: {}.", e.getMessage(), e);
}
return where;
}
/**
* 构建数据范围过滤条件
*
* @param dataPermission 数据权限
* @param where 当前查询条件
* @return 构建后查询条件
*/
private Expression buildDataScopeFilter(DataPermission dataPermission, Expression where) {
Expression expression = null;
DataPermissionCurrentUser currentUser = dataPermissionFilter.getCurrentUser();
Set<DataPermissionCurrentUser.CurrentUserRole> roles = currentUser.getRoles();
for (DataPermissionCurrentUser.CurrentUserRole role : roles) {
DataScope dataScope = role.getDataScope();
if (DataScope.ALL.equals(dataScope)) {
return where;
}
switch (dataScope) {
case DEPT_AND_CHILD -> expression = this
.buildDeptAndChildExpression(dataPermission, currentUser, expression);
case DEPT -> expression = this.buildDeptExpression(dataPermission, currentUser, expression);
case SELF -> expression = this.buildSelfExpression(dataPermission, currentUser, expression);
case CUSTOM -> expression = this.buildCustomExpression(dataPermission, role, expression);
default -> throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(dataScope));
}
}
return null != where ? new AndExpression(where, new Parenthesis(expression)) : expression;
}
/**
* 构建本部门及以下数据权限表达式
*
* <p>
* 处理完后的 SQL 示例:<br /> select t1.* from table as t1 where t1.dept_id in (select id from sys_dept where id =
* xxx or find_in_set(xxx, ancestors));
* </p>
*
* @param dataPermission 数据权限
* @param currentUser 当前用户
* @param expression 处理前的表达式
* @return 处理完后的表达式
*/
private Expression buildDeptAndChildExpression(DataPermission dataPermission,
DataPermissionCurrentUser currentUser,
Expression expression) {
ParenthesedSelect subSelect = new ParenthesedSelect();
PlainSelect select = new PlainSelect();
select.setSelectItems(Collections.singletonList(new SelectItem<>(new Column(dataPermission.id()))));
select.setFromItem(new Table(dataPermission.deptTableAlias()));
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(dataPermission.id()));
equalsTo.setRightExpression(new LongValue(currentUser.getDeptId()));
Function function = new Function();
function.setName("find_in_set");
function.setParameters(new ExpressionList(new LongValue(currentUser.getDeptId()), new Column("ancestors")));
select.setWhere(new OrExpression(equalsTo, function));
subSelect.setSelect(select);
// 构建父查询
InExpression inExpression = new InExpression();
inExpression.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
inExpression.setRightExpression(subSelect);
return null != expression ? new OrExpression(expression, inExpression) : inExpression;
}
/**
* 构建本部门数据权限表达式
*
* <p>
* 处理完后的 SQL 示例:<br /> select t1.* from table as t1 where t1.dept_id = xxx;
* </p>
*
* @param dataPermission 数据权限
* @param currentUser 当前用户
* @param expression 处理前的表达式
* @return 处理完后的表达式
*/
private Expression buildDeptExpression(DataPermission dataPermission,
DataPermissionCurrentUser currentUser,
Expression expression) {
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
equalsTo.setRightExpression(new LongValue(currentUser.getDeptId()));
return null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
}
/**
* 构建仅本人数据权限表达式
*
* <p>
* 处理完后的 SQL 示例:<br /> select t1.* from table as t1 where t1.create_user = xxx;
* </p>
*
* @param dataPermission 数据权限
* @param currentUser 当前用户
* @param expression 处理前的表达式
* @return 处理完后的表达式
*/
private Expression buildSelfExpression(DataPermission dataPermission,
DataPermissionCurrentUser currentUser,
Expression expression) {
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.userId()));
equalsTo.setRightExpression(new LongValue(currentUser.getUserId()));
return null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
}
/**
* 构建自定义数据权限表达式
*
* <p>
* 处理完后的 SQL 示例:<br /> select t1.* from table as t1 where t1.dept_id in (select dept_id from sys_role_dept
* where role_id = xxx);
* </p>
*
* @param dataPermission 数据权限
* @param role 当前用户角色
* @param expression 处理前的表达式
* @return 处理完后的表达式
*/
private Expression buildCustomExpression(DataPermission dataPermission,
DataPermissionCurrentUser.CurrentUserRole role,
Expression expression) {
ParenthesedSelect subSelect = new ParenthesedSelect();
PlainSelect select = new PlainSelect();
select.setSelectItems(Collections.singletonList(new SelectItem<>(new Column(dataPermission.deptId()))));
select.setFromItem(new Table(dataPermission.roleDeptTableAlias()));
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(dataPermission.roleId()));
equalsTo.setRightExpression(new LongValue(role.getRoleId()));
select.setWhere(equalsTo);
subSelect.setSelect(select);
// 构建父查询
InExpression inExpression = new InExpression();
inExpression.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
inExpression.setRightExpression(subSelect);
return null != expression ? new OrExpression(expression, inExpression) : inExpression;
}
/**
* 构建 Column
*
* @param tableAlias 表别名
* @param columnName 字段名称
* @return 带表别名字段
*/
private Column buildColumn(String tableAlias, String columnName) {
if (StringUtils.isNotEmpty(tableAlias)) {
return new Column("%s.%s".formatted(tableAlias, columnName));
}
return new Column(columnName);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.datapermission;
/**
* 数据权限枚举
*
* @author Charles7c
* @since 1.1.0
*/
public enum DataScope {
/**
* 全部数据权限
*/
ALL,
/**
* 本部门及以下数据权限
*/
DEPT_AND_CHILD,
/**
* 本部门数据权限
*/
DEPT,
/**
* 仅本人数据权限
*/
SELF,
/**
* 自定义数据权限
*/
CUSTOM,
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.enums;
/**
* MyBatis ID 生成器类型枚举
*
* @author Charles7c
* @since 1.4.0
*/
public enum MyBatisPlusIdGeneratorType {
/**
* 默认
*/
DEFAULT,
/**
* CosId
*/
COSID,
/**
* 自定义
*/
CUSTOM
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.handler;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import top.continew.starter.core.enums.BaseEnum;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* 自定义枚举属性转换器
*
* @author hubin
* @author Charles7c
* @since 2.4.0
*/
public class MybatisBaseEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
private static final Map<String, String> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>();
private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
private final Class<E> enumClassType;
private final Class<?> propertyType;
private final Invoker getInvoker;
public MybatisBaseEnumTypeHandler(Class<E> enumClassType) {
if (enumClassType == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.enumClassType = enumClassType;
MetaClass metaClass = MetaClass.forClass(enumClassType, REFLECTOR_FACTORY);
String name = "value";
if (!BaseEnum.class.isAssignableFrom(enumClassType) && !IEnum.class.isAssignableFrom(enumClassType)) {
name = findEnumValueFieldName(this.enumClassType).orElseThrow(() -> new IllegalArgumentException(String
.format("Could not find @EnumValue in Class: %s.", this.enumClassType.getName())));
}
this.propertyType = ReflectionKit.resolvePrimitiveIfNecessary(metaClass.getGetterType(name));
this.getInvoker = metaClass.getGetInvoker(name);
}
/**
* 查找标记标记EnumValue字段
*
* @param clazz class
* @return EnumValue字段
*/
public static Optional<String> findEnumValueFieldName(Class<?> clazz) {
if (clazz != null && clazz.isEnum()) {
String className = clazz.getName();
return Optional.ofNullable(CollectionUtils.computeIfAbsent(TABLE_METHOD_OF_ENUM_TYPES, className, key -> {
Optional<Field> fieldOptional = findEnumValueAnnotationField(clazz);
return fieldOptional.map(Field::getName).orElse(null);
}));
}
return Optional.empty();
}
private static Optional<Field> findEnumValueAnnotationField(Class<?> clazz) {
return Arrays.stream(clazz.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(EnumValue.class))
.findFirst();
}
/**
* 判断是否为MP枚举处理
*
* @param clazz class
* @return 是否为MP枚举处理
*/
public static boolean isMpEnums(Class<?> clazz) {
return clazz != null && clazz.isEnum() && (BaseEnum.class.isAssignableFrom(clazz) || IEnum.class
.isAssignableFrom(clazz) || findEnumValueFieldName(clazz).isPresent());
}
@SuppressWarnings("Duplicates")
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
if (jdbcType == null) {
ps.setObject(i, this.getValue(parameter));
} else {
// see r3589
ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE);
}
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
Object value = rs.getObject(columnName, this.propertyType);
if (null == value || rs.wasNull()) {
return null;
}
return this.valueOf(value);
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Object value = rs.getObject(columnIndex, this.propertyType);
if (null == value || rs.wasNull()) {
return null;
}
return this.valueOf(value);
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Object value = cs.getObject(columnIndex, this.propertyType);
if (null == value || cs.wasNull()) {
return null;
}
return this.valueOf(value);
}
private E valueOf(Object value) {
E[] es = this.enumClassType.getEnumConstants();
return Arrays.stream(es).filter(e -> equalsValue(value, getValue(e))).findAny().orElse(null);
}
/**
* 值比较
*
* @param sourceValue 数据库字段值
* @param targetValue 当前枚举属性值
* @return 是否匹配
*/
private boolean equalsValue(Object sourceValue, Object targetValue) {
String sValue = StringUtils.toStringTrim(sourceValue);
String tValue = StringUtils.toStringTrim(targetValue);
if (sourceValue instanceof Number && targetValue instanceof Number && new BigDecimal(sValue)
.compareTo(new BigDecimal(tValue)) == 0) {
return true;
}
return Objects.equals(sValue, tValue);
}
private Object getValue(Object object) {
try {
return this.getInvoker.invoke(object, new Object[0]);
} catch (ReflectiveOperationException e) {
throw ExceptionUtils.mpe(e);
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.service;
/**
* 通用业务接口
*
* @param <T> 实体类型
* @author Charles7c
* @since 1.2.0
*/
public interface IService<T> extends com.baomidou.mybatisplus.extension.service.IService<T> {
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.service.impl;
import cn.hutool.core.util.ClassUtil;
import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.data.mp.base.BaseMapper;
import top.continew.starter.data.mp.service.IService;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.List;
/**
* 通用业务实现类
*
* @param <M> Mapper 接口
* @param <T> 实体类型
* @author Charles7c
* @since 1.5.0
*/
public class ServiceImpl<M extends BaseMapper<T>, T> extends com.baomidou.mybatisplus.extension.service.impl.ServiceImpl<M, T> implements IService<T> {
private List<Field> entityFields;
@Override
public T getById(Serializable id) {
return this.getById(id, true);
}
/**
* 获取当前实体类型字段
*
* @return 当前实体类型字段列表
*/
public List<Field> getEntityFields() {
if (this.entityFields == null) {
this.entityFields = ReflectUtils.getNonStaticFields(this.getEntityClass());
}
return this.entityFields;
}
/**
* 根据 ID 查询
*
* @param id ID
* @param isCheckExists 是否检查存在
* @return 实体信息
*/
protected T getById(Serializable id, boolean isCheckExists) {
T entity = baseMapper.selectById(id);
if (isCheckExists) {
CheckUtils.throwIfNotExists(entity, ClassUtil.getClassName(this.getEntityClass(), true), "ID", id);
}
return entity;
}
}

View File

@@ -0,0 +1,213 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.data.mp.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.data.domain.Sort;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.annotation.QueryIgnore;
import top.continew.starter.data.core.enums.QueryType;
import top.continew.starter.data.core.util.SqlInjectionUtils;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Consumer;
/**
* QueryWrapper 助手
*
* @author Charles7c
* @author Jasmine
* @since 1.0.0
*/
public class QueryWrapperHelper {
private static final Logger log = LoggerFactory.getLogger(QueryWrapperHelper.class);
private QueryWrapperHelper() {
}
/**
* 构建 QueryWrapper
*
* @param query 查询条件
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
* @return QueryWrapper
*/
public static <Q, R> QueryWrapper<R> build(Q query) {
return build(query, Sort.unsorted());
}
/**
* 构建 QueryWrapper
*
* @param query 查询条件
* @param sort 排序条件
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
* @return QueryWrapper
* @since 2.5.2
*/
public static <Q, R> QueryWrapper<R> build(Q query, Sort sort) {
QueryWrapper<R> queryWrapper = new QueryWrapper<>();
// 没有查询条件,直接返回
if (null == query) {
return queryWrapper;
}
// 设置排序条件
if (sort != null && sort.isSorted()) {
for (Sort.Order order : sort) {
String field = CharSequenceUtil.toUnderlineCase(order.getProperty());
ValidationUtils.throwIf(SqlInjectionUtils.check(field), "排序字段包含非法字符");
queryWrapper.orderBy(true, order.isAscending(), field);
}
}
// 获取查询条件中所有的字段
List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
return build(query, fieldList, queryWrapper);
}
/**
* 构建 QueryWrapper
*
* @param query 查询条件
* @param fields 查询条件字段列表
* @param queryWrapper QueryWrapper
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
* @return QueryWrapper
*/
public static <Q, R> QueryWrapper<R> build(Q query, List<Field> fields, QueryWrapper<R> queryWrapper) {
// 没有查询条件,直接返回
if (null == query) {
return queryWrapper;
}
// 解析并拼接查询条件
for (Field field : fields) {
List<Consumer<QueryWrapper<R>>> consumers = buildWrapperConsumer(query, field);
queryWrapper.and(CollUtil.isNotEmpty(consumers), q -> consumers.forEach(q::or));
}
return queryWrapper;
}
/**
* 构建 QueryWrapper Consumer
*
* @param query 查询条件
* @param field 查询条件字段
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
* @return QueryWrapper Consumer
*/
private static <Q, R> List<Consumer<QueryWrapper<R>>> buildWrapperConsumer(Q query, Field field) {
boolean accessible = field.canAccess(query);
try {
field.setAccessible(true);
// 如果字段值为空,直接返回
Object fieldValue = field.get(query);
if (ObjectUtil.isEmpty(fieldValue)) {
return Collections.emptyList();
}
// 设置了 @QueryIgnore 注解,直接忽略
QueryIgnore queryIgnoreAnnotation = field.getAnnotation(QueryIgnore.class);
if (null != queryIgnoreAnnotation && queryIgnoreAnnotation.value()) {
return Collections.emptyList();
}
// 建议:数据库表列建议采用下划线连接法命名,程序变量建议采用驼峰法命名
String fieldName = field.getName();
// 没有 @Query 注解,默认等值查询
Query queryAnnotation = field.getAnnotation(Query.class);
if (null == queryAnnotation) {
return Collections.singletonList(q -> q.eq(CharSequenceUtil.toUnderlineCase(fieldName), fieldValue));
}
// 解析单列查询
QueryType queryType = queryAnnotation.type();
String[] columns = queryAnnotation.columns();
final int columnLength = ArrayUtil.length(columns);
List<Consumer<QueryWrapper<R>>> consumers = new ArrayList<>(columnLength);
if (columnLength <= 1) {
String columnName = columnLength == 1 ? columns[0] : CharSequenceUtil.toUnderlineCase(fieldName);
parse(queryType, columnName, fieldValue, consumers);
return consumers;
}
// 解析多列查询
for (String column : columns) {
parse(queryType, column, fieldValue, consumers);
}
return consumers;
} catch (BadRequestException e) {
throw e;
} catch (Exception e) {
log.error("Build query wrapper occurred an error: {}. Query: {}, Field: {}.", e
.getMessage(), query, field, e);
} finally {
field.setAccessible(accessible);
}
return Collections.emptyList();
}
/**
* 解析查询条件
*
* @param queryType 查询类型
* @param columnName 列名
* @param fieldValue 字段值
* @param <R> 查询数据类型
*/
private static <R> void parse(QueryType queryType,
String columnName,
Object fieldValue,
List<Consumer<QueryWrapper<R>>> consumers) {
switch (queryType) {
case EQ -> consumers.add(q -> q.eq(columnName, fieldValue));
case NE -> consumers.add(q -> q.ne(columnName, fieldValue));
case GT -> consumers.add(q -> q.gt(columnName, fieldValue));
case GE -> consumers.add(q -> q.ge(columnName, fieldValue));
case LT -> consumers.add(q -> q.lt(columnName, fieldValue));
case LE -> consumers.add(q -> q.le(columnName, fieldValue));
case BETWEEN -> {
List<Object> between = new ArrayList<>((List<Object>)fieldValue);
ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", columnName);
consumers.add(q -> q.between(columnName, between.get(0), between.get(1)));
}
case LIKE -> consumers.add(q -> q.like(columnName, fieldValue));
case LIKE_LEFT -> consumers.add(q -> q.likeLeft(columnName, fieldValue));
case LIKE_RIGHT -> consumers.add(q -> q.likeRight(columnName, fieldValue));
case IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
consumers.add(q -> q.in(columnName, (Collection<Object>)fieldValue));
}
case NOT_IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
consumers.add(q -> q.notIn(columnName, (Collection<Object>)fieldValue));
}
case IS_NULL -> consumers.add(q -> q.isNull(columnName));
case IS_NOT_NULL -> consumers.add(q -> q.isNotNull(columnName));
default -> throw new IllegalArgumentException("暂不支持 [%s] 查询类型".formatted(queryType));
}
}
}

View File

@@ -0,0 +1 @@
top.continew.starter.data.mp.autoconfigure.MybatisPlusAutoConfiguration

View File

@@ -0,0 +1,17 @@
--- ### MyBatis Plus 配置https://baomidou.com/pages/56bac0/#%E5%9F%BA%E6%9C%AC%E9%85%8D%E7%BD%AE
mybatis-plus:
# 启动时是否检查 MyBatis XML 文件的存在默认false 不检查)
check-config-location: true
## MyBatis 原生支持配置
configuration:
# 是否开启自动驼峰命名规则camel case映射即从经典数据库列名 A_COLUMN下划线命名到经典 Java 属性名 aColumn驼峰命名的类似映射
# 此属性在 MyBatis 中原默认值为 false在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名
map-underscore-to-camel-case: true
# MyBatis 自动映射时未知列或未知属性处理策略,通过该配置可指定 MyBatis 在自动映射过程中遇到未知列或者未知属性时如何处理
# NONE不做任何处理 (默认值)WARNING以日志的形式打印相关警告信息FAILING当作映射失败处理并抛出异常和详细信息
auto-mapping-unknown-column-behavior: NONE
# 日志配置
# 默认org.apache.ibatis.logging.slf4j.Slf4jImpl
# 更详细会有性能损耗org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭(可单纯使用 p6spy 分析org.apache.ibatis.logging.nologging.NoLoggingImpl
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl

View File

@@ -0,0 +1,30 @@
############################################################################
# P6Spy 配置SQL 性能分析组件) #
############################################################################
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 SQL
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 P6Spy Driver 代理
deregisterdrivers=true
# 取消 JDBC URL 前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# SQL语句打印时间格式
databaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否启用慢 SQL 记录
outagedetection=true
# 慢 SQL 记录标准 2 秒
outagedetectioninterval=2
# 是否过滤 Log
filter=true
# 过滤 Log 时所排除的 SQL 关键字,以逗号分隔
exclude=SELECT 1