Compare commits

...

32 Commits

Author SHA1 Message Date
YunaiV 998ee168d7 1. 增加 Flowable 的多租户字段过滤逻辑
2. 增加 Flowable 对多租户多 DB 的支持
2023-03-11 23:22:16 +08:00
YunaiV b42fe75b88 修复多租户多 db 下,修改用户报不存在的问题 2023-03-10 08:15:03 +08:00
xingyuv 337411ab89 chore: 拆分 uniapp 2023-03-07 15:05:57 +08:00
xingyuv 7eeefe5b26 up deps 2023-03-05 17:38:32 +08:00
xingyuv 4828ce080f up hutool 5.8.12 2023-03-05 17:29:02 +08:00
xingyuv f72155aae7 up springboot 2.7.9 2023-03-05 17:28:11 +08:00
YunaiV 83b3eb487f 优化 Swagger 的版本依赖,避免冲突 2023-03-04 14:58:15 +08:00
YunaiV 2379b0d544 Redis 缓存,使用 : 单冒号,而不是双 : 冒号 2023-03-03 00:46:16 +08:00
YunaiV 1e4735c1fc 简化 Redis DAO 的使用,移除 RedisKeyDefine 类 2023-03-03 00:20:21 +08:00
YunaiV 848e0c897f Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/dev-yunai
# Conflicts:
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
#	yudao-server/src/main/resources/application-dev.yaml
#	yudao-server/src/main/resources/application-local.yaml
2023-03-02 23:54:37 +08:00
YunaiV af8f66993e 修改 @date 注释为 @since 2023-03-02 23:45:59 +08:00
YunaiV 68b49c6dd6 缓存改造:SmsTemplate 使用 Redis 作为缓存 2023-03-02 23:44:08 +08:00
YunaiV 5dda7a3c05 缓存改造:MailTemplate、MailAccount 使用 Redis 作为缓存 2023-03-02 23:28:21 +08:00
YunaiV d23d72c8bb 缓存改造:NotifyTemplate 使用 Redis 作为缓存 2023-03-02 22:21:33 +08:00
YunaiV 7ccdf86d3a 缓存改造:OAuth2Client 使用 Redis 作为缓存 2023-03-02 09:43:03 +08:00
YunaiV 5572bf3fcb 完善 PermissionService 的单元测试 2023-02-28 23:57:18 +08:00
YunaiV cae35b636a 完善 PermissionService 的单元测试 2023-02-28 21:00:46 +08:00
YunaiV afd3128803 完善 RoleService 的单元测试 2023-02-28 01:17:03 +08:00
YunaiV 152fc70e86 完善 MenuService 的单元测试 2023-02-28 00:39:25 +08:00
YunaiV be465d758b 完善 AdminUserServiceImpl 的单元测试 2023-02-28 00:17:02 +08:00
YunaiV 6c9ef97122 优化 dept 部门的实现逻辑,以及相关的单元测试 2023-02-28 00:07:39 +08:00
YunaiV bcf926f7c7 修复多数据源的部分数据源无法切换的问题 2023-02-27 22:29:05 +08:00
YunaiV 3efcc4bc62 增加租户的数据源配置 2023-02-27 22:11:12 +08:00
YunaiV 1b563fecaa TenantDsProcessor 接入动态数据源的加载 2023-02-27 00:44:12 +08:00
YunaiV 9be666e068 改造多租户的创建,基于 @DSTransactional 实现多数据的事务 2023-02-26 00:58:23 +08:00
YunaiV dfb1bdb9fb 缓存改造:Menu 使用 Redis 作为缓存 2023-02-25 22:55:26 +08:00
YunaiV 2db6a4510c 缓存改造:Dept 使用 Redis 作为缓存 2023-02-25 22:42:50 +08:00
YunaiV 56c44acd11 缓存改造:RoleMenu 使用 Redis 作为缓存 2023-02-25 18:00:05 +08:00
YunaiV 9a1a80e4b8 缓存改造:UserRole 使用 Redis 作为缓存 2023-02-25 17:41:43 +08:00
YunaiV b371225292 将 list-menus 合并到 get-permission-info 接口,接口职责更统一 2023-02-25 14:13:29 +08:00
YunaiV 2976675331 缓存改造:Role 使用 Redis 作为缓存 2023-02-25 11:50:05 +08:00
YunaiV 45a5b7d1d4 1. 增加 @TenantDS 注解,支持指定租户库
2. 标记 system 模块中,适合租户分库的表
2023-02-23 23:26:21 +08:00
537 changed files with 2349 additions and 48325 deletions

View File

