mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 02:08:43 +08:00 
			
		
		
		
	Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/mall_product
This commit is contained in:
		| @@ -1,6 +1,7 @@ | |||||||
| # Docker Build & Up | # Docker Build & Up | ||||||
|  |  | ||||||
| 目标: 快速部署体验系统,帮助了解系统之间的依赖关系。 | 目标: 快速部署体验系统,帮助了解系统之间的依赖关系。 | ||||||
|  | 依赖:docker compose v2,删除`name: yudao-system`,降低`version`版本为`3.3`以下,支持`docker-compose`。 | ||||||
|  |  | ||||||
| ## 功能文件列表 | ## 功能文件列表 | ||||||
|  |  | ||||||
| @@ -8,16 +9,16 @@ | |||||||
| . | . | ||||||
| ├── Docker-HOWTO.md                  | ├── Docker-HOWTO.md                  | ||||||
| ├── docker-compose.yml               | ├── docker-compose.yml               | ||||||
| ├── docker.env | ├── docker.env                      <-- 提供docker-compose环境变量配置 | ||||||
| ├── yudao-server | ├── yudao-server | ||||||
| │   ├── Dockerfile | │   └── Dockerfile | ||||||
| │   └── nginx.conf |  | ||||||
| └── yudao-ui-admin | └── yudao-ui-admin | ||||||
|     ├── .dockerignore |     ├── .dockerignore | ||||||
|     └── Dockerfile |     ├── Dockerfile | ||||||
|  |     └── nginx.conf                  <-- 提供基础配置,gzip压缩、api转发 | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## Maven build (Optional) | ## 构建 jar 包 | ||||||
|  |  | ||||||
| ```shell | ```shell | ||||||
| # 创建maven缓存volume | # 创建maven缓存volume | ||||||
| @@ -30,29 +31,19 @@ docker run -it --rm --name yudao-maven \ | |||||||
|     maven mvn clean install package '-Dmaven.test.skip=true' |     maven mvn clean install package '-Dmaven.test.skip=true' | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## Docker Compose Build | ## 构建启动服务 | ||||||
|  |  | ||||||
| ```shell |  | ||||||
| docker compose --env-file docker.env build |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Docker Compose Up |  | ||||||
|  |  | ||||||
| ```shell | ```shell | ||||||
| docker compose --env-file docker.env up -d | docker compose --env-file docker.env up -d | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| 第一次执行,由于数据库未初始化,因此yudao-server容器会运行失败。执行如下命令初始化数据库: | 首次运行会自动构建容器。可以通过`docker compose build [service]`来手动构建所有或某个docker镜像 | ||||||
|  |  | ||||||
| ```shell | `--env-file docker.env`为可选参数,只是展示了通过`.env`文件配置容器启动的环境变量,`docker-compose.yml`本身已经提供足够的默认参数来正常运行系统。 | ||||||
| docker compose exec -T mysql \ |  | ||||||
|     sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" --default-character-set=utf8mb4 ruoyi-vue-pro' \ |  | ||||||
|     < ./sql/mysql/ruoyi-vue-pro.sql |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Server:Port | ## 服务器的宿主机端口映射 | ||||||
|  |  | ||||||
| - admin: http://localhost:8080 | - admin ui: http://localhost:8080 | ||||||
| - API: http://localhost:48080 | - api server: http://localhost:48080 | ||||||
| - mysql: root/123456, port: 3308 | - mysql: root/123456, port: 3306 | ||||||
| - redis: port: 6379 | - redis: port: 6379 | ||||||
|   | |||||||
| @@ -102,7 +102,7 @@ | |||||||
|  |  | ||||||
| 系统内置多种多种业务功能,可以用于快速你的业务系统: | 系统内置多种多种业务功能,可以用于快速你的业务系统: | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| * 系统功能 | * 系统功能 | ||||||
| * 基础设施 | * 基础设施 | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| version: "3.8" | version: "3.4" | ||||||
|  |  | ||||||
| name: yudao-system | name: yudao-system | ||||||
|  |  | ||||||
| @@ -9,25 +9,22 @@ services: | |||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     tty: true |     tty: true | ||||||
|     ports: |     ports: | ||||||
|       - 13306:3306 |       - "3306:3306" | ||||||
|     environment: |     environment: | ||||||
|       MYSQL_DATABASE: ${MYSQL_DATABASE:-ruoyi-vue-pro} |       MYSQL_DATABASE: ${MYSQL_DATABASE:-ruoyi-vue-pro} | ||||||
|       MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-123456} |       MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-123456} | ||||||
|     volumes: |     volumes: | ||||||
|       - mysql:/var/lib/mysql/ |       - mysql:/var/lib/mysql/ | ||||||
|     networks: |       - ./sql/mysql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/ruoyi-vue-pro.sql:ro | ||||||
|       - yudao-network |  | ||||||
|  |  | ||||||
|   redis: |   redis: | ||||||
|     container_name: yudao-redis |     container_name: yudao-redis | ||||||
|     image: redis:6-alpine |     image: redis:6-alpine | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     ports: |     ports: | ||||||
|       - 16379:6379 |       - "6379:6379" | ||||||
|     volumes: |     volumes: | ||||||
|       - redis:/data |       - redis:/data | ||||||
|     networks: |  | ||||||
|       - yudao-network |  | ||||||
|  |  | ||||||
|   server: |   server: | ||||||
|     container_name: yudao-server |     container_name: yudao-server | ||||||
| @@ -36,7 +33,7 @@ services: | |||||||
|     image: yudao-server |     image: yudao-server | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     ports: |     ports: | ||||||
|       - 48080:48080 |       - "48080:48080" | ||||||
|     environment: |     environment: | ||||||
|       # https://github.com/polovyivan/docker-pass-configs-to-container |       # https://github.com/polovyivan/docker-pass-configs-to-container | ||||||
|       SPRING_PROFILES_ACTIVE: local |       SPRING_PROFILES_ACTIVE: local | ||||||
| @@ -54,8 +51,6 @@ services: | |||||||
|         --spring.datasource.dynamic.datasource.slave.username=${SLAVE_DATASOURCE_USERNAME:-root} |         --spring.datasource.dynamic.datasource.slave.username=${SLAVE_DATASOURCE_USERNAME:-root} | ||||||
|         --spring.datasource.dynamic.datasource.slave.password=${SLAVE_DATASOURCE_PASSWORD:-123456} |         --spring.datasource.dynamic.datasource.slave.password=${SLAVE_DATASOURCE_PASSWORD:-123456} | ||||||
|         --spring.redis.host=${REDIS_HOST:-yudao-redis} |         --spring.redis.host=${REDIS_HOST:-yudao-redis} | ||||||
|     networks: |  | ||||||
|       - yudao-network |  | ||||||
|     depends_on: |     depends_on: | ||||||
|       - mysql |       - mysql | ||||||
|       - redis |       - redis | ||||||
| @@ -78,16 +73,10 @@ services: | |||||||
|     image: yudao-admin |     image: yudao-admin | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     ports: |     ports: | ||||||
|       - 8080:80 |       - "8080:80" | ||||||
|     networks: |  | ||||||
|       - yudao-network |  | ||||||
|     depends_on: |     depends_on: | ||||||
|       - server |       - server | ||||||
|  |  | ||||||
| networks: |  | ||||||
|   yudao-network: |  | ||||||
|     driver: bridge |  | ||||||
|  |  | ||||||
| volumes: | volumes: | ||||||
|   mysql: |   mysql: | ||||||
|     driver: local |     driver: local | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -30,7 +30,7 @@ | |||||||
|     <url>https://github.com/YunaiV/ruoyi-vue-pro</url> |     <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | ||||||
|  |  | ||||||
|     <properties> |     <properties> | ||||||
|         <revision>1.7.3-snapshot</revision> |         <revision>1.8.0-snapshot</revision> | ||||||
|         <!-- Maven 相关 --> |         <!-- Maven 相关 --> | ||||||
|         <java.version>1.8</java.version> |         <java.version>1.8</java.version> | ||||||
|         <maven.compiler.source>${java.version}</maven.compiler.source> |         <maven.compiler.source>${java.version}</maven.compiler.source> | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ | |||||||
|     <url>https://github.com/YunaiV/ruoyi-vue-pro</url> |     <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | ||||||
|  |  | ||||||
|     <properties> |     <properties> | ||||||
|         <revision>1.7.3-snapshot</revision> |         <revision>1.8.0-snapshot</revision> | ||||||
|         <!-- 统一依赖管理 --> |         <!-- 统一依赖管理 --> | ||||||
|         <spring.boot.version>2.7.13</spring.boot.version> |         <spring.boot.version>2.7.13</spring.boot.version> | ||||||
|         <!-- Web 相关 --> |         <!-- Web 相关 --> | ||||||
| @@ -71,7 +71,7 @@ | |||||||
|         <justauth.version>1.0.1</justauth.version> |         <justauth.version>1.0.1</justauth.version> | ||||||
|         <jimureport.version>1.5.8</jimureport.version> |         <jimureport.version>1.5.8</jimureport.version> | ||||||
|         <xercesImpl.version>2.12.2</xercesImpl.version> |         <xercesImpl.version>2.12.2</xercesImpl.version> | ||||||
|         <wx-java-mp.version>4.5.0</wx-java-mp.version> |         <weixin-java.version>4.5.0</weixin-java.version> | ||||||
|     </properties> |     </properties> | ||||||
|  |  | ||||||
|     <dependencyManagement> |     <dependencyManagement> | ||||||
| @@ -216,10 +216,9 @@ | |||||||
|                 <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 --> |                 <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 --> | ||||||
|                 <version>${dynamic-datasource.version}</version> |                 <version>${dynamic-datasource.version}</version> | ||||||
|             </dependency> |             </dependency> | ||||||
|  |  | ||||||
|             <dependency> |             <dependency> | ||||||
|                 <groupId>com.github.yulichang</groupId> |                 <groupId>com.github.yulichang</groupId> | ||||||
|                 <artifactId>mybatis-plus-join-boot-starter</artifactId> |                 <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 --> | ||||||
|                 <version>${mybatis-plus-join-boot-starter.version}</version> |                 <version>${mybatis-plus-join-boot-starter.version}</version> | ||||||
|             </dependency> |             </dependency> | ||||||
|  |  | ||||||
| @@ -599,10 +598,25 @@ | |||||||
|                 <version>${justauth.version}</version> |                 <version>${justauth.version}</version> | ||||||
|             </dependency> |             </dependency> | ||||||
|  |  | ||||||
|  |             <dependency> | ||||||
|  |                 <groupId>com.github.binarywang</groupId> | ||||||
|  |                 <artifactId>weixin-java-pay</artifactId> | ||||||
|  |                 <version>${weixin-java.version}</version> | ||||||
|  |             </dependency> | ||||||
|  |             <dependency> | ||||||
|  |                 <groupId>com.github.binarywang</groupId> | ||||||
|  |                 <artifactId>weixin-java-mp</artifactId> | ||||||
|  |                 <version>${weixin-java.version}</version> | ||||||
|  |             </dependency> | ||||||
|             <dependency> |             <dependency> | ||||||
|                 <groupId>com.github.binarywang</groupId> |                 <groupId>com.github.binarywang</groupId> | ||||||
|                 <artifactId>wx-java-mp-spring-boot-starter</artifactId> |                 <artifactId>wx-java-mp-spring-boot-starter</artifactId> | ||||||
|                 <version>${wx-java-mp.version}</version> |                 <version>${weixin-java.version}</version> | ||||||
|  |             </dependency> | ||||||
|  |             <dependency> | ||||||
|  |                 <groupId>com.github.binarywang</groupId> | ||||||
|  |                 <artifactId>wx-java-miniapp-spring-boot-starter</artifactId> | ||||||
|  |                 <version>${weixin-java.version}</version> | ||||||
|             </dependency> |             </dependency> | ||||||
|  |  | ||||||
|             <!-- 积木报表--> |             <!-- 积木报表--> | ||||||
|   | |||||||
| @@ -164,7 +164,7 @@ public class CollectionUtils { | |||||||
|         return from.stream().filter(predicate).findFirst().orElse(null); |         return from.stream().filter(predicate).findFirst().orElse(null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static <T, V extends Comparable<? super V>> V getMaxValue(List<T> from, Function<T, V> valueFunc) { |     public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) { | ||||||
|         if (CollUtil.isEmpty(from)) { |         if (CollUtil.isEmpty(from)) { | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -4,11 +4,11 @@ import cn.hutool.core.collection.CollUtil; | |||||||
| import cn.hutool.core.util.ReflectUtil; | import cn.hutool.core.util.ReflectUtil; | ||||||
| import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||||||
| import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | ||||||
| import cn.iocoder.yudao.module.system.api.permission.PermissionApi; |  | ||||||
| import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO; |  | ||||||
| import cn.iocoder.yudao.framework.security.core.LoginUser; | import cn.iocoder.yudao.framework.security.core.LoginUser; | ||||||
| import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | ||||||
| import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||||||
|  | import cn.iocoder.yudao.module.system.api.permission.PermissionApi; | ||||||
|  | import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO; | ||||||
| import net.sf.jsqlparser.expression.Alias; | import net.sf.jsqlparser.expression.Alias; | ||||||
| import net.sf.jsqlparser.expression.Expression; | import net.sf.jsqlparser.expression.Expression; | ||||||
| import org.junit.jupiter.api.BeforeEach; | import org.junit.jupiter.api.BeforeEach; | ||||||
| @@ -23,6 +23,7 @@ import static cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataP | |||||||
| import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | ||||||
| import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; | ||||||
| import static org.junit.jupiter.api.Assertions.*; | import static org.junit.jupiter.api.Assertions.*; | ||||||
|  | import static org.mockito.ArgumentMatchers.eq; | ||||||
| import static org.mockito.ArgumentMatchers.same; | import static org.mockito.ArgumentMatchers.same; | ||||||
| import static org.mockito.Mockito.mockStatic; | import static org.mockito.Mockito.mockStatic; | ||||||
| import static org.mockito.Mockito.when; | import static org.mockito.Mockito.when; | ||||||
| @@ -73,6 +74,8 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { | |||||||
|             LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L) |             LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L) | ||||||
|                     .setUserType(UserTypeEnum.ADMIN.getValue())); |                     .setUserType(UserTypeEnum.ADMIN.getValue())); | ||||||
|             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); |             securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); | ||||||
|  |             // mock 方法(permissionApi 返回 null) | ||||||
|  |             when(permissionApi.getDeptDataPermission(eq(loginUser.getId()))).thenReturn(null); | ||||||
|  |  | ||||||
|             // 调用 |             // 调用 | ||||||
|             NullPointerException exception = assertThrows(NullPointerException.class, |             NullPointerException exception = assertThrows(NullPointerException.class, | ||||||
|   | |||||||
| @@ -63,9 +63,7 @@ | |||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>com.github.binarywang</groupId> |             <groupId>com.github.binarywang</groupId> | ||||||
|             <artifactId>weixin-java-pay</artifactId> |             <artifactId>weixin-java-pay</artifactId> | ||||||
|             <version>4.5.0</version> |  | ||||||
|         </dependency> |         </dependency> | ||||||
|         <!-- TODO 芋艿:清理 --> |  | ||||||
|  |  | ||||||
|         <!-- Test 测试相关 --> |         <!-- Test 测试相关 --> | ||||||
|         <dependency> |         <dependency> | ||||||
|   | |||||||
| @@ -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); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -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); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -34,16 +34,12 @@ | |||||||
|         <!-- 三方云服务相关 --> |         <!-- 三方云服务相关 --> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>com.github.binarywang</groupId> |             <groupId>com.github.binarywang</groupId> | ||||||
| <!--            <artifactId>weixin-java-mp</artifactId>--> |  | ||||||
|             <artifactId>wx-java-mp-spring-boot-starter</artifactId> |             <artifactId>wx-java-mp-spring-boot-starter</artifactId> | ||||||
|             <version>4.5.0</version> |  | ||||||
|         </dependency> |         </dependency> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>com.github.binarywang</groupId> |             <groupId>com.github.binarywang</groupId> | ||||||
|             <artifactId>wx-java-miniapp-spring-boot-starter</artifactId> |             <artifactId>wx-java-miniapp-spring-boot-starter</artifactId> | ||||||
|             <version>4.5.0</version> |  | ||||||
|         </dependency> |         </dependency> | ||||||
|         <!-- TODO 芋艿:清理 --> |  | ||||||
|     </dependencies> |     </dependencies> | ||||||
|  |  | ||||||
| </project> | </project> | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| package cn.iocoder.yudao.framework.captcha.config; | 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.service.RedisCaptchaServiceImpl; | ||||||
| import com.xingyuv.captcha.properties.AjCaptchaProperties; | import com.xingyuv.captcha.properties.AjCaptchaProperties; | ||||||
| import com.xingyuv.captcha.service.CaptchaCacheService; | import com.xingyuv.captcha.service.CaptchaCacheService; | ||||||
| @@ -15,12 +13,6 @@ import javax.annotation.Resource; | |||||||
| @AutoConfiguration | @AutoConfiguration | ||||||
| public class YudaoCaptchaConfiguration { | public class YudaoCaptchaConfiguration { | ||||||
|  |  | ||||||
|     static { |  | ||||||
|         // 手动加载 Lock4jRedisKeyConstants 类,因为它不会被使用到 |  | ||||||
|         // 如果不加载,会导致 Redis 监控,看到它的 Redis Key 枚举 |  | ||||||
|         ClassUtil.loadClass(CaptchaRedisKeyConstants.class.getName()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Resource |     @Resource | ||||||
|     private StringRedisTemplate stringRedisTemplate; |     private StringRedisTemplate stringRedisTemplate; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,12 +1,5 @@ | |||||||
| package cn.iocoder.yudao.framework.captcha.core.enums; | 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 枚举类 |  * 验证码 Redis Key 枚举类 | ||||||
|  * |  * | ||||||
| @@ -14,12 +7,22 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S | |||||||
|  */ |  */ | ||||||
| public interface CaptchaRedisKeyConstants { | public interface CaptchaRedisKeyConstants { | ||||||
|  |  | ||||||
|     RedisKeyDefine AJ_CAPTCHA_REQ_LIMIT = new RedisKeyDefine("验证码的请求限流", |     /** | ||||||
|             "AJ.CAPTCHA.REQ.LIMIT-%s-%s", |      * 验证码的请求限流 | ||||||
|             STRING, Integer.class, Duration.ofSeconds(60)); // 例如说:验证失败 5 次,get 接口锁定 |      * | ||||||
|  |      * KEY 格式:AJ.CAPTCHA.REQ.LIMIT-%s-%s | ||||||
|  |      * VALUE 数据类型:String // 例如说:验证失败 5 次,get 接口锁定 | ||||||
|  |      * 过期时间:60 秒 | ||||||
|  |      */ | ||||||
|  |     String AJ_CAPTCHA_REQ_LIMIT = "AJ.CAPTCHA.REQ.LIMIT-%s-%s"; | ||||||
|  |  | ||||||
|     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} |      * | ||||||
|  |      * KEY 格式:RUNNING:CAPTCHA:%s // AbstractCaptchaService.REDIS_CAPTCHA_KEY | ||||||
|  |      * VALUE 数据类型:String // PointVO.class {"secretKey":"PP1w2Frr2KEejD2m","x":162,"y":5} | ||||||
|  |      * 过期时间:120 秒 | ||||||
|  |      */ | ||||||
|  |     String AJ_CAPTCHA_RUNNING = "RUNNING:CAPTCHA:%s"; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -64,9 +64,8 @@ | |||||||
|  |  | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>com.github.yulichang</groupId> |             <groupId>com.github.yulichang</groupId> | ||||||
|             <artifactId>mybatis-plus-join-boot-starter</artifactId> |             <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 --> | ||||||
|         </dependency> |         </dependency> | ||||||
|  |  | ||||||
|     </dependencies> |     </dependencies> | ||||||
|  |  | ||||||
| </project> | </project> | ||||||
|   | |||||||
| @@ -6,10 +6,10 @@ import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; | |||||||
| import com.baomidou.mybatisplus.core.conditions.Wrapper; | import com.baomidou.mybatisplus.core.conditions.Wrapper; | ||||||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||||
|  | import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | import com.baomidou.mybatisplus.core.metadata.IPage; | ||||||
| import com.baomidou.mybatisplus.core.toolkit.support.SFunction; | import com.baomidou.mybatisplus.core.toolkit.support.SFunction; | ||||||
| import com.baomidou.mybatisplus.extension.toolkit.Db; | import com.baomidou.mybatisplus.extension.toolkit.Db; | ||||||
| import com.github.yulichang.base.MPJBaseMapper; |  | ||||||
| import org.apache.ibatis.annotations.Param; | import org.apache.ibatis.annotations.Param; | ||||||
|  |  | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| @@ -17,10 +17,8 @@ import java.util.List; | |||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力 |  * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力 | ||||||
|  * <p> |  | ||||||
|  * 为什么继承 MPJBaseMapper 接口?支持 MyBatis Plus 多表 Join 的能力。 |  | ||||||
|  */ |  */ | ||||||
| public interface BaseMapperX<T> extends MPJBaseMapper<T> { | public interface BaseMapperX<T> extends BaseMapper<T> { | ||||||
|  |  | ||||||
|     default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) { |     default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) { | ||||||
|         // MyBatis Plus 查询 |         // MyBatis Plus 查询 | ||||||
| @@ -46,18 +44,6 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { | |||||||
|         return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)); |         return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2, |  | ||||||
|                         SFunction<T, ?> field3, Object value3) { |  | ||||||
|         return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2) |  | ||||||
|                 .eq(field3, value3)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2, |  | ||||||
|                         SFunction<T, ?> field3, Object value3, SFunction<T, ?> field4, Object value4) { |  | ||||||
|         return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2) |  | ||||||
|                 .eq(field3, value3).eq(field4, value4)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     default Long selectCount() { |     default Long selectCount() { | ||||||
|         return selectCount(new QueryWrapper<T>()); |         return selectCount(new QueryWrapper<T>()); | ||||||
|     } |     } | ||||||
| @@ -117,11 +103,6 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { | |||||||
|         update(update, new QueryWrapper<>()); |         update(update, new QueryWrapper<>()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 根据ID 批量更新,适合大量数据更新 |  | ||||||
|      * |  | ||||||
|      * @param entities 实体们 |  | ||||||
|      */ |  | ||||||
|     default void updateBatch(Collection<T> entities) { |     default void updateBatch(Collection<T> entities) { | ||||||
|         Db.updateBatchById(entities); |         Db.updateBatchById(entities); | ||||||
|     } |     } | ||||||
| @@ -130,13 +111,8 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { | |||||||
|         Db.updateBatchById(entities, size); |         Db.updateBatchById(entities, size); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     default void saveOrUpdateBatch(Collection<T> collection) { | ||||||
|      * 批量修改插入, 会根据实体的主键是否为空,更新还是修改。默认为 1000 |         Db.saveOrUpdateBatch(collection); | ||||||
|      * |  | ||||||
|      * @param entities 实体们 |  | ||||||
|      */ |  | ||||||
|     default void saveOrUpdateBatch(Collection<T> entities){ |  | ||||||
|         Db.saveOrUpdateBatch(entities); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,13 +1,10 @@ | |||||||
| package cn.iocoder.yudao.framework.idempotent.core.redis; | package cn.iocoder.yudao.framework.idempotent.core.redis; | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; |  | ||||||
| import lombok.AllArgsConstructor; | import lombok.AllArgsConstructor; | ||||||
| import org.springframework.data.redis.core.StringRedisTemplate; | import org.springframework.data.redis.core.StringRedisTemplate; | ||||||
|  |  | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 幂等 Redis DAO |  * 幂等 Redis DAO | ||||||
|  * |  * | ||||||
| @@ -16,9 +13,14 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S | |||||||
| @AllArgsConstructor | @AllArgsConstructor | ||||||
| public class IdempotentRedisDAO { | public class IdempotentRedisDAO { | ||||||
|  |  | ||||||
|     private static final RedisKeyDefine IDEMPOTENT = new RedisKeyDefine("幂等操作", |     /** | ||||||
|             "idempotent:%s", // 参数为 uuid |      * 幂等操作 | ||||||
|             STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); |      * | ||||||
|  |      * KEY 格式:idempotent:%s // 参数为 uuid | ||||||
|  |      * VALUE 格式:String | ||||||
|  |      * 过期时间:不固定 | ||||||
|  |      */ | ||||||
|  |     private static final String IDEMPOTENT = "idempotent:%s"; | ||||||
|  |  | ||||||
|     private final StringRedisTemplate redisTemplate; |     private final StringRedisTemplate redisTemplate; | ||||||
|  |  | ||||||
| @@ -28,7 +30,7 @@ public class IdempotentRedisDAO { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static String formatKey(String key) { |     private static String formatKey(String key) { | ||||||
|         return String.format(IDEMPOTENT.getKeyTemplate(), key); |         return String.format(IDEMPOTENT, key); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,21 +1,13 @@ | |||||||
| package cn.iocoder.yudao.framework.lock4j.config; | 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.DefaultLockFailureStrategy; | ||||||
| import cn.iocoder.yudao.framework.lock4j.core.Lock4jRedisKeyConstants; | import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration; | ||||||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||||
| import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||||
|  |  | ||||||
| @AutoConfiguration(before = LockAutoConfiguration.class) | @AutoConfiguration(before = LockAutoConfiguration.class) | ||||||
| public class YudaoLock4jConfiguration { | public class YudaoLock4jConfiguration { | ||||||
|  |  | ||||||
|     static { |  | ||||||
|         // 手动加载 Lock4jRedisKeyConstants 类,因为它不会被使用到 |  | ||||||
|         // 如果不加载,会导致 Redis 监控,看到它的 Redis Key 枚举 |  | ||||||
|         ClassUtil.loadClass(Lock4jRedisKeyConstants.class.getName()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Bean |     @Bean | ||||||
|     public DefaultLockFailureStrategy lockFailureStrategy() { |     public DefaultLockFailureStrategy lockFailureStrategy() { | ||||||
|         return new DefaultLockFailureStrategy(); |         return new DefaultLockFailureStrategy(); | ||||||
|   | |||||||
| @@ -1,10 +1,5 @@ | |||||||
| package cn.iocoder.yudao.framework.lock4j.core; | 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 枚举类 |  * Lock4j Redis Key 枚举类 | ||||||
|  * |  * | ||||||
| @@ -12,8 +7,13 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.H | |||||||
|  */ |  */ | ||||||
| public interface Lock4jRedisKeyConstants { | public interface Lock4jRedisKeyConstants { | ||||||
|  |  | ||||||
|     RedisKeyDefine LOCK4J = new RedisKeyDefine("分布式锁", |     /** | ||||||
|             "lock4j:%s", // 参数来自 DefaultLockKeyBuilder 类 |      * 分布式锁 | ||||||
|             HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构 |      * | ||||||
|  |      * KEY 格式:lock4j:%s // 参数来自 DefaultLockKeyBuilder 类 | ||||||
|  |      * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构 | ||||||
|  |      * 过期时间:不固定 | ||||||
|  |      */ | ||||||
|  |     String LOCK4J = "lock4j:%s"; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -37,6 +37,11 @@ | |||||||
|             <artifactId>netty-all</artifactId> |             <artifactId>netty-all</artifactId> | ||||||
|         </dependency> |         </dependency> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.fasterxml.jackson.datatype</groupId> | ||||||
|  |             <artifactId>jackson-datatype-jsr310</artifactId> | ||||||
|  |         </dependency> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|  |  | ||||||
| </project> | </project> | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| package cn.iocoder.yudao.framework.redis.config; | 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.AutoConfiguration; | ||||||
| import org.springframework.boot.autoconfigure.cache.CacheProperties; | import org.springframework.boot.autoconfigure.cache.CacheProperties; | ||||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||||
| @@ -7,8 +9,15 @@ import org.springframework.cache.annotation.EnableCaching; | |||||||
| import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||||
| import org.springframework.context.annotation.Primary; | import org.springframework.context.annotation.Primary; | ||||||
| import org.springframework.data.redis.cache.RedisCacheConfiguration; | 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.RedisSerializationContext; | ||||||
| import org.springframework.data.redis.serializer.RedisSerializer; |  | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | import static cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration.buildRedisSerializer; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Cache 配置类,基于 Redis 实现 |  * Cache 配置类,基于 Redis 实现 | ||||||
| @@ -20,15 +29,19 @@ public class YudaoCacheAutoConfiguration { | |||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * RedisCacheConfiguration Bean |      * RedisCacheConfiguration Bean | ||||||
|      * |      * <p> | ||||||
|      * 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法 |      * 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法 | ||||||
|      */ |      */ | ||||||
|     @Bean |     @Bean | ||||||
|     @Primary |     @Primary | ||||||
|     public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) { |     public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) { | ||||||
|         // 设置使用 JSON 序列化方式 |  | ||||||
|         RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); |         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 的属性 | ||||||
|         CacheProperties.Redis redisProperties = cacheProperties.getRedis(); |         CacheProperties.Redis redisProperties = cacheProperties.getRedis(); | ||||||
| @@ -47,4 +60,14 @@ public class YudaoCacheAutoConfiguration { | |||||||
|         return config; |         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); | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,8 @@ | |||||||
| package cn.iocoder.yudao.framework.redis.config; | 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.boot.autoconfigure.AutoConfiguration; | ||||||
| import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||||
| import org.springframework.data.redis.connection.RedisConnectionFactory; | import org.springframework.data.redis.connection.RedisConnectionFactory; | ||||||
| @@ -25,9 +28,17 @@ public class YudaoRedisAutoConfiguration { | |||||||
|         template.setKeySerializer(RedisSerializer.string()); |         template.setKeySerializer(RedisSerializer.string()); | ||||||
|         template.setHashKeySerializer(RedisSerializer.string()); |         template.setHashKeySerializer(RedisSerializer.string()); | ||||||
|         // 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。 |         // 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。 | ||||||
|         template.setValueSerializer(RedisSerializer.json()); |         template.setValueSerializer(buildRedisSerializer()); | ||||||
|         template.setHashValueSerializer(RedisSerializer.json()); |         template.setHashValueSerializer(buildRedisSerializer()); | ||||||
|         return template; |         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; | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -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(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -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); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -60,7 +60,7 @@ public class YudaoWebSecurityConfigurerAdapter { | |||||||
|     /** |     /** | ||||||
|      * 自定义的权限映射 Bean 们 |      * 自定义的权限映射 Bean 们 | ||||||
|      * |      * | ||||||
|      * @see #configure(HttpSecurity) |      * @see #filterChain(HttpSecurity) | ||||||
|      */ |      */ | ||||||
|     @Resource |     @Resource | ||||||
|     private List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers; |     private List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers; | ||||||
| @@ -79,7 +79,7 @@ public class YudaoWebSecurityConfigurerAdapter { | |||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 配置 URL 的安全配置 |      * 配置 URL 的安全配置 | ||||||
|      * <p> |      * | ||||||
|      * anyRequest          |   匹配所有请求路径 |      * anyRequest          |   匹配所有请求路径 | ||||||
|      * access              |   SpringEl表达式结果为true时可以访问 |      * access              |   SpringEl表达式结果为true时可以访问 | ||||||
|      * anonymous           |   匿名可以访问 |      * anonymous           |   匿名可以访问 | ||||||
| @@ -141,7 +141,6 @@ public class YudaoWebSecurityConfigurerAdapter { | |||||||
|  |  | ||||||
|         // 添加 Token Filter |         // 添加 Token Filter | ||||||
|         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); |         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); | ||||||
|  |  | ||||||
|         return httpSecurity.build(); |         return httpSecurity.build(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ public class BaseDbAndRedisUnitTest { | |||||||
|  |  | ||||||
|             // Redis 配置类 |             // Redis 配置类 | ||||||
|             RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer |             RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer | ||||||
|  | //            RedisAutoConfiguration.class, // Spring Redis 自动配置类 | ||||||
|             YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类 |             YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类 | ||||||
|             RedissonAutoConfiguration.class, // Redisson 自动高配置类 |             RedissonAutoConfiguration.class, // Redisson 自动高配置类 | ||||||
|     }) |     }) | ||||||
|   | |||||||
| @@ -52,7 +52,8 @@ public class YudaoSwaggerAutoConfiguration { | |||||||
|                 // 接口信息 |                 // 接口信息 | ||||||
|                 .info(buildInfo(properties)) |                 .info(buildInfo(properties)) | ||||||
|                 // 接口安全配置 |                 // 接口安全配置 | ||||||
|                 .components(new Components().securitySchemes(securitySchemas)); |                 .components(new Components().securitySchemes(securitySchemas)) | ||||||
|  |                 .addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION)); | ||||||
|         securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key))); |         securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key))); | ||||||
|         return openAPI; |         return openAPI; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -73,11 +73,13 @@ public interface BpmTaskConvert { | |||||||
|  |  | ||||||
|     BpmTaskDonePageItemRespVO convert2(HistoricTaskInstance bean); |     BpmTaskDonePageItemRespVO convert2(HistoricTaskInstance bean); | ||||||
|  |  | ||||||
|     @Mappings({@Mapping(source = "processInstance.id", target = "id"), |     @Mappings({ | ||||||
|  |             @Mapping(source = "processInstance.id", target = "id"), | ||||||
|             @Mapping(source = "processInstance.name", target = "name"), |             @Mapping(source = "processInstance.name", target = "name"), | ||||||
|             @Mapping(source = "processInstance.startUserId", target = "startUserId"), |             @Mapping(source = "processInstance.startUserId", target = "startUserId"), | ||||||
|             @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"), |             @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"), | ||||||
|         @Mapping(source = "startUser.nickname", target = "startUserNickname")}) |             @Mapping(source = "startUser.nickname", target = "startUserNickname") | ||||||
|  |     }) | ||||||
|     BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser); |     BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser); | ||||||
|  |  | ||||||
|     default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks, |     default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks, | ||||||
|   | |||||||
| @@ -15,8 +15,8 @@ import cn.iocoder.yudao.module.bpm.convert.definition.BpmTaskAssignRuleConvert; | |||||||
| import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO; | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO; | ||||||
| import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; | ||||||
| import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper; | import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper; | ||||||
| import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; |  | ||||||
| import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants; | import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants; | ||||||
|  | import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript; | import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript; | ||||||
| import cn.iocoder.yudao.module.system.api.dept.DeptApi; | import cn.iocoder.yudao.module.system.api.dept.DeptApi; | ||||||
| import cn.iocoder.yudao.module.system.api.dept.PostApi; | import cn.iocoder.yudao.module.system.api.dept.PostApi; | ||||||
| @@ -298,7 +298,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private Set<Long> calculateTaskCandidateUsersByPost(BpmTaskAssignRuleDO rule) { |     private Set<Long> calculateTaskCandidateUsersByPost(BpmTaskAssignRuleDO rule) { | ||||||
|         List<AdminUserRespDTO> users = adminUserApi.getUsersByPostIds(rule.getOptions()); |         List<AdminUserRespDTO> users = adminUserApi.getUserListByPostIds(rule.getOptions()); | ||||||
|         return convertSet(users, AdminUserRespDTO::getId); |         return convertSet(users, AdminUserRespDTO::getId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,6 +40,8 @@ public class BpmTaskAssignLeaderX2ScriptTest extends BaseMockitoUnitTest { | |||||||
|         // mock 方法(startUser) |         // mock 方法(startUser) | ||||||
|         AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptId(10L)); |         AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptId(10L)); | ||||||
|         when(adminUserApi.getUser(eq(1L))).thenReturn(startUser); |         when(adminUserApi.getUser(eq(1L))).thenReturn(startUser); | ||||||
|  |         // mock 方法(getStartUserDept)没有部门 | ||||||
|  |         when(deptApi.getDept(eq(10L))).thenReturn(null); | ||||||
|  |  | ||||||
|         // 调用 |         // 调用 | ||||||
|         Set<Long> result = script.calculateTaskCandidateUsers(execution); |         Set<Long> result = script.calculateTaskCandidateUsers(execution); | ||||||
| @@ -56,7 +58,9 @@ public class BpmTaskAssignLeaderX2ScriptTest extends BaseMockitoUnitTest { | |||||||
|         when(adminUserApi.getUser(eq(1L))).thenReturn(startUser); |         when(adminUserApi.getUser(eq(1L))).thenReturn(startUser); | ||||||
|         DeptRespDTO startUserDept = randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(100L) |         DeptRespDTO startUserDept = randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(100L) | ||||||
|                 .setLeaderUserId(20L)); |                 .setLeaderUserId(20L)); | ||||||
|  |         // mock 方法(getDept) | ||||||
|         when(deptApi.getDept(eq(10L))).thenReturn(startUserDept); |         when(deptApi.getDept(eq(10L))).thenReturn(startUserDept); | ||||||
|  |         when(deptApi.getDept(eq(100L))).thenReturn(null); | ||||||
|  |  | ||||||
|         // 调用 |         // 调用 | ||||||
|         Set<Long> result = script.calculateTaskCandidateUsers(execution); |         Set<Long> result = script.calculateTaskCandidateUsers(execution); | ||||||
|   | |||||||
| @@ -120,7 +120,7 @@ public class BpmTaskAssignRuleServiceImplTest extends BaseDbUnitTest { | |||||||
|         // mock 方法 |         // mock 方法 | ||||||
|         List<AdminUserRespDTO> users = CollectionUtils.convertList(asSet(11L, 22L), |         List<AdminUserRespDTO> users = CollectionUtils.convertList(asSet(11L, 22L), | ||||||
|                 id -> new AdminUserRespDTO().setId(id)); |                 id -> new AdminUserRespDTO().setId(id)); | ||||||
|         when(adminUserApi.getUsersByPostIds(eq(rule.getOptions()))).thenReturn(users); |         when(adminUserApi.getUserListByPostIds(eq(rule.getOptions()))).thenReturn(users); | ||||||
|         mockGetUserMap(asSet(11L, 22L)); |         mockGetUserMap(asSet(11L, 22L)); | ||||||
|  |  | ||||||
|         // 调用 |         // 调用 | ||||||
|   | |||||||
| @@ -23,10 +23,10 @@ import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgn | |||||||
| import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS; | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| * {@link BpmUserGroupServiceImpl} 的单元测试类 |  * {@link BpmUserGroupServiceImpl} 的单元测试类 | ||||||
| * |  * | ||||||
| * @author 芋道源码 |  * @author 芋道源码 | ||||||
| */ |  */ | ||||||
| @Import(BpmUserGroupServiceImpl.class) | @Import(BpmUserGroupServiceImpl.class) | ||||||
| public class BpmUserGroupServiceTest extends BaseDbUnitTest { | public class BpmUserGroupServiceTest extends BaseDbUnitTest { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,8 +2,3 @@ | |||||||
| GET {{baseUrl}}/infra/redis/get-monitor-info | GET {{baseUrl}}/infra/redis/get-monitor-info | ||||||
| Authorization: Bearer {{token}} | Authorization: Bearer {{token}} | ||||||
| tenant-id: {{adminTenentId}} | tenant-id: {{adminTenentId}} | ||||||
|  |  | ||||||
| ### 请求 /infra/redis/get-key-list 接口 => 成功 |  | ||||||
| GET {{baseUrl}}/infra/redis/get-key-list |  | ||||||
| Authorization: Bearer {{token}} |  | ||||||
| tenant-id: {{adminTenentId}} |  | ||||||
|   | |||||||
| @@ -1,27 +1,20 @@ | |||||||
| package cn.iocoder.yudao.module.infra.controller.admin.redis; | 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.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.controller.admin.redis.vo.RedisMonitorRespVO; | ||||||
| import cn.iocoder.yudao.module.infra.convert.redis.RedisConvert; | 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.Operation; | ||||||
|  | import io.swagger.v3.oas.annotations.tags.Tag; | ||||||
| import org.springframework.data.redis.connection.RedisServerCommands; | import org.springframework.data.redis.connection.RedisServerCommands; | ||||||
| import org.springframework.data.redis.core.Cursor; |  | ||||||
| import org.springframework.data.redis.core.RedisCallback; | import org.springframework.data.redis.core.RedisCallback; | ||||||
| import org.springframework.data.redis.core.ScanOptions; |  | ||||||
| import org.springframework.data.redis.core.StringRedisTemplate; | import org.springframework.data.redis.core.StringRedisTemplate; | ||||||
| import org.springframework.security.access.prepost.PreAuthorize; | import org.springframework.security.access.prepost.PreAuthorize; | ||||||
| import org.springframework.web.bind.annotation.*; | import org.springframework.web.bind.annotation.GetMapping; | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
|  | import org.springframework.web.bind.annotation.RestController; | ||||||
|  |  | ||||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||||
| import java.util.*; | import java.util.Properties; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||||||
|  |  | ||||||
| @@ -47,66 +40,4 @@ public class RedisController { | |||||||
|         return success(RedisConvert.INSTANCE.build(info, dbSize, commandStats)); |         return success(RedisConvert.INSTANCE.build(info, dbSize, commandStats)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @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)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @GetMapping("/get-key-list") |  | ||||||
|     @Operation(summary = "获得 Redis keys 键名列表") |  | ||||||
|     @Parameter(name = "keyTemplate", description = "Redis Key 定义", example = "true") |  | ||||||
|     @PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')") |  | ||||||
|     public CommonResult<Set<String>> getKeyDefineList(@RequestParam("keyTemplate") String keyTemplate) { |  | ||||||
|         return success(getKeyDefineList0(keyTemplate)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private Set<String> getKeyDefineList0(String keyTemplate) { |  | ||||||
|         // key 格式化 |  | ||||||
|         String key = StrUtil.replace(keyTemplate, "%[s|c|b|d|x|o|f|a|e|g]", parameter -> "*"); |  | ||||||
|         // scan 扫描 key |  | ||||||
|         Set<String> keys = new LinkedHashSet<>(); |  | ||||||
|         stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> { |  | ||||||
|             try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(key).count(100).build())) { |  | ||||||
|                 cursor.forEachRemaining(value -> keys.add(StrUtil.utf8Str(value))); |  | ||||||
|             } catch (Exception e) { |  | ||||||
|                 throw new RuntimeException(e); |  | ||||||
|             } |  | ||||||
|             return keys; |  | ||||||
|         }); |  | ||||||
|         return keys; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @GetMapping("/get-key-value") |  | ||||||
|     @Operation(summary = "获得 Redis key 内容") |  | ||||||
|     @Parameter(name = "key", description = "Redis Key", example = "oauth2_access_token:233") |  | ||||||
|     @PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')") |  | ||||||
|     public CommonResult<RedisKeyValueRespVO> getKeyValue(@RequestParam("key") String key) { |  | ||||||
|         String value = stringRedisTemplate.opsForValue().get(key); |  | ||||||
|         return success(new RedisKeyValueRespVO(key, value)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @DeleteMapping("/delete-key") |  | ||||||
|     @Operation(summary = "删除 Redis Key") |  | ||||||
|     @Parameter(name = "key", description = "Redis Key", example = "oauth2_access_token:233") |  | ||||||
|     @PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')") |  | ||||||
|     public CommonResult<Boolean> deleteKey(@RequestParam("key") String key) { |  | ||||||
|         stringRedisTemplate.delete(key); |  | ||||||
|         return success(Boolean.TRUE); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @DeleteMapping("/delete-keys") |  | ||||||
|     @Operation(summary = "删除 Redis Key 根据模板") |  | ||||||
|     @Parameter(name = "keyTemplate", description = "Redis Key 定义", example = "true") |  | ||||||
|     @PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')") |  | ||||||
|     public CommonResult<Boolean> deleteKeys(@RequestParam("keyTemplate") String keyTemplate) { |  | ||||||
|         Set<String> keys = getKeyDefineList0(keyTemplate); |  | ||||||
|         if (CollUtil.isNotEmpty(keys)) { |  | ||||||
|             stringRedisTemplate.delete(keys); |  | ||||||
|         } |  | ||||||
|         return success(Boolean.TRUE); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 模板", requiredMode = Schema.RequiredMode.REQUIRED, example = "login_user:%s") |  | ||||||
|     private String keyTemplate; |  | ||||||
|  |  | ||||||
|     @Schema(description = "Key 类型的枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "String") |  | ||||||
|     private RedisKeyDefine.KeyTypeEnum keyType; |  | ||||||
|  |  | ||||||
|     @Schema(description = "Value 类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "java.lang.String") |  | ||||||
|     private Class<?> valueType; |  | ||||||
|  |  | ||||||
|     @Schema(description = "超时类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |  | ||||||
|     private RedisKeyDefine.TimeoutTypeEnum timeoutType; |  | ||||||
|  |  | ||||||
|     @Schema(description = "过期时间,单位:毫秒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |  | ||||||
|     private Duration timeout; |  | ||||||
|  |  | ||||||
|     @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "啦啦啦啦~") |  | ||||||
|     private String memo; |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.infra.controller.admin.redis.vo; |  | ||||||
|  |  | ||||||
| import io.swagger.v3.oas.annotations.media.Schema; |  | ||||||
| import lombok.AllArgsConstructor; |  | ||||||
| import lombok.Data; |  | ||||||
|  |  | ||||||
| @Schema(description = "管理后台 - 单个 Redis Key Value Response VO") |  | ||||||
| @Data |  | ||||||
| @AllArgsConstructor |  | ||||||
| public class RedisKeyValueRespVO { |  | ||||||
|  |  | ||||||
|     @Schema(description = "c5f6990767804a928f4bb96ca249febf", requiredMode = Schema.RequiredMode.REQUIRED, example = "String") |  | ||||||
|     private String key; |  | ||||||
|  |  | ||||||
|     @Schema(requiredMode = Schema.RequiredMode.REQUIRED, example = "String") |  | ||||||
|     private String value; |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,14 +1,11 @@ | |||||||
| package cn.iocoder.yudao.module.infra.convert.redis; | package cn.iocoder.yudao.module.infra.convert.redis; | ||||||
|  |  | ||||||
| import cn.hutool.core.util.StrUtil; | 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 cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisMonitorRespVO; | ||||||
| import org.mapstruct.Mapper; | import org.mapstruct.Mapper; | ||||||
| import org.mapstruct.factory.Mappers; | import org.mapstruct.factory.Mappers; | ||||||
|  |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; |  | ||||||
| import java.util.Properties; | import java.util.Properties; | ||||||
|  |  | ||||||
| @Mapper | @Mapper | ||||||
| @@ -29,6 +26,4 @@ public interface RedisConvert { | |||||||
|         return respVO; |         return respVO; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     List<RedisKeyDefineRespVO> convertList(List<RedisKeyDefine> list); |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | |||||||
| import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; | ||||||
| import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; | ||||||
| import org.apache.ibatis.annotations.Mapper; | import org.apache.ibatis.annotations.Mapper; | ||||||
|  | import org.apache.ibatis.annotations.Select; | ||||||
|  |  | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
| @Mapper | @Mapper | ||||||
| public interface FileConfigMapper extends BaseMapperX<FileConfigDO> { | public interface FileConfigMapper extends BaseMapperX<FileConfigDO> { | ||||||
| @@ -18,4 +21,7 @@ public interface FileConfigMapper extends BaseMapperX<FileConfigDO> { | |||||||
|                 .orderByDesc(FileConfigDO::getId)); |                 .orderByDesc(FileConfigDO::getId)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Select("SELECT COUNT(*) FROM infra_file_config WHERE update_time > #{maxUpdateTime}") | ||||||
|  |     Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime); | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.infra.mq.consumer.file; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.infra.service.file.FileConfigService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link FileConfigRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class FileConfigRefreshConsumer extends AbstractChannelMessageListener<FileConfigRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private FileConfigService fileConfigService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(FileConfigRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 FileConfig 刷新消息]"); |  | ||||||
|         fileConfigService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| /** | /** | ||||||
|  * 占位符,避免缩进 |  * 消息队列的消费者 | ||||||
|  */ |  */ | ||||||
| package cn.iocoder.yudao.module.infra.mq.consumer; | package cn.iocoder.yudao.module.infra.mq.consumer; | ||||||
|   | |||||||
| @@ -1,17 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.infra.mq.message.file; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage; |  | ||||||
| import lombok.Data; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 文件配置数据刷新 Message |  | ||||||
|  */ |  | ||||||
| @Data |  | ||||||
| public class FileConfigRefreshMessage extends AbstractChannelMessage { |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getChannel() { |  | ||||||
|         return "infra.file-config.refresh"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| /** | /** | ||||||
|  * 占位符,避免缩进 |  * 消息队列的消息 | ||||||
|  */ |  */ | ||||||
| package cn.iocoder.yudao.module.infra.mq.message; | package cn.iocoder.yudao.module.infra.mq.message; | ||||||
|   | |||||||
| @@ -1,26 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.infra.mq.producer.file; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; |  | ||||||
| import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 文件配置相关消息的 Producer |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| public class FileConfigProducer { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private RedisMQTemplate redisMQTemplate; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 发送 {@link FileConfigRefreshMessage} 消息 |  | ||||||
|      */ |  | ||||||
|     public void sendFileConfigRefreshMessage() { |  | ||||||
|         FileConfigRefreshMessage message = new FileConfigRefreshMessage(); |  | ||||||
|         redisMQTemplate.send(message); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| /** | /** | ||||||
|  * 占位符,避免缩进 |  * 消息队列的生产者 | ||||||
|  */ |  */ | ||||||
| package cn.iocoder.yudao.module.infra.mq.producer; | package cn.iocoder.yudao.module.infra.mq.producer; | ||||||
|   | |||||||
| @@ -16,11 +16,6 @@ import javax.validation.Valid; | |||||||
|  */ |  */ | ||||||
| public interface FileConfigService { | public interface FileConfigService { | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 初始化文件客户端 |  | ||||||
|      */ |  | ||||||
|     void initLocalCache(); |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 创建文件配置 |      * 创建文件配置 | ||||||
|      * |      * | ||||||
|   | |||||||
| @@ -1,8 +1,10 @@ | |||||||
| package cn.iocoder.yudao.module.infra.service.file; | package cn.iocoder.yudao.module.infra.service.file; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.collection.CollUtil; | ||||||
| import cn.hutool.core.io.resource.ResourceUtil; | import cn.hutool.core.io.resource.ResourceUtil; | ||||||
| import cn.hutool.core.util.IdUtil; | import cn.hutool.core.util.IdUtil; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||||
|  | 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.json.JsonUtils; | ||||||
| import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | ||||||
| import cn.iocoder.yudao.framework.file.core.client.FileClient; | import cn.iocoder.yudao.framework.file.core.client.FileClient; | ||||||
| @@ -15,20 +17,20 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigU | |||||||
| import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert; | import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert; | ||||||
| import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; | ||||||
| import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; | import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; | ||||||
| import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer; |  | ||||||
| import lombok.Getter; | import lombok.Getter; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.springframework.scheduling.annotation.Scheduled; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| import org.springframework.transaction.annotation.Transactional; | import org.springframework.transaction.annotation.Transactional; | ||||||
| import org.springframework.transaction.support.TransactionSynchronization; |  | ||||||
| import org.springframework.transaction.support.TransactionSynchronizationManager; |  | ||||||
| import org.springframework.validation.annotation.Validated; | import org.springframework.validation.annotation.Validated; | ||||||
|  |  | ||||||
| import javax.annotation.PostConstruct; | import javax.annotation.PostConstruct; | ||||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||||
| import javax.validation.Validator; | import javax.validation.Validator; | ||||||
|  | import java.time.LocalDateTime; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||||
| import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER; | import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER; | ||||||
| @@ -46,6 +48,12 @@ public class FileConfigServiceImpl implements FileConfigService { | |||||||
|  |  | ||||||
|     @Resource |     @Resource | ||||||
|     private FileClientFactory fileClientFactory; |     private FileClientFactory fileClientFactory; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 文件配置的缓存 | ||||||
|  |      */ | ||||||
|  |     @Getter | ||||||
|  |     private List<FileConfigDO> fileConfigCache; | ||||||
|     /** |     /** | ||||||
|      * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的 |      * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的 | ||||||
|      */ |      */ | ||||||
| @@ -55,13 +63,9 @@ public class FileConfigServiceImpl implements FileConfigService { | |||||||
|     @Resource |     @Resource | ||||||
|     private FileConfigMapper fileConfigMapper; |     private FileConfigMapper fileConfigMapper; | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private FileConfigProducer fileConfigProducer; |  | ||||||
|  |  | ||||||
|     @Resource |     @Resource | ||||||
|     private Validator validator; |     private Validator validator; | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     @PostConstruct |     @PostConstruct | ||||||
|     public void initLocalCache() { |     public void initLocalCache() { | ||||||
|         // 第一步:查询数据 |         // 第一步:查询数据 | ||||||
| @@ -76,6 +80,27 @@ public class FileConfigServiceImpl implements FileConfigService { | |||||||
|                 masterFileClient = fileClientFactory.getFileClient(config.getId()); |                 masterFileClient = fileClientFactory.getFileClient(config.getId()); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |         this.fileConfigCache = configs; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 通过定时任务轮询,刷新缓存 | ||||||
|  |      * | ||||||
|  |      * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新 | ||||||
|  |      */ | ||||||
|  |     @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS) | ||||||
|  |     public void refreshLocalCache() { | ||||||
|  |         // 情况一:如果缓存里没有数据,则直接刷新缓存 | ||||||
|  |         if (CollUtil.isEmpty(fileConfigCache)) { | ||||||
|  |             initLocalCache(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存 | ||||||
|  |         LocalDateTime maxTime = CollectionUtils.getMaxValue(fileConfigCache, FileConfigDO::getUpdateTime); | ||||||
|  |         if (fileConfigMapper.selectCountByUpdateTimeGt(maxTime) > 0) { | ||||||
|  |             initLocalCache(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -85,9 +110,9 @@ public class FileConfigServiceImpl implements FileConfigService { | |||||||
|                 .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig())) |                 .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig())) | ||||||
|                 .setMaster(false); // 默认非 master |                 .setMaster(false); // 默认非 master | ||||||
|         fileConfigMapper.insert(fileConfig); |         fileConfigMapper.insert(fileConfig); | ||||||
|         // 发送刷新配置的消息 |  | ||||||
|         fileConfigProducer.sendFileConfigRefreshMessage(); |         // 刷新缓存 | ||||||
|         // 返回 |         initLocalCache(); | ||||||
|         return fileConfig.getId(); |         return fileConfig.getId(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -99,8 +124,9 @@ public class FileConfigServiceImpl implements FileConfigService { | |||||||
|         FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO) |         FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO) | ||||||
|                 .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig())); |                 .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig())); | ||||||
|         fileConfigMapper.updateById(updateObj); |         fileConfigMapper.updateById(updateObj); | ||||||
|         // 发送刷新配置的消息 |  | ||||||
|         fileConfigProducer.sendFileConfigRefreshMessage(); |         // 刷新缓存 | ||||||
|  |         initLocalCache(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -112,15 +138,9 @@ public class FileConfigServiceImpl implements FileConfigService { | |||||||
|         fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false)); |         fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false)); | ||||||
|         // 更新 |         // 更新 | ||||||
|         fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true)); |         fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true)); | ||||||
|         // 发送刷新配置的消息 |  | ||||||
|         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { |  | ||||||
|  |  | ||||||
|             @Override |         // 刷新缓存 | ||||||
|             public void afterCommit() { |         initLocalCache(); | ||||||
|                 fileConfigProducer.sendFileConfigRefreshMessage(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) { |     private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) { | ||||||
| @@ -143,8 +163,9 @@ public class FileConfigServiceImpl implements FileConfigService { | |||||||
|         } |         } | ||||||
|         // 删除 |         // 删除 | ||||||
|         fileConfigMapper.deleteById(id); |         fileConfigMapper.deleteById(id); | ||||||
|         // 发送刷新配置的消息 |  | ||||||
|         fileConfigProducer.sendFileConfigRefreshMessage(); |         // 刷新缓存 | ||||||
|  |         initLocalCache(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private FileConfigDO validateFileConfigExists(Long id) { |     private FileConfigDO validateFileConfigExists(Long id) { | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigP | |||||||
| import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; | ||||||
| import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; | ||||||
| import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; | import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; | ||||||
| import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer; |  | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| import org.springframework.boot.test.mock.mockito.MockBean; | import org.springframework.boot.test.mock.mockito.MockBean; | ||||||
| @@ -55,8 +54,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { | |||||||
|     @Resource |     @Resource | ||||||
|     private FileConfigMapper fileConfigMapper; |     private FileConfigMapper fileConfigMapper; | ||||||
|  |  | ||||||
|     @MockBean |  | ||||||
|     private FileConfigProducer fileConfigProducer; |  | ||||||
|     @MockBean |     @MockBean | ||||||
|     private Validator validator; |     private Validator validator; | ||||||
|     @MockBean |     @MockBean | ||||||
| @@ -81,6 +78,10 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { | |||||||
|         verify(fileClientFactory).createOrUpdateFileClient(eq(2L), |         verify(fileClientFactory).createOrUpdateFileClient(eq(2L), | ||||||
|                 eq(configDO2.getStorage()), eq(configDO2.getConfig())); |                 eq(configDO2.getStorage()), eq(configDO2.getConfig())); | ||||||
|         assertSame(masterFileClient, fileConfigService.getMasterFileClient()); |         assertSame(masterFileClient, fileConfigService.getMasterFileClient()); | ||||||
|  |         // 断言 fileConfigCache 缓存 | ||||||
|  |         assertEquals(2, fileConfigService.getFileConfigCache().size()); | ||||||
|  |         assertEquals(configDO1, fileConfigService.getFileConfigCache().get(0)); | ||||||
|  |         assertEquals(configDO2, fileConfigService.getFileConfigCache().get(1)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
| @@ -101,8 +102,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { | |||||||
|         assertFalse(fileConfig.getMaster()); |         assertFalse(fileConfig.getMaster()); | ||||||
|         assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath()); |         assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath()); | ||||||
|         assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain()); |         assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain()); | ||||||
|         // verify 调用 |  | ||||||
|         verify(fileConfigProducer).sendFileConfigRefreshMessage(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
| @@ -126,8 +125,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { | |||||||
|         assertPojoEquals(reqVO, fileConfig, "config"); |         assertPojoEquals(reqVO, fileConfig, "config"); | ||||||
|         assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath()); |         assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath()); | ||||||
|         assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain()); |         assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain()); | ||||||
|         // verify 调用 |  | ||||||
|         verify(fileConfigProducer).sendFileConfigRefreshMessage(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
| @@ -152,8 +149,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { | |||||||
|         // 断言数据 |         // 断言数据 | ||||||
|         assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster()); |         assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster()); | ||||||
|         assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster()); |         assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster()); | ||||||
|         // verify 调用 |  | ||||||
|         verify(fileConfigProducer).sendFileConfigRefreshMessage(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
| @@ -174,8 +169,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { | |||||||
|         fileConfigService.deleteFileConfig(id); |         fileConfigService.deleteFileConfig(id); | ||||||
|        // 校验数据不存在了 |        // 校验数据不存在了 | ||||||
|        assertNull(fileConfigMapper.selectById(id)); |        assertNull(fileConfigMapper.selectById(id)); | ||||||
|         // verify 调用 |  | ||||||
|         verify(fileConfigProducer).sendFileConfigRefreshMessage(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|   | |||||||
| @@ -19,10 +19,10 @@ public interface TradeCartMapper extends BaseMapperX<TradeCartDO> { | |||||||
|  |  | ||||||
|     default TradeCartDO selectByUserIdAndSkuId(Long userId, Long skuId, |     default TradeCartDO selectByUserIdAndSkuId(Long userId, Long skuId, | ||||||
|                                                Boolean addStatus, Boolean orderStatus) { |                                                Boolean addStatus, Boolean orderStatus) { | ||||||
|         return selectOne(TradeCartDO::getUserId, userId, |         return selectOne(new LambdaQueryWrapper<TradeCartDO>().eq(TradeCartDO::getUserId, userId) | ||||||
|                 TradeCartDO::getSkuId, skuId, |                 .eq(TradeCartDO::getSkuId, skuId) | ||||||
|                 TradeCartDO::getAddStatus, addStatus, |                 .eq(TradeCartDO::getAddStatus, addStatus) | ||||||
|                 TradeCartDO::getOrderStatus, orderStatus); |                 .eq(TradeCartDO::getOrderStatus, orderStatus)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     default Integer selectSumByUserId(Long userId) { |     default Integer selectSumByUserId(Long userId) { | ||||||
|   | |||||||
| @@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | |||||||
| import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; | import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; | ||||||
| import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; | import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; | ||||||
| import org.apache.ibatis.annotations.Mapper; | import org.apache.ibatis.annotations.Mapper; | ||||||
|  | import org.apache.ibatis.annotations.Select; | ||||||
|  |  | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
| @Mapper | @Mapper | ||||||
| public interface MpAccountMapper extends BaseMapperX<MpAccountDO> { | public interface MpAccountMapper extends BaseMapperX<MpAccountDO> { | ||||||
| @@ -22,4 +25,7 @@ public interface MpAccountMapper extends BaseMapperX<MpAccountDO> { | |||||||
|         return selectOne(MpAccountDO::getAppId, appId); |         return selectOne(MpAccountDO::getAppId, appId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Select("SELECT COUNT(*) FROM mp_account WHERE update_time > #{maxUpdateTime}") | ||||||
|  |     Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime); | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.mp.mq.consumer; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.mp.mq.message.MpAccountRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.mp.service.account.MpAccountService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link MpAccountRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class MpAccountRefreshConsumer extends AbstractChannelMessageListener<MpAccountRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private MpAccountService mpAccountService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(MpAccountRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 Account 刷新消息]"); |  | ||||||
|         mpAccountService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.mp.mq.message; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage; |  | ||||||
| import lombok.Data; |  | ||||||
| import lombok.EqualsAndHashCode; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 公众号账号刷新 Message |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Data |  | ||||||
| @EqualsAndHashCode(callSuper = true) |  | ||||||
| public class MpAccountRefreshMessage extends AbstractChannelMessage { |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getChannel() { |  | ||||||
|         return "mp.account.refresh"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.mp.mq.producer; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; |  | ||||||
| import cn.iocoder.yudao.module.mp.mq.message.MpAccountRefreshMessage; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 公众号账号 Producer |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| public class MpAccountProducer { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private RedisMQTemplate redisMQTemplate; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 发送 {@link MpAccountRefreshMessage} 消息 |  | ||||||
|      */ |  | ||||||
|     public void sendAccountRefreshMessage() { |  | ||||||
|         MpAccountRefreshMessage message = new MpAccountRefreshMessage(); |  | ||||||
|         redisMQTemplate.send(message); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,9 +1,9 @@ | |||||||
| package cn.iocoder.yudao.module.mp.service.account; | package cn.iocoder.yudao.module.mp.service.account; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.collection.CollUtil; | ||||||
| import cn.hutool.core.util.ObjUtil; | import cn.hutool.core.util.ObjUtil; | ||||||
| import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; | import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||||
| import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |  | ||||||
| import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | ||||||
| import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO; | import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO; | ||||||
| import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; | import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; | ||||||
| @@ -13,7 +13,6 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; | |||||||
| import cn.iocoder.yudao.module.mp.dal.mysql.account.MpAccountMapper; | import cn.iocoder.yudao.module.mp.dal.mysql.account.MpAccountMapper; | ||||||
| import cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants; | import cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants; | ||||||
| import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; | import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; | ||||||
| import cn.iocoder.yudao.module.mp.mq.producer.MpAccountProducer; |  | ||||||
| import com.google.common.annotations.VisibleForTesting; | import com.google.common.annotations.VisibleForTesting; | ||||||
| import lombok.Getter; | import lombok.Getter; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
| @@ -21,15 +20,20 @@ import me.chanjar.weixin.common.error.WxErrorException; | |||||||
| import me.chanjar.weixin.mp.api.WxMpService; | import me.chanjar.weixin.mp.api.WxMpService; | ||||||
| import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; | import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; | ||||||
| import org.springframework.context.annotation.Lazy; | import org.springframework.context.annotation.Lazy; | ||||||
|  | import org.springframework.scheduling.annotation.Scheduled; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| import org.springframework.validation.annotation.Validated; | import org.springframework.validation.annotation.Validated; | ||||||
|  |  | ||||||
| import javax.annotation.PostConstruct; | import javax.annotation.PostConstruct; | ||||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||||
|  | import java.time.LocalDateTime; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||||
|  | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||||||
|  | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue; | ||||||
| import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_USERNAME_EXISTS; | import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_USERNAME_EXISTS; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -58,9 +62,6 @@ public class MpAccountServiceImpl implements MpAccountService { | |||||||
|     @Lazy // 延迟加载,解决循环依赖的问题 |     @Lazy // 延迟加载,解决循环依赖的问题 | ||||||
|     private MpServiceFactory mpServiceFactory; |     private MpServiceFactory mpServiceFactory; | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private MpAccountProducer mpAccountProducer; |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     @PostConstruct |     @PostConstruct | ||||||
|     public void initLocalCache() { |     public void initLocalCache() { | ||||||
| @@ -72,7 +73,30 @@ public class MpAccountServiceImpl implements MpAccountService { | |||||||
|  |  | ||||||
|             // 第二步:构建缓存。创建或更新支付 Client |             // 第二步:构建缓存。创建或更新支付 Client | ||||||
|             mpServiceFactory.init(accounts); |             mpServiceFactory.init(accounts); | ||||||
|             accountCache = CollectionUtils.convertMap(accounts, MpAccountDO::getAppId); |             accountCache = convertMap(accounts, MpAccountDO::getAppId); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 通过定时任务轮询,刷新缓存 | ||||||
|  |      * | ||||||
|  |      * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新 | ||||||
|  |      */ | ||||||
|  |     @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS) | ||||||
|  |     public void refreshLocalCache() { | ||||||
|  |         // 注意:忽略自动多租户,因为要全局初始化缓存 | ||||||
|  |         TenantUtils.executeIgnore(() -> { | ||||||
|  |             // 情况一:如果缓存里没有数据,则直接刷新缓存 | ||||||
|  |             if (CollUtil.isEmpty(accountCache)) { | ||||||
|  |                 initLocalCache(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存 | ||||||
|  |             LocalDateTime maxTime = getMaxValue(accountCache.values(), MpAccountDO::getUpdateTime); | ||||||
|  |             if (mpAccountMapper.selectCountByUpdateTimeGt(maxTime) > 0) { | ||||||
|  |                 initLocalCache(); | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -85,8 +109,8 @@ public class MpAccountServiceImpl implements MpAccountService { | |||||||
|         MpAccountDO account = MpAccountConvert.INSTANCE.convert(createReqVO); |         MpAccountDO account = MpAccountConvert.INSTANCE.convert(createReqVO); | ||||||
|         mpAccountMapper.insert(account); |         mpAccountMapper.insert(account); | ||||||
|  |  | ||||||
|         // 发送刷新消息 |         // 刷新缓存 | ||||||
|         mpAccountProducer.sendAccountRefreshMessage(); |         initLocalCache(); | ||||||
|         return account.getId(); |         return account.getId(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -101,8 +125,8 @@ public class MpAccountServiceImpl implements MpAccountService { | |||||||
|         MpAccountDO updateObj = MpAccountConvert.INSTANCE.convert(updateReqVO); |         MpAccountDO updateObj = MpAccountConvert.INSTANCE.convert(updateReqVO); | ||||||
|         mpAccountMapper.updateById(updateObj); |         mpAccountMapper.updateById(updateObj); | ||||||
|  |  | ||||||
|         // 发送刷新消息 |         // 刷新缓存 | ||||||
|         mpAccountProducer.sendAccountRefreshMessage(); |         initLocalCache(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -112,8 +136,8 @@ public class MpAccountServiceImpl implements MpAccountService { | |||||||
|         // 删除 |         // 删除 | ||||||
|         mpAccountMapper.deleteById(id); |         mpAccountMapper.deleteById(id); | ||||||
|  |  | ||||||
|         // 发送刷新消息 |         // 刷新缓存 | ||||||
|         mpAccountProducer.sendAccountRefreshMessage(); |         initLocalCache(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private MpAccountDO validateAccountExists(Long id) { |     private MpAccountDO validateAccountExists(Long id) { | ||||||
|   | |||||||
| @@ -6,8 +6,6 @@ import lombok.Data; | |||||||
| /** | /** | ||||||
|  * 支付单信息 Response DTO |  * 支付单信息 Response DTO | ||||||
|  * |  * | ||||||
|  * TODO 芋艿:还没定好字段 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  * @author 芋道源码 | ||||||
|  */ |  */ | ||||||
| @Data | @Data | ||||||
|   | |||||||
| @@ -8,8 +8,6 @@ import java.time.LocalDateTime; | |||||||
| /** | /** | ||||||
|  * 退款单信息 Response DTO |  * 退款单信息 Response DTO | ||||||
|  * |  * | ||||||
|  * TODO 芋艿:还没定好字段 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  * @author 芋道源码 | ||||||
|  */ |  */ | ||||||
| @Data | @Data | ||||||
|   | |||||||
| @@ -61,12 +61,6 @@ | |||||||
|             <artifactId>yudao-spring-boot-starter-job</artifactId> |             <artifactId>yudao-spring-boot-starter-job</artifactId> | ||||||
|         </dependency> |         </dependency> | ||||||
|  |  | ||||||
|         <!-- 消息队列相关 --> |  | ||||||
|         <dependency> |  | ||||||
|             <groupId>cn.iocoder.boot</groupId> |  | ||||||
|             <artifactId>yudao-spring-boot-starter-mq</artifactId> |  | ||||||
|         </dependency> |  | ||||||
|  |  | ||||||
|         <!-- Test 测试相关 --> |         <!-- Test 测试相关 --> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>cn.iocoder.boot</groupId> |             <groupId>cn.iocoder.boot</groupId> | ||||||
|   | |||||||
| @@ -1,8 +1,5 @@ | |||||||
| package cn.iocoder.yudao.module.pay.dal.redis; | package cn.iocoder.yudao.module.pay.dal.redis; | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; |  | ||||||
| import org.redisson.api.RLock; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 支付 Redis Key 枚举类 |  * 支付 Redis Key 枚举类 | ||||||
|  * |  * | ||||||
| @@ -10,9 +7,14 @@ import org.redisson.api.RLock; | |||||||
|  */ |  */ | ||||||
| public interface RedisKeyConstants { | public interface RedisKeyConstants { | ||||||
|  |  | ||||||
|     RedisKeyDefine PAY_NOTIFY_LOCK = new RedisKeyDefine("通知任务的分布式锁", |     /** | ||||||
|             "pay_notify:lock:%d", // 参数来自 DefaultLockKeyBuilder 类 |      * 通知任务的分布式锁 | ||||||
|             RedisKeyDefine.KeyTypeEnum.HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构 |      * | ||||||
|  |      * KEY 格式:pay_notify:lock:%d // 参数来自 DefaultLockKeyBuilder 类 | ||||||
|  |      * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构 | ||||||
|  |      * 过期时间:不固定 | ||||||
|  |      */ | ||||||
|  |     String PAY_NOTIFY_LOCK = "pay_notify:lock:%d"; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 支付序号的缓存 |      * 支付序号的缓存 | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ public class PayNotifyLockRedisDAO { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static String formatKey(Long id) { |     private static String formatKey(Long id) { | ||||||
|         return String.format(PAY_NOTIFY_LOCK.getKeyTemplate(), id); |         return String.format(PAY_NOTIFY_LOCK, id); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,7 +15,6 @@ import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateR | |||||||
| import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert; | import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert; | ||||||
| import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; | import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; | ||||||
| import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper; | import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper; | ||||||
| import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants; |  | ||||||
| import lombok.Getter; | import lombok.Getter; | ||||||
| import lombok.Setter; | import lombok.Setter; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
| @@ -112,7 +111,7 @@ public class PayChannelServiceImpl implements PayChannelService { | |||||||
|         channelMapper.insert(channel); |         channelMapper.insert(channel); | ||||||
|  |  | ||||||
|         // 刷新缓存 |         // 刷新缓存 | ||||||
|         refreshLocalCache(); |         initLocalCache(); | ||||||
|         return channel.getId(); |         return channel.getId(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -127,7 +126,7 @@ public class PayChannelServiceImpl implements PayChannelService { | |||||||
|         channelMapper.updateById(channel); |         channelMapper.updateById(channel); | ||||||
|  |  | ||||||
|         // 刷新缓存 |         // 刷新缓存 | ||||||
|         refreshLocalCache(); |         initLocalCache(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -160,7 +159,7 @@ public class PayChannelServiceImpl implements PayChannelService { | |||||||
|         channelMapper.deleteById(id); |         channelMapper.deleteById(id); | ||||||
|  |  | ||||||
|         // 刷新缓存 |         // 刷新缓存 | ||||||
|         refreshLocalCache(); |         initLocalCache(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private PayChannelDO validateChannelExists(Long id) { |     private PayChannelDO validateChannelExists(Long id) { | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ public interface AdminUserApi { | |||||||
|      * @param postIds 岗位数组 |      * @param postIds 岗位数组 | ||||||
|      * @return 用户数组 |      * @return 用户数组 | ||||||
|      */ |      */ | ||||||
|     List<AdminUserRespDTO> getUsersByPostIds(Collection<Long> postIds); |     List<AdminUserRespDTO> getUserListByPostIds(Collection<Long> postIds); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 获得用户 Map |      * 获得用户 Map | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ public class PermissionApiImpl implements PermissionApi { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) { |     public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) { | ||||||
|         return permissionService.getUserRoleIdListByRoleIds(roleIds); |         return permissionService.getUserRoleIdListByRoleId(roleIds); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ public class AdminUserApiImpl implements AdminUserApi { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public List<AdminUserRespDTO> getUsersByPostIds(Collection<Long> postIds) { |     public List<AdminUserRespDTO> getUserListByPostIds(Collection<Long> postIds) { | ||||||
|         List<AdminUserDO> users = userService.getUserListByPostIds(postIds); |         List<AdminUserDO> users = userService.getUserListByPostIds(postIds); | ||||||
|         return UserConvert.INSTANCE.convertList4(users); |         return UserConvert.INSTANCE.convertList4(users); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| POST {{baseUrl}}/system/auth/login | POST {{baseUrl}}/system/auth/login | ||||||
| Content-Type: application/json | Content-Type: application/json | ||||||
| tenant-id: {{adminTenentId}} | tenant-id: {{adminTenentId}} | ||||||
|  | tag: Yunai.local | ||||||
|  |  | ||||||
| { | { | ||||||
|   "username": "admin", |   "username": "admin", | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.controller.admin.auth; | |||||||
| import cn.hutool.core.util.StrUtil; | import cn.hutool.core.util.StrUtil; | ||||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 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.operatelog.core.annotations.OperateLog; | ||||||
| import cn.iocoder.yudao.framework.security.config.SecurityProperties; | import cn.iocoder.yudao.framework.security.config.SecurityProperties; | ||||||
| import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; | ||||||
| @@ -12,8 +11,8 @@ 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.permission.RoleDO; | ||||||
| import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; | 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.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.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.PermissionService; | ||||||
| import cn.iocoder.yudao.module.system.service.permission.RoleService; | 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.social.SocialUserService; | ||||||
| @@ -34,9 +33,9 @@ import java.util.List; | |||||||
| import java.util.Set; | import java.util.Set; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 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.getLoginUserId; | ||||||
| import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization; | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization; | ||||||
| import static java.util.Collections.singleton; |  | ||||||
|  |  | ||||||
| @Tag(name = "管理后台 - 认证") | @Tag(name = "管理后台 - 认证") | ||||||
| @RestController | @RestController | ||||||
| @@ -52,6 +51,8 @@ public class AuthController { | |||||||
|     @Resource |     @Resource | ||||||
|     private RoleService roleService; |     private RoleService roleService; | ||||||
|     @Resource |     @Resource | ||||||
|  |     private MenuService menuService; | ||||||
|  |     @Resource | ||||||
|     private PermissionService permissionService; |     private PermissionService permissionService; | ||||||
|     @Resource |     @Resource | ||||||
|     private SocialUserService socialUserService; |     private SocialUserService socialUserService; | ||||||
| @@ -91,33 +92,24 @@ public class AuthController { | |||||||
|     @GetMapping("/get-permission-info") |     @GetMapping("/get-permission-info") | ||||||
|     @Operation(summary = "获取登录用户的权限信息") |     @Operation(summary = "获取登录用户的权限信息") | ||||||
|     public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() { |     public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() { | ||||||
|         // 获得用户信息 |         // 1.1 获得用户信息 | ||||||
|         AdminUserDO user = userService.getUser(getLoginUserId()); |         AdminUserDO user = userService.getUser(getLoginUserId()); | ||||||
|         if (user == null) { |         if (user == null) { | ||||||
|             return 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") |         // 1.2 获得角色列表 | ||||||
|     @Operation(summary = "获得登录用户的菜单列表") |         Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId()); | ||||||
|     public CommonResult<List<AuthMenuRespVO>> getMenuList() { |         List<RoleDO> roles = roleService.getRoleList(roleIds); | ||||||
|         // 获得角色列表 |         roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色 | ||||||
|         Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus())); |  | ||||||
|         // 获得用户拥有的菜单列表 |         // 1.3 获得菜单列表 | ||||||
|         List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds, |         Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId)); | ||||||
|                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型 |         List<MenuDO> menuList = menuService.getMenuList(menuIds); | ||||||
|                 singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的 |         menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单 | ||||||
|         // 转换成 Tree 结构返回 |  | ||||||
|         return success(AuthConvert.INSTANCE.buildMenuTree(menuList)); |         // 2. 拼接结果返回 | ||||||
|  |         return success(AuthConvert.INSTANCE.convert(user, roles, menuList)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ========== 短信登录相关 ========== |     // ========== 短信登录相关 ========== | ||||||
|   | |||||||
| @@ -6,9 +6,10 @@ import lombok.Builder; | |||||||
| import lombok.Data; | import lombok.Data; | ||||||
| import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
|  |  | ||||||
| @Schema(description = "管理后台 - 登录用户的权限信息 Response VO,额外包括用户信息和角色列表") | @Schema(description = "管理后台 - 登录用户的权限信息 Response VO,额外包括用户信息和角色列表") | ||||||
| @Data | @Data | ||||||
| @NoArgsConstructor | @NoArgsConstructor | ||||||
| @AllArgsConstructor | @AllArgsConstructor | ||||||
| @@ -24,6 +25,9 @@ public class AuthPermissionInfoRespVO { | |||||||
|     @Schema(description = "操作权限数组", requiredMode = Schema.RequiredMode.REQUIRED) |     @Schema(description = "操作权限数组", requiredMode = Schema.RequiredMode.REQUIRED) | ||||||
|     private Set<String> permissions; |     private Set<String> permissions; | ||||||
|  |  | ||||||
|  |     @Schema(description = "菜单树", required = true) | ||||||
|  |     private List<MenuVO> menus; | ||||||
|  |  | ||||||
|     @Schema(description = "用户信息 VO") |     @Schema(description = "用户信息 VO") | ||||||
|     @Data |     @Data | ||||||
|     @NoArgsConstructor |     @NoArgsConstructor | ||||||
| @@ -37,9 +41,53 @@ public class AuthPermissionInfoRespVO { | |||||||
|         @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") |         @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") | ||||||
|         private String nickname; |         private String nickname; | ||||||
|  |  | ||||||
|         @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://www.iocoder.cn/xx.jpg") |         @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.jpg") | ||||||
|         private String avatar; |         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; | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,9 +11,9 @@ import java.util.Map; | |||||||
| import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| * 邮件日志 Base VO,提供给添加、修改、详细的子 VO 使用 |  * 邮件日志 Base VO,提供给添加、修改、详细的子 VO 使用 | ||||||
| * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 |  * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 | ||||||
| */ |  */ | ||||||
| @Data | @Data | ||||||
| public class MailLogBaseVO { | public class MailLogBaseVO { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -37,10 +37,10 @@ public class PermissionController { | |||||||
|  |  | ||||||
|     @Operation(summary = "获得角色拥有的菜单编号") |     @Operation(summary = "获得角色拥有的菜单编号") | ||||||
|     @Parameter(name = "roleId", description = "角色编号", required = true) |     @Parameter(name = "roleId", description = "角色编号", required = true) | ||||||
|     @GetMapping("/list-role-resources") |     @GetMapping("/list-role-menus") | ||||||
|     @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')") |     @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')") | ||||||
|     public CommonResult<Set<Long>> listRoleMenus(Long roleId) { |     public CommonResult<Set<Long>> getRoleMenuList(Long roleId) { | ||||||
|         return success(permissionService.getRoleMenuIds(roleId)); |         return success(permissionService.getRoleMenuListByRoleId(roleId)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @PostMapping("/assign-role-menu") |     @PostMapping("/assign-role-menu") | ||||||
|   | |||||||
| @@ -40,6 +40,3 @@ tenant-id: {{adminTenentId}} | |||||||
| GET {{baseUrl}}/system/role/page?pageNo=1&pageSize=10 | GET {{baseUrl}}/system/role/page?pageNo=1&pageSize=10 | ||||||
| Authorization: Bearer {{token}} | Authorization: Bearer {{token}} | ||||||
| tenant-id: {{adminTenentId}} | tenant-id: {{adminTenentId}} | ||||||
|  |  | ||||||
| ### |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ public class PermissionAssignRoleDataScopeReqVO { | |||||||
| //    TODO 这里要多一个枚举校验 | //    TODO 这里要多一个枚举校验 | ||||||
|     private Integer dataScope; |     private Integer dataScope; | ||||||
|  |  | ||||||
|     @Schema(description = "部门编号列表,只有范围类型为 DEPT_CUSTOM 时,该字段才需要", example = "1,3,5") |     @Schema(description = "部门编号列表,只有范围类型为 DEPT_CUSTOM 时,该字段才需要", example = "1,3,5") | ||||||
|     private Set<Long> dataScopeDeptIds = Collections.emptySet(); // 兜底 |     private Set<Long> dataScopeDeptIds = Collections.emptySet(); // 兜底 | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,9 +7,9 @@ import javax.validation.constraints.NotNull; | |||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| * 敏感词 Base VO,提供给添加、修改、详细的子 VO 使用 |  * 敏感词 Base VO,提供给添加、修改、详细的子 VO 使用 | ||||||
| * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 |  * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 | ||||||
| */ |  */ | ||||||
| @Data | @Data | ||||||
| public class SensitiveWordBaseVO { | public class SensitiveWordBaseVO { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,9 +7,9 @@ import javax.validation.constraints.*; | |||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| * 租户 Base VO,提供给添加、修改、详细的子 VO 使用 |  * 租户 Base VO,提供给添加、修改、详细的子 VO 使用 | ||||||
| * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 |  * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 | ||||||
| */ |  */ | ||||||
| @Data | @Data | ||||||
| public class TenantBaseVO { | public class TenantBaseVO { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ public class UserProfileUpdateReqVO { | |||||||
|     @Length(min = 11, max = 11, message = "手机号长度必须 11 位") |     @Length(min = 11, max = 11, message = "手机号长度必须 11 位") | ||||||
|     private String mobile; |     private String mobile; | ||||||
|  |  | ||||||
|     @Schema(description = "用户性别-参见 SexEnum 枚举类", example = "1") |     @Schema(description = "用户性别,参见 SexEnum 枚举类", example = "1") | ||||||
|     private Integer sex; |     private Integer sex; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ public class UserExportReqVO { | |||||||
|     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||||||
|     private LocalDateTime[] createTime; |     private LocalDateTime[] createTime; | ||||||
|  |  | ||||||
|     @Schema(description = "部门编号,同时筛选子部门", example = "1024") |     @Schema(description = "部门编号,同时筛选子部门", example = "1024") | ||||||
|     private Long deptId; |     private Long deptId; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import lombok.Data; | |||||||
| import lombok.EqualsAndHashCode; | import lombok.EqualsAndHashCode; | ||||||
| import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||||
|  |  | ||||||
| @Schema(description = "管理后台 - 用户分页时的信息 Response VO,相比用户基本信息来说,会多部门信息") | @Schema(description = "管理后台 - 用户分页时的信息 Response VO,相比用户基本信息来说,会多部门信息") | ||||||
| @Data | @Data | ||||||
| @NoArgsConstructor | @NoArgsConstructor | ||||||
| @AllArgsConstructor | @AllArgsConstructor | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| package cn.iocoder.yudao.module.system.controller.admin.user.vo.user; | package cn.iocoder.yudao.module.system.controller.admin.user.vo.user; | ||||||
|  |  | ||||||
|  | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||||
|  | import cn.iocoder.yudao.framework.common.validation.InEnum; | ||||||
| import io.swagger.v3.oas.annotations.media.Schema; | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
|  |  | ||||||
| @@ -13,9 +15,9 @@ public class UserUpdateStatusReqVO { | |||||||
|     @NotNull(message = "角色编号不能为空") |     @NotNull(message = "角色编号不能为空") | ||||||
|     private Long id; |     private Long id; | ||||||
|  |  | ||||||
|     @Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |     @Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||||||
|     @NotNull(message = "状态不能为空") |     @NotNull(message = "状态不能为空") | ||||||
| //    @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}") |     @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}") | ||||||
|     private Integer status; |     private Integer status; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ 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.MenuDO; | ||||||
| import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; | 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.dal.dataobject.user.AdminUserDO; | ||||||
|  | import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum; | ||||||
| import org.mapstruct.Mapper; | import org.mapstruct.Mapper; | ||||||
| import org.mapstruct.factory.Mappers; | import org.mapstruct.factory.Mappers; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| @@ -29,11 +30,14 @@ public interface AuthConvert { | |||||||
|         return AuthPermissionInfoRespVO.builder() |         return AuthPermissionInfoRespVO.builder() | ||||||
|                 .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build()) |                 .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build()) | ||||||
|                 .roles(convertSet(roleList, RoleDO::getCode)) |                 .roles(convertSet(roleList, RoleDO::getCode)) | ||||||
|  |                 // 权限标识信息 | ||||||
|                 .permissions(convertSet(menuList, MenuDO::getPermission)) |                 .permissions(convertSet(menuList, MenuDO::getPermission)) | ||||||
|  |                 // 菜单树 | ||||||
|  |                 .menus(buildMenuTree(menuList)) | ||||||
|                 .build(); |                 .build(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     AuthMenuRespVO convertTreeNode(MenuDO menu); |     AuthPermissionInfoRespVO.MenuVO convertTreeNode(MenuDO menu); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 将菜单列表,构建成菜单树 |      * 将菜单列表,构建成菜单树 | ||||||
| @@ -41,17 +45,20 @@ public interface AuthConvert { | |||||||
|      * @param menuList 菜单列表 |      * @param menuList 菜单列表 | ||||||
|      * @return 菜单树 |      * @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)); |         menuList.sort(Comparator.comparing(MenuDO::getSort)); | ||||||
|  |  | ||||||
|         // 构建菜单树 |         // 构建菜单树 | ||||||
|         // 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。 |         // 使用 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))); |         menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu))); | ||||||
|         // 处理父子关系 |         // 处理父子关系 | ||||||
|         treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(ID_ROOT)).forEach(childNode -> { |         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) { |             if (parentNode == null) { | ||||||
|                 LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]", |                 LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]", | ||||||
|                         childNode.getId(), childNode.getParentId()); |                         childNode.getId(), childNode.getParentId()); | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqV | |||||||
| import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; | import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; | ||||||
| import org.apache.ibatis.annotations.Mapper; | import org.apache.ibatis.annotations.Mapper; | ||||||
|  |  | ||||||
|  | import java.util.Collection; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| @Mapper | @Mapper | ||||||
| @@ -25,4 +26,8 @@ public interface DeptMapper extends BaseMapperX<DeptDO> { | |||||||
|         return selectCount(DeptDO::getParentId, parentId); |         return selectCount(DeptDO::getParentId, parentId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     default List<DeptDO> selectListByParentId(Collection<Long> parentIds) { | ||||||
|  |         return selectList(DeptDO::getParentId, parentIds); | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,4 +25,7 @@ public interface MenuMapper extends BaseMapperX<MenuDO> { | |||||||
|                 .eqIfPresent(MenuDO::getStatus, reqVO.getStatus())); |                 .eqIfPresent(MenuDO::getStatus, reqVO.getStatus())); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     default List<MenuDO> selectListByPermission(String permission) { | ||||||
|  |         return selectList(MenuDO::getPermission, permission); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.permission; | |||||||
| import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||||||
| import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO; | import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO; | ||||||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||||
| import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |  | ||||||
| import org.apache.ibatis.annotations.Mapper; | import org.apache.ibatis.annotations.Mapper; | ||||||
| import org.springframework.stereotype.Repository; |  | ||||||
|  |  | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @@ -13,14 +11,18 @@ import java.util.List; | |||||||
| @Mapper | @Mapper | ||||||
| public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> { | public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> { | ||||||
|  |  | ||||||
|     @Repository |  | ||||||
|     class BatchInsertMapper extends ServiceImpl<RoleMenuMapper, RoleMenuDO> { |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     default List<RoleMenuDO> selectListByRoleId(Long roleId) { |     default List<RoleMenuDO> selectListByRoleId(Long roleId) { | ||||||
|         return selectList(RoleMenuDO::getRoleId, 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) { |     default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) { | ||||||
|         delete(new LambdaQueryWrapper<RoleMenuDO>() |         delete(new LambdaQueryWrapper<RoleMenuDO>() | ||||||
|                 .eq(RoleMenuDO::getRoleId, roleId) |                 .eq(RoleMenuDO::getRoleId, roleId) | ||||||
|   | |||||||
| @@ -7,7 +7,9 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv | |||||||
| import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO; | import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO; | ||||||
| import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; | import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; | ||||||
| import org.apache.ibatis.annotations.Mapper; | import org.apache.ibatis.annotations.Mapper; | ||||||
|  | import org.apache.ibatis.annotations.Select; | ||||||
|  |  | ||||||
|  | import java.time.LocalDateTime; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -40,4 +42,7 @@ public interface SensitiveWordMapper extends BaseMapperX<SensitiveWordDO> { | |||||||
|         return selectOne(SensitiveWordDO::getName, name); |         return selectOne(SensitiveWordDO::getName, name); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Select("SELECT COUNT(*) FROM system_sensitive_word WHERE update_time > #{maxUpdateTime}") | ||||||
|  |     Long selectCountByUpdateTimeGt(LocalDateTime maxTime); | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,9 @@ 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.controller.admin.sms.vo.channel.SmsChannelPageReqVO; | ||||||
| import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; | import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; | ||||||
| import org.apache.ibatis.annotations.Mapper; | import org.apache.ibatis.annotations.Mapper; | ||||||
|  | import org.apache.ibatis.annotations.Select; | ||||||
|  |  | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
| @Mapper | @Mapper | ||||||
| public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> { | public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> { | ||||||
| @@ -18,4 +21,7 @@ public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> { | |||||||
|                 .orderByDesc(SmsChannelDO::getId)); |                 .orderByDesc(SmsChannelDO::getId)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Select("SELECT COUNT(*) FROM system_sms_channel WHERE update_time > #{maxUpdateTime}") | ||||||
|  |     Long selectCountByUpdateTimeGt(LocalDateTime maxTime); | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,12 +1,7 @@ | |||||||
| package cn.iocoder.yudao.module.system.dal.redis; | package cn.iocoder.yudao.module.system.dal.redis; | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; |  | ||||||
| import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | ||||||
|  |  | ||||||
| import java.time.Duration; |  | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * System Redis Key 枚举类 |  * System Redis Key 枚举类 | ||||||
|  * |  * | ||||||
| @@ -14,16 +9,93 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S | |||||||
|  */ |  */ | ||||||
| public interface RedisKeyConstants { | public interface RedisKeyConstants { | ||||||
|  |  | ||||||
|     RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存", |     /** | ||||||
|             "captcha_code:%s", // 参数为 uuid |      * 指定部门的所有子部门编号数组的缓存 | ||||||
|             STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); |      * <p> | ||||||
|  |      * KEY 格式:dept_children_ids:{id} | ||||||
|  |      * VALUE 数据类型:String 子部门编号集合 | ||||||
|  |      */ | ||||||
|  |     String DEPT_CHILDREN_ID_LIST = "dept_children_ids"; | ||||||
|  |  | ||||||
|     RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存", |     /** | ||||||
|             "oauth2_access_token:%s", // 参数为访问令牌 token |      * 角色的缓存 | ||||||
|             STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); |      * <p> | ||||||
|  |      * KEY 格式:role:{id} | ||||||
|  |      * VALUE 数据类型:String 角色信息 | ||||||
|  |      */ | ||||||
|  |     String ROLE = "role"; | ||||||
|  |  | ||||||
|     RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到 |     /** | ||||||
|             "social_auth_state:%s", // 参数为 state |      * 用户拥有的角色编号的缓存 | ||||||
|             STRING, String.class, Duration.ofHours(24)); // 值为 state |      * <p> | ||||||
|  |      * KEY 格式:user_role_ids:{userId} | ||||||
|  |      * VALUE 数据类型:String 角色编号集合 | ||||||
|  |      */ | ||||||
|  |     String USER_ROLE_ID_LIST = "user_role_ids"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 拥有指定菜单的角色编号的缓存 | ||||||
|  |      * <p> | ||||||
|  |      * KEY 格式:user_role_ids:{menuId} | ||||||
|  |      * VALUE 数据类型:String 角色编号集合 | ||||||
|  |      */ | ||||||
|  |     String MENU_ROLE_ID_LIST = "menu_role_ids"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 拥有权限对应的菜单编号数组的缓存 | ||||||
|  |      * <p> | ||||||
|  |      * KEY 格式:permission_menu_ids:{permission} | ||||||
|  |      * VALUE 数据类型:String 菜单编号数组 | ||||||
|  |      */ | ||||||
|  |     String PERMISSION_MENU_ID_LIST = "permission_menu_ids"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * OAuth2 客户端的缓存 | ||||||
|  |      * <p> | ||||||
|  |      * KEY 格式:user:{id} | ||||||
|  |      * VALUE 数据类型:String 客户端信息 | ||||||
|  |      */ | ||||||
|  |     String OAUTH_CLIENT = "oauth_client"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 访问令牌的缓存 | ||||||
|  |      * <p> | ||||||
|  |      * KEY 格式:oauth2_access_token:{token} | ||||||
|  |      * VALUE 数据类型:String 访问令牌信息 {@link OAuth2AccessTokenDO} | ||||||
|  |      * <p> | ||||||
|  |      * 由于动态过期时间,使用 RedisTemplate 操作 | ||||||
|  |      */ | ||||||
|  |     String OAUTH2_ACCESS_TOKEN = "oauth2_access_token:%s"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 站内信模版的缓存 | ||||||
|  |      * <p> | ||||||
|  |      * KEY 格式:notify_template:{code} | ||||||
|  |      * VALUE 数据格式:String 模版信息 | ||||||
|  |      */ | ||||||
|  |     String NOTIFY_TEMPLATE = "notify_template"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 邮件账号的缓存 | ||||||
|  |      * <p> | ||||||
|  |      * KEY 格式:sms_template:{id} | ||||||
|  |      * VALUE 数据格式:String 账号信息 | ||||||
|  |      */ | ||||||
|  |     String MAIL_ACCOUNT = "mail_account"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 邮件模版的缓存 | ||||||
|  |      * <p> | ||||||
|  |      * KEY 格式:mail_template:{code} | ||||||
|  |      * VALUE 数据格式:String 模版信息 | ||||||
|  |      */ | ||||||
|  |     String MAIL_TEMPLATE = "mail_template"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 短信模版的缓存 | ||||||
|  |      * <p> | ||||||
|  |      * KEY 格式:sms_template:{id} | ||||||
|  |      * VALUE 数据格式:String 模版信息 | ||||||
|  |      */ | ||||||
|  |     String SMS_TEMPLATE = "sms_template"; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,41 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.dal.redis.common; |  | ||||||
|  |  | ||||||
| import org.springframework.data.redis.core.StringRedisTemplate; |  | ||||||
| import org.springframework.stereotype.Repository; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
| import java.time.Duration; |  | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.CAPTCHA_CODE; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 验证码的 Redis DAO |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Repository |  | ||||||
| public class CaptchaRedisDAO { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private StringRedisTemplate stringRedisTemplate; |  | ||||||
|  |  | ||||||
|     public String get(String uuid) { |  | ||||||
|         String redisKey = formatKey(uuid); |  | ||||||
|         return stringRedisTemplate.opsForValue().get(redisKey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void set(String uuid, String code, Duration timeout) { |  | ||||||
|         String redisKey = formatKey(uuid); |  | ||||||
|         stringRedisTemplate.opsForValue().set(redisKey, code, timeout); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void delete(String uuid) { |  | ||||||
|         String redisKey = formatKey(uuid); |  | ||||||
|         stringRedisTemplate.delete(redisKey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static String formatKey(String uuid) { |  | ||||||
|         return String.format(CAPTCHA_CODE.getKeyTemplate(), uuid); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -53,7 +53,7 @@ public class OAuth2AccessTokenRedisDAO { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static String formatKey(String accessToken) { |     private static String formatKey(String accessToken) { | ||||||
|         return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken); |         return String.format(OAUTH2_ACCESS_TOKEN, accessToken); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.auth; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link OAuth2ClientRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class OAuth2ClientRefreshConsumer extends AbstractChannelMessageListener<OAuth2ClientRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private OAuth2ClientService oauth2ClientService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(OAuth2ClientRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 OAuth2Client 刷新消息]"); |  | ||||||
|         oauth2ClientService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.dept; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.dept.DeptRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.service.dept.DeptService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link DeptRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class DeptRefreshConsumer extends AbstractChannelMessageListener<DeptRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private DeptService deptService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(DeptRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 Dept 刷新消息]"); |  | ||||||
|         deptService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,31 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.mail; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.mail.MailAccountRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.service.mail.MailAccountService; |  | ||||||
| import cn.iocoder.yudao.module.system.service.mail.MailTemplateService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link MailAccountRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author wangjingyi |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class MailAccountRefreshConsumer extends AbstractChannelMessageListener<MailAccountRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private MailAccountService mailAccountService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(MailAccountRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 Mail Account 刷新信息]"); |  | ||||||
|         mailAccountService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.mail; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.service.mail.MailTemplateService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link MailTemplateRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author wangjingyi |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class MailTemplateRefreshConsumer extends AbstractChannelMessageListener<MailTemplateRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private MailTemplateService mailTemplateService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(MailTemplateRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 Mail Template 刷新信息]"); |  | ||||||
|         mailTemplateService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.notify; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.notify.NotifyTemplateRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.service.notify.NotifyTemplateService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link NotifyTemplateRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author xrcoder |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class NotifyTemplateRefreshConsumer extends AbstractChannelMessageListener<NotifyTemplateRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private NotifyTemplateService notifyTemplateService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(NotifyTemplateRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 NotifyTemplate 刷新消息]"); |  | ||||||
|         notifyTemplateService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.permission; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.permission.MenuRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.service.permission.MenuService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link MenuRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class MenuRefreshConsumer extends AbstractChannelMessageListener<MenuRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private MenuService menuService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(MenuRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 Menu 刷新消息]"); |  | ||||||
|         menuService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.permission; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.permission.RoleMenuRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.service.permission.PermissionService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link RoleMenuRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class RoleMenuRefreshConsumer extends AbstractChannelMessageListener<RoleMenuRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private PermissionService permissionService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(RoleMenuRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 Role 与 Menu 的关联刷新消息]"); |  | ||||||
|         permissionService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.permission; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.permission.RoleRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.service.permission.RoleService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link RoleRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class RoleRefreshConsumer extends AbstractChannelMessageListener<RoleRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private RoleService roleService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(RoleRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 Role 刷新消息]"); |  | ||||||
|         roleService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.permission; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.permission.UserRoleRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.service.permission.PermissionService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link UserRoleRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class UserRoleRefreshConsumer extends AbstractChannelMessageListener<UserRoleRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private PermissionService permissionService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(UserRoleRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 User 与 Role 的关联刷新消息]"); |  | ||||||
|         permissionService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.sensitiveword; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.sensitiveword.SensitiveWordRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.service.sensitiveword.SensitiveWordService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link SensitiveWordRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class SensitiveWordRefreshConsumer extends AbstractChannelMessageListener<SensitiveWordRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private SensitiveWordService sensitiveWordService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(SensitiveWordRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 SensitiveWord 刷新消息]"); |  | ||||||
|         sensitiveWordService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.sms; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.sms.SmsChannelRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.service.sms.SmsChannelService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link SmsChannelRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class SmsChannelRefreshConsumer extends AbstractChannelMessageListener<SmsChannelRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private SmsChannelService smsChannelService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(SmsChannelRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 SmsChannel 刷新消息]"); |  | ||||||
|         smsChannelService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.consumer.sms; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.module.system.mq.message.sms.SmsTemplateRefreshMessage; |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |  | ||||||
| import cn.iocoder.yudao.module.system.service.sms.SmsTemplateService; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import javax.annotation.Resource; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 针对 {@link SmsTemplateRefreshMessage} 的消费者 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class SmsTemplateRefreshConsumer extends AbstractChannelMessageListener<SmsTemplateRefreshMessage> { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private SmsTemplateService smsTemplateService; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onMessage(SmsTemplateRefreshMessage message) { |  | ||||||
|         log.info("[onMessage][收到 SmsTemplate 刷新消息]"); |  | ||||||
|         smsTemplateService.initLocalCache(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.message.auth; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage; |  | ||||||
| import lombok.Data; |  | ||||||
| import lombok.EqualsAndHashCode; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * OAuth 2.0 客户端的数据刷新 Message |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Data |  | ||||||
| @EqualsAndHashCode(callSuper = true) |  | ||||||
| public class OAuth2ClientRefreshMessage extends AbstractChannelMessage { |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getChannel() { |  | ||||||
|         return "system.oauth2-client.refresh"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.message.dept; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage; |  | ||||||
| import lombok.Data; |  | ||||||
| import lombok.EqualsAndHashCode; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 部门数据刷新 Message |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Data |  | ||||||
| @EqualsAndHashCode(callSuper = true) |  | ||||||
| public class DeptRefreshMessage extends AbstractChannelMessage { |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getChannel() { |  | ||||||
|         return "system.dept.refresh"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.message.mail; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage; |  | ||||||
| import lombok.Data; |  | ||||||
| import lombok.EqualsAndHashCode; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 邮箱账号的数据刷新 Message |  | ||||||
|  * |  | ||||||
|  * @author wangjingyi |  | ||||||
|  */ |  | ||||||
| @Data |  | ||||||
| @EqualsAndHashCode(callSuper = true) |  | ||||||
| public class MailAccountRefreshMessage extends AbstractChannelMessage { |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getChannel() { |  | ||||||
|         return "system.mail-account.refresh"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.system.mq.message.mail; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage; |  | ||||||
| import lombok.Data; |  | ||||||
| import lombok.EqualsAndHashCode; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 邮箱模板的数据刷新 Message |  | ||||||
|  * |  | ||||||
|  * @author wangjingyi |  | ||||||
|  */ |  | ||||||
| @Data |  | ||||||
| @EqualsAndHashCode(callSuper = true) |  | ||||||
| public class MailTemplateRefreshMessage extends AbstractChannelMessage { |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getChannel() { |  | ||||||
|         return "system.mail-template.refresh"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV