mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-11-12 06:57:10 +08:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cd3935d71 | |||
| 1ce5eb3b73 | |||
| a8c6ea3079 | |||
| 9b7ea33c0b | |||
| e9b9d8b82e | |||
| 04498ffe56 | |||
| bd60411f3e | |||
| 673e586aaf | |||
| f4b23102a9 | |||
| 5891c4aa61 | |||
| 679cae1760 | |||
| 46d2212f53 | |||
| af295d6938 | |||
| 802dcb5735 | |||
| cd69b2adb6 | |||
| f8437918de | |||
| f85839559a | |||
| 7666d56019 | |||
| 04e0b4b1cc | |||
| be4dec5a30 | |||
| 3535ac64f7 | |||
|
|
ca1b92cde3 | ||
| 1a97a1b709 | |||
| 0c334dadcc | |||
| 46773df9dd | |||
| 1fc80cda9e | |||
| 0cede6bf9f | |||
| 5d1601e6eb | |||
| 06d3a6ca41 | |||
| 08ef09c9b5 |
53
CHANGELOG.md
53
CHANGELOG.md
@@ -1,3 +1,52 @@
|
||||
## [v2.7.2](https://github.com/continew-org/continew-starter/compare/v2.7.1...v2.7.2) (2024-11-12)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 【extension/crud】支持树结构全局配置 ([5891c4a](https://github.com/continew-org/continew-starter/commit/5891c4aa61b14ba11a387a478fb3616dfc52217c))
|
||||
- 【extension/crud】查询字典列表新增支持 extraKeys 额外信息字段 ([9b7ea33](https://github.com/continew-org/continew-starter/commit/9b7ea33c0b6714e2ea631aa26f0650e78857079a)) ([a8c6ea3](https://github.com/continew-org/continew-starter/commit/a8c6ea30797811d885f294f28eb95afb935ad7b4))
|
||||
- 【cache/redisson】RedisUtils 新增上锁、释放锁方法 ([04498ff](https://github.com/continew-org/continew-starter/commit/04498ffe56b062bce1200292b23d2c31341771e6))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【extension/crud】移除 TreeUtils ([f4b2310](https://github.com/continew-org/continew-starter/commit/f4b23102a9a31b2120f40a8288bc0aedc36e11b4))
|
||||
- 【data/mp】移除冗余的数据库类型判空处理 ([bd60411](https://github.com/continew-org/continew-starter/commit/bd60411f3e4fa87c26e492df96fbfb088ea3ce85))
|
||||
- 【core】重构 IP 工具类获取归属地的返回格式(更方便数据处理) ([e9b9d8b](https://github.com/continew-org/continew-starter/commit/e9b9d8b82e7e28be82c9ed518582d88f507cfac2))
|
||||
- 【data】Query 范围查询支持数组数据 ([673e586](https://github.com/continew-org/continew-starter/commit/673e586aafc8578f0c7ab063ca9df9b1265f88d5))
|
||||
- 【data】重构 MetaUtils 获取表信息方法 ([1ce5eb3](https://github.com/continew-org/continew-starter/commit/1ce5eb3b734b13ccd47e3848117daf3c2d7d0afa))
|
||||
|
||||
## [v2.7.0](https://github.com/continew-org/continew-starter/compare/v2.6.0...v2.7.0) (2024-09-28)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 【data/mp】新增乐观锁插件启用配置(默认关闭) ([08ef09c](https://github.com/continew-org/continew-starter/commit/08ef09c9b594dca75b39e36add38998826b234bf))
|
||||
- 【extension/tenant】新增 continew-starter-extension-tenant 多租户模块 ([1a97a1b](https://github.com/continew-org/continew-starter/commit/1a97a1b709ee0c04300fd39758506fd479da0713)) ([f843791](https://github.com/continew-org/continew-starter/commit/f8437918de342ee45d15df30c20de5e8d3b18379))
|
||||
- 【extension/datapermission】新增数据权限模块(原 data/mp 中数据权限移除) ([7666d56](https://github.com/continew-org/continew-starter/commit/7666d56019bb309dca004d43b0717f6bb0e56c8f))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【data/mp】移除多数据源依赖,如需使用可手动引入 ([06d3a6c](https://github.com/continew-org/continew-starter/commit/06d3a6ca412b0bdeba9c0e460db6a0b05215b6b3))
|
||||
- 完善 ConditionalOnProperty 配置 ([0cede6b](https://github.com/continew-org/continew-starter/commit/0cede6bf9fc89e0c5009e9721b5cea2cf73b890c))
|
||||
- 优化部分代码写法 ([1fc80cd](https://github.com/continew-org/continew-starter/commit/1fc80cda9eb5b377b30d834692dff58d8f93053b))
|
||||
- 优化代码格式 ([46773df](https://github.com/continew-org/continew-starter/commit/46773df9dd2dc473459d58fc17f650d3da260545))
|
||||
- 【data/mp】移除 QueryIgnore 的无用属性 ([0c334da](https://github.com/continew-org/continew-starter/commit/0c334dadcce9d74301dbcc3c336dc28ffc4cf62e))
|
||||
- 【file/excel】导出方法增加排除字段参数 ([3535ac6](https://github.com/continew-org/continew-starter/commit/3535ac64f79c7c3d8e03d8ed2a996ebdfab1ff92))
|
||||
- 统一部分命名风格 ([f858395](https://github.com/continew-org/continew-starter/commit/f85839559ad7002dffbe3c5999a75e801ef9c4d1))
|
||||
- 优化部分依赖传递范围 ([cd69b2a](https://github.com/continew-org/continew-starter/commit/cd69b2adb67cf17b12619f06b8e81492cbb41c26))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【log/interceptor】修复 continew-starter.log.exclude-patterns 配置不生效的问题 ([ca1b92c](https://github.com/continew-org/continew-starter/commit/ca1b92cde3cf8f9d9ee0b7420f5b13f200e80781))
|
||||
- 【log/interceptor】修复全局配置和局部配置包含请求、响应体冲突 ([be4dec5](https://github.com/continew-org/continew-starter/commit/be4dec5a3039625e62d346dbb148206b602af6aa))
|
||||
|
||||
### 📦 依赖升级
|
||||
|
||||
- Spring Boot 3.2.7 => 3.2.10 ([802dcb5](https://github.com/continew-org/continew-starter/commit/802dcb5735562e911e3a51741cfcf17dbe59a89e))
|
||||
- MyBatis Plus 3.5.7 => 3.5.8
|
||||
- Redisson 3.35.0 => 3.36.0
|
||||
- CosID 2.9.6 => 2.9.8
|
||||
- SMS4J 3.2.1 => 3.3.3
|
||||
- X File Storage 2.2.0 => 2.2.1
|
||||
|
||||
## [v2.6.0](https://github.com/continew-org/continew-starter/compare/v2.5.2...v2.6.0) (2024-09-06)
|
||||
|
||||
### ✨ 新特性
|
||||
@@ -27,9 +76,7 @@
|
||||
- MyBatis Flex 1.9.3 => 1.9.7
|
||||
- Redisson 3.32.0 => 3.35.0
|
||||
- Cos ID 2.9.1 => 2.9.6
|
||||
- SMS4J 3.2.1 => 3.3.2
|
||||
- X File Storage 2.2.0 => 2.2.1
|
||||
- Hutool 5.8.32 => 5.8.29
|
||||
- Hutool 5.8.29 => 5.8.32
|
||||
- aws-java-sdk-s3 1.12.761 => 1.12.771
|
||||
- snakeyaml 2.2 => 2.3
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -179,10 +179,16 @@ continew-starter
|
||||
├─ continew-starter-storage(存储模块)
|
||||
│ └─ continew-starter-storage-local(本地存储)
|
||||
└─ continew-starter-extension(扩展模块)
|
||||
├─ continew-starter-extension-datapermission(数据权限模块)
|
||||
│ ├─ continew-starter-extension-datapermission-core(通用模块)
|
||||
│ └─ continew-starter-extension-datapermission-mp(MyBatis Plus)
|
||||
├─ continew-starter-extension-tenant(多租户模块)
|
||||
│ ├─ continew-starter-extension-tenant-core(通用模块)
|
||||
│ └─ continew-starter-extension-tenant-mp(MyBatis Plus)
|
||||
└─ continew-starter-extension-crud(CRUD 模块)
|
||||
├─ continew-starter-extension-crud-core(通用模块)
|
||||
├─ continew-starter-extension-crud-mp(MyBatis Plus)
|
||||
└─ continew-starter-extension-crud-mf(MyBatis Flex)
|
||||
├─ continew-starter-extension-crud-core(通用模块)
|
||||
├─ continew-starter-extension-crud-mp(MyBatis Plus)
|
||||
└─ continew-starter-extension-crud-mf(MyBatis Flex)
|
||||
```
|
||||
|
||||
## 贡献代码
|
||||
|
||||
@@ -28,7 +28,9 @@ import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springdoc.core.configuration.SpringDocConfiguration;
|
||||
import org.springdoc.core.customizers.*;
|
||||
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
|
||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
||||
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
|
||||
import org.springdoc.core.properties.SpringDocConfigProperties;
|
||||
import org.springdoc.core.providers.JavadocProvider;
|
||||
import org.springdoc.core.service.OpenAPIService;
|
||||
@@ -48,7 +50,9 @@ import top.continew.starter.apidoc.handler.OpenApiHandler;
|
||||
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,12 +26,12 @@ import io.swagger.v3.oas.models.parameters.Parameter;
|
||||
import org.springdoc.core.customizers.ParameterCustomizer;
|
||||
import org.springdoc.core.customizers.PropertyCustomizer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import top.continew.starter.apidoc.util.DocUtils;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import top.continew.starter.apidoc.util.DocUtils;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
|
||||
/**
|
||||
* 自定义 BaseEnum 枚举参数处理器
|
||||
|
||||
@@ -146,12 +146,15 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
|
||||
if (openAPI.isPresent()) {
|
||||
this.openAPI = openAPI.get();
|
||||
if (this.openAPI.getComponents() == null)
|
||||
if (this.openAPI.getComponents() == null) {
|
||||
this.openAPI.setComponents(new Components());
|
||||
if (this.openAPI.getPaths() == null)
|
||||
}
|
||||
if (this.openAPI.getPaths() == null) {
|
||||
this.openAPI.setPaths(new Paths());
|
||||
if (!CollectionUtils.isEmpty(this.openAPI.getServers()))
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(this.openAPI.getServers())) {
|
||||
this.isServersPresent = true;
|
||||
}
|
||||
}
|
||||
this.propertyResolverUtils = propertyResolverUtils;
|
||||
this.securityParser = securityParser;
|
||||
@@ -159,8 +162,9 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
this.openApiBuilderCustomisers = openApiBuilderCustomizers;
|
||||
this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
|
||||
this.javadocProvider = javadocProvider;
|
||||
if (springDocConfigProperties.isUseFqn())
|
||||
if (springDocConfigProperties.isUseFqn()) {
|
||||
TypeNameResolver.std.setUseFqn(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,10 +176,11 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale);
|
||||
buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
|
||||
|
||||
if (!CollectionUtils.isEmpty(tagsStr))
|
||||
if (!CollectionUtils.isEmpty(tagsStr)) {
|
||||
tagsStr = tagsStr.stream()
|
||||
.map(str -> propertyResolverUtils.resolve(str, locale))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
if (springdocTags.containsKey(handlerMethod)) {
|
||||
Tag tag = springdocTags.get(handlerMethod);
|
||||
@@ -186,9 +191,9 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(tagsStr)) {
|
||||
if (CollectionUtils.isEmpty(operation.getTags()))
|
||||
if (CollectionUtils.isEmpty(operation.getTags())) {
|
||||
operation.setTags(new ArrayList<>(tagsStr));
|
||||
else {
|
||||
} else {
|
||||
Set<String> operationTagsSet = new HashSet<>(operation.getTags());
|
||||
operationTagsSet.addAll(tagsStr);
|
||||
operation.getTags().clear();
|
||||
@@ -223,8 +228,9 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
if (!CollectionUtils.isEmpty(tags)) {
|
||||
// Existing tags
|
||||
List<Tag> openApiTags = openAPI.getTags();
|
||||
if (!CollectionUtils.isEmpty(openApiTags))
|
||||
if (!CollectionUtils.isEmpty(openApiTags)) {
|
||||
tags.addAll(openApiTags);
|
||||
}
|
||||
openAPI.setTags(new ArrayList<>(tags));
|
||||
}
|
||||
|
||||
@@ -232,10 +238,11 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements = securityParser
|
||||
.getSecurityRequirements(handlerMethod);
|
||||
if (securityRequirements != null) {
|
||||
if (securityRequirements.length == 0)
|
||||
if (securityRequirements.length == 0) {
|
||||
operation.setSecurity(Collections.emptyList());
|
||||
else
|
||||
} else {
|
||||
securityParser.buildSecurityRequirement(securityRequirements, operation);
|
||||
}
|
||||
}
|
||||
|
||||
return operation;
|
||||
@@ -263,8 +270,9 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
tagsSet.forEach(tag -> {
|
||||
tag.name(propertyResolverUtils.resolve(tag.getName(), locale));
|
||||
tag.description(propertyResolverUtils.resolve(tag.getDescription(), locale));
|
||||
if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName())))
|
||||
if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName()))) {
|
||||
tags.add(tag);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,14 +16,15 @@
|
||||
|
||||
package top.continew.starter.apidoc.util;
|
||||
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
|
||||
/**
|
||||
* 接口文档工具类
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import top.continew.starter.auth.justauth.core.JustAuthStateCacheRedisImpl;
|
||||
import top.continew.starter.auth.justauth.core.AuthStateCacheRedisDefaultImpl;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
@@ -35,19 +35,19 @@ import top.continew.starter.core.constant.PropertiesConstants;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration(before = com.xkcoding.justauth.autoconfigure.JustAuthAutoConfiguration.class)
|
||||
@ConditionalOnProperty(prefix = "justauth", name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = "justauth", name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class JustAuthAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(JustAuthAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* 自定义 State 缓存实现
|
||||
* State 缓存 Redis 实现(默认)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnClass(RedisClient.class)
|
||||
@ConditionalOnProperty(prefix = "justauth.cache", name = "type", havingValue = "redis")
|
||||
public AuthStateCache authStateCache() {
|
||||
JustAuthStateCacheRedisImpl impl = new JustAuthStateCacheRedisImpl();
|
||||
AuthStateCacheRedisDefaultImpl impl = new AuthStateCacheRedisDefaultImpl();
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'JustAuth-AuthStateCache-Redis' completed initialization.");
|
||||
return impl;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ import top.continew.starter.cache.redisson.util.RedisUtils;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* JustAuth State 缓存 Redis 实现
|
||||
* 默认 State 缓存 Redis 实现
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class JustAuthStateCacheRedisImpl implements AuthStateCache {
|
||||
public class AuthStateCacheRedisDefaultImpl implements AuthStateCache {
|
||||
|
||||
private static final String KEY_PREFIX = "SOCIAL_AUTH_STATE";
|
||||
|
||||
@@ -29,7 +29,10 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.continew.starter.auth.satoken.autoconfigure.dao.SaTokenDaoConfiguration;
|
||||
@@ -78,8 +81,7 @@ public class SaTokenAutoConfiguration implements WebMvcConfigurer {
|
||||
@Configuration
|
||||
@Import({SaTokenDaoConfiguration.Default.class, SaTokenDaoConfiguration.Redis.class,
|
||||
SaTokenDaoConfiguration.Custom.class})
|
||||
protected static class SaTokenDaoAutoConfiguration {
|
||||
}
|
||||
protected static class SaTokenDaoAutoConfiguration {}
|
||||
|
||||
/**
|
||||
* 整合 JWT(简单模式)
|
||||
|
||||
@@ -60,7 +60,7 @@ public class SaTokenDaoConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义持久层实现-Redis
|
||||
* 自定义持久层实现-Redis(默认)
|
||||
*/
|
||||
@ConditionalOnMissingBean(SaTokenDao.class)
|
||||
@ConditionalOnClass(RedisClient.class)
|
||||
@@ -73,7 +73,7 @@ public class SaTokenDaoConfiguration {
|
||||
|
||||
@Bean
|
||||
public SaTokenDao saTokenDao() {
|
||||
return new SaTokenDaoRedisImpl();
|
||||
return new SaTokenDaoRedisDefaultImpl();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Sa-Token 持久层 Redis 实现(参考:Sa-Token/sa-token-plugin/sa-token-dao-redisx/SaTokenDaoOfRedis.java)
|
||||
* 默认 Sa-Token 持久层 Redis 实现(参考:Sa-Token/sa-token-plugin/sa-token-dao-redisx/SaTokenDaoOfRedis.java)
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class SaTokenDaoRedisImpl implements SaTokenDao {
|
||||
public class SaTokenDaoRedisDefaultImpl implements SaTokenDao {
|
||||
|
||||
@Override
|
||||
public String get(String key) {
|
||||
@@ -46,7 +46,7 @@ import java.util.List;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(prefix = "spring.data.redisson", name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = "spring.data.redisson", name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
@EnableConfigurationProperties(RedissonProperties.class)
|
||||
public class RedissonAutoConfiguration {
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ import top.continew.starter.core.constant.StringConstants;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Redis 工具类
|
||||
@@ -207,6 +209,80 @@ public class RedisUtils {
|
||||
return rateLimiter.tryAcquire(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试获取锁
|
||||
*
|
||||
* @param key 键
|
||||
* @param expireTime 锁过期时间(单位:毫秒)
|
||||
* @param timeout 获取锁超时时间(单位:毫秒)
|
||||
* @return true:成功;false:失败
|
||||
* @since 2.7.2
|
||||
*/
|
||||
public static boolean tryLock(String key, long expireTime, long timeout) {
|
||||
return tryLock(key, expireTime, timeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放锁
|
||||
*
|
||||
* @param key 键
|
||||
* @return true:释放成功;false:释放失败
|
||||
* @since 2.7.2
|
||||
*/
|
||||
public static boolean unlock(String key) {
|
||||
RLock lock = getLock(key);
|
||||
return unlock(lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试获取锁
|
||||
*
|
||||
* @param key 键
|
||||
* @param expireTime 锁过期时间
|
||||
* @param timeout 获取锁超时时间
|
||||
* @param unit 时间单位
|
||||
* @return true:成功;false:失败
|
||||
* @since 2.7.2
|
||||
*/
|
||||
public static boolean tryLock(String key, long expireTime, long timeout, TimeUnit unit) {
|
||||
RLock lock = getLock(key);
|
||||
try {
|
||||
return lock.tryLock(timeout, expireTime, unit);
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放锁
|
||||
*
|
||||
* @param lock 锁实例
|
||||
* @return true:释放成功;false:释放失败
|
||||
* @since 2.7.2
|
||||
*/
|
||||
public static boolean unlock(RLock lock) {
|
||||
if (lock.isHeldByCurrentThread()) {
|
||||
try {
|
||||
lock.unlockAsync().get();
|
||||
return true;
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取锁实例
|
||||
*
|
||||
* @param key 键
|
||||
* @return 锁实例
|
||||
* @since 2.7.2
|
||||
*/
|
||||
public static RLock getLock(String key) {
|
||||
return CLIENT.getLock(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化键,将各子键用 : 拼接起来
|
||||
*
|
||||
|
||||
@@ -49,7 +49,7 @@ import java.util.Properties;
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(BehaviorCaptchaProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.CAPTCHA_BEHAVIOR, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.CAPTCHA_BEHAVIOR, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class BehaviorCaptchaAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BehaviorCaptchaAutoConfiguration.class);
|
||||
|
||||
@@ -35,7 +35,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(GraphicCaptchaProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.CAPTCHA_GRAPHIC, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.CAPTCHA_GRAPHIC, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class GraphicCaptchaAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GraphicCaptchaAutoConfiguration.class);
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
package top.continew.starter.captcha.graphic.core;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.wf.captcha.base.Captcha;
|
||||
import top.continew.starter.captcha.graphic.autoconfigure.GraphicCaptchaProperties;
|
||||
|
||||
|
||||
@@ -31,5 +31,4 @@ import org.springframework.context.annotation.Import;
|
||||
@ComponentScan("cn.hutool.extra.spring")
|
||||
@Import(cn.hutool.extra.spring.SpringUtil.class)
|
||||
@EnableConfigurationProperties(ProjectProperties.class)
|
||||
public class ProjectAutoConfiguration {
|
||||
}
|
||||
public class ProjectAutoConfiguration {}
|
||||
|
||||
@@ -42,7 +42,7 @@ import java.util.concurrent.Executor;
|
||||
@Lazy
|
||||
@AutoConfiguration
|
||||
@EnableAsync(proxyTargetClass = true)
|
||||
@ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class AsyncAutoConfiguration implements AsyncConfigurer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AsyncAutoConfiguration.class);
|
||||
|
||||
@@ -52,7 +52,7 @@ public class ThreadPoolAutoConfiguration {
|
||||
* 异步任务线程池配置
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public ThreadPoolTaskExecutorCustomizer threadPoolTaskExecutorCustomizer(ThreadPoolExtensionProperties properties) {
|
||||
return executor -> {
|
||||
// 核心(最小)线程数
|
||||
@@ -71,7 +71,7 @@ public class ThreadPoolAutoConfiguration {
|
||||
* 定时任务线程池配置
|
||||
*/
|
||||
@EnableScheduling
|
||||
@ConditionalOnProperty(prefix = "spring.task.scheduling.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = "spring.task.scheduling.extension", name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public static class TaskSchedulerConfiguration {
|
||||
@Bean
|
||||
public ThreadPoolTaskSchedulerCustomizer threadPoolTaskSchedulerCustomizer(ThreadPoolExtensionProperties properties) {
|
||||
|
||||
@@ -119,6 +119,21 @@ public class PropertiesConstants {
|
||||
*/
|
||||
public static final String MESSAGING_WEBSOCKET = MESSAGING + StringConstants.DOT + "websocket";
|
||||
|
||||
/**
|
||||
* CRUD 配置
|
||||
*/
|
||||
public static final String CRUD = CONTINEW_STARTER + StringConstants.DOT + "crud";
|
||||
|
||||
/**
|
||||
* 数据权限配置
|
||||
*/
|
||||
public static final String DATA_PERMISSION = CONTINEW_STARTER + StringConstants.DOT + "data-permission";
|
||||
|
||||
/**
|
||||
* 多租户配置
|
||||
*/
|
||||
public static final String TENANT = CONTINEW_STARTER + StringConstants.DOT + "tenant";
|
||||
|
||||
private PropertiesConstants() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,8 +120,7 @@ public class StringConstants {
|
||||
public static final String DOT = StrPool.DOT;
|
||||
|
||||
/**
|
||||
* 字符串常量:双点 {@code ".."} <br>
|
||||
* 用途:作为指向上级文件夹的路径,如:{@code "../path"}
|
||||
* 字符串常量:双点 {@code ".."} <br> 用途:作为指向上级文件夹的路径,如:{@code "../path"}
|
||||
*/
|
||||
public static final String DOUBLE_DOT = StrPool.DOUBLE_DOT;
|
||||
|
||||
@@ -136,8 +135,7 @@ public class StringConstants {
|
||||
public static final String BACKSLASH = StrPool.BACKSLASH;
|
||||
|
||||
/**
|
||||
* 字符串常量:回车符 {@code "\r"} <br>
|
||||
* 解释:该字符常用于表示 Linux 系统和 MacOS 系统下的文本换行
|
||||
* 字符串常量:回车符 {@code "\r"} <br> 解释:该字符常用于表示 Linux 系统和 MacOS 系统下的文本换行
|
||||
*/
|
||||
public static final String CR = StrPool.CR;
|
||||
|
||||
@@ -147,8 +145,7 @@ public class StringConstants {
|
||||
public static final String LF = StrPool.LF;
|
||||
|
||||
/**
|
||||
* 字符串常量:Windows 换行 {@code "\r\n"} <br>
|
||||
* 解释:该字符串常用于表示 Windows 系统下的文本换行
|
||||
* 字符串常量:Windows 换行 {@code "\r\n"} <br> 解释:该字符串常用于表示 Windows 系统下的文本换行
|
||||
*/
|
||||
public static final String CRLF = StrPool.CRLF;
|
||||
|
||||
@@ -257,6 +254,11 @@ public class StringConstants {
|
||||
*/
|
||||
public static final String QUESTION_MARK = "?";
|
||||
|
||||
/**
|
||||
* 管道符
|
||||
*/
|
||||
public static final String PIPE = "|";
|
||||
|
||||
/**
|
||||
* 中文逗号
|
||||
*/
|
||||
|
||||
@@ -18,13 +18,13 @@ package top.continew.starter.core.util;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
|
||||
import net.dreamlu.mica.ip2region.core.IpInfo;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -50,12 +50,13 @@ public class IpUtils {
|
||||
}
|
||||
Ip2regionSearcher ip2regionSearcher = SpringUtil.getBean(Ip2regionSearcher.class);
|
||||
IpInfo ipInfo = ip2regionSearcher.memorySearch(ip);
|
||||
if (null != ipInfo) {
|
||||
Set<String> regionSet = CollUtil.newLinkedHashSet(ipInfo.getAddress(), ipInfo.getIsp());
|
||||
regionSet.removeIf(CharSequenceUtil::isBlank);
|
||||
return String.join(StringConstants.SPACE, regionSet);
|
||||
if (null == ipInfo) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
Set<String> regionSet = CollUtil.newLinkedHashSet(ipInfo.getCountry(), ipInfo.getRegion(), ipInfo
|
||||
.getProvince(), ipInfo.getCity(), ipInfo.getIsp());
|
||||
regionSet.removeIf(Objects::isNull);
|
||||
return String.join(StringConstants.PIPE, regionSet);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,12 +27,4 @@ import java.lang.annotation.*;
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface QueryIgnore {
|
||||
|
||||
/**
|
||||
* 获取是否忽略查询解析
|
||||
*
|
||||
* @return 是否忽略查询解析
|
||||
*/
|
||||
boolean value() default true;
|
||||
}
|
||||
public @interface QueryIgnore {}
|
||||
|
||||
@@ -16,18 +16,21 @@
|
||||
|
||||
package top.continew.starter.data.core.util;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.db.Db;
|
||||
import cn.hutool.db.Entity;
|
||||
import cn.hutool.db.DbRuntimeException;
|
||||
import cn.hutool.db.DbUtil;
|
||||
import cn.hutool.db.meta.Column;
|
||||
import cn.hutool.db.meta.MetaUtil;
|
||||
import cn.hutool.db.meta.Table;
|
||||
import cn.hutool.db.meta.TableType;
|
||||
import top.continew.starter.core.exception.BusinessException;
|
||||
import top.continew.starter.data.core.enums.DatabaseType;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -80,7 +83,7 @@ public class MetaUtils {
|
||||
* @param dataSource 数据源
|
||||
* @return 表信息列表
|
||||
*/
|
||||
public static List<Table> getTables(DataSource dataSource) throws SQLException {
|
||||
public static List<Table> getTables(DataSource dataSource) {
|
||||
return getTables(dataSource, null);
|
||||
}
|
||||
|
||||
@@ -90,27 +93,39 @@ public class MetaUtils {
|
||||
* @param dataSource 数据源
|
||||
* @param tableName 表名称
|
||||
* @return 表信息列表
|
||||
* @author looly
|
||||
* @since 2.7.2
|
||||
*/
|
||||
public static List<Table> getTables(DataSource dataSource, String tableName) throws SQLException {
|
||||
String querySql = "SHOW TABLE STATUS";
|
||||
List<Entity> tableEntityList;
|
||||
Db db = Db.use(dataSource);
|
||||
if (CharSequenceUtil.isNotBlank(tableName)) {
|
||||
tableEntityList = db.query("%s WHERE NAME = ?".formatted(querySql), tableName);
|
||||
} else {
|
||||
tableEntityList = db.query(querySql);
|
||||
public static List<Table> getTables(DataSource dataSource, String tableName) {
|
||||
List<Table> tables = new ArrayList<>();
|
||||
Connection conn = null;
|
||||
try {
|
||||
conn = dataSource.getConnection();
|
||||
String catalog = MetaUtil.getCatalog(conn);
|
||||
String schema = MetaUtil.getSchema(conn);
|
||||
final DatabaseMetaData metaData = conn.getMetaData();
|
||||
try (final ResultSet rs = metaData.getTables(catalog, schema, tableName, Convert
|
||||
.toStrArray(TableType.TABLE))) {
|
||||
if (null != rs) {
|
||||
String name;
|
||||
while (rs.next()) {
|
||||
name = rs.getString("TABLE_NAME");
|
||||
if (CharSequenceUtil.isNotBlank(name)) {
|
||||
final Table table = Table.create(name);
|
||||
table.setCatalog(catalog);
|
||||
table.setSchema(schema);
|
||||
table.setComment(MetaUtil.getRemarks(metaData, catalog, schema, name));
|
||||
tables.add(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tables;
|
||||
} catch (Exception e) {
|
||||
throw new DbRuntimeException("Get tables error!", e);
|
||||
} finally {
|
||||
DbUtil.close(conn);
|
||||
}
|
||||
List<Table> tableList = new ArrayList<>(tableEntityList.size());
|
||||
for (Entity tableEntity : tableEntityList) {
|
||||
Table table = new Table(tableEntity.getStr("NAME"));
|
||||
table.setComment(tableEntity.getStr("COMMENT"));
|
||||
table.setEngine(tableEntity.getStr("ENGINE"));
|
||||
table.setCharset(tableEntity.getStr("COLLATION"));
|
||||
table.setCreateTime(DateUtil.toLocalDateTime(tableEntity.getDate("CREATE_TIME")));
|
||||
table.setUpdateTime(DateUtil.toLocalDateTime(tableEntity.getDate("UPDATE_TIME")));
|
||||
tableList.add(table);
|
||||
}
|
||||
return tableList;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +136,7 @@ public class MetaUtils {
|
||||
* @return 列信息列表
|
||||
*/
|
||||
public static Collection<Column> getColumns(DataSource dataSource, String tableName) {
|
||||
cn.hutool.db.meta.Table table = MetaUtil.getTableMeta(dataSource, tableName);
|
||||
Table table = MetaUtil.getTableMeta(dataSource, tableName);
|
||||
return table.getColumns();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* 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.core.util;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据库表信息
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class Table implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 表名称
|
||||
*/
|
||||
private String tableName;
|
||||
|
||||
/**
|
||||
* 注释
|
||||
*/
|
||||
private String comment;
|
||||
|
||||
/**
|
||||
* 存储引擎
|
||||
*/
|
||||
private String engine;
|
||||
|
||||
/**
|
||||
* 字符集
|
||||
*/
|
||||
private String charset;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public Table(String tableName) {
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
public void setTableName(String tableName) {
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public String getEngine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
public void setEngine(String engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
public void setCharset(String charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(LocalDateTime updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
||||
@@ -23,5 +23,4 @@ package top.continew.starter.data.mf.service;
|
||||
* @author hellokaton
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public interface IService<T> extends com.mybatisflex.core.service.IService<T> {
|
||||
}
|
||||
public interface IService<T> extends com.mybatisflex.core.service.IService<T> {}
|
||||
|
||||
@@ -143,7 +143,7 @@ public class QueryWrapperHelper {
|
||||
}
|
||||
// 设置了 @QueryIgnore 注解,直接忽略
|
||||
QueryIgnore queryIgnoreAnnotation = field.getAnnotation(QueryIgnore.class);
|
||||
if (null != queryIgnoreAnnotation && queryIgnoreAnnotation.value()) {
|
||||
if (null != queryIgnoreAnnotation) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 建议:数据库表列建议采用下划线连接法命名,程序变量建议采用驼峰法命名
|
||||
@@ -199,7 +199,9 @@ public class QueryWrapperHelper {
|
||||
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);
|
||||
List<Object> between = new ArrayList<>(ArrayUtil.isArray(fieldValue)
|
||||
? CollUtil.toList(fieldValue)
|
||||
: (List<Object>)fieldValue);
|
||||
ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", columnName);
|
||||
consumers.add(q -> q.between(columnName, between.get(0), between.get(1)));
|
||||
}
|
||||
@@ -208,11 +210,15 @@ public class QueryWrapperHelper {
|
||||
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));
|
||||
consumers.add(q -> q.in(columnName, ArrayUtil.isArray(fieldValue)
|
||||
? CollUtil.toList(fieldValue)
|
||||
: (Collection<Object>)fieldValue));
|
||||
}
|
||||
case NOT_IN -> {
|
||||
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
|
||||
consumers.add(q -> q.notIn(columnName, (Collection<Object>)fieldValue));
|
||||
consumers.add(q -> q.notIn(columnName, ArrayUtil.isArray(fieldValue)
|
||||
? CollUtil.toList(fieldValue)
|
||||
: (Collection<Object>)fieldValue));
|
||||
}
|
||||
case IS_NULL -> consumers.add(q -> q.isNull(columnName));
|
||||
case IS_NOT_NULL -> consumers.add(q -> q.isNotNull(columnName));
|
||||
|
||||
@@ -19,12 +19,6 @@
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Dynamic Datasource(基于 Spring Boot 的快速集成多数据源的启动器) -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- P6Spy(SQL 性能分析组件) -->
|
||||
<dependency>
|
||||
<groupId>p6spy</groupId>
|
||||
|
||||
@@ -49,40 +49,21 @@ public class MyBatisPlusExtensionProperties {
|
||||
@NestedConfigurationProperty
|
||||
private MyBatisPlusIdGeneratorProperties idGenerator;
|
||||
|
||||
/**
|
||||
* 数据权限插件配置
|
||||
*/
|
||||
private DataPermissionProperties dataPermission;
|
||||
|
||||
/**
|
||||
* 分页插件配置
|
||||
*/
|
||||
private PaginationProperties pagination;
|
||||
|
||||
/**
|
||||
* 启用乐观锁插件
|
||||
*/
|
||||
private boolean optimisticLockerEnabled = false;
|
||||
|
||||
/**
|
||||
* 启用防全表更新与删除插件
|
||||
*/
|
||||
private boolean blockAttackPluginEnabled = true;
|
||||
|
||||
/**
|
||||
* 数据权限插件配置属性
|
||||
*/
|
||||
public static class DataPermissionProperties {
|
||||
|
||||
/**
|
||||
* 是否启用数据权限插件
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页插件配置属性
|
||||
*/
|
||||
@@ -165,14 +146,6 @@ public class MyBatisPlusExtensionProperties {
|
||||
this.idGenerator = idGenerator;
|
||||
}
|
||||
|
||||
public DataPermissionProperties getDataPermission() {
|
||||
return dataPermission;
|
||||
}
|
||||
|
||||
public void setDataPermission(DataPermissionProperties dataPermission) {
|
||||
this.dataPermission = dataPermission;
|
||||
}
|
||||
|
||||
public PaginationProperties getPagination() {
|
||||
return pagination;
|
||||
}
|
||||
@@ -181,6 +154,14 @@ public class MyBatisPlusExtensionProperties {
|
||||
this.pagination = pagination;
|
||||
}
|
||||
|
||||
public boolean isOptimisticLockerEnabled() {
|
||||
return optimisticLockerEnabled;
|
||||
}
|
||||
|
||||
public void setOptimisticLockerEnabled(boolean optimisticLockerEnabled) {
|
||||
this.optimisticLockerEnabled = optimisticLockerEnabled;
|
||||
}
|
||||
|
||||
public boolean isBlockAttackPluginEnabled() {
|
||||
return blockAttackPluginEnabled;
|
||||
}
|
||||
|
||||
@@ -19,11 +19,7 @@ package top.continew.starter.data.mp.autoconfigure;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.*;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.slf4j.Logger;
|
||||
@@ -40,8 +36,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
import top.continew.starter.data.mp.autoconfigure.idgenerator.MyBatisPlusIdGeneratorConfiguration;
|
||||
import top.continew.starter.data.mp.datapermission.DataPermissionFilter;
|
||||
import top.continew.starter.data.mp.datapermission.DataPermissionHandlerImpl;
|
||||
import top.continew.starter.data.mp.handler.MybatisBaseEnumTypeHandler;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -84,18 +78,15 @@ public class MybatisPlusAutoConfiguration {
|
||||
if (!innerInterceptors.isEmpty()) {
|
||||
innerInterceptors.values().forEach(interceptor::addInnerInterceptor);
|
||||
}
|
||||
// 数据权限插件
|
||||
MyBatisPlusExtensionProperties.DataPermissionProperties dataPermissionProperties = properties
|
||||
.getDataPermission();
|
||||
if (null != dataPermissionProperties && dataPermissionProperties.isEnabled()) {
|
||||
interceptor.addInnerInterceptor(new DataPermissionInterceptor(SpringUtil
|
||||
.getBean(DataPermissionHandler.class)));
|
||||
}
|
||||
// 分页插件
|
||||
MyBatisPlusExtensionProperties.PaginationProperties paginationProperties = properties.getPagination();
|
||||
if (null != paginationProperties && paginationProperties.isEnabled()) {
|
||||
interceptor.addInnerInterceptor(this.paginationInnerInterceptor(paginationProperties));
|
||||
}
|
||||
// 乐观锁插件
|
||||
if (properties.isOptimisticLockerEnabled()) {
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||
}
|
||||
// 防全表更新与删除插件
|
||||
if (properties.isBlockAttackPluginEnabled()) {
|
||||
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
||||
@@ -111,24 +102,13 @@ public class MybatisPlusAutoConfiguration {
|
||||
MyBatisPlusIdGeneratorConfiguration.Custom.class})
|
||||
protected static class MyBatisPlusIdGeneratorAutoConfiguration {}
|
||||
|
||||
/**
|
||||
* 数据权限处理器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnEnabledDataPermission
|
||||
public DataPermissionHandler dataPermissionHandler(DataPermissionFilter dataPermissionFilter) {
|
||||
return new DataPermissionHandlerImpl(dataPermissionFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页插件配置(<a href="https://baomidou.com/pages/97710a/#paginationinnerinterceptor">PaginationInnerInterceptor</a>)
|
||||
*/
|
||||
private PaginationInnerInterceptor paginationInnerInterceptor(MyBatisPlusExtensionProperties.PaginationProperties paginationProperties) {
|
||||
// 对于单一数据库类型来说,都建议配置该值,避免每次分页都去抓取数据库类型
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = null != paginationProperties.getDbType()
|
||||
? new PaginationInnerInterceptor(paginationProperties.getDbType())
|
||||
: new PaginationInnerInterceptor();
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(paginationProperties
|
||||
.getDbType());
|
||||
paginationInnerInterceptor.setOverflow(paginationProperties.isOverflow());
|
||||
paginationInnerInterceptor.setMaxLimit(paginationProperties.getMaxLimit());
|
||||
return paginationInnerInterceptor;
|
||||
|
||||
@@ -23,5 +23,4 @@ package top.continew.starter.data.mp.service;
|
||||
* @author Charles7c
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public interface IService<T> extends com.baomidou.mybatisplus.extension.service.IService<T> {
|
||||
}
|
||||
public interface IService<T> extends com.baomidou.mybatisplus.extension.service.IService<T> {}
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
package top.continew.starter.data.mp.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import top.continew.starter.core.exception.BadRequestException;
|
||||
import top.continew.starter.core.util.ReflectUtils;
|
||||
@@ -33,7 +33,10 @@ import top.continew.starter.data.core.enums.QueryType;
|
||||
import top.continew.starter.data.core.util.SqlInjectionUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -134,7 +137,7 @@ public class QueryWrapperHelper {
|
||||
}
|
||||
// 设置了 @QueryIgnore 注解,直接忽略
|
||||
QueryIgnore queryIgnoreAnnotation = field.getAnnotation(QueryIgnore.class);
|
||||
if (null != queryIgnoreAnnotation && queryIgnoreAnnotation.value()) {
|
||||
if (null != queryIgnoreAnnotation) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 建议:数据库表列建议采用下划线连接法命名,程序变量建议采用驼峰法命名
|
||||
@@ -190,7 +193,9 @@ public class QueryWrapperHelper {
|
||||
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);
|
||||
List<Object> between = new ArrayList<>(ArrayUtil.isArray(fieldValue)
|
||||
? CollUtil.toList(fieldValue)
|
||||
: (List<Object>)fieldValue);
|
||||
ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", columnName);
|
||||
consumers.add(q -> q.between(columnName, between.get(0), between.get(1)));
|
||||
}
|
||||
@@ -199,11 +204,15 @@ public class QueryWrapperHelper {
|
||||
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));
|
||||
consumers.add(q -> q.in(columnName, ArrayUtil.isArray(fieldValue)
|
||||
? CollUtil.toList(fieldValue)
|
||||
: (Collection<Object>)fieldValue));
|
||||
}
|
||||
case NOT_IN -> {
|
||||
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
|
||||
consumers.add(q -> q.notIn(columnName, (Collection<Object>)fieldValue));
|
||||
consumers.add(q -> q.notIn(columnName, ArrayUtil.isArray(fieldValue)
|
||||
? CollUtil.toList(fieldValue)
|
||||
: (Collection<Object>)fieldValue));
|
||||
}
|
||||
case IS_NULL -> consumers.add(q -> q.isNull(columnName));
|
||||
case IS_NOT_NULL -> consumers.add(q -> q.isNotNull(columnName));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>3.2.7</version>
|
||||
<version>3.2.10</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
@@ -43,23 +43,23 @@
|
||||
|
||||
<properties>
|
||||
<!-- 项目版本号 -->
|
||||
<revision>2.6.0</revision>
|
||||
<revision>2.7.2</revision>
|
||||
<snail-job.version>1.1.2</snail-job.version>
|
||||
<sa-token.version>1.39.0</sa-token.version>
|
||||
<just-auth.version>1.16.6</just-auth.version>
|
||||
<mybatis-plus.version>3.5.7</mybatis-plus.version>
|
||||
<mybatis-plus.version>3.5.8</mybatis-plus.version>
|
||||
<mybatis-flex.version>1.9.7</mybatis-flex.version>
|
||||
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<jetcache.version>2.7.6</jetcache.version>
|
||||
<redisson.version>3.35.0</redisson.version>
|
||||
<cosid.version>2.9.6</cosid.version>
|
||||
<sms4j.version>3.2.1</sms4j.version>
|
||||
<redisson.version>3.36.0</redisson.version>
|
||||
<cosid.version>2.9.8</cosid.version>
|
||||
<sms4j.version>3.3.3</sms4j.version>
|
||||
<aj-captcha.version>1.3.0</aj-captcha.version>
|
||||
<easy-captcha.version>1.6.2</easy-captcha.version>
|
||||
<easy-excel.version>3.3.4</easy-excel.version>
|
||||
<nashorn.version>15.4</nashorn.version>
|
||||
<x-file-storage.version>2.2.0</x-file-storage.version>
|
||||
<x-file-storage.version>2.2.1</x-file-storage.version>
|
||||
<aws-s3.version>1.12.771</aws-s3.version>
|
||||
<graceful-response.version>5.0.0-boot3</graceful-response.version>
|
||||
<crane4j.version>2.9.0</crane4j.version>
|
||||
@@ -346,14 +346,12 @@
|
||||
<artifactId>continew-starter-extension-crud-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展模块 - CRUD - MyBatis Plus ORM 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-crud-mp</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展模块 - CRUD - MyBatis Flex ORM 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
@@ -361,6 +359,32 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展模块 - 数据权限 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-datapermission-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 扩展模块 - 数据权限 - MyBatis Plus ORM 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-datapermission-mp</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展模块 - 多租户 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-tenant-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 扩展模块 - 多租户 - MyBatis Plus ORM 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-tenant-mp</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 认证模块 - JustAuth -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
|
||||
@@ -53,5 +53,11 @@
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-file-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- API 文档模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-api-doc</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -42,4 +42,11 @@ public @interface DictField {
|
||||
* @return 值字段名
|
||||
*/
|
||||
String valueKey() default "id";
|
||||
|
||||
/**
|
||||
* 额外信息字段名
|
||||
*
|
||||
* @return 额外信息字段名
|
||||
*/
|
||||
String[] extraKeys() default {};
|
||||
}
|
||||
|
||||
@@ -31,5 +31,4 @@ import java.lang.annotation.*;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import({CrudRestControllerAutoConfiguration.class})
|
||||
public @interface EnableCrudRestController {
|
||||
}
|
||||
public @interface EnableCrudRestController {}
|
||||
|
||||
@@ -71,4 +71,11 @@ public @interface TreeField {
|
||||
* @return 递归深度
|
||||
*/
|
||||
int deep() default -1;
|
||||
|
||||
/**
|
||||
* 根节点 ID
|
||||
*
|
||||
* @return 根节点 ID
|
||||
*/
|
||||
long rootId() default 0L;
|
||||
}
|
||||
|
||||
@@ -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.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* CRUD 配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.2
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.CRUD)
|
||||
public class CrudProperties {
|
||||
|
||||
/**
|
||||
* 树配置
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private CrudTreeProperties tree = new CrudTreeProperties();
|
||||
|
||||
public CrudTreeProperties getTree() {
|
||||
return tree;
|
||||
}
|
||||
|
||||
public void setTree(CrudTreeProperties tree) {
|
||||
this.tree = tree;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
@@ -36,6 +37,7 @@ import org.springframework.web.servlet.resource.ResourceUrlProvider;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(CrudProperties.class)
|
||||
public class CrudRestControllerAutoConfiguration extends DelegatingWebMvcConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CrudRestControllerAutoConfiguration.class);
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.lang.tree.TreeNodeConfig;
|
||||
import top.continew.starter.core.util.validate.CheckUtils;
|
||||
import top.continew.starter.extension.crud.annotation.TreeField;
|
||||
|
||||
/**
|
||||
* CRUD 树列表配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.2
|
||||
*/
|
||||
public class CrudTreeProperties {
|
||||
|
||||
/**
|
||||
* ID 字段名
|
||||
*/
|
||||
private String idKey = "id";
|
||||
|
||||
/**
|
||||
* 父 ID 字段名
|
||||
*
|
||||
*/
|
||||
private String parentIdKey = "parentId";
|
||||
|
||||
/**
|
||||
* 名称字段名
|
||||
*
|
||||
*/
|
||||
private String nameKey = "name";
|
||||
|
||||
/**
|
||||
* 排序字段名
|
||||
*
|
||||
*/
|
||||
private String weightKey = "weight";
|
||||
|
||||
/**
|
||||
* 子列表字段名
|
||||
*
|
||||
*/
|
||||
private String childrenKey = "children";
|
||||
|
||||
/**
|
||||
* 递归深度(< 0 不限制)
|
||||
*/
|
||||
private Integer deep = -1;
|
||||
|
||||
/**
|
||||
* 根节点 ID
|
||||
*/
|
||||
private Long rootId = 0L;
|
||||
|
||||
public String getIdKey() {
|
||||
return idKey;
|
||||
}
|
||||
|
||||
public void setIdKey(String idKey) {
|
||||
this.idKey = idKey;
|
||||
}
|
||||
|
||||
public String getParentIdKey() {
|
||||
return parentIdKey;
|
||||
}
|
||||
|
||||
public void setParentIdKey(String parentIdKey) {
|
||||
this.parentIdKey = parentIdKey;
|
||||
}
|
||||
|
||||
public String getNameKey() {
|
||||
return nameKey;
|
||||
}
|
||||
|
||||
public void setNameKey(String nameKey) {
|
||||
this.nameKey = nameKey;
|
||||
}
|
||||
|
||||
public String getWeightKey() {
|
||||
return weightKey;
|
||||
}
|
||||
|
||||
public void setWeightKey(String weightKey) {
|
||||
this.weightKey = weightKey;
|
||||
}
|
||||
|
||||
public String getChildrenKey() {
|
||||
return childrenKey;
|
||||
}
|
||||
|
||||
public void setChildrenKey(String childrenKey) {
|
||||
this.childrenKey = childrenKey;
|
||||
}
|
||||
|
||||
public Integer getDeep() {
|
||||
return deep;
|
||||
}
|
||||
|
||||
public void setDeep(Integer deep) {
|
||||
this.deep = deep;
|
||||
}
|
||||
|
||||
public Long getRootId() {
|
||||
return rootId;
|
||||
}
|
||||
|
||||
public void setRootId(Long rootId) {
|
||||
this.rootId = rootId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 {@link TreeNodeConfig} 对象
|
||||
*
|
||||
* @return {@link TreeNodeConfig} 对象
|
||||
*/
|
||||
public TreeNodeConfig genTreeNodeConfig() {
|
||||
return TreeNodeConfig.DEFAULT_CONFIG.setIdKey(idKey)
|
||||
.setParentIdKey(parentIdKey)
|
||||
.setNameKey(nameKey)
|
||||
.setWeightKey(weightKey)
|
||||
.setChildrenKey(childrenKey)
|
||||
.setDeep(deep < 0 ? null : deep);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 @TreeField 配置生成树结构配置
|
||||
*
|
||||
* @param treeField 树结构字段注解
|
||||
* @return 树结构配置
|
||||
*/
|
||||
public 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());
|
||||
}
|
||||
}
|
||||
@@ -28,34 +28,42 @@ public enum Api {
|
||||
* 所有 API
|
||||
*/
|
||||
ALL,
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*/
|
||||
PAGE,
|
||||
/**
|
||||
* 树列表
|
||||
*/
|
||||
TREE,
|
||||
|
||||
/**
|
||||
* 列表
|
||||
*/
|
||||
LIST,
|
||||
|
||||
/**
|
||||
* 树列表
|
||||
*/
|
||||
TREE,
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
GET,
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
ADD,
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
UPDATE,
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
DELETE,
|
||||
|
||||
/**
|
||||
* 导出
|
||||
*/
|
||||
|
||||
@@ -54,11 +54,11 @@ public class LabelValueResp<T> implements Serializable {
|
||||
private Boolean disabled;
|
||||
|
||||
/**
|
||||
* 扩展
|
||||
* 额外数据
|
||||
*/
|
||||
@Schema(description = "扩展")
|
||||
@Schema(description = "额外数据")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private Object extend;
|
||||
private Object extra;
|
||||
|
||||
public LabelValueResp() {
|
||||
}
|
||||
@@ -68,10 +68,10 @@ public class LabelValueResp<T> implements Serializable {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public LabelValueResp(String label, T value, Object extend) {
|
||||
public LabelValueResp(String label, T value, Object extra) {
|
||||
this.label = label;
|
||||
this.value = value;
|
||||
this.extend = extend;
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
public LabelValueResp(String label, T value, Boolean disabled) {
|
||||
@@ -104,11 +104,11 @@ public class LabelValueResp<T> implements Serializable {
|
||||
this.disabled = disabled;
|
||||
}
|
||||
|
||||
public Object getExtend() {
|
||||
return extend;
|
||||
public Object getExtra() {
|
||||
return extra;
|
||||
}
|
||||
|
||||
public void setExtend(Object extend) {
|
||||
this.extend = extend;
|
||||
public void setExtra(Object extra) {
|
||||
this.extra = extra;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
@@ -33,13 +33,11 @@ public interface ValidateGroup extends Default {
|
||||
/**
|
||||
* 分组校验-创建
|
||||
*/
|
||||
interface Add extends Crud {
|
||||
}
|
||||
interface Add extends Crud {}
|
||||
|
||||
/**
|
||||
* 分组校验-修改
|
||||
*/
|
||||
interface Update extends Crud {
|
||||
}
|
||||
interface Update extends Crud {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,21 +71,6 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
return baseService.page(query, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @return 树列表信息
|
||||
*/
|
||||
@Operation(summary = "查询树列表", description = "查询树列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/tree")
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return baseService.tree(query, sortQuery, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
@@ -101,6 +86,21 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
return baseService.list(query, sortQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @return 树列表信息
|
||||
*/
|
||||
@Operation(summary = "查询树列表", description = "查询树列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/tree")
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return baseService.tree(query, sortQuery, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询详情
|
||||
*
|
||||
|
||||
@@ -18,8 +18,8 @@ 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.SortQuery;
|
||||
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.LabelValueResp;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
|
||||
@@ -46,16 +46,6 @@ public interface BaseService<L, D, Q, C> {
|
||||
*/
|
||||
PageResp<L> page(Q query, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段)
|
||||
* @return 树列表信息
|
||||
*/
|
||||
List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
@@ -65,6 +55,20 @@ public interface BaseService<L, D, Q, C> {
|
||||
*/
|
||||
List<L> list(Q query, SortQuery sortQuery);
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
* <p>
|
||||
* 虽然提供了查询条件,但不建议使用,容易因缺失根节点导致树节点丢失。
|
||||
* 建议在前端进行查询过滤,如需使用建议重写方法。
|
||||
* </p>
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @DictField 局部配置)
|
||||
* @return 树列表信息
|
||||
*/
|
||||
List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple);
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
*
|
||||
|
||||
@@ -22,27 +22,30 @@ import cn.hutool.core.bean.copier.CopyOptions;
|
||||
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.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.apache.poi.ss.formula.functions.T;
|
||||
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.mf.base.BaseMapper;
|
||||
import top.continew.starter.data.mf.util.QueryWrapperHelper;
|
||||
import top.continew.starter.data.mf.service.impl.ServiceImpl;
|
||||
import top.continew.starter.data.mf.util.QueryWrapperHelper;
|
||||
import top.continew.starter.extension.crud.annotation.TreeField;
|
||||
import top.continew.starter.extension.crud.autoconfigure.CrudProperties;
|
||||
import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties;
|
||||
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.extension.crud.model.entity.BaseIdDO;
|
||||
import top.continew.starter.file.excel.util.ExcelUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -79,26 +82,39 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
return pageResp;
|
||||
}
|
||||
|
||||
@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 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;
|
||||
CrudProperties crudProperties = SpringUtil.getBean(CrudProperties.class);
|
||||
CrudTreeProperties treeProperties = crudProperties.getTree();
|
||||
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class);
|
||||
if (!isSimple) {
|
||||
// 根据 @TreeField 配置生成树结构配置
|
||||
treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField);
|
||||
TreeNodeConfig treeNodeConfig;
|
||||
Long rootId;
|
||||
// 简单树(下拉列表)使用全局配置结构,复杂树(表格)使用局部配置
|
||||
if (isSimple) {
|
||||
treeNodeConfig = treeProperties.genTreeNodeConfig();
|
||||
rootId = treeProperties.getRootId();
|
||||
} else {
|
||||
treeNodeConfig = treeProperties.genTreeNodeConfig(treeField);
|
||||
rootId = treeField.rootId();
|
||||
}
|
||||
// 构建树
|
||||
return TreeUtils.build(list, treeNodeConfig, (node, tree) -> {
|
||||
// 转换器
|
||||
return TreeUtil.build(list, rootId, 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
|
||||
@@ -109,13 +125,6 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
});
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
@@ -30,13 +30,13 @@ 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.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.BaseIdResp;
|
||||
import top.continew.starter.extension.crud.util.ValidateGroup;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
import top.continew.starter.extension.crud.service.BaseService;
|
||||
import top.continew.starter.extension.crud.util.ValidateGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -71,21 +71,6 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
return baseService.page(query, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @return 树列表信息
|
||||
*/
|
||||
@Operation(summary = "查询树列表", description = "查询树列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/tree")
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return baseService.tree(query, sortQuery, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
@@ -101,6 +86,21 @@ public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q,
|
||||
return baseService.list(query, sortQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @return 树列表信息
|
||||
*/
|
||||
@Operation(summary = "查询树列表", description = "查询树列表")
|
||||
@ResponseBody
|
||||
@GetMapping("/tree")
|
||||
public List<Tree<Long>> tree(Q query, SortQuery sortQuery) {
|
||||
this.checkPermission(Api.LIST);
|
||||
return baseService.tree(query, sortQuery, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询详情
|
||||
*
|
||||
|
||||
@@ -46,16 +46,6 @@ public interface BaseService<L, D, Q, C> {
|
||||
*/
|
||||
PageResp<L> page(Q query, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段)
|
||||
* @return 树列表信息
|
||||
*/
|
||||
List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
@@ -65,6 +55,20 @@ public interface BaseService<L, D, Q, C> {
|
||||
*/
|
||||
List<L> list(Q query, SortQuery sortQuery);
|
||||
|
||||
/**
|
||||
* 查询树列表
|
||||
* <p>
|
||||
* 虽然提供了查询条件,但不建议使用,容易因缺失根节点导致树节点丢失。
|
||||
* 建议在前端进行查询过滤,如需使用建议重写方法。
|
||||
* </p>
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param sortQuery 排序查询条件
|
||||
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @DictField 局部配置)
|
||||
* @return 树列表信息
|
||||
*/
|
||||
List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple);
|
||||
|
||||
/**
|
||||
* 查询详情
|
||||
*
|
||||
|
||||
@@ -20,8 +20,10 @@ 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.convert.Convert;
|
||||
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.map.MapUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
@@ -42,20 +44,18 @@ import top.continew.starter.data.mp.service.impl.ServiceImpl;
|
||||
import top.continew.starter.data.mp.util.QueryWrapperHelper;
|
||||
import top.continew.starter.extension.crud.annotation.DictField;
|
||||
import top.continew.starter.extension.crud.annotation.TreeField;
|
||||
import top.continew.starter.extension.crud.autoconfigure.CrudProperties;
|
||||
import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties;
|
||||
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.LabelValueResp;
|
||||
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.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 业务实现基类
|
||||
@@ -86,28 +86,41 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
return pageResp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<L> list(Q query, SortQuery sortQuery) {
|
||||
List<L> list = this.list(query, sortQuery, this.getListClass());
|
||||
list.forEach(this::fill);
|
||||
return list;
|
||||
}
|
||||
|
||||
@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 = this.getListClass().getDeclaredAnnotation(TreeField.class);
|
||||
if (!isSimple) {
|
||||
// 根据 @TreeField 配置生成树结构配置
|
||||
treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField);
|
||||
CrudProperties crudProperties = SpringUtil.getBean(CrudProperties.class);
|
||||
CrudTreeProperties treeProperties = crudProperties.getTree();
|
||||
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class);
|
||||
TreeNodeConfig treeNodeConfig;
|
||||
Long rootId;
|
||||
// 简单树(下拉列表)使用全局配置结构,复杂树(表格)使用局部配置
|
||||
if (isSimple) {
|
||||
treeNodeConfig = treeProperties.genTreeNodeConfig();
|
||||
rootId = treeProperties.getRootId();
|
||||
} else {
|
||||
treeNodeConfig = treeProperties.genTreeNodeConfig(treeField);
|
||||
rootId = treeField.rootId();
|
||||
}
|
||||
// 构建树
|
||||
return TreeUtils.build(list, treeNodeConfig, (node, tree) -> {
|
||||
// 转换器
|
||||
return TreeUtil.build(list, rootId, 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(this.getListClass());
|
||||
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
|
||||
@@ -116,13 +129,6 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<L> list(Q query, SortQuery sortQuery) {
|
||||
List<L> list = this.list(query, sortQuery, this.getListClass());
|
||||
list.forEach(this::fill);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public D get(Long id) {
|
||||
T entity = super.getById(id, false);
|
||||
@@ -133,19 +139,40 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdD
|
||||
|
||||
@Override
|
||||
public List<LabelValueResp> listDict(Q query, SortQuery sortQuery) {
|
||||
QueryWrapper<T> queryWrapper = this.buildQueryWrapper(query);
|
||||
this.sort(queryWrapper, sortQuery);
|
||||
DictField dictField = super.getEntityClass().getDeclaredAnnotation(DictField.class);
|
||||
CheckUtils.throwIfNull(dictField, "请添加并配置 @DictField 字典结构信息");
|
||||
// 指定查询字典字段
|
||||
queryWrapper.select(dictField.labelKey(), dictField.valueKey());
|
||||
List<T> entityList = baseMapper.selectList(queryWrapper);
|
||||
List<L> list = this.list(query, sortQuery);
|
||||
// 解析映射
|
||||
Map<String, String> fieldMapping = MapUtil.newHashMap(2);
|
||||
fieldMapping.put(CharSequenceUtil.toCamelCase(dictField.labelKey()), "label");
|
||||
fieldMapping.put(CharSequenceUtil.toCamelCase(dictField.valueKey()), "value");
|
||||
return BeanUtil.copyToList(entityList, LabelValueResp.class, CopyOptions.create()
|
||||
.setFieldMapping(fieldMapping));
|
||||
List<LabelValueResp> respList = new ArrayList<>(list.size());
|
||||
String labelKey = dictField.labelKey().contains(StringConstants.DOT)
|
||||
? CharSequenceUtil.subAfter(dictField.labelKey(), StringConstants.DOT, true)
|
||||
: dictField.labelKey();
|
||||
String valueKey = dictField.valueKey().contains(StringConstants.DOT)
|
||||
? CharSequenceUtil.subAfter(dictField.valueKey(), StringConstants.DOT, true)
|
||||
: dictField.valueKey();
|
||||
List<String> extraFieldNames = Arrays.stream(dictField.extraKeys())
|
||||
.map(extraKey -> extraKey.contains(StringConstants.DOT)
|
||||
? CharSequenceUtil.subAfter(extraKey, StringConstants.DOT, true)
|
||||
: extraKey)
|
||||
.map(CharSequenceUtil::toCamelCase)
|
||||
.toList();
|
||||
for (L entity : list) {
|
||||
LabelValueResp<Object> labelValueResp = new LabelValueResp<>();
|
||||
labelValueResp.setLabel(Convert.toStr(ReflectUtil.getFieldValue(entity, CharSequenceUtil
|
||||
.toCamelCase(labelKey))));
|
||||
labelValueResp.setValue(ReflectUtil.getFieldValue(entity, CharSequenceUtil.toCamelCase(valueKey)));
|
||||
respList.add(labelValueResp);
|
||||
if (CollUtil.isEmpty(extraFieldNames)) {
|
||||
continue;
|
||||
}
|
||||
// 额外数据
|
||||
Map<String, Object> extraMap = MapUtil.newHashMap(dictField.extraKeys().length);
|
||||
for (String extraFieldName : extraFieldNames) {
|
||||
extraMap.put(extraFieldName, ReflectUtil.getFieldValue(entity, extraFieldName));
|
||||
}
|
||||
labelValueResp.setExtra(extraMap);
|
||||
}
|
||||
return respList;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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-datapermission</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-datapermission-core</artifactId>
|
||||
<description>ContiNew Starter 扩展模块 - 数据权限 - 核心模块</description>
|
||||
</project>
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.data.mp.datapermission;
|
||||
package top.continew.starter.extension.datapermission.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@@ -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.datapermission.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* 数据权限配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.DATA_PERMISSION)
|
||||
public class DataPermissionProperties {
|
||||
|
||||
/**
|
||||
* 是否启用多租户
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.data.mp.datapermission;
|
||||
package top.continew.starter.extension.datapermission.enums;
|
||||
|
||||
/**
|
||||
* 数据权限枚举
|
||||
@@ -14,15 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.data.mp.datapermission;
|
||||
package top.continew.starter.extension.datapermission.filter;
|
||||
|
||||
import top.continew.starter.extension.datapermission.model.UserContext;
|
||||
|
||||
/**
|
||||
* 数据权限过滤器接口
|
||||
* 数据权限用户上下文提供者
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface DataPermissionFilter {
|
||||
public interface DataPermissionUserContextProvider {
|
||||
|
||||
/**
|
||||
* 是否过滤
|
||||
@@ -32,9 +34,9 @@ public interface DataPermissionFilter {
|
||||
boolean isFilter();
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
* 获取用户上下文
|
||||
*
|
||||
* @return 当前用户信息
|
||||
* @return 用户上下文
|
||||
*/
|
||||
DataPermissionCurrentUser getCurrentUser();
|
||||
UserContext getUserContext();
|
||||
}
|
||||
@@ -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.datapermission.model;
|
||||
|
||||
import top.continew.starter.extension.datapermission.enums.DataScope;
|
||||
|
||||
/**
|
||||
* 角色上下文
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class RoleContext {
|
||||
|
||||
/**
|
||||
* 角色 ID
|
||||
*/
|
||||
private String roleId;
|
||||
|
||||
/**
|
||||
* 数据权限
|
||||
*/
|
||||
private DataScope dataScope;
|
||||
|
||||
public RoleContext() {
|
||||
}
|
||||
|
||||
public RoleContext(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;
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.data.mp.datapermission;
|
||||
package top.continew.starter.extension.datapermission.model;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 当前用户信息
|
||||
* 用户上下文
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class DataPermissionCurrentUser {
|
||||
public class UserContext {
|
||||
|
||||
/**
|
||||
* 用户 ID
|
||||
@@ -34,53 +34,13 @@ public class DataPermissionCurrentUser {
|
||||
/**
|
||||
* 角色列表
|
||||
*/
|
||||
private Set<CurrentUserRole> roles;
|
||||
private Set<RoleContext> 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;
|
||||
}
|
||||
@@ -89,11 +49,11 @@ public class DataPermissionCurrentUser {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Set<CurrentUserRole> getRoles() {
|
||||
public Set<RoleContext> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(Set<CurrentUserRole> roles) {
|
||||
public void setRoles(Set<RoleContext> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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-datapermission</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-datapermission-mp</artifactId>
|
||||
<description>ContiNew Starter 扩展模块 - 数据权限 - MyBatis Plus ORM 模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- MyBatis Plus(MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-extension</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-datapermission-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -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.datapermission.autoconfigure;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
|
||||
import top.continew.starter.extension.datapermission.handler.DefaultDataPermissionHandler;
|
||||
|
||||
/**
|
||||
* 数据权限自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(DataPermissionProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.DATA_PERMISSION, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class DataPermissionAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DataPermissionAutoConfiguration.class);
|
||||
|
||||
private DataPermissionAutoConfiguration() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据权限拦截器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DataPermissionInterceptor dataPermissionInterceptor(DataPermissionHandler dataPermissionHandler) {
|
||||
return new DataPermissionInterceptor(dataPermissionHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据权限处理器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DataPermissionHandler dataPermissionHandler(DataPermissionUserContextProvider dataPermissionUserContextProvider) {
|
||||
return new DefaultDataPermissionHandler(dataPermissionUserContextProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据权限用户上下文提供者
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DataPermissionUserContextProvider dataPermissionUserContextProvider() {
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
|
||||
.forClass(DataPermissionUserContextProvider.class));
|
||||
}
|
||||
throw new NoSuchBeanDefinitionException(DataPermissionUserContextProvider.class);
|
||||
}
|
||||
|
||||
static {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'DataPermission' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.data.mp.datapermission;
|
||||
package top.continew.starter.extension.datapermission.handler;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
@@ -22,37 +22,44 @@ import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.Function;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
import net.sf.jsqlparser.expression.Parenthesis;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.select.*;
|
||||
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.SelectItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.extension.datapermission.annotation.DataPermission;
|
||||
import top.continew.starter.extension.datapermission.enums.DataScope;
|
||||
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
|
||||
import top.continew.starter.extension.datapermission.model.RoleContext;
|
||||
import top.continew.starter.extension.datapermission.model.UserContext;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 数据权限处理器实现类
|
||||
* 默认数据权限处理器
|
||||
*
|
||||
* @author <a href="https://gitee.com/baomidou/mybatis-plus/issues/I37I90">DataPermissionInterceptor 如何使用?</a>
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DataPermissionHandlerImpl.class);
|
||||
private final DataPermissionFilter dataPermissionFilter;
|
||||
private static final Logger log = LoggerFactory.getLogger(DefaultDataPermissionHandler.class);
|
||||
private final DataPermissionUserContextProvider dataPermissionUserContextProvider;
|
||||
|
||||
public DataPermissionHandlerImpl(DataPermissionFilter dataPermissionFilter) {
|
||||
this.dataPermissionFilter = dataPermissionFilter;
|
||||
public DefaultDataPermissionHandler(DataPermissionUserContextProvider dataPermissionUserContextProvider) {
|
||||
this.dataPermissionUserContextProvider = dataPermissionUserContextProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,7 +75,7 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
if (null == dataPermission || !CharSequenceUtil.equalsAny(methodName, name, name + "_COUNT")) {
|
||||
continue;
|
||||
}
|
||||
if (dataPermissionFilter.isFilter()) {
|
||||
if (dataPermissionUserContextProvider.isFilter()) {
|
||||
return buildDataScopeFilter(dataPermission, where);
|
||||
}
|
||||
}
|
||||
@@ -87,23 +94,23 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
*/
|
||||
private Expression buildDataScopeFilter(DataPermission dataPermission, Expression where) {
|
||||
Expression expression = null;
|
||||
DataPermissionCurrentUser currentUser = dataPermissionFilter.getCurrentUser();
|
||||
Set<DataPermissionCurrentUser.CurrentUserRole> roles = currentUser.getRoles();
|
||||
for (DataPermissionCurrentUser.CurrentUserRole role : roles) {
|
||||
DataScope dataScope = role.getDataScope();
|
||||
UserContext userContext = dataPermissionUserContextProvider.getUserContext();
|
||||
Set<RoleContext> roles = userContext.getRoles();
|
||||
for (RoleContext roleContext : roles) {
|
||||
DataScope dataScope = roleContext.getDataScope();
|
||||
if (DataScope.ALL.equals(dataScope)) {
|
||||
return where;
|
||||
}
|
||||
switch (dataScope) {
|
||||
case DEPT_AND_CHILD -> expression = this
|
||||
.buildDeptAndChildExpression(dataPermission, currentUser, expression);
|
||||
case DEPT -> expression = this.buildDeptExpression(dataPermission, currentUser, expression);
|
||||
case SELF -> expression = this.buildSelfExpression(dataPermission, currentUser, expression);
|
||||
case CUSTOM -> expression = this.buildCustomExpression(dataPermission, role, expression);
|
||||
.buildDeptAndChildExpression(dataPermission, userContext, expression);
|
||||
case DEPT -> expression = this.buildDeptExpression(dataPermission, userContext, expression);
|
||||
case SELF -> expression = this.buildSelfExpression(dataPermission, userContext, expression);
|
||||
case CUSTOM -> expression = this.buildCustomExpression(dataPermission, roleContext, expression);
|
||||
default -> throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(dataScope));
|
||||
}
|
||||
}
|
||||
return null != where ? new AndExpression(where, new Parenthesis(expression)) : expression;
|
||||
return null != where ? new AndExpression(where, new ParenthesedExpressionList<>(expression)) : expression;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,12 +122,12 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
* </p>
|
||||
*
|
||||
* @param dataPermission 数据权限
|
||||
* @param currentUser 当前用户
|
||||
* @param userContext 用户上下文
|
||||
* @param expression 处理前的表达式
|
||||
* @return 处理完后的表达式
|
||||
*/
|
||||
private Expression buildDeptAndChildExpression(DataPermission dataPermission,
|
||||
DataPermissionCurrentUser currentUser,
|
||||
UserContext userContext,
|
||||
Expression expression) {
|
||||
ParenthesedSelect subSelect = new ParenthesedSelect();
|
||||
PlainSelect select = new PlainSelect();
|
||||
@@ -128,10 +135,10 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
select.setFromItem(new Table(dataPermission.deptTableAlias()));
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(new Column(dataPermission.id()));
|
||||
equalsTo.setRightExpression(new LongValue(currentUser.getDeptId()));
|
||||
equalsTo.setRightExpression(new LongValue(userContext.getDeptId()));
|
||||
Function function = new Function();
|
||||
function.setName("find_in_set");
|
||||
function.setParameters(new ExpressionList(new LongValue(currentUser.getDeptId()), new Column("ancestors")));
|
||||
function.setParameters(new ExpressionList<>(new LongValue(userContext.getDeptId()), new Column("ancestors")));
|
||||
select.setWhere(new OrExpression(equalsTo, function));
|
||||
subSelect.setSelect(select);
|
||||
// 构建父查询
|
||||
@@ -149,16 +156,16 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
* </p>
|
||||
*
|
||||
* @param dataPermission 数据权限
|
||||
* @param currentUser 当前用户
|
||||
* @param userContext 用户上下文
|
||||
* @param expression 处理前的表达式
|
||||
* @return 处理完后的表达式
|
||||
*/
|
||||
private Expression buildDeptExpression(DataPermission dataPermission,
|
||||
DataPermissionCurrentUser currentUser,
|
||||
UserContext userContext,
|
||||
Expression expression) {
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
|
||||
equalsTo.setRightExpression(new LongValue(currentUser.getDeptId()));
|
||||
equalsTo.setRightExpression(new LongValue(userContext.getDeptId()));
|
||||
return null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
|
||||
}
|
||||
|
||||
@@ -170,16 +177,16 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
* </p>
|
||||
*
|
||||
* @param dataPermission 数据权限
|
||||
* @param currentUser 当前用户
|
||||
* @param userContext 用户上下文
|
||||
* @param expression 处理前的表达式
|
||||
* @return 处理完后的表达式
|
||||
*/
|
||||
private Expression buildSelfExpression(DataPermission dataPermission,
|
||||
DataPermissionCurrentUser currentUser,
|
||||
UserContext userContext,
|
||||
Expression expression) {
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.userId()));
|
||||
equalsTo.setRightExpression(new LongValue(currentUser.getUserId()));
|
||||
equalsTo.setRightExpression(new LongValue(userContext.getUserId()));
|
||||
return null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
|
||||
}
|
||||
|
||||
@@ -192,12 +199,12 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
* </p>
|
||||
*
|
||||
* @param dataPermission 数据权限
|
||||
* @param role 当前用户角色
|
||||
* @param roleContext 角色上下文
|
||||
* @param expression 处理前的表达式
|
||||
* @return 处理完后的表达式
|
||||
*/
|
||||
private Expression buildCustomExpression(DataPermission dataPermission,
|
||||
DataPermissionCurrentUser.CurrentUserRole role,
|
||||
RoleContext roleContext,
|
||||
Expression expression) {
|
||||
ParenthesedSelect subSelect = new ParenthesedSelect();
|
||||
PlainSelect select = new PlainSelect();
|
||||
@@ -205,7 +212,7 @@ public class DataPermissionHandlerImpl implements DataPermissionHandler {
|
||||
select.setFromItem(new Table(dataPermission.roleDeptTableAlias()));
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(new Column(dataPermission.roleId()));
|
||||
equalsTo.setRightExpression(new LongValue(role.getRoleId()));
|
||||
equalsTo.setRightExpression(new LongValue(roleContext.getRoleId()));
|
||||
select.setWhere(equalsTo);
|
||||
subSelect.setSelect(select);
|
||||
// 构建父查询
|
||||
@@ -0,0 +1 @@
|
||||
top.continew.starter.extension.datapermission.autoconfigure.DataPermissionAutoConfiguration
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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-datapermission</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<description>ContiNew Starter 扩展模块 - 数据权限</description>
|
||||
|
||||
<modules>
|
||||
<module>continew-starter-extension-datapermission-core</module>
|
||||
<module>continew-starter-extension-datapermission-mp</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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-tenant</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-tenant-core</artifactId>
|
||||
<description>ContiNew Starter 扩展模块 - 多租户 - 核心模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- TTL(线程间传递 ThreadLocal,异步执行时上下文传递的解决方案) -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -14,22 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.data.mp.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
package top.continew.starter.extension.tenant.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 是否启用数据权限注解
|
||||
* 多租户数据源级隔离忽略注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
* @since 2.7.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@ConditionalOnProperty(prefix = "mybatis-plus.extension.data-permission", name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
public @interface ConditionalOnEnabledDataPermission {
|
||||
}
|
||||
public @interface TenantDataSourceIgnore {
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.tenant.autoconfigure;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import top.continew.starter.extension.tenant.context.TenantContext;
|
||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||
|
||||
/**
|
||||
* 租户拦截器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class TenantInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final TenantProperties tenantProperties;
|
||||
|
||||
public TenantInterceptor(TenantProperties tenantProperties) {
|
||||
this.tenantProperties = tenantProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
String tenantId = request.getHeader(tenantProperties.getTenantIdHeader());
|
||||
TenantContext tenantContext = new TenantContext();
|
||||
tenantContext.setTenantId(Convert.toLong(tenantId));
|
||||
TenantContextHolder.setContext(tenantContext);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.tenant.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 租户配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.TENANT)
|
||||
public class TenantProperties {
|
||||
|
||||
/**
|
||||
* 是否启用多租户
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 租户隔离级别
|
||||
*/
|
||||
private TenantIsolationLevel isolationLevel = TenantIsolationLevel.LINE;
|
||||
|
||||
/**
|
||||
* 租户 ID 列名
|
||||
*/
|
||||
private String tenantIdColumn = "tenant_id";
|
||||
|
||||
/**
|
||||
* 请求头中租户 ID 键名
|
||||
*/
|
||||
private String tenantIdHeader = "X-Tenant-Id";
|
||||
|
||||
/**
|
||||
* 超级租户 ID
|
||||
*/
|
||||
private Long superTenantId = 1L;
|
||||
|
||||
/**
|
||||
* 忽略表(忽略拼接多租户条件)
|
||||
*/
|
||||
private List<String> ignoreTables;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public TenantIsolationLevel getIsolationLevel() {
|
||||
return isolationLevel;
|
||||
}
|
||||
|
||||
public void setIsolationLevel(TenantIsolationLevel isolationLevel) {
|
||||
this.isolationLevel = isolationLevel;
|
||||
}
|
||||
|
||||
public String getTenantIdColumn() {
|
||||
return tenantIdColumn;
|
||||
}
|
||||
|
||||
public void setTenantIdColumn(String tenantIdColumn) {
|
||||
this.tenantIdColumn = tenantIdColumn;
|
||||
}
|
||||
|
||||
public String getTenantIdHeader() {
|
||||
return tenantIdHeader;
|
||||
}
|
||||
|
||||
public void setTenantIdHeader(String tenantIdHeader) {
|
||||
this.tenantIdHeader = tenantIdHeader;
|
||||
}
|
||||
|
||||
public Long getSuperTenantId() {
|
||||
return superTenantId;
|
||||
}
|
||||
|
||||
public void setSuperTenantId(Long superTenantId) {
|
||||
this.superTenantId = superTenantId;
|
||||
}
|
||||
|
||||
public List<String> getIgnoreTables() {
|
||||
return ignoreTables;
|
||||
}
|
||||
|
||||
public void setIgnoreTables(List<String> ignoreTables) {
|
||||
this.ignoreTables = ignoreTables;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.tenant.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* 多租户 Web MVC 自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.TENANT, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class TenantWebMvcAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private final TenantProperties tenantProperties;
|
||||
|
||||
public TenantWebMvcAutoConfiguration(TenantProperties tenantProperties) {
|
||||
this.tenantProperties = tenantProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new TenantInterceptor(tenantProperties));
|
||||
}
|
||||
}
|
||||
@@ -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.tenant.config;
|
||||
|
||||
/**
|
||||
* 租户数据源配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class TenantDataSource {
|
||||
|
||||
/**
|
||||
* 连接池名称
|
||||
*/
|
||||
private String poolName;
|
||||
|
||||
/**
|
||||
* 驱动类名
|
||||
*/
|
||||
private String driverClassName;
|
||||
|
||||
/**
|
||||
* 数据库连接地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 数据库用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 数据库密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
public String getPoolName() {
|
||||
return poolName;
|
||||
}
|
||||
|
||||
public void setPoolName(String poolName) {
|
||||
this.poolName = poolName;
|
||||
}
|
||||
|
||||
public String getDriverClassName() {
|
||||
return driverClassName;
|
||||
}
|
||||
|
||||
public void setDriverClassName(String driverClassName) {
|
||||
this.driverClassName = driverClassName;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
@@ -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.tenant.config;
|
||||
|
||||
/**
|
||||
* 租户数据源提供者
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public interface TenantDataSourceProvider {
|
||||
|
||||
/**
|
||||
* 根据租户 ID 获取数据源配置
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @return 数据源配置
|
||||
*/
|
||||
TenantDataSource getByTenantId(String tenantId);
|
||||
}
|
||||
@@ -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.tenant.context;
|
||||
|
||||
/**
|
||||
* 租户上下文
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class TenantContext {
|
||||
|
||||
/**
|
||||
* 租户 ID
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
public Long getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(Long tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.tenant.context;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 租户上下文 Holder
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class TenantContextHolder {
|
||||
|
||||
private static final TransmittableThreadLocal<TenantContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
|
||||
|
||||
private TenantContextHolder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上下文
|
||||
*
|
||||
* @param context 上下文
|
||||
*/
|
||||
public static void setContext(TenantContext context) {
|
||||
CONTEXT_HOLDER.set(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文
|
||||
*
|
||||
* @return 上下文
|
||||
*/
|
||||
public static TenantContext getContext() {
|
||||
return CONTEXT_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除上下文
|
||||
*/
|
||||
public static void clearContext() {
|
||||
CONTEXT_HOLDER.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户 ID
|
||||
*
|
||||
* @return 租户 ID
|
||||
*/
|
||||
public static Long getTenantId() {
|
||||
return Optional.ofNullable(getContext()).map(TenantContext::getTenantId).orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.tenant.enums;
|
||||
|
||||
/**
|
||||
* 租户隔离级别
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public enum TenantIsolationLevel {
|
||||
|
||||
/**
|
||||
* 行级
|
||||
*/
|
||||
LINE,
|
||||
|
||||
/**
|
||||
* 数据源级
|
||||
*/
|
||||
DATASOURCE
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.tenant.handler;
|
||||
|
||||
import top.continew.starter.extension.tenant.config.TenantDataSource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* 租户数据源级隔离处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public interface TenantDataSourceHandler {
|
||||
|
||||
/**
|
||||
* 切换数据源
|
||||
*
|
||||
* @param dataSourceName 数据源名称
|
||||
*/
|
||||
void changeDataSource(String dataSourceName);
|
||||
|
||||
/**
|
||||
* 是否存在指定数据源
|
||||
*
|
||||
* @param dataSourceName 数据源名称
|
||||
* @return 是否存在指定数据源
|
||||
*/
|
||||
boolean containsDataSource(String dataSourceName);
|
||||
|
||||
/**
|
||||
* 创建数据源
|
||||
*
|
||||
* @param tenantDataSource 数据源配置
|
||||
* @return 数据源
|
||||
*/
|
||||
DataSource createDataSource(TenantDataSource tenantDataSource);
|
||||
|
||||
/**
|
||||
* 移除数据源
|
||||
*
|
||||
* @param dataSourceName 数据源名称
|
||||
*/
|
||||
void removeDataSource(String dataSourceName);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
top.continew.starter.extension.tenant.autoconfigure.TenantWebMvcAutoConfiguration
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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-tenant</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-extension-tenant-mp</artifactId>
|
||||
<description>ContiNew Starter 扩展模块 - 多租户 - MyBatis Plus ORM 模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- MyBatis Plus(MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-extension</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Dynamic Datasource(基于 Spring Boot 的快速集成多数据源的启动器) -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-tenant-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.extension.tenant.autoconfigure;
|
||||
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.extension.tenant.config.TenantDataSourceProvider;
|
||||
import top.continew.starter.extension.tenant.handler.*;
|
||||
|
||||
/**
|
||||
* 多租户自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(TenantProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.TENANT, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class TenantAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TenantAutoConfiguration.class);
|
||||
|
||||
private TenantAutoConfiguration() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户隔离级别:行级
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(name = PropertiesConstants.TENANT + ".isolation-level", havingValue = "line", matchIfMissing = true)
|
||||
public static class Line {
|
||||
static {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant-Line' completed initialization.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户行级隔离拦截器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantLineHandler tenantLineHandler) {
|
||||
return new TenantLineInnerInterceptor(tenantLineHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户行级隔离处理器(默认)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantLineHandler tenantLineHandler(TenantProperties properties) {
|
||||
return new DefaultTenantLineHandler(properties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户隔离级别:数据源级
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(name = PropertiesConstants.TENANT + ".isolation-level", havingValue = "datasource")
|
||||
public static class DataSource {
|
||||
static {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant-DataSource' completed initialization.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户数据源级隔离通知
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceAdvisor tenantDataSourceAdvisor(TenantDataSourceInterceptor tenantDataSourceInterceptor) {
|
||||
return new TenantDataSourceAdvisor(tenantDataSourceInterceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户数据源级隔离拦截器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceInterceptor tenantDataSourceInterceptor(TenantDataSourceHandler tenantDataSourceHandler) {
|
||||
return new TenantDataSourceInterceptor(tenantDataSourceHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户数据源级隔离处理器(默认)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceHandler tenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider,
|
||||
DynamicRoutingDataSource dynamicRoutingDataSource,
|
||||
DefaultDataSourceCreator dataSourceCreator) {
|
||||
return new DefaultTenantDataSourceHandler(tenantDataSourceProvider, dynamicRoutingDataSource, dataSourceCreator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多租户数据源提供者
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceProvider tenantDataSourceProvider() {
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
|
||||
.forClass(TenantDataSourceProvider.class));
|
||||
}
|
||||
throw new NoSuchBeanDefinitionException(TenantDataSourceProvider.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.tenant.handler;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
||||
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.extension.tenant.config.TenantDataSource;
|
||||
import top.continew.starter.extension.tenant.config.TenantDataSourceProvider;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* 默认租户数据源级隔离处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DefaultTenantDataSourceHandler.class);
|
||||
private final DynamicRoutingDataSource dynamicRoutingDataSource;
|
||||
private final DefaultDataSourceCreator dataSourceCreator;
|
||||
private final TenantDataSourceProvider tenantDataSourceProvider;
|
||||
|
||||
public DefaultTenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider,
|
||||
DynamicRoutingDataSource dynamicRoutingDataSource,
|
||||
DefaultDataSourceCreator dataSourceCreator) {
|
||||
this.tenantDataSourceProvider = tenantDataSourceProvider;
|
||||
this.dynamicRoutingDataSource = dynamicRoutingDataSource;
|
||||
this.dataSourceCreator = dataSourceCreator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeDataSource(String dataSourceName) {
|
||||
if (!this.containsDataSource(dataSourceName)) {
|
||||
TenantDataSource tenantDataSource = tenantDataSourceProvider.getByTenantId(dataSourceName);
|
||||
if (null == tenantDataSource) {
|
||||
throw new IllegalArgumentException("Data source [%s] configuration not found"
|
||||
.formatted(dataSourceName));
|
||||
}
|
||||
DataSource datasource = this.createDataSource(tenantDataSource);
|
||||
dynamicRoutingDataSource.addDataSource(dataSourceName, datasource);
|
||||
log.info("Load data source: {}", dataSourceName);
|
||||
}
|
||||
DynamicDataSourceContextHolder.push(dataSourceName);
|
||||
log.info("Change data source: {}", dataSourceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsDataSource(String dataSourceName) {
|
||||
return CharSequenceUtil.isNotBlank(dataSourceName) && dynamicRoutingDataSource.getDataSources()
|
||||
.containsKey(dataSourceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource createDataSource(TenantDataSource tenantDataSource) {
|
||||
DataSourceProperty dataSourceProperty = new DataSourceProperty();
|
||||
dataSourceProperty.setPoolName(tenantDataSource.getPoolName());
|
||||
dataSourceProperty.setDriverClassName(tenantDataSource.getDriverClassName());
|
||||
dataSourceProperty.setUrl(tenantDataSource.getUrl());
|
||||
dataSourceProperty.setUsername(tenantDataSource.getUsername());
|
||||
dataSourceProperty.setPassword(tenantDataSource.getPassword());
|
||||
return dataSourceCreator.createDataSource(dataSourceProperty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDataSource(String dataSourceName) {
|
||||
dynamicRoutingDataSource.removeDataSource(dataSourceName);
|
||||
}
|
||||
}
|
||||
@@ -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.tenant.handler;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||
|
||||
/**
|
||||
* 默认租户行级隔离处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class DefaultTenantLineHandler implements TenantLineHandler {
|
||||
|
||||
private final TenantProperties tenantProperties;
|
||||
|
||||
public DefaultTenantLineHandler(TenantProperties tenantProperties) {
|
||||
this.tenantProperties = tenantProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getTenantId() {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (null != tenantId) {
|
||||
return new LongValue(tenantId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTenantIdColumn() {
|
||||
return tenantProperties.getTenantIdColumn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ignoreTable(String tableName) {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (null != tenantId && tenantId.equals(tenantProperties.getSuperTenantId())) {
|
||||
return true;
|
||||
}
|
||||
return CollUtil.contains(tenantProperties.getIgnoreTables(), tableName);
|
||||
}
|
||||
}
|
||||
@@ -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.extension.tenant.handler;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
|
||||
import org.springframework.aop.support.AbstractPointcutAdvisor;
|
||||
import org.springframework.aop.support.ComposablePointcut;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
|
||||
/**
|
||||
* 租户数据源级隔离通知
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class TenantDataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
|
||||
|
||||
private final Advice advice;
|
||||
private final Pointcut pointcut;
|
||||
|
||||
public TenantDataSourceAdvisor(TenantDataSourceInterceptor interceptor) {
|
||||
this.advice = interceptor;
|
||||
this.pointcut = buildPointcut();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pointcut getPointcut() {
|
||||
return this.pointcut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Advice getAdvice() {
|
||||
return this.advice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
if (this.advice instanceof BeanFactoryAware beanFactoryAware) {
|
||||
beanFactoryAware.setBeanFactory(beanFactory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建切点
|
||||
*
|
||||
* @return 切点
|
||||
*/
|
||||
private Pointcut buildPointcut() {
|
||||
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
|
||||
cut.setExpression("!@annotation(top.continew.starter.extension.tenant.annotation.TenantDataSourceIgnore)");
|
||||
return new ComposablePointcut((Pointcut)cut);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.tenant.handler;
|
||||
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||
|
||||
/**
|
||||
* 租户数据源级隔离拦截器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class TenantDataSourceInterceptor implements MethodInterceptor {
|
||||
|
||||
private final TenantDataSourceHandler tenantDataSourceHandler;
|
||||
|
||||
public TenantDataSourceInterceptor(TenantDataSourceHandler tenantDataSourceHandler) {
|
||||
this.tenantDataSourceHandler = tenantDataSourceHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (null == tenantId) {
|
||||
return invocation.proceed();
|
||||
}
|
||||
// 切换数据源
|
||||
boolean isPush = false;
|
||||
try {
|
||||
String dataSourceName = tenantId.toString();
|
||||
tenantDataSourceHandler.changeDataSource(dataSourceName);
|
||||
isPush = true;
|
||||
return invocation.proceed();
|
||||
} finally {
|
||||
if (isPush) {
|
||||
DynamicDataSourceContextHolder.poll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
top.continew.starter.extension.tenant.autoconfigure.TenantAutoConfiguration
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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-tenant</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<description>ContiNew Starter 扩展模块 - 多租户</description>
|
||||
|
||||
<modules>
|
||||
<module>continew-starter-extension-tenant-core</module>
|
||||
<module>continew-starter-extension-tenant-mp</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
<modules>
|
||||
<module>continew-starter-extension-crud</module>
|
||||
<module>continew-starter-extension-datapermission</module>
|
||||
<module>continew-starter-extension-tenant</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -30,8 +30,8 @@ import top.continew.starter.core.enums.BaseEnum;
|
||||
/**
|
||||
* Easy Excel 枚举接口转换器
|
||||
*
|
||||
* @see BaseEnum
|
||||
* @author Charles7c
|
||||
* @see BaseEnum
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class ExcelBaseEnumConverter implements Converter<BaseEnum<Integer>> {
|
||||
|
||||
@@ -27,8 +27,10 @@ import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.core.exception.BaseException;
|
||||
import top.continew.starter.file.excel.converter.ExcelBigNumberConverter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Excel 工具类
|
||||
@@ -52,21 +54,23 @@ public class ExcelUtils {
|
||||
* @param response 响应对象
|
||||
*/
|
||||
public static <T> void export(List<T> list, String fileName, Class<T> clazz, HttpServletResponse response) {
|
||||
export(list, fileName, "Sheet1", clazz, response);
|
||||
export(list, fileName, "Sheet1", Collections.emptySet(), clazz, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出
|
||||
*
|
||||
* @param list 导出数据集合
|
||||
* @param fileName 文件名
|
||||
* @param sheetName 工作表名称
|
||||
* @param clazz 导出数据类型
|
||||
* @param response 响应对象
|
||||
* @param list 导出数据集合
|
||||
* @param fileName 文件名
|
||||
* @param sheetName 工作表名称
|
||||
* @param excludeColumnFieldNames 排除字段
|
||||
* @param clazz 导出数据类型
|
||||
* @param response 响应对象
|
||||
*/
|
||||
public static <T> void export(List<T> list,
|
||||
String fileName,
|
||||
String sheetName,
|
||||
Set<String> excludeColumnFieldNames,
|
||||
Class<T> clazz,
|
||||
HttpServletResponse response) {
|
||||
try {
|
||||
@@ -81,6 +85,7 @@ public class ExcelUtils {
|
||||
// 自动转换大数值
|
||||
.registerConverter(new ExcelBigNumberConverter())
|
||||
.sheet(sheetName)
|
||||
.excludeColumnFieldNames(excludeColumnFieldNames)
|
||||
.doWrite(list);
|
||||
} catch (Exception e) {
|
||||
log.error("Export excel occurred an error: {}. fileName: {}.", e.getMessage(), fileName, e);
|
||||
|
||||
@@ -18,7 +18,8 @@ package top.continew.starter.log.core.model;
|
||||
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 响应信息
|
||||
|
||||
@@ -30,6 +30,6 @@ import java.lang.annotation.*;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Documented
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.LOG, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.LOG, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public @interface ConditionalOnEnabledLog {
|
||||
}
|
||||
@@ -96,6 +96,6 @@ public class LogProperties {
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public boolean isMatch(String uri) {
|
||||
return this.getExcludePatterns().stream().anyMatch(pattern -> SpringWebUtils.isMatch(pattern, uri));
|
||||
return this.getExcludePatterns().stream().anyMatch(pattern -> SpringWebUtils.isMatch(uri, pattern));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,12 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.interceptor.autoconfigure.LogProperties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 日志过滤器
|
||||
@@ -123,9 +121,7 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
|
||||
* @return true:是;false:否
|
||||
*/
|
||||
private boolean isRequestWrapper(HttpServletRequest request) {
|
||||
Set<Include> includeSet = logProperties.getIncludes();
|
||||
return !(request instanceof ContentCachingRequestWrapper) && (includeSet
|
||||
.contains(Include.REQUEST_BODY) || includeSet.contains(Include.REQUEST_PARAM));
|
||||
return !(request instanceof ContentCachingRequestWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,9 +131,7 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
|
||||
* @return true:是;false:否
|
||||
*/
|
||||
private boolean isResponseWrapper(HttpServletResponse response) {
|
||||
Set<Include> includeSet = logProperties.getIncludes();
|
||||
return !(response instanceof ContentCachingResponseWrapper) && (includeSet
|
||||
.contains(Include.RESPONSE_BODY) || includeSet.contains(Include.RESPONSE_PARAM));
|
||||
return !(response instanceof ContentCachingResponseWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,8 @@ import top.continew.starter.log.core.model.RecordableHttpRequest;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可记录的 HTTP 请求信息适配器
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.log.core.model.RecordableHttpResponse;
|
||||
import top.continew.starter.web.util.ServletUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可记录的 HTTP 响应信息适配器
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package top.continew.starter.messaging.websocket.autoconfigure;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -46,7 +45,7 @@ import top.continew.starter.messaging.websocket.dao.WebSocketSessionDaoDefaultIm
|
||||
@AutoConfiguration
|
||||
@EnableWebSocket
|
||||
@EnableConfigurationProperties(WebSocketProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.MESSAGING_WEBSOCKET, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.MESSAGING_WEBSOCKET, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class WebSocketAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WebSocketAutoConfiguration.class);
|
||||
@@ -65,15 +64,14 @@ public class WebSocketAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebSocketHandler webSocketHandler() {
|
||||
return new top.continew.starter.messaging.websocket.core.WebSocketHandler(properties, SpringUtil
|
||||
.getBean(WebSocketSessionDao.class));
|
||||
public WebSocketHandler webSocketHandler(WebSocketSessionDao webSocketSessionDao) {
|
||||
return new top.continew.starter.messaging.websocket.core.WebSocketHandler(properties, webSocketSessionDao);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public HandshakeInterceptor handshakeInterceptor() {
|
||||
return new WebSocketInterceptor(properties, SpringUtil.getBean(WebSocketClientService.class));
|
||||
public HandshakeInterceptor handshakeInterceptor(WebSocketClientService webSocketClientService) {
|
||||
return new WebSocketInterceptor(properties, webSocketClientService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -78,7 +78,7 @@ public class WebSocketHandler extends TextWebSocketHandler {
|
||||
|
||||
/**
|
||||
* 获取客户端 ID
|
||||
*
|
||||
*
|
||||
* @param session 会话
|
||||
* @return 客户端 ID
|
||||
*/
|
||||
|
||||
@@ -36,7 +36,7 @@ import top.continew.starter.security.crypto.core.MyBatisEncryptInterceptor;
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(CryptoProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_CRYPTO, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_CRYPTO, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class CryptoAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CryptoAutoConfiguration.class);
|
||||
|
||||
@@ -19,7 +19,10 @@ package top.continew.starter.security.crypto.core;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import org.apache.ibatis.executor.resultset.ResultSetHandler;
|
||||
import org.apache.ibatis.plugin.*;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.plugin.Intercepts;
|
||||
import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
import org.apache.ibatis.type.SimpleTypeRegistry;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
|
||||
|
||||
@@ -35,7 +35,10 @@ import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
|
||||
import top.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -134,10 +137,10 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
*
|
||||
* @param parameter Wrapper 参数
|
||||
* @param mappedStatement 映射语句
|
||||
* @since 2.1.1
|
||||
* @author cary
|
||||
* @author wangshaopeng@talkweb.com.cn(<a
|
||||
* href="https://blog.csdn.net/tianmaxingkonger/article/details/130986784">基于Mybatis-Plus拦截器实现MySQL数据加解密</a>)
|
||||
* @since 2.1.1
|
||||
*/
|
||||
private void encryptUpdateWrapper(Object parameter, MappedStatement mappedStatement) {
|
||||
if (parameter instanceof AbstractWrapper updateWrapper) {
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 限流注解
|
||||
*
|
||||
*
|
||||
* @author KAI
|
||||
* @since 2.2.0
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 限流组注解
|
||||
*
|
||||
*
|
||||
* @author KAI
|
||||
* @since 2.2.0
|
||||
*/
|
||||
|
||||
@@ -31,7 +31,7 @@ import top.continew.starter.security.limiter.core.RateLimiterNameGenerator;
|
||||
|
||||
/**
|
||||
* 限流器自动配置
|
||||
*
|
||||
*
|
||||
* @author KAI
|
||||
* @author Charles7c
|
||||
* @since 2.2.0
|
||||
@@ -39,7 +39,7 @@ import top.continew.starter.security.limiter.core.RateLimiterNameGenerator;
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(RateLimiterProperties.class)
|
||||
@ComponentScan({"top.continew.starter.security.limiter.core"})
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_LIMITER, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_LIMITER, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class RateLimiterAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RateLimiterAutoConfiguration.class);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user