@ -39,6 +39,7 @@
| 项目名 | 说明 | 传送门 |
|--------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------|
| `ruoyi-vue-pro` | Spring Boot 多模块 | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro)**     [Github](https://github.com/YunaiV/ruoyi-vue-pro) |
| `yudao-boot-lite` | Spring Boot 多模块 精简版 | **[Gitee](https://gitee.com/yudaocode/yudao-boot-lite)** |
| `yudao-cloud` | Spring Cloud 微服务 | **[Gitee](https://gitee.com/zhijiantianya/yudao-cloud)**     [Github](https://github.com/YunaiV/yudao-cloud) |
| `Spring-Boot-Labs` | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/yudaocode/SpringBoot-Labs)**     [Github](https://github.com/yudaocode/SpringBoot-Labs) |
@ -226,16 +227,16 @@ ps核心功能已经实现正在对接微信小程序中...
> * [`yudao-ui-admin-vue3`](https://gitee.com/yudaocode/yudao-ui-admin-vue3):基于 Vue3 + element-plus 实现的管理后台
> * `yudao-ui-admin`:基于 Vue2 + element-ui 实现的管理后台
> * [`yudao-ui-go-view`](https://gitee.com/yudaocode/yudao-ui-go-view):基于 Vue3 + naive-ui 实现的大屏报表
> * `yudao-ui-admin-uniapp`:基于 uni-app + uni-ui 实现的管理后台的小程序
> * [`yudao-ui-admin-uniapp`](https://gitee.com/yudaocode/yudao-ui-admin-uniapp):基于 uni-app + uni-ui 实现的管理后台的小程序
> * `yudao-ui-app`:基于 uni-app + uview 实现的用户 App
### 后端
| 框架 | 说明 | 版本 | 学习指南 |
|---------------------------------------------------------------------------------------------|------------------|-------------|----------------------------------------------------------------|
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.7.8 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.7.9 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | |
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.15 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.16 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.3.1 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.6.1 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [Redis](https://redis.io/) | key-value 数据库 | 5.0 / 6.0 | |

View File

@ -39,7 +39,7 @@
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<!-- 看看咋放到 bom 里 -->
<lombok.version>1.18.24</lombok.version>
<spring.boot.version>2.7.8</spring.boot.version>
<spring.boot.version>2.7.9</spring.boot.version>
<mapstruct.version>1.5.3.Final</mapstruct.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

View File

@ -16,13 +16,14 @@
<properties>
<revision>1.7.0-snapshot</revision>
<!-- 统一依赖管理 -->
<spring.boot.version>2.7.8</spring.boot.version>
<spring.boot.version>2.7.9</spring.boot.version>
<!-- Web 相关 -->
<servlet.versoin>2.5</servlet.versoin>
<swagger.version>2.2.8</swagger.version>
<springdoc.version>1.6.14</springdoc.version>
<knife4j.version>4.0.0</knife4j.version>
<servlet.versoin>2.5</servlet.versoin>
<!-- DB 相关 -->
<druid.version>1.2.15</druid.version>
<druid.version>1.2.16</druid.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<mybatis-plus-generator.version>3.5.3.1</mybatis-plus-generator.version>
<dynamic-datasource.version>3.6.1</dynamic-datasource.version>
@ -45,8 +46,8 @@
<jsoup.version>1.15.3</jsoup.version>
<lombok.version>1.18.24</lombok.version>
<mapstruct.version>1.5.3.Final</mapstruct.version>
<hutool.version>5.8.11</hutool.version>
<easyexcel.verion>3.2.0</easyexcel.verion>
<hutool.version>5.8.12</hutool.version>
<easyexcel.verion>3.2.1</easyexcel.verion>
<velocity.version>2.3</velocity.version>
<screw.version>1.0.5</screw.version>
<fastjson.version>1.2.83</fastjson.version>
@ -56,15 +57,15 @@
<commons-net.version>3.8.0</commons-net.version>
<jsch.version>0.1.55</jsch.version>
<tika-core.version>2.6.0</tika-core.version>
<netty-all.version>4.1.86.Final</netty-all.version>
<ip2region.version>2.6.6</ip2region.version>
<netty-all.version>4.1.89.Final</netty-all.version>
<ip2region.version>2.7.0</ip2region.version>
<!-- 三方云服务相关 -->
<okio.version>3.0.0</okio.version>
<okhttp3.version>4.10.0</okhttp3.version>
<minio.version>8.5.1</minio.version>
<minio.version>8.5.2</minio.version>
<aliyun-java-sdk-core.version>4.6.3</aliyun-java-sdk-core.version>
<aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
<tencentcloud-sdk-java.version>3.1.676</tencentcloud-sdk-java.version>
<tencentcloud-sdk-java.version>3.1.706</tencentcloud-sdk-java.version>
<justauth.version>1.4.0</justauth.version>
<jimureport.version>1.5.6</jimureport.version>
<xercesImpl.version>2.12.2</xercesImpl.version>
@ -171,15 +172,40 @@
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
<groupId>io.swagger.core.v3</groupId> <!-- 接口文档:使用最新版本的 Swagger 模型 -->
<artifactId>swagger-annotations</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<groupId>io.swagger.core.v3</groupId> <!-- 接口文档:使用最新版本的 Swagger 模型 -->
<artifactId>swagger-models</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId> <!-- 接口文档 UI解决 knife4j 引入的 Spring Doc 版本太老 -->
<artifactId>springdoc-openapi-common</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId> <!-- 接口文档 UI解决 knife4j 引入的 Spring Doc 版本太老 -->
<artifactId>springdoc-openapi-webmvc-core</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId> <!-- 接口文档 UI解决 knife4j 引入的 Spring Doc 版本太老 -->
<artifactId>springdoc-openapi-webflux-core</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId> <!-- 接口文档 UI默认 -->
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId> <!-- 接口文档 UIknife4j -->
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- DB 相关 -->
<dependency>

View File

@ -21,7 +21,7 @@
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 统一依赖管理 -->
<spring.boot.version>2.7.8</spring.boot.version>
<spring.boot.version>2.7.9</spring.boot.version>
</properties>
<dependencyManagement>
@ -52,7 +52,7 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version>
<version>5.8.12</version>
</dependency>
<dependency>

View File

@ -21,7 +21,7 @@
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 统一依赖管理 -->
<spring.boot.version>2.7.8</spring.boot.version>
<spring.boot.version>2.7.9</spring.boot.version>
</properties>
<dependencyManagement>
@ -52,7 +52,7 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version>
<version>5.8.12</version>
</dependency>
<dependency>

View File

@ -59,9 +59,9 @@
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<scope>provided</scope> <!-- 设置为 provided主要是 PageParam 使用到 -->
<groupId>io.swagger.core.v3</groupId> <!-- 接口文档:使用最新版本的 Swagger 模型 -->
<artifactId>swagger-annotations</artifactId>
<scope>provided</scope>
</dependency>
<!-- 监控相关 -->

View File

@ -1,46 +0,0 @@
package cn.iocoder.yudao.framework.common.util.spring;
import cn.hutool.core.bean.BeanUtil;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.support.AopUtils;
/**
* Spring AOP 工具类
*
* 参考波克尔 http://www.bubuko.com/infodetail-3471885.html 实现
*/
public class SpringAopUtils {
/**
* 获取代理的目标对象
*
* @param proxy 代理对象
* @return 目标对象
*/
public static Object getTarget(Object proxy) throws Exception {
// 不是代理对象
if (!AopUtils.isAopProxy(proxy)) {
return proxy;
}
// Jdk 代理
if (AopUtils.isJdkDynamicProxy(proxy)) {
return getJdkDynamicProxyTargetObject(proxy);
}
// Cglib 代理
return getCglibProxyTargetObject(proxy);
}
private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
Object dynamicAdvisedInterceptor = BeanUtil.getFieldValue(proxy, "CGLIB$CALLBACK_0");
AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(dynamicAdvisedInterceptor, "advised");
return advisedSupport.getTargetSource().getTarget();
}
private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
AopProxy aopProxy = (AopProxy) BeanUtil.getFieldValue(proxy, "h");
AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(aopProxy, "advised");
return advisedSupport.getTargetSource().getTarget();
}
}

View File

@ -16,7 +16,7 @@ import org.springframework.context.annotation.Primary;
* 社交自动装配类
*
* @author timfruit
* @date 2021-10-30
* @since 2021-10-30
*/
@Slf4j
@AutoConfiguration

View File

@ -18,7 +18,7 @@ import java.lang.reflect.Method;
* 为使得拓展配置 {@link AuthConfig} 和默认配置齐平所以自定义本工厂类
*
* @author timfruit
* @date 2021-10-31
* @since 2021-10-31
*/
public class YudaoAuthRequestFactory extends AuthRequestFactory {

View File

@ -20,7 +20,7 @@ import me.zhyd.oauth.utils.UrlBuilder;
* 由于 JustAuth 定位是面向 Web 为主的三方登录所以微信小程序只能自己封装
*
* @author timfruit
* @date 2021-10-29
* @since 2021-10-29
*/
public class AuthWeChatMiniAppRequest extends AuthDefaultRequest {

View File

@ -39,4 +39,11 @@ public class TenantProperties {
*/
private Set<String> ignoreTables = Collections.emptySet();
/**
* 需要忽略多租户的 {@link org.springframework.cache.Cache}
*
* 即默认所有 Cache 都开启多租户的功能
*/
private Set<String> ignoreCaches = Collections.emptySet();
}

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDsProcessor;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor;
@ -17,6 +18,11 @@ import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceCreator;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.processor.DsProcessor;
import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.beans.BeansException;
@ -33,6 +39,7 @@ import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import javax.sql.DataSource;
import java.util.Objects;
@AutoConfiguration
@ -64,6 +71,18 @@ public class YudaoTenantAutoConfiguration {
return inner;
}
@Bean
public DsProcessor dsProcessor(
// TenantFrameworkService tenantFrameworkService,
// DataSource dataSource,
// DefaultDataSourceCreator dataSourceCreator
) {
// TenantDsProcessor tenantDsProcessor = new TenantDsProcessor(tenantFrameworkService, dataSourceCreator);
TenantDsProcessor tenantDsProcessor = new TenantDsProcessor();
tenantDsProcessor.setNextProcessor(new DsSpelExpressionProcessor());
return tenantDsProcessor;
}
// ========== WEB ==========
@Bean
@ -123,13 +142,14 @@ public class YudaoTenantAutoConfiguration {
@Bean
@Primary // 引入租户时tenantRedisCacheManager 为主 Bean
public RedisCacheManager tenantRedisCacheManager(RedisTemplate<String, Object> redisTemplate,
RedisCacheConfiguration redisCacheConfiguration) {
public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
RedisCacheConfiguration redisCacheConfiguration,
TenantProperties tenantProperties) {
// 创建 RedisCacheWriter 对象
RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
// 创建 TenantRedisCacheManager 对象
return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration);
return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, tenantProperties);
}
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.framework.tenant.core.db.dynamic;
import com.baomidou.dynamic.datasource.annotation.DS;
import java.lang.annotation.*;
/**
* 使用租户所在的数据源
*
* 使用方式当我们希望一个表使用租户所在的数据源可以在该表的 Mapper 上添加该注解
*
* @author 芋道源码
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@DS(TenantDS.KEY)
public @interface TenantDS {
/**
* 租户对应的数据源的占位符
*/
String KEY = "#context.tenantId";
}

View File

@ -0,0 +1,86 @@
package cn.iocoder.yudao.framework.tenant.core.db.dynamic;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.processor.DsProcessor;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import jodd.util.CollectionUtil;
import lombok.RequiredArgsConstructor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.annotation.Lazy;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Objects;
/**
* 基于 {@link TenantDS} 的数据源处理器
*
* 1. 如果有 @TenantDS 注解返回该租户的数据源
* 2. 如果该租户的数据源未创建则进行创建
*
* @author 芋道源码
*/
@RequiredArgsConstructor
public class TenantDsProcessor extends DsProcessor {
/**
* 用于获取租户数据源配置的 Service
*/
@Resource
@Lazy
private TenantFrameworkService tenantFrameworkService;
/**
* 动态数据源
*/
@Resource
@Lazy // 为什么添加 @Lazy 注解因为它和 DynamicRoutingDataSource 相互依赖导致无法初始化
private DynamicRoutingDataSource dynamicRoutingDataSource;
/**
* 用于创建租户数据源的 Creator
*/
@Resource
@Lazy
private DefaultDataSourceCreator dataSourceCreator;
@Override
public boolean matches(String key) {
return Objects.equals(key, TenantDS.KEY);
}
@Override
public String doDetermineDatasource(MethodInvocation invocation, String key) {
// 获得数据源配置
Long tenantId = TenantContextHolder.getRequiredTenantId();
DataSourceProperty dataSourceProperty = tenantFrameworkService.getDataSourceProperty(tenantId);
// 创建 or 创建数据源并返回数据源名字
return createDatasourceIfAbsent(dataSourceProperty);
}
private String createDatasourceIfAbsent(DataSourceProperty dataSourceProperty) {
// 1. 重点如果数据源不存在则进行创建
if (isDataSourceNotExist(dataSourceProperty)) {
// 问题一为什么要加锁因为如果多个线程同时执行到这里会导致多次创建数据源
// 问题二为什么要使用 poolName 加锁保证多个不同的 poolName 可以并发创建数据源
// 问题三为什么要使用 intern 方法因为intern 方法会返回一个字符串的常量池中的引用
// intern 的说明可见 https://www.cnblogs.com/xrq730/p/6662232.html 文章
synchronized (dataSourceProperty.getPoolName().intern()) {
if (isDataSourceNotExist(dataSourceProperty)) {
DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
dynamicRoutingDataSource.addDataSource(dataSourceProperty.getPoolName(), dataSource);
}
}
}
// 2. 返回数据源的名字
return dataSourceProperty.getPoolName();
}
private boolean isDataSourceNotExist(DataSourceProperty dataSourceProperty) {
return !dynamicRoutingDataSource.getDataSources().containsKey(dataSourceProperty.getPoolName());
}
}

View File

@ -1,6 +1,10 @@
package cn.iocoder.yudao.framework.tenant.core.redis;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import jodd.io.StreamUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
@ -10,24 +14,34 @@ import org.springframework.data.redis.cache.RedisCacheWriter;
/**
* 多租户的 {@link RedisCacheManager} 实现类
*
* 操作指定 name {@link Cache} 自动拼接租户后缀格式为 name + ":" + tenantId + 后缀
* 操作指定 name {@link Cache} 自动拼接租户后缀格式为 name + "::t" + tenantId + 后缀
*
* @author airhead
*/
@Slf4j
public class TenantRedisCacheManager extends RedisCacheManager {
public class TenantRedisCacheManager extends TimeoutRedisCacheManager {
/**
* 多租户 Redis Key 的前缀补充在原有 name : 后面
*
* 原因如果只补充租户编号可读性较差
*/
public static final String PREFIX = "t";
private final TenantProperties tenantProperties;
public TenantRedisCacheManager(RedisCacheWriter cacheWriter,
RedisCacheConfiguration defaultCacheConfiguration) {
RedisCacheConfiguration defaultCacheConfiguration,
TenantProperties tenantProperties) {
super(cacheWriter, defaultCacheConfiguration);
this.tenantProperties = tenantProperties;
}
@Override
public Cache getCache(String name) {
// 如果开启多租户 name 拼接租户后缀
if (!TenantContextHolder.isIgnore()
&& TenantContextHolder.getTenantId() != null) {
name = name + ":" + TenantContextHolder.getTenantId();
// 如果不忽略多租户的 Cache则自动拼接租户后缀
if (!tenantProperties.getIgnoreCaches().contains(name)) {
name = name + StrUtil.COLON + PREFIX + TenantContextHolder.getRequiredTenantId();
}
// 继续基于父方法

View File

@ -1,47 +0,0 @@
package cn.iocoder.yudao.framework.tenant.core.redis;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import java.time.Duration;
/**
* 多租户拓展的 RedisKeyDefine 实现类
*
* 由于 Redis 不同于 MySQL column 字段无法通过类似 WHERE tenant_id = ? 的方式过滤
* 所以需要通过在 Redis Key 上增加后缀的方式进行租户之间的隔离具体的步骤是
* 1. 假设 Redis Key user:%d示例是 user:1对应到多租户的 Redis Key user:%d:%d
* 2. Redis DAO 需要使用 {@link #formatKey(Object...)} 方法进行 Redis Key 的格式化
*
* 注意大多数情况下并不用使用 TenantRedisKeyDefine 实现主要的使用场景还是 Redis Key 可能存在冲突的情况
* 例如说租户 1 2 都有一个手机号作为 Key则他们会存在冲突的问题
*
* @author 芋道源码
*/
public class TenantRedisKeyDefine extends RedisKeyDefine {
/**
* 多租户的 KEY 模板
*/
private static final String KEY_TEMPLATE_SUFFIX = ":%d";
public TenantRedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, Duration timeout) {
super(memo, buildKeyTemplate(keyTemplate), keyType, valueType, timeout);
}
public TenantRedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, TimeoutTypeEnum timeoutType) {
super(memo, buildKeyTemplate(keyTemplate), keyType, valueType, timeoutType);
}
private static String buildKeyTemplate(String keyTemplate) {
return keyTemplate + KEY_TEMPLATE_SUFFIX;
}
@Override
public String formatKey(Object... args) {
args = ArrayUtil.append(args, TenantContextHolder.getRequiredTenantId());
return super.formatKey(args);
}
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.tenant.core.service;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import java.util.List;
/**
@ -23,4 +25,12 @@ public interface TenantFrameworkService {
*/
void validTenant(Long id);
/**
* 获得租户对应的数据源配置
*
* @param id 租户编号
* @return 数据源配置
*/
DataSourceProperty getDataSourceProperty(Long id);
}

View File

@ -2,7 +2,10 @@ package cn.iocoder.yudao.framework.tenant.core.service;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
import cn.iocoder.yudao.module.infra.api.db.dto.DataSourceConfigRespDTO;
import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
import cn.iocoder.yudao.module.system.api.tenant.dto.TenantDataSourceConfigRespDTO;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.RequiredArgsConstructor;
@ -56,6 +59,28 @@ public class TenantFrameworkServiceImpl implements TenantFrameworkService {
});
/**
* 针对 {@link #getDataSourceProperty(Long)} 的缓存
*/
private final LoadingCache<Long, DataSourceProperty> dataSourcePropertyCache = CacheUtils.buildAsyncReloadingCache(
Duration.ofMinutes(1L), // 过期时间 1 分钟
new CacheLoader<Long, DataSourceProperty>() {
@Override
public DataSourceProperty load(Long id) {
// 获得租户对应的数据源配置
TenantDataSourceConfigRespDTO dataSourceConfig = tenantApi.getTenantDataSourceConfig(id);
if (dataSourceConfig == null) {
return null;
}
// 转换成 dynamic-datasource 配置
return new DataSourceProperty()
.setPoolName(dataSourceConfig.getName()).setUrl(dataSourceConfig.getUrl())
.setUsername(dataSourceConfig.getUsername()).setPassword(dataSourceConfig.getPassword());
}
});
@Override
@SneakyThrows
public List<Long> getTenantIds() {
@ -70,4 +95,10 @@ public class TenantFrameworkServiceImpl implements TenantFrameworkService {
}
}
@Override
@SneakyThrows
public DataSourceProperty getDataSourceProperty(Long id) {
return dataSourcePropertyCache.get(id);
}
}

View File

@ -1,27 +0,0 @@
package cn.iocoder.yudao.framework.tenant.core.redis;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TenantRedisKeyDefineTest {
@Test
public void testFormatKey() {
Long tenantId = 30L;
TenantContextHolder.setTenantId(tenantId);
// 准备参数
TenantRedisKeyDefine define = new TenantRedisKeyDefine("", "user:%d:%d", RedisKeyDefine.KeyTypeEnum.HASH,
Object.class, RedisKeyDefine.TimeoutTypeEnum.FIXED);
Long userId = 10L;
Integer userType = 1;
// 调用
String key = define.formatKey(userId, userType);
// 断言
assertEquals("user:10:1:30", key);
}
}

View File

@ -1,8 +1,6 @@
package cn.iocoder.yudao.framework.captcha.config;
import cn.hutool.core.util.ClassUtil;
import cn.iocoder.yudao.framework.captcha.core.enums.CaptchaRedisKeyConstants;
import cn.iocoder.yudao.framework.captcha.core.service.RedisCaptchaServiceImpl;
import cn.iocoder.yudao.framework.captcha.core.RedisCaptchaServiceImpl;
import com.xingyuv.captcha.service.CaptchaCacheService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
@ -11,12 +9,6 @@ import org.springframework.data.redis.core.StringRedisTemplate;
@AutoConfiguration
public class YudaoCaptchaConfiguration {
static {
// 手动加载 Lock4jRedisKeyConstants 因为它不会被使用到
// 如果不加载会导致 Redis 监控看到它的 Redis Key 枚举
ClassUtil.loadClass(CaptchaRedisKeyConstants.class.getName());
}
@Bean
public CaptchaCacheService captchaCacheService(StringRedisTemplate stringRedisTemplate) {
return new RedisCaptchaServiceImpl(stringRedisTemplate);

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.framework.captcha.core.service;
package cn.iocoder.yudao.framework.captcha.core;
import com.xingyuv.captcha.service.CaptchaCacheService;
import lombok.AllArgsConstructor;

View File

@ -1,25 +0,0 @@
package cn.iocoder.yudao.framework.captcha.core.enums;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import com.xingyuv.captcha.model.vo.PointVO;
import java.time.Duration;
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
/**
* 验证码 Redis Key 枚举类
*
* @author 芋道源码
*/
public interface CaptchaRedisKeyConstants {
RedisKeyDefine AJ_CAPTCHA_REQ_LIMIT = new RedisKeyDefine("验证码的请求限流",
"AJ.CAPTCHA.REQ.LIMIT-%s-%s",
STRING, Integer.class, Duration.ofSeconds(60)); // 例如说验证失败 5 get 接口锁定
RedisKeyDefine AJ_CAPTCHA_RUNNING = new RedisKeyDefine("验证码的坐标",
"RUNNING:CAPTCHA:%s", // AbstractCaptchaService.REDIS_CAPTCHA_KEY
STRING, PointVO.class, Duration.ofSeconds(120)); // {"secretKey":"PP1w2Frr2KEejD2m","x":162,"y":5}
}

View File

@ -1 +1 @@
cn.iocoder.yudao.framework.captcha.core.service.RedisCaptchaServiceImpl
cn.iocoder.yudao.framework.captcha.core.RedisCaptchaServiceImpl

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.flowable.core.util;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
@ -26,6 +28,21 @@ public class FlowableUtils {
Authentication.setAuthenticatedUserId(null);
}
/**
* 获得租户编号
*
* 因为 Flowable 的租户编号是字符串所以封装该方法
*
* @return 租户编号
*/
public static String getTenantId() {
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
if (loginUser == null || loginUser.getTenantId() == null) {
return null;
}
return String.valueOf(loginUser.getTenantId());
}
// ========== BPMN 相关的工具方法 ==========
/**

View File

@ -1,13 +1,10 @@
package cn.iocoder.yudao.framework.idempotent.core.redis;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
/**
* 幂等 Redis DAO
*
@ -16,9 +13,14 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S
@AllArgsConstructor
public class IdempotentRedisDAO {
private static final RedisKeyDefine IDEMPOTENT = new RedisKeyDefine("幂等操作",
"idempotent:%s", // 参数为 uuid
STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
/**
* 幂等操作的 Redis Key 模板
*
* KEY 格式idempotent:{uuid}
* VALUE 格式空字符串
* 过期时间动态传参
*/
private static final String IDEMPOTENT_KEY_TEMPLATE = "idempotent:%s";
private final StringRedisTemplate redisTemplate;
@ -28,7 +30,7 @@ public class IdempotentRedisDAO {
}
private static String formatKey(String key) {
return String.format(IDEMPOTENT.getKeyTemplate(), key);
return String.format(IDEMPOTENT_KEY_TEMPLATE, key);
}
}

View File

@ -1,21 +1,13 @@
package cn.iocoder.yudao.framework.lock4j.config;
import cn.hutool.core.util.ClassUtil;
import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;
import cn.iocoder.yudao.framework.lock4j.core.DefaultLockFailureStrategy;
import cn.iocoder.yudao.framework.lock4j.core.Lock4jRedisKeyConstants;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
@AutoConfiguration(before = LockAutoConfiguration.class)
public class YudaoLock4jConfiguration {
static {
// 手动加载 Lock4jRedisKeyConstants 因为它不会被使用到
// 如果不加载会导致 Redis 监控看到它的 Redis Key 枚举
ClassUtil.loadClass(Lock4jRedisKeyConstants.class.getName());
}
@Bean
public DefaultLockFailureStrategy lockFailureStrategy() {
return new DefaultLockFailureStrategy();

View File

@ -1,19 +0,0 @@
package cn.iocoder.yudao.framework.lock4j.core;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import org.redisson.api.RLock;
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH;
/**
* Lock4j Redis Key 枚举类
*
* @author 芋道源码
*/
public interface Lock4jRedisKeyConstants {
RedisKeyDefine LOCK4J = new RedisKeyDefine("分布式锁",
"lock4j:%s", // 参数来自 DefaultLockKeyBuilder
HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson Lock 使用 Hash 数据结构
}

View File

@ -32,11 +32,16 @@
<artifactId>spring-boot-starter-cache</artifactId> <!-- 实现对 Caches 的自动化配置 -->
</dependency>
<!-- 工具相关 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.redis.config;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -7,9 +9,17 @@ import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.util.Objects;
import static cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration.buildRedisSerializer;
/**
* Cache 配置类基于 Redis 实现
*/
@ -26,9 +36,13 @@ public class YudaoCacheAutoConfiguration {
@Bean
@Primary
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
// 设置使用 JSON 序列化方式
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
// 设置使用 : 单冒号而不是双 :: 冒号避免 Redis Desktop Manager 多余空格
// 详细可见 https://blog.csdn.net/chuixue24/article/details/103928965 博客
config = config.computePrefixWith(cacheName -> cacheName + StrUtil.COLON);
// 设置使用 JSON 序列化方式
config = config.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(buildRedisSerializer()));
// 设置 CacheProperties.Redis 的属性
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
@ -47,4 +61,14 @@ public class YudaoCacheAutoConfiguration {
return config;
}
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
RedisCacheConfiguration redisCacheConfiguration) {
// 创建 RedisCacheWriter 对象
RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
// 创建 TenantRedisCacheManager 对象
return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
}
}

View File

@ -1,5 +1,8 @@
package cn.iocoder.yudao.framework.redis.config;
import cn.hutool.core.util.ReflectUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@ -25,9 +28,17 @@ public class YudaoRedisAutoConfiguration {
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 使用 JSON 序列化方式库是 Jackson 序列化 VALUE
template.setValueSerializer(RedisSerializer.json());
template.setHashValueSerializer(RedisSerializer.json());
template.setValueSerializer(buildRedisSerializer());
template.setHashValueSerializer(buildRedisSerializer());
return template;
}
public static RedisSerializer<?> buildRedisSerializer() {
RedisSerializer<Object> json = RedisSerializer.json();
// 解决 LocalDateTime 的序列化
ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
objectMapper.registerModules(new JavaTimeModule());
return json;
}
}

View File

@ -1,113 +0,0 @@
package cn.iocoder.yudao.framework.redis.core;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import java.time.Duration;
/**
* Redis Key 定义类
*
* @author 芋道源码
*/
@Data
public class RedisKeyDefine {
@Getter
@AllArgsConstructor
public enum KeyTypeEnum {
STRING("String"),
LIST("List"),
HASH("Hash"),
SET("Set"),
ZSET("Sorted Set"),
STREAM("Stream"),
PUBSUB("Pub/Sub");
/**
* 类型
*/
@JsonValue
private final String type;
}
@Getter
@AllArgsConstructor
public enum TimeoutTypeEnum {
FOREVER(1), // 永不超时
DYNAMIC(2), // 动态超时
FIXED(3); // 固定超时
/**
* 类型
*/
@JsonValue
private final Integer type;
}
/**
* Key 模板
*/
private final String keyTemplate;
/**
* Key 类型的枚举
*/
private final KeyTypeEnum keyType;
/**
* Value 类型
*
* 如果是使用分布式锁设置为 {@link java.util.concurrent.locks.Lock} 类型
*/
private final Class<?> valueType;
/**
* 超时类型
*/
private final TimeoutTypeEnum timeoutType;
/**
* 过期时间
*/
private final Duration timeout;
/**
* 备注
*/
private final String memo;
private RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType,
TimeoutTypeEnum timeoutType, Duration timeout) {
this.memo = memo;
this.keyTemplate = keyTemplate;
this.keyType = keyType;
this.valueType = valueType;
this.timeout = timeout;
this.timeoutType = timeoutType;
// 添加注册表
RedisKeyRegistry.add(this);
}
public RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, Duration timeout) {
this(memo, keyTemplate, keyType, valueType, TimeoutTypeEnum.FIXED, timeout);
}
public RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, TimeoutTypeEnum timeoutType) {
this(memo, keyTemplate, keyType, valueType, timeoutType, Duration.ZERO);
}
/**
* 格式化 Key
*
* 注意内部采用 {@link String#format(String, Object...)} 实现
*
* @param args 格式化的参数
* @return Key
*/
public String formatKey(Object... args) {
return String.format(keyTemplate, args);
}
}

View File

@ -1,28 +0,0 @@
package cn.iocoder.yudao.framework.redis.core;
import java.util.ArrayList;
import java.util.List;
/**
* {@link RedisKeyDefine} 注册表
*/
public class RedisKeyRegistry {
/**
* Redis RedisKeyDefine 数组
*/
private static final List<RedisKeyDefine> DEFINES = new ArrayList<>();
public static void add(RedisKeyDefine define) {
DEFINES.add(define);
}
public static List<RedisKeyDefine> list() {
return DEFINES;
}
public static int size() {
return DEFINES.size();
}
}

View File

@ -0,0 +1,51 @@
package cn.iocoder.yudao.framework.redis.core;
import cn.hutool.core.util.StrUtil;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
/**
* 支持自定义过期时间的 {@link RedisCacheManager} 实现类
*
* {@link Cacheable#cacheNames()} 格式为 "key#ttl" # 后面的 ttl 为过期时间单位为秒
*
* @author 芋道源码
*/
public class TimeoutRedisCacheManager extends RedisCacheManager {
private static final String SPLIT = "#";
public TimeoutRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
if (StrUtil.isEmpty(name)) {
return super.createRedisCache(name, cacheConfig);
}
// 如果使用 # 分隔大小不为 2则说明不使用自定义过期时间
String[] names = StrUtil.splitToArray(name, SPLIT);
if (names.length != 2) {
return super.createRedisCache(name, cacheConfig);
}
// 核心通过修改 cacheConfig 的过期时间实现自定义过期时间
if (cacheConfig != null) {
// 移除 # 后面的 : 以及后面的内容避免影响解析
names[1] = StrUtil.subBefore(names[1], StrUtil.COLON, false);
// 解析时间
Duration duration = DurationStyle.detectAndParse(names[1], ChronoUnit.SECONDS);
cacheConfig = cacheConfig.entryTtl(duration);
}
return super.createRedisCache(names[0], cacheConfig);
}
}

View File

@ -21,25 +21,17 @@
<artifactId>yudao-common</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring boot 配置所需依赖 -->
<!-- Spring Boot 配置所需依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
@ -48,6 +40,15 @@
<scope>provided</scope> <!-- 设置为 provided主要是 GlobalExceptionHandler 使用 -->
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId> <!-- 接口文档 -->
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>

View File

@ -17,7 +17,7 @@ import java.util.Set;
* 第二步多个任务候选人们设置到 DelegateExecution collectionVariable 变量中以便 BpmUserTaskActivityBehavior 使用它
*
* @author kemengkai
* @date 2022-04-21 16:57
* @since 2022-04-21 16:57
*/
@Slf4j
public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior {

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.definition;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO;
@ -29,6 +30,7 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
*/
@Service
@Validated
@TenantDS // 工作流的 Service 必须添加 @TenantDS 注解原因是Flowable 使用事务无法切换数据源需要提使用 @TenantDS 切到它的数据源
public class BpmFormServiceImpl implements BpmFormService {
@Resource

View File

@ -7,6 +7,8 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
@ -47,6 +49,7 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
@Service
@Validated
@Slf4j
@TenantDS // 工作流的 Service 必须添加 @TenantDS 注解原因是Flowable 使用事务无法切换数据源需要提使用 @TenantDS 切到它的数据源
public class BpmModelServiceImpl implements BpmModelService {
@Resource
@ -71,7 +74,8 @@ public class BpmModelServiceImpl implements BpmModelService {
modelQuery.modelCategory(pageVO.getCategory());
}
// 执行查询
List<Model> models = modelQuery.orderByCreateTime().desc()
List<Model> models = modelQuery.modelTenantId(FlowableUtils.getTenantId())
.orderByCreateTime().desc()
.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
// 获得 Form Map
@ -107,6 +111,7 @@ public class BpmModelServiceImpl implements BpmModelService {
// 创建流程定义
Model model = repositoryService.newModel();
BpmModelConvert.INSTANCE.copy(model, createReqVO);
model.setTenantId(FlowableUtils.getTenantId());
// 保存流程定义
repositoryService.saveModel(model);
// 保存 BPMN XML

View File

@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
@ -49,6 +50,7 @@ import static java.util.Collections.emptyList;
@Service
@Validated
@Slf4j
@TenantDS // 工作流的 Service 必须添加 @TenantDS 注解原因是Flowable 使用事务无法切换数据源需要提使用 @TenantDS 切到它的数据源
public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionService {
private static final String BPMN_FILE_SUFFIX = ".bpmn";
@ -124,6 +126,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
Deployment deploy = repositoryService.createDeployment()
.key(createReqDTO.getKey()).name(createReqDTO.getName()).category(createReqDTO.getCategory())
.addBytes(createReqDTO.getKey() + BPMN_FILE_SUFFIX, createReqDTO.getBpmnBytes())
.tenantId(FlowableUtils.getTenantId())
.deploy();
// 设置 ProcessDefinition category 分类
@ -234,6 +237,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
definitionQuery.active();
}
// 执行查询
definitionQuery.processDefinitionTenantId(FlowableUtils.getTenantId());
List<ProcessDefinition> processDefinitions = definitionQuery.list();
if (CollUtil.isEmpty(processDefinitions)) {
return Collections.emptyList();

View File

@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
@ -53,6 +54,7 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
@Service
@Validated
@Slf4j
@TenantDS // 工作流的 Service 必须添加 @TenantDS 注解原因是Flowable 使用事务无法切换数据源需要提使用 @TenantDS 切到它的数据源
public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
@Resource

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupUpdateReqVO;
@ -30,6 +31,7 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
*/
@Service
@Validated
@TenantDS // 工作流的 Service 必须添加 @TenantDS 注解原因是Flowable 使用事务无法切换数据源需要提使用 @TenantDS 切到它的数据源
public class BpmUserGroupServiceImpl implements BpmUserGroupService {
@Resource

View File

@ -8,7 +8,10 @@ import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcess
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
@ -32,6 +35,7 @@ public class BpmMessageServiceImpl implements BpmMessageService {
private WebProperties webProperties;
@Override
@Async // 必须使用异步因为是在 Flowable 事务内调用无法切换数据源
public void sendMessageWhenProcessInstanceApprove(BpmMessageSendWhenProcessInstanceApproveReqDTO reqDTO) {
Map<String, Object> templateParams = new HashMap<>();
templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
@ -41,6 +45,7 @@ public class BpmMessageServiceImpl implements BpmMessageService {
}
@Override
@Async // 必须使用异步因为是在 Flowable 事务内调用无法切换数据源
public void sendMessageWhenProcessInstanceReject(BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO) {
Map<String, Object> templateParams = new HashMap<>();
templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
@ -51,6 +56,7 @@ public class BpmMessageServiceImpl implements BpmMessageService {
}
@Override
@Async // 必须使用异步因为是在 Flowable 事务内调用无法切换数据源
public void sendMessageWhenTaskAssigned(BpmMessageSendWhenTaskCreatedReqDTO reqDTO) {
Map<String, Object> templateParams = new HashMap<>();
templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.service.oa;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeaveCreateReqVO;
@ -29,6 +30,7 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.OA_LEAVE_NOT_
*/
@Service
@Validated
@TenantDS // 工作流的 Service 必须添加 @TenantDS 注解原因是Flowable 使用事务无法切换数据源需要提使用 @TenantDS 切到它的数据源
public class BpmOALeaveServiceImpl implements BpmOALeaveService {
/**

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.service.task;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
import cn.iocoder.yudao.module.bpm.convert.task.BpmActivityConvert;
import lombok.extern.slf4j.Slf4j;
@ -20,6 +21,7 @@ import java.util.List;
@Service
@Slf4j
@Validated
@TenantDS // 工作流的 Service 必须添加 @TenantDS 注解原因是Flowable 使用事务无法切换数据源需要提使用 @TenantDS 切到它的数据源
public class BpmActivityServiceImpl implements BpmActivityService {
@Resource

View File

@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
@ -51,6 +52,7 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
*/
@Slf4j
@Service
@TenantDS // 工作流的 Service 必须添加 @TenantDS 注解原因是Flowable 使用事务无法切换数据源需要提使用 @TenantDS 切到它的数据源
public class BpmTaskServiceImpl implements BpmTaskService {
@Resource

View File

@ -1 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task;

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.infra.api.db;
import cn.iocoder.yudao.module.infra.api.db.dto.DataSourceConfigRespDTO;
/**
* 数据源配置 API 接口
*
* @author 芋道源码
*/
public interface DataSourceConfigServiceApi {
/**
* 获得数据源配置
*
* @param id 编号
* @return 数据源配置
*/
DataSourceConfigRespDTO getDataSourceConfig(Long id);
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.infra.api.db.dto;
import lombok.Data;
/**
* 数据源配置 Response DTO
*
* @author 芋道源码
*/
@Data
public class DataSourceConfigRespDTO {
/**
* 主键编号
*/
private Long id;
/**
* 连接名
*/
private String name;
/**
* 数据源连接
*/
private String url;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.infra.api.db;
import cn.iocoder.yudao.module.infra.api.db.dto.DataSourceConfigRespDTO;
import cn.iocoder.yudao.module.infra.convert.db.DataSourceConfigConvert;
import cn.iocoder.yudao.module.infra.service.db.DataSourceConfigService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 数据源配置 API 实现类
*
* @author 芋道源码
*/
@Service
public class DataSourceConfigServiceApiImpl implements DataSourceConfigServiceApi {
@Resource
private DataSourceConfigService dataSourceConfigService;
@Override
public DataSourceConfigRespDTO getDataSourceConfig(Long id) {
return DataSourceConfigConvert.INSTANCE.convert02(dataSourceConfigService.getDataSourceConfig(id));
}
}

View File

@ -3,13 +3,14 @@ package cn.iocoder.yudao.module.infra.controller.admin.db;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.infra.controller.admin.db.vo.DataSourceConfigCreateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.db.vo.DataSourceConfigRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.db.vo.DataSourceConfigSimpleRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.db.vo.DataSourceConfigUpdateReqVO;
import cn.iocoder.yudao.module.infra.convert.db.DataSourceConfigConvert;
import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
import cn.iocoder.yudao.module.infra.service.db.DataSourceConfigService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -70,4 +71,11 @@ public class DataSourceConfigController {
return success(DataSourceConfigConvert.INSTANCE.convertList(list));
}
@GetMapping("/list-all-simple")
@Operation(summary = "获取数据源配置精简信息列表", description = "主要用于前端的下拉选项")
public CommonResult<List<DataSourceConfigSimpleRespVO>> getSimpleDataSourceConfigList() {
List<DataSourceConfigDO> list = dataSourceConfigService.getDataSourceConfigList();
return success(DataSourceConfigConvert.INSTANCE.convertList02(list));
}
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.infra.controller.admin.db.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 数据源配置的精简 Response VO")
@Data
public class DataSourceConfigSimpleRespVO {
@Schema(description = "主键编号", required = true, example = "1024")
private Integer id;
@Schema(description = "数据源名称", required = true, example = "test")
private String name;
}

View File

@ -3,15 +3,12 @@ package cn.iocoder.yudao.module.infra.controller.admin.redis;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import cn.iocoder.yudao.framework.redis.core.RedisKeyRegistry;
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisKeyDefineRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisKeyValueRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisMonitorRespVO;
import cn.iocoder.yudao.module.infra.convert.redis.RedisConvert;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
@ -50,9 +47,9 @@ public class RedisController {
@GetMapping("/get-key-define-list")
@Operation(summary = "获得 Redis Key 模板列表")
@PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
public CommonResult<List<RedisKeyDefineRespVO>> getKeyDefineList() {
List<RedisKeyDefine> keyDefines = RedisKeyRegistry.list();
return success(RedisConvert.INSTANCE.convertList(keyDefines));
@Deprecated // 建议使用 https://blog.jetbrains.com/datagrip/2022/11/02/datagrip-2022-3-eap-2-redis-support/ 连接 Redis 查询
public CommonResult<List<Object>> getKeyDefineList() {
return success(Collections.emptyList());
}
@GetMapping("/get-key-list")

View File

@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.infra.controller.admin.redis.vo;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import java.time.Duration;
@Schema(description = "管理后台 - Redis Key 信息 Response VO")
@Data
@Builder
@AllArgsConstructor
public class RedisKeyDefineRespVO {
@Schema(description = "Key 模板", required = true, example = "login_user:%s")
private String keyTemplate;
@Schema(description = "Key 类型的枚举", required = true, example = "String")
private RedisKeyDefine.KeyTypeEnum keyType;
@Schema(description = "Value 类型", required = true, example = "java.lang.String")
private Class<?> valueType;
@Schema(description = "超时类型", required = true, example = "1")
private RedisKeyDefine.TimeoutTypeEnum timeoutType;
@Schema(description = "过期时间,单位:毫秒", required = true, example = "1024")
private Duration timeout;
@Schema(description = "备注", required = true, example = "啦啦啦啦~")
private String memo;
}

View File

@ -4,6 +4,7 @@ import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.api.db.dto.DataSourceConfigRespDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import cn.iocoder.yudao.module.infra.controller.admin.db.vo.*;
@ -27,4 +28,8 @@ public interface DataSourceConfigConvert {
List<DataSourceConfigRespVO> convertList(List<DataSourceConfigDO> list);
DataSourceConfigRespDTO convert02(DataSourceConfigDO bean);
List<DataSourceConfigSimpleRespVO> convertList02(List<DataSourceConfigDO> list);
}

View File

@ -1,14 +1,11 @@
package cn.iocoder.yudao.module.infra.convert.redis;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisKeyDefineRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisMonitorRespVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
@Mapper
@ -29,6 +26,4 @@ public interface RedisConvert {
return respVO;
}
List<RedisKeyDefineRespVO> convertList(List<RedisKeyDefine> list);
}

View File

@ -24,11 +24,7 @@ public class SecurityConfiguration {
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
// Swagger 接口文档
registry.antMatchers("/v3/api-docs/**").permitAll()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-ui/**").permitAll()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous();
.antMatchers("/swagger-ui.html").permitAll();
// 积木报表
registry.antMatchers("/jmreport/**").permitAll();
// Spring Boot Actuator 的安全配置

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.pay.dal.redis;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import org.redisson.api.RLock;
/**
@ -10,8 +9,13 @@ import org.redisson.api.RLock;
*/
public interface RedisKeyConstants {
RedisKeyDefine PAY_NOTIFY_LOCK = new RedisKeyDefine("通知任务的分布式锁",
"pay_notify:lock:", // 参数来自 DefaultLockKeyBuilder
RedisKeyDefine.KeyTypeEnum.HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson Lock 使用 Hash 数据结构
/**
* 通知任务的分布式锁
*
* KEY 格式pay_notify:{id}
* VALUE 数据类型HASH {@link RLock}
* 过期时间动态传参
*/
String PAY_NOTIFY_LOCK = "pay_notify:lock:%s";
}

View File

@ -20,6 +20,13 @@ public class PayNotifyLockRedisDAO {
@Resource
private RedissonClient redissonClient;
/**
* 获得分布式锁执行逻辑
*
* @param id 通知任务编号
* @param timeoutMillis 过期时间
* @param runnable 逻辑
*/
public void lock(Long id, Long timeoutMillis, Runnable runnable) {
String lockKey = formatKey(id);
RLock lock = redissonClient.getLock(lockKey);
@ -33,7 +40,7 @@ public class PayNotifyLockRedisDAO {
}
private static String formatKey(Long id) {
return String.format(PAY_NOTIFY_LOCK.getKeyTemplate(), id);
return String.format(PAY_NOTIFY_LOCK, id);
}
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.system.api.tenant;
import cn.iocoder.yudao.module.system.api.tenant.dto.TenantDataSourceConfigRespDTO;
import java.util.List;
/**
@ -23,4 +25,12 @@ public interface TenantApi {
*/
void validateTenant(Long id);
/**
* 获得租户的数据源配置
*
* @param tenantId 租户编号
* @return 数据源配置
*/
TenantDataSourceConfigRespDTO getTenantDataSourceConfig(Long tenantId);
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.system.api.tenant.dto;
import lombok.Data;
/**
* 多租户的数据源配置 Response DTO
*
* @author 芋道源码
*/
@Data
public class TenantDataSourceConfigRespDTO {
/**
* 连接名
*/
private String name;
/**
* 数据源连接
*/
private String url;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}

View File

@ -7,7 +7,7 @@ import lombok.Getter;
* 短信的接收状态枚举
*
* @author 芋道源码
* @date 2021/2/1 13:39
* @since 2021/2/1 13:39
*/
@Getter
@AllArgsConstructor

View File

@ -7,7 +7,7 @@ import lombok.Getter;
* 短信的发送状态枚举
*
* @author zzf
* @date 2021/2/1 13:39
* @since 2021/2/1 13:39
*/
@Getter
@AllArgsConstructor

View File

@ -21,7 +21,7 @@ public class PermissionApiImpl implements PermissionApi {
@Override
public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
return permissionService.getUserRoleIdListByRoleIds(roleIds);
return permissionService.getUserRoleIdListByRoleId(roleIds);
}
@Override

View File

@ -1,5 +1,9 @@
package cn.iocoder.yudao.module.system.api.tenant;
import cn.iocoder.yudao.module.infra.api.db.DataSourceConfigServiceApi;
import cn.iocoder.yudao.module.system.api.tenant.dto.TenantDataSourceConfigRespDTO;
import cn.iocoder.yudao.module.system.convert.tenant.TenantConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import cn.iocoder.yudao.module.system.service.tenant.TenantService;
import org.springframework.stereotype.Service;
@ -17,6 +21,9 @@ public class TenantApiImpl implements TenantApi {
@Resource
private TenantService tenantService;
@Resource
private DataSourceConfigServiceApi dataSourceConfigServiceApi;
@Override
public List<Long> getTenantIdList() {
return tenantService.getTenantIdList();
@ -27,4 +34,16 @@ public class TenantApiImpl implements TenantApi {
tenantService.validTenant(id);
}
@Override
public TenantDataSourceConfigRespDTO getTenantDataSourceConfig(Long tenantId) {
// 获得租户信息
TenantDO tenant = tenantService.getTenant(tenantId);
if (tenant == null) {
return null;
}
// 获得租户的数据源配置
return TenantConvert.INSTANCE.convert(
dataSourceConfigServiceApi.getDataSourceConfig(tenant.getDataSourceConfigId()));
}
}

View File

@ -24,9 +24,3 @@ tenant-id: {{adminTenentId}}
GET {{baseUrl}}/system/auth/get-permission-info
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 请求 /list-menus 接口 => 成功
GET {{baseUrl}}/system/list-menus
Authorization: Bearer {{token}}
#Authorization: Bearer a6aa7714a2e44c95aaa8a2c5adc2a67a
tenant-id: {{adminTenentId}}

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.controller.admin.auth;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
@ -12,16 +11,16 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
import cn.iocoder.yudao.module.system.service.permission.MenuService;
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.permission.RoleService;
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -34,9 +33,9 @@ import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
import static java.util.Collections.singleton;
@Tag(name = "管理后台 - 认证")
@RestController
@ -52,6 +51,8 @@ public class AuthController {
@Resource
private RoleService roleService;
@Resource
private MenuService menuService;
@Resource
private PermissionService permissionService;
@Resource
private SocialUserService socialUserService;
@ -90,33 +91,24 @@ public class AuthController {
@GetMapping("/get-permission-info")
@Operation(summary = "获取登录用户的权限信息")
public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
// 获得用户信息
// 1.1 获得用户信息
AdminUserDO user = userService.getUser(getLoginUserId());
if (user == null) {
return null;
}
// 获得角色列表
Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
List<RoleDO> roleList = roleService.getRoleListFromCache(roleIds);
// 获得菜单列表
List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
// 拼接结果返回
return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
}
@GetMapping("/list-menus")
@Operation(summary = "获得登录用户的菜单列表")
public CommonResult<List<AuthMenuRespVO>> getMenuList() {
// 获得角色列表
Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
// 获得用户拥有的菜单列表
List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
// 转换成 Tree 结构返回
return success(AuthConvert.INSTANCE.buildMenuTree(menuList));
// 1.2 获得角色列表
Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId());
List<RoleDO> roles = roleService.getRoleList(roleIds);
roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色
// 1.3 获得菜单列表
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));
List<MenuDO> menuList = menuService.getMenuList(menuIds);
menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单
// 2. 拼接结果返回
return success(AuthConvert.INSTANCE.convert(user, roles, menuList));
}
// ========== 短信登录相关 ==========

View File

@ -1,53 +0,0 @@
package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Schema(description = "管理后台 - 登录用户的菜单信息 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthMenuRespVO {
@Schema(description = "菜单名称", required = true, example = "芋道")
private Long id;
@Schema(description = "父菜单 ID", required = true, example = "1024")
private Long parentId;
@Schema(description = "菜单名称", required = true, example = "芋道")
private String name;
@Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
private String path;
@Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
private String component;
@Schema(description = "组件名", example = "SystemUser")
private String componentName;
@Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
private String icon;
@Schema(description = "是否可见", required = true, example = "false")
private Boolean visible;
@Schema(description = "是否缓存", required = true, example = "false")
private Boolean keepAlive;
@Schema(description = "是否总是显示", example = "false")
private Boolean alwaysShow;
/**
* 子路由
*/
private List<AuthMenuRespVO> children;
}

View File

@ -6,6 +6,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Set;
@Schema(description = "管理后台 - 登录用户的权限信息 Response VO,额外包括用户信息和角色列表")
@ -24,6 +25,9 @@ public class AuthPermissionInfoRespVO {
@Schema(description = "操作权限数组", required = true)
private Set<String> permissions;
@Schema(description = "菜单树", required = true)
private List<MenuVO> menus;
@Schema(description = "用户信息 VO")
@Data
@NoArgsConstructor
@ -37,9 +41,53 @@ public class AuthPermissionInfoRespVO {
@Schema(description = "用户昵称", required = true, example = "芋道源码")
private String nickname;
@Schema(description = "用户头像", required = true, example = "http://www.iocoder.cn/xx.jpg")
@Schema(description = "用户头像", required = true, example = "https://www.iocoder.cn/xx.jpg")
private String avatar;
}
@Schema(description = "管理后台 - 登录用户的菜单信息 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class MenuVO {
@Schema(description = "菜单名称", required = true, example = "芋道")
private Long id;
@Schema(description = "父菜单 ID", required = true, example = "1024")
private Long parentId;
@Schema(description = "菜单名称", required = true, example = "芋道")
private String name;
@Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
private String path;
@Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
private String component;
@Schema(description = "组件名", example = "SystemUser")
private String componentName;
@Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
private String icon;
@Schema(description = "是否可见", required = true, example = "false")
private Boolean visible;
@Schema(description = "是否缓存", required = true, example = "false")
private Boolean keepAlive;
@Schema(description = "是否总是显示", example = "false")
private Boolean alwaysShow;
/**
* 子路由
*/
private List<MenuVO> children;
}
}

View File

@ -37,10 +37,10 @@ public class PermissionController {
@Operation(summary = "获得角色拥有的菜单编号")
@Parameter(name = "roleId", description = "角色编号", required = true)
@GetMapping("/list-role-resources")
@GetMapping("/list-role-menus")
@PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
public CommonResult<Set<Long>> listRoleMenus(Long roleId) {
return success(permissionService.getRoleMenuIds(roleId));
public CommonResult<Set<Long>> getRoleMenuList(Long roleId) {
return success(permissionService.getRoleMenuListByRoleId(roleId));
}
@PostMapping("/assign-role-menu")

View File

@ -13,7 +13,7 @@ tenant-id: {{adminTenentId}}
"contactMobile": "15601691300",
"status": 0,
"domain": "https://www.iocoder.cn",
"packageId": 110,
"packageId": 111,
"expireTime": 1699545600000,
"accountCount": 20,
"username": "admin",

View File

@ -43,4 +43,8 @@ public class TenantBaseVO {
@NotNull(message = "账号数量不能为空")
private Integer accountCount;
@Schema(description = "数据源配置编号", required = true, example = "4096")
@NotNull(message = "数据源配置编号不能为空")
private Long dataSourceConfigId;
}

View File

@ -63,7 +63,7 @@ public class UserProfileController {
AdminUserDO user = userService.getUser(getLoginUserId());
UserProfileRespVO resp = UserConvert.INSTANCE.convert03(user);
// 获得用户角色
List<RoleDO> userRoles = roleService.getRoleListFromCache(permissionService.getUserRoleIdListByUserId(user.getId()));
List<RoleDO> userRoles = roleService.getRoleList(permissionService.getUserRoleIdListByUserId(user.getId()));
resp.setRoles(UserConvert.INSTANCE.convertList(userRoles));
// 获得部门信息
if (user.getDeptId() != null) {

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.convert.auth;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
@ -9,12 +8,14 @@ import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import org.slf4j.LoggerFactory;
import java.util.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
@ -27,13 +28,19 @@ public interface AuthConvert {
default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
return AuthPermissionInfoRespVO.builder()
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build())
.roles(CollectionUtils.convertSet(roleList, RoleDO::getCode))
.permissions(CollectionUtils.convertSet(menuList, MenuDO::getPermission))
.build();
// 用户信息
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId())
.nickname(user.getNickname()).avatar(user.getAvatar()).build())
// 角色信息
.roles(convertSet(roleList, RoleDO::getCode))
// 权限标识信息
.permissions(convertSet(menuList, MenuDO::getPermission))
// 菜单树
.menus(buildMenuTree(menuList))
.build();
}
AuthMenuRespVO convertTreeNode(MenuDO menu);
AuthPermissionInfoRespVO.MenuVO convertTreeNode(MenuDO menu);
/**
* 将菜单列表构建成菜单树
@ -41,17 +48,20 @@ public interface AuthConvert {
* @param menuList 菜单列表
* @return 菜单树
*/
default List<AuthMenuRespVO> buildMenuTree(List<MenuDO> menuList) {
default List<AuthPermissionInfoRespVO.MenuVO> buildMenuTree(List<MenuDO> menuList) {
// 移除按钮
menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType()));
// 排序保证菜单的有序性
menuList.sort(Comparator.comparing(MenuDO::getSort));
// 构建菜单树
// 使用 LinkedHashMap 的原因是为了排序 实际也可以用 Stream API 就是太丑了
Map<Long, AuthMenuRespVO> treeNodeMap = new LinkedHashMap<>();
Map<Long, AuthPermissionInfoRespVO.MenuVO> treeNodeMap = new LinkedHashMap<>();
menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu)));
// 处理父子关系
treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(ID_ROOT)).forEach(childNode -> {
// 获得父节点
AuthMenuRespVO parentNode = treeNodeMap.get(childNode.getParentId());
AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId());
if (parentNode == null) {
LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]",
childNode.getId(), childNode.getParentId());

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.system.convert.tenant;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.api.db.dto.DataSourceConfigRespDTO;
import cn.iocoder.yudao.module.system.api.tenant.dto.TenantDataSourceConfigRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExcelVO;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantRespVO;
@ -42,4 +44,6 @@ public interface TenantConvert {
return reqVO;
}
TenantDataSourceConfigRespDTO convert(DataSourceConfigRespDTO bean);
}

View File

@ -79,4 +79,13 @@ public class TenantDO extends BaseDO {
*/
private Integer accountCount;
/**
* 数据源配置编号
*
* 多租户采用分库方案时通过该字段配置所在数据源
*
* 关联 DataSourceConfigDO id 字段
*/
private Long dataSourceConfigId;
}

View File

@ -2,13 +2,16 @@ package cn.iocoder.yudao.module.system.dal.mysql.dept;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
@Mapper
@TenantDS
public interface DeptMapper extends BaseMapperX<DeptDO> {
default List<DeptDO> selectList(DeptListReqVO reqVO) {
@ -25,4 +28,8 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
return selectCount(DeptDO::getParentId, parentId);
}
default List<DeptDO> selectListByParentId(Collection<Long> parentIds) {
return selectList(DeptDO::getParentId, parentIds);
}
}

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.dept;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.post.PostExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.post.PostPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
@ -12,6 +13,7 @@ import java.util.Collection;
import java.util.List;
@Mapper
@TenantDS
public interface PostMapper extends BaseMapperX<PostDO> {
default List<PostDO> selectList(Collection<Long> ids, Collection<Integer> statuses) {

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.dept;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.ibatis.annotations.Mapper;
@ -10,6 +11,7 @@ import java.util.Collection;
import java.util.List;
@Mapper
@TenantDS
public interface UserPostMapper extends BaseMapperX<UserPostDO> {
default List<UserPostDO> selectListByUserId(Long userId) {
@ -27,6 +29,6 @@ public interface UserPostMapper extends BaseMapperX<UserPostDO> {
}
default void deleteByUserId(Long userId) {
delete(Wrappers.lambdaUpdate(UserPostDO.class).eq(UserPostDO::getUserId, userId));
delete(new LambdaQueryWrapperX<UserPostDO>().eq(UserPostDO::getUserId, userId));
}
}

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
@ -14,6 +15,7 @@ import java.util.Collection;
import java.util.List;
@Mapper
@Master
public interface DictDataMapper extends BaseMapperX<DictDataDO> {
default DictDataDO selectByDictTypeAndValue(String dictType, String value) {

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypeExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
@ -14,6 +15,7 @@ import java.time.LocalDateTime;
import java.util.List;
@Mapper
@Master
public interface DictTypeMapper extends BaseMapperX<DictTypeDO> {
default PageResult<DictTypeDO> selectPage(DictTypePageReqVO reqVO) {

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.errorcode.vo.ErrorCodeExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.errorcode.vo.ErrorCodePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.errorcode.ErrorCodeDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
@ -13,6 +14,7 @@ import java.util.Collection;
import java.util.List;
@Mapper
@Master
public interface ErrorCodeMapper extends BaseMapperX<ErrorCodeDO> {
default PageResult<ErrorCodeDO> selectPage(ErrorCodePageReqVO reqVO) {

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.logger;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.controller.admin.logger.vo.loginlog.LoginLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.LoginLogDO;
@ -12,6 +13,7 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
@TenantDS
public interface LoginLogMapper extends BaseMapperX<LoginLogDO> {
default PageResult<LoginLogDO> selectPage(LoginLogPageReqVO reqVO) {

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstant
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
@ -13,6 +14,7 @@ import java.util.Collection;
import java.util.List;
@Mapper
@TenantDS
public interface OperateLogMapper extends BaseMapperX<OperateLogDO> {
default PageResult<OperateLogDO> selectPage(OperateLogPageReqVO reqVO, Collection<Long> userIds) {

View File

@ -6,9 +6,11 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
@Mapper
@Master
public interface MailAccountMapper extends BaseMapperX<MailAccountDO> {
default PageResult<MailAccountDO> selectPage(MailAccountPageReqVO pageReqVO) {

View File

@ -5,9 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
@Mapper
@Master
public interface MailLogMapper extends BaseMapperX<MailLogDO> {
default PageResult<MailLogDO> selectPage(MailLogPageReqVO reqVO) {

View File

@ -7,12 +7,14 @@ import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Date;
@Mapper
@Master
public interface MailTemplateMapper extends BaseMapperX<MailTemplateDO> {
default PageResult<MailTemplateDO> selectPage(MailTemplatePageReqVO pageReqVO){

View File

@ -3,11 +3,13 @@ package cn.iocoder.yudao.module.system.dal.mysql.notice;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.controller.admin.notice.vo.NoticePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.notice.NoticeDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
@TenantDS
public interface NoticeMapper extends BaseMapperX<NoticeDO> {
default PageResult<NoticeDO> selectPage(NoticePageReqVO reqVO) {

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyMessageDO;
@ -14,6 +15,7 @@ import java.util.Collection;
import java.util.List;
@Mapper
@TenantDS
public interface NotifyMessageMapper extends BaseMapperX<NotifyMessageDO> {
default PageResult<NotifyMessageDO> selectPage(NotifyMessagePageReqVO reqVO) {

View File

@ -5,9 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
@Mapper
@Master
public interface NotifyTemplateMapper extends BaseMapperX<NotifyTemplateDO> {
default NotifyTemplateDO selectByCode(String code) {

View File

@ -4,11 +4,13 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
@Master
public interface MenuMapper extends BaseMapperX<MenuDO> {
default MenuDO selectByParentIdAndName(Long parentId, String name) {
@ -25,4 +27,7 @@ public interface MenuMapper extends BaseMapperX<MenuDO> {
.eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));
}
default List<MenuDO> selectListByPermission(String permission) {
return selectList(MenuDO::getPermission, permission);
}
}

View File

@ -4,16 +4,20 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.lang.Nullable;
import java.util.Collection;
import java.util.List;
@Mapper
@TenantDS
public interface RoleMapper extends BaseMapperX<RoleDO> {
default PageResult<RoleDO> selectPage(RolePageReqVO reqVO) {
@ -41,7 +45,7 @@ public interface RoleMapper extends BaseMapperX<RoleDO> {
return selectOne(RoleDO::getCode, code);
}
default List<RoleDO> selectListByStatus(@Nullable Collection<Integer> statuses) {
default List<RoleDO> selectListByStatus(Collection<Integer> statuses) {
return selectList(RoleDO::getStatus, statuses);
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.dal.mysql.permission;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -9,18 +10,24 @@ import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@Mapper
@TenantDS
public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> {
@Repository
class BatchInsertMapper extends ServiceImpl<RoleMenuMapper, RoleMenuDO> {
}
default List<RoleMenuDO> selectListByRoleId(Long roleId) {
return selectList(RoleMenuDO::getRoleId, roleId);
}
default List<RoleMenuDO> selectListByRoleId(Collection<Long> roleIds) {
return selectList(RoleMenuDO::getRoleId, roleIds);
}
default List<RoleMenuDO> selectListByMenuId(Long menuId) {
return selectList(RoleMenuDO::getMenuId, menuId);
}
default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {
delete(new LambdaQueryWrapper<RoleMenuDO>()
.eq(RoleMenuDO::getRoleId, roleId)

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.dal.mysql.permission;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
@ -9,6 +10,7 @@ import java.util.Collection;
import java.util.List;
@Mapper
@TenantDS
public interface UserRoleMapper extends BaseMapperX<UserRoleDO> {
default List<UserRoleDO> selectListByUserId(Long userId) {

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@ -16,6 +17,7 @@ import java.util.List;
* @author 永不言败
*/
@Mapper
@Master
public interface SensitiveWordMapper extends BaseMapperX<SensitiveWordDO> {
default PageResult<SensitiveWordDO> selectPage(SensitiveWordPageReqVO reqVO) {

View File

@ -5,9 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
@Mapper
@Master
public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
default PageResult<SmsChannelDO> selectPage(SmsChannelPageReqVO reqVO) {

View File

@ -3,9 +3,11 @@ package cn.iocoder.yudao.module.system.dal.mysql.sms;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsCodeDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
@Mapper
@Master
public interface SmsCodeMapper extends BaseMapperX<SmsCodeDO> {
/**

View File

@ -6,11 +6,13 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
@Master
public interface SmsLogMapper extends BaseMapperX<SmsLogDO> {
default PageResult<SmsLogDO> selectPage(SmsLogPageReqVO reqVO) {

View File

@ -6,11 +6,13 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import com.baomidou.dynamic.datasource.annotation.Master;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
@Master
public interface SmsTemplateMapper extends BaseMapperX<SmsTemplateDO> {
default SmsTemplateDO selectByCode(String code) {

View File

@ -2,12 +2,14 @@ package cn.iocoder.yudao.module.system.dal.mysql.social;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
@TenantDS
public interface SocialUserBindMapper extends BaseMapperX<SocialUserBindDO> {
default void deleteByUserTypeAndUserIdAndSocialType(Integer userType, Long userId, Integer socialType) {

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.system.dal.mysql.social;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@ -10,6 +11,7 @@ import java.util.Collection;
import java.util.List;
@Mapper
@TenantDS
public interface SocialUserMapper extends BaseMapperX<SocialUserDO> {
default SocialUserDO selectByTypeAndCodeAnState(Integer type, String code, String state) {

Some files were not shown because too many files have changed in this diff Show More