mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-09-09 08:57:17 +08:00
feat(extension/crud-mf): 支持Mybatis Flex
This commit is contained in:
@@ -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-mybatis-flex</artifactId>
|
||||
<description>ContiNew Starter 数据访问模块 - MyBatis Flex</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus(MyBatis 的增强工具,在 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>
|
@@ -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.mybatis.flex.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 {
|
||||
}
|
@@ -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.mybatis.flex.autoconfigure;
|
||||
|
||||
import com.mybatisflex.core.dialect.DbType;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* MyBatis Plus 扩展配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "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;
|
||||
}
|
||||
}
|
@@ -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.mybatis.flex.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.mybatis.flex.datapermission.DataPermissionDialect;
|
||||
import top.continew.starter.data.mybatis.flex.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));
|
||||
}
|
||||
|
||||
}
|
@@ -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.mybatis.flex.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);
|
||||
}
|
||||
|
||||
}
|
@@ -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.mybatis.flex.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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.mybatis.flex.datapermission;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 数据权限注解
|
||||
*
|
||||
* @author hellokaton
|
||||
* @since 2.0.2
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DataPermission {
|
||||
|
||||
/**
|
||||
* Alias for the {@link #tableAlias()} attribute.
|
||||
*/
|
||||
@AliasFor("tableAlias")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* 表别名
|
||||
*/
|
||||
@AliasFor("value")
|
||||
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";
|
||||
}
|
@@ -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.mybatis.flex.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();
|
||||
}
|
||||
|
||||
}
|
@@ -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.mybatis.flex.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;
|
||||
}
|
||||
}
|
@@ -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.mybatis.flex.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;
|
||||
}
|
||||
|
||||
}
|
@@ -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.mybatis.flex.datapermission;
|
||||
|
||||
/**
|
||||
* 数据权限过滤器接口
|
||||
*
|
||||
* @author hellokaton
|
||||
* @since 2.0.2
|
||||
*/
|
||||
public interface DataPermissionFilter {
|
||||
|
||||
/**
|
||||
* 是否过滤
|
||||
*
|
||||
* @return true:过滤;false:不过滤
|
||||
*/
|
||||
boolean isFilter();
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*
|
||||
* @return 当前用户信息
|
||||
*/
|
||||
DataPermissionCurrentUser getCurrentUser();
|
||||
}
|
@@ -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.mybatis.flex.datapermission;
|
||||
|
||||
/**
|
||||
* 数据权限枚举
|
||||
*
|
||||
* @author hellokaton
|
||||
* @since 2.0.2
|
||||
*/
|
||||
public enum DataScope {
|
||||
|
||||
/**
|
||||
* 全部数据权限
|
||||
*/
|
||||
ALL,
|
||||
|
||||
/**
|
||||
* 本部门及以下数据权限
|
||||
*/
|
||||
DEPT_AND_CHILD,
|
||||
|
||||
/**
|
||||
* 本部门数据权限
|
||||
*/
|
||||
DEPT,
|
||||
|
||||
/**
|
||||
* 仅本人数据权限
|
||||
*/
|
||||
SELF,
|
||||
|
||||
/**
|
||||
* 自定义数据权限
|
||||
*/
|
||||
CUSTOM,
|
||||
}
|
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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.mybatis.flex.query;
|
||||
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
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 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 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> 查询条件数据类型
|
||||
* @param <R> 查询数据类型
|
||||
* @return QueryWrapper
|
||||
*/
|
||||
public static <Q, R> 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 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));
|
||||
}
|
||||
}
|
||||
}
|
@@ -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.mybatis.flex.service;
|
||||
|
||||
/**
|
||||
* 通用业务接口
|
||||
*
|
||||
* @param <T> 实体类型
|
||||
* @author hellokaton
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public interface IService<T> extends com.mybatisflex.core.service.IService<T> {
|
||||
}
|
@@ -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.mybatis.flex.service.impl;
|
||||
|
||||
import top.continew.starter.core.util.ClassUtils;
|
||||
import top.continew.starter.data.mybatis.flex.base.BaseMapper;
|
||||
import top.continew.starter.data.mybatis.flex.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];
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1 @@
|
||||
top.continew.starter.data.mybatis.flex.autoconfigure.MybatisFlexAutoConfiguration
|
@@ -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
|
@@ -16,6 +16,7 @@
|
||||
<modules>
|
||||
<module>continew-starter-data-core</module>
|
||||
<module>continew-starter-data-mybatis-plus</module>
|
||||
<module>continew-starter-data-mybatis-flex</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
@@ -47,6 +47,7 @@
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
<just-auth.version>1.16.6</just-auth.version>
|
||||
<mybatis-plus.version>3.5.5</mybatis-plus.version>
|
||||
<mybatis-flex.version>1.8.9</mybatis-flex.version>
|
||||
<dynamic-datasource.version>4.3.0</dynamic-datasource.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<jetcache.version>2.7.5</jetcache.version>
|
||||
@@ -119,6 +120,18 @@
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Flex(MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
|
||||
<version>${mybatis-flex.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-processor</artifactId>
|
||||
<version>${mybatis-flex.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Dynamic Datasource(基于 Spring Boot 的快速集成多数据源的启动器) -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
@@ -306,6 +319,12 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-crud-mf</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 认证模块 - JustAuth -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
@@ -327,6 +346,13 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据访问模块 - MyBatis Flex -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-mybatis-flex</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据访问模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
|
@@ -0,0 +1,51 @@
|
||||
<?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-extension</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-crud-mf</artifactId>
|
||||
<description>ContiNew Starter 扩展模块 - CRUD(增删改查)</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Crane4j(一个基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架) -->
|
||||
<dependency>
|
||||
<groupId>cn.crane4j</groupId>
|
||||
<artifactId>crane4j-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 认证模块 - SaToken -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-auth-satoken</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据访问模块 - MyBatis Flex -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-mybatis-flex</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 文件处理模块 - Excel -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-file-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.extension.crud.annotation;
|
||||
|
||||
import top.continew.starter.extension.crud.enums.Api;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* CRUD(增删改查)请求映射器注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface CrudRequestMapping {
|
||||
|
||||
/**
|
||||
* 路径映射 URI(等同于:@RequestMapping("/foo1"))
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* API 列表
|
||||
*/
|
||||
Api[] api() default {Api.PAGE, Api.GET, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT};
|
||||
}
|
@@ -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.extension.crud.annotation;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import top.continew.starter.extension.crud.autoconfigure.CrudRestControllerAutoConfiguration;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* CRUD REST Controller 启用注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import({CrudRestControllerAutoConfiguration.class})
|
||||
public @interface EnableCrudRestController {
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.extension.crud.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 树结构字段
|
||||
*
|
||||
* @author Charles7c
|
||||
* @see cn.hutool.core.lang.tree.TreeNodeConfig
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface TreeField {
|
||||
|
||||
/**
|
||||
* ID 字段名
|
||||
*
|
||||
* @return ID 字段名
|
||||
*/
|
||||
String value() default "key";
|
||||
|
||||
/**
|
||||
* 父 ID 字段名
|
||||
*
|
||||
* @return 父 ID 字段名
|
||||
*/
|
||||
String parentIdKey() default "parentId";
|
||||
|
||||
/**
|
||||
* 名称字段名
|
||||
*
|
||||
* @return 名称字段名
|
||||
*/
|
||||
String nameKey() default "title";
|
||||
|
||||
/**
|
||||
* 排序字段名
|
||||
*
|
||||
* @return 排序字段名
|
||||
*/
|
||||
String weightKey() default "sort";
|
||||
|
||||
/**
|
||||
* 子列表字段名
|
||||
*
|
||||
* @return 子列表字段名
|
||||
*/
|
||||
String childrenKey() default "children";
|
||||
|
||||
/**
|
||||
* 递归深度(< 0 不限制)
|
||||
*
|
||||
* @return 递归深度
|
||||
*/
|
||||
int deep() default -1;
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.extension.crud.autoconfigure;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.mvc.condition.RequestCondition;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
import top.continew.starter.core.util.ExceptionUtils;
|
||||
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
|
||||
import top.continew.starter.extension.crud.enums.Api;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* CRUD 请求映射器处理器映射器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class CrudRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
|
||||
|
||||
@Override
|
||||
protected RequestMappingInfo getMappingForMethod(@NonNull Method method, @NonNull Class<?> handlerType) {
|
||||
RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method, handlerType);
|
||||
if (null == requestMappingInfo) {
|
||||
return null;
|
||||
}
|
||||
// 如果没有声明 @CrudRequestMapping 注解,直接返回
|
||||
if (!handlerType.isAnnotationPresent(CrudRequestMapping.class)) {
|
||||
return requestMappingInfo;
|
||||
}
|
||||
CrudRequestMapping crudRequestMapping = handlerType.getDeclaredAnnotation(CrudRequestMapping.class);
|
||||
// 过滤 API,如果非本类中定义,且 API 列表中不包含,则忽略
|
||||
Api[] apiArr = crudRequestMapping.api();
|
||||
Api api = ExceptionUtils.exToNull(() -> Api.valueOf(method.getName().toUpperCase()));
|
||||
if (method.getDeclaringClass() != handlerType && !ArrayUtil.containsAny(apiArr, Api.ALL, api)) {
|
||||
return null;
|
||||
}
|
||||
// 拼接路径(合并了 @RequestMapping 的部分能力)
|
||||
return this.getMappingForMethodWrapper(method, handlerType, crudRequestMapping);
|
||||
}
|
||||
|
||||
private RequestMappingInfo getMappingForMethodWrapper(@NonNull Method method,
|
||||
@NonNull Class<?> handlerType,
|
||||
CrudRequestMapping crudRequestMapping) {
|
||||
RequestMappingInfo info = this.buildRequestMappingInfo(method);
|
||||
if (null != info) {
|
||||
RequestMappingInfo typeInfo = this.buildRequestMappingInfo(handlerType);
|
||||
if (null != typeInfo) {
|
||||
info = typeInfo.combine(info);
|
||||
}
|
||||
String prefix = crudRequestMapping.value();
|
||||
if (null != prefix) {
|
||||
RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration();
|
||||
options.setPatternParser(PathPatternParser.defaultInstance);
|
||||
info = RequestMappingInfo.paths(prefix).options(options).build().combine(info);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private RequestMappingInfo buildRequestMappingInfo(AnnotatedElement element) {
|
||||
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
|
||||
RequestCondition<?> condition = (element instanceof Class<?> clazz
|
||||
? getCustomTypeCondition(clazz)
|
||||
: getCustomMethodCondition((Method)element));
|
||||
return (requestMapping != null ? super.createRequestMappingInfo(requestMapping, condition) : null);
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.extension.crud.autoconfigure;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.servlet.resource.ResourceUrlProvider;
|
||||
|
||||
/**
|
||||
* CRUD REST Controller 自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
public class CrudRestControllerAutoConfiguration extends DelegatingWebMvcConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CrudRestControllerAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* CRUD 请求映射器处理器映射器(覆盖默认 RequestMappingHandlerMapping)
|
||||
*/
|
||||
@Override
|
||||
public RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
|
||||
return new CrudRequestMappingHandlerMapping();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@Override
|
||||
public RequestMappingHandlerMapping requestMappingHandlerMapping(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
|
||||
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
|
||||
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
|
||||
return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService, resourceUrlProvider);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Extension-CRUD REST Controller' completed initialization.");
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.extension.crud.constant;
|
||||
|
||||
/**
|
||||
* 数据源容器相关常量(Crane4j 数据填充组件使用)
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class ContainerPool {
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
public static final String USER_NICKNAME = "UserNickname";
|
||||
|
||||
protected ContainerPool() {
|
||||
}
|
||||
}
|
@@ -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.extension.crud.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
|
||||
import top.continew.starter.extension.crud.util.ValidateGroup;
|
||||
import top.continew.starter.extension.crud.enums.Api;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.query.SortQuery;
|
||||
import top.continew.starter.extension.crud.model.req.BaseReq;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
import top.continew.starter.extension.crud.service.BaseService;
|
||||
import top.continew.starter.web.model.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 控制器基类
|
||||
*
|
||||
* @param <S> 业务接口
|
||||
* @param <L> 列表类型
|
||||
* @param <D> 详情类型
|
||||
* @param <Q> 查询条件
|
||||
* @param <C> 创建或修改类型
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q, C extends BaseReq> {
|
||||
|
||||
@Autowired
|
||||
protected S baseService;
|
||||
|
||||
/**
|
||||
* 分页查询列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param pageQuery 分页查询条件
|
||||
* @return 分页信息
|
||||
*/
|
||||
@Operation(summary = "分页查询列表", description = "分页查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping
|
||||
public R<PageResp<L>> page(Q query, @Validated PageQuery pageQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.page(query, pageQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @return 树列表信息
|
||||
*/
|
||||
@Operation(summary = "查询树列表", description = "查询树列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/tree")
|
||||
public R<List<Tree<Long>>> tree(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.tree(query, sortQuery, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @return 列表信息
|
||||
*/
|
||||
@Operation(summary = "查询列表", description = "查询列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/list")
|
||||
public R<List<L>> list(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.list(query, sortQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询详情
|
||||
*
|
||||
* @param id ID
|
||||
* @return 详情信息
|
||||
*/
|
||||
@Operation(summary = "查询详情", description = "查询详情")
|
||||
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@GetMapping("/{id}")
|
||||
public R<D> get(@PathVariable Long id) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return R.ok(baseService.get(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*
|
||||
* @param req 创建信息
|
||||
* @return 自增 ID
|
||||
*/
|
||||
@Operation(summary = "新增数据", description = "新增数据")
|
||||
@ResponseBody
|
||||
@PostMapping
|
||||
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) {
|
||||
this.checkPermission(Api.ADD);
|
||||
return R.ok("新增成功", baseService.add(req));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*
|
||||
* @param req 修改信息
|
||||
* @param id ID
|
||||
* @return /
|
||||
*/
|
||||
@Operation(summary = "修改数据", description = "修改数据")
|
||||
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@PutMapping("/{id}")
|
||||
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable Long id) {
|
||||
this.checkPermission(Api.UPDATE);
|
||||
baseService.update(req, id);
|
||||
return R.ok("修改成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param ids ID 列表
|
||||
* @return /
|
||||
*/
|
||||
@Operation(summary = "删除数据", description = "删除数据")
|
||||
@Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH)
|
||||
@ResponseBody
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> delete(@PathVariable List<Long> ids) {
|
||||
this.checkPermission(Api.DELETE);
|
||||
baseService.delete(ids);
|
||||
return R.ok("删除成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param response 响应对象
|
||||
*/
|
||||
@Operation(summary = "导出数据", description = "导出数据")
|
||||
@GetMapping("/export")
|
||||
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
|
||||
this.checkPermission(Api.EXPORT);
|
||||
baseService.export(query, sortQuery, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 API 类型进行权限验证
|
||||
*
|
||||
* @param api API 类型
|
||||
*/
|
||||
protected void checkPermission(Api api) {
|
||||
CrudRequestMapping crudRequestMapping = this.getClass().getDeclaredAnnotation(CrudRequestMapping.class);
|
||||
String path = crudRequestMapping.value();
|
||||
String permissionPrefix = String.join(StringConstants.COLON, CharSequenceUtil
|
||||
.splitTrim(path, StringConstants.SLASH));
|
||||
StpUtil.checkPermission("%s:%s".formatted(permissionPrefix, api.name().toLowerCase()));
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.extension.crud.converter;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import com.alibaba.excel.converters.Converter;
|
||||
import com.alibaba.excel.enums.CellDataTypeEnum;
|
||||
import com.alibaba.excel.metadata.GlobalConfiguration;
|
||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.data.mybatis.flex.base.IBaseEnum;
|
||||
|
||||
/**
|
||||
* Easy Excel 枚举接口转换器
|
||||
*
|
||||
* @see IBaseEnum
|
||||
* @author Charles7c
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class ExcelBaseEnumConverter implements Converter<IBaseEnum<Integer>> {
|
||||
|
||||
@Override
|
||||
public Class<IBaseEnum> supportJavaTypeKey() {
|
||||
return IBaseEnum.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellDataTypeEnum supportExcelTypeKey() {
|
||||
return CellDataTypeEnum.STRING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 Java 数据(读取 Excel)
|
||||
*/
|
||||
@Override
|
||||
public IBaseEnum convertToJavaData(ReadCellData<?> cellData,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
return this.getEnum(IBaseEnum.class, Convert.toStr(cellData.getData()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 Excel 数据(写入 Excel)
|
||||
*/
|
||||
@Override
|
||||
public WriteCellData<String> convertToExcelData(IBaseEnum<Integer> value,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
if (null == value) {
|
||||
return new WriteCellData<>(StringConstants.EMPTY);
|
||||
}
|
||||
return new WriteCellData<>(value.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 value 获取枚举对象,获取不到时为 {@code null}
|
||||
*
|
||||
* @param enumType 枚举类型
|
||||
* @param description 描述
|
||||
* @return 对应枚举 ,获取不到时为 {@code null}
|
||||
*/
|
||||
private IBaseEnum<Integer> getEnum(Class<?> enumType, String description) {
|
||||
Object[] enumConstants = enumType.getEnumConstants();
|
||||
for (Object enumConstant : enumConstants) {
|
||||
if (ClassUtil.isAssignable(IBaseEnum.class, enumType)) {
|
||||
IBaseEnum<Integer> baseEnum = (IBaseEnum<Integer>)enumConstant;
|
||||
if (baseEnum.getDescription().equals(description)) {
|
||||
return baseEnum;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.extension.crud.enums;
|
||||
|
||||
/**
|
||||
* API 类型枚举
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public enum Api {
|
||||
|
||||
/**
|
||||
* 所有 API
|
||||
*/
|
||||
ALL,
|
||||
/**
|
||||
* 分页
|
||||
*/
|
||||
PAGE,
|
||||
/**
|
||||
* 树列表
|
||||
*/
|
||||
TREE,
|
||||
/**
|
||||
* 列表
|
||||
*/
|
||||
LIST,
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
GET,
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
ADD,
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
UPDATE,
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
DELETE,
|
||||
/**
|
||||
* 导出
|
||||
*/
|
||||
EXPORT,
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.extension.crud.model.entity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 实体类基类
|
||||
*
|
||||
* <p>
|
||||
* 通用字段:创建人、创建时间
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.0.1
|
||||
*/
|
||||
public class BaseCreateDO extends BaseIdDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private Long createUser;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
public Long getCreateUser() {
|
||||
return createUser;
|
||||
}
|
||||
|
||||
public void setCreateUser(Long createUser) {
|
||||
this.createUser = createUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.extension.crud.model.entity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 实体类基类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class BaseDO extends BaseIdDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private Long createUser;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
private Long updateUser;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public Long getCreateUser() {
|
||||
return createUser;
|
||||
}
|
||||
|
||||
public void setCreateUser(Long createUser) {
|
||||
this.createUser = createUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public Long getUpdateUser() {
|
||||
return updateUser;
|
||||
}
|
||||
|
||||
public void setUpdateUser(Long updateUser) {
|
||||
this.updateUser = updateUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(LocalDateTime updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.extension.crud.model.entity;
|
||||
|
||||
import com.mybatisflex.annotation.Id;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 实体类基类
|
||||
*
|
||||
* <p>
|
||||
* 通用字段:ID 主键
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.0.1
|
||||
*/
|
||||
public class BaseIdDO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.extension.crud.model.entity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 实体类基类
|
||||
*
|
||||
* <p>
|
||||
* 通用字段:创建人、创建时间
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.0.1
|
||||
*/
|
||||
public class BaseUpdateDO extends BaseIdDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
private Long updateUser;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public Long getUpdateUser() {
|
||||
return updateUser;
|
||||
}
|
||||
|
||||
public void setUpdateUser(Long updateUser) {
|
||||
this.updateUser = updateUser;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(LocalDateTime updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.extension.crud.model.query;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 分页查询条件
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ParameterObject
|
||||
@Schema(description = "分页查询条件")
|
||||
public class PageQuery extends SortQuery {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 默认页码:1
|
||||
*/
|
||||
private static final int DEFAULT_PAGE = 1;
|
||||
/**
|
||||
* 默认每页条数:10
|
||||
*/
|
||||
private static final int DEFAULT_SIZE = 10;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
@Schema(description = "页码", example = "1")
|
||||
@Min(value = 1, message = "页码最小值为 {value}")
|
||||
private Integer page = DEFAULT_PAGE;
|
||||
|
||||
/**
|
||||
* 每页条数
|
||||
*/
|
||||
@Schema(description = "每页条数", example = "10")
|
||||
@Range(min = 1, max = 1000, message = "每页条数(取值范围 {min}-{max})")
|
||||
private Integer size = DEFAULT_SIZE;
|
||||
|
||||
public Integer getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(Integer page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public Integer getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(Integer size) {
|
||||
this.size = size;
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.extension.crud.model.query;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 排序查询条件
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Schema(description = "排序查询条件")
|
||||
public class SortQuery implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 排序条件
|
||||
*/
|
||||
@Schema(description = "排序条件", example = "createTime,desc")
|
||||
private String[] sort;
|
||||
|
||||
/**
|
||||
* 解析排序条件为 Spring 分页排序实体
|
||||
*
|
||||
* @return Spring 分页排序实体
|
||||
*/
|
||||
public Sort getSort() {
|
||||
if (ArrayUtil.isEmpty(sort)) {
|
||||
return Sort.unsorted();
|
||||
}
|
||||
|
||||
List<Sort.Order> orders = new ArrayList<>(sort.length);
|
||||
if (CharSequenceUtil.contains(sort[0], StringConstants.COMMA)) {
|
||||
// e.g "sort=createTime,desc&sort=name,asc"
|
||||
for (String s : sort) {
|
||||
List<String> sortList = CharSequenceUtil.splitTrim(s, StringConstants.COMMA);
|
||||
Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sortList.get(1).toUpperCase()), sortList
|
||||
.get(0));
|
||||
orders.add(order);
|
||||
}
|
||||
} else {
|
||||
// e.g "sort=createTime,desc"
|
||||
Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sort[1].toUpperCase()), sort[0]);
|
||||
orders.add(order);
|
||||
}
|
||||
return Sort.by(orders);
|
||||
}
|
||||
|
||||
public void setSort(String[] sort) {
|
||||
this.sort = sort;
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.extension.crud.model.req;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 请求基类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class BaseReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.extension.crud.model.resp;
|
||||
|
||||
import cn.crane4j.annotation.Assemble;
|
||||
import cn.crane4j.annotation.Mapping;
|
||||
import cn.crane4j.annotation.condition.ConditionOnPropertyNotNull;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import top.continew.starter.extension.crud.constant.ContainerPool;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 详情响应基类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class BaseDetailResp extends BaseResp {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
@JsonIgnore
|
||||
@ConditionOnPropertyNotNull
|
||||
@Assemble(container = ContainerPool.USER_NICKNAME, props = @Mapping(ref = "updateUserString"))
|
||||
private Long updateUser;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
@Schema(description = "修改人", example = "李四")
|
||||
@ExcelProperty(value = "修改人", order = Integer.MAX_VALUE - 2)
|
||||
private String updateUserString;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@Schema(description = "修改时间", example = "2023-08-08 08:08:08", type = "string")
|
||||
@ExcelProperty(value = "修改时间", order = Integer.MAX_VALUE - 1)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public Long getUpdateUser() {
|
||||
return updateUser;
|
||||
}
|
||||
|
||||
public void setUpdateUser(Long updateUser) {
|
||||
this.updateUser = updateUser;
|
||||
}
|
||||
|
||||
public String getUpdateUserString() {
|
||||
return updateUserString;
|
||||
}
|
||||
|
||||
public void setUpdateUserString(String updateUserString) {
|
||||
this.updateUserString = updateUserString;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(LocalDateTime updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.extension.crud.model.resp;
|
||||
|
||||
import cn.crane4j.annotation.Assemble;
|
||||
import cn.crane4j.annotation.Mapping;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import top.continew.starter.extension.crud.constant.ContainerPool;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 响应基类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class BaseResp implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@Schema(description = "ID", example = "1")
|
||||
@ExcelProperty(value = "ID", order = 1)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@JsonIgnore
|
||||
@Assemble(container = ContainerPool.USER_NICKNAME, props = @Mapping(ref = "createUserString"))
|
||||
private Long createUser;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@Schema(description = "创建人", example = "超级管理员")
|
||||
@ExcelProperty(value = "创建人", order = Integer.MAX_VALUE - 4)
|
||||
private String createUserString;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string")
|
||||
@ExcelProperty(value = "创建时间", order = Integer.MAX_VALUE - 3)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 是否禁用修改
|
||||
*/
|
||||
@Schema(description = "是否禁用修改", example = "true")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private Boolean disabled;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getCreateUser() {
|
||||
return createUser;
|
||||
}
|
||||
|
||||
public void setCreateUser(Long createUser) {
|
||||
this.createUser = createUser;
|
||||
}
|
||||
|
||||
public String getCreateUserString() {
|
||||
return createUserString;
|
||||
}
|
||||
|
||||
public void setCreateUserString(String createUserString) {
|
||||
this.createUserString = createUserString;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public Boolean getDisabled() {
|
||||
return disabled;
|
||||
}
|
||||
|
||||
public void setDisabled(Boolean disabled) {
|
||||
this.disabled = disabled;
|
||||
}
|
||||
}
|
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.extension.crud.model.resp;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页信息
|
||||
*
|
||||
* @param <L> 列表数据类型
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Schema(description = "分页信息")
|
||||
public class PageResp<L> implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 列表数据
|
||||
*/
|
||||
@Schema(description = "列表数据")
|
||||
private List<L> list;
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
@Schema(description = "总记录数", example = "10")
|
||||
private long total;
|
||||
|
||||
/**
|
||||
* 基于 MyBatis Plus 分页数据构建分页信息,并将源数据转换为指定类型数据
|
||||
*
|
||||
* @param page MyBatis Plus 分页数据
|
||||
* @param targetClass 目标类型 Class 对象
|
||||
* @param <T> 源列表数据类型
|
||||
* @param <L> 目标列表数据类型
|
||||
* @return 分页信息
|
||||
*/
|
||||
public static <T, L> PageResp<L> build(Page<T> page, Class<L> targetClass) {
|
||||
if (null == page) {
|
||||
return empty();
|
||||
}
|
||||
PageResp<L> pageResp = new PageResp<>();
|
||||
pageResp.setList(BeanUtil.copyToList(page.getRecords(), targetClass));
|
||||
pageResp.setTotal(page.getTotalRow());
|
||||
return pageResp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 MyBatis Plus 分页数据构建分页信息
|
||||
*
|
||||
* @param page MyBatis Plus 分页数据
|
||||
* @param <L> 列表数据类型
|
||||
* @return 分页信息
|
||||
*/
|
||||
public static <L> PageResp<L> build(Page<L> page) {
|
||||
if (null == page) {
|
||||
return empty();
|
||||
}
|
||||
PageResp<L> pageResp = new PageResp<>();
|
||||
pageResp.setList(page.getRecords());
|
||||
pageResp.setTotal(page.getTotalRow());
|
||||
return pageResp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于列表数据构建分页信息
|
||||
*
|
||||
* @param page 页码
|
||||
* @param size 每页条数
|
||||
* @param list 列表数据
|
||||
* @param <L> 列表数据类型
|
||||
* @return 分页信息
|
||||
*/
|
||||
public static <L> PageResp<L> build(int page, int size, List<L> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return empty();
|
||||
}
|
||||
PageResp<L> pageResp = new PageResp<>();
|
||||
pageResp.setTotal(list.size());
|
||||
// 对列表数据进行分页
|
||||
int fromIndex = (page - 1) * size;
|
||||
int toIndex = page * size + fromIndex;
|
||||
if (fromIndex > list.size()) {
|
||||
pageResp.setList(new ArrayList<>(0));
|
||||
} else if (toIndex >= list.size()) {
|
||||
pageResp.setList(list.subList(fromIndex, list.size()));
|
||||
} else {
|
||||
pageResp.setList(list.subList(fromIndex, toIndex));
|
||||
}
|
||||
return pageResp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 空分页信息
|
||||
*
|
||||
* @param <L> 列表数据类型
|
||||
* @return 分页信息
|
||||
*/
|
||||
private static <L> PageResp<L> empty() {
|
||||
PageResp<L> pageResp = new PageResp<>();
|
||||
pageResp.setList(Collections.emptyList());
|
||||
return pageResp;
|
||||
}
|
||||
|
||||
public List<L> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setList(List<L> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total) {
|
||||
this.total = total;
|
||||
}
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.extension.crud.service;
|
||||
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.query.SortQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 业务接口基类
|
||||
*
|
||||
* @param <L> 列表类型
|
||||
* @param <D> 详情类型
|
||||
* @param <Q> 查询条件
|
||||
* @param <C> 创建或修改类型
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface BaseService<L, D, Q, C> {
|
||||
|
||||
/**
|
||||
* 分页查询列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param pageQuery 分页查询条件
|
||||
* @return 分页列表信息
|
||||
*/
|
||||
PageResp<L> page(Q query, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段)
|
||||
* @return 树列表信息
|
||||
*/
|
||||
List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @return 列表信息
|
||||
*/
|
||||
List<L> list(Q query, SortQuery sortQuery);
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
*
|
||||
* @param id ID
|
||||
* @return 详情信息
|
||||
*/
|
||||
D get(Long id);
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*
|
||||
* @param req 创建信息
|
||||
* @return 自增 ID
|
||||
*/
|
||||
Long add(C req);
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*
|
||||
* @param req 修改信息
|
||||
* @param id ID
|
||||
*/
|
||||
void update(C req, Long id);
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param ids ID 列表
|
||||
*/
|
||||
void delete(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 导出
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param response 响应对象
|
||||
*/
|
||||
void export(Q query, SortQuery sortQuery, HttpServletResponse response);
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.extension.crud.service;
|
||||
|
||||
import cn.crane4j.annotation.ContainerMethod;
|
||||
import cn.crane4j.annotation.MappingType;
|
||||
import top.continew.starter.extension.crud.constant.ContainerPool;
|
||||
|
||||
/**
|
||||
* 公共用户业务接口
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface CommonUserService {
|
||||
|
||||
/**
|
||||
* 根据 ID 查询昵称
|
||||
*
|
||||
* @param id ID
|
||||
* @return 昵称
|
||||
*/
|
||||
@ContainerMethod(namespace = ContainerPool.USER_NICKNAME, type = MappingType.ORDER_OF_KEYS)
|
||||
String getNicknameById(Long id);
|
||||
}
|
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* 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.extension.crud.service.impl;
|
||||
|
||||
import cn.crane4j.core.support.OperateTemplate;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.copier.CopyOptions;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Opt;
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import cn.hutool.core.lang.tree.TreeNodeConfig;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.ReflectUtils;
|
||||
import top.continew.starter.core.util.validate.ValidationUtils;
|
||||
import top.continew.starter.data.mybatis.flex.base.BaseMapper;
|
||||
import top.continew.starter.data.mybatis.flex.query.QueryWrapperHelper;
|
||||
import top.continew.starter.data.mybatis.flex.service.impl.ServiceImpl;
|
||||
import top.continew.starter.extension.crud.annotation.TreeField;
|
||||
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.query.SortQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
import top.continew.starter.extension.crud.service.BaseService;
|
||||
import top.continew.starter.extension.crud.util.TreeUtils;
|
||||
import top.continew.starter.file.excel.util.ExcelUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 业务实现基类
|
||||
*
|
||||
* @param <M> Mapper 接口
|
||||
* @param <T> 实体类型
|
||||
* @param <L> 列表类型
|
||||
* @param <D> 详情类型
|
||||
* @param <Q> 查询条件
|
||||
* @param <C> 创建或修改类型
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D, Q, C> extends ServiceImpl<M, T> implements BaseService<L, D, Q, C> {
|
||||
|
||||
protected final Class<L> listClass = this.currentListClass();
|
||||
protected final Class<D> detailClass = this.currentDetailClass();
|
||||
protected final Class<Q> queryClass = this.currentQueryClass();
|
||||
private final List<Field> queryFields = ReflectUtils.getNonStaticFields(this.queryClass);
|
||||
|
||||
@Override
|
||||
public PageResp<L> page(Q query, PageQuery pageQuery) {
|
||||
QueryWrapper queryWrapper = this.buildQueryWrapper(query);
|
||||
Page<T> page = mapper.paginate(pageQuery.getPage(), pageQuery.getSize(), queryWrapper);
|
||||
PageResp<L> pageResp = PageResp.build(page, listClass);
|
||||
pageResp.getList().forEach(this::fill);
|
||||
return pageResp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) {
|
||||
List<L> list = this.list(query, sortQuery);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
// 如果构建简单树结构,则不包含基本树结构之外的扩展字段
|
||||
TreeNodeConfig treeNodeConfig = TreeUtils.DEFAULT_CONFIG;
|
||||
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class);
|
||||
if (!isSimple) {
|
||||
// 根据 @TreeField 配置生成树结构配置
|
||||
treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField);
|
||||
}
|
||||
// 构建树
|
||||
return TreeUtils.build(list, treeNodeConfig, (node, tree) -> {
|
||||
// 转换器
|
||||
tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value())));
|
||||
tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey())));
|
||||
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
|
||||
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
|
||||
if (!isSimple) {
|
||||
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
|
||||
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
|
||||
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
|
||||
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
|
||||
.getName()))));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<L> list(Q query, SortQuery sortQuery) {
|
||||
List<L> list = this.list(query, sortQuery, listClass);
|
||||
list.forEach(this::fill);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public D get(Long id) {
|
||||
T entity = super.getById(id);
|
||||
D detail = BeanUtil.toBean(entity, detailClass);
|
||||
this.fill(detail);
|
||||
return detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long add(C req) {
|
||||
this.beforeAdd(req);
|
||||
T entity = BeanUtil.copyProperties(req, this.entityClass);
|
||||
mapper.insert(entity);
|
||||
this.afterAdd(req, entity);
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(C req, Long id) {
|
||||
this.beforeUpdate(req, id);
|
||||
T entity = this.getById(id);
|
||||
BeanUtil.copyProperties(req, entity, CopyOptions.create().ignoreNullValue());
|
||||
mapper.update(entity);
|
||||
this.afterUpdate(req, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(List<Long> ids) {
|
||||
this.beforeDelete(ids);
|
||||
mapper.deleteBatchByIds(ids);
|
||||
this.afterDelete(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
|
||||
List<D> list = this.list(query, sortQuery, detailClass);
|
||||
list.forEach(this::fill);
|
||||
ExcelUtils.export(list, "导出数据", detailClass, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param targetClass 指定类型
|
||||
* @return 列表信息
|
||||
*/
|
||||
protected <E> List<E> list(Q query, SortQuery sortQuery, Class<E> targetClass) {
|
||||
QueryWrapper queryWrapper = this.buildQueryWrapper(query);
|
||||
// 设置排序
|
||||
this.sort(queryWrapper, sortQuery);
|
||||
List<T> entityList = mapper.selectListByQuery(queryWrapper);
|
||||
if (this.entityClass == targetClass) {
|
||||
return (List<E>)entityList;
|
||||
}
|
||||
return BeanUtil.copyToList(entityList, targetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置排序
|
||||
*
|
||||
* @param queryWrapper 查询条件封装对象
|
||||
* @param sortQuery 排序查询条件
|
||||
*/
|
||||
protected void sort(QueryWrapper queryWrapper, SortQuery sortQuery) {
|
||||
Sort sort = Opt.ofNullable(sortQuery).orElseGet(SortQuery::new).getSort();
|
||||
List<Field> entityFields = ReflectUtils.getNonStaticFields(this.entityClass);
|
||||
for (Sort.Order order : sort) {
|
||||
if (null == order) {
|
||||
continue;
|
||||
}
|
||||
String property = order.getProperty();
|
||||
String checkProperty;
|
||||
// 携带表别名则获取 . 后面的字段名
|
||||
if (property.contains(StringConstants.DOT)) {
|
||||
checkProperty = CollUtil.getLast(CharSequenceUtil.split(property, StringConstants.DOT));
|
||||
} else {
|
||||
checkProperty = property;
|
||||
}
|
||||
Optional<Field> optional = entityFields.stream()
|
||||
.filter(field -> checkProperty.equals(field.getName()))
|
||||
.findFirst();
|
||||
ValidationUtils.throwIf(optional.isEmpty(), "无效的排序字段 [{}]", property);
|
||||
queryWrapper.orderBy(CharSequenceUtil.toUnderlineCase(property), order.isAscending());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充数据
|
||||
*
|
||||
* @param obj 待填充信息
|
||||
*/
|
||||
protected void fill(Object obj) {
|
||||
if (null == obj) {
|
||||
return;
|
||||
}
|
||||
OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class);
|
||||
operateTemplate.execute(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 QueryWrapper
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return QueryWrapper
|
||||
*/
|
||||
protected QueryWrapper buildQueryWrapper(Q query) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create();
|
||||
// 解析并拼接查询条件
|
||||
return QueryWrapperHelper.build(query, queryFields, queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增前置处理
|
||||
*
|
||||
* @param req 创建信息
|
||||
*/
|
||||
protected void beforeAdd(C req) {
|
||||
/* 新增前置处理 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改前置处理
|
||||
*
|
||||
* @param req 修改信息
|
||||
* @param id ID
|
||||
*/
|
||||
protected void beforeUpdate(C req, Long id) {
|
||||
/* 修改前置处理 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除前置处理
|
||||
*
|
||||
* @param ids ID 列表
|
||||
*/
|
||||
protected void beforeDelete(List<Long> ids) {
|
||||
/* 删除前置处理 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增后置处理
|
||||
*
|
||||
* @param req 创建信息
|
||||
* @param entity 实体信息
|
||||
*/
|
||||
protected void afterAdd(C req, T entity) {
|
||||
/* 新增后置处理 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改后置处理
|
||||
*
|
||||
* @param req 修改信息
|
||||
* @param entity 实体信息
|
||||
*/
|
||||
protected void afterUpdate(C req, T entity) {
|
||||
/* 修改后置处理 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除后置处理
|
||||
*
|
||||
* @param ids ID 列表
|
||||
*/
|
||||
protected void afterDelete(List<Long> ids) {
|
||||
/* 删除后置处理 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前列表信息类型
|
||||
*
|
||||
* @return 当前列表信息类型
|
||||
*/
|
||||
protected Class<L> currentListClass() {
|
||||
return (Class<L>)this.typeArguments[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前详情信息类型
|
||||
*
|
||||
* @return 当前详情信息类型
|
||||
*/
|
||||
protected Class<D> currentDetailClass() {
|
||||
return (Class<D>)this.typeArguments[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前查询条件类型
|
||||
*
|
||||
* @return 当前查询条件类型
|
||||
*/
|
||||
protected Class<Q> currentQueryClass() {
|
||||
return (Class<Q>)this.typeArguments[4];
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.extension.crud.util;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import cn.hutool.core.lang.tree.TreeNodeConfig;
|
||||
import cn.hutool.core.lang.tree.TreeUtil;
|
||||
import cn.hutool.core.lang.tree.parser.NodeParser;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import top.continew.starter.core.util.validate.CheckUtils;
|
||||
import top.continew.starter.extension.crud.annotation.TreeField;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 树工具类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class TreeUtils {
|
||||
|
||||
/**
|
||||
* 默认字段配置对象(根据前端树结构灵活调整名称)
|
||||
*/
|
||||
public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("title")
|
||||
.setIdKey("key")
|
||||
.setWeightKey("sort");
|
||||
|
||||
private TreeUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 树构建
|
||||
*
|
||||
* @param <T> 转换的实体 为数据源里的对象类型
|
||||
* @param <E> ID类型
|
||||
* @param list 源数据集合
|
||||
* @param nodeParser 转换器
|
||||
* @return List 树列表
|
||||
*/
|
||||
public static <T, E> List<Tree<E>> build(List<T> list, NodeParser<T, E> nodeParser) {
|
||||
return build(list, DEFAULT_CONFIG, nodeParser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 树构建
|
||||
*
|
||||
* @param <T> 转换的实体 为数据源里的对象类型
|
||||
* @param <E> ID类型
|
||||
* @param list 源数据集合
|
||||
* @param treeNodeConfig 配置
|
||||
* @param nodeParser 转换器
|
||||
* @return List 树列表
|
||||
*/
|
||||
public static <T, E> List<Tree<E>> build(List<T> list, TreeNodeConfig treeNodeConfig, NodeParser<T, E> nodeParser) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
E parentId = (E)ReflectUtil.getFieldValue(list.get(0), treeNodeConfig.getParentIdKey());
|
||||
return TreeUtil.build(list, parentId, treeNodeConfig, nodeParser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 @TreeField 配置生成树结构配置
|
||||
*
|
||||
* @param treeField 树结构字段注解
|
||||
* @return 树结构配置
|
||||
*/
|
||||
public static TreeNodeConfig genTreeNodeConfig(TreeField treeField) {
|
||||
CheckUtils.throwIfNull(treeField, "请添加并配置 @TreeField 树结构信息");
|
||||
return new TreeNodeConfig().setIdKey(treeField.value())
|
||||
.setParentIdKey(treeField.parentIdKey())
|
||||
.setNameKey(treeField.nameKey())
|
||||
.setWeightKey(treeField.weightKey())
|
||||
.setChildrenKey(treeField.childrenKey())
|
||||
.setDeep(treeField.deep() < 0 ? null : treeField.deep());
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.extension.crud.util;
|
||||
|
||||
import jakarta.validation.groups.Default;
|
||||
|
||||
/**
|
||||
* 分组校验
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface ValidateGroup extends Default {
|
||||
|
||||
/**
|
||||
* 分组校验-增删改查
|
||||
*/
|
||||
interface Crud extends ValidateGroup {
|
||||
/**
|
||||
* 分组校验-创建
|
||||
*/
|
||||
interface Add extends Crud {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分组校验-修改
|
||||
*/
|
||||
interface Update extends Crud {
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@
|
||||
|
||||
<modules>
|
||||
<module>continew-starter-extension-crud</module>
|
||||
<module>continew-starter-extension-crud-mf</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
Reference in New Issue
Block a user