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,45 @@
<?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-mf</artifactId>
<description>ContiNew Starter 数据访问模块 - MyBatis Flex</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-processor</artifactId>
<scope>provided</scope>
</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.mf.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import top.continew.starter.core.constant.PropertiesConstants;
import java.lang.annotation.*;
/**
* 是否启用数据权限注解
*
* @author hellokaton
* @since 2.0.2
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@ConditionalOnProperty(prefix = "mybatis-flex.extension.data-permission", name = PropertiesConstants.ENABLED, havingValue = "true")
public @interface ConditionalOnEnabledDataPermission {
}

View File

@@ -0,0 +1,162 @@
/*
* 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.mf.autoconfigure;
import com.mybatisflex.core.dialect.DbType;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* MyBatis Plus 扩展配置属性
*
* @author Charles7c
* @since 1.0.0
*/
@ConfigurationProperties("mybatis-flex.extension")
public class MyBatisFlexExtensionProperties {
/**
* 是否启用扩展
*/
private boolean enabled = false;
/**
* Mapper 接口扫描包配置时必须使用mapper-package 键名)
* <p>
* e.g. com.example.**.mapper
* </p>
*/
private String mapperPackage;
/**
* 数据权限插件配置
*/
private DataPermissionProperties dataPermission;
/**
* 分页插件配置
*/
private PaginationProperties pagination;
/**
* 数据权限插件配置属性
*/
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 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;
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.mf.autoconfigure;
import com.mybatisflex.core.dialect.DbType;
import com.mybatisflex.core.dialect.DialectFactory;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
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.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
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.mf.datapermission.DataPermissionDialect;
import top.continew.starter.data.mf.datapermission.DataPermissionFilter;
/**
* MyBatis Flex 自动配置
*
* @author hellokaton
* @since 2.0.2
*/
@AutoConfiguration
@MapperScan("${mybatis-flex.extension.mapper-package}")
@EnableTransactionManagement(proxyTargetClass = true)
@EnableConfigurationProperties(MyBatisFlexExtensionProperties.class)
@ConditionalOnProperty(prefix = "mybatis-flex.extension", name = PropertiesConstants.ENABLED, havingValue = "true")
@PropertySource(value = "classpath:default-data-mybatis-flex.yml", factory = GeneralPropertySourceFactory.class)
public class MybatisFlexAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(MybatisFlexAutoConfiguration.class);
@Resource
private DataPermissionFilter dataPermissionFilter;
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'MyBatis Flex' completed initialization.");
DialectFactory.registerDialect(DbType.MYSQL, new DataPermissionDialect(dataPermissionFilter));
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.mf.base;
import cn.hutool.core.util.ClassUtil;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.row.Db;
import java.util.Collection;
/**
* Mapper 基类
*
* @param <T> 实体类
* @author hellokaton
* @since 1.0.0
*/
public interface BaseMapper<T> extends com.mybatisflex.core.BaseMapper<T> {
/**
* 批量更新记录
*
* @param entityList 实体列表
* @return 是否成功
*/
default boolean updateBatchById(Collection<T> entityList) {
return Db.updateEntitiesBatch(entityList) > 0;
}
/**
* 链式查询
*
* @return QueryWrapper 的包装类
*/
default QueryWrapper query() {
return QueryWrapper.create();
}
/**
* 链式查询
*
* @return QueryWrapper 的包装类
*/
default QueryWrapper query(T entity) {
return QueryWrapper.create(entity);
}
/**
* 获取实体类 Class 对象
*
* @return 实体类 Class 对象
*/
default Class<T> currentEntityClass() {
return (Class<T>)ClassUtil.getTypeArgument(this.getClass(), 0);
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.mf.base;
import java.io.Serializable;
/**
* 枚举接口
*
* @param <T> value 类型
* @author hellokaton
* @since 1.0.0
*/
public interface IBaseEnum<T extends Serializable> {
/**
* 枚举描述
*
* @return 枚举描述
*/
String getDescription();
/**
* 枚举数据库存储值
*/
T getValue();
/**
* 颜色
*
* @return 颜色
*/
default String getColor() {
return null;
}
}

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.mf.datapermission;
import java.lang.annotation.*;
/**
* 数据权限注解
*
* @author hellokaton
* @since 2.0.2
*/
@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,50 @@
/*
* 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.mf.datapermission;
import org.aspectj.lang.annotation.*;
@Aspect
public class DataPermissionAspect {
// ThreadLocal用于存储注解信息
private static final ThreadLocal<DataPermission> THREAD_LOCAL = new ThreadLocal<>();
@Pointcut("@annotation(dataPermission)")
public void dataPermissionPointcut(DataPermission dataPermission) {
}
@Before("dataPermissionPointcut(dataPermission)")
public void beforeMethod(DataPermission dataPermission) {
THREAD_LOCAL.set(dataPermission);
}
@AfterThrowing(pointcut = "dataPermissionPointcut(dataPermission)")
public void afterThrowingMethod(DataPermission dataPermission) {
THREAD_LOCAL.remove();
}
@After("dataPermissionPointcut(dataPermission)")
public void afterMethod(DataPermission dataPermission) {
THREAD_LOCAL.remove();
}
public static DataPermission currentDataPermission() {
return THREAD_LOCAL.get();
}
}

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.mf.datapermission;
import java.util.Set;
/**
* 当前用户信息
*
* @author hellokaton
* @since 2.0.2
*/
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,161 @@
/*
* 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.mf.datapermission;
import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.dialect.impl.CommonsDialectImpl;
import com.mybatisflex.core.query.QueryWrapper;
import java.util.Set;
/**
* 数据权限处理器实现类
*
* @author <a href="https://mybatis-flex.com/zh/core/data-permission.html">数据权限</a>
* @author hellokaton
* @since 2.0.2
*/
public class DataPermissionDialect extends CommonsDialectImpl {
private final DataPermissionFilter dataPermissionFilter;
public DataPermissionDialect(DataPermissionFilter dataPermissionFilter) {
this.dataPermissionFilter = dataPermissionFilter;
}
@Override
public String forSelectByQuery(QueryWrapper queryWrapper) {
if (!dataPermissionFilter.isFilter()) {
return super.buildSelectSql(queryWrapper);
}
DataPermission dataPermission = DataPermissionAspect.currentDataPermission();
if (null == dataPermission) {
return super.buildSelectSql(queryWrapper);
}
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 super.buildSelectSql(queryWrapper);
}
switch (dataScope) {
case DEPT_AND_CHILD -> this.buildDeptAndChildExpression(dataPermission, currentUser, queryWrapper);
case DEPT -> this.buildDeptExpression(dataPermission, currentUser, queryWrapper);
case SELF -> this.buildSelfExpression(dataPermission, currentUser, queryWrapper);
case CUSTOM -> this.buildCustomExpression(dataPermission, role, queryWrapper);
default -> throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(dataScope));
}
}
return super.buildSelectSql(queryWrapper);
}
/**
* 构建自定义数据权限表达式
*
* <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 queryWrapper 查询条件
* @return 处理完后的表达式
*/
private void buildCustomExpression(DataPermission dataPermission,
DataPermissionCurrentUser.CurrentUserRole role,
QueryWrapper queryWrapper) {
QueryWrapper subQueryWrapper = QueryWrapper.create();
subQueryWrapper.select(dataPermission.deptId()).from(dataPermission.roleDeptTableAlias());
subQueryWrapper.eq(dataPermission.roleId(), role.getRoleId());
queryWrapper.in(buildColumn(dataPermission.tableAlias(), dataPermission.deptId()), subQueryWrapper);
}
/**
* 构建仅本人数据权限表达式
*
* <p>
* 处理完后的 SQL 示例:<br /> select t1.* from table as t1 where t1.create_user = xxx;
* </p>
*
* @param dataPermission 数据权限
* @param currentUser 当前用户
* @param queryWrapper 处理前的表达式
*/
private void buildSelfExpression(DataPermission dataPermission,
DataPermissionCurrentUser currentUser,
QueryWrapper queryWrapper) {
queryWrapper.eq(buildColumn(dataPermission.tableAlias(), dataPermission.userId()), currentUser.getUserId());
}
/**
* 构建本部门数据权限表达式
*
* <p>
* 处理完后的 SQL 示例:<br /> select t1.* from table as t1 where t1.dept_id = xxx;
* </p>
*
* @param dataPermission 数据权限
* @param currentUser 当前用户
* @param queryWrapper 查询条件
*/
private void buildDeptExpression(DataPermission dataPermission,
DataPermissionCurrentUser currentUser,
QueryWrapper queryWrapper) {
queryWrapper.eq(buildColumn(dataPermission.tableAlias(), dataPermission.deptId()), currentUser.getDeptId());
}
/**
* 构建本部门及以下数据权限表达式
*
* <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 queryWrapper 查询条件
*/
private void buildDeptAndChildExpression(DataPermission dataPermission,
DataPermissionCurrentUser currentUser,
QueryWrapper queryWrapper) {
QueryWrapper subQueryWrapper = QueryWrapper.create();
subQueryWrapper.select(dataPermission.id()).from(dataPermission.deptTableAlias());
subQueryWrapper.and(qw -> {
qw.eq(dataPermission.id(), currentUser.getDeptId())
.or("find_in_set(" + currentUser.getDeptId() + ",ancestors)");
});
queryWrapper.in(buildColumn(dataPermission.tableAlias(), dataPermission.deptId()), subQueryWrapper);
}
/**
* 构建 Column
*
* @param tableAlias 表别名
* @param columnName 字段名称
* @return 带表别名字段
*/
private String buildColumn(String tableAlias, String columnName) {
if (StrUtil.isNotEmpty(tableAlias)) {
return "%s.%s".formatted(tableAlias, columnName);
}
return columnName;
}
}

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.mf.datapermission;
/**
* 数据权限过滤器接口
*
* @author hellokaton
* @since 2.0.2
*/
public interface DataPermissionFilter {
/**
* 是否过滤
*
* @return true过滤false不过滤
*/
boolean isFilter();
/**
* 获取当前用户信息
*
* @return 当前用户信息
*/
DataPermissionCurrentUser getCurrentUser();
}

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.mf.datapermission;
/**
* 数据权限枚举
*
* @author hellokaton
* @since 2.0.2
*/
public enum DataScope {
/**
* 全部数据权限
*/
ALL,
/**
* 本部门及以下数据权限
*/
DEPT_AND_CHILD,
/**
* 本部门数据权限
*/
DEPT,
/**
* 仅本人数据权限
*/
SELF,
/**
* 自定义数据权限
*/
CUSTOM,
}

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.mf.service;
/**
* 通用业务接口
*
* @param <T> 实体类型
* @author hellokaton
* @since 1.2.0
*/
public interface IService<T> extends com.mybatisflex.core.service.IService<T> {
}

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.mf.service.impl;
import top.continew.starter.core.util.ClassUtils;
import top.continew.starter.data.mf.base.BaseMapper;
import top.continew.starter.data.mf.service.IService;
/**
* 通用业务实现类
*
* @param <M> Mapper 接口
* @param <T> 实体类型
* @author hellokaton
* @since 1.5.0
*/
public class ServiceImpl<M extends BaseMapper<T>, T> extends com.mybatisflex.spring.service.impl.ServiceImpl<M, T> implements IService<T> {
protected final Class<?>[] typeArguments = ClassUtils.getTypeArguments(this.getClass());
protected final Class<T> entityClass = currentModelClass();
protected Class<T> currentModelClass() {
return (Class<T>)this.typeArguments[1];
}
}

View File

@@ -0,0 +1,222 @@
/*
* 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.mf.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.mybatisflex.core.query.QueryWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/**
* QueryWrapper 助手
*
* @author hellokaton
* @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> 查询条件数据类型
* @return QueryWrapper
*/
public static <Q> QueryWrapper build(Q query) {
QueryWrapper queryWrapper = QueryWrapper.create();
// 没有查询条件,直接返回
if (null == query) {
return queryWrapper;
}
// 获取查询条件中所有的字段
List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
return build(query, fieldList, queryWrapper);
}
/**
* 构建 QueryWrapper
*
* @param query 查询条件
* @param sort 排序条件
* @param <Q> 查询条件数据类型
* @return QueryWrapper
* @since 2.5.2
*/
public static <Q> QueryWrapper build(Q query, Sort sort) {
QueryWrapper queryWrapper = QueryWrapper.create();
// 没有查询条件,直接返回
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(field, order.isAscending());
}
}
// 获取查询条件中所有的字段
List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
return build(query, fieldList, queryWrapper);
}
/**
* 构建 QueryWrapper
*
* @param query 查询条件
* @param fields 查询条件字段列表
* @param queryWrapper QueryWrapper
* @param <Q> 查询条件数据类型
* @return QueryWrapper
*/
public static <Q> QueryWrapper build(Q query, List<Field> fields, QueryWrapper queryWrapper) {
// 没有查询条件,直接返回
if (null == query) {
return queryWrapper;
}
// 解析并拼接查询条件
for (Field field : fields) {
List<Consumer<QueryWrapper>> consumers = buildWrapperConsumer(query, field);
if (CollUtil.isNotEmpty(consumers)) {
consumers.forEach(queryWrapper::and);
}
}
return queryWrapper;
}
/**
* 构建 QueryWrapper Consumer
*
* @param query 查询条件
* @param field 查询条件字段
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
* @return QueryWrapper Consumer
*/
private static <Q, R> List<Consumer<QueryWrapper>> 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>> 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>> 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.mf.autoconfigure.MybatisFlexAutoConfiguration

View File

@@ -0,0 +1,22 @@
--- ### MyBatis Flex 配置https://mybatis-flex.com/zh/base/configuration.html
mybatis-flex:
# 启动时是否检查 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
global-config:
key-config:
key-type: generator
# flexId 主键生成器 com.mybatisflex.core.keygen.impl.FlexIDKeyGenerator
value: flexId