Commit bf3e848c171c6e63c94f09cbb2df2fee01c2b9b3

Authored by YunaiV
2 parents 6a387387 7a49e45b

Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/mall_product

Showing 169 changed files with 2097 additions and 3939 deletions
Docker-HOWTO.md
1 1 # Docker Build & Up
2 2  
3 3 目标: 快速部署体验系统,帮助了解系统之间的依赖关系。
  4 +依赖:docker compose v2,删除`name: yudao-system`,降低`version`版本为`3.3`以下,支持`docker-compose`。
4 5  
5 6 ## 功能文件列表
6 7  
7 8 ```text
8 9 .
9   -├── Docker-HOWTO.md
10   -├── docker-compose.yml
11   -├── docker.env
  10 +├── Docker-HOWTO.md
  11 +├── docker-compose.yml
  12 +├── docker.env <-- 提供docker-compose环境变量配置
12 13 ├── yudao-server
13   -│ ├── Dockerfile
14   -│ └── nginx.conf
  14 +│ └── Dockerfile
15 15 └── yudao-ui-admin
16 16 ├── .dockerignore
17   - └── Dockerfile
  17 + ├── Dockerfile
  18 + └── nginx.conf <-- 提供基础配置,gzip压缩、api转发
18 19 ```
19 20  
20   -## Maven build (Optional)
  21 +## 构建 jar 包
21 22  
22 23 ```shell
23 24 # 创建maven缓存volume
... ... @@ -30,29 +31,19 @@ docker run -it --rm --name yudao-maven \
30 31 maven mvn clean install package '-Dmaven.test.skip=true'
31 32 ```
32 33  
33   -## Docker Compose Build
34   -
35   -```shell
36   -docker compose --env-file docker.env build
37   -```
38   -
39   -## Docker Compose Up
  34 +## 构建启动服务
40 35  
41 36 ```shell
42 37 docker compose --env-file docker.env up -d
43 38 ```
44 39  
45   -第一次执行,由于数据库未初始化,因此yudao-server容器会运行失败。执行如下命令初始化数据库:
  40 +首次运行会自动构建容器。可以通过`docker compose build [service]`来手动构建所有或某个docker镜像
46 41  
47   -```shell
48   -docker compose exec -T mysql \
49   - sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" --default-character-set=utf8mb4 ruoyi-vue-pro' \
50   - < ./sql/mysql/ruoyi-vue-pro.sql
51   -```
  42 +`--env-file docker.env`为可选参数,只是展示了通过`.env`文件配置容器启动的环境变量,`docker-compose.yml`本身已经提供足够的默认参数来正常运行系统。
52 43  
53   -## Server:Port
  44 +## 服务器的宿主机端口映射
54 45  
55   -- admin: http://localhost:8080
56   -- API: http://localhost:48080
57   -- mysql: root/123456, port: 3308
  46 +- admin ui: http://localhost:8080
  47 +- api server: http://localhost:48080
  48 +- mysql: root/123456, port: 3306
58 49 - redis: port: 6379
... ...
README.md
... ... @@ -102,7 +102,7 @@
102 102  
103 103 系统内置多种多种业务功能,可以用于快速你的业务系统:
104 104  
105   -![功能分层](https://static.iocoder.cn/ruoyi-vue-pro-biz.png)
  105 +![功能分层](https://static.iocoder.cn/ruoyi-vue-pro-biz.png?imageView2/2/format/webp)
106 106  
107 107 * 系统功能
108 108 * 基础设施
... ...
docker-compose.yml
1   -version: "3.8"
  1 +version: "3.4"
2 2  
3 3 name: yudao-system
4 4  
... ... @@ -9,25 +9,22 @@ services:
9 9 restart: unless-stopped
10 10 tty: true
11 11 ports:
12   - - 13306:3306
  12 + - "3306:3306"
13 13 environment:
14 14 MYSQL_DATABASE: ${MYSQL_DATABASE:-ruoyi-vue-pro}
15 15 MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-123456}
16 16 volumes:
17 17 - mysql:/var/lib/mysql/
18   - networks:
19   - - yudao-network
  18 + - ./sql/mysql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/ruoyi-vue-pro.sql:ro
20 19  
21 20 redis:
22 21 container_name: yudao-redis
23 22 image: redis:6-alpine
24 23 restart: unless-stopped
25 24 ports:
26   - - 16379:6379
  25 + - "6379:6379"
27 26 volumes:
28 27 - redis:/data
29   - networks:
30   - - yudao-network
31 28  
32 29 server:
33 30 container_name: yudao-server
... ... @@ -36,7 +33,7 @@ services:
36 33 image: yudao-server
37 34 restart: unless-stopped
38 35 ports:
39   - - 48080:48080
  36 + - "48080:48080"
40 37 environment:
41 38 # https://github.com/polovyivan/docker-pass-configs-to-container
42 39 SPRING_PROFILES_ACTIVE: local
... ... @@ -54,8 +51,6 @@ services:
54 51 --spring.datasource.dynamic.datasource.slave.username=${SLAVE_DATASOURCE_USERNAME:-root}
55 52 --spring.datasource.dynamic.datasource.slave.password=${SLAVE_DATASOURCE_PASSWORD:-123456}
56 53 --spring.redis.host=${REDIS_HOST:-yudao-redis}
57   - networks:
58   - - yudao-network
59 54 depends_on:
60 55 - mysql
61 56 - redis
... ... @@ -78,16 +73,10 @@ services:
78 73 image: yudao-admin
79 74 restart: unless-stopped
80 75 ports:
81   - - 8080:80
82   - networks:
83   - - yudao-network
  76 + - "8080:80"
84 77 depends_on:
85 78 - server
86 79  
87   -networks:
88   - yudao-network:
89   - driver: bridge
90   -
91 80 volumes:
92 81 mysql:
93 82 driver: local
... ...
... ... @@ -30,7 +30,7 @@
30 30 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
31 31  
32 32 <properties>
33   - <revision>1.7.3-snapshot</revision>
  33 + <revision>1.8.0-snapshot</revision>
34 34 <!-- Maven 相关 -->
35 35 <java.version>1.8</java.version>
36 36 <maven.compiler.source>${java.version}</maven.compiler.source>
... ...
yudao-dependencies/pom.xml
... ... @@ -14,7 +14,7 @@
14 14 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
15 15  
16 16 <properties>
17   - <revision>1.7.3-snapshot</revision>
  17 + <revision>1.8.0-snapshot</revision>
18 18 <!-- 统一依赖管理 -->
19 19 <spring.boot.version>2.7.13</spring.boot.version>
20 20 <!-- Web 相关 -->
... ... @@ -71,7 +71,7 @@
71 71 <justauth.version>1.0.1</justauth.version>
72 72 <jimureport.version>1.5.8</jimureport.version>
73 73 <xercesImpl.version>2.12.2</xercesImpl.version>
74   - <wx-java-mp.version>4.5.0</wx-java-mp.version>
  74 + <weixin-java.version>4.5.0</weixin-java.version>
75 75 </properties>
76 76  
77 77 <dependencyManagement>
... ... @@ -216,10 +216,9 @@
216 216 <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
217 217 <version>${dynamic-datasource.version}</version>
218 218 </dependency>
219   -
220 219 <dependency>
221 220 <groupId>com.github.yulichang</groupId>
222   - <artifactId>mybatis-plus-join-boot-starter</artifactId>
  221 + <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
223 222 <version>${mybatis-plus-join-boot-starter.version}</version>
224 223 </dependency>
225 224  
... ... @@ -601,8 +600,23 @@
601 600  
602 601 <dependency>
603 602 <groupId>com.github.binarywang</groupId>
  603 + <artifactId>weixin-java-pay</artifactId>
  604 + <version>${weixin-java.version}</version>
  605 + </dependency>
  606 + <dependency>
  607 + <groupId>com.github.binarywang</groupId>
  608 + <artifactId>weixin-java-mp</artifactId>
  609 + <version>${weixin-java.version}</version>
  610 + </dependency>
  611 + <dependency>
  612 + <groupId>com.github.binarywang</groupId>
604 613 <artifactId>wx-java-mp-spring-boot-starter</artifactId>
605   - <version>${wx-java-mp.version}</version>
  614 + <version>${weixin-java.version}</version>
  615 + </dependency>
  616 + <dependency>
  617 + <groupId>com.github.binarywang</groupId>
  618 + <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
  619 + <version>${weixin-java.version}</version>
606 620 </dependency>
607 621  
608 622 <!-- 积木报表-->
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
... ... @@ -164,7 +164,7 @@ public class CollectionUtils {
164 164 return from.stream().filter(predicate).findFirst().orElse(null);
165 165 }
166 166  
167   - public static <T, V extends Comparable<? super V>> V getMaxValue(List<T> from, Function<T, V> valueFunc) {
  167 + public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) {
168 168 if (CollUtil.isEmpty(from)) {
169 169 return null;
170 170 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java
... ... @@ -4,11 +4,11 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ReflectUtil;
5 5 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6 6 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
7   -import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
8   -import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
9 7 import cn.iocoder.yudao.framework.security.core.LoginUser;
10 8 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
11 9 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  10 +import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
  11 +import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
12 12 import net.sf.jsqlparser.expression.Alias;
13 13 import net.sf.jsqlparser.expression.Expression;
14 14 import org.junit.jupiter.api.BeforeEach;
... ... @@ -23,6 +23,7 @@ import static cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataP
23 23 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
24 24 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
25 25 import static org.junit.jupiter.api.Assertions.*;
  26 +import static org.mockito.ArgumentMatchers.eq;
26 27 import static org.mockito.ArgumentMatchers.same;
27 28 import static org.mockito.Mockito.mockStatic;
28 29 import static org.mockito.Mockito.when;
... ... @@ -73,6 +74,8 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
73 74 LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
74 75 .setUserType(UserTypeEnum.ADMIN.getValue()));
75 76 securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
  77 + // mock 方法(permissionApi 返回 null)
  78 + when(permissionApi.getDeptDataPermission(eq(loginUser.getId()))).thenReturn(null);
76 79  
77 80 // 调用
78 81 NullPointerException exception = assertThrows(NullPointerException.class,
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml
... ... @@ -63,9 +63,7 @@
63 63 <dependency>
64 64 <groupId>com.github.binarywang</groupId>
65 65 <artifactId>weixin-java-pay</artifactId>
66   - <version>4.5.0</version>
67 66 </dependency>
68   - <!-- TODO 芋艿:清理 -->
69 67  
70 68 <!-- Test 测试相关 -->
71 69 <dependency>
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisKeyDefine.java deleted
1   -package cn.iocoder.yudao.framework.tenant.core.redis;
2   -
3   -import cn.hutool.core.util.ArrayUtil;
4   -import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
5   -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
6   -
7   -import java.time.Duration;
8   -
9   -/**
10   - * 多租户拓展的 RedisKeyDefine 实现类
11   - *
12   - * 由于 Redis 不同于 MySQL 有 column 字段,无法通过类似 WHERE tenant_id = ? 的方式过滤
13   - * 所以需要通过在 Redis Key 上增加后缀的方式,进行租户之间的隔离。具体的步骤是:
14   - * 1. 假设 Redis Key 是 user:%d,示例是 user:1;对应到多租户的 Redis Key 是 user:%d:%d,
15   - * 2. 在 Redis DAO 中,需要使用 {@link #formatKey(Object...)} 方法,进行 Redis Key 的格式化
16   - *
17   - * 注意,大多数情况下,并不用使用 TenantRedisKeyDefine 实现。主要的使用场景,还是 Redis Key 可能存在冲突的情况。
18   - * 例如说,租户 1 和 2 都有一个手机号作为 Key,则他们会存在冲突的问题
19   - *
20   - * @author 芋道源码
21   - */
22   -public class TenantRedisKeyDefine extends RedisKeyDefine {
23   -
24   - /**
25   - * 多租户的 KEY 模板
26   - */
27   - private static final String KEY_TEMPLATE_SUFFIX = ":%d";
28   -
29   - public TenantRedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, Duration timeout) {
30   - super(memo, buildKeyTemplate(keyTemplate), keyType, valueType, timeout);
31   - }
32   -
33   - public TenantRedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, TimeoutTypeEnum timeoutType) {
34   - super(memo, buildKeyTemplate(keyTemplate), keyType, valueType, timeoutType);
35   - }
36   -
37   - private static String buildKeyTemplate(String keyTemplate) {
38   - return keyTemplate + KEY_TEMPLATE_SUFFIX;
39   - }
40   -
41   - @Override
42   - public String formatKey(Object... args) {
43   - args = ArrayUtil.append(args, TenantContextHolder.getRequiredTenantId());
44   - return super.formatKey(args);
45   - }
46   -
47   -}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/test/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisKeyDefineTest.java deleted
1   -package cn.iocoder.yudao.framework.tenant.core.redis;
2   -
3   -import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
4   -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
5   -import org.junit.jupiter.api.Test;
6   -
7   -import static org.junit.jupiter.api.Assertions.assertEquals;
8   -
9   -class TenantRedisKeyDefineTest {
10   -
11   - @Test
12   - public void testFormatKey() {
13   - Long tenantId = 30L;
14   - TenantContextHolder.setTenantId(tenantId);
15   - // 准备参数
16   - TenantRedisKeyDefine define = new TenantRedisKeyDefine("", "user:%d:%d", RedisKeyDefine.KeyTypeEnum.HASH,
17   - Object.class, RedisKeyDefine.TimeoutTypeEnum.FIXED);
18   - Long userId = 10L;
19   - Integer userType = 1;
20   -
21   - // 调用
22   - String key = define.formatKey(userId, userType);
23   - // 断言
24   - assertEquals("user:10:1:30", key);
25   - }
26   -
27   -}
yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml
... ... @@ -34,16 +34,12 @@
34 34 <!-- 三方云服务相关 -->
35 35 <dependency>
36 36 <groupId>com.github.binarywang</groupId>
37   -<!-- <artifactId>weixin-java-mp</artifactId>-->
38 37 <artifactId>wx-java-mp-spring-boot-starter</artifactId>
39   - <version>4.5.0</version>
40 38 </dependency>
41 39 <dependency>
42 40 <groupId>com.github.binarywang</groupId>
43 41 <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
44   - <version>4.5.0</version>
45 42 </dependency>
46   - <!-- TODO 芋艿:清理 -->
47 43 </dependencies>
48 44  
49 45 </project>
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/config/YudaoCaptchaConfiguration.java
1 1 package cn.iocoder.yudao.framework.captcha.config;
2 2  
3   -import cn.hutool.core.util.ClassUtil;
4   -import cn.iocoder.yudao.framework.captcha.core.enums.CaptchaRedisKeyConstants;
5 3 import cn.iocoder.yudao.framework.captcha.core.service.RedisCaptchaServiceImpl;
6 4 import com.xingyuv.captcha.properties.AjCaptchaProperties;
7 5 import com.xingyuv.captcha.service.CaptchaCacheService;
... ... @@ -15,12 +13,6 @@ import javax.annotation.Resource;
15 13 @AutoConfiguration
16 14 public class YudaoCaptchaConfiguration {
17 15  
18   - static {
19   - // 手动加载 Lock4jRedisKeyConstants 类,因为它不会被使用到
20   - // 如果不加载,会导致 Redis 监控,看到它的 Redis Key 枚举
21   - ClassUtil.loadClass(CaptchaRedisKeyConstants.class.getName());
22   - }
23   -
24 16 @Resource
25 17 private StringRedisTemplate stringRedisTemplate;
26 18  
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/enums/CaptchaRedisKeyConstants.java
1 1 package cn.iocoder.yudao.framework.captcha.core.enums;
2 2  
3   -import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
4   -import com.xingyuv.captcha.model.vo.PointVO;
5   -
6   -import java.time.Duration;
7   -
8   -import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
9   -
10 3 /**
11 4 * 验证码 Redis Key 枚举类
12 5 *
... ... @@ -14,12 +7,22 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S
14 7 */
15 8 public interface CaptchaRedisKeyConstants {
16 9  
17   - RedisKeyDefine AJ_CAPTCHA_REQ_LIMIT = new RedisKeyDefine("验证码的请求限流",
18   - "AJ.CAPTCHA.REQ.LIMIT-%s-%s",
19   - STRING, Integer.class, Duration.ofSeconds(60)); // 例如说:验证失败 5 次,get 接口锁定
  10 + /**
  11 + * 验证码的请求限流
  12 + *
  13 + * KEY 格式:AJ.CAPTCHA.REQ.LIMIT-%s-%s
  14 + * VALUE 数据类型:String // 例如说:验证失败 5 次,get 接口锁定
  15 + * 过期时间:60 秒
  16 + */
  17 + String AJ_CAPTCHA_REQ_LIMIT = "AJ.CAPTCHA.REQ.LIMIT-%s-%s";
20 18  
21   - RedisKeyDefine AJ_CAPTCHA_RUNNING = new RedisKeyDefine("验证码的坐标",
22   - "RUNNING:CAPTCHA:%s", // AbstractCaptchaService.REDIS_CAPTCHA_KEY
23   - STRING, PointVO.class, Duration.ofSeconds(120)); // {"secretKey":"PP1w2Frr2KEejD2m","x":162,"y":5}
  19 + /**
  20 + * 验证码的坐标
  21 + *
  22 + * KEY 格式:RUNNING:CAPTCHA:%s // AbstractCaptchaService.REDIS_CAPTCHA_KEY
  23 + * VALUE 数据类型:String // PointVO.class {"secretKey":"PP1w2Frr2KEejD2m","x":162,"y":5}
  24 + * 过期时间:120 秒
  25 + */
  26 + String AJ_CAPTCHA_RUNNING = "RUNNING:CAPTCHA:%s";
24 27  
25 28 }
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
... ... @@ -64,9 +64,8 @@
64 64  
65 65 <dependency>
66 66 <groupId>com.github.yulichang</groupId>
67   - <artifactId>mybatis-plus-join-boot-starter</artifactId>
  67 + <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
68 68 </dependency>
69   -
70 69 </dependencies>
71 70  
72 71 </project>
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
... ... @@ -6,10 +6,10 @@ import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
6 6 import com.baomidou.mybatisplus.core.conditions.Wrapper;
7 7 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
8 8 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  9 +import com.baomidou.mybatisplus.core.mapper.BaseMapper;
9 10 import com.baomidou.mybatisplus.core.metadata.IPage;
10 11 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
11 12 import com.baomidou.mybatisplus.extension.toolkit.Db;
12   -import com.github.yulichang.base.MPJBaseMapper;
13 13 import org.apache.ibatis.annotations.Param;
14 14  
15 15 import java.util.Collection;
... ... @@ -17,10 +17,8 @@ import java.util.List;
17 17  
18 18 /**
19 19 * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力
20   - * <p>
21   - * 为什么继承 MPJBaseMapper 接口?支持 MyBatis Plus 多表 Join 的能力。
22 20 */
23   -public interface BaseMapperX<T> extends MPJBaseMapper<T> {
  21 +public interface BaseMapperX<T> extends BaseMapper<T> {
24 22  
25 23 default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
26 24 // MyBatis Plus 查询
... ... @@ -46,18 +44,6 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
46 44 return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
47 45 }
48 46  
49   - default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
50   - SFunction<T, ?> field3, Object value3) {
51   - return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
52   - .eq(field3, value3));
53   - }
54   -
55   - default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
56   - SFunction<T, ?> field3, Object value3, SFunction<T, ?> field4, Object value4) {
57   - return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
58   - .eq(field3, value3).eq(field4, value4));
59   - }
60   -
61 47 default Long selectCount() {
62 48 return selectCount(new QueryWrapper<T>());
63 49 }
... ... @@ -117,11 +103,6 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
117 103 update(update, new QueryWrapper<>());
118 104 }
119 105  
120   - /**
121   - * 根据ID 批量更新,适合大量数据更新
122   - *
123   - * @param entities 实体们
124   - */
125 106 default void updateBatch(Collection<T> entities) {
126 107 Db.updateBatchById(entities);
127 108 }
... ... @@ -130,13 +111,8 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
130 111 Db.updateBatchById(entities, size);
131 112 }
132 113  
133   - /**
134   - * 批量修改插入, 会根据实体的主键是否为空,更新还是修改。默认为 1000
135   - *
136   - * @param entities 实体们
137   - */
138   - default void saveOrUpdateBatch(Collection<T> entities){
139   - Db.saveOrUpdateBatch(entities);
  114 + default void saveOrUpdateBatch(Collection<T> collection) {
  115 + Db.saveOrUpdateBatch(collection);
140 116 }
141 117  
142 118 }
... ...
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/idempotent/core/redis/IdempotentRedisDAO.java
1 1 package cn.iocoder.yudao.framework.idempotent.core.redis;
2 2  
3   -import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
4 3 import lombok.AllArgsConstructor;
5 4 import org.springframework.data.redis.core.StringRedisTemplate;
6 5  
7 6 import java.util.concurrent.TimeUnit;
8 7  
9   -import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
10   -
11 8 /**
12 9 * 幂等 Redis DAO
13 10 *
... ... @@ -16,9 +13,14 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S
16 13 @AllArgsConstructor
17 14 public class IdempotentRedisDAO {
18 15  
19   - private static final RedisKeyDefine IDEMPOTENT = new RedisKeyDefine("幂等操作",
20   - "idempotent:%s", // 参数为 uuid
21   - STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
  16 + /**
  17 + * 幂等操作
  18 + *
  19 + * KEY 格式:idempotent:%s // 参数为 uuid
  20 + * VALUE 格式:String
  21 + * 过期时间:不固定
  22 + */
  23 + private static final String IDEMPOTENT = "idempotent:%s";
22 24  
23 25 private final StringRedisTemplate redisTemplate;
24 26  
... ... @@ -28,7 +30,7 @@ public class IdempotentRedisDAO {
28 30 }
29 31  
30 32 private static String formatKey(String key) {
31   - return String.format(IDEMPOTENT.getKeyTemplate(), key);
  33 + return String.format(IDEMPOTENT, key);
32 34 }
33 35  
34 36 }
... ...
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/lock4j/config/YudaoLock4jConfiguration.java
1 1 package cn.iocoder.yudao.framework.lock4j.config;
2 2  
3   -import cn.hutool.core.util.ClassUtil;
4   -import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;
5 3 import cn.iocoder.yudao.framework.lock4j.core.DefaultLockFailureStrategy;
6   -import cn.iocoder.yudao.framework.lock4j.core.Lock4jRedisKeyConstants;
  4 +import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;
7 5 import org.springframework.boot.autoconfigure.AutoConfiguration;
8 6 import org.springframework.context.annotation.Bean;
9 7  
10 8 @AutoConfiguration(before = LockAutoConfiguration.class)
11 9 public class YudaoLock4jConfiguration {
12 10  
13   - static {
14   - // 手动加载 Lock4jRedisKeyConstants 类,因为它不会被使用到
15   - // 如果不加载,会导致 Redis 监控,看到它的 Redis Key 枚举
16   - ClassUtil.loadClass(Lock4jRedisKeyConstants.class.getName());
17   - }
18   -
19 11 @Bean
20 12 public DefaultLockFailureStrategy lockFailureStrategy() {
21 13 return new DefaultLockFailureStrategy();
... ...
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/lock4j/core/Lock4jRedisKeyConstants.java
1 1 package cn.iocoder.yudao.framework.lock4j.core;
2 2  
3   -import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
4   -import org.redisson.api.RLock;
5   -
6   -import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH;
7   -
8 3 /**
9 4 * Lock4j Redis Key 枚举类
10 5 *
... ... @@ -12,8 +7,13 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.H
12 7 */
13 8 public interface Lock4jRedisKeyConstants {
14 9  
15   - RedisKeyDefine LOCK4J = new RedisKeyDefine("分布式锁",
16   - "lock4j:%s", // 参数来自 DefaultLockKeyBuilder 类
17   - HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
  10 + /**
  11 + * 分布式锁
  12 + *
  13 + * KEY 格式:lock4j:%s // 参数来自 DefaultLockKeyBuilder 类
  14 + * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构
  15 + * 过期时间:不固定
  16 + */
  17 + String LOCK4J = "lock4j:%s";
18 18  
19 19 }
... ...
yudao-framework/yudao-spring-boot-starter-redis/pom.xml
... ... @@ -37,6 +37,11 @@
37 37 <artifactId>netty-all</artifactId>
38 38 </dependency>
39 39  
  40 +
  41 + <dependency>
  42 + <groupId>com.fasterxml.jackson.datatype</groupId>
  43 + <artifactId>jackson-datatype-jsr310</artifactId>
  44 + </dependency>
40 45 </dependencies>
41 46  
42 47 </project>
... ...
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.redis.config;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
3 5 import org.springframework.boot.autoconfigure.AutoConfiguration;
4 6 import org.springframework.boot.autoconfigure.cache.CacheProperties;
5 7 import org.springframework.boot.context.properties.EnableConfigurationProperties;
... ... @@ -7,8 +9,15 @@ import org.springframework.cache.annotation.EnableCaching;
7 9 import org.springframework.context.annotation.Bean;
8 10 import org.springframework.context.annotation.Primary;
9 11 import org.springframework.data.redis.cache.RedisCacheConfiguration;
  12 +import org.springframework.data.redis.cache.RedisCacheManager;
  13 +import org.springframework.data.redis.cache.RedisCacheWriter;
  14 +import org.springframework.data.redis.connection.RedisConnectionFactory;
  15 +import org.springframework.data.redis.core.RedisTemplate;
10 16 import org.springframework.data.redis.serializer.RedisSerializationContext;
11   -import org.springframework.data.redis.serializer.RedisSerializer;
  17 +
  18 +import java.util.Objects;
  19 +
  20 +import static cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration.buildRedisSerializer;
12 21  
13 22 /**
14 23 * Cache 配置类,基于 Redis 实现
... ... @@ -20,15 +29,19 @@ public class YudaoCacheAutoConfiguration {
20 29  
21 30 /**
22 31 * RedisCacheConfiguration Bean
23   - *
  32 + * <p>
24 33 * 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法
25 34 */
26 35 @Bean
27 36 @Primary
28 37 public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
29   - // 设置使用 JSON 序列化方式
30 38 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
31   - config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
  39 + // 设置使用 : 单冒号,而不是双 :: 冒号,避免 Redis Desktop Manager 多余空格
  40 + // 详细可见 https://blog.csdn.net/chuixue24/article/details/103928965 博客
  41 + config = config.computePrefixWith(cacheName -> cacheName + StrUtil.COLON);
  42 + // 设置使用 JSON 序列化方式
  43 + config = config.serializeValuesWith(
  44 + RedisSerializationContext.SerializationPair.fromSerializer(buildRedisSerializer()));
32 45  
33 46 // 设置 CacheProperties.Redis 的属性
34 47 CacheProperties.Redis redisProperties = cacheProperties.getRedis();
... ... @@ -47,4 +60,14 @@ public class YudaoCacheAutoConfiguration {
47 60 return config;
48 61 }
49 62  
  63 + @Bean
  64 + public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
  65 + RedisCacheConfiguration redisCacheConfiguration) {
  66 + // 创建 RedisCacheWriter 对象
  67 + RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
  68 + RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
  69 + // 创建 TenantRedisCacheManager 对象
  70 + return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
  71 + }
  72 +
50 73 }
... ...
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoRedisAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.redis.config;
2 2  
  3 +import cn.hutool.core.util.ReflectUtil;
  4 +import com.fasterxml.jackson.databind.ObjectMapper;
  5 +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
3 6 import org.springframework.boot.autoconfigure.AutoConfiguration;
4 7 import org.springframework.context.annotation.Bean;
5 8 import org.springframework.data.redis.connection.RedisConnectionFactory;
... ... @@ -25,9 +28,17 @@ public class YudaoRedisAutoConfiguration {
25 28 template.setKeySerializer(RedisSerializer.string());
26 29 template.setHashKeySerializer(RedisSerializer.string());
27 30 // 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
28   - template.setValueSerializer(RedisSerializer.json());
29   - template.setHashValueSerializer(RedisSerializer.json());
  31 + template.setValueSerializer(buildRedisSerializer());
  32 + template.setHashValueSerializer(buildRedisSerializer());
30 33 return template;
31 34 }
32 35  
  36 + public static RedisSerializer<?> buildRedisSerializer() {
  37 + RedisSerializer<Object> json = RedisSerializer.json();
  38 + // 解决 LocalDateTime 的序列化
  39 + ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
  40 + objectMapper.registerModules(new JavaTimeModule());
  41 + return json;
  42 + }
  43 +
33 44 }
... ...
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/RedisKeyDefine.java deleted
1   -package cn.iocoder.yudao.framework.redis.core;
2   -
3   -import com.fasterxml.jackson.annotation.JsonValue;
4   -import lombok.AllArgsConstructor;
5   -import lombok.Data;
6   -import lombok.Getter;
7   -
8   -import java.time.Duration;
9   -
10   -/**
11   - * Redis Key 定义类
12   - *
13   - * @author 芋道源码
14   - */
15   -@Data
16   -public class RedisKeyDefine {
17   -
18   - @Getter
19   - @AllArgsConstructor
20   - public enum KeyTypeEnum {
21   -
22   - STRING("String"),
23   - LIST("List"),
24   - HASH("Hash"),
25   - SET("Set"),
26   - ZSET("Sorted Set"),
27   - STREAM("Stream"),
28   - PUBSUB("Pub/Sub");
29   -
30   - /**
31   - * 类型
32   - */
33   - @JsonValue
34   - private final String type;
35   -
36   - }
37   -
38   - @Getter
39   - @AllArgsConstructor
40   - public enum TimeoutTypeEnum {
41   -
42   - FOREVER(1), // 永不超时
43   - DYNAMIC(2), // 动态超时
44   - FIXED(3); // 固定超时
45   -
46   - /**
47   - * 类型
48   - */
49   - @JsonValue
50   - private final Integer type;
51   -
52   - }
53   -
54   - /**
55   - * Key 模板
56   - */
57   - private final String keyTemplate;
58   - /**
59   - * Key 类型的枚举
60   - */
61   - private final KeyTypeEnum keyType;
62   - /**
63   - * Value 类型
64   - *
65   - * 如果是使用分布式锁,设置为 {@link java.util.concurrent.locks.Lock} 类型
66   - */
67   - private final Class<?> valueType;
68   - /**
69   - * 超时类型
70   - */
71   - private final TimeoutTypeEnum timeoutType;
72   - /**
73   - * 过期时间
74   - */
75   - private final Duration timeout;
76   - /**
77   - * 备注
78   - */
79   - private final String memo;
80   -
81   - private RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType,
82   - TimeoutTypeEnum timeoutType, Duration timeout) {
83   - this.memo = memo;
84   - this.keyTemplate = keyTemplate;
85   - this.keyType = keyType;
86   - this.valueType = valueType;
87   - this.timeout = timeout;
88   - this.timeoutType = timeoutType;
89   - // 添加注册表
90   - RedisKeyRegistry.add(this);
91   - }
92   -
93   - public RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, Duration timeout) {
94   - this(memo, keyTemplate, keyType, valueType, TimeoutTypeEnum.FIXED, timeout);
95   - }
96   -
97   - public RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, TimeoutTypeEnum timeoutType) {
98   - this(memo, keyTemplate, keyType, valueType, timeoutType, Duration.ZERO);
99   - }
100   -
101   - /**
102   - * 格式化 Key
103   - *
104   - * 注意,内部采用 {@link String#format(String, Object...)} 实现
105   - *
106   - * @param args 格式化的参数
107   - * @return Key
108   - */
109   - public String formatKey(Object... args) {
110   - return String.format(keyTemplate, args);
111   - }
112   -
113   -}
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/RedisKeyRegistry.java deleted
1   -package cn.iocoder.yudao.framework.redis.core;
2   -
3   -import java.util.ArrayList;
4   -import java.util.List;
5   -
6   -/**
7   - * {@link RedisKeyDefine} 注册表
8   - */
9   -public class RedisKeyRegistry {
10   -
11   - /**
12   - * Redis RedisKeyDefine 数组
13   - */
14   - private static final List<RedisKeyDefine> DEFINES = new ArrayList<>();
15   -
16   - public static void add(RedisKeyDefine define) {
17   - DEFINES.add(define);
18   - }
19   -
20   - public static List<RedisKeyDefine> list() {
21   - return DEFINES;
22   - }
23   -
24   - public static int size() {
25   - return DEFINES.size();
26   - }
27   -
28   -}
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/TimeoutRedisCacheManager.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.redis.core;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import org.springframework.boot.convert.DurationStyle;
  5 +import org.springframework.cache.annotation.Cacheable;
  6 +import org.springframework.data.redis.cache.RedisCache;
  7 +import org.springframework.data.redis.cache.RedisCacheConfiguration;
  8 +import org.springframework.data.redis.cache.RedisCacheManager;
  9 +import org.springframework.data.redis.cache.RedisCacheWriter;
  10 +
  11 +import java.time.Duration;
  12 +import java.time.temporal.ChronoUnit;
  13 +
  14 +/**
  15 + * 支持自定义过期时间的 {@link RedisCacheManager} 实现类
  16 + *
  17 + * 在 {@link Cacheable#cacheNames()} 格式为 "key#ttl" 时,# 后面的 ttl 为过期时间,单位为秒
  18 + *
  19 + * @author 芋道源码
  20 + */
  21 +public class TimeoutRedisCacheManager extends RedisCacheManager {
  22 +
  23 + private static final String SPLIT = "#";
  24 +
  25 + public TimeoutRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
  26 + super(cacheWriter, defaultCacheConfiguration);
  27 + }
  28 +
  29 + @Override
  30 + protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
  31 + if (StrUtil.isEmpty(name)) {
  32 + return super.createRedisCache(name, cacheConfig);
  33 + }
  34 + // 如果使用 # 分隔,大小不为 2,则说明不使用自定义过期时间
  35 + String[] names = StrUtil.splitToArray(name, SPLIT);
  36 + if (names.length != 2) {
  37 + return super.createRedisCache(name, cacheConfig);
  38 + }
  39 +
  40 + // 核心:通过修改 cacheConfig 的过期时间,实现自定义过期时间
  41 + if (cacheConfig != null) {
  42 + // 移除 # 后面的 : 以及后面的内容,避免影响解析
  43 + names[1] = StrUtil.subBefore(names[1], StrUtil.COLON, false);
  44 + // 解析时间
  45 + Duration duration = DurationStyle.detectAndParse(names[1], ChronoUnit.SECONDS);
  46 + cacheConfig = cacheConfig.entryTtl(duration);
  47 + }
  48 + return super.createRedisCache(names[0], cacheConfig);
  49 + }
  50 +
  51 +}
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
... ... @@ -60,7 +60,7 @@ public class YudaoWebSecurityConfigurerAdapter {
60 60 /**
61 61 * 自定义的权限映射 Bean 们
62 62 *
63   - * @see #configure(HttpSecurity)
  63 + * @see #filterChain(HttpSecurity)
64 64 */
65 65 @Resource
66 66 private List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers;
... ... @@ -79,7 +79,7 @@ public class YudaoWebSecurityConfigurerAdapter {
79 79  
80 80 /**
81 81 * 配置 URL 的安全配置
82   - * <p>
  82 + *
83 83 * anyRequest | 匹配所有请求路径
84 84 * access | SpringEl表达式结果为true时可以访问
85 85 * anonymous | 匿名可以访问
... ... @@ -141,7 +141,6 @@ public class YudaoWebSecurityConfigurerAdapter {
141 141  
142 142 // 添加 Token Filter
143 143 httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
144   -
145 144 return httpSecurity.build();
146 145 }
147 146  
... ...
yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbAndRedisUnitTest.java
... ... @@ -40,6 +40,7 @@ public class BaseDbAndRedisUnitTest {
40 40  
41 41 // Redis 配置类
42 42 RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
  43 +// RedisAutoConfiguration.class, // Spring Redis 自动配置类
43 44 YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
44 45 RedissonAutoConfiguration.class, // Redisson 自动高配置类
45 46 })
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java
... ... @@ -52,7 +52,8 @@ public class YudaoSwaggerAutoConfiguration {
52 52 // 接口信息
53 53 .info(buildInfo(properties))
54 54 // 接口安全配置
55   - .components(new Components().securitySchemes(securitySchemas));
  55 + .components(new Components().securitySchemes(securitySchemas))
  56 + .addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION));
56 57 securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key)));
57 58 return openAPI;
58 59 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
... ... @@ -47,7 +47,7 @@ public class BpmTaskController {
47 47 @Parameter(name = "processInstanceId", description = "流程实例的编号", required = true)
48 48 @PreAuthorize("@ss.hasPermission('bpm:task:query')")
49 49 public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId(
50   - @RequestParam("processInstanceId") String processInstanceId) {
  50 + @RequestParam("processInstanceId") String processInstanceId) {
51 51 return success(taskService.getTaskListByProcessInstanceId(processInstanceId));
52 52 }
53 53  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
... ... @@ -73,11 +73,13 @@ public interface BpmTaskConvert {
73 73  
74 74 BpmTaskDonePageItemRespVO convert2(HistoricTaskInstance bean);
75 75  
76   - @Mappings({@Mapping(source = "processInstance.id", target = "id"),
77   - @Mapping(source = "processInstance.name", target = "name"),
78   - @Mapping(source = "processInstance.startUserId", target = "startUserId"),
79   - @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
80   - @Mapping(source = "startUser.nickname", target = "startUserNickname")})
  76 + @Mappings({
  77 + @Mapping(source = "processInstance.id", target = "id"),
  78 + @Mapping(source = "processInstance.name", target = "name"),
  79 + @Mapping(source = "processInstance.startUserId", target = "startUserId"),
  80 + @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
  81 + @Mapping(source = "startUser.nickname", target = "startUserNickname")
  82 + })
81 83 BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser);
82 84  
83 85 default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks,
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java
... ... @@ -15,8 +15,8 @@ import cn.iocoder.yudao.module.bpm.convert.definition.BpmTaskAssignRuleConvert;
15 15 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
16 16 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
17 17 import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper;
18   -import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
19 18 import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants;
  19 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
20 20 import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
21 21 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
22 22 import cn.iocoder.yudao.module.system.api.dept.PostApi;
... ... @@ -89,7 +89,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
89 89  
90 90 @Override
91 91 public List<BpmTaskAssignRuleDO> getTaskAssignRuleListByProcessDefinitionId(String processDefinitionId,
92   - String taskDefinitionKey) {
  92 + String taskDefinitionKey) {
93 93 return taskRuleMapper.selectListByProcessDefinitionId(processDefinitionId, taskDefinitionKey);
94 94 }
95 95  
... ... @@ -128,14 +128,14 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
128 128 validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
129 129 // 校验是否已经配置
130 130 BpmTaskAssignRuleDO existRule =
131   - taskRuleMapper.selectListByModelIdAndTaskDefinitionKey(reqVO.getModelId(), reqVO.getTaskDefinitionKey());
  131 + taskRuleMapper.selectListByModelIdAndTaskDefinitionKey(reqVO.getModelId(), reqVO.getTaskDefinitionKey());
132 132 if (existRule != null) {
133 133 throw exception(TASK_ASSIGN_RULE_EXISTS, reqVO.getModelId(), reqVO.getTaskDefinitionKey());
134 134 }
135 135  
136 136 // 存储
137 137 BpmTaskAssignRuleDO rule = BpmTaskAssignRuleConvert.INSTANCE.convert(reqVO)
138   - .setProcessDefinitionId(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL); // 只有流程模型,才允许新建
  138 + .setProcessDefinitionId(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL); // 只有流程模型,才允许新建
139 139 taskRuleMapper.insert(rule);
140 140 return rule.getId();
141 141 }
... ... @@ -169,14 +169,14 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
169 169  
170 170 // 遍历,匹配对应的规则
171 171 Map<String, BpmTaskAssignRuleRespVO> processInstanceRuleMap =
172   - CollectionUtils.convertMap(processInstanceRules, BpmTaskAssignRuleRespVO::getTaskDefinitionKey);
  172 + CollectionUtils.convertMap(processInstanceRules, BpmTaskAssignRuleRespVO::getTaskDefinitionKey);
173 173 for (BpmTaskAssignRuleRespVO modelRule : modelRules) {
174 174 BpmTaskAssignRuleRespVO processInstanceRule = processInstanceRuleMap.get(modelRule.getTaskDefinitionKey());
175 175 if (processInstanceRule == null) {
176 176 return false;
177 177 }
178 178 if (!ObjectUtil.equals(modelRule.getType(), processInstanceRule.getType()) || !ObjectUtil.equal(
179   - modelRule.getOptions(), processInstanceRule.getOptions())) {
  179 + modelRule.getOptions(), processInstanceRule.getOptions())) {
180 180 return false;
181 181 }
182 182 }
... ... @@ -192,7 +192,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
192 192 // 开始复制
193 193 List<BpmTaskAssignRuleDO> newRules = BpmTaskAssignRuleConvert.INSTANCE.convertList2(rules);
194 194 newRules.forEach(rule -> rule.setProcessDefinitionId(toProcessDefinitionId).setId(null).setCreateTime(null)
195   - .setUpdateTime(null));
  195 + .setUpdateTime(null));
196 196 taskRuleMapper.insertBatch(newRules);
197 197 }
198 198  
... ... @@ -215,7 +215,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
215 215 if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.ROLE.getType())) {
216 216 roleApi.validRoleList(options);
217 217 } else if (ObjectUtils.equalsAny(type, BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(),
218   - BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType())) {
  218 + BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType())) {
219 219 deptApi.validateDeptList(options);
220 220 } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.POST.getType())) {
221 221 postApi.validPostList(options);
... ... @@ -225,7 +225,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
225 225 userGroupService.validUserGroups(options);
226 226 } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.SCRIPT.getType())) {
227 227 dictDataApi.validateDictDataList(DictTypeConstants.TASK_ASSIGN_SCRIPT,
228   - CollectionUtils.convertSet(options, String::valueOf));
  228 + CollectionUtils.convertSet(options, String::valueOf));
229 229 } else {
230 230 throw new IllegalArgumentException(format("未知的规则类型({})", type));
231 231 }
... ... @@ -298,7 +298,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
298 298 }
299 299  
300 300 private Set<Long> calculateTaskCandidateUsersByPost(BpmTaskAssignRuleDO rule) {
301   - List<AdminUserRespDTO> users = adminUserApi.getUsersByPostIds(rule.getOptions());
  301 + List<AdminUserRespDTO> users = adminUserApi.getUserListByPostIds(rule.getOptions());
302 302 return convertSet(users, AdminUserRespDTO::getId);
303 303 }
304 304  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
1   -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.time.LocalDateTime; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource private BpmMessageService messageService; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult<BpmProcessInstanceExtDO> pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List<String> processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null) { dept = deptApi.getDept(startUser.getDeptId()); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String)event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 补全流程实例的拓展表 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) .setFormVariables(variables)); return instance.getId(); } }
2 1 \ No newline at end of file
  2 +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.time.LocalDateTime; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource private BpmMessageService messageService; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult<BpmProcessInstanceExtDO> pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List<String> processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null) { dept = deptApi.getDept(startUser.getDeptId()); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String)event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 补全流程实例的拓展表 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) .setFormVariables(variables)); return instance.getId(); } }
3 3 \ No newline at end of file
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/script/impl/BpmTaskAssignLeaderX2ScriptTest.java
... ... @@ -40,6 +40,8 @@ public class BpmTaskAssignLeaderX2ScriptTest extends BaseMockitoUnitTest {
40 40 // mock 方法(startUser)
41 41 AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptId(10L));
42 42 when(adminUserApi.getUser(eq(1L))).thenReturn(startUser);
  43 + // mock 方法(getStartUserDept)没有部门
  44 + when(deptApi.getDept(eq(10L))).thenReturn(null);
43 45  
44 46 // 调用
45 47 Set<Long> result = script.calculateTaskCandidateUsers(execution);
... ... @@ -56,7 +58,9 @@ public class BpmTaskAssignLeaderX2ScriptTest extends BaseMockitoUnitTest {
56 58 when(adminUserApi.getUser(eq(1L))).thenReturn(startUser);
57 59 DeptRespDTO startUserDept = randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(100L)
58 60 .setLeaderUserId(20L));
  61 + // mock 方法(getDept)
59 62 when(deptApi.getDept(eq(10L))).thenReturn(startUserDept);
  63 + when(deptApi.getDept(eq(100L))).thenReturn(null);
60 64  
61 65 // 调用
62 66 Set<Long> result = script.calculateTaskCandidateUsers(execution);
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImplTest.java
... ... @@ -120,7 +120,7 @@ public class BpmTaskAssignRuleServiceImplTest extends BaseDbUnitTest {
120 120 // mock 方法
121 121 List<AdminUserRespDTO> users = CollectionUtils.convertList(asSet(11L, 22L),
122 122 id -> new AdminUserRespDTO().setId(id));
123   - when(adminUserApi.getUsersByPostIds(eq(rule.getOptions()))).thenReturn(users);
  123 + when(adminUserApi.getUserListByPostIds(eq(rule.getOptions()))).thenReturn(users);
124 124 mockGetUserMap(asSet(11L, 22L));
125 125  
126 126 // 调用
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupServiceTest.java
... ... @@ -23,10 +23,10 @@ import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgn
23 23 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS;
24 24  
25 25 /**
26   -* {@link BpmUserGroupServiceImpl} 的单元测试类
27   -*
28   -* @author 芋道源码
29   -*/
  26 + * {@link BpmUserGroupServiceImpl} 的单元测试类
  27 + *
  28 + * @author 芋道源码
  29 + */
30 30 @Import(BpmUserGroupServiceImpl.class)
31 31 public class BpmUserGroupServiceTest extends BaseDbUnitTest {
32 32  
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/redis/RedisController.http
... ... @@ -2,8 +2,3 @@
2 2 GET {{baseUrl}}/infra/redis/get-monitor-info
3 3 Authorization: Bearer {{token}}
4 4 tenant-id: {{adminTenentId}}
5   -
6   -### 请求 /infra/redis/get-key-list 接口 => 成功
7   -GET {{baseUrl}}/infra/redis/get-key-list
8   -Authorization: Bearer {{token}}
9   -tenant-id: {{adminTenentId}}
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/redis/RedisController.java
1 1 package cn.iocoder.yudao.module.infra.controller.admin.redis;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.util.StrUtil;
5 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6   -import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
7   -import cn.iocoder.yudao.framework.redis.core.RedisKeyRegistry;
8   -import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisKeyDefineRespVO;
9   -import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisKeyValueRespVO;
10 4 import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisMonitorRespVO;
11 5 import cn.iocoder.yudao.module.infra.convert.redis.RedisConvert;
12   -import io.swagger.v3.oas.annotations.tags.Tag;
13   -import io.swagger.v3.oas.annotations.Parameter;
14 6 import io.swagger.v3.oas.annotations.Operation;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
15 8 import org.springframework.data.redis.connection.RedisServerCommands;
16   -import org.springframework.data.redis.core.Cursor;
17 9 import org.springframework.data.redis.core.RedisCallback;
18   -import org.springframework.data.redis.core.ScanOptions;
19 10 import org.springframework.data.redis.core.StringRedisTemplate;
20 11 import org.springframework.security.access.prepost.PreAuthorize;
21   -import org.springframework.web.bind.annotation.*;
  12 +import org.springframework.web.bind.annotation.GetMapping;
  13 +import org.springframework.web.bind.annotation.RequestMapping;
  14 +import org.springframework.web.bind.annotation.RestController;
22 15  
23 16 import javax.annotation.Resource;
24   -import java.util.*;
  17 +import java.util.Properties;
25 18  
26 19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
27 20  
... ... @@ -47,66 +40,4 @@ public class RedisController {
47 40 return success(RedisConvert.INSTANCE.build(info, dbSize, commandStats));
48 41 }
49 42  
50   - @GetMapping("/get-key-define-list")
51   - @Operation(summary = "获得 Redis Key 模板列表")
52   - @PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
53   - public CommonResult<List<RedisKeyDefineRespVO>> getKeyDefineList() {
54   - List<RedisKeyDefine> keyDefines = RedisKeyRegistry.list();
55   - return success(RedisConvert.INSTANCE.convertList(keyDefines));
56   - }
57   -
58   - @GetMapping("/get-key-list")
59   - @Operation(summary = "获得 Redis keys 键名列表")
60   - @Parameter(name = "keyTemplate", description = "Redis Key 定义", example = "true")
61   - @PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
62   - public CommonResult<Set<String>> getKeyDefineList(@RequestParam("keyTemplate") String keyTemplate) {
63   - return success(getKeyDefineList0(keyTemplate));
64   - }
65   -
66   - private Set<String> getKeyDefineList0(String keyTemplate) {
67   - // key 格式化
68   - String key = StrUtil.replace(keyTemplate, "%[s|c|b|d|x|o|f|a|e|g]", parameter -> "*");
69   - // scan 扫描 key
70   - Set<String> keys = new LinkedHashSet<>();
71   - stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> {
72   - try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(key).count(100).build())) {
73   - cursor.forEachRemaining(value -> keys.add(StrUtil.utf8Str(value)));
74   - } catch (Exception e) {
75   - throw new RuntimeException(e);
76   - }
77   - return keys;
78   - });
79   - return keys;
80   - }
81   -
82   - @GetMapping("/get-key-value")
83   - @Operation(summary = "获得 Redis key 内容")
84   - @Parameter(name = "key", description = "Redis Key", example = "oauth2_access_token:233")
85   - @PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
86   - public CommonResult<RedisKeyValueRespVO> getKeyValue(@RequestParam("key") String key) {
87   - String value = stringRedisTemplate.opsForValue().get(key);
88   - return success(new RedisKeyValueRespVO(key, value));
89   - }
90   -
91   - @DeleteMapping("/delete-key")
92   - @Operation(summary = "删除 Redis Key")
93   - @Parameter(name = "key", description = "Redis Key", example = "oauth2_access_token:233")
94   - @PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
95   - public CommonResult<Boolean> deleteKey(@RequestParam("key") String key) {
96   - stringRedisTemplate.delete(key);
97   - return success(Boolean.TRUE);
98   - }
99   -
100   - @DeleteMapping("/delete-keys")
101   - @Operation(summary = "删除 Redis Key 根据模板")
102   - @Parameter(name = "keyTemplate", description = "Redis Key 定义", example = "true")
103   - @PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
104   - public CommonResult<Boolean> deleteKeys(@RequestParam("keyTemplate") String keyTemplate) {
105   - Set<String> keys = getKeyDefineList0(keyTemplate);
106   - if (CollUtil.isNotEmpty(keys)) {
107   - stringRedisTemplate.delete(keys);
108   - }
109   - return success(Boolean.TRUE);
110   - }
111   -
112 43 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/redis/vo/RedisKeyDefineRespVO.java deleted
1   -package cn.iocoder.yudao.module.infra.controller.admin.redis.vo;
2   -
3   -import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
4   -import io.swagger.v3.oas.annotations.media.Schema;
5   -import lombok.AllArgsConstructor;
6   -import lombok.Builder;
7   -import lombok.Data;
8   -
9   -import java.time.Duration;
10   -
11   -@Schema(description = "管理后台 - Redis Key 信息 Response VO")
12   -@Data
13   -@Builder
14   -@AllArgsConstructor
15   -public class RedisKeyDefineRespVO {
16   -
17   - @Schema(description = "Key 模板", requiredMode = Schema.RequiredMode.REQUIRED, example = "login_user:%s")
18   - private String keyTemplate;
19   -
20   - @Schema(description = "Key 类型的枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "String")
21   - private RedisKeyDefine.KeyTypeEnum keyType;
22   -
23   - @Schema(description = "Value 类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "java.lang.String")
24   - private Class<?> valueType;
25   -
26   - @Schema(description = "超时类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
27   - private RedisKeyDefine.TimeoutTypeEnum timeoutType;
28   -
29   - @Schema(description = "过期时间,单位:毫秒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
30   - private Duration timeout;
31   -
32   - @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "啦啦啦啦~")
33   - private String memo;
34   -
35   -}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/redis/vo/RedisKeyValueRespVO.java deleted
1   -package cn.iocoder.yudao.module.infra.controller.admin.redis.vo;
2   -
3   -import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.AllArgsConstructor;
5   -import lombok.Data;
6   -
7   -@Schema(description = "管理后台 - 单个 Redis Key Value Response VO")
8   -@Data
9   -@AllArgsConstructor
10   -public class RedisKeyValueRespVO {
11   -
12   - @Schema(description = "c5f6990767804a928f4bb96ca249febf", requiredMode = Schema.RequiredMode.REQUIRED, example = "String")
13   - private String key;
14   -
15   - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, example = "String")
16   - private String value;
17   -
18   -}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/convert/redis/RedisConvert.java
1 1 package cn.iocoder.yudao.module.infra.convert.redis;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4   -import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
5   -import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisKeyDefineRespVO;
6 4 import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisMonitorRespVO;
7 5 import org.mapstruct.Mapper;
8 6 import org.mapstruct.factory.Mappers;
9 7  
10 8 import java.util.ArrayList;
11   -import java.util.List;
12 9 import java.util.Properties;
13 10  
14 11 @Mapper
... ... @@ -29,6 +26,4 @@ public interface RedisConvert {
29 26 return respVO;
30 27 }
31 28  
32   - List<RedisKeyDefineRespVO> convertList(List<RedisKeyDefine> list);
33   -
34 29 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java
... ... @@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
7 7 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
8 8 import org.apache.ibatis.annotations.Mapper;
  9 +import org.apache.ibatis.annotations.Select;
  10 +
  11 +import java.time.LocalDateTime;
9 12  
10 13 @Mapper
11 14 public interface FileConfigMapper extends BaseMapperX<FileConfigDO> {
... ... @@ -18,4 +21,7 @@ public interface FileConfigMapper extends BaseMapperX&lt;FileConfigDO&gt; {
18 21 .orderByDesc(FileConfigDO::getId));
19 22 }
20 23  
  24 + @Select("SELECT COUNT(*) FROM infra_file_config WHERE update_time > #{maxUpdateTime}")
  25 + Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
  26 +
21 27 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.infra.mq.consumer.file;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage;
5   -import cn.iocoder.yudao.module.infra.service.file.FileConfigService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link FileConfigRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class FileConfigRefreshConsumer extends AbstractChannelMessageListener<FileConfigRefreshMessage> {
19   -
20   - @Resource
21   - private FileConfigService fileConfigService;
22   -
23   - @Override
24   - public void onMessage(FileConfigRefreshMessage message) {
25   - log.info("[onMessage][收到 FileConfig 刷新消息]");
26   - fileConfigService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/package-info.java
1 1 /**
2   - * 占位符,避免缩进
  2 + * 消息队列的消费者
3 3 */
4 4 package cn.iocoder.yudao.module.infra.mq.consumer;
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/message/file/FileConfigRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.infra.mq.message.file;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -
6   -/**
7   - * 文件配置数据刷新 Message
8   - */
9   -@Data
10   -public class FileConfigRefreshMessage extends AbstractChannelMessage {
11   -
12   - @Override
13   - public String getChannel() {
14   - return "infra.file-config.refresh";
15   - }
16   -
17   -}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/message/package-info.java
1 1 /**
2   - * 占位符,避免缩进
  2 + * 消息队列的消息
3 3 */
4 4 package cn.iocoder.yudao.module.infra.mq.message;
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/file/FileConfigProducer.java deleted
1   -package cn.iocoder.yudao.module.infra.mq.producer.file;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
4   -import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage;
5   -import org.springframework.stereotype.Component;
6   -
7   -import javax.annotation.Resource;
8   -
9   -/**
10   - * 文件配置相关消息的 Producer
11   - */
12   -@Component
13   -public class FileConfigProducer {
14   -
15   - @Resource
16   - private RedisMQTemplate redisMQTemplate;
17   -
18   - /**
19   - * 发送 {@link FileConfigRefreshMessage} 消息
20   - */
21   - public void sendFileConfigRefreshMessage() {
22   - FileConfigRefreshMessage message = new FileConfigRefreshMessage();
23   - redisMQTemplate.send(message);
24   - }
25   -
26   -}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/package-info.java
1 1 /**
2   - * 占位符,避免缩进
  2 + * 消息队列的生产者
3 3 */
4 4 package cn.iocoder.yudao.module.infra.mq.producer;
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java
... ... @@ -17,11 +17,6 @@ import javax.validation.Valid;
17 17 public interface FileConfigService {
18 18  
19 19 /**
20   - * 初始化文件客户端
21   - */
22   - void initLocalCache();
23   -
24   - /**
25 20 * 创建文件配置
26 21 *
27 22 * @param createReqVO 创建信息
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
1 1 package cn.iocoder.yudao.module.infra.service.file;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.io.resource.ResourceUtil;
4 5 import cn.hutool.core.util.IdUtil;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 9 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
8 10 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
15 17 import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert;
16 18 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
17 19 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
18   -import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
19 20 import lombok.Getter;
20 21 import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.scheduling.annotation.Scheduled;
21 23 import org.springframework.stereotype.Service;
22 24 import org.springframework.transaction.annotation.Transactional;
23   -import org.springframework.transaction.support.TransactionSynchronization;
24   -import org.springframework.transaction.support.TransactionSynchronizationManager;
25 25 import org.springframework.validation.annotation.Validated;
26 26  
27 27 import javax.annotation.PostConstruct;
28 28 import javax.annotation.Resource;
29 29 import javax.validation.Validator;
  30 +import java.time.LocalDateTime;
30 31 import java.util.List;
31 32 import java.util.Map;
  33 +import java.util.concurrent.TimeUnit;
32 34  
33 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
34 36 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER;
... ... @@ -46,6 +48,12 @@ public class FileConfigServiceImpl implements FileConfigService {
46 48  
47 49 @Resource
48 50 private FileClientFactory fileClientFactory;
  51 +
  52 + /**
  53 + * 文件配置的缓存
  54 + */
  55 + @Getter
  56 + private List<FileConfigDO> fileConfigCache;
49 57 /**
50 58 * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的
51 59 */
... ... @@ -56,12 +64,8 @@ public class FileConfigServiceImpl implements FileConfigService {
56 64 private FileConfigMapper fileConfigMapper;
57 65  
58 66 @Resource
59   - private FileConfigProducer fileConfigProducer;
60   -
61   - @Resource
62 67 private Validator validator;
63 68  
64   - @Override
65 69 @PostConstruct
66 70 public void initLocalCache() {
67 71 // 第一步:查询数据
... ... @@ -76,6 +80,27 @@ public class FileConfigServiceImpl implements FileConfigService {
76 80 masterFileClient = fileClientFactory.getFileClient(config.getId());
77 81 }
78 82 });
  83 + this.fileConfigCache = configs;
  84 + }
  85 +
  86 + /**
  87 + * 通过定时任务轮询,刷新缓存
  88 + *
  89 + * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
  90 + */
  91 + @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
  92 + public void refreshLocalCache() {
  93 + // 情况一:如果缓存里没有数据,则直接刷新缓存
  94 + if (CollUtil.isEmpty(fileConfigCache)) {
  95 + initLocalCache();
  96 + return;
  97 + }
  98 +
  99 + // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
  100 + LocalDateTime maxTime = CollectionUtils.getMaxValue(fileConfigCache, FileConfigDO::getUpdateTime);
  101 + if (fileConfigMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
  102 + initLocalCache();
  103 + }
79 104 }
80 105  
81 106 @Override
... ... @@ -85,9 +110,9 @@ public class FileConfigServiceImpl implements FileConfigService {
85 110 .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig()))
86 111 .setMaster(false); // 默认非 master
87 112 fileConfigMapper.insert(fileConfig);
88   - // 发送刷新配置的消息
89   - fileConfigProducer.sendFileConfigRefreshMessage();
90   - // 返回
  113 +
  114 + // 刷新缓存
  115 + initLocalCache();
91 116 return fileConfig.getId();
92 117 }
93 118  
... ... @@ -99,8 +124,9 @@ public class FileConfigServiceImpl implements FileConfigService {
99 124 FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO)
100 125 .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig()));
101 126 fileConfigMapper.updateById(updateObj);
102   - // 发送刷新配置的消息
103   - fileConfigProducer.sendFileConfigRefreshMessage();
  127 +
  128 + // 刷新缓存
  129 + initLocalCache();
104 130 }
105 131  
106 132 @Override
... ... @@ -112,15 +138,9 @@ public class FileConfigServiceImpl implements FileConfigService {
112 138 fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false));
113 139 // 更新
114 140 fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true));
115   - // 发送刷新配置的消息
116   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
117   -
118   - @Override
119   - public void afterCommit() {
120   - fileConfigProducer.sendFileConfigRefreshMessage();
121   - }
122 141  
123   - });
  142 + // 刷新缓存
  143 + initLocalCache();
124 144 }
125 145  
126 146 private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) {
... ... @@ -143,8 +163,9 @@ public class FileConfigServiceImpl implements FileConfigService {
143 163 }
144 164 // 删除
145 165 fileConfigMapper.deleteById(id);
146   - // 发送刷新配置的消息
147   - fileConfigProducer.sendFileConfigRefreshMessage();
  166 +
  167 + // 刷新缓存
  168 + initLocalCache();
148 169 }
149 170  
150 171 private FileConfigDO validateFileConfigExists(Long id) {
... ...
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java
... ... @@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigP
16 16 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO;
17 17 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
18 18 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
19   -import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
20 19 import lombok.Data;
21 20 import org.junit.jupiter.api.Test;
22 21 import org.springframework.boot.test.mock.mockito.MockBean;
... ... @@ -56,8 +55,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
56 55 private FileConfigMapper fileConfigMapper;
57 56  
58 57 @MockBean
59   - private FileConfigProducer fileConfigProducer;
60   - @MockBean
61 58 private Validator validator;
62 59 @MockBean
63 60 private FileClientFactory fileClientFactory;
... ... @@ -81,6 +78,10 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
81 78 verify(fileClientFactory).createOrUpdateFileClient(eq(2L),
82 79 eq(configDO2.getStorage()), eq(configDO2.getConfig()));
83 80 assertSame(masterFileClient, fileConfigService.getMasterFileClient());
  81 + // 断言 fileConfigCache 缓存
  82 + assertEquals(2, fileConfigService.getFileConfigCache().size());
  83 + assertEquals(configDO1, fileConfigService.getFileConfigCache().get(0));
  84 + assertEquals(configDO2, fileConfigService.getFileConfigCache().get(1));
84 85 }
85 86  
86 87 @Test
... ... @@ -101,8 +102,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
101 102 assertFalse(fileConfig.getMaster());
102 103 assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
103 104 assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
104   - // verify 调用
105   - verify(fileConfigProducer).sendFileConfigRefreshMessage();
106 105 }
107 106  
108 107 @Test
... ... @@ -126,8 +125,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
126 125 assertPojoEquals(reqVO, fileConfig, "config");
127 126 assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
128 127 assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
129   - // verify 调用
130   - verify(fileConfigProducer).sendFileConfigRefreshMessage();
131 128 }
132 129  
133 130 @Test
... ... @@ -152,8 +149,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
152 149 // 断言数据
153 150 assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster());
154 151 assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster());
155   - // verify 调用
156   - verify(fileConfigProducer).sendFileConfigRefreshMessage();
157 152 }
158 153  
159 154 @Test
... ... @@ -174,8 +169,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
174 169 fileConfigService.deleteFileConfig(id);
175 170 // 校验数据不存在了
176 171 assertNull(fileConfigMapper.selectById(id));
177   - // verify 调用
178   - verify(fileConfigProducer).sendFileConfigRefreshMessage();
179 172 }
180 173  
181 174 @Test
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/cart/TradeCartMapper.java
... ... @@ -19,10 +19,10 @@ public interface TradeCartMapper extends BaseMapperX&lt;TradeCartDO&gt; {
19 19  
20 20 default TradeCartDO selectByUserIdAndSkuId(Long userId, Long skuId,
21 21 Boolean addStatus, Boolean orderStatus) {
22   - return selectOne(TradeCartDO::getUserId, userId,
23   - TradeCartDO::getSkuId, skuId,
24   - TradeCartDO::getAddStatus, addStatus,
25   - TradeCartDO::getOrderStatus, orderStatus);
  22 + return selectOne(new LambdaQueryWrapper<TradeCartDO>().eq(TradeCartDO::getUserId, userId)
  23 + .eq(TradeCartDO::getSkuId, skuId)
  24 + .eq(TradeCartDO::getAddStatus, addStatus)
  25 + .eq(TradeCartDO::getOrderStatus, orderStatus));
26 26 }
27 27  
28 28 default Integer selectSumByUserId(Long userId) {
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java
... ... @@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO;
7 7 import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
8 8 import org.apache.ibatis.annotations.Mapper;
  9 +import org.apache.ibatis.annotations.Select;
  10 +
  11 +import java.time.LocalDateTime;
9 12  
10 13 @Mapper
11 14 public interface MpAccountMapper extends BaseMapperX<MpAccountDO> {
... ... @@ -22,4 +25,7 @@ public interface MpAccountMapper extends BaseMapperX&lt;MpAccountDO&gt; {
22 25 return selectOne(MpAccountDO::getAppId, appId);
23 26 }
24 27  
  28 + @Select("SELECT COUNT(*) FROM mp_account WHERE update_time > #{maxUpdateTime}")
  29 + Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
  30 +
25 31 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/consumer/MpAccountRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.mp.mq.consumer;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.mp.mq.message.MpAccountRefreshMessage;
5   -import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link MpAccountRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class MpAccountRefreshConsumer extends AbstractChannelMessageListener<MpAccountRefreshMessage> {
19   -
20   - @Resource
21   - private MpAccountService mpAccountService;
22   -
23   - @Override
24   - public void onMessage(MpAccountRefreshMessage message) {
25   - log.info("[onMessage][收到 Account 刷新消息]");
26   - mpAccountService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/message/MpAccountRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.mp.mq.message;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 公众号账号刷新 Message
9   - *
10   - * @author 芋道源码
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class MpAccountRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "mp.account.refresh";
19   - }
20   -
21   -}
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/producer/MpAccountProducer.java deleted
1   -package cn.iocoder.yudao.module.mp.mq.producer;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
4   -import cn.iocoder.yudao.module.mp.mq.message.MpAccountRefreshMessage;
5   -import org.springframework.stereotype.Component;
6   -
7   -import javax.annotation.Resource;
8   -
9   -/**
10   - * 公众号账号 Producer
11   - *
12   - * @author 芋道源码
13   - */
14   -@Component
15   -public class MpAccountProducer {
16   -
17   - @Resource
18   - private RedisMQTemplate redisMQTemplate;
19   -
20   - /**
21   - * 发送 {@link MpAccountRefreshMessage} 消息
22   - */
23   - public void sendAccountRefreshMessage() {
24   - MpAccountRefreshMessage message = new MpAccountRefreshMessage();
25   - redisMQTemplate.send(message);
26   - }
27   -
28   -}
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java
1 1 package cn.iocoder.yudao.module.mp.service.account;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.util.ObjUtil;
4 5 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 7 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
8 8 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO;
9 9 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;
13 13 import cn.iocoder.yudao.module.mp.dal.mysql.account.MpAccountMapper;
14 14 import cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants;
15 15 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
16   -import cn.iocoder.yudao.module.mp.mq.producer.MpAccountProducer;
17 16 import com.google.common.annotations.VisibleForTesting;
18 17 import lombok.Getter;
19 18 import lombok.extern.slf4j.Slf4j;
... ... @@ -21,15 +20,20 @@ import me.chanjar.weixin.common.error.WxErrorException;
21 20 import me.chanjar.weixin.mp.api.WxMpService;
22 21 import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
23 22 import org.springframework.context.annotation.Lazy;
  23 +import org.springframework.scheduling.annotation.Scheduled;
24 24 import org.springframework.stereotype.Service;
25 25 import org.springframework.validation.annotation.Validated;
26 26  
27 27 import javax.annotation.PostConstruct;
28 28 import javax.annotation.Resource;
  29 +import java.time.LocalDateTime;
29 30 import java.util.List;
30 31 import java.util.Map;
  32 +import java.util.concurrent.TimeUnit;
31 33  
32 34 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  36 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
33 37 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_USERNAME_EXISTS;
34 38  
35 39 /**
... ... @@ -58,9 +62,6 @@ public class MpAccountServiceImpl implements MpAccountService {
58 62 @Lazy // 延迟加载,解决循环依赖的问题
59 63 private MpServiceFactory mpServiceFactory;
60 64  
61   - @Resource
62   - private MpAccountProducer mpAccountProducer;
63   -
64 65 @Override
65 66 @PostConstruct
66 67 public void initLocalCache() {
... ... @@ -72,7 +73,30 @@ public class MpAccountServiceImpl implements MpAccountService {
72 73  
73 74 // 第二步:构建缓存。创建或更新支付 Client
74 75 mpServiceFactory.init(accounts);
75   - accountCache = CollectionUtils.convertMap(accounts, MpAccountDO::getAppId);
  76 + accountCache = convertMap(accounts, MpAccountDO::getAppId);
  77 + });
  78 + }
  79 +
  80 + /**
  81 + * 通过定时任务轮询,刷新缓存
  82 + *
  83 + * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
  84 + */
  85 + @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
  86 + public void refreshLocalCache() {
  87 + // 注意:忽略自动多租户,因为要全局初始化缓存
  88 + TenantUtils.executeIgnore(() -> {
  89 + // 情况一:如果缓存里没有数据,则直接刷新缓存
  90 + if (CollUtil.isEmpty(accountCache)) {
  91 + initLocalCache();
  92 + return;
  93 + }
  94 +
  95 + // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
  96 + LocalDateTime maxTime = getMaxValue(accountCache.values(), MpAccountDO::getUpdateTime);
  97 + if (mpAccountMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
  98 + initLocalCache();
  99 + }
76 100 });
77 101 }
78 102  
... ... @@ -85,8 +109,8 @@ public class MpAccountServiceImpl implements MpAccountService {
85 109 MpAccountDO account = MpAccountConvert.INSTANCE.convert(createReqVO);
86 110 mpAccountMapper.insert(account);
87 111  
88   - // 发送刷新消息
89   - mpAccountProducer.sendAccountRefreshMessage();
  112 + // 刷新缓存
  113 + initLocalCache();
90 114 return account.getId();
91 115 }
92 116  
... ... @@ -101,8 +125,8 @@ public class MpAccountServiceImpl implements MpAccountService {
101 125 MpAccountDO updateObj = MpAccountConvert.INSTANCE.convert(updateReqVO);
102 126 mpAccountMapper.updateById(updateObj);
103 127  
104   - // 发送刷新消息
105   - mpAccountProducer.sendAccountRefreshMessage();
  128 + // 刷新缓存
  129 + initLocalCache();
106 130 }
107 131  
108 132 @Override
... ... @@ -112,8 +136,8 @@ public class MpAccountServiceImpl implements MpAccountService {
112 136 // 删除
113 137 mpAccountMapper.deleteById(id);
114 138  
115   - // 发送刷新消息
116   - mpAccountProducer.sendAccountRefreshMessage();
  139 + // 刷新缓存
  140 + initLocalCache();
117 141 }
118 142  
119 143 private MpAccountDO validateAccountExists(Long id) {
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java
... ... @@ -6,8 +6,6 @@ import lombok.Data;
6 6 /**
7 7 * 支付单信息 Response DTO
8 8 *
9   - * TODO 芋艿:还没定好字段
10   - *
11 9 * @author 芋道源码
12 10 */
13 11 @Data
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java
... ... @@ -8,8 +8,6 @@ import java.time.LocalDateTime;
8 8 /**
9 9 * 退款单信息 Response DTO
10 10 *
11   - * TODO 芋艿:还没定好字段
12   - *
13 11 * @author 芋道源码
14 12 */
15 13 @Data
... ...
yudao-module-pay/yudao-module-pay-biz/pom.xml
... ... @@ -61,12 +61,6 @@
61 61 <artifactId>yudao-spring-boot-starter-job</artifactId>
62 62 </dependency>
63 63  
64   - <!-- 消息队列相关 -->
65   - <dependency>
66   - <groupId>cn.iocoder.boot</groupId>
67   - <artifactId>yudao-spring-boot-starter-mq</artifactId>
68   - </dependency>
69   -
70 64 <!-- Test 测试相关 -->
71 65 <dependency>
72 66 <groupId>cn.iocoder.boot</groupId>
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java
1 1 package cn.iocoder.yudao.module.pay.dal.redis;
2 2  
3   -import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
4   -import org.redisson.api.RLock;
5   -
6 3 /**
7 4 * 支付 Redis Key 枚举类
8 5 *
... ... @@ -10,9 +7,14 @@ import org.redisson.api.RLock;
10 7 */
11 8 public interface RedisKeyConstants {
12 9  
13   - RedisKeyDefine PAY_NOTIFY_LOCK = new RedisKeyDefine("通知任务的分布式锁",
14   - "pay_notify:lock:%d", // 参数来自 DefaultLockKeyBuilder 类
15   - RedisKeyDefine.KeyTypeEnum.HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
  10 + /**
  11 + * 通知任务的分布式锁
  12 + *
  13 + * KEY 格式:pay_notify:lock:%d // 参数来自 DefaultLockKeyBuilder 类
  14 + * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构
  15 + * 过期时间:不固定
  16 + */
  17 + String PAY_NOTIFY_LOCK = "pay_notify:lock:%d";
16 18  
17 19 /**
18 20 * 支付序号的缓存
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/notify/PayNotifyLockRedisDAO.java
... ... @@ -33,7 +33,7 @@ public class PayNotifyLockRedisDAO {
33 33 }
34 34  
35 35 private static String formatKey(Long id) {
36   - return String.format(PAY_NOTIFY_LOCK.getKeyTemplate(), id);
  36 + return String.format(PAY_NOTIFY_LOCK, id);
37 37 }
38 38  
39 39 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java
... ... @@ -15,7 +15,6 @@ import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateR
15 15 import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert;
16 16 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
17 17 import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper;
18   -import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
19 18 import lombok.Getter;
20 19 import lombok.Setter;
21 20 import lombok.extern.slf4j.Slf4j;
... ... @@ -112,7 +111,7 @@ public class PayChannelServiceImpl implements PayChannelService {
112 111 channelMapper.insert(channel);
113 112  
114 113 // 刷新缓存
115   - refreshLocalCache();
  114 + initLocalCache();
116 115 return channel.getId();
117 116 }
118 117  
... ... @@ -127,7 +126,7 @@ public class PayChannelServiceImpl implements PayChannelService {
127 126 channelMapper.updateById(channel);
128 127  
129 128 // 刷新缓存
130   - refreshLocalCache();
  129 + initLocalCache();
131 130 }
132 131  
133 132 /**
... ... @@ -160,7 +159,7 @@ public class PayChannelServiceImpl implements PayChannelService {
160 159 channelMapper.deleteById(id);
161 160  
162 161 // 刷新缓存
163   - refreshLocalCache();
  162 + initLocalCache();
164 163 }
165 164  
166 165 private PayChannelDO validateChannelExists(Long id) {
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java
... ... @@ -44,7 +44,7 @@ public interface AdminUserApi {
44 44 * @param postIds 岗位数组
45 45 * @return 用户数组
46 46 */
47   - List<AdminUserRespDTO> getUsersByPostIds(Collection<Long> postIds);
  47 + List<AdminUserRespDTO> getUserListByPostIds(Collection<Long> postIds);
48 48  
49 49 /**
50 50 * 获得用户 Map
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApiImpl.java
... ... @@ -21,7 +21,7 @@ public class PermissionApiImpl implements PermissionApi {
21 21  
22 22 @Override
23 23 public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
24   - return permissionService.getUserRoleIdListByRoleIds(roleIds);
  24 + return permissionService.getUserRoleIdListByRoleId(roleIds);
25 25 }
26 26  
27 27 @Override
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java
... ... @@ -40,7 +40,7 @@ public class AdminUserApiImpl implements AdminUserApi {
40 40 }
41 41  
42 42 @Override
43   - public List<AdminUserRespDTO> getUsersByPostIds(Collection<Long> postIds) {
  43 + public List<AdminUserRespDTO> getUserListByPostIds(Collection<Long> postIds) {
44 44 List<AdminUserDO> users = userService.getUserListByPostIds(postIds);
45 45 return UserConvert.INSTANCE.convertList4(users);
46 46 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http
... ... @@ -2,6 +2,7 @@
2 2 POST {{baseUrl}}/system/auth/login
3 3 Content-Type: application/json
4 4 tenant-id: {{adminTenentId}}
  5 +tag: Yunai.local
5 6  
6 7 {
7 8 "username": "admin",
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.controller.admin.auth;
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6   -import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
7 6 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
8 7 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
9 8 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
... ... @@ -12,8 +11,8 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
12 11 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
13 12 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
14 13 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
15   -import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
16 14 import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
  15 +import cn.iocoder.yudao.module.system.service.permission.MenuService;
17 16 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
18 17 import cn.iocoder.yudao.module.system.service.permission.RoleService;
19 18 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
... ... @@ -34,9 +33,9 @@ import java.util.List;
34 33 import java.util.Set;
35 34  
36 35 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  36 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
37 37 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
38 38 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
39   -import static java.util.Collections.singleton;
40 39  
41 40 @Tag(name = "管理后台 - 认证")
42 41 @RestController
... ... @@ -52,6 +51,8 @@ public class AuthController {
52 51 @Resource
53 52 private RoleService roleService;
54 53 @Resource
  54 + private MenuService menuService;
  55 + @Resource
55 56 private PermissionService permissionService;
56 57 @Resource
57 58 private SocialUserService socialUserService;
... ... @@ -91,33 +92,24 @@ public class AuthController {
91 92 @GetMapping("/get-permission-info")
92 93 @Operation(summary = "获取登录用户的权限信息")
93 94 public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
94   - // 获得用户信息
  95 + // 1.1 获得用户信息
95 96 AdminUserDO user = userService.getUser(getLoginUserId());
96 97 if (user == null) {
97 98 return null;
98 99 }
99   - // 获得角色列表
100   - Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
101   - List<RoleDO> roleList = roleService.getRoleListFromCache(roleIds);
102   - // 获得菜单列表
103   - List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
104   - SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
105   - singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
106   - // 拼接结果返回
107   - return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
108   - }
109 100  
110   - @GetMapping("/list-menus")
111   - @Operation(summary = "获得登录用户的菜单列表")
112   - public CommonResult<List<AuthMenuRespVO>> getMenuList() {
113   - // 获得角色列表
114   - Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
115   - // 获得用户拥有的菜单列表
116   - List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
117   - SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
118   - singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
119   - // 转换成 Tree 结构返回
120   - return success(AuthConvert.INSTANCE.buildMenuTree(menuList));
  101 + // 1.2 获得角色列表
  102 + Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId());
  103 + List<RoleDO> roles = roleService.getRoleList(roleIds);
  104 + roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色
  105 +
  106 + // 1.3 获得菜单列表
  107 + Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));
  108 + List<MenuDO> menuList = menuService.getMenuList(menuIds);
  109 + menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单
  110 +
  111 + // 2. 拼接结果返回
  112 + return success(AuthConvert.INSTANCE.convert(user, roles, menuList));
121 113 }
122 114  
123 115 // ========== 短信登录相关 ==========
... ... @@ -149,7 +141,7 @@ public class AuthController {
149 141 @Parameter(name = "redirectUri", description = "回调路径")
150 142 })
151 143 public CommonResult<String> socialLogin(@RequestParam("type") Integer type,
152   - @RequestParam("redirectUri") String redirectUri) {
  144 + @RequestParam("redirectUri") String redirectUri) {
153 145 return CommonResult.success(socialUserService.getAuthorizeUrl(type, redirectUri));
154 146 }
155 147  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java
... ... @@ -6,9 +6,10 @@ import lombok.Builder;
6 6 import lombok.Data;
7 7 import lombok.NoArgsConstructor;
8 8  
  9 +import java.util.List;
9 10 import java.util.Set;
10 11  
11   -@Schema(description = "管理后台 - 登录用户的权限信息 Response VO,额外包括用户信息和角色列表")
  12 +@Schema(description = "管理后台 - 登录用户的权限信息 Response VO,额外包括用户信息和角色列表")
12 13 @Data
13 14 @NoArgsConstructor
14 15 @AllArgsConstructor
... ... @@ -24,6 +25,9 @@ public class AuthPermissionInfoRespVO {
24 25 @Schema(description = "操作权限数组", requiredMode = Schema.RequiredMode.REQUIRED)
25 26 private Set<String> permissions;
26 27  
  28 + @Schema(description = "菜单树", required = true)
  29 + private List<MenuVO> menus;
  30 +
27 31 @Schema(description = "用户信息 VO")
28 32 @Data
29 33 @NoArgsConstructor
... ... @@ -37,9 +41,53 @@ public class AuthPermissionInfoRespVO {
37 41 @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
38 42 private String nickname;
39 43  
40   - @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://www.iocoder.cn/xx.jpg")
  44 + @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.jpg")
41 45 private String avatar;
42 46  
43 47 }
44 48  
  49 + @Schema(description = "管理后台 - 登录用户的菜单信息 Response VO")
  50 + @Data
  51 + @NoArgsConstructor
  52 + @AllArgsConstructor
  53 + @Builder
  54 + public static class MenuVO {
  55 +
  56 + @Schema(description = "菜单名称", required = true, example = "芋道")
  57 + private Long id;
  58 +
  59 + @Schema(description = "父菜单 ID", required = true, example = "1024")
  60 + private Long parentId;
  61 +
  62 + @Schema(description = "菜单名称", required = true, example = "芋道")
  63 + private String name;
  64 +
  65 + @Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
  66 + private String path;
  67 +
  68 + @Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
  69 + private String component;
  70 +
  71 + @Schema(description = "组件名", example = "SystemUser")
  72 + private String componentName;
  73 +
  74 + @Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
  75 + private String icon;
  76 +
  77 + @Schema(description = "是否可见", required = true, example = "false")
  78 + private Boolean visible;
  79 +
  80 + @Schema(description = "是否缓存", required = true, example = "false")
  81 + private Boolean keepAlive;
  82 +
  83 + @Schema(description = "是否总是显示", example = "false")
  84 + private Boolean alwaysShow;
  85 +
  86 + /**
  87 + * 子路由
  88 + */
  89 + private List<MenuVO> children;
  90 +
  91 + }
  92 +
45 93 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogBaseVO.java
... ... @@ -11,9 +11,9 @@ import java.util.Map;
11 11 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
12 12  
13 13 /**
14   -* 邮件日志 Base VO,提供给添加、修改、详细的子 VO 使用
15   -* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
16   -*/
  14 + * 邮件日志 Base VO,提供给添加、修改、详细的子 VO 使用
  15 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  16 + */
17 17 @Data
18 18 public class MailLogBaseVO {
19 19  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java
... ... @@ -37,10 +37,10 @@ public class PermissionController {
37 37  
38 38 @Operation(summary = "获得角色拥有的菜单编号")
39 39 @Parameter(name = "roleId", description = "角色编号", required = true)
40   - @GetMapping("/list-role-resources")
  40 + @GetMapping("/list-role-menus")
41 41 @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
42   - public CommonResult<Set<Long>> listRoleMenus(Long roleId) {
43   - return success(permissionService.getRoleMenuIds(roleId));
  42 + public CommonResult<Set<Long>> getRoleMenuList(Long roleId) {
  43 + return success(permissionService.getRoleMenuListByRoleId(roleId));
44 44 }
45 45  
46 46 @PostMapping("/assign-role-menu")
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/RoleController.http
... ... @@ -40,6 +40,3 @@ tenant-id: {{adminTenentId}}
40 40 GET {{baseUrl}}/system/role/page?pageNo=1&pageSize=10
41 41 Authorization: Bearer {{token}}
42 42 tenant-id: {{adminTenentId}}
43   -
44   -###
45   -
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/permission/PermissionAssignRoleDataScopeReqVO.java
... ... @@ -20,7 +20,7 @@ public class PermissionAssignRoleDataScopeReqVO {
20 20 // TODO 这里要多一个枚举校验
21 21 private Integer dataScope;
22 22  
23   - @Schema(description = "部门编号列表,只有范围类型为 DEPT_CUSTOM 时,该字段才需要", example = "1,3,5")
  23 + @Schema(description = "部门编号列表,只有范围类型为 DEPT_CUSTOM 时,该字段才需要", example = "1,3,5")
24 24 private Set<Long> dataScopeDeptIds = Collections.emptySet(); // 兜底
25 25  
26 26 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordBaseVO.java
... ... @@ -7,9 +7,9 @@ import javax.validation.constraints.NotNull;
7 7 import java.util.List;
8 8  
9 9 /**
10   -* 敏感词 Base VO,提供给添加、修改、详细的子 VO 使用
11   -* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
12   -*/
  10 + * 敏感词 Base VO,提供给添加、修改、详细的子 VO 使用
  11 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  12 + */
13 13 @Data
14 14 public class SensitiveWordBaseVO {
15 15  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantBaseVO.java
... ... @@ -7,9 +7,9 @@ import javax.validation.constraints.*;
7 7 import java.time.LocalDateTime;
8 8  
9 9 /**
10   -* 租户 Base VO,提供给添加、修改、详细的子 VO 使用
11   -* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
12   -*/
  10 + * 租户 Base VO,提供给添加、修改、详细的子 VO 使用
  11 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  12 + */
13 13 @Data
14 14 public class TenantBaseVO {
15 15  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileUpdateReqVO.java
... ... @@ -25,7 +25,7 @@ public class UserProfileUpdateReqVO {
25 25 @Length(min = 11, max = 11, message = "手机号长度必须 11 位")
26 26 private String mobile;
27 27  
28   - @Schema(description = "用户性别-参见 SexEnum 枚举类", example = "1")
  28 + @Schema(description = "用户性别,参见 SexEnum 枚举类", example = "1")
29 29 private Integer sex;
30 30  
31 31 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserExportReqVO.java
... ... @@ -29,7 +29,7 @@ public class UserExportReqVO {
29 29 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
30 30 private LocalDateTime[] createTime;
31 31  
32   - @Schema(description = "部门编号,同时筛选子部门", example = "1024")
  32 + @Schema(description = "部门编号,同时筛选子部门", example = "1024")
33 33 private Long deptId;
34 34  
35 35 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserPageItemRespVO.java
... ... @@ -6,7 +6,7 @@ import lombok.Data;
6 6 import lombok.EqualsAndHashCode;
7 7 import lombok.NoArgsConstructor;
8 8  
9   -@Schema(description = "管理后台 - 用户分页时的信息 Response VO,相比用户基本信息来说,会多部门信息")
  9 +@Schema(description = "管理后台 - 用户分页时的信息 Response VO,相比用户基本信息来说,会多部门信息")
10 10 @Data
11 11 @NoArgsConstructor
12 12 @AllArgsConstructor
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserUpdateStatusReqVO.java
1 1 package cn.iocoder.yudao.module.system.controller.admin.user.vo.user;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
3 5 import io.swagger.v3.oas.annotations.media.Schema;
4 6 import lombok.Data;
5 7  
... ... @@ -13,9 +15,9 @@ public class UserUpdateStatusReqVO {
13 15 @NotNull(message = "角色编号不能为空")
14 16 private Long id;
15 17  
16   - @Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + @Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
17 19 @NotNull(message = "状态不能为空")
18   -// @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
  20 + @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
19 21 private Integer status;
20 22  
21 23 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java
... ... @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
8 8 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
9 9 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
10 10 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
  11 +import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
11 12 import org.mapstruct.Mapper;
12 13 import org.mapstruct.factory.Mappers;
13 14 import org.slf4j.LoggerFactory;
... ... @@ -27,13 +28,16 @@ public interface AuthConvert {
27 28  
28 29 default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
29 30 return AuthPermissionInfoRespVO.builder()
30   - .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build())
31   - .roles(convertSet(roleList, RoleDO::getCode))
32   - .permissions(convertSet(menuList, MenuDO::getPermission))
33   - .build();
  31 + .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build())
  32 + .roles(convertSet(roleList, RoleDO::getCode))
  33 + // 权限标识信息
  34 + .permissions(convertSet(menuList, MenuDO::getPermission))
  35 + // 菜单树
  36 + .menus(buildMenuTree(menuList))
  37 + .build();
34 38 }
35 39  
36   - AuthMenuRespVO convertTreeNode(MenuDO menu);
  40 + AuthPermissionInfoRespVO.MenuVO convertTreeNode(MenuDO menu);
37 41  
38 42 /**
39 43 * 将菜单列表,构建成菜单树
... ... @@ -41,20 +45,23 @@ public interface AuthConvert {
41 45 * @param menuList 菜单列表
42 46 * @return 菜单树
43 47 */
44   - default List<AuthMenuRespVO> buildMenuTree(List<MenuDO> menuList) {
  48 + default List<AuthPermissionInfoRespVO.MenuVO> buildMenuTree(List<MenuDO> menuList) {
  49 + // 移除按钮
  50 + menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType()));
45 51 // 排序,保证菜单的有序性
46 52 menuList.sort(Comparator.comparing(MenuDO::getSort));
  53 +
47 54 // 构建菜单树
48 55 // 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。
49   - Map<Long, AuthMenuRespVO> treeNodeMap = new LinkedHashMap<>();
  56 + Map<Long, AuthPermissionInfoRespVO.MenuVO> treeNodeMap = new LinkedHashMap<>();
50 57 menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu)));
51 58 // 处理父子关系
52 59 treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(ID_ROOT)).forEach(childNode -> {
53 60 // 获得父节点
54   - AuthMenuRespVO parentNode = treeNodeMap.get(childNode.getParentId());
  61 + AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId());
55 62 if (parentNode == null) {
56 63 LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]",
57   - childNode.getId(), childNode.getParentId());
  64 + childNode.getId(), childNode.getParentId());
58 65 return;
59 66 }
60 67 // 将自己添加到父节点中
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dept/DeptMapper.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqV
6 6 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
7 7 import org.apache.ibatis.annotations.Mapper;
8 8  
  9 +import java.util.Collection;
9 10 import java.util.List;
10 11  
11 12 @Mapper
... ... @@ -25,4 +26,8 @@ public interface DeptMapper extends BaseMapperX&lt;DeptDO&gt; {
25 26 return selectCount(DeptDO::getParentId, parentId);
26 27 }
27 28  
  29 + default List<DeptDO> selectListByParentId(Collection<Long> parentIds) {
  30 + return selectList(DeptDO::getParentId, parentIds);
  31 + }
  32 +
28 33 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/MenuMapper.java
... ... @@ -25,4 +25,7 @@ public interface MenuMapper extends BaseMapperX&lt;MenuDO&gt; {
25 25 .eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));
26 26 }
27 27  
  28 + default List<MenuDO> selectListByPermission(String permission) {
  29 + return selectList(MenuDO::getPermission, permission);
  30 + }
28 31 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java
... ... @@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.permission;
3 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 4 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
5 5 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
6   -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
7 6 import org.apache.ibatis.annotations.Mapper;
8   -import org.springframework.stereotype.Repository;
9 7  
10 8 import java.util.Collection;
11 9 import java.util.List;
... ... @@ -13,14 +11,18 @@ import java.util.List;
13 11 @Mapper
14 12 public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> {
15 13  
16   - @Repository
17   - class BatchInsertMapper extends ServiceImpl<RoleMenuMapper, RoleMenuDO> {
18   - }
19   -
20 14 default List<RoleMenuDO> selectListByRoleId(Long roleId) {
21 15 return selectList(RoleMenuDO::getRoleId, roleId);
22 16 }
23 17  
  18 + default List<RoleMenuDO> selectListByRoleId(Collection<Long> roleIds) {
  19 + return selectList(RoleMenuDO::getRoleId, roleIds);
  20 + }
  21 +
  22 + default List<RoleMenuDO> selectListByMenuId(Long menuId) {
  23 + return selectList(RoleMenuDO::getMenuId, menuId);
  24 + }
  25 +
24 26 default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {
25 27 delete(new LambdaQueryWrapper<RoleMenuDO>()
26 28 .eq(RoleMenuDO::getRoleId, roleId)
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sensitiveword/SensitiveWordMapper.java
... ... @@ -7,7 +7,9 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
7 7 import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO;
8 8 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
9 9 import org.apache.ibatis.annotations.Mapper;
  10 +import org.apache.ibatis.annotations.Select;
10 11  
  12 +import java.time.LocalDateTime;
11 13 import java.util.List;
12 14  
13 15 /**
... ... @@ -40,4 +42,7 @@ public interface SensitiveWordMapper extends BaseMapperX&lt;SensitiveWordDO&gt; {
40 42 return selectOne(SensitiveWordDO::getName, name);
41 43 }
42 44  
  45 + @Select("SELECT COUNT(*) FROM system_sensitive_word WHERE update_time > #{maxUpdateTime}")
  46 + Long selectCountByUpdateTimeGt(LocalDateTime maxTime);
  47 +
43 48 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsChannelMapper.java
... ... @@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
7 7 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
8 8 import org.apache.ibatis.annotations.Mapper;
  9 +import org.apache.ibatis.annotations.Select;
  10 +
  11 +import java.time.LocalDateTime;
9 12  
10 13 @Mapper
11 14 public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
... ... @@ -18,4 +21,7 @@ public interface SmsChannelMapper extends BaseMapperX&lt;SmsChannelDO&gt; {
18 21 .orderByDesc(SmsChannelDO::getId));
19 22 }
20 23  
  24 + @Select("SELECT COUNT(*) FROM system_sms_channel WHERE update_time > #{maxUpdateTime}")
  25 + Long selectCountByUpdateTimeGt(LocalDateTime maxTime);
  26 +
21 27 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
1 1 package cn.iocoder.yudao.module.system.dal.redis;
2 2  
3   -import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
4 3 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
5 4  
6   -import java.time.Duration;
7   -
8   -import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
9   -
10 5 /**
11 6 * System Redis Key 枚举类
12 7 *
... ... @@ -14,16 +9,93 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S
14 9 */
15 10 public interface RedisKeyConstants {
16 11  
17   - RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存",
18   - "captcha_code:%s", // 参数为 uuid
19   - STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
  12 + /**
  13 + * 指定部门的所有子部门编号数组的缓存
  14 + * <p>
  15 + * KEY 格式:dept_children_ids:{id}
  16 + * VALUE 数据类型:String 子部门编号集合
  17 + */
  18 + String DEPT_CHILDREN_ID_LIST = "dept_children_ids";
  19 +
  20 + /**
  21 + * 角色的缓存
  22 + * <p>
  23 + * KEY 格式:role:{id}
  24 + * VALUE 数据类型:String 角色信息
  25 + */
  26 + String ROLE = "role";
  27 +
  28 + /**
  29 + * 用户拥有的角色编号的缓存
  30 + * <p>
  31 + * KEY 格式:user_role_ids:{userId}
  32 + * VALUE 数据类型:String 角色编号集合
  33 + */
  34 + String USER_ROLE_ID_LIST = "user_role_ids";
  35 +
  36 + /**
  37 + * 拥有指定菜单的角色编号的缓存
  38 + * <p>
  39 + * KEY 格式:user_role_ids:{menuId}
  40 + * VALUE 数据类型:String 角色编号集合
  41 + */
  42 + String MENU_ROLE_ID_LIST = "menu_role_ids";
  43 +
  44 + /**
  45 + * 拥有权限对应的菜单编号数组的缓存
  46 + * <p>
  47 + * KEY 格式:permission_menu_ids:{permission}
  48 + * VALUE 数据类型:String 菜单编号数组
  49 + */
  50 + String PERMISSION_MENU_ID_LIST = "permission_menu_ids";
  51 +
  52 + /**
  53 + * OAuth2 客户端的缓存
  54 + * <p>
  55 + * KEY 格式:user:{id}
  56 + * VALUE 数据类型:String 客户端信息
  57 + */
  58 + String OAUTH_CLIENT = "oauth_client";
  59 +
  60 + /**
  61 + * 访问令牌的缓存
  62 + * <p>
  63 + * KEY 格式:oauth2_access_token:{token}
  64 + * VALUE 数据类型:String 访问令牌信息 {@link OAuth2AccessTokenDO}
  65 + * <p>
  66 + * 由于动态过期时间,使用 RedisTemplate 操作
  67 + */
  68 + String OAUTH2_ACCESS_TOKEN = "oauth2_access_token:%s";
  69 +
  70 + /**
  71 + * 站内信模版的缓存
  72 + * <p>
  73 + * KEY 格式:notify_template:{code}
  74 + * VALUE 数据格式:String 模版信息
  75 + */
  76 + String NOTIFY_TEMPLATE = "notify_template";
20 77  
21   - RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存",
22   - "oauth2_access_token:%s", // 参数为访问令牌 token
23   - STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
  78 + /**
  79 + * 邮件账号的缓存
  80 + * <p>
  81 + * KEY 格式:sms_template:{id}
  82 + * VALUE 数据格式:String 账号信息
  83 + */
  84 + String MAIL_ACCOUNT = "mail_account";
24 85  
25   - RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
26   - "social_auth_state:%s", // 参数为 state
27   - STRING, String.class, Duration.ofHours(24)); // 值为 state
  86 + /**
  87 + * 邮件模版的缓存
  88 + * <p>
  89 + * KEY 格式:mail_template:{code}
  90 + * VALUE 数据格式:String 模版信息
  91 + */
  92 + String MAIL_TEMPLATE = "mail_template";
28 93  
  94 + /**
  95 + * 短信模版的缓存
  96 + * <p>
  97 + * KEY 格式:sms_template:{id}
  98 + * VALUE 数据格式:String 模版信息
  99 + */
  100 + String SMS_TEMPLATE = "sms_template";
29 101 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/common/CaptchaRedisDAO.java deleted
1   -package cn.iocoder.yudao.module.system.dal.redis.common;
2   -
3   -import org.springframework.data.redis.core.StringRedisTemplate;
4   -import org.springframework.stereotype.Repository;
5   -
6   -import javax.annotation.Resource;
7   -import java.time.Duration;
8   -
9   -import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.CAPTCHA_CODE;
10   -
11   -/**
12   - * 验证码的 Redis DAO
13   - *
14   - * @author 芋道源码
15   - */
16   -@Repository
17   -public class CaptchaRedisDAO {
18   -
19   - @Resource
20   - private StringRedisTemplate stringRedisTemplate;
21   -
22   - public String get(String uuid) {
23   - String redisKey = formatKey(uuid);
24   - return stringRedisTemplate.opsForValue().get(redisKey);
25   - }
26   -
27   - public void set(String uuid, String code, Duration timeout) {
28   - String redisKey = formatKey(uuid);
29   - stringRedisTemplate.opsForValue().set(redisKey, code, timeout);
30   - }
31   -
32   - public void delete(String uuid) {
33   - String redisKey = formatKey(uuid);
34   - stringRedisTemplate.delete(redisKey);
35   - }
36   -
37   - private static String formatKey(String uuid) {
38   - return String.format(CAPTCHA_CODE.getKeyTemplate(), uuid);
39   - }
40   -
41   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java
... ... @@ -53,7 +53,7 @@ public class OAuth2AccessTokenRedisDAO {
53 53 }
54 54  
55 55 private static String formatKey(String accessToken) {
56   - return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken);
  56 + return String.format(OAUTH2_ACCESS_TOKEN, accessToken);
57 57 }
58 58  
59 59 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/auth/OAuth2ClientRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.auth;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link OAuth2ClientRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class OAuth2ClientRefreshConsumer extends AbstractChannelMessageListener<OAuth2ClientRefreshMessage> {
19   -
20   - @Resource
21   - private OAuth2ClientService oauth2ClientService;
22   -
23   - @Override
24   - public void onMessage(OAuth2ClientRefreshMessage message) {
25   - log.info("[onMessage][收到 OAuth2Client 刷新消息]");
26   - oauth2ClientService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/dept/DeptRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.dept;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.dept.DeptRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.dept.DeptService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link DeptRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class DeptRefreshConsumer extends AbstractChannelMessageListener<DeptRefreshMessage> {
19   -
20   - @Resource
21   - private DeptService deptService;
22   -
23   - @Override
24   - public void onMessage(DeptRefreshMessage message) {
25   - log.info("[onMessage][收到 Dept 刷新消息]");
26   - deptService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailAccountRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.mail;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.mail.MailAccountRefreshMessage;
5   -import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage;
6   -import cn.iocoder.yudao.module.system.service.mail.MailAccountService;
7   -import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
8   -import lombok.extern.slf4j.Slf4j;
9   -import org.springframework.stereotype.Component;
10   -
11   -import javax.annotation.Resource;
12   -
13   -/**
14   - * 针对 {@link MailAccountRefreshMessage} 的消费者
15   - *
16   - * @author wangjingyi
17   - */
18   -@Component
19   -@Slf4j
20   -public class MailAccountRefreshConsumer extends AbstractChannelMessageListener<MailAccountRefreshMessage> {
21   -
22   - @Resource
23   - private MailAccountService mailAccountService;
24   -
25   - @Override
26   - public void onMessage(MailAccountRefreshMessage message) {
27   - log.info("[onMessage][收到 Mail Account 刷新信息]");
28   - mailAccountService.initLocalCache();
29   - }
30   -
31   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailTemplateRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.mail;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link MailTemplateRefreshMessage} 的消费者
13   - *
14   - * @author wangjingyi
15   - */
16   -@Component
17   -@Slf4j
18   -public class MailTemplateRefreshConsumer extends AbstractChannelMessageListener<MailTemplateRefreshMessage> {
19   -
20   - @Resource
21   - private MailTemplateService mailTemplateService;
22   -
23   - @Override
24   - public void onMessage(MailTemplateRefreshMessage message) {
25   - log.info("[onMessage][收到 Mail Template 刷新信息]");
26   - mailTemplateService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/notify/NotifyTemplateRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.notify;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.notify.NotifyTemplateRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.notify.NotifyTemplateService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link NotifyTemplateRefreshMessage} 的消费者
13   - *
14   - * @author xrcoder
15   - */
16   -@Component
17   -@Slf4j
18   -public class NotifyTemplateRefreshConsumer extends AbstractChannelMessageListener<NotifyTemplateRefreshMessage> {
19   -
20   - @Resource
21   - private NotifyTemplateService notifyTemplateService;
22   -
23   - @Override
24   - public void onMessage(NotifyTemplateRefreshMessage message) {
25   - log.info("[onMessage][收到 NotifyTemplate 刷新消息]");
26   - notifyTemplateService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/MenuRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.permission;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.permission.MenuRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.permission.MenuService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link MenuRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class MenuRefreshConsumer extends AbstractChannelMessageListener<MenuRefreshMessage> {
19   -
20   - @Resource
21   - private MenuService menuService;
22   -
23   - @Override
24   - public void onMessage(MenuRefreshMessage message) {
25   - log.info("[onMessage][收到 Menu 刷新消息]");
26   - menuService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/RoleMenuRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.permission;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.permission.RoleMenuRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.permission.PermissionService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link RoleMenuRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class RoleMenuRefreshConsumer extends AbstractChannelMessageListener<RoleMenuRefreshMessage> {
19   -
20   - @Resource
21   - private PermissionService permissionService;
22   -
23   - @Override
24   - public void onMessage(RoleMenuRefreshMessage message) {
25   - log.info("[onMessage][收到 Role 与 Menu 的关联刷新消息]");
26   - permissionService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/RoleRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.permission;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.permission.RoleRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.permission.RoleService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link RoleRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class RoleRefreshConsumer extends AbstractChannelMessageListener<RoleRefreshMessage> {
19   -
20   - @Resource
21   - private RoleService roleService;
22   -
23   - @Override
24   - public void onMessage(RoleRefreshMessage message) {
25   - log.info("[onMessage][收到 Role 刷新消息]");
26   - roleService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/UserRoleRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.permission;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.permission.UserRoleRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.permission.PermissionService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link UserRoleRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class UserRoleRefreshConsumer extends AbstractChannelMessageListener<UserRoleRefreshMessage> {
19   -
20   - @Resource
21   - private PermissionService permissionService;
22   -
23   - @Override
24   - public void onMessage(UserRoleRefreshMessage message) {
25   - log.info("[onMessage][收到 User 与 Role 的关联刷新消息]");
26   - permissionService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sensitiveword/SensitiveWordRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.sensitiveword;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.sensitiveword.SensitiveWordRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.sensitiveword.SensitiveWordService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link SensitiveWordRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class SensitiveWordRefreshConsumer extends AbstractChannelMessageListener<SensitiveWordRefreshMessage> {
19   -
20   - @Resource
21   - private SensitiveWordService sensitiveWordService;
22   -
23   - @Override
24   - public void onMessage(SensitiveWordRefreshMessage message) {
25   - log.info("[onMessage][收到 SensitiveWord 刷新消息]");
26   - sensitiveWordService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sms/SmsChannelRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.sms;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.sms.SmsChannelRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.sms.SmsChannelService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link SmsChannelRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class SmsChannelRefreshConsumer extends AbstractChannelMessageListener<SmsChannelRefreshMessage> {
19   -
20   - @Resource
21   - private SmsChannelService smsChannelService;
22   -
23   - @Override
24   - public void onMessage(SmsChannelRefreshMessage message) {
25   - log.info("[onMessage][收到 SmsChannel 刷新消息]");
26   - smsChannelService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sms/SmsTemplateRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.sms;
2   -
3   -import cn.iocoder.yudao.module.system.mq.message.sms.SmsTemplateRefreshMessage;
4   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
5   -import cn.iocoder.yudao.module.system.service.sms.SmsTemplateService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link SmsTemplateRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class SmsTemplateRefreshConsumer extends AbstractChannelMessageListener<SmsTemplateRefreshMessage> {
19   -
20   - @Resource
21   - private SmsTemplateService smsTemplateService;
22   -
23   - @Override
24   - public void onMessage(SmsTemplateRefreshMessage message) {
25   - log.info("[onMessage][收到 SmsTemplate 刷新消息]");
26   - smsTemplateService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/auth/OAuth2ClientRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.auth;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * OAuth 2.0 客户端的数据刷新 Message
9   - *
10   - * @author 芋道源码
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class OAuth2ClientRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.oauth2-client.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/dept/DeptRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.dept;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 部门数据刷新 Message
9   - *
10   - * @author 芋道源码
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class DeptRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.dept.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailAccountRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.mail;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 邮箱账号的数据刷新 Message
9   - *
10   - * @author wangjingyi
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class MailAccountRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.mail-account.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailTemplateRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.mail;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 邮箱模板的数据刷新 Message
9   - *
10   - * @author wangjingyi
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class MailTemplateRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.mail-template.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/notify/NotifyTemplateRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.notify;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 站内信模板的数据刷新 Message
9   - *
10   - * @author xrcoder
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class NotifyTemplateRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.notify-template.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/MenuRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.permission;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 菜单数据刷新 Message
9   - *
10   - * @author 芋道源码
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class MenuRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.menu.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/RoleMenuRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.permission;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 角色与菜单数据刷新 Message
9   - *
10   - * @author 芋道源码
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class RoleMenuRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.role-menu.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/RoleRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.permission;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 角色数据刷新 Message
9   - *
10   - * @author 芋道源码
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class RoleRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.role.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/UserRoleRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.permission;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 用户与角色的数据刷新 Message
9   - *
10   - * @author 芋道源码
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class UserRoleRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.user-role.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/sensitiveword/SensitiveWordRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.sensitiveword;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 敏感词的刷新 Message
9   - */
10   -@Data
11   -@EqualsAndHashCode(callSuper = true)
12   -public class SensitiveWordRefreshMessage extends AbstractChannelMessage {
13   -
14   - @Override
15   - public String getChannel() {
16   - return "system.sensitive-word.refresh";
17   - }
18   -
19   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/sms/SmsChannelRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.sms;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 短信渠道的数据刷新 Message
9   - *
10   - * @author 芋道源码
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class SmsChannelRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.sms-channel.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/sms/SmsTemplateRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.sms;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 短信模板的数据刷新 Message
9   - *
10   - * @author 芋道源码
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class SmsTemplateRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.sms-template.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/auth/OAuth2ClientProducer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.producer.auth;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
4   -import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage;
5   -import org.springframework.stereotype.Component;
6   -
7   -import javax.annotation.Resource;
8   -
9   -/**
10   - * OAuth 2.0 客户端相关消息的 Producer
11   - */
12   -@Component
13   -public class OAuth2ClientProducer {
14   -
15   - @Resource
16   - private RedisMQTemplate redisMQTemplate;
17   -
18   - /**
19   - * 发送 {@link OAuth2ClientRefreshMessage} 消息
20   - */
21   - public void sendOAuth2ClientRefreshMessage() {
22   - OAuth2ClientRefreshMessage message = new OAuth2ClientRefreshMessage();
23   - redisMQTemplate.send(message);
24   - }
25   -
26   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/dept/DeptProducer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.producer.dept;
2   -
3   -import cn.iocoder.yudao.module.system.mq.message.dept.DeptRefreshMessage;
4   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
5   -import org.springframework.stereotype.Component;
6   -
7   -import javax.annotation.Resource;
8   -
9   -/**
10   - * Dept 部门相关消息的 Producer
11   - */
12   -@Component
13   -public class DeptProducer {
14   -
15   - @Resource
16   - private RedisMQTemplate redisMQTemplate;
17   -
18   - /**
19   - * 发送 {@link DeptRefreshMessage} 消息
20   - */
21   - public void sendDeptRefreshMessage() {
22   - DeptRefreshMessage message = new DeptRefreshMessage();
23   - redisMQTemplate.send(message);
24   - }
25   -
26   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java
1 1 package cn.iocoder.yudao.module.system.mq.producer.mail;
2 2  
3 3 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
4   -import cn.iocoder.yudao.module.system.mq.message.mail.MailAccountRefreshMessage;
5 4 import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
6   -import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage;
7 5 import lombok.extern.slf4j.Slf4j;
8 6 import org.springframework.stereotype.Component;
9 7  
... ... @@ -23,22 +21,6 @@ public class MailProducer {
23 21 private RedisMQTemplate redisMQTemplate;
24 22  
25 23 /**
26   - * 发送 {@link MailTemplateRefreshMessage} 消息
27   - */
28   - public void sendMailTemplateRefreshMessage() {
29   - MailTemplateRefreshMessage message = new MailTemplateRefreshMessage();
30   - redisMQTemplate.send(message);
31   - }
32   -
33   - /**
34   - * 发送 {@link MailAccountRefreshMessage} 消息
35   - */
36   - public void sendMailAccountRefreshMessage() {
37   - MailAccountRefreshMessage message = new MailAccountRefreshMessage();
38   - redisMQTemplate.send(message);
39   - }
40   -
41   - /**
42 24 * 发送 {@link MailSendMessage} 消息
43 25 *
44 26 * @param sendLogId 发送日志编码
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/notify/NotifyProducer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.producer.notify;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
4   -import cn.iocoder.yudao.module.system.mq.message.notify.NotifyTemplateRefreshMessage;
5   -import lombok.extern.slf4j.Slf4j;
6   -import org.springframework.stereotype.Component;
7   -
8   -import javax.annotation.Resource;
9   -
10   -/**
11   - * Notify 站内信相关消息的 Producer
12   - *
13   - * @author xrcoder
14   - * @since 2022-08-06
15   - */
16   -@Slf4j
17   -@Component
18   -public class NotifyProducer {
19   -
20   - @Resource
21   - private RedisMQTemplate redisMQTemplate;
22   -
23   -
24   - /**
25   - * 发送 {@link NotifyTemplateRefreshMessage} 消息
26   - */
27   - public void sendNotifyTemplateRefreshMessage() {
28   - NotifyTemplateRefreshMessage message = new NotifyTemplateRefreshMessage();
29   - redisMQTemplate.send(message);
30   - }
31   -
32   -
33   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/package-info.java deleted
1   -/**
2   - * 占位
3   - */
4   -package cn.iocoder.yudao.module.system.mq.producer;
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/permission/MenuProducer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.producer.permission;
2   -
3   -import cn.iocoder.yudao.module.system.mq.message.permission.MenuRefreshMessage;
4   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
5   -import org.springframework.stereotype.Component;
6   -
7   -import javax.annotation.Resource;
8   -
9   -/**
10   - * Menu 菜单相关消息的 Producer
11   - */
12   -@Component
13   -public class MenuProducer {
14   -
15   - @Resource
16   - private RedisMQTemplate redisMQTemplate;
17   -
18   - /**
19   - * 发送 {@link MenuRefreshMessage} 消息
20   - */
21   - public void sendMenuRefreshMessage() {
22   - MenuRefreshMessage message = new MenuRefreshMessage();
23   - redisMQTemplate.send(message);
24   - }
25   -
26   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/permission/PermissionProducer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.producer.permission;
2   -
3   -import cn.iocoder.yudao.module.system.mq.message.permission.RoleMenuRefreshMessage;
4   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
5   -import cn.iocoder.yudao.module.system.mq.message.permission.UserRoleRefreshMessage;
6   -import org.springframework.stereotype.Component;
7   -
8   -import javax.annotation.Resource;
9   -
10   -/**
11   - * Permission 权限相关消息的 Producer
12   - */
13   -@Component
14   -public class PermissionProducer {
15   -
16   - @Resource
17   - private RedisMQTemplate redisMQTemplate;
18   -
19   - /**
20   - * 发送 {@link RoleMenuRefreshMessage} 消息
21   - */
22   - public void sendRoleMenuRefreshMessage() {
23   - RoleMenuRefreshMessage message = new RoleMenuRefreshMessage();
24   - redisMQTemplate.send(message);
25   - }
26   -
27   - /**
28   - * 发送 {@link UserRoleRefreshMessage} 消息
29   - */
30   - public void sendUserRoleRefreshMessage() {
31   - UserRoleRefreshMessage message = new UserRoleRefreshMessage();
32   - redisMQTemplate.send(message);
33   - }
34   -
35   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/permission/RoleProducer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.producer.permission;
2   -
3   -import cn.iocoder.yudao.module.system.mq.message.permission.RoleRefreshMessage;
4   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
5   -import org.springframework.stereotype.Component;
6   -
7   -import javax.annotation.Resource;
8   -
9   -/**
10   - * Role 角色相关消息的 Producer
11   - *
12   - * @author 芋道源码
13   - */
14   -@Component
15   -public class RoleProducer {
16   -
17   - @Resource
18   - private RedisMQTemplate redisMQTemplate;
19   -
20   - /**
21   - * 发送 {@link RoleRefreshMessage} 消息
22   - */
23   - public void sendRoleRefreshMessage() {
24   - RoleRefreshMessage message = new RoleRefreshMessage();
25   - redisMQTemplate.send(message);
26   - }
27   -
28   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sensitiveword/SensitiveWordProducer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.producer.sensitiveword;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
4   -import cn.iocoder.yudao.module.system.mq.message.sensitiveword.SensitiveWordRefreshMessage;
5   -import org.springframework.stereotype.Component;
6   -
7   -import javax.annotation.Resource;
8   -
9   -/**
10   - * 敏感词相关的 Producer
11   - */
12   -@Component
13   -public class SensitiveWordProducer {
14   -
15   - @Resource
16   - private RedisMQTemplate redisMQTemplate;
17   -
18   - /**
19   - * 发送 {@link SensitiveWordRefreshMessage} 消息
20   - */
21   - public void sendSensitiveWordRefreshMessage() {
22   - SensitiveWordRefreshMessage message = new SensitiveWordRefreshMessage();
23   - redisMQTemplate.send(message);
24   - }
25   -
26   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sms/SmsProducer.java
1 1 package cn.iocoder.yudao.module.system.mq.producer.sms;
2 2  
3 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
4   -import cn.iocoder.yudao.module.system.mq.message.sms.SmsChannelRefreshMessage;
5   -import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
6   -import cn.iocoder.yudao.module.system.mq.message.sms.SmsTemplateRefreshMessage;
7 4 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
  5 +import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
8 6 import lombok.extern.slf4j.Slf4j;
9 7 import org.springframework.stereotype.Component;
10 8  
... ... @@ -15,7 +13,7 @@ import java.util.List;
15 13 * Sms 短信相关消息的 Producer
16 14 *
17 15 * @author zzf
18   - * @date 2021/3/9 16:35
  16 + * @since 2021/3/9 16:35
19 17 */
20 18 @Slf4j
21 19 @Component
... ... @@ -25,22 +23,6 @@ public class SmsProducer {
25 23 private RedisMQTemplate redisMQTemplate;
26 24  
27 25 /**
28   - * 发送 {@link SmsChannelRefreshMessage} 消息
29   - */
30   - public void sendSmsChannelRefreshMessage() {
31   - SmsChannelRefreshMessage message = new SmsChannelRefreshMessage();
32   - redisMQTemplate.send(message);
33   - }
34   -
35   - /**
36   - * 发送 {@link SmsTemplateRefreshMessage} 消息
37   - */
38   - public void sendSmsTemplateRefreshMessage() {
39   - SmsTemplateRefreshMessage message = new SmsTemplateRefreshMessage();
40   - redisMQTemplate.send(message);
41   - }
42   -
43   - /**
44 26 * 发送 {@link SmsSendMessage} 消息
45 27 *
46 28 * @param logId 短信日志编号
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java
... ... @@ -7,10 +7,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqV
7 7 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
8 8 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
9 9  
10   -import java.util.Collection;
11   -import java.util.Collections;
12   -import java.util.List;
13   -import java.util.Map;
  10 +import java.util.*;
14 11  
15 12 /**
16 13 * 部门 Service 接口
... ... @@ -20,11 +17,6 @@ import java.util.Map;
20 17 public interface DeptService {
21 18  
22 19 /**
23   - * 初始化部门的本地缓存
24   - */
25   - void initLocalCache();
26   -
27   - /**
28 20 * 创建部门
29 21 *
30 22 * @param reqVO 部门信息
... ... @@ -47,21 +39,12 @@ public interface DeptService {
47 39 void deleteDept(Long id);
48 40  
49 41 /**
50   - * 筛选部门列表
51   - *
52   - * @param reqVO 筛选条件请求 VO
53   - * @return 部门列表
54   - */
55   - List<DeptDO> getDeptList(DeptListReqVO reqVO);
56   -
57   - /**
58   - * 获得所有子部门,从缓存中
  42 + * 获得部门信息
59 43 *
60   - * @param parentId 部门编号
61   - * @param recursive 是否递归获取所有
62   - * @return 子部门列表
  44 + * @param id 部门编号
  45 + * @return 部门信息
63 46 */
64   - List<DeptDO> getDeptListByParentIdFromCache(Long parentId, boolean recursive);
  47 + DeptDO getDept(Long id);
65 48  
66 49 /**
67 50 * 获得部门信息数组
... ... @@ -72,6 +55,14 @@ public interface DeptService {
72 55 List<DeptDO> getDeptList(Collection<Long> ids);
73 56  
74 57 /**
  58 + * 筛选部门列表
  59 + *
  60 + * @param reqVO 筛选条件请求 VO
  61 + * @return 部门列表
  62 + */
  63 + List<DeptDO> getDeptList(DeptListReqVO reqVO);
  64 +
  65 + /**
75 66 * 获得指定编号的部门 Map
76 67 *
77 68 * @param ids 部门编号数组
... ... @@ -86,12 +77,20 @@ public interface DeptService {
86 77 }
87 78  
88 79 /**
89   - * 获得部门信息
  80 + * 获得指定部门的所有子部门
90 81 *
91 82 * @param id 部门编号
92   - * @return 部门信息
  83 + * @return 子部门列表
93 84 */
94   - DeptDO getDept(Long id);
  85 + List<DeptDO> getChildDeptList(Long id);
  86 +
  87 + /**
  88 + * 获得所有子部门,从缓存中
  89 + *
  90 + * @param id 父部门编号
  91 + * @return 子部门列表
  92 + */
  93 + Set<Long> getChildDeptIdListFromCache(Long id);
95 94  
96 95 /**
97 96 * 校验部门们是否有效。如下情况,视为无效:
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
1 1 package cn.iocoder.yudao.module.system.service.dept;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
4 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5   -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
6   -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
  6 +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
7 7 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
8 8 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
9 9 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
10 10 import cn.iocoder.yudao.module.system.convert.dept.DeptConvert;
11 11 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
12 12 import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
  13 +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
13 14 import cn.iocoder.yudao.module.system.enums.dept.DeptIdEnum;
14   -import cn.iocoder.yudao.module.system.mq.producer.dept.DeptProducer;
15   -import com.google.common.collect.ImmutableMap;
16   -import com.google.common.collect.ImmutableMultimap;
17   -import com.google.common.collect.Multimap;
18   -import lombok.Getter;
  15 +import com.google.common.annotations.VisibleForTesting;
19 16 import lombok.extern.slf4j.Slf4j;
  17 +import org.springframework.cache.annotation.CacheEvict;
  18 +import org.springframework.cache.annotation.Cacheable;
20 19 import org.springframework.stereotype.Service;
21 20 import org.springframework.validation.annotation.Validated;
22 21  
23   -import javax.annotation.PostConstruct;
24 22 import javax.annotation.Resource;
25 23 import java.util.*;
26 24  
27 25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
28 27 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
29 28  
30 29 /**
... ... @@ -37,55 +36,12 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
37 36 @Slf4j
38 37 public class DeptServiceImpl implements DeptService {
39 38  
40   - /**
41   - * 部门缓存
42   - * key:部门编号 {@link DeptDO#getId()}
43   - *
44   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
45   - */
46   - @Getter
47   - private volatile Map<Long, DeptDO> deptCache;
48   - /**
49   - * 父部门缓存
50   - * key:部门编号 {@link DeptDO#getParentId()}
51   - * value: 直接子部门列表
52   - *
53   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
54   - */
55   - @Getter
56   - private volatile Multimap<Long, DeptDO> parentDeptCache;
57   -
58 39 @Resource
59 40 private DeptMapper deptMapper;
60 41  
61   - @Resource
62   - private DeptProducer deptProducer;
63   -
64   - /**
65   - * 初始化 {@link #parentDeptCache} 和 {@link #deptCache} 缓存
66   - */
67   - @Override
68   - @PostConstruct
69   - public synchronized void initLocalCache() {
70   - // 注意:忽略自动多租户,因为要全局初始化缓存
71   - TenantUtils.executeIgnore(() -> {
72   - // 第一步:查询数据
73   - List<DeptDO> depts = deptMapper.selectList();
74   - log.info("[initLocalCache][缓存部门,数量为:{}]", depts.size());
75   -
76   - // 第二步:构建缓存
77   - ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
78   - ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
79   - depts.forEach(deptDO -> {
80   - builder.put(deptDO.getId(), deptDO);
81   - parentBuilder.put(deptDO.getParentId(), deptDO);
82   - });
83   - deptCache = builder.build();
84   - parentDeptCache = parentBuilder.build();
85   - });
86   - }
87   -
88 42 @Override
  43 + @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
  44 + allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
89 45 public Long createDept(DeptCreateReqVO reqVO) {
90 46 // 校验正确性
91 47 if (reqVO.getParentId() == null) {
... ... @@ -95,12 +51,12 @@ public class DeptServiceImpl implements DeptService {
95 51 // 插入部门
96 52 DeptDO dept = DeptConvert.INSTANCE.convert(reqVO);
97 53 deptMapper.insert(dept);
98   - // 发送刷新消息
99   - deptProducer.sendDeptRefreshMessage();
100 54 return dept.getId();
101 55 }
102 56  
103 57 @Override
  58 + @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
  59 + allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
104 60 public void updateDept(DeptUpdateReqVO reqVO) {
105 61 // 校验正确性
106 62 if (reqVO.getParentId() == null) {
... ... @@ -110,11 +66,11 @@ public class DeptServiceImpl implements DeptService {
110 66 // 更新部门
111 67 DeptDO updateObj = DeptConvert.INSTANCE.convert(reqVO);
112 68 deptMapper.updateById(updateObj);
113   - // 发送刷新消息
114   - deptProducer.sendDeptRefreshMessage();
115 69 }
116 70  
117 71 @Override
  72 + @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
  73 + allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
118 74 public void deleteDept(Long id) {
119 75 // 校验是否存在
120 76 validateDeptExists(id);
... ... @@ -124,70 +80,30 @@ public class DeptServiceImpl implements DeptService {
124 80 }
125 81 // 删除部门
126 82 deptMapper.deleteById(id);
127   - // 发送刷新消息
128   - deptProducer.sendDeptRefreshMessage();
129   - }
130   -
131   - @Override
132   - public List<DeptDO> getDeptList(DeptListReqVO reqVO) {
133   - return deptMapper.selectList(reqVO);
134   - }
135   -
136   - @Override
137   - public List<DeptDO> getDeptListByParentIdFromCache(Long parentId, boolean recursive) {
138   - if (parentId == null) {
139   - return Collections.emptyList();
140   - }
141   - List<DeptDO> result = new ArrayList<>();
142   - // 递归,简单粗暴
143   - getDeptsByParentIdFromCache(result, parentId,
144   - recursive ? Integer.MAX_VALUE : 1, // 如果递归获取,则无限;否则,只递归 1 次
145   - parentDeptCache);
146   - return result;
147   - }
148   -
149   - /**
150   - * 递归获取所有的子部门,添加到 result 结果
151   - *
152   - * @param result 结果
153   - * @param parentId 父编号
154   - * @param recursiveCount 递归次数
155   - * @param parentDeptMap 父部门 Map,使用缓存,避免变化
156   - */
157   - private void getDeptsByParentIdFromCache(List<DeptDO> result, Long parentId, int recursiveCount,
158   - Multimap<Long, DeptDO> parentDeptMap) {
159   - // 递归次数为 0,结束!
160   - if (recursiveCount == 0) {
161   - return;
162   - }
163   -
164   - // 获得子部门
165   - Collection<DeptDO> depts = parentDeptMap.get(parentId);
166   - if (CollUtil.isEmpty(depts)) {
167   - return;
168   - }
169   - // 针对多租户,过滤掉非当前租户的部门
170   - Long tenantId = TenantContextHolder.getTenantId();
171   - if (tenantId != null) {
172   - depts = CollUtil.filterNew(depts, dept -> tenantId.equals(dept.getTenantId()));
173   - }
174   - result.addAll(depts);
175   -
176   - // 继续递归
177   - depts.forEach(dept -> getDeptsByParentIdFromCache(result, dept.getId(),
178   - recursiveCount - 1, parentDeptMap));
179 83 }
180 84  
181 85 private void validateForCreateOrUpdate(Long id, Long parentId, String name) {
182 86 // 校验自己存在
183 87 validateDeptExists(id);
184 88 // 校验父部门的有效性
185   - validateParentDeptEnable(id, parentId);
  89 + validateParentDept(id, parentId);
186 90 // 校验部门名的唯一性
187 91 validateDeptNameUnique(id, parentId, name);
188 92 }
189 93  
190   - private void validateParentDeptEnable(Long id, Long parentId) {
  94 + @VisibleForTesting
  95 + void validateDeptExists(Long id) {
  96 + if (id == null) {
  97 + return;
  98 + }
  99 + DeptDO dept = deptMapper.selectById(id);
  100 + if (dept == null) {
  101 + throw exception(DEPT_NOT_FOUND);
  102 + }
  103 + }
  104 +
  105 + @VisibleForTesting
  106 + void validateParentDept(Long id, Long parentId) {
191 107 if (parentId == null || DeptIdEnum.ROOT.getId().equals(parentId)) {
192 108 return;
193 109 }
... ... @@ -200,49 +116,71 @@ public class DeptServiceImpl implements DeptService {
200 116 if (dept == null) {
201 117 throw exception(DEPT_PARENT_NOT_EXITS);
202 118 }
203   - // 父部门被禁用
204   - if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) {
205   - throw exception(DEPT_NOT_ENABLE);
206   - }
207 119 // 父部门不能是原来的子部门
208   - List<DeptDO> children = getDeptListByParentIdFromCache(id, true);
  120 + List<DeptDO> children = getChildDeptList(id);
209 121 if (children.stream().anyMatch(dept1 -> dept1.getId().equals(parentId))) {
210 122 throw exception(DEPT_PARENT_IS_CHILD);
211 123 }
212 124 }
213 125  
214   - private void validateDeptExists(Long id) {
215   - if (id == null) {
216   - return;
217   - }
218   - DeptDO dept = deptMapper.selectById(id);
  126 + @VisibleForTesting
  127 + void validateDeptNameUnique(Long id, Long parentId, String name) {
  128 + DeptDO dept = deptMapper.selectByParentIdAndName(parentId, name);
219 129 if (dept == null) {
220   - throw exception(DEPT_NOT_FOUND);
221   - }
222   - }
223   -
224   - private void validateDeptNameUnique(Long id, Long parentId, String name) {
225   - DeptDO menu = deptMapper.selectByParentIdAndName(parentId, name);
226   - if (menu == null) {
227 130 return;
228 131 }
229 132 // 如果 id 为空,说明不用比较是否为相同 id 的岗位
230 133 if (id == null) {
231 134 throw exception(DEPT_NAME_DUPLICATE);
232 135 }
233   - if (!menu.getId().equals(id)) {
  136 + if (ObjectUtil.notEqual(dept.getId(), id)) {
234 137 throw exception(DEPT_NAME_DUPLICATE);
235 138 }
236 139 }
237 140  
238 141 @Override
  142 + public DeptDO getDept(Long id) {
  143 + return deptMapper.selectById(id);
  144 + }
  145 +
  146 + @Override
239 147 public List<DeptDO> getDeptList(Collection<Long> ids) {
  148 + if (CollUtil.isEmpty(ids)) {
  149 + return Collections.emptyList();
  150 + }
240 151 return deptMapper.selectBatchIds(ids);
241 152 }
242 153  
243 154 @Override
244   - public DeptDO getDept(Long id) {
245   - return deptMapper.selectById(id);
  155 + public List<DeptDO> getDeptList(DeptListReqVO reqVO) {
  156 + return deptMapper.selectList(reqVO);
  157 + }
  158 +
  159 + @Override
  160 + public List<DeptDO> getChildDeptList(Long id) {
  161 + List<DeptDO> children = new LinkedList<>();
  162 + // 遍历每一层
  163 + Collection<Long> parentIds = Collections.singleton(id);
  164 + for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下,存在死循环
  165 + // 查询当前层,所有的子部门
  166 + List<DeptDO> depts = deptMapper.selectListByParentId(parentIds);
  167 + // 1. 如果没有子部门,则结束遍历
  168 + if (CollUtil.isEmpty(depts)) {
  169 + break;
  170 + }
  171 + // 2. 如果有子部门,继续遍历
  172 + children.addAll(depts);
  173 + parentIds = convertSet(depts, DeptDO::getId);
  174 + }
  175 + return children;
  176 + }
  177 +
  178 + @Override
  179 + @DataPermission(enable = false) // 禁用数据权限,避免简历不正确的缓存
  180 + @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id")
  181 + public Set<Long> getChildDeptIdListFromCache(Long id) {
  182 + List<DeptDO> children = getChildDeptList(id);
  183 + return convertSet(children, DeptDO::getId);
246 184 }
247 185  
248 186 @Override
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountService.java
... ... @@ -18,19 +18,6 @@ import java.util.List;
18 18 public interface MailAccountService {
19 19  
20 20 /**
21   - * 初始化邮箱账号的本地缓存
22   - */
23   - void initLocalCache();
24   -
25   - /**
26   - * 从缓存中获取邮箱账号
27   - *
28   - * @param id 编号
29   - * @return 邮箱账号
30   - */
31   - MailAccountDO getMailAccountFromCache(Long id);
32   -
33   - /**
34 21 * 创建邮箱账号
35 22 *
36 23 * @param createReqVO 邮箱账号信息
... ... @@ -61,6 +48,14 @@ public interface MailAccountService {
61 48 MailAccountDO getMailAccount(Long id);
62 49  
63 50 /**
  51 + * 从缓存中获取邮箱账号
  52 + *
  53 + * @param id 编号
  54 + * @return 邮箱账号
  55 + */
  56 + MailAccountDO getMailAccountFromCache(Long id);
  57 +
  58 + /**
64 59 * 获取邮箱账号分页信息
65 60 *
66 61 * @param pageReqVO 邮箱账号分页参数
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImpl.java
... ... @@ -7,20 +7,18 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccou
7 7 import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
8 8 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
9 9 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
10   -import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
11   -import lombok.Getter;
  10 +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
12 11 import lombok.extern.slf4j.Slf4j;
  12 +import org.springframework.cache.annotation.Cacheable;
13 13 import org.springframework.stereotype.Service;
14 14 import org.springframework.validation.annotation.Validated;
15 15  
16   -import javax.annotation.PostConstruct;
17 16 import javax.annotation.Resource;
18 17 import java.util.List;
19   -import java.util.Map;
20 18  
21 19 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
22   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
23   -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
  20 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS;
  21 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS;
24 22  
25 23 /**
26 24 * 邮箱账号 Service 实现类
... ... @@ -39,46 +37,16 @@ public class MailAccountServiceImpl implements MailAccountService {
39 37 @Resource
40 38 private MailTemplateService mailTemplateService;
41 39  
42   - @Resource
43   - private MailProducer mailProducer;
44   -
45   - /**
46   - * 邮箱账号缓存
47   - * key:邮箱账号编码 {@link MailAccountDO#getId()}
48   - *
49   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
50   - */
51   - @Getter
52   - private volatile Map<Long, MailAccountDO> mailAccountCache;
53   -
54   - @Override
55   - @PostConstruct
56   - public void initLocalCache() {
57   - // 第一步:查询数据
58   - List<MailAccountDO> accounts = mailAccountMapper.selectList();
59   - log.info("[initLocalCache][缓存邮箱账号,数量:{}]", accounts.size());
60   -
61   - // 第二步:构建缓存
62   - mailAccountCache = convertMap(accounts, MailAccountDO::getId);
63   - }
64   -
65   - @Override
66   - public MailAccountDO getMailAccountFromCache(Long id) {
67   - return mailAccountCache.get(id);
68   - }
69   -
70 40 @Override
71 41 public Long createMailAccount(MailAccountCreateReqVO createReqVO) {
72 42 // 插入
73 43 MailAccountDO account = MailAccountConvert.INSTANCE.convert(createReqVO);
74 44 mailAccountMapper.insert(account);
75   -
76   - // 发送刷新消息
77   - mailProducer.sendMailAccountRefreshMessage();
78 45 return account.getId();
79 46 }
80 47  
81 48 @Override
  49 + @Cacheable(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#updateReqVO.id")
82 50 public void updateMailAccount(MailAccountUpdateReqVO updateReqVO) {
83 51 // 校验是否存在
84 52 validateMailAccountExists(updateReqVO.getId());
... ... @@ -86,11 +54,10 @@ public class MailAccountServiceImpl implements MailAccountService {
86 54 // 更新
87 55 MailAccountDO updateObj = MailAccountConvert.INSTANCE.convert(updateReqVO);
88 56 mailAccountMapper.updateById(updateObj);
89   - // 发送刷新消息
90   - mailProducer.sendMailAccountRefreshMessage();
91 57 }
92 58  
93 59 @Override
  60 + @Cacheable(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#id")
94 61 public void deleteMailAccount(Long id) {
95 62 // 校验是否存在账号
96 63 validateMailAccountExists(id);
... ... @@ -101,8 +68,6 @@ public class MailAccountServiceImpl implements MailAccountService {
101 68  
102 69 // 删除
103 70 mailAccountMapper.deleteById(id);
104   - // 发送刷新消息
105   - mailProducer.sendMailAccountRefreshMessage();
106 71 }
107 72  
108 73 private void validateMailAccountExists(Long id) {
... ... @@ -117,6 +82,12 @@ public class MailAccountServiceImpl implements MailAccountService {
117 82 }
118 83  
119 84 @Override
  85 + @Cacheable(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#id", unless = "#result == null")
  86 + public MailAccountDO getMailAccountFromCache(Long id) {
  87 + return getMailAccount(id);
  88 + }
  89 +
  90 + @Override
120 91 public PageResult<MailAccountDO> getMailAccountPage(MailAccountPageReqVO pageReqVO) {
121 92 return mailAccountMapper.selectPage(pageReqVO);
122 93 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateService.java
... ... @@ -19,11 +19,6 @@ import java.util.Map;
19 19 public interface MailTemplateService {
20 20  
21 21 /**
22   - * 初始化邮件模版的本地缓存
23   - */
24   - void initLocalCache();
25   -
26   - /**
27 22 * 邮件模版创建
28 23 *
29 24 * @param createReqVO 邮件信息
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImpl.java
... ... @@ -10,14 +10,14 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemp
10 10 import cn.iocoder.yudao.module.system.convert.mail.MailTemplateConvert;
11 11 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
12 12 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
13   -import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
  13 +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
14 14 import com.google.common.annotations.VisibleForTesting;
15   -import lombok.Getter;
16 15 import lombok.extern.slf4j.Slf4j;
  16 +import org.springframework.cache.annotation.CacheEvict;
  17 +import org.springframework.cache.annotation.Cacheable;
17 18 import org.springframework.stereotype.Service;
18 19 import org.springframework.validation.annotation.Validated;
19 20  
20   -import javax.annotation.PostConstruct;
21 21 import javax.annotation.Resource;
22 22 import javax.validation.Valid;
23 23 import java.util.List;
... ... @@ -25,8 +25,8 @@ import java.util.Map;
25 25 import java.util.regex.Pattern;
26 26  
27 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
29   -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
  28 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_CODE_EXISTS;
  29 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS;
30 30  
31 31 /**
32 32 * 邮箱模版 Service 实现类
... ... @@ -47,29 +47,6 @@ public class MailTemplateServiceImpl implements MailTemplateService {
47 47 @Resource
48 48 private MailTemplateMapper mailTemplateMapper;
49 49  
50   - @Resource
51   - private MailProducer mailProducer;
52   -
53   - /**
54   - * 邮件模板缓存
55   - * key:邮件模版标识 {@link MailTemplateDO#getCode()}
56   - *
57   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
58   - */
59   - @Getter
60   - private volatile Map<String, MailTemplateDO> mailTemplateCache;
61   -
62   - @Override
63   - @PostConstruct
64   - public void initLocalCache() {
65   - // 第一步:查询数据
66   - List<MailTemplateDO> templates = mailTemplateMapper.selectList();
67   - log.info("[initLocalCache][缓存邮件模版,数量:{}]", templates.size());
68   -
69   - // 第二步:构建缓存
70   - mailTemplateCache = convertMap(templates, MailTemplateDO::getCode);
71   - }
72   -
73 50 @Override
74 51 public Long createMailTemplate(MailTemplateCreateReqVO createReqVO) {
75 52 // 校验 code 是否唯一
... ... @@ -79,12 +56,12 @@ public class MailTemplateServiceImpl implements MailTemplateService {
79 56 MailTemplateDO template = MailTemplateConvert.INSTANCE.convert(createReqVO)
80 57 .setParams(parseTemplateContentParams(createReqVO.getContent()));
81 58 mailTemplateMapper.insert(template);
82   - // 发送刷新消息
83   - mailProducer.sendMailTemplateRefreshMessage();
84 59 return template.getId();
85 60 }
86 61  
87 62 @Override
  63 + @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
  64 + allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理
88 65 public void updateMailTemplate(@Valid MailTemplateUpdateReqVO updateReqVO) {
89 66 // 校验是否存在
90 67 validateMailTemplateExists(updateReqVO.getId());
... ... @@ -95,12 +72,10 @@ public class MailTemplateServiceImpl implements MailTemplateService {
95 72 MailTemplateDO updateObj = MailTemplateConvert.INSTANCE.convert(updateReqVO)
96 73 .setParams(parseTemplateContentParams(updateReqVO.getContent()));
97 74 mailTemplateMapper.updateById(updateObj);
98   - // 发送刷新消息
99   - mailProducer.sendMailTemplateRefreshMessage();
100 75 }
101 76  
102 77 @VisibleForTesting
103   - public void validateCodeUnique(Long id, String code) {
  78 + void validateCodeUnique(Long id, String code) {
104 79 MailTemplateDO template = mailTemplateMapper.selectByCode(code);
105 80 if (template == null) {
106 81 return;
... ... @@ -113,14 +88,14 @@ public class MailTemplateServiceImpl implements MailTemplateService {
113 88 }
114 89  
115 90 @Override
  91 + @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
  92 + allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理
116 93 public void deleteMailTemplate(Long id) {
117 94 // 校验是否存在
118 95 validateMailTemplateExists(id);
119 96  
120 97 // 删除
121 98 mailTemplateMapper.deleteById(id);
122   - // 发送刷新消息
123   - mailProducer.sendMailTemplateRefreshMessage();
124 99 }
125 100  
126 101 private void validateMailTemplateExists(Long id) {
... ... @@ -133,6 +108,12 @@ public class MailTemplateServiceImpl implements MailTemplateService {
133 108 public MailTemplateDO getMailTemplate(Long id) {return mailTemplateMapper.selectById(id);}
134 109  
135 110 @Override
  111 + @Cacheable(value = RedisKeyConstants.MAIL_TEMPLATE, key = "#code", unless = "#result == null")
  112 + public MailTemplateDO getMailTemplateByCodeFromCache(String code) {
  113 + return mailTemplateMapper.selectByCode(code);
  114 + }
  115 +
  116 + @Override
136 117 public PageResult<MailTemplateDO> getMailTemplatePage(MailTemplatePageReqVO pageReqVO) {
137 118 return mailTemplateMapper.selectPage(pageReqVO);
138 119 }
... ... @@ -141,11 +122,6 @@ public class MailTemplateServiceImpl implements MailTemplateService {
141 122 public List<MailTemplateDO> getMailTemplateList() {return mailTemplateMapper.selectList();}
142 123  
143 124 @Override
144   - public MailTemplateDO getMailTemplateByCodeFromCache(String code) {
145   - return mailTemplateCache.get(code);
146   - }
147   -
148   - @Override
149 125 public String formatMailTemplateContent(String content, Map<String, Object> params) {
150 126 return StrUtil.format(content, params);
151 127 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifyTemplateService.java
... ... @@ -17,19 +17,6 @@ import java.util.Map;
17 17 public interface NotifyTemplateService {
18 18  
19 19 /**
20   - * 初始化站内信模板的本地缓存
21   - */
22   - void initLocalCache();
23   -
24   - /**
25   - * 获得站内信模板,从缓存中
26   - *
27   - * @param code 模板编码
28   - * @return 站内信模板
29   - */
30   - NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code);
31   -
32   - /**
33 20 * 创建站内信模版
34 21 *
35 22 * @param createReqVO 创建信息
... ... @@ -60,6 +47,14 @@ public interface NotifyTemplateService {
60 47 NotifyTemplateDO getNotifyTemplate(Long id);
61 48  
62 49 /**
  50 + * 获得站内信模板,从缓存中
  51 + *
  52 + * @param code 模板编码
  53 + * @return 站内信模板
  54 + */
  55 + NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code);
  56 +
  57 + /**
63 58 * 获得站内信模版分页
64 59 *
65 60 * @param pageReqVO 分页查询
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifyTemplateServiceImpl.java
... ... @@ -3,27 +3,28 @@ package cn.iocoder.yudao.module.system.service.notify;
3 3 import cn.hutool.core.util.ReUtil;
4 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 6 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateCreateReqVO;
8 7 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO;
9 8 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateUpdateReqVO;
10 9 import cn.iocoder.yudao.module.system.convert.notify.NotifyTemplateConvert;
11 10 import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
12 11 import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyTemplateMapper;
13   -import cn.iocoder.yudao.module.system.mq.producer.notify.NotifyProducer;
  12 +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
14 13 import com.google.common.annotations.VisibleForTesting;
15 14 import lombok.extern.slf4j.Slf4j;
  15 +import org.springframework.cache.annotation.CacheEvict;
  16 +import org.springframework.cache.annotation.Cacheable;
16 17 import org.springframework.stereotype.Service;
17 18 import org.springframework.validation.annotation.Validated;
18 19  
19   -import javax.annotation.PostConstruct;
20 20 import javax.annotation.Resource;
21 21 import java.util.List;
22 22 import java.util.Map;
23 23 import java.util.regex.Pattern;
24 24  
25 25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
26   -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
  26 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_CODE_DUPLICATE;
  27 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS;
27 28  
28 29 /**
29 30 * 站内信模版 Service 实现类
... ... @@ -43,36 +44,6 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
43 44 @Resource
44 45 private NotifyTemplateMapper notifyTemplateMapper;
45 46  
46   - @Resource
47   - private NotifyProducer notifyProducer;
48   -
49   - /**
50   - * 站内信模板缓存
51   - * key:站内信模板编码 {@link NotifyTemplateDO#getCode()}
52   - * <p>
53   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
54   - */
55   - private volatile Map<String, NotifyTemplateDO> notifyTemplateCache;
56   -
57   - /**
58   - * 初始化站内信模板的本地缓存
59   - */
60   - @Override
61   - @PostConstruct
62   - public void initLocalCache() {
63   - // 第一步:查询数据
64   - List<NotifyTemplateDO> templates = notifyTemplateMapper.selectList();
65   - log.info("[initLocalCache][缓存站内信模版,数量为:{}]", templates.size());
66   -
67   - // 第二步:构建缓存
68   - notifyTemplateCache = CollectionUtils.convertMap(templates, NotifyTemplateDO::getCode);
69   - }
70   -
71   - @Override
72   - public NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code) {
73   - return notifyTemplateCache.get(code);
74   - }
75   -
76 47 @Override
77 48 public Long createNotifyTemplate(NotifyTemplateCreateReqVO createReqVO) {
78 49 // 校验站内信编码是否重复
... ... @@ -82,13 +53,12 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
82 53 NotifyTemplateDO notifyTemplate = NotifyTemplateConvert.INSTANCE.convert(createReqVO);
83 54 notifyTemplate.setParams(parseTemplateContentParams(notifyTemplate.getContent()));
84 55 notifyTemplateMapper.insert(notifyTemplate);
85   -
86   - // 发送刷新消息
87   - notifyProducer.sendNotifyTemplateRefreshMessage();
88 56 return notifyTemplate.getId();
89 57 }
90 58  
91 59 @Override
  60 + @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
  61 + allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理
92 62 public void updateNotifyTemplate(NotifyTemplateUpdateReqVO updateReqVO) {
93 63 // 校验存在
94 64 validateNotifyTemplateExists(updateReqVO.getId());
... ... @@ -99,9 +69,6 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
99 69 NotifyTemplateDO updateObj = NotifyTemplateConvert.INSTANCE.convert(updateReqVO);
100 70 updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));
101 71 notifyTemplateMapper.updateById(updateObj);
102   -
103   - // 发送刷新消息
104   - notifyProducer.sendNotifyTemplateRefreshMessage();
105 72 }
106 73  
107 74 @VisibleForTesting
... ... @@ -110,13 +77,13 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
110 77 }
111 78  
112 79 @Override
  80 + @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
  81 + allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理
113 82 public void deleteNotifyTemplate(Long id) {
114 83 // 校验存在
115 84 validateNotifyTemplateExists(id);
116 85 // 删除
117 86 notifyTemplateMapper.deleteById(id);
118   - // 发送刷新消息
119   - notifyProducer.sendNotifyTemplateRefreshMessage();
120 87 }
121 88  
122 89 private void validateNotifyTemplateExists(Long id) {
... ... @@ -131,12 +98,19 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
131 98 }
132 99  
133 100 @Override
  101 + @Cacheable(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE, key = "#code",
  102 + unless = "#result == null")
  103 + public NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code) {
  104 + return notifyTemplateMapper.selectByCode(code);
  105 + }
  106 +
  107 + @Override
134 108 public PageResult<NotifyTemplateDO> getNotifyTemplatePage(NotifyTemplatePageReqVO pageReqVO) {
135 109 return notifyTemplateMapper.selectPage(pageReqVO);
136 110 }
137 111  
138 112 @VisibleForTesting
139   - public void validateNotifyTemplateCodeDuplicate(Long id, String code) {
  113 + void validateNotifyTemplateCodeDuplicate(Long id, String code) {
140 114 NotifyTemplateDO template = notifyTemplateMapper.selectByCode(code);
141 115 if (template == null) {
142 116 return;
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientService.java
... ... @@ -19,11 +19,6 @@ import java.util.Collection;
19 19 public interface OAuth2ClientService {
20 20  
21 21 /**
22   - * 初始化 OAuth2Client 的本地缓存
23   - */
24   - void initLocalCache();
25   -
26   - /**
27 22 * 创建 OAuth2 客户端
28 23 *
29 24 * @param createReqVO 创建信息
... ... @@ -54,6 +49,14 @@ public interface OAuth2ClientService {
54 49 OAuth2ClientDO getOAuth2Client(Long id);
55 50  
56 51 /**
  52 + * 获得 OAuth2 客户端,从缓存中
  53 + *
  54 + * @param clientId 客户端编号
  55 + * @return OAuth2 客户端
  56 + */
  57 + OAuth2ClientDO getOAuth2ClientFromCache(String clientId);
  58 +
  59 + /**
57 60 * 获得 OAuth2 客户端分页
58 61 *
59 62 * @param pageReqVO 分页查询
... ... @@ -82,7 +85,7 @@ public interface OAuth2ClientService {
82 85 * @param redirectUri 重定向地址
83 86 * @return 客户端
84 87 */
85   - OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret,
86   - String authorizedGrantType, Collection<String> scopes, String redirectUri);
  88 + OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, String authorizedGrantType,
  89 + Collection<String> scopes, String redirectUri);
87 90  
88 91 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImpl.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.oauth2;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjectUtil;
5 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.extra.spring.SpringUtil;
6 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 9 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
... ... @@ -12,22 +13,18 @@ import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2Cl
12 13 import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert;
13 14 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
14 15 import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2ClientMapper;
15   -import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer;
  16 +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
16 17 import com.google.common.annotations.VisibleForTesting;
17   -import lombok.Getter;
18   -import lombok.Setter;
19 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.cache.annotation.CacheEvict;
  20 +import org.springframework.cache.annotation.Cacheable;
20 21 import org.springframework.stereotype.Service;
21 22 import org.springframework.validation.annotation.Validated;
22 23  
23   -import javax.annotation.PostConstruct;
24 24 import javax.annotation.Resource;
25 25 import java.util.Collection;
26   -import java.util.List;
27   -import java.util.Map;
28 26  
29 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
30   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
31 28 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
32 29  
33 30 /**
... ... @@ -40,48 +37,21 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
40 37 @Slf4j
41 38 public class OAuth2ClientServiceImpl implements OAuth2ClientService {
42 39  
43   - /**
44   - * 客户端缓存
45   - * key:客户端编号 {@link OAuth2ClientDO#getClientId()} ()}
46   - *
47   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
48   - */
49   - @Getter // 解决单测
50   - @Setter // 解决单测
51   - private volatile Map<String, OAuth2ClientDO> clientCache;
52   -
53 40 @Resource
54 41 private OAuth2ClientMapper oauth2ClientMapper;
55 42  
56   - @Resource
57   - private OAuth2ClientProducer oauth2ClientProducer;
58   -
59   - /**
60   - * 初始化 {@link #clientCache} 缓存
61   - */
62   - @Override
63   - @PostConstruct
64   - public void initLocalCache() {
65   - // 第一步:查询数据
66   - List<OAuth2ClientDO> clients = oauth2ClientMapper.selectList();
67   - log.info("[initLocalCache][缓存 OAuth2 客户端,数量为:{}]", clients.size());
68   -
69   - // 第二步:构建缓存。
70   - clientCache = convertMap(clients, OAuth2ClientDO::getClientId);
71   - }
72   -
73 43 @Override
74 44 public Long createOAuth2Client(OAuth2ClientCreateReqVO createReqVO) {
75 45 validateClientIdExists(null, createReqVO.getClientId());
76 46 // 插入
77 47 OAuth2ClientDO oauth2Client = OAuth2ClientConvert.INSTANCE.convert(createReqVO);
78 48 oauth2ClientMapper.insert(oauth2Client);
79   - // 发送刷新消息
80   - oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
81 49 return oauth2Client.getId();
82 50 }
83 51  
84 52 @Override
  53 + @CacheEvict(cacheNames = RedisKeyConstants.OAUTH_CLIENT,
  54 + allEntries = true) // allEntries 清空所有缓存,因为可能修改到 clientId 字段,不好清理
85 55 public void updateOAuth2Client(OAuth2ClientUpdateReqVO updateReqVO) {
86 56 // 校验存在
87 57 validateOAuth2ClientExists(updateReqVO.getId());
... ... @@ -91,18 +61,16 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
91 61 // 更新
92 62 OAuth2ClientDO updateObj = OAuth2ClientConvert.INSTANCE.convert(updateReqVO);
93 63 oauth2ClientMapper.updateById(updateObj);
94   - // 发送刷新消息
95   - oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
96 64 }
97 65  
98 66 @Override
  67 + @CacheEvict(cacheNames = RedisKeyConstants.OAUTH_CLIENT,
  68 + allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 key,不好清理
99 69 public void deleteOAuth2Client(Long id) {
100 70 // 校验存在
101 71 validateOAuth2ClientExists(id);
102 72 // 删除
103 73 oauth2ClientMapper.deleteById(id);
104   - // 发送刷新消息
105   - oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
106 74 }
107 75  
108 76 private void validateOAuth2ClientExists(Long id) {
... ... @@ -132,15 +100,22 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
132 100 }
133 101  
134 102 @Override
  103 + @Cacheable(cacheNames = RedisKeyConstants.OAUTH_CLIENT, key = "#clientId",
  104 + unless = "#result == null")
  105 + public OAuth2ClientDO getOAuth2ClientFromCache(String clientId) {
  106 + return oauth2ClientMapper.selectByClientId(clientId);
  107 + }
  108 +
  109 + @Override
135 110 public PageResult<OAuth2ClientDO> getOAuth2ClientPage(OAuth2ClientPageReqVO pageReqVO) {
136 111 return oauth2ClientMapper.selectPage(pageReqVO);
137 112 }
138 113  
139 114 @Override
140   - public OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret,
141   - String authorizedGrantType, Collection<String> scopes, String redirectUri) {
  115 + public OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, String authorizedGrantType,
  116 + Collection<String> scopes, String redirectUri) {
142 117 // 校验客户端存在、且开启
143   - OAuth2ClientDO client = clientCache.get(clientId);
  118 + OAuth2ClientDO client = getSelf().getOAuth2ClientFromCache(clientId);
144 119 if (client == null) {
145 120 throw exception(OAUTH2_CLIENT_NOT_EXISTS);
146 121 }
... ... @@ -167,4 +142,13 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
167 142 return client;
168 143 }
169 144  
  145 + /**
  146 + * 获得自身的代理对象,解决 AOP 生效问题
  147 + *
  148 + * @return 自己
  149 + */
  150 + private OAuth2ClientServiceImpl getSelf() {
  151 + return SpringUtil.getBean(getClass());
  152 + }
  153 +
170 154 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java
... ... @@ -16,11 +16,6 @@ import java.util.List;
16 16 public interface MenuService {
17 17  
18 18 /**
19   - * 初始化菜单的本地缓存
20   - */
21   - void initLocalCache();
22   -
23   - /**
24 19 * 创建菜单
25 20 *
26 21 * @param reqVO 菜单信息
... ... @@ -67,36 +62,12 @@ public interface MenuService {
67 62 List<MenuDO> getMenuList(MenuListReqVO reqVO);
68 63  
69 64 /**
70   - * 获得所有菜单,从缓存中
71   - *
72   - * 任一参数为空时,则返回为空
73   - *
74   - * @param menuTypes 菜单类型数组
75   - * @param menusStatuses 菜单状态数组
76   - * @return 菜单列表
77   - */
78   - List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses);
79   -
80   - /**
81   - * 获得指定编号的菜单数组,从缓存中
82   - *
83   - * 任一参数为空时,则返回为空
84   - *
85   - * @param menuIds 菜单编号数组
86   - * @param menuTypes 菜单类型数组
87   - * @param menusStatuses 菜单状态数组
88   - * @return 菜单数组
89   - */
90   - List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
91   - Collection<Integer> menusStatuses);
92   -
93   - /**
94   - * 获得权限对应的菜单数组
  65 + * 获得权限对应的菜单编号数组
95 66 *
96 67 * @param permission 权限标识
97 68 * @return 数组
98 69 */
99   - List<MenuDO> getMenuListByPermissionFromCache(String permission);
  70 + List<Long> getMenuIdListByPermissionFromCache(String permission);
100 71  
101 72 /**
102 73 * 获得菜单
... ... @@ -106,4 +77,12 @@ public interface MenuService {
106 77 */
107 78 MenuDO getMenu(Long id);
108 79  
  80 + /**
  81 + * 获得菜单数组
  82 + *
  83 + * @param ids 菜单编号数组
  84 + * @return 菜单数组
  85 + */
  86 + List<MenuDO> getMenuList(Collection<Long> ids);
  87 +
109 88 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
1 1 package cn.iocoder.yudao.module.system.service.permission;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.util.StrUtil;
5   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 4 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuCreateReqVO;
7 5 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
8 6 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuUpdateReqVO;
9 7 import cn.iocoder.yudao.module.system.convert.permission.MenuConvert;
10 8 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
11 9 import cn.iocoder.yudao.module.system.dal.mysql.permission.MenuMapper;
  10 +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
12 11 import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
13   -import cn.iocoder.yudao.module.system.mq.producer.permission.MenuProducer;
14 12 import cn.iocoder.yudao.module.system.service.tenant.TenantService;
15 13 import com.google.common.annotations.VisibleForTesting;
16   -import com.google.common.collect.ImmutableMap;
17   -import com.google.common.collect.ImmutableMultimap;
18   -import com.google.common.collect.Multimap;
19   -import lombok.Getter;
20   -import lombok.Setter;
21 14 import lombok.extern.slf4j.Slf4j;
  15 +import org.springframework.cache.annotation.CacheEvict;
  16 +import org.springframework.cache.annotation.Cacheable;
22 17 import org.springframework.context.annotation.Lazy;
23 18 import org.springframework.stereotype.Service;
24 19 import org.springframework.transaction.annotation.Transactional;
25   -import org.springframework.transaction.support.TransactionSynchronization;
26   -import org.springframework.transaction.support.TransactionSynchronizationManager;
27 20  
28   -import javax.annotation.PostConstruct;
29 21 import javax.annotation.Resource;
30   -import java.util.*;
31   -import java.util.stream.Collectors;
  22 +import java.util.Collection;
  23 +import java.util.List;
32 24  
33 25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
34 27 import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
35 28 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
36 29  
... ... @@ -43,26 +36,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
43 36 @Slf4j
44 37 public class MenuServiceImpl implements MenuService {
45 38  
46   - /**
47   - * 菜单缓存
48   - * key:菜单编号
49   - *
50   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
51   - */
52   - @Getter
53   - @Setter
54   - private volatile Map<Long, MenuDO> menuCache;
55   - /**
56   - * 权限与菜单缓存
57   - * key:权限 {@link MenuDO#getPermission()}
58   - * value:MenuDO 数组,因为一个权限可能对应多个 MenuDO 对象
59   - *
60   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
61   - */
62   - @Getter
63   - @Setter
64   - private volatile Multimap<String, MenuDO> permissionMenuCache;
65   -
66 39 @Resource
67 40 private MenuMapper menuMapper;
68 41 @Resource
... ... @@ -71,33 +44,8 @@ public class MenuServiceImpl implements MenuService {
71 44 @Lazy // 延迟,避免循环依赖报错
72 45 private TenantService tenantService;
73 46  
74   - @Resource
75   - private MenuProducer menuProducer;
76   -
77   - /**
78   - * 初始化 {@link #menuCache} 和 {@link #permissionMenuCache} 缓存
79   - */
80   - @Override
81   - @PostConstruct
82   - public synchronized void initLocalCache() {
83   - // 第一步:查询数据
84   - List<MenuDO> menuList = menuMapper.selectList();
85   - log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size());
86   -
87   - // 第二步:构建缓存
88   - ImmutableMap.Builder<Long, MenuDO> menuCacheBuilder = ImmutableMap.builder();
89   - ImmutableMultimap.Builder<String, MenuDO> permMenuCacheBuilder = ImmutableMultimap.builder();
90   - menuList.forEach(menuDO -> {
91   - menuCacheBuilder.put(menuDO.getId(), menuDO);
92   - if (StrUtil.isNotEmpty(menuDO.getPermission())) { // 会存在 permission 为 null 的情况,导致 put 报 NPE 异常
93   - permMenuCacheBuilder.put(menuDO.getPermission(), menuDO);
94   - }
95   - });
96   - menuCache = menuCacheBuilder.build();
97   - permissionMenuCache = permMenuCacheBuilder.build();
98   - }
99   -
100 47 @Override
  48 + @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#reqVO.permission")
101 49 public Long createMenu(MenuCreateReqVO reqVO) {
102 50 // 校验父菜单存在
103 51 validateParentMenu(reqVO.getParentId(), null);
... ... @@ -108,13 +56,13 @@ public class MenuServiceImpl implements MenuService {
108 56 MenuDO menu = MenuConvert.INSTANCE.convert(reqVO);
109 57 initMenuProperty(menu);
110 58 menuMapper.insert(menu);
111   - // 发送刷新消息
112   - menuProducer.sendMenuRefreshMessage();
113 59 // 返回
114 60 return menu.getId();
115 61 }
116 62  
117 63 @Override
  64 + @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
  65 + allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理,简单有效
118 66 public void updateMenu(MenuUpdateReqVO reqVO) {
119 67 // 校验更新的菜单是否存在
120 68 if (menuMapper.selectById(reqVO.getId()) == null) {
... ... @@ -129,34 +77,25 @@ public class MenuServiceImpl implements MenuService {
129 77 MenuDO updateObject = MenuConvert.INSTANCE.convert(reqVO);
130 78 initMenuProperty(updateObject);
131 79 menuMapper.updateById(updateObject);
132   - // 发送刷新消息
133   - menuProducer.sendMenuRefreshMessage();
134 80 }
135 81  
136 82 @Override
137 83 @Transactional(rollbackFor = Exception.class)
138   - public void deleteMenu(Long menuId) {
  84 + @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
  85 + allEntries = true) // allEntries 清空所有缓存,因为此时不知道 id 对应的 permission 是多少。直接清理,简单有效
  86 + public void deleteMenu(Long id) {
139 87 // 校验是否还有子菜单
140   - if (menuMapper.selectCountByParentId(menuId) > 0) {
  88 + if (menuMapper.selectCountByParentId(id) > 0) {
141 89 throw exception(MENU_EXISTS_CHILDREN);
142 90 }
143 91 // 校验删除的菜单是否存在
144   - if (menuMapper.selectById(menuId) == null) {
  92 + if (menuMapper.selectById(id) == null) {
145 93 throw exception(MENU_NOT_EXISTS);
146 94 }
147 95 // 标记删除
148   - menuMapper.deleteById(menuId);
  96 + menuMapper.deleteById(id);
149 97 // 删除授予给角色的权限
150   - permissionService.processMenuDeleted(menuId);
151   - // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
152   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
153   -
154   - @Override
155   - public void afterCommit() {
156   - menuProducer.sendMenuRefreshMessage();
157   - }
158   -
159   - });
  98 + permissionService.processMenuDeleted(id);
160 99 }
161 100  
162 101 @Override
... ... @@ -178,49 +117,31 @@ public class MenuServiceImpl implements MenuService {
178 117 }
179 118  
180 119 @Override
181   - public List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses) {
182   - // 任一一个参数为空,则返回空
183   - if (CollectionUtils.isAnyEmpty(menuTypes, menusStatuses)) {
184   - return Collections.emptyList();
185   - }
186   - // 创建新数组,避免缓存被修改
187   - return menuCache.values().stream().filter(menu -> menuTypes.contains(menu.getType())
188   - && menusStatuses.contains(menu.getStatus()))
189   - .collect(Collectors.toList());
190   - }
191   -
192   - @Override
193   - public List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
194   - Collection<Integer> menusStatuses) {
195   - // 任一一个参数为空,则返回空
196   - if (CollectionUtils.isAnyEmpty(menuIds, menuTypes, menusStatuses)) {
197   - return Collections.emptyList();
198   - }
199   - return menuCache.values().stream().filter(menu -> menuIds.contains(menu.getId())
200   - && menuTypes.contains(menu.getType())
201   - && menusStatuses.contains(menu.getStatus()))
202   - .collect(Collectors.toList());
  120 + @Cacheable(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#permission")
  121 + public List<Long> getMenuIdListByPermissionFromCache(String permission) {
  122 + List<MenuDO> menus = menuMapper.selectListByPermission(permission);
  123 + return convertList(menus, MenuDO::getId);
203 124 }
204 125  
205 126 @Override
206   - public List<MenuDO> getMenuListByPermissionFromCache(String permission) {
207   - return new ArrayList<>(permissionMenuCache.get(permission));
  127 + public MenuDO getMenu(Long id) {
  128 + return menuMapper.selectById(id);
208 129 }
209 130  
210 131 @Override
211   - public MenuDO getMenu(Long id) {
212   - return menuMapper.selectById(id);
  132 + public List<MenuDO> getMenuList(Collection<Long> ids) {
  133 + return menuMapper.selectBatchIds(ids);
213 134 }
214 135  
215 136 /**
216 137 * 校验父菜单是否合法
217   - *
  138 + * <p>
218 139 * 1. 不能设置自己为父菜单
219 140 * 2. 父菜单不存在
220 141 * 3. 父菜单必须是 {@link MenuTypeEnum#MENU} 菜单类型
221 142 *
222 143 * @param parentId 父菜单编号
223   - * @param childId 当前菜单编号
  144 + * @param childId 当前菜单编号
224 145 */
225 146 @VisibleForTesting
226 147 void validateParentMenu(Long parentId, Long childId) {
... ... @@ -238,19 +159,19 @@ public class MenuServiceImpl implements MenuService {
238 159 }
239 160 // 父菜单必须是目录或者菜单类型
240 161 if (!MenuTypeEnum.DIR.getType().equals(menu.getType())
241   - && !MenuTypeEnum.MENU.getType().equals(menu.getType())) {
  162 + && !MenuTypeEnum.MENU.getType().equals(menu.getType())) {
242 163 throw exception(MENU_PARENT_NOT_DIR_OR_MENU);
243 164 }
244 165 }
245 166  
246 167 /**
247 168 * 校验菜单是否合法
248   - *
  169 + * <p>
249 170 * 1. 校验相同父菜单编号下,是否存在相同的菜单名
250 171 *
251   - * @param name 菜单名字
  172 + * @param name 菜单名字
252 173 * @param parentId 父菜单编号
253   - * @param id 菜单编号
  174 + * @param id 菜单编号
254 175 */
255 176 @VisibleForTesting
256 177 void validateMenu(Long parentId, String name, Long id) {
... ... @@ -269,7 +190,7 @@ public class MenuServiceImpl implements MenuService {
269 190  
270 191 /**
271 192 * 初始化菜单的通用属性。
272   - *
  193 + * <p>
273 194 * 例如说,只有目录或者菜单类型的菜单,才设置 icon
274 195 *
275 196 * @param menu 菜单
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionService.java
1 1 package cn.iocoder.yudao.module.system.service.permission;
2 2  
3 3 import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
4   -import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
5   -import org.springframework.lang.Nullable;
6 4  
7 5 import java.util.Collection;
8   -import java.util.List;
9 6 import java.util.Set;
10 7  
  8 +import static java.util.Collections.singleton;
  9 +
11 10 /**
12 11 * 权限 Service 接口
13   - *
  12 + * <p>
14 13 * 提供用户-角色、角色-菜单、角色-部门的关联权限处理
15 14 *
16 15 * @author 芋道源码
... ... @@ -18,118 +17,123 @@ import java.util.Set;
18 17 public interface PermissionService {
19 18  
20 19 /**
21   - * 初始化权限的本地缓存
  20 + * 判断是否有权限,任一一个即可
  21 + *
  22 + * @param userId 用户编号
  23 + * @param permissions 权限
  24 + * @return 是否
22 25 */
23   - void initLocalCache();
  26 + boolean hasAnyPermissions(Long userId, String... permissions);
24 27  
25 28 /**
26   - * 获得角色们拥有的菜单列表,从缓存中获取
27   - *
28   - * 任一参数为空时,则返回为空
  29 + * 判断是否有角色,任一一个即可
29 30 *
30   - * @param roleIds 角色编号数组
31   - * @param menuTypes 菜单类型数组
32   - * @param menusStatuses 菜单状态数组
33   - * @return 菜单列表
  31 + * @param roles 角色数组
  32 + * @return 是否
34 33 */
35   - List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
36   - Collection<Integer> menusStatuses);
  34 + boolean hasAnyRoles(Long userId, String... roles);
  35 +
  36 + // ========== 角色-菜单的相关方法 ==========
37 37  
38 38 /**
39   - * 获得用户拥有的角色编号集合,从缓存中获取
  39 + * 设置角色菜单
40 40 *
41   - * @param userId 用户编号
42   - * @param roleStatuses 角色状态集合. 允许为空,为空时不过滤
43   - * @return 角色编号集合
  41 + * @param roleId 角色编号
  42 + * @param menuIds 菜单编号集合
44 43 */
45   - Set<Long> getUserRoleIdsFromCache(Long userId, @Nullable Collection<Integer> roleStatuses);
  44 + void assignRoleMenu(Long roleId, Set<Long> menuIds);
46 45  
47 46 /**
48   - * 获得角色拥有的菜单编号集合
  47 + * 处理角色删除时,删除关联授权数据
49 48 *
50 49 * @param roleId 角色编号
51   - * @return 菜单编号集合
52 50 */
53   - Set<Long> getRoleMenuIds(Long roleId);
  51 + void processRoleDeleted(Long roleId);
54 52  
55 53 /**
56   - * 获得拥有多个角色的用户编号集合
  54 + * 处理菜单删除时,删除关联授权数据
57 55 *
58   - * @param roleIds 角色编号集合
59   - * @return 用户编号集合
  56 + * @param menuId 菜单编号
60 57 */
61   - Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds);
  58 + void processMenuDeleted(Long menuId);
62 59  
63 60 /**
64   - * 设置角色菜单
  61 + * 获得角色拥有的菜单编号集合
65 62 *
66 63 * @param roleId 角色编号
67   - * @param menuIds 菜单编号集合
  64 + * @return 菜单编号集合
68 65 */
69   - void assignRoleMenu(Long roleId, Set<Long> menuIds);
  66 + default Set<Long> getRoleMenuListByRoleId(Long roleId) {
  67 + return getRoleMenuListByRoleId(singleton(roleId));
  68 + }
70 69  
71 70 /**
72   - * 获得用户拥有的角色编号集合
  71 + * 获得角色们拥有的菜单编号集合
73 72 *
74   - * @param userId 用户编号
75   - * @return 角色编号集合
  73 + * @param roleIds 角色编号数组
  74 + * @return 菜单编号集合
76 75 */
77   - Set<Long> getUserRoleIdListByUserId(Long userId);
  76 + Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds);
78 77  
79 78 /**
80   - * 设置用户角色
  79 + * 获得拥有指定菜单的角色编号数组,从缓存中获取
81 80 *
82   - * @param userId 角色编号
83   - * @param roleIds 角色编号集合
  81 + * @param menuId 菜单编号
  82 + * @return 角色编号数组
84 83 */
85   - void assignUserRole(Long userId, Set<Long> roleIds);
  84 + Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId);
  85 +
  86 + // ========== 用户-角色的相关方法 ==========
86 87  
87 88 /**
88   - * 设置角色的数据权限
  89 + * 设置用户角色
89 90 *
90   - * @param roleId 角色编号
91   - * @param dataScope 数据范围
92   - * @param dataScopeDeptIds 部门编号数组
  91 + * @param userId 角色编号
  92 + * @param roleIds 角色编号集合
93 93 */
94   - void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds);
  94 + void assignUserRole(Long userId, Set<Long> roleIds);
95 95  
96 96 /**
97   - * 处理角色删除时,删除关联授权数据
  97 + * 处理用户删除时,删除关联授权数据
98 98 *
99   - * @param roleId 角色编号
  99 + * @param userId 用户编号
100 100 */
101   - void processRoleDeleted(Long roleId);
  101 + void processUserDeleted(Long userId);
102 102  
103 103 /**
104   - * 处理菜单删除时,删除关联授权数据
  104 + * 获得拥有多个角色的用户编号集合
105 105 *
106   - * @param menuId 菜单编号
  106 + * @param roleIds 角色编号集合
  107 + * @return 用户编号集合
107 108 */
108   - void processMenuDeleted(Long menuId);
  109 + Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds);
109 110  
110 111 /**
111   - * 处理用户删除是,删除关联授权数据
  112 + * 获得用户拥有的角色编号集合
112 113 *
113 114 * @param userId 用户编号
  115 + * @return 角色编号集合
114 116 */
115   - void processUserDeleted(Long userId);
  117 + Set<Long> getUserRoleIdListByUserId(Long userId);
116 118  
117 119 /**
118   - * 判断是否有权限,任一一个即可
  120 + * 获得用户拥有的角色编号集合,从缓存中获取
119 121 *
120 122 * @param userId 用户编号
121   - * @param permissions 权限
122   - * @return 是否
  123 + * @return 角色编号集合
123 124 */
124   - boolean hasAnyPermissions(Long userId, String... permissions);
  125 + Set<Long> getUserRoleIdListByUserIdFromCache(Long userId);
  126 +
  127 + // ========== 用户-部门的相关方法 ==========
125 128  
126 129 /**
127   - * 判断是否有角色,任一一个即可
  130 + * 设置角色的数据权限
128 131 *
129   - * @param roles 角色数组
130   - * @return 是否
  132 + * @param roleId 角色编号
  133 + * @param dataScope 数据范围
  134 + * @param dataScopeDeptIds 部门编号数组
131 135 */
132   - boolean hasAnyRoles(Long userId, String... roles);
  136 + void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds);
133 137  
134 138 /**
135 139 * 获得登陆用户的部门数据权限
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java
... ... @@ -3,45 +3,38 @@ package cn.iocoder.yudao.module.system.service.permission;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.CollectionUtil;
5 5 import cn.hutool.core.util.ArrayUtil;
  6 +import cn.hutool.extra.spring.SpringUtil;
6 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 8 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8   -import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
9   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
10 9 import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
11   -import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
12   -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
13 10 import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
14   -import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
15 11 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
16 12 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
17 13 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
18 14 import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
19 15 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
20 16 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
  17 +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
21 18 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
22   -import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
23 19 import cn.iocoder.yudao.module.system.service.dept.DeptService;
24 20 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
  21 +import com.baomidou.dynamic.datasource.annotation.DSTransactional;
25 22 import com.google.common.annotations.VisibleForTesting;
26 23 import com.google.common.base.Suppliers;
27   -import com.google.common.collect.ImmutableMultimap;
28   -import com.google.common.collect.Multimap;
29 24 import com.google.common.collect.Sets;
30   -import lombok.Getter;
31   -import lombok.Setter;
32 25 import lombok.extern.slf4j.Slf4j;
  26 +import org.springframework.cache.annotation.CacheEvict;
  27 +import org.springframework.cache.annotation.Cacheable;
  28 +import org.springframework.cache.annotation.Caching;
33 29 import org.springframework.stereotype.Service;
34 30 import org.springframework.transaction.annotation.Transactional;
35   -import org.springframework.transaction.support.TransactionSynchronization;
36   -import org.springframework.transaction.support.TransactionSynchronizationManager;
37 31  
38   -import javax.annotation.PostConstruct;
39 32 import javax.annotation.Resource;
40 33 import java.util.*;
41 34 import java.util.function.Supplier;
42 35  
43 36 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
44   -import static java.util.Collections.singleton;
  37 +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
45 38  
46 39 /**
47 40 * 权限 Service 实现类
... ... @@ -52,38 +45,6 @@ import static java.util.Collections.singleton;
52 45 @Slf4j
53 46 public class PermissionServiceImpl implements PermissionService {
54 47  
55   - /**
56   - * 角色编号与菜单编号的缓存映射
57   - * key:角色编号
58   - * value:菜单编号的数组
59   - *
60   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
61   - */
62   - @Getter
63   - @Setter // 单元测试需要
64   - private volatile Multimap<Long, Long> roleMenuCache;
65   - /**
66   - * 菜单编号与角色编号的缓存映射
67   - * key:菜单编号
68   - * value:角色编号的数组
69   - *
70   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
71   - */
72   - @Getter
73   - @Setter // 单元测试需要
74   - private volatile Multimap<Long, Long> menuRoleCache;
75   -
76   - /**
77   - * 用户编号与角色编号的缓存映射
78   - * key:用户编号
79   - * value:角色编号的数组
80   - *
81   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
82   - */
83   - @Getter
84   - @Setter // 单元测试需要
85   - private volatile Map<Long, Set<Long>> userRoleCache;
86   -
87 48 @Resource
88 49 private RoleMenuMapper roleMenuMapper;
89 50 @Resource
... ... @@ -98,115 +59,89 @@ public class PermissionServiceImpl implements PermissionService {
98 59 @Resource
99 60 private AdminUserService userService;
100 61  
101   - @Resource
102   - private PermissionProducer permissionProducer;
103   -
104 62 @Override
105   - @PostConstruct
106   - public void initLocalCache() {
107   - initLocalCacheForRoleMenu();
108   - initLocalCacheForUserRole();
109   - }
  63 + public boolean hasAnyPermissions(Long userId, String... permissions) {
  64 + // 如果为空,说明已经有权限
  65 + if (ArrayUtil.isEmpty(permissions)) {
  66 + return true;
  67 + }
110 68  
111   - /**
112   - * 刷新 RoleMenu 本地缓存
113   - */
114   - @VisibleForTesting
115   - void initLocalCacheForRoleMenu() {
116   - // 注意:忽略自动多租户,因为要全局初始化缓存
117   - TenantUtils.executeIgnore(() -> {
118   - // 第一步:查询数据
119   - List<RoleMenuDO> roleMenus = roleMenuMapper.selectList();
120   - log.info("[initLocalCacheForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
121   -
122   - // 第二步:构建缓存
123   - ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
124   - ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
125   - roleMenus.forEach(roleMenuDO -> {
126   - roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
127   - menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
128   - });
129   - roleMenuCache = roleMenuCacheBuilder.build();
130   - menuRoleCache = menuRoleCacheBuilder.build();
131   - });
  69 + // 获得当前登录的角色。如果为空,说明没有权限
  70 + List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
  71 + if (CollUtil.isEmpty(roles)) {
  72 + return false;
  73 + }
  74 +
  75 + // 情况一:遍历判断每个权限,如果有一满足,说明有权限
  76 + for (String permission : permissions) {
  77 + if (hasAnyPermission(roles, permission)) {
  78 + return true;
  79 + }
  80 + }
  81 +
  82 + // 情况二:如果是超管,也说明有权限
  83 + return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));
132 84 }
133 85  
134 86 /**
135   - * 刷新 UserRole 本地缓存
  87 + * 判断指定角色,是否拥有该 permission 权限
  88 + *
  89 + * @param roles 指定角色数组
  90 + * @param permission 权限标识
  91 + * @return 是否拥有
136 92 */
137   - @VisibleForTesting
138   - void initLocalCacheForUserRole() {
139   - // 注意:忽略自动多租户,因为要全局初始化缓存
140   - TenantUtils.executeIgnore(() -> {
141   - // 第一步:加载数据
142   - List<UserRoleDO> userRoles = userRoleMapper.selectList();
143   - log.info("[initLocalCacheForUserRole][缓存用户与角色,数量为:{}]", userRoles.size());
144   -
145   - // 第二步:构建缓存。
146   - ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
147   - userRoles.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
148   - userRoleCache = CollectionUtils.convertMultiMap2(userRoles, UserRoleDO::getUserId, UserRoleDO::getRoleId);
149   - });
150   - }
151   -
152   - @Override
153   - public List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
154   - Collection<Integer> menusStatuses) {
155   - // 任一一个参数为空时,不返回任何菜单
156   - if (CollectionUtils.isAnyEmpty(roleIds, menuTypes, menusStatuses)) {
157   - return Collections.emptyList();
  93 + private boolean hasAnyPermission(List<RoleDO> roles, String permission) {
  94 + List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission);
  95 + // 采用严格模式,如果权限找不到对应的 Menu 的话,也认为没有权限
  96 + if (CollUtil.isEmpty(menuIds)) {
  97 + return false;
158 98 }
159 99  
160   - // 判断角色是否包含超级管理员。如果是超级管理员,获取到全部
161   - List<RoleDO> roleList = roleService.getRoleListFromCache(roleIds);
162   - if (roleService.hasAnySuperAdmin(roleList)) {
163   - return menuService.getMenuListFromCache(menuTypes, menusStatuses);
  100 + // 判断是否有权限
  101 + Set<Long> roleIds = convertSet(roles, RoleDO::getId);
  102 + for (Long menuId : menuIds) {
  103 + // 获得拥有该菜单的角色编号集合
  104 + Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId);
  105 + // 如果有交集,说明有权限
  106 + if (CollUtil.containsAny(menuRoleIds, roleIds)) {
  107 + return true;
  108 + }
164 109 }
165   -
166   - // 获得角色拥有的菜单关联
167   - List<Long> menuIds = MapUtils.getList(roleMenuCache, roleIds);
168   - return menuService.getMenuListFromCache(menuIds, menuTypes, menusStatuses);
  110 + return false;
169 111 }
170 112  
171 113 @Override
172   - public Set<Long> getUserRoleIdsFromCache(Long userId, Collection<Integer> roleStatuses) {
173   - Set<Long> cacheRoleIds = userRoleCache.get(userId);
174   - // 创建用户的时候没有分配角色,会存在空指针异常
175   - if (CollUtil.isEmpty(cacheRoleIds)) {
176   - return Collections.emptySet();
177   - }
178   - Set<Long> roleIds = new HashSet<>(cacheRoleIds);
179   - // 过滤角色状态
180   - if (CollectionUtil.isNotEmpty(roleStatuses)) {
181   - roleIds.removeIf(roleId -> {
182   - RoleDO role = roleService.getRoleFromCache(roleId);
183   - return role == null || !roleStatuses.contains(role.getStatus());
184   - });
  114 + public boolean hasAnyRoles(Long userId, String... roles) {
  115 + // 如果为空,说明已经有权限
  116 + if (ArrayUtil.isEmpty(roles)) {
  117 + return true;
185 118 }
186   - return roleIds;
187   - }
188 119  
189   - @Override
190   - public Set<Long> getRoleMenuIds(Long roleId) {
191   - // 如果是管理员的情况下,获取全部菜单编号
192   - if (roleService.hasAnySuperAdmin(Collections.singleton(roleId))) {
193   - return convertSet(menuService.getMenuList(), MenuDO::getId);
  120 + // 获得当前登录的角色。如果为空,说明没有权限
  121 + List<RoleDO> roleList = getEnableUserRoleListByUserIdFromCache(userId);
  122 + if (CollUtil.isEmpty(roleList)) {
  123 + return false;
194 124 }
195   - // 如果是非管理员的情况下,获得拥有的菜单编号
196   - return convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
  125 +
  126 + // 判断是否有角色
  127 + Set<String> userRoles = convertSet(roleList, RoleDO::getCode);
  128 + return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
197 129 }
198 130  
  131 + // ========== 角色-菜单的相关方法 ==========
  132 +
199 133 @Override
200   - @Transactional(rollbackFor = Exception.class)
  134 + @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
  135 + @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
  136 + allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快
201 137 public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
202 138 // 获得角色拥有菜单编号
203   - Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId),
204   - RoleMenuDO::getMenuId);
  139 + Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
205 140 // 计算新增和删除的菜单编号
206 141 Collection<Long> createMenuIds = CollUtil.subtract(menuIds, dbMenuIds);
207 142 Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIds);
208 143 // 执行新增和删除。对于已经授权的菜单,不用做任何处理
209   - if (!CollectionUtil.isEmpty(createMenuIds)) {
  144 + if (CollUtil.isNotEmpty(createMenuIds)) {
210 145 roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
211 146 RoleMenuDO entity = new RoleMenuDO();
212 147 entity.setRoleId(roleId);
... ... @@ -214,34 +149,57 @@ public class PermissionServiceImpl implements PermissionService {
214 149 return entity;
215 150 }));
216 151 }
217   - if (!CollectionUtil.isEmpty(deleteMenuIds)) {
  152 + if (CollUtil.isNotEmpty(deleteMenuIds)) {
218 153 roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
219 154 }
220   - // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
221   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
  155 + }
222 156  
223   - @Override
224   - public void afterCommit() {
225   - permissionProducer.sendRoleMenuRefreshMessage();
226   - }
  157 + @Override
  158 + @Transactional(rollbackFor = Exception.class)
  159 + @Caching(evict = {
  160 + @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
  161 + allEntries = true), // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 menu 缓存们
  162 + @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST,
  163 + allEntries = true) // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 user 缓存们
  164 + })
  165 + public void processRoleDeleted(Long roleId) {
  166 + // 标记删除 UserRole
  167 + userRoleMapper.deleteListByRoleId(roleId);
  168 + // 标记删除 RoleMenu
  169 + roleMenuMapper.deleteListByRoleId(roleId);
  170 + }
227 171  
228   - });
  172 + @Override
  173 + @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
  174 + public void processMenuDeleted(Long menuId) {
  175 + roleMenuMapper.deleteListByMenuId(menuId);
229 176 }
230 177  
231 178 @Override
232   - public Set<Long> getUserRoleIdListByUserId(Long userId) {
233   - return convertSet(userRoleMapper.selectListByUserId(userId),
234   - UserRoleDO::getRoleId);
  179 + public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
  180 + if (CollUtil.isEmpty(roleIds)) {
  181 + return Collections.emptySet();
  182 + }
  183 +
  184 + // 如果是管理员的情况下,获取全部菜单编号
  185 + if (roleService.hasAnySuperAdmin(roleIds)) {
  186 + return convertSet(menuService.getMenuList(), MenuDO::getId);
  187 + }
  188 + // 如果是非管理员的情况下,获得拥有的菜单编号
  189 + return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
235 190 }
236 191  
237 192 @Override
238   - public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
239   - return convertSet(userRoleMapper.selectListByRoleIds(roleIds),
240   - UserRoleDO::getUserId);
  193 + @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
  194 + public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {
  195 + return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);
241 196 }
242 197  
  198 + // ========== 用户-角色的相关方法 ==========
  199 +
243 200 @Override
244   - @Transactional(rollbackFor = Exception.class)
  201 + @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
  202 + @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
245 203 public void assignUserRole(Long userId, Set<Long> roleIds) {
246 204 // 获得角色拥有角色编号
247 205 Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
... ... @@ -261,137 +219,68 @@ public class PermissionServiceImpl implements PermissionService {
261 219 if (!CollectionUtil.isEmpty(deleteMenuIds)) {
262 220 userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
263 221 }
264   - // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
265   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
266   -
267   - @Override
268   - public void afterCommit() {
269   - permissionProducer.sendUserRoleRefreshMessage();
270   - }
271   -
272   - });
273 222 }
274 223  
275 224 @Override
276   - public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {
277   - roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
  225 + @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
  226 + public void processUserDeleted(Long userId) {
  227 + userRoleMapper.deleteListByUserId(userId);
278 228 }
279 229  
280 230 @Override
281   - @Transactional(rollbackFor = Exception.class)
282   - public void processRoleDeleted(Long roleId) {
283   - // 标记删除 UserRole
284   - userRoleMapper.deleteListByRoleId(roleId);
285   - // 标记删除 RoleMenu
286   - roleMenuMapper.deleteListByRoleId(roleId);
287   - // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
288   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
289   -
290   - @Override
291   - public void afterCommit() {
292   - permissionProducer.sendRoleMenuRefreshMessage();
293   - permissionProducer.sendUserRoleRefreshMessage();
294   - }
295   -
296   - });
  231 + public Set<Long> getUserRoleIdListByUserId(Long userId) {
  232 + return convertSet(userRoleMapper.selectListByUserId(userId), UserRoleDO::getRoleId);
297 233 }
298 234  
299 235 @Override
300   - @Transactional(rollbackFor = Exception.class)
301   - public void processMenuDeleted(Long menuId) {
302   - roleMenuMapper.deleteListByMenuId(menuId);
303   - // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
304   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
305   -
306   - @Override
307   - public void afterCommit() {
308   - permissionProducer.sendRoleMenuRefreshMessage();
309   - }
310   -
311   - });
  236 + @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
  237 + public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) {
  238 + return getUserRoleIdListByUserId(userId);
312 239 }
313 240  
314 241 @Override
315   - @Transactional(rollbackFor = Exception.class)
316   - public void processUserDeleted(Long userId) {
317   - userRoleMapper.deleteListByUserId(userId);
318   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
319   -
320   - @Override
321   - public void afterCommit() {
322   - permissionProducer.sendUserRoleRefreshMessage();
323   - }
324   -
325   - });
  242 + public Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds) {
  243 + return convertSet(userRoleMapper.selectListByRoleIds(roleIds), UserRoleDO::getUserId);
326 244 }
327 245  
328   - @Override
329   - public boolean hasAnyPermissions(Long userId, String... permissions) {
330   - // 如果为空,说明已经有权限
331   - if (ArrayUtil.isEmpty(permissions)) {
332   - return true;
333   - }
334   -
335   - // 获得当前登录的角色。如果为空,说明没有权限
336   - Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
337   - if (CollUtil.isEmpty(roleIds)) {
338   - return false;
339   - }
340   - // 判断是否是超管。如果是,当然符合条件
341   - if (roleService.hasAnySuperAdmin(roleIds)) {
342   - return true;
343   - }
344   -
345   - // 遍历权限,判断是否有一个满足
346   - return Arrays.stream(permissions).anyMatch(permission -> {
347   - List<MenuDO> menuList = menuService.getMenuListByPermissionFromCache(permission);
348   - // 采用严格模式,如果权限找不到对应的 Menu 的话,认为
349   - if (CollUtil.isEmpty(menuList)) {
350   - return false;
351   - }
352   - // 获得是否拥有该权限,任一一个
353   - return menuList.stream().anyMatch(menu -> CollUtil.containsAny(roleIds,
354   - menuRoleCache.get(menu.getId())));
355   - });
  246 + /**
  247 + * 获得用户拥有的角色,并且这些角色是开启状态的
  248 + *
  249 + * @param userId 用户编号
  250 + * @return 用户拥有的角色
  251 + */
  252 + @VisibleForTesting
  253 + List<RoleDO> getEnableUserRoleListByUserIdFromCache(Long userId) {
  254 + // 获得用户拥有的角色编号
  255 + Set<Long> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId);
  256 + // 获得角色数组,并移除被禁用的
  257 + List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);
  258 + roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));
  259 + return roles;
356 260 }
357 261  
358   - @Override
359   - public boolean hasAnyRoles(Long userId, String... roles) {
360   - // 如果为空,说明已经有权限
361   - if (ArrayUtil.isEmpty(roles)) {
362   - return true;
363   - }
  262 + // ========== 用户-部门的相关方法 ==========
364 263  
365   - // 获得当前登录的角色。如果为空,说明没有权限
366   - Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
367   - if (CollUtil.isEmpty(roleIds)) {
368   - return false;
369   - }
370   - // 判断是否是超管。如果是,当然符合条件
371   - if (roleService.hasAnySuperAdmin(roleIds)) {
372   - return true;
373   - }
374   - Set<String> userRoles = convertSet(roleService.getRoleListFromCache(roleIds),
375   - RoleDO::getCode);
376   - return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
  264 + @Override
  265 + public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {
  266 + roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
377 267 }
378 268  
379 269 @Override
380 270 @DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题
381   - @TenantIgnore // 忽略多租户的自动过滤。如果不忽略,会导致添加租户时,因为切换租户,导致获取不到 User。即使忽略,本身该方法不存在跨租户的操作,不会存在问题。
382 271 public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
383 272 // 获得用户的角色
384   - Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
  273 + List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
  274 +
385 275 // 如果角色为空,则只能查看自己
386 276 DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
387   - if (CollUtil.isEmpty(roleIds)) {
  277 + if (CollUtil.isEmpty(roles)) {
388 278 result.setSelf(true);
389 279 return result;
390 280 }
391   - List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);
392 281  
393 282 // 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询
394   - Supplier<Long> userDeptIdCache = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
  283 + Supplier<Long> userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
395 284 // 遍历每个角色,计算
396 285 for (RoleDO role : roles) {
397 286 // 为空时,跳过
... ... @@ -408,20 +297,19 @@ public class PermissionServiceImpl implements PermissionService {
408 297 CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
409 298 // 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
410 299 // 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
411   - CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
  300 + CollUtil.addAll(result.getDeptIds(), userDeptId.get());
412 301 continue;
413 302 }
414 303 // 情况三,DEPT_ONLY
415 304 if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
416   - CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptIdCache.get());
  305 + CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());
417 306 continue;
418 307 }
419 308 // 情况四,DEPT_DEPT_AND_CHILD
420 309 if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
421   - List<DeptDO> depts = deptService.getDeptListByParentIdFromCache(userDeptIdCache.get(), true);
422   - CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, DeptDO::getId));
  310 + CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get()));
423 311 // 添加本身部门编号
424   - CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
  312 + CollUtil.addAll(result.getDeptIds(), userDeptId.get());
425 313 continue;
426 314 }
427 315 // 情况五,SELF
... ... @@ -430,9 +318,18 @@ public class PermissionServiceImpl implements PermissionService {
430 318 continue;
431 319 }
432 320 // 未知情况,error log 即可
433   - log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, JsonUtils.toJsonString(result));
  321 + log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result));
434 322 }
435 323 return result;
436 324 }
437 325  
  326 + /**
  327 + * 获得自身的代理对象,解决 AOP 生效问题
  328 + *
  329 + * @return 自己
  330 + */
  331 + private PermissionServiceImpl getSelf() {
  332 + return SpringUtil.getBean(getClass());
  333 + }
  334 +
438 335 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java
... ... @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleEx
6 6 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
7 7 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleUpdateReqVO;
8 8 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
9   -import org.springframework.lang.Nullable;
10 9  
11 10 import javax.validation.Valid;
12 11 import java.util.Collection;
... ... @@ -21,11 +20,6 @@ import java.util.Set;
21 20 public interface RoleService {
22 21  
23 22 /**
24   - * 初始化角色的本地缓存
25   - */
26   - void initLocalCache();
27   -
28   - /**
29 23 * 创建角色
30 24 *
31 25 * @param reqVO 创建角色信息
... ... @@ -66,6 +60,14 @@ public interface RoleService {
66 60 void updateRoleDataScope(Long id, Integer dataScope, Set<Long> dataScopeDeptIds);
67 61  
68 62 /**
  63 + * 获得角色
  64 + *
  65 + * @param id 角色编号
  66 + * @return 角色
  67 + */
  68 + RoleDO getRole(Long id);
  69 +
  70 + /**
69 71 * 获得角色,从缓存中
70 72 *
71 73 * @param id 角色编号
... ... @@ -76,10 +78,10 @@ public interface RoleService {
76 78 /**
77 79 * 获得角色列表
78 80 *
79   - * @param statuses 筛选的状态。允许空,空时不筛选
  81 + * @param ids 角色编号数组
80 82 * @return 角色列表
81 83 */
82   - List<RoleDO> getRoleListByStatus(@Nullable Collection<Integer> statuses);
  84 + List<RoleDO> getRoleList(Collection<Long> ids);
83 85  
84 86 /**
85 87 * 获得角色数组,从缓存中
... ... @@ -90,30 +92,19 @@ public interface RoleService {
90 92 List<RoleDO> getRoleListFromCache(Collection<Long> ids);
91 93  
92 94 /**
93   - * 判断角色数组中,是否有超级管理员
94   - *
95   - * @param roleList 角色数组
96   - * @return 是否有管理员
97   - */
98   - boolean hasAnySuperAdmin(Collection<RoleDO> roleList);
99   -
100   - /**
101   - * 判断角色编号数组中,是否有管理员
  95 + * 获得角色列表
102 96 *
103   - * @param ids 角色编号数组
104   - * @return 是否有管理员
  97 + * @param statuses 筛选的状态
  98 + * @return 角色列表
105 99 */
106   - default boolean hasAnySuperAdmin(Set<Long> ids) {
107   - return hasAnySuperAdmin(getRoleListFromCache(ids));
108   - }
  100 + List<RoleDO> getRoleListByStatus(Collection<Integer> statuses);
109 101  
110 102 /**
111   - * 获得角色
  103 + * 获得所有角色列表
112 104 *
113   - * @param id 角色编号
114   - * @return 角色
  105 + * @return 角色列表
115 106 */
116   - RoleDO getRole(Long id);
  107 + List<RoleDO> getRoleList();
117 108  
118 109 /**
119 110 * 获得角色分页
... ... @@ -132,6 +123,14 @@ public interface RoleService {
132 123 List<RoleDO> getRoleList(RoleExportReqVO reqVO);
133 124  
134 125 /**
  126 + * 判断角色编号数组中,是否有管理员
  127 + *
  128 + * @param ids 角色编号数组
  129 + * @return 是否有管理员
  130 + */
  131 + boolean hasAnySuperAdmin(Collection<Long> ids);
  132 +
  133 + /**
135 134 * 校验角色们是否有效。如下情况,视为无效:
136 135 * 1. 角色编号不存在
137 136 * 2. 角色被禁用
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
... ... @@ -3,9 +3,9 @@ package cn.iocoder.yudao.module.system.service.permission;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.CollectionUtil;
5 5 import cn.hutool.core.util.ObjectUtil;
  6 +import cn.hutool.extra.spring.SpringUtil;
6 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8   -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
9 9 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleCreateReqVO;
10 10 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
11 11 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
... ... @@ -13,26 +13,23 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleUp
13 13 import cn.iocoder.yudao.module.system.convert.permission.RoleConvert;
14 14 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
15 15 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
  16 +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
16 17 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
17 18 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
18 19 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
19   -import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer;
20 20 import com.google.common.annotations.VisibleForTesting;
21   -import lombok.Getter;
22 21 import lombok.extern.slf4j.Slf4j;
23   -import org.springframework.lang.Nullable;
  22 +import org.springframework.cache.annotation.CacheEvict;
  23 +import org.springframework.cache.annotation.Cacheable;
24 24 import org.springframework.stereotype.Service;
25 25 import org.springframework.transaction.annotation.Transactional;
26   -import org.springframework.transaction.support.TransactionSynchronization;
27   -import org.springframework.transaction.support.TransactionSynchronizationManager;
28 26 import org.springframework.util.StringUtils;
29 27  
30   -import javax.annotation.PostConstruct;
31 28 import javax.annotation.Resource;
32 29 import java.util.*;
33   -import java.util.stream.Collectors;
34 30  
35 31 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  32 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
36 33 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
37 34 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
38 35  
... ... @@ -45,43 +42,14 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
45 42 @Slf4j
46 43 public class RoleServiceImpl implements RoleService {
47 44  
48   - /**
49   - * 角色缓存
50   - * key:角色编号 {@link RoleDO#getId()}
51   - *
52   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
53   - */
54   - @Getter
55   - private volatile Map<Long, RoleDO> roleCache;
56   -
57 45 @Resource
58 46 private PermissionService permissionService;
59 47  
60 48 @Resource
61 49 private RoleMapper roleMapper;
62 50  
63   - @Resource
64   - private RoleProducer roleProducer;
65   -
66   - /**
67   - * 初始化 {@link #roleCache} 缓存
68   - */
69   - @Override
70   - @PostConstruct
71   - public void initLocalCache() {
72   - // 注意:忽略自动多租户,因为要全局初始化缓存
73   - TenantUtils.executeIgnore(() -> {
74   - // 第一步:查询数据
75   - List<RoleDO> roleList = roleMapper.selectList();
76   - log.info("[initLocalCache][缓存角色,数量为:{}]", roleList.size());
77   -
78   - // 第二步:构建缓存
79   - roleCache = convertMap(roleList, RoleDO::getId);
80   - });
81   - }
82   -
83 51 @Override
84   - @Transactional
  52 + @Transactional(rollbackFor = Exception.class)
85 53 public Long createRole(RoleCreateReqVO reqVO, Integer type) {
86 54 // 校验角色
87 55 validateRoleDuplicate(reqVO.getName(), reqVO.getCode(), null);
... ... @@ -91,18 +59,12 @@ public class RoleServiceImpl implements RoleService {
91 59 role.setStatus(CommonStatusEnum.ENABLE.getStatus());
92 60 role.setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限
93 61 roleMapper.insert(role);
94   - // 发送刷新消息
95   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
96   - @Override
97   - public void afterCommit() {
98   - roleProducer.sendRoleRefreshMessage();
99   - }
100   - });
101 62 // 返回
102 63 return role.getId();
103 64 }
104 65  
105 66 @Override
  67 + @CacheEvict(value = RedisKeyConstants.ROLE, key = "#reqVO.id")
106 68 public void updateRole(RoleUpdateReqVO reqVO) {
107 69 // 校验是否可以更新
108 70 validateRoleForUpdate(reqVO.getId());
... ... @@ -112,11 +74,10 @@ public class RoleServiceImpl implements RoleService {
112 74 // 更新到数据库
113 75 RoleDO updateObj = RoleConvert.INSTANCE.convert(reqVO);
114 76 roleMapper.updateById(updateObj);
115   - // 发送刷新消息
116   - roleProducer.sendRoleRefreshMessage();
117 77 }
118 78  
119 79 @Override
  80 + @CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
120 81 public void updateRoleStatus(Long id, Integer status) {
121 82 // 校验是否可以更新
122 83 validateRoleForUpdate(id);
... ... @@ -124,11 +85,10 @@ public class RoleServiceImpl implements RoleService {
124 85 // 更新状态
125 86 RoleDO updateObj = new RoleDO().setId(id).setStatus(status);
126 87 roleMapper.updateById(updateObj);
127   - // 发送刷新消息
128   - roleProducer.sendRoleRefreshMessage();
129 88 }
130 89  
131 90 @Override
  91 + @CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
132 92 public void updateRoleDataScope(Long id, Integer dataScope, Set<Long> dataScopeDeptIds) {
133 93 // 校验是否可以更新
134 94 validateRoleForUpdate(id);
... ... @@ -139,12 +99,11 @@ public class RoleServiceImpl implements RoleService {
139 99 updateObject.setDataScope(dataScope);
140 100 updateObject.setDataScopeDeptIds(dataScopeDeptIds);
141 101 roleMapper.updateById(updateObject);
142   - // 发送刷新消息
143   - roleProducer.sendRoleRefreshMessage();
144 102 }
145 103  
146 104 @Override
147 105 @Transactional(rollbackFor = Exception.class)
  106 + @CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
148 107 public void deleteRole(Long id) {
149 108 // 校验是否可以更新
150 109 validateRoleForUpdate(id);
... ... @@ -152,60 +111,6 @@ public class RoleServiceImpl implements RoleService {
152 111 roleMapper.deleteById(id);
153 112 // 删除相关数据
154 113 permissionService.processRoleDeleted(id);
155   - // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
156   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
157   -
158   - @Override
159   - public void afterCommit() {
160   - roleProducer.sendRoleRefreshMessage();
161   - }
162   -
163   - });
164   - }
165   -
166   - @Override
167   - public RoleDO getRoleFromCache(Long id) {
168   - return roleCache.get(id);
169   - }
170   -
171   - @Override
172   - public List<RoleDO> getRoleListByStatus(@Nullable Collection<Integer> statuses) {
173   - if (CollUtil.isEmpty(statuses)) {
174   - return roleMapper.selectList();
175   - }
176   - return roleMapper.selectListByStatus(statuses);
177   - }
178   -
179   - @Override
180   - public List<RoleDO> getRoleListFromCache(Collection<Long> ids) {
181   - if (CollectionUtil.isEmpty(ids)) {
182   - return Collections.emptyList();
183   - }
184   - return roleCache.values().stream().filter(roleDO -> ids.contains(roleDO.getId()))
185   - .collect(Collectors.toList());
186   - }
187   -
188   - @Override
189   - public boolean hasAnySuperAdmin(Collection<RoleDO> roleList) {
190   - if (CollectionUtil.isEmpty(roleList)) {
191   - return false;
192   - }
193   - return roleList.stream().anyMatch(role -> RoleCodeEnum.isSuperAdmin(role.getCode()));
194   - }
195   -
196   - @Override
197   - public RoleDO getRole(Long id) {
198   - return roleMapper.selectById(id);
199   - }
200   -
201   - @Override
202   - public PageResult<RoleDO> getRolePage(RolePageReqVO reqVO) {
203   - return roleMapper.selectPage(reqVO);
204   - }
205   -
206   - @Override
207   - public List<RoleDO> getRoleList(RoleExportReqVO reqVO) {
208   - return roleMapper.selectList(reqVO);
209 114 }
210 115  
211 116 /**
... ... @@ -258,6 +163,69 @@ public class RoleServiceImpl implements RoleService {
258 163 }
259 164  
260 165 @Override
  166 + public RoleDO getRole(Long id) {
  167 + return roleMapper.selectById(id);
  168 + }
  169 +
  170 + @Override
  171 + @Cacheable(value = RedisKeyConstants.ROLE, key = "#id",
  172 + unless = "#result == null")
  173 + public RoleDO getRoleFromCache(Long id) {
  174 + return roleMapper.selectById(id);
  175 + }
  176 +
  177 +
  178 + @Override
  179 + public List<RoleDO> getRoleListByStatus(Collection<Integer> statuses) {
  180 + return roleMapper.selectListByStatus(statuses);
  181 + }
  182 +
  183 + @Override
  184 + public List<RoleDO> getRoleList() {
  185 + return roleMapper.selectList();
  186 + }
  187 +
  188 + @Override
  189 + public List<RoleDO> getRoleList(Collection<Long> ids) {
  190 + if (CollectionUtil.isEmpty(ids)) {
  191 + return Collections.emptyList();
  192 + }
  193 + return roleMapper.selectBatchIds(ids);
  194 + }
  195 +
  196 + @Override
  197 + public List<RoleDO> getRoleListFromCache(Collection<Long> ids) {
  198 + if (CollectionUtil.isEmpty(ids)) {
  199 + return Collections.emptyList();
  200 + }
  201 + // 这里采用 for 循环从缓存中获取,主要考虑 Spring CacheManager 无法批量操作的问题
  202 + RoleServiceImpl self = getSelf();
  203 + return convertList(ids, self::getRoleFromCache);
  204 + }
  205 +
  206 + @Override
  207 + public PageResult<RoleDO> getRolePage(RolePageReqVO reqVO) {
  208 + return roleMapper.selectPage(reqVO);
  209 + }
  210 +
  211 + @Override
  212 + public List<RoleDO> getRoleList(RoleExportReqVO reqVO) {
  213 + return roleMapper.selectList(reqVO);
  214 + }
  215 +
  216 + @Override
  217 + public boolean hasAnySuperAdmin(Collection<Long> ids) {
  218 + if (CollectionUtil.isEmpty(ids)) {
  219 + return false;
  220 + }
  221 + RoleServiceImpl self = getSelf();
  222 + return ids.stream().anyMatch(id -> {
  223 + RoleDO role = self.getRoleFromCache(id);
  224 + return role != null && RoleCodeEnum.isSuperAdmin(role.getCode());
  225 + });
  226 + }
  227 +
  228 + @Override
261 229 public void validateRoleList(Collection<Long> ids) {
262 230 if (CollUtil.isEmpty(ids)) {
263 231 return;
... ... @@ -276,4 +244,14 @@ public class RoleServiceImpl implements RoleService {
276 244 }
277 245 });
278 246 }
  247 +
  248 + /**
  249 + * 获得自身的代理对象,解决 AOP 生效问题
  250 + *
  251 + * @return 自己
  252 + */
  253 + private RoleServiceImpl getSelf() {
  254 + return SpringUtil.getBean(getClass());
  255 + }
  256 +
279 257 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordService.java
... ... @@ -19,11 +19,6 @@ import java.util.Set;
19 19 public interface SensitiveWordService {
20 20  
21 21 /**
22   - * 初始化本地缓存
23   - */
24   - void initLocalCache();
25   -
26   - /**
27 22 * 创建敏感词
28 23 *
29 24 * @param createReqVO 创建信息
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java
... ... @@ -11,21 +11,24 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
11 11 import cn.iocoder.yudao.module.system.convert.sensitiveword.SensitiveWordConvert;
12 12 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
13 13 import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper;
14   -import cn.iocoder.yudao.module.system.mq.producer.sensitiveword.SensitiveWordProducer;
15 14 import cn.iocoder.yudao.module.system.util.collection.SimpleTrie;
16 15 import com.google.common.collect.HashMultimap;
17 16 import com.google.common.collect.Multimap;
18 17 import lombok.Getter;
19 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.scheduling.annotation.Scheduled;
20 20 import org.springframework.stereotype.Service;
21 21 import org.springframework.validation.annotation.Validated;
22 22  
23 23 import javax.annotation.PostConstruct;
24 24 import javax.annotation.Resource;
  25 +import java.time.LocalDateTime;
25 26 import java.util.*;
  27 +import java.util.concurrent.TimeUnit;
26 28  
27 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 30 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
  31 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
29 32 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_EXISTS;
30 33 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS;
31 34  
... ... @@ -40,6 +43,11 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_
40 43 public class SensitiveWordServiceImpl implements SensitiveWordService {
41 44  
42 45 /**
  46 + * 敏感词列表缓存
  47 + */
  48 + @Getter
  49 + private volatile List<SensitiveWordDO> sensitiveWordCache = Collections.emptyList();
  50 + /**
43 51 * 敏感词标签缓存
44 52 * key:敏感词编号 {@link SensitiveWordDO#getId()}
45 53 * <p>
... ... @@ -51,9 +59,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
51 59 @Resource
52 60 private SensitiveWordMapper sensitiveWordMapper;
53 61  
54   - @Resource
55   - private SensitiveWordProducer sensitiveWordProducer;
56   -
57 62 /**
58 63 * 默认的敏感词的字典树,包含所有敏感词
59 64 */
... ... @@ -68,7 +73,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
68 73 /**
69 74 * 初始化缓存
70 75 */
71   - @Override
72 76 @PostConstruct
73 77 public void initLocalCache() {
74 78 // 第一步:查询数据
... ... @@ -80,6 +84,7 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
80 84 Set<String> tags = new HashSet<>();
81 85 sensitiveWords.forEach(word -> tags.addAll(word.getTags()));
82 86 sensitiveWordTagsCache = tags;
  87 + sensitiveWordCache = sensitiveWords;
83 88 // 写入 defaultSensitiveWordTrie、tagSensitiveWordTries 缓存
84 89 initSensitiveWordTrie(sensitiveWords);
85 90 }
... ... @@ -105,6 +110,26 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
105 110 this.tagSensitiveWordTries = tagSensitiveWordTries;
106 111 }
107 112  
  113 + /**
  114 + * 通过定时任务轮询,刷新缓存
  115 + *
  116 + * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
  117 + */
  118 + @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
  119 + public void refreshLocalCache() {
  120 + // 情况一:如果缓存里没有数据,则直接刷新缓存
  121 + if (CollUtil.isEmpty(sensitiveWordCache)) {
  122 + initLocalCache();
  123 + return;
  124 + }
  125 +
  126 + // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
  127 + LocalDateTime maxTime = getMaxValue(sensitiveWordCache, SensitiveWordDO::getUpdateTime);
  128 + if (sensitiveWordMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
  129 + initLocalCache();
  130 + }
  131 + }
  132 +
108 133 @Override
109 134 public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) {
110 135 // 校验唯一性
... ... @@ -113,8 +138,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
113 138 // 插入
114 139 SensitiveWordDO sensitiveWord = SensitiveWordConvert.INSTANCE.convert(createReqVO);
115 140 sensitiveWordMapper.insert(sensitiveWord);
116   - // 发送消息,刷新缓存
117   - sensitiveWordProducer.sendSensitiveWordRefreshMessage();
  141 +
  142 + // 刷新缓存
  143 + initLocalCache();
118 144 return sensitiveWord.getId();
119 145 }
120 146  
... ... @@ -127,8 +153,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
127 153 // 更新
128 154 SensitiveWordDO updateObj = SensitiveWordConvert.INSTANCE.convert(updateReqVO);
129 155 sensitiveWordMapper.updateById(updateObj);
130   - // 发送消息,刷新缓存
131   - sensitiveWordProducer.sendSensitiveWordRefreshMessage();
  156 +
  157 + // 刷新缓存
  158 + initLocalCache();
132 159 }
133 160  
134 161 @Override
... ... @@ -137,8 +164,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
137 164 validateSensitiveWordExists(id);
138 165 // 删除
139 166 sensitiveWordMapper.deleteById(id);
140   - // 发送消息,刷新缓存
141   - sensitiveWordProducer.sendSensitiveWordRefreshMessage();
  167 +
  168 + // 刷新缓存
  169 + initLocalCache();
142 170 }
143 171  
144 172 private void validateSensitiveWordNameUnique(Long id, String name) {
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java
1 1 package cn.iocoder.yudao.module.system.service.sms;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
5 6 import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
... ... @@ -9,15 +10,20 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannel
9 10 import cn.iocoder.yudao.module.system.convert.sms.SmsChannelConvert;
10 11 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
11 12 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
12   -import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
  13 +import lombok.Getter;
13 14 import lombok.extern.slf4j.Slf4j;
  15 +import org.springframework.scheduling.annotation.Scheduled;
14 16 import org.springframework.stereotype.Service;
15 17  
16 18 import javax.annotation.PostConstruct;
17 19 import javax.annotation.Resource;
  20 +import java.time.LocalDateTime;
  21 +import java.util.Collections;
18 22 import java.util.List;
  23 +import java.util.concurrent.TimeUnit;
19 24  
20 25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
21 27 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
22 28 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
23 29  
... ... @@ -30,6 +36,12 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNE
30 36 @Slf4j
31 37 public class SmsChannelServiceImpl implements SmsChannelService {
32 38  
  39 + /**
  40 + * 短信渠道列表的缓存
  41 + */
  42 + @Getter
  43 + private volatile List<SmsChannelDO> channelCache = Collections.emptyList();
  44 +
33 45 @Resource
34 46 private SmsClientFactory smsClientFactory;
35 47  
... ... @@ -39,9 +51,6 @@ public class SmsChannelServiceImpl implements SmsChannelService {
39 51 @Resource
40 52 private SmsTemplateService smsTemplateService;
41 53  
42   - @Resource
43   - private SmsProducer smsProducer;
44   -
45 54 @Override
46 55 @PostConstruct
47 56 public void initLocalCache() {
... ... @@ -52,6 +61,27 @@ public class SmsChannelServiceImpl implements SmsChannelService {
52 61 // 第二步:构建缓存:创建或更新短信 Client
53 62 List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(channels);
54 63 propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
  64 + this.channelCache = channels;
  65 + }
  66 +
  67 + /**
  68 + * 通过定时任务轮询,刷新缓存
  69 + *
  70 + * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
  71 + */
  72 + @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
  73 + public void refreshLocalCache() {
  74 + // 情况一:如果缓存里没有数据,则直接刷新缓存
  75 + if (CollUtil.isEmpty(channelCache)) {
  76 + initLocalCache();
  77 + return;
  78 + }
  79 +
  80 + // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
  81 + LocalDateTime maxTime = getMaxValue(channelCache, SmsChannelDO::getUpdateTime);
  82 + if (smsChannelMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
  83 + initLocalCache();
  84 + }
55 85 }
56 86  
57 87 @Override
... ... @@ -59,9 +89,9 @@ public class SmsChannelServiceImpl implements SmsChannelService {
59 89 // 插入
60 90 SmsChannelDO smsChannel = SmsChannelConvert.INSTANCE.convert(createReqVO);
61 91 smsChannelMapper.insert(smsChannel);
62   - // 发送刷新消息
63   - smsProducer.sendSmsChannelRefreshMessage();
64   - // 返回
  92 +
  93 + // 刷新缓存
  94 + initLocalCache();
65 95 return smsChannel.getId();
66 96 }
67 97  
... ... @@ -72,8 +102,9 @@ public class SmsChannelServiceImpl implements SmsChannelService {
72 102 // 更新
73 103 SmsChannelDO updateObj = SmsChannelConvert.INSTANCE.convert(updateReqVO);
74 104 smsChannelMapper.updateById(updateObj);
75   - // 发送刷新消息
76   - smsProducer.sendSmsChannelRefreshMessage();
  105 +
  106 + // 刷新缓存
  107 + initLocalCache();
77 108 }
78 109  
79 110 @Override
... ... @@ -86,8 +117,9 @@ public class SmsChannelServiceImpl implements SmsChannelService {
86 117 }
87 118 // 删除
88 119 smsChannelMapper.deleteById(id);
89   - // 发送刷新消息
90   - smsProducer.sendSmsChannelRefreshMessage();
  120 +
  121 + // 刷新缓存
  122 + initLocalCache();
91 123 }
92 124  
93 125 private void validateSmsChannelExists(Long id) {
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateService.java
... ... @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
8 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 9  
10 10 import javax.validation.Valid;
11   -import java.util.Collection;
12 11 import java.util.List;
13 12 import java.util.Map;
14 13  
... ... @@ -16,41 +15,11 @@ import java.util.Map;
16 15 * 短信模板 Service 接口
17 16 *
18 17 * @author zzf
19   - * @date 2021/1/25 9:24
  18 + * @since 2021/1/25 9:24
20 19 */
21 20 public interface SmsTemplateService {
22 21  
23 22 /**
24   - * 初始化短信模板的本地缓存
25   - */
26   - void initLocalCache();
27   -
28   - /**
29   - * 获得短信模板,从缓存中
30   - *
31   - * @param code 模板编码
32   - * @return 短信模板
33   - */
34   - SmsTemplateDO getSmsTemplateByCodeFromCache(String code);
35   -
36   - /**
37   - * 格式化短信内容
38   - *
39   - * @param content 短信模板的内容
40   - * @param params 内容的参数
41   - * @return 格式化后的内容
42   - */
43   - String formatSmsTemplateContent(String content, Map<String, Object> params);
44   -
45   - /**
46   - * 获得短信模板
47   - *
48   - * @param code 模板编码
49   - * @return 短信模板
50   - */
51   - SmsTemplateDO getSmsTemplateByCode(String code);
52   -
53   - /**
54 23 * 创建短信模板
55 24 *
56 25 * @param createReqVO 创建信息
... ... @@ -81,12 +50,12 @@ public interface SmsTemplateService {
81 50 SmsTemplateDO getSmsTemplate(Long id);
82 51  
83 52 /**
84   - * 获得短信模板列表
  53 + * 获得短信模板,从缓存中
85 54 *
86   - * @param ids 编号
87   - * @return 短信模板列表
  55 + * @param code 模板编码
  56 + * @return 短信模板
88 57 */
89   - List<SmsTemplateDO> getSmsTemplateList(Collection<Long> ids);
  58 + SmsTemplateDO getSmsTemplateByCodeFromCache(String code);
90 59  
91 60 /**
92 61 * 获得短信模板分页
... ... @@ -112,4 +81,14 @@ public interface SmsTemplateService {
112 81 */
113 82 Long countByChannelId(Long channelId);
114 83  
  84 +
  85 + /**
  86 + * 格式化短信内容
  87 + *
  88 + * @param content 短信模板的内容
  89 + * @param params 内容的参数
  90 + * @return 格式化后的内容
  91 + */
  92 + String formatSmsTemplateContent(String content, Map<String, Object> params);
  93 +
115 94 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java
... ... @@ -4,7 +4,6 @@ import cn.hutool.core.util.ReUtil;
4 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 7 import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
9 8 import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
10 9 import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
... ... @@ -17,16 +16,15 @@ import cn.iocoder.yudao.module.system.convert.sms.SmsTemplateConvert;
17 16 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
18 17 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
19 18 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper;
20   -import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
  19 +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
21 20 import com.google.common.annotations.VisibleForTesting;
22   -import lombok.Getter;
23 21 import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.cache.annotation.CacheEvict;
  23 +import org.springframework.cache.annotation.Cacheable;
24 24 import org.springframework.stereotype.Service;
25 25 import org.springframework.util.Assert;
26 26  
27   -import javax.annotation.PostConstruct;
28 27 import javax.annotation.Resource;
29   -import java.util.Collection;
30 28 import java.util.List;
31 29 import java.util.Map;
32 30 import java.util.Objects;
... ... @@ -59,49 +57,6 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
59 57 @Resource
60 58 private SmsClientFactory smsClientFactory;
61 59  
62   - @Resource
63   - private SmsProducer smsProducer;
64   -
65   - /**
66   - * 短信模板缓存
67   - * key:短信模板编码 {@link SmsTemplateDO#getCode()}
68   - *
69   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
70   - */
71   - @Getter // 为了方便测试,这里提供 getter 方法
72   - private volatile Map<String, SmsTemplateDO> smsTemplateCache;
73   -
74   - @Override
75   - @PostConstruct
76   - public void initLocalCache() {
77   - // 第一步:查询数据
78   - List<SmsTemplateDO> smsTemplateList = smsTemplateMapper.selectList();
79   - log.info("[initLocalCache][缓存短信模版,数量为:{}]", smsTemplateList.size());
80   -
81   - // 第二步:构建缓存
82   - smsTemplateCache = CollectionUtils.convertMap(smsTemplateList, SmsTemplateDO::getCode);
83   - }
84   -
85   - @Override
86   - public SmsTemplateDO getSmsTemplateByCodeFromCache(String code) {
87   - return smsTemplateCache.get(code);
88   - }
89   -
90   - @Override
91   - public String formatSmsTemplateContent(String content, Map<String, Object> params) {
92   - return StrUtil.format(content, params);
93   - }
94   -
95   - @Override
96   - public SmsTemplateDO getSmsTemplateByCode(String code) {
97   - return smsTemplateMapper.selectByCode(code);
98   - }
99   -
100   - @VisibleForTesting
101   - public List<String> parseTemplateContentParams(String content) {
102   - return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
103   - }
104   -
105 60 @Override
106 61 public Long createSmsTemplate(SmsTemplateCreateReqVO createReqVO) {
107 62 // 校验短信渠道
... ... @@ -116,13 +71,13 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
116 71 template.setParams(parseTemplateContentParams(template.getContent()));
117 72 template.setChannelCode(channelDO.getCode());
118 73 smsTemplateMapper.insert(template);
119   - // 发送刷新消息
120   - smsProducer.sendSmsTemplateRefreshMessage();
121 74 // 返回
122 75 return template.getId();
123 76 }
124 77  
125 78 @Override
  79 + @CacheEvict(cacheNames = RedisKeyConstants.SMS_TEMPLATE,
  80 + allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理
126 81 public void updateSmsTemplate(SmsTemplateUpdateReqVO updateReqVO) {
127 82 // 校验存在
128 83 validateSmsTemplateExists(updateReqVO.getId());
... ... @@ -138,18 +93,16 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
138 93 updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));
139 94 updateObj.setChannelCode(channelDO.getCode());
140 95 smsTemplateMapper.updateById(updateObj);
141   - // 发送刷新消息
142   - smsProducer.sendSmsTemplateRefreshMessage();
143 96 }
144 97  
145 98 @Override
  99 + @CacheEvict(cacheNames = RedisKeyConstants.SMS_TEMPLATE,
  100 + allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理
146 101 public void deleteSmsTemplate(Long id) {
147 102 // 校验存在
148 103 validateSmsTemplateExists(id);
149 104 // 更新
150 105 smsTemplateMapper.deleteById(id);
151   - // 发送刷新消息
152   - smsProducer.sendSmsTemplateRefreshMessage();
153 106 }
154 107  
155 108 private void validateSmsTemplateExists(Long id) {
... ... @@ -164,8 +117,10 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
164 117 }
165 118  
166 119 @Override
167   - public List<SmsTemplateDO> getSmsTemplateList(Collection<Long> ids) {
168   - return smsTemplateMapper.selectBatchIds(ids);
  120 + @Cacheable(cacheNames = RedisKeyConstants.SMS_TEMPLATE, key = "#code",
  121 + unless = "#result == null")
  122 + public SmsTemplateDO getSmsTemplateByCodeFromCache(String code) {
  123 + return smsTemplateMapper.selectByCode(code);
169 124 }
170 125  
171 126 @Override
... ... @@ -217,7 +172,7 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
217 172 * @param apiTemplateId API 模板编号
218 173 */
219 174 @VisibleForTesting
220   - public void validateApiTemplate(Long channelId, String apiTemplateId) {
  175 + void validateApiTemplate(Long channelId, String apiTemplateId) {
221 176 // 获得短信模板
222 177 SmsClient smsClient = smsClientFactory.getSmsClient(channelId);
223 178 Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", channelId));
... ... @@ -226,4 +181,14 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
226 181 templateResult.checkError();
227 182 }
228 183  
  184 + @Override
  185 + public String formatSmsTemplateContent(String content, Map<String, Object> params) {
  186 + return StrUtil.format(content, params);
  187 + }
  188 +
  189 + @VisibleForTesting
  190 + List<String> parseTemplateContentParams(String content) {
  191 + return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
  192 + }
  193 +
229 194 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java
... ... @@ -10,9 +10,9 @@ import cn.iocoder.yudao.module.system.convert.tenant.TenantPackageConvert;
10 10 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
11 11 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
12 12 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantPackageMapper;
  13 +import com.baomidou.dynamic.datasource.annotation.DSTransactional;
13 14 import org.springframework.context.annotation.Lazy;
14 15 import org.springframework.stereotype.Service;
15   -import org.springframework.transaction.annotation.Transactional;
16 16 import org.springframework.validation.annotation.Validated;
17 17  
18 18 import javax.annotation.Resource;
... ... @@ -47,7 +47,7 @@ public class TenantPackageServiceImpl implements TenantPackageService {
47 47 }
48 48  
49 49 @Override
50   - @Transactional(rollbackFor = Exception.class)
  50 + @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
51 51 public void updateTenantPackage(TenantPackageUpdateReqVO updateReqVO) {
52 52 // 校验存在
53 53 TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId());
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java
... ... @@ -29,6 +29,7 @@ import cn.iocoder.yudao.module.system.service.permission.RoleService;
29 29 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantInfoHandler;
30 30 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler;
31 31 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
  32 +import com.baomidou.dynamic.datasource.annotation.DSTransactional;
32 33 import lombok.extern.slf4j.Slf4j;
33 34 import org.springframework.beans.factory.annotation.Autowired;
34 35 import org.springframework.context.annotation.Lazy;
... ... @@ -95,7 +96,7 @@ public class TenantServiceImpl implements TenantService {
95 96 }
96 97  
97 98 @Override
98   - @Transactional(rollbackFor = Exception.class)
  99 + @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
99 100 public Long createTenant(TenantCreateReqVO createReqVO) {
100 101 // 校验租户名称是否重复
101 102 validTenantNameDuplicate(createReqVO.getName(), null);
... ... @@ -137,7 +138,7 @@ public class TenantServiceImpl implements TenantService {
137 138 }
138 139  
139 140 @Override
140   - @Transactional(rollbackFor = Exception.class)
  141 + @DSTransactional
141 142 public void updateTenant(TenantUpdateReqVO updateReqVO) {
142 143 // 校验存在
143 144 TenantDO tenant = validateUpdateTenant(updateReqVO.getId());
... ... @@ -170,7 +171,7 @@ public class TenantServiceImpl implements TenantService {
170 171 }
171 172  
172 173 @Override
173   - @Transactional(rollbackFor = Exception.class)
  174 + @DSTransactional
174 175 public void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds) {
175 176 TenantUtils.execute(tenantId, () -> {
176 177 // 获得所有角色
... ... @@ -186,7 +187,7 @@ public class TenantServiceImpl implements TenantService {
186 187 return;
187 188 }
188 189 // 如果是其他角色,则去掉超过套餐的权限
189   - Set<Long> roleMenuIds = permissionService.getRoleMenuIds(role.getId());
  190 + Set<Long> roleMenuIds = permissionService.getRoleMenuListByRoleId(role.getId());
190 191 roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds);
191 192 permissionService.assignRoleMenu(role.getId(), roleMenuIds);
192 193 log.info("[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), roleMenuIds);
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
... ... @@ -290,14 +290,13 @@ public class AdminUserServiceImpl implements AdminUserService {
290 290 if (deptId == null) {
291 291 return Collections.emptySet();
292 292 }
293   - Set<Long> deptIds = convertSet(deptService.getDeptListByParentIdFromCache(
294   - deptId, true), DeptDO::getId);
  293 + Set<Long> deptIds = convertSet(deptService.getChildDeptList(deptId), DeptDO::getId);
295 294 deptIds.add(deptId); // 包括自身
296 295 return deptIds;
297 296 }
298 297  
299 298 private void validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
300   - Long deptId, Set<Long> postIds) {
  299 + Long deptId, Set<Long> postIds) {
301 300 // 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确
302 301 DataPermissionUtils.executeIgnore(() -> {
303 302 // 校验用户存在
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenControllerTest.java
... ... @@ -39,6 +39,9 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
39 39 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
40 40 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
41 41 import static java.util.Arrays.asList;
  42 +import static org.hamcrest.CoreMatchers.anyOf;
  43 +import static org.hamcrest.CoreMatchers.is;
  44 +import static org.hamcrest.MatcherAssert.assertThat;
42 45 import static org.junit.jupiter.api.Assertions.*;
43 46 import static org.mockito.ArgumentMatchers.eq;
44 47 import static org.mockito.ArgumentMatchers.isNull;
... ... @@ -289,7 +292,10 @@ public class OAuth2OpenControllerTest extends BaseMockitoUnitTest {
289 292 scope, redirectUri, true, state);
290 293 // 断言
291 294 assertEquals(0, result.getCode());
292   - assertEquals("https://www.iocoder.cn#access_token=test_access_token&token_type=bearer&state=test&expires_in=30&scope=read", result.getData());
  295 + assertThat(result.getData(), anyOf( // 29 和 30 都有一定概率,主要是时间计算
  296 + is("https://www.iocoder.cn#access_token=test_access_token&token_type=bearer&state=test&expires_in=29&scope=read"),
  297 + is("https://www.iocoder.cn#access_token=test_access_token&token_type=bearer&state=test&expires_in=30&scope=read")
  298 + ));
293 299 }
294 300  
295 301 @Test // autoApprove = false,通过 + code
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java
... ... @@ -148,7 +148,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
148 148 );
149 149 }
150 150  
151   - @Test
  151 + @Test
152 152 public void testLogin_success() {
153 153 // 准备参数
154 154 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o ->
... ... @@ -174,9 +174,9 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
174 174 assertPojoEquals(accessTokenDO, loginRespVO);
175 175 // 校验调用参数
176 176 verify(loginLogService).createLoginLog(
177   - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
178   - && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())
179   - && o.getUserId().equals(user.getId()))
  177 + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
  178 + && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())
  179 + && o.getUserId().equals(user.getId()))
180 180 );
181 181 verify(socialUserService).bindSocialUser(eq(new SocialUserBindReqDTO(
182 182 user.getId(), UserTypeEnum.ADMIN.getValue(),
... ... @@ -317,8 +317,8 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
317 317 assertServiceException(() -> authService.validateCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR, "就是不对");
318 318 // 校验调用参数
319 319 verify(loginLogService).createLoginLog(
320   - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
321   - && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult()))
  320 + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
  321 + && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult()))
322 322 );
323 323 }
324 324  
... ... @@ -349,8 +349,9 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
349 349 // 调用
350 350 authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
351 351 // 校验调用参数
352   - verify(loginLogService).createLoginLog(argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType())
353   - && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))
  352 + verify(loginLogService).createLoginLog(
  353 + argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType())
  354 + && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))
354 355 );
355 356 // 调用,并校验
356 357  
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java
1 1 package cn.iocoder.yudao.module.system.service.dept;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4   -import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
5 4 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
6   -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 5 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
8 6 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
9 7 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
... ... @@ -11,27 +9,20 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateRe
11 9 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
12 10 import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
13 11 import cn.iocoder.yudao.module.system.enums.dept.DeptIdEnum;
14   -import cn.iocoder.yudao.module.system.mq.producer.dept.DeptProducer;
15   -import com.google.common.collect.Multimap;
16   -import org.junit.jupiter.api.BeforeEach;
17 12 import org.junit.jupiter.api.Test;
18   -import org.springframework.boot.test.mock.mockito.MockBean;
19 13 import org.springframework.context.annotation.Import;
20 14  
21 15 import javax.annotation.Resource;
22 16 import java.util.Arrays;
23 17 import java.util.List;
24   -import java.util.Map;
25   -import java.util.function.Consumer;
  18 +import java.util.Set;
26 19  
27   -import static cn.hutool.core.util.RandomUtil.randomEle;
28 20 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
29 21 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
30 22 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
31 23 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
32 24 import static java.util.Collections.singletonList;
33 25 import static org.junit.jupiter.api.Assertions.*;
34   -import static org.mockito.Mockito.verify;
35 26  
36 27 /**
37 28 * {@link DeptServiceImpl} 的单元测试类
... ... @@ -45,63 +36,9 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
45 36 private DeptServiceImpl deptService;
46 37 @Resource
47 38 private DeptMapper deptMapper;
48   - @MockBean
49   - private DeptProducer deptProducer;
50   -
51   - @BeforeEach
52   - public void setUp() {
53   - // 清理租户上下文
54   - TenantContextHolder.clear();
55   - }
56   -
57   - @Test
58   - public void testInitLocalCache() {
59   - // mock 数据
60   - DeptDO deptDO1 = randomDeptDO();
61   - deptMapper.insert(deptDO1);
62   - DeptDO deptDO2 = randomDeptDO();
63   - deptMapper.insert(deptDO2);
64   -
65   - // 调用
66   - deptService.initLocalCache();
67   - // 断言 deptCache 缓存
68   - Map<Long, DeptDO> deptCache = deptService.getDeptCache();
69   - assertEquals(2, deptCache.size());
70   - assertPojoEquals(deptDO1, deptCache.get(deptDO1.getId()));
71   - assertPojoEquals(deptDO2, deptCache.get(deptDO2.getId()));
72   - // 断言 parentDeptCache 缓存
73   - Multimap<Long, DeptDO> parentDeptCache = deptService.getParentDeptCache();
74   - assertEquals(2, parentDeptCache.size());
75   - assertPojoEquals(deptDO1, parentDeptCache.get(deptDO1.getParentId()));
76   - assertPojoEquals(deptDO2, parentDeptCache.get(deptDO2.getParentId()));
77   - }
78   -
79   - @Test
80   - public void testListDepts() {
81   - // mock 数据
82   - DeptDO dept = randomPojo(DeptDO.class, o -> { // 等会查询到
83   - o.setName("开发部");
84   - o.setStatus(CommonStatusEnum.ENABLE.getStatus());
85   - });
86   - deptMapper.insert(dept);
87   - // 测试 name 不匹配
88   - deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setName("发")));
89   - // 测试 status 不匹配
90   - deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
91   - // 准备参数
92   - DeptListReqVO reqVO = new DeptListReqVO();
93   - reqVO.setName("开");
94   - reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
95   -
96   - // 调用
97   - List<DeptDO> sysDeptDOS = deptService.getDeptList(reqVO);
98   - // 断言
99   - assertEquals(1, sysDeptDOS.size());
100   - assertPojoEquals(dept, sysDeptDOS.get(0));
101   - }
102 39  
103 40 @Test
104   - public void testCreateDept_success() {
  41 + public void testCreateDept() {
105 42 // 准备参数
106 43 DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class, o -> {
107 44 o.setParentId(DeptIdEnum.ROOT.getId());
... ... @@ -115,12 +52,10 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
115 52 // 校验记录的属性是否正确
116 53 DeptDO deptDO = deptMapper.selectById(deptId);
117 54 assertPojoEquals(reqVO, deptDO);
118   - // 校验调用
119   - verify(deptProducer).sendDeptRefreshMessage();
120 55 }
121 56  
122 57 @Test
123   - public void testUpdateDept_success() {
  58 + public void testUpdateDept() {
124 59 // mock 数据
125 60 DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
126 61 deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
... ... @@ -137,14 +72,12 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
137 72 // 校验是否更新正确
138 73 DeptDO deptDO = deptMapper.selectById(reqVO.getId()); // 获取最新的
139 74 assertPojoEquals(reqVO, deptDO);
140   - // 校验调用
141   - verify(deptProducer).sendDeptRefreshMessage();
142 75 }
143 76  
144 77 @Test
145 78 public void testDeleteDept_success() {
146 79 // mock 数据
147   - DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
  80 + DeptDO dbDeptDO = randomPojo(DeptDO.class);
148 81 deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
149 82 // 准备参数
150 83 Long id = dbDeptDO.getId();
... ... @@ -153,134 +86,99 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
153 86 deptService.deleteDept(id);
154 87 // 校验数据不存在了
155 88 assertNull(deptMapper.selectById(id));
156   - // 校验调用
157   - verify(deptProducer).sendDeptRefreshMessage();
158 89 }
159 90  
160 91 @Test
161   - public void testValidateDept_nameDuplicateForUpdate() {
  92 + public void testDeleteDept_exitsChildren() {
162 93 // mock 数据
163   - DeptDO deptDO = randomDeptDO();
164   - // 设置根节点部门
165   - deptDO.setParentId(DeptIdEnum.ROOT.getId());
166   - deptMapper.insert(deptDO);
167   - // mock 数据 稍后模拟重复它的 name
168   - DeptDO nameDeptDO = randomDeptDO();
169   - // 设置根节点部门
170   - nameDeptDO.setParentId(DeptIdEnum.ROOT.getId());
171   - deptMapper.insert(nameDeptDO);
  94 + DeptDO parentDept = randomPojo(DeptDO.class);
  95 + deptMapper.insert(parentDept);// @Sql: 先插入出一条存在的数据
172 96 // 准备参数
173   - DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
174   - // 设置根节点部门
175   - o.setParentId(DeptIdEnum.ROOT.getId());
176   - // 设置更新的 ID
177   - o.setId(deptDO.getId());
178   - // 模拟 name 重复
179   - o.setName(nameDeptDO.getName());
  97 + DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> {
  98 + o.setParentId(parentDept.getId());
  99 + o.setStatus(randomCommonStatus());
180 100 });
  101 + // 插入子部门
  102 + deptMapper.insert(childrenDeptDO);
181 103  
182 104 // 调用, 并断言异常
183   - assertServiceException(() -> deptService.updateDept(reqVO), DEPT_NAME_DUPLICATE);
  105 + assertServiceException(() -> deptService.deleteDept(parentDept.getId()), DEPT_EXITS_CHILDREN);
184 106 }
185 107  
186 108 @Test
187   - public void testValidateDept_parentNotExitsForCreate() {
  109 + public void testValidateDeptExists_notFound() {
188 110 // 准备参数
189   - DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class,
190   - o -> o.setStatus(randomCommonStatus()));
  111 + Long id = randomLongId();
191 112  
192   - // 调用,并断言异常
193   - assertServiceException(() -> deptService.createDept(reqVO), DEPT_PARENT_NOT_EXITS);
  113 + // 调用, 并断言异常
  114 + assertServiceException(() -> deptService.validateDeptExists(id), DEPT_NOT_FOUND);
194 115 }
195 116  
196 117 @Test
197   - public void testValidateDept_notFoundForDelete() {
  118 + public void testValidateParentDept_parentError() {
198 119 // 准备参数
199 120 Long id = randomLongId();
200 121  
201 122 // 调用, 并断言异常
202   - assertServiceException(() -> deptService.deleteDept(id), DEPT_NOT_FOUND);
  123 + assertServiceException(() -> deptService.validateParentDept(id, id),
  124 + DEPT_PARENT_ERROR);
203 125 }
204 126  
205 127 @Test
206   - public void testValidateDept_exitsChildrenForDelete() {
207   - // mock 数据
208   - DeptDO parentDept = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
209   - deptMapper.insert(parentDept);// @Sql: 先插入出一条存在的数据
210   - // 准备参数
211   - DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> {
  128 + public void testValidateParentDept_parentIsChild() {
  129 + // mock 数据(父节点)
  130 + DeptDO parentDept = randomPojo(DeptDO.class);
  131 + deptMapper.insert(parentDept);
  132 + // mock 数据(子节点)
  133 + DeptDO childDept = randomPojo(DeptDO.class, o -> {
212 134 o.setParentId(parentDept.getId());
213   - o.setStatus(randomCommonStatus());
214 135 });
215   - // 插入子部门
216   - deptMapper.insert(childrenDeptDO);
217   - // 调用, 并断言异常
218   - assertServiceException(() -> deptService.deleteDept(parentDept.getId()), DEPT_EXITS_CHILDREN);
219   - }
  136 + deptMapper.insert(childDept);
220 137  
221   - @Test
222   - public void testValidateDept_parentErrorForUpdate() {
223   - // mock 数据
224   - DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
225   - deptMapper.insert(dbDeptDO);
226 138 // 准备参数
227   - DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
228   - // 设置自己为父部门
229   - o.setParentId(dbDeptDO.getId());
230   - // 设置更新的 ID
231   - o.setId(dbDeptDO.getId());
232   - });
  139 + Long id = parentDept.getId();
  140 + Long parentId = childDept.getId();
233 141  
234 142 // 调用, 并断言异常
235   - assertServiceException(() -> deptService.updateDept(reqVO), DEPT_PARENT_ERROR);
  143 + assertServiceException(() -> deptService.validateParentDept(id, parentId), DEPT_PARENT_IS_CHILD);
236 144 }
237 145  
238 146 @Test
239   - public void testValidateDept_notEnableForCreate() {
  147 + public void testValidateNameUnique_duplicate() {
240 148 // mock 数据
241   - DeptDO deptDO = randomPojo(DeptDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
  149 + DeptDO deptDO = randomPojo(DeptDO.class);
242 150 deptMapper.insert(deptDO);
  151 +
243 152 // 准备参数
244   - DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class, o -> {
245   - // 设置未启用的部门为父部门
246   - o.setParentId(deptDO.getId());
247   - });
  153 + Long id = randomLongId();
  154 + Long parentId = deptDO.getParentId();
  155 + String name = deptDO.getName();
248 156  
249 157 // 调用, 并断言异常
250   - assertServiceException(() -> deptService.createDept(reqVO), DEPT_NOT_ENABLE);
  158 + assertServiceException(() -> deptService.validateDeptNameUnique(id, parentId, name),
  159 + DEPT_NAME_DUPLICATE);
251 160 }
252 161  
253 162 @Test
254   - public void testCheckDept_parentIsChildForUpdate() {
  163 + public void testGetDept() {
255 164 // mock 数据
256   - DeptDO parentDept = randomPojo(DeptDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
257   - deptMapper.insert(parentDept);
258   - DeptDO childDept = randomPojo(DeptDO.class, o -> {
259   - o.setStatus(CommonStatusEnum.ENABLE.getStatus());
260   - o.setParentId(parentDept.getId());
261   - });
262   - deptMapper.insert(childDept);
263   - // 初始化本地缓存
264   - deptService.initLocalCache();
265   -
  165 + DeptDO deptDO = randomPojo(DeptDO.class);
  166 + deptMapper.insert(deptDO);
266 167 // 准备参数
267   - DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
268   - // 设置自己的子部门为父部门
269   - o.setParentId(childDept.getId());
270   - // 设置更新的 ID
271   - o.setId(parentDept.getId());
272   - });
  168 + Long id = deptDO.getId();
273 169  
274   - // 调用, 并断言异常
275   - assertServiceException(() -> deptService.updateDept(reqVO), DEPT_PARENT_IS_CHILD);
  170 + // 调用
  171 + DeptDO dbDept = deptService.getDept(id);
  172 + // 断言
  173 + assertEquals(deptDO, dbDept);
276 174 }
277 175  
278 176 @Test
279   - public void testGetDeptList() {
  177 + public void testGetDeptList_ids() {
280 178 // mock 数据
281   - DeptDO deptDO01 = randomDeptDO();
  179 + DeptDO deptDO01 = randomPojo(DeptDO.class);
282 180 deptMapper.insert(deptDO01);
283   - DeptDO deptDO02 = randomDeptDO();
  181 + DeptDO deptDO02 = randomPojo(DeptDO.class);
284 182 deptMapper.insert(deptDO02);
285 183 // 准备参数
286 184 List<Long> ids = Arrays.asList(deptDO01.getId(), deptDO02.getId());
... ... @@ -294,23 +192,79 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
294 192 }
295 193  
296 194 @Test
297   - public void testGetDept() {
  195 + public void testGetDeptList_reqVO() {
298 196 // mock 数据
299   - DeptDO deptDO = randomDeptDO();
300   - deptMapper.insert(deptDO);
  197 + DeptDO dept = randomPojo(DeptDO.class, o -> { // 等会查询到
  198 + o.setName("开发部");
  199 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  200 + });
  201 + deptMapper.insert(dept);
  202 + // 测试 name 不匹配
  203 + deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setName("发")));
  204 + // 测试 status 不匹配
  205 + deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
301 206 // 准备参数
302   - Long id = deptDO.getId();
  207 + DeptListReqVO reqVO = new DeptListReqVO();
  208 + reqVO.setName("开");
  209 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
303 210  
304 211 // 调用
305   - DeptDO dbDept = deptService.getDept(id);
  212 + List<DeptDO> sysDeptDOS = deptService.getDeptList(reqVO);
306 213 // 断言
307   - assertEquals(deptDO, dbDept);
  214 + assertEquals(1, sysDeptDOS.size());
  215 + assertPojoEquals(dept, sysDeptDOS.get(0));
  216 + }
  217 +
  218 + @Test
  219 + public void testGetChildDeptList() {
  220 + // mock 数据(1 级别子节点)
  221 + DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1"));
  222 + deptMapper.insert(dept1);
  223 + DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2"));
  224 + deptMapper.insert(dept2);
  225 + // mock 数据(2 级子节点)
  226 + DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId()));
  227 + deptMapper.insert(dept1a);
  228 + DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId()));
  229 + deptMapper.insert(dept2a);
  230 + // 准备参数
  231 + Long id = dept1.getParentId();
  232 +
  233 + // 调用
  234 + List<DeptDO> result = deptService.getChildDeptList(id);
  235 + // 断言
  236 + assertEquals(result.size(), 2);
  237 + assertPojoEquals(dept1, result.get(0));
  238 + assertPojoEquals(dept1a, result.get(1));
  239 + }
  240 +
  241 + @Test
  242 + public void testGetChildDeptListFromCache() {
  243 + // mock 数据(1 级别子节点)
  244 + DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1"));
  245 + deptMapper.insert(dept1);
  246 + DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2"));
  247 + deptMapper.insert(dept2);
  248 + // mock 数据(2 级子节点)
  249 + DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId()));
  250 + deptMapper.insert(dept1a);
  251 + DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId()));
  252 + deptMapper.insert(dept2a);
  253 + // 准备参数
  254 + Long id = dept1.getParentId();
  255 +
  256 + // 调用
  257 + Set<Long> result = deptService.getChildDeptIdListFromCache(id);
  258 + // 断言
  259 + assertEquals(result.size(), 2);
  260 + assertTrue(result.contains(dept1.getId()));
  261 + assertTrue(result.contains(dept1a.getId()));
308 262 }
309 263  
310 264 @Test
311 265 public void testValidateDeptList_success() {
312 266 // mock 数据
313   - DeptDO deptDO = randomDeptDO().setStatus(CommonStatusEnum.ENABLE.getStatus());
  267 + DeptDO deptDO = randomPojo(DeptDO.class).setStatus(CommonStatusEnum.ENABLE.getStatus());
314 268 deptMapper.insert(deptDO);
315 269 // 准备参数
316 270 List<Long> ids = singletonList(deptDO.getId());
... ... @@ -331,7 +285,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
331 285 @Test
332 286 public void testValidateDeptList_notEnable() {
333 287 // mock 数据
334   - DeptDO deptDO = randomDeptDO().setStatus(CommonStatusEnum.DISABLE.getStatus());
  288 + DeptDO deptDO = randomPojo(DeptDO.class).setStatus(CommonStatusEnum.DISABLE.getStatus());
335 289 deptMapper.insert(deptDO);
336 290 // 准备参数
337 291 List<Long> ids = singletonList(deptDO.getId());
... ... @@ -340,12 +294,4 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
340 294 assertServiceException(() -> deptService.validateDeptList(ids), DEPT_NOT_ENABLE, deptDO.getName());
341 295 }
342 296  
343   - @SafeVarargs
344   - private static DeptDO randomDeptDO(Consumer<DeptDO>... consumers) {
345   - Consumer<DeptDO> consumer = (o) -> {
346   - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
347   - };
348   - return randomPojo(DeptDO.class, ArrayUtils.append(consumer, consumers));
349   - }
350   -
351 297 }
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImplTest.java
... ... @@ -7,14 +7,12 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccou
7 7 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountUpdateReqVO;
8 8 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
9 9 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
10   -import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
11 10 import org.junit.jupiter.api.Test;
12 11 import org.springframework.boot.test.mock.mockito.MockBean;
13 12 import org.springframework.context.annotation.Import;
14 13  
15 14 import javax.annotation.Resource;
16 15 import java.util.List;
17   -import java.util.Map;
18 16  
19 17 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
20 18 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
... ... @@ -23,14 +21,13 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
23 21 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS;
24 22 import static org.junit.jupiter.api.Assertions.*;
25 23 import static org.mockito.ArgumentMatchers.eq;
26   -import static org.mockito.Mockito.verify;
27 24 import static org.mockito.Mockito.when;
28 25  
29 26 /**
30   -* {@link MailAccountServiceImpl} 的单元测试类
31   -*
32   -* @author 芋道源码
33   -*/
  27 + * {@link MailAccountServiceImpl} 的单元测试类
  28 + *
  29 + * @author 芋道源码
  30 + */
34 31 @Import(MailAccountServiceImpl.class)
35 32 public class MailAccountServiceImplTest extends BaseDbUnitTest {
36 33  
... ... @@ -42,23 +39,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
42 39  
43 40 @MockBean
44 41 private MailTemplateService mailTemplateService;
45   - @MockBean
46   - private MailProducer mailProducer;
47   -
48   - @Test
49   - public void testInitLocalCache() {
50   - MailAccountDO accountDO1 = randomPojo(MailAccountDO.class);
51   - mailAccountMapper.insert(accountDO1);
52   - MailAccountDO accountDO02 = randomPojo(MailAccountDO.class);
53   - mailAccountMapper.insert(accountDO02);
54   -
55   - // 调用
56   - mailAccountService.initLocalCache();
57   - // 断言 mailAccountCache 缓存
58   - Map<Long, MailAccountDO> mailAccountCache = mailAccountService.getMailAccountCache();
59   - assertPojoEquals(accountDO1, mailAccountCache.get(accountDO1.getId()));
60   - assertPojoEquals(accountDO02, mailAccountCache.get(accountDO02.getId()));
61   - }
62 42  
63 43 @Test
64 44 public void testCreateMailAccount_success() {
... ... @@ -72,7 +52,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
72 52 // 校验记录的属性是否正确
73 53 MailAccountDO mailAccount = mailAccountMapper.selectById(mailAccountId);
74 54 assertPojoEquals(reqVO, mailAccount);
75   - verify(mailProducer).sendMailAccountRefreshMessage();
76 55 }
77 56  
78 57 @Test
... ... @@ -91,7 +70,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
91 70 // 校验是否更新正确
92 71 MailAccountDO mailAccount = mailAccountMapper.selectById(reqVO.getId()); // 获取最新的
93 72 assertPojoEquals(reqVO, mailAccount);
94   - verify(mailProducer).sendMailAccountRefreshMessage();
95 73 }
96 74  
97 75 @Test
... ... @@ -115,9 +93,8 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
115 93  
116 94 // 调用
117 95 mailAccountService.deleteMailAccount(id);
118   - // 校验数据不存在了
119   - assertNull(mailAccountMapper.selectById(id));
120   - verify(mailProducer).sendMailAccountRefreshMessage();
  96 + // 校验数据不存在了
  97 + assertNull(mailAccountMapper.selectById(id));
121 98 }
122 99  
123 100 @Test
... ... @@ -125,7 +102,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
125 102 // mock 数据
126 103 MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class);
127 104 mailAccountMapper.insert(dbMailAccount);// @Sql: 先插入出一条存在的数据
128   - mailAccountService.initLocalCache();
129 105 // 准备参数
130 106 Long id = dbMailAccount.getId();
131 107  
... ... @@ -146,27 +122,27 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
146 122  
147 123 @Test
148 124 public void testGetMailAccountPage() {
149   - // mock 数据
150   - MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class, o -> { // 等会查询到
151   - o.setMail("768@qq.com");
152   - o.setUsername("yunai");
153   - });
154   - mailAccountMapper.insert(dbMailAccount);
155   - // 测试 mail 不匹配
156   - mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setMail("788@qq.com")));
157   - // 测试 username 不匹配
158   - mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setUsername("tudou")));
159   - // 准备参数
160   - MailAccountPageReqVO reqVO = new MailAccountPageReqVO();
161   - reqVO.setMail("768");
162   - reqVO.setUsername("yu");
163   -
164   - // 调用
165   - PageResult<MailAccountDO> pageResult = mailAccountService.getMailAccountPage(reqVO);
166   - // 断言
167   - assertEquals(1, pageResult.getTotal());
168   - assertEquals(1, pageResult.getList().size());
169   - assertPojoEquals(dbMailAccount, pageResult.getList().get(0));
  125 + // mock 数据
  126 + MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class, o -> { // 等会查询到
  127 + o.setMail("768@qq.com");
  128 + o.setUsername("yunai");
  129 + });
  130 + mailAccountMapper.insert(dbMailAccount);
  131 + // 测试 mail 不匹配
  132 + mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setMail("788@qq.com")));
  133 + // 测试 username 不匹配
  134 + mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setUsername("tudou")));
  135 + // 准备参数
  136 + MailAccountPageReqVO reqVO = new MailAccountPageReqVO();
  137 + reqVO.setMail("768");
  138 + reqVO.setUsername("yu");
  139 +
  140 + // 调用
  141 + PageResult<MailAccountDO> pageResult = mailAccountService.getMailAccountPage(reqVO);
  142 + // 断言
  143 + assertEquals(1, pageResult.getTotal());
  144 + assertEquals(1, pageResult.getList().size());
  145 + assertPojoEquals(dbMailAccount, pageResult.getList().get(0));
170 146 }
171 147  
172 148 @Test
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java
... ... @@ -25,10 +25,10 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
25 25 import static org.junit.jupiter.api.Assertions.*;
26 26  
27 27 /**
28   -* {@link MailLogServiceImpl} 的单元测试类
29   -*
30   -* @author 芋道源码
31   -*/
  28 + * {@link MailLogServiceImpl} 的单元测试类
  29 + *
  30 + * @author 芋道源码
  31 + */
32 32 @Import(MailLogServiceImpl.class)
33 33 public class MailLogServiceImplTest extends BaseDbUnitTest {
34 34  
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java
... ... @@ -33,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
33 33 import static org.mockito.ArgumentMatchers.eq;
34 34 import static org.mockito.Mockito.*;
35 35  
36   -class MailSendServiceImplTest extends BaseMockitoUnitTest {
  36 +public class MailSendServiceImplTest extends BaseMockitoUnitTest {
37 37  
38 38 @InjectMocks
39 39 private MailSendServiceImpl mailSendService;
... ... @@ -278,16 +278,17 @@ class MailSendServiceImplTest extends BaseMockitoUnitTest {
278 278  
279 279 // mock 方法(发送邮件)
280 280 String messageId = randomString();
281   - mailUtilMock.when(() -> MailUtil.send(argThat(mailAccount -> {
282   - assertEquals("芋艿 <7685@qq.com>", mailAccount.getFrom());
283   - assertTrue(mailAccount.isAuth());
284   - assertEquals(account.getUsername(), mailAccount.getUser());
285   - assertEquals(account.getPassword(), mailAccount.getPass());
286   - assertEquals(account.getHost(), mailAccount.getHost());
287   - assertEquals(account.getPort(), mailAccount.getPort());
288   - assertEquals(account.getSslEnable(), mailAccount.isSslEnable());
289   - return true;
290   - }), eq(message.getMail()), eq(message.getTitle()), eq(message.getContent()), eq(true)))
  281 + mailUtilMock.when(() -> MailUtil.send(
  282 + argThat(mailAccount -> {
  283 + assertEquals("芋艿 <7685@qq.com>", mailAccount.getFrom());
  284 + assertTrue(mailAccount.isAuth());
  285 + assertEquals(account.getUsername(), mailAccount.getUser());
  286 + assertEquals(account.getPassword(), mailAccount.getPass());
  287 + assertEquals(account.getHost(), mailAccount.getHost());
  288 + assertEquals(account.getPort(), mailAccount.getPort());
  289 + assertEquals(account.getSslEnable(), mailAccount.isSslEnable());
  290 + return true;
  291 + }), eq(message.getMail()), eq(message.getTitle()), eq(message.getContent()), eq(true)))
291 292 .thenReturn(messageId);
292 293  
293 294 // 调用
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImplTest.java
... ... @@ -8,9 +8,7 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemp
8 8 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateUpdateReqVO;
9 9 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
10 10 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
11   -import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
12 11 import org.junit.jupiter.api.Test;
13   -import org.springframework.boot.test.mock.mockito.MockBean;
14 12 import org.springframework.context.annotation.Import;
15 13  
16 14 import javax.annotation.Resource;
... ... @@ -27,13 +25,12 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId
27 25 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
28 26 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS;
29 27 import static org.junit.jupiter.api.Assertions.*;
30   -import static org.mockito.Mockito.verify;
31 28  
32 29 /**
33   -* {@link MailTemplateServiceImpl} 的单元测试类
34   -*
35   -* @author 芋道源码
36   -*/
  30 + * {@link MailTemplateServiceImpl} 的单元测试类
  31 + *
  32 + * @author 芋道源码
  33 + */
37 34 @Import(MailTemplateServiceImpl.class)
38 35 public class MailTemplateServiceImplTest extends BaseDbUnitTest {
39 36  
... ... @@ -43,24 +40,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
43 40 @Resource
44 41 private MailTemplateMapper mailTemplateMapper;
45 42  
46   - @MockBean
47   - private MailProducer mailProducer;
48   -
49   - @Test
50   - public void testInitLocalCache() {
51   - MailTemplateDO templateDO01 = randomPojo(MailTemplateDO.class);
52   - mailTemplateMapper.insert(templateDO01);
53   - MailTemplateDO templateDO02 = randomPojo(MailTemplateDO.class);
54   - mailTemplateMapper.insert(templateDO02);
55   -
56   - // 调用
57   - mailTemplateService.initLocalCache();
58   - // 断言 mailTemplateCache 缓存
59   - Map<String, MailTemplateDO> mailTemplateCache = mailTemplateService.getMailTemplateCache();
60   - assertPojoEquals(templateDO01, mailTemplateCache.get(templateDO01.getCode()));
61   - assertPojoEquals(templateDO02, mailTemplateCache.get(templateDO02.getCode()));
62   - }
63   -
64 43 @Test
65 44 public void testCreateMailTemplate_success() {
66 45 // 准备参数
... ... @@ -73,7 +52,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
73 52 // 校验记录的属性是否正确
74 53 MailTemplateDO mailTemplate = mailTemplateMapper.selectById(mailTemplateId);
75 54 assertPojoEquals(reqVO, mailTemplate);
76   - verify(mailProducer).sendMailTemplateRefreshMessage();
77 55 }
78 56  
79 57 @Test
... ... @@ -91,7 +69,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
91 69 // 校验是否更新正确
92 70 MailTemplateDO mailTemplate = mailTemplateMapper.selectById(reqVO.getId()); // 获取最新的
93 71 assertPojoEquals(reqVO, mailTemplate);
94   - verify(mailProducer).sendMailTemplateRefreshMessage();
95 72 }
96 73  
97 74 @Test
... ... @@ -115,7 +92,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
115 92 mailTemplateService.deleteMailTemplate(id);
116 93 // 校验数据不存在了
117 94 assertNull(mailTemplateMapper.selectById(id));
118   - verify(mailProducer).sendMailTemplateRefreshMessage();
119 95 }
120 96  
121 97 @Test
... ... @@ -129,39 +105,39 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
129 105  
130 106 @Test
131 107 public void testGetMailTemplatePage() {
132   - // mock 数据
133   - MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class, o -> { // 等会查询到
134   - o.setName("源码");
135   - o.setCode("test_01");
136   - o.setAccountId(1L);
137   - o.setStatus(CommonStatusEnum.ENABLE.getStatus());
138   - o.setCreateTime(buildTime(2023, 2, 3));
139   - });
140   - mailTemplateMapper.insert(dbMailTemplate);
141   - // 测试 name 不匹配
142   - mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setName("芋道")));
143   - // 测试 code 不匹配
144   - mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCode("test_02")));
145   - // 测试 accountId 不匹配
146   - mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setAccountId(2L)));
147   - // 测试 status 不匹配
148   - mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
149   - // 测试 createTime 不匹配
150   - mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCreateTime(buildTime(2023, 1, 5))));
151   - // 准备参数
152   - MailTemplatePageReqVO reqVO = new MailTemplatePageReqVO();
153   - reqVO.setName("源");
154   - reqVO.setCode("est_01");
155   - reqVO.setAccountId(1L);
156   - reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
157   - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 5));
158   -
159   - // 调用
160   - PageResult<MailTemplateDO> pageResult = mailTemplateService.getMailTemplatePage(reqVO);
161   - // 断言
162   - assertEquals(1, pageResult.getTotal());
163   - assertEquals(1, pageResult.getList().size());
164   - assertPojoEquals(dbMailTemplate, pageResult.getList().get(0));
  108 + // mock 数据
  109 + MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class, o -> { // 等会查询到
  110 + o.setName("源码");
  111 + o.setCode("test_01");
  112 + o.setAccountId(1L);
  113 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  114 + o.setCreateTime(buildTime(2023, 2, 3));
  115 + });
  116 + mailTemplateMapper.insert(dbMailTemplate);
  117 + // 测试 name 不匹配
  118 + mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setName("芋道")));
  119 + // 测试 code 不匹配
  120 + mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCode("test_02")));
  121 + // 测试 accountId 不匹配
  122 + mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setAccountId(2L)));
  123 + // 测试 status 不匹配
  124 + mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
  125 + // 测试 createTime 不匹配
  126 + mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCreateTime(buildTime(2023, 1, 5))));
  127 + // 准备参数
  128 + MailTemplatePageReqVO reqVO = new MailTemplatePageReqVO();
  129 + reqVO.setName("源");
  130 + reqVO.setCode("est_01");
  131 + reqVO.setAccountId(1L);
  132 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
  133 + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 5));
  134 +
  135 + // 调用
  136 + PageResult<MailTemplateDO> pageResult = mailTemplateService.getMailTemplatePage(reqVO);
  137 + // 断言
  138 + assertEquals(1, pageResult.getTotal());
  139 + assertEquals(1, pageResult.getList().size());
  140 + assertPojoEquals(dbMailTemplate, pageResult.getList().get(0));
165 141 }
166 142  
167 143 @Test
... ... @@ -199,7 +175,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
199 175 // mock 数据
200 176 MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class);
201 177 mailTemplateMapper.insert(dbMailTemplate);
202   - mailTemplateService.initLocalCache();
203 178 // 准备参数
204 179 String code = dbMailTemplate.getCode();
205 180  
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/notify/NotifyTemplateServiceImplTest.java
... ... @@ -8,9 +8,7 @@ import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.Notify
8 8 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateUpdateReqVO;
9 9 import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
10 10 import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyTemplateMapper;
11   -import cn.iocoder.yudao.module.system.mq.producer.notify.NotifyProducer;
12 11 import org.junit.jupiter.api.Test;
13   -import org.springframework.boot.test.mock.mockito.MockBean;
14 12 import org.springframework.context.annotation.Import;
15 13  
16 14 import javax.annotation.Resource;
... ... @@ -25,13 +23,12 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
25 23 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
26 24 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS;
27 25 import static org.junit.jupiter.api.Assertions.*;
28   -import static org.mockito.Mockito.verify;
29 26  
30 27 /**
31   -* {@link NotifyTemplateServiceImpl} 的单元测试类
32   -*
33   -* @author 芋道源码
34   -*/
  28 + * {@link NotifyTemplateServiceImpl} 的单元测试类
  29 + *
  30 + * @author 芋道源码
  31 + */
35 32 @Import(NotifyTemplateServiceImpl.class)
36 33 public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
37 34  
... ... @@ -41,9 +38,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
41 38 @Resource
42 39 private NotifyTemplateMapper notifyTemplateMapper;
43 40  
44   - @MockBean
45   - private NotifyProducer notifyProducer;
46   -
47 41 @Test
48 42 public void testCreateNotifyTemplate_success() {
49 43 // 准备参数
... ... @@ -57,7 +51,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
57 51 // 校验记录的属性是否正确
58 52 NotifyTemplateDO notifyTemplate = notifyTemplateMapper.selectById(notifyTemplateId);
59 53 assertPojoEquals(reqVO, notifyTemplate);
60   - verify(notifyProducer).sendNotifyTemplateRefreshMessage();
61 54 }
62 55  
63 56 @Test
... ... @@ -76,7 +69,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
76 69 // 校验是否更新正确
77 70 NotifyTemplateDO notifyTemplate = notifyTemplateMapper.selectById(reqVO.getId()); // 获取最新的
78 71 assertPojoEquals(reqVO, notifyTemplate);
79   - verify(notifyProducer).sendNotifyTemplateRefreshMessage();
80 72 }
81 73  
82 74 @Test
... ... @@ -98,9 +90,8 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
98 90  
99 91 // 调用
100 92 notifyTemplateService.deleteNotifyTemplate(id);
101   - // 校验数据不存在了
102   - assertNull(notifyTemplateMapper.selectById(id));
103   - verify(notifyProducer).sendNotifyTemplateRefreshMessage();
  93 + // 校验数据不存在了
  94 + assertNull(notifyTemplateMapper.selectById(id));
104 95 }
105 96  
106 97 @Test
... ... @@ -114,35 +105,35 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
114 105  
115 106 @Test
116 107 public void testGetNotifyTemplatePage() {
117   - // mock 数据
118   - NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class, o -> { // 等会查询到
119   - o.setName("芋头");
120   - o.setCode("test_01");
121   - o.setStatus(CommonStatusEnum.ENABLE.getStatus());
122   - o.setCreateTime(buildTime(2022, 2, 3));
123   - });
124   - notifyTemplateMapper.insert(dbNotifyTemplate);
125   - // 测试 name 不匹配
126   - notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setName("投")));
127   - // 测试 code 不匹配
128   - notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCode("test_02")));
129   - // 测试 status 不匹配
130   - notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
131   - // 测试 createTime 不匹配
132   - notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCreateTime(buildTime(2022, 1, 5))));
133   - // 准备参数
134   - NotifyTemplatePageReqVO reqVO = new NotifyTemplatePageReqVO();
135   - reqVO.setName("芋");
136   - reqVO.setCode("est_01");
137   - reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
138   - reqVO.setCreateTime(buildBetweenTime(2022, 2, 1, 2022, 2, 5));
139   -
140   - // 调用
141   - PageResult<NotifyTemplateDO> pageResult = notifyTemplateService.getNotifyTemplatePage(reqVO);
142   - // 断言
143   - assertEquals(1, pageResult.getTotal());
144   - assertEquals(1, pageResult.getList().size());
145   - assertPojoEquals(dbNotifyTemplate, pageResult.getList().get(0));
  108 + // mock 数据
  109 + NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class, o -> { // 等会查询到
  110 + o.setName("芋头");
  111 + o.setCode("test_01");
  112 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  113 + o.setCreateTime(buildTime(2022, 2, 3));
  114 + });
  115 + notifyTemplateMapper.insert(dbNotifyTemplate);
  116 + // 测试 name 不匹配
  117 + notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setName("投")));
  118 + // 测试 code 不匹配
  119 + notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCode("test_02")));
  120 + // 测试 status 不匹配
  121 + notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
  122 + // 测试 createTime 不匹配
  123 + notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCreateTime(buildTime(2022, 1, 5))));
  124 + // 准备参数
  125 + NotifyTemplatePageReqVO reqVO = new NotifyTemplatePageReqVO();
  126 + reqVO.setName("芋");
  127 + reqVO.setCode("est_01");
  128 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
  129 + reqVO.setCreateTime(buildBetweenTime(2022, 2, 1, 2022, 2, 5));
  130 +
  131 + // 调用
  132 + PageResult<NotifyTemplateDO> pageResult = notifyTemplateService.getNotifyTemplatePage(reqVO);
  133 + // 断言
  134 + assertEquals(1, pageResult.getTotal());
  135 + assertEquals(1, pageResult.getList().size());
  136 + assertPojoEquals(dbNotifyTemplate, pageResult.getList().get(0));
146 137 }
147 138  
148 139 @Test
... ... @@ -164,7 +155,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
164 155 // mock 数据
165 156 NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class);
166 157 notifyTemplateMapper.insert(dbNotifyTemplate);
167   - notifyTemplateService.initLocalCache();
168 158 // 准备参数
169 159 String code = dbNotifyTemplate.getCode();
170 160  
... ... @@ -173,7 +163,7 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
173 163 // 断言
174 164 assertPojoEquals(dbNotifyTemplate, notifyTemplate);
175 165 }
176   -
  166 +
177 167 @Test
178 168 public void testFormatNotifyTemplateContent() {
179 169 // 准备参数
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImplTest.java
1 1 package cn.iocoder.yudao.module.system.service.oauth2;
2 2  
3   -import cn.hutool.core.map.MapUtil;
  3 +import cn.hutool.extra.spring.SpringUtil;
4 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
... ... @@ -9,14 +9,12 @@ import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2Cl
9 9 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientUpdateReqVO;
10 10 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
11 11 import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2ClientMapper;
12   -import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer;
13 12 import org.junit.jupiter.api.Test;
14   -import org.springframework.boot.test.mock.mockito.MockBean;
  13 +import org.mockito.MockedStatic;
15 14 import org.springframework.context.annotation.Import;
16 15  
17 16 import javax.annotation.Resource;
18 17 import java.util.Collections;
19   -import java.util.Map;
20 18  
21 19 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
22 20 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
... ... @@ -24,13 +22,14 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
24 22 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
25 23 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
26 24 import static org.junit.jupiter.api.Assertions.*;
27   -import static org.mockito.Mockito.verify;
  25 +import static org.mockito.ArgumentMatchers.eq;
  26 +import static org.mockito.Mockito.mockStatic;
28 27  
29 28 /**
30   -* {@link OAuth2ClientServiceImpl} 的单元测试类
31   -*
32   -* @author 芋道源码
33   -*/
  29 + * {@link OAuth2ClientServiceImpl} 的单元测试类
  30 + *
  31 + * @author 芋道源码
  32 + */
34 33 @Import(OAuth2ClientServiceImpl.class)
35 34 public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
36 35  
... ... @@ -40,26 +39,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
40 39 @Resource
41 40 private OAuth2ClientMapper oauth2ClientMapper;
42 41  
43   - @MockBean
44   - private OAuth2ClientProducer oauth2ClientProducer;
45   -
46   - @Test
47   - public void testInitLocalCache() {
48   - // mock 数据
49   - OAuth2ClientDO clientDO1 = randomPojo(OAuth2ClientDO.class);
50   - oauth2ClientMapper.insert(clientDO1);
51   - OAuth2ClientDO clientDO2 = randomPojo(OAuth2ClientDO.class);
52   - oauth2ClientMapper.insert(clientDO2);
53   -
54   - // 调用
55   - oauth2ClientService.initLocalCache();
56   - // 断言 clientCache 缓存
57   - Map<String, OAuth2ClientDO> clientCache = oauth2ClientService.getClientCache();
58   - assertEquals(2, clientCache.size());
59   - assertPojoEquals(clientDO1, clientCache.get(clientDO1.getClientId()));
60   - assertPojoEquals(clientDO2, clientCache.get(clientDO2.getClientId()));
61   - }
62   -
63 42 @Test
64 43 public void testCreateOAuth2Client_success() {
65 44 // 准备参数
... ... @@ -73,7 +52,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
73 52 // 校验记录的属性是否正确
74 53 OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(oauth2ClientId);
75 54 assertPojoEquals(reqVO, oAuth2Client);
76   - verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
77 55 }
78 56  
79 57 @Test
... ... @@ -92,7 +70,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
92 70 // 校验是否更新正确
93 71 OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(reqVO.getId()); // 获取最新的
94 72 assertPojoEquals(reqVO, oAuth2Client);
95   - verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
96 73 }
97 74  
98 75 @Test
... ... @@ -116,7 +93,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
116 93 oauth2ClientService.deleteOAuth2Client(id);
117 94 // 校验数据不存在了
118 95 assertNull(oauth2ClientMapper.selectById(id));
119   - verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
120 96 }
121 97  
122 98 @Test
... ... @@ -167,62 +143,78 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
167 143 }
168 144  
169 145 @Test
  146 + public void testGetOAuth2ClientFromCache() {
  147 + // mock 数据
  148 + OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class);
  149 + oauth2ClientMapper.insert(clientDO);
  150 + // 准备参数
  151 + String clientId = clientDO.getClientId();
  152 +
  153 + // 调用,并断言
  154 + OAuth2ClientDO dbClientDO = oauth2ClientService.getOAuth2ClientFromCache(clientId);
  155 + assertPojoEquals(clientDO, dbClientDO);
  156 + }
  157 +
  158 + @Test
170 159 public void testGetOAuth2ClientPage() {
171   - // mock 数据
172   - OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class, o -> { // 等会查询到
173   - o.setName("潜龙");
174   - o.setStatus(CommonStatusEnum.ENABLE.getStatus());
175   - });
176   - oauth2ClientMapper.insert(dbOAuth2Client);
177   - // 测试 name 不匹配
178   - oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰")));
179   - // 测试 status 不匹配
180   - oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
181   - // 准备参数
182   - OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO();
183   - reqVO.setName("龙");
184   - reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
185   -
186   - // 调用
187   - PageResult<OAuth2ClientDO> pageResult = oauth2ClientService.getOAuth2ClientPage(reqVO);
188   - // 断言
189   - assertEquals(1, pageResult.getTotal());
190   - assertEquals(1, pageResult.getList().size());
191   - assertPojoEquals(dbOAuth2Client, pageResult.getList().get(0));
  160 + // mock 数据
  161 + OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class, o -> { // 等会查询到
  162 + o.setName("潜龙");
  163 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  164 + });
  165 + oauth2ClientMapper.insert(dbOAuth2Client);
  166 + // 测试 name 不匹配
  167 + oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰")));
  168 + // 测试 status 不匹配
  169 + oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
  170 + // 准备参数
  171 + OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO();
  172 + reqVO.setName("龙");
  173 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
  174 +
  175 + // 调用
  176 + PageResult<OAuth2ClientDO> pageResult = oauth2ClientService.getOAuth2ClientPage(reqVO);
  177 + // 断言
  178 + assertEquals(1, pageResult.getTotal());
  179 + assertEquals(1, pageResult.getList().size());
  180 + assertPojoEquals(dbOAuth2Client, pageResult.getList().get(0));
192 181 }
193 182  
194 183 @Test
195 184 public void testValidOAuthClientFromCache() {
196   - // mock 方法
197   - OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId("default")
198   - .setStatus(CommonStatusEnum.ENABLE.getStatus());
199   - OAuth2ClientDO client02 = randomPojo(OAuth2ClientDO.class).setClientId("disable")
200   - .setStatus(CommonStatusEnum.DISABLE.getStatus());
201   - Map<String, OAuth2ClientDO> clientCache = MapUtil.<String, OAuth2ClientDO>builder()
202   - .put(client.getClientId(), client)
203   - .put(client02.getClientId(), client02).build();
204   - oauth2ClientService.setClientCache(clientCache);
205   -
206   - // 调用,并断言
207   - assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(randomString(),
208   - null, null, null, null), OAUTH2_CLIENT_NOT_EXISTS);
209   - assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("disable",
210   - null, null, null, null), OAUTH2_CLIENT_DISABLE);
211   - assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
212   - randomString(), null, null, null), OAUTH2_CLIENT_CLIENT_SECRET_ERROR);
213   - assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
214   - null, randomString(), null, null), OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS);
215   - assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
216   - null, null, Collections.singleton(randomString()), null), OAUTH2_CLIENT_SCOPE_OVER);
217   - assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
218   - null, null, null, "test"), OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH, "test");
219   - // 成功调用(1:参数完整)
220   - OAuth2ClientDO result = oauth2ClientService.validOAuthClientFromCache(client.getClientId(), client.getSecret(),
221   - client.getAuthorizedGrantTypes().get(0), client.getScopes(), client.getRedirectUris().get(0));
222   - assertPojoEquals(client, result);
223   - // 成功调用(2:只有 clientId 参数)
224   - result = oauth2ClientService.validOAuthClientFromCache(client.getClientId());
225   - assertPojoEquals(client, result);
  185 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  186 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(OAuth2ClientServiceImpl.class)))
  187 + .thenReturn(oauth2ClientService);
  188 +
  189 + // mock 方法
  190 + OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId("default")
  191 + .setStatus(CommonStatusEnum.ENABLE.getStatus());
  192 + oauth2ClientMapper.insert(client);
  193 + OAuth2ClientDO client02 = randomPojo(OAuth2ClientDO.class).setClientId("disable")
  194 + .setStatus(CommonStatusEnum.DISABLE.getStatus());
  195 + oauth2ClientMapper.insert(client02);
  196 +
  197 + // 调用,并断言
  198 + assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(randomString(),
  199 + null, null, null, null), OAUTH2_CLIENT_NOT_EXISTS);
  200 + assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("disable",
  201 + null, null, null, null), OAUTH2_CLIENT_DISABLE);
  202 + assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
  203 + randomString(), null, null, null), OAUTH2_CLIENT_CLIENT_SECRET_ERROR);
  204 + assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
  205 + null, randomString(), null, null), OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS);
  206 + assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
  207 + null, null, Collections.singleton(randomString()), null), OAUTH2_CLIENT_SCOPE_OVER);
  208 + assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
  209 + null, null, null, "test"), OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH, "test");
  210 + // 成功调用(1:参数完整)
  211 + OAuth2ClientDO result = oauth2ClientService.validOAuthClientFromCache(client.getClientId(), client.getSecret(),
  212 + client.getAuthorizedGrantTypes().get(0), client.getScopes(), client.getRedirectUris().get(0));
  213 + assertPojoEquals(client, result);
  214 + // 成功调用(2:只有 clientId 参数)
  215 + result = oauth2ClientService.validOAuthClientFromCache(client.getClientId());
  216 + assertPojoEquals(client, result);
  217 + }
226 218 }
227 219  
228 220 }
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImplTest.java
... ... @@ -8,10 +8,7 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuUp
8 8 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
9 9 import cn.iocoder.yudao.module.system.dal.mysql.permission.MenuMapper;
10 10 import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
11   -import cn.iocoder.yudao.module.system.mq.producer.permission.MenuProducer;
12 11 import cn.iocoder.yudao.module.system.service.tenant.TenantService;
13   -import com.google.common.collect.LinkedListMultimap;
14   -import com.google.common.collect.Multimap;
15 12 import org.junit.jupiter.api.Test;
16 13 import org.springframework.boot.test.mock.mockito.MockBean;
17 14 import org.springframework.context.annotation.Import;
... ... @@ -26,8 +23,6 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
26 23 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
27 24 import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
28 25 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
29   -import static java.util.Arrays.asList;
30   -import static java.util.Collections.singletonList;
31 26 import static org.junit.jupiter.api.Assertions.assertEquals;
32 27 import static org.junit.jupiter.api.Assertions.assertNull;
33 28 import static org.mockito.ArgumentMatchers.argThat;
... ... @@ -46,35 +41,12 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
46 41 @MockBean
47 42 private PermissionService permissionService;
48 43 @MockBean
49   - private MenuProducer menuProducer;
50   - @MockBean
51 44 private TenantService tenantService;
52 45  
53 46 @Test
54   - public void testInitLocalCache_success() {
55   - MenuDO menuDO1 = randomPojo(MenuDO.class);
56   - menuMapper.insert(menuDO1);
57   - MenuDO menuDO2 = randomPojo(MenuDO.class);
58   - menuMapper.insert(menuDO2);
59   -
60   - // 调用
61   - menuService.initLocalCache();
62   - // 校验 menuCache 缓存
63   - Map<Long, MenuDO> menuCache = menuService.getMenuCache();
64   - assertEquals(2, menuCache.size());
65   - assertPojoEquals(menuDO1, menuCache.get(menuDO1.getId()));
66   - assertPojoEquals(menuDO2, menuCache.get(menuDO2.getId()));
67   - // 校验 permissionMenuCache 缓存
68   - Multimap<String, MenuDO> permissionMenuCache = menuService.getPermissionMenuCache();
69   - assertEquals(2, permissionMenuCache.size());
70   - assertPojoEquals(menuDO1, permissionMenuCache.get(menuDO1.getPermission()));
71   - assertPojoEquals(menuDO2, permissionMenuCache.get(menuDO2.getPermission()));
72   - }
73   -
74   - @Test
75 47 public void testCreateMenu_success() {
76 48 // mock 数据(构造父菜单)
77   - MenuDO menuDO = createMenuDO(MenuTypeEnum.MENU,
  49 + MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU,
78 50 "parent", 0L);
79 51 menuMapper.insert(menuDO);
80 52 Long parentId = menuDO.getId();
... ... @@ -89,14 +61,12 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
89 61 // 校验记录的属性是否正确
90 62 MenuDO dbMenu = menuMapper.selectById(menuId);
91 63 assertPojoEquals(reqVO, dbMenu);
92   - // 校验调用
93   - verify(menuProducer).sendMenuRefreshMessage();
94 64 }
95 65  
96 66 @Test
97 67 public void testUpdateMenu_success() {
98 68 // mock 数据(构造父子菜单)
99   - MenuDO sonMenuDO = initParentAndSonMenu();
  69 + MenuDO sonMenuDO = createParentAndSonMenu();
100 70 Long sonId = sonMenuDO.getId();
101 71 // 准备参数
102 72 MenuUpdateReqVO reqVO = randomPojo(MenuUpdateReqVO.class, o -> {
... ... @@ -111,8 +81,6 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
111 81 // 校验记录的属性是否正确
112 82 MenuDO dbMenu = menuMapper.selectById(sonId);
113 83 assertPojoEquals(reqVO, dbMenu);
114   - // 校验调用
115   - verify(menuProducer).sendMenuRefreshMessage();
116 84 }
117 85  
118 86 @Test
... ... @@ -137,7 +105,6 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
137 105 MenuDO dbMenuDO = menuMapper.selectById(id);
138 106 assertNull(dbMenuDO);
139 107 verify(permissionService).processMenuDeleted(id);
140   - verify(menuProducer).sendMenuRefreshMessage();
141 108 }
142 109  
143 110 @Test
... ... @@ -149,7 +116,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
149 116 @Test
150 117 public void testDeleteMenu_existChildren() {
151 118 // mock 数据(构造父子菜单)
152   - MenuDO sonMenu = initParentAndSonMenu();
  119 + MenuDO sonMenu = createParentAndSonMenu();
153 120 // 准备参数
154 121 Long parentId = sonMenu.getParentId();
155 122  
... ... @@ -219,85 +186,6 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
219 186 }
220 187  
221 188 @Test
222   - public void testListMenusFromCache_withoutId() {
223   - // mock 缓存
224   - Map<Long, MenuDO> menuCache = new HashMap<>();
225   - // 可被匹配
226   - MenuDO menuDO = randomPojo(MenuDO.class, o -> o.setId(1L)
227   - .setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus()));
228   - menuCache.put(menuDO.getId(), menuDO);
229   - // 测试 type 不匹配
230   - menuCache.put(3L, randomPojo(MenuDO.class, o -> o.setId(3L)
231   - .setType(MenuTypeEnum.BUTTON.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus())));
232   - // 测试 status 不匹配
233   - menuCache.put(4L, randomPojo(MenuDO.class, o -> o.setId(4L)
234   - .setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.DISABLE.getStatus())));
235   - menuService.setMenuCache(menuCache);
236   - // 准备参数
237   - Collection<Integer> menuTypes = singletonList(MenuTypeEnum.MENU.getType());
238   - Collection<Integer> menusStatuses = singletonList(CommonStatusEnum.ENABLE.getStatus());
239   -
240   - // 调用
241   - List<MenuDO> list = menuService.getMenuListFromCache(menuTypes, menusStatuses);
242   - // 断言
243   - assertEquals(1, list.size());
244   - assertPojoEquals(menuDO, list.get(0));
245   - }
246   -
247   - @Test
248   - public void testListMenusFromCache_withId() {
249   - // mock 缓存
250   - Map<Long, MenuDO> menuCache = new HashMap<>();
251   - // 可被匹配
252   - MenuDO menuDO = randomPojo(MenuDO.class, o -> o.setId(1L)
253   - .setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus()));
254   - menuCache.put(menuDO.getId(), menuDO);
255   - // 测试 id 不匹配
256   - menuCache.put(2L, randomPojo(MenuDO.class, o -> o.setId(2L)
257   - .setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus())));
258   - // 测试 type 不匹配
259   - menuCache.put(3L, randomPojo(MenuDO.class, o -> o.setId(3L)
260   - .setType(MenuTypeEnum.BUTTON.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus())));
261   - // 测试 status 不匹配
262   - menuCache.put(4L, randomPojo(MenuDO.class, o -> o.setId(4L)
263   - .setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.DISABLE.getStatus())));
264   - menuService.setMenuCache(menuCache);
265   - // 准备参数
266   - Collection<Long> menuIds = asList(1L, 3L, 4L);
267   - Collection<Integer> menuTypes = singletonList(MenuTypeEnum.MENU.getType());
268   - Collection<Integer> menusStatuses = singletonList(CommonStatusEnum.ENABLE.getStatus());
269   -
270   - // 调用
271   - List<MenuDO> list = menuService.getMenuListFromCache(menuIds, menuTypes, menusStatuses);
272   - // 断言
273   - assertEquals(1, list.size());
274   - assertPojoEquals(menuDO, list.get(0));
275   - }
276   -
277   - @Test
278   - public void testGetMenuListByPermissionFromCache() {
279   - // mock 缓存
280   - Multimap<String, MenuDO> permissionMenuCache = LinkedListMultimap.create();
281   - // 可被匹配
282   - MenuDO menuDO01 = randomPojo(MenuDO.class, o -> o.setId(1L).setPermission("123"));
283   - permissionMenuCache.put(menuDO01.getPermission(), menuDO01);
284   - MenuDO menuDO02 = randomPojo(MenuDO.class, o -> o.setId(2L).setPermission("123"));
285   - permissionMenuCache.put(menuDO02.getPermission(), menuDO02);
286   - // 不可匹配
287   - permissionMenuCache.put("456", randomPojo(MenuDO.class, o -> o.setId(3L).setPermission("456")));
288   - menuService.setPermissionMenuCache(permissionMenuCache);
289   - // 准备参数
290   - String permission = "123";
291   -
292   - // 调用
293   - List<MenuDO> list = menuService.getMenuListByPermissionFromCache(permission);
294   - // 断言
295   - assertEquals(2, list.size());
296   - assertPojoEquals(menuDO01, list.get(0));
297   - assertPojoEquals(menuDO02, list.get(1));
298   - }
299   -
300   - @Test
301 189 public void testGetMenu() {
302 190 // mock 数据
303 191 MenuDO menu = randomPojo(MenuDO.class);
... ... @@ -314,7 +202,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
314 202 @Test
315 203 public void testValidateParentMenu_success() {
316 204 // mock 数据
317   - MenuDO menuDO = createMenuDO(MenuTypeEnum.MENU, "parent", 0L);
  205 + MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU, "parent", 0L);
318 206 menuMapper.insert(menuDO);
319 207 // 准备参数
320 208 Long parentId = menuDO.getId();
... ... @@ -340,7 +228,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
340 228 @Test
341 229 public void testValidateParentMenu_parentTypeError() {
342 230 // mock 数据
343   - MenuDO menuDO = createMenuDO(MenuTypeEnum.BUTTON, "parent", 0L);
  231 + MenuDO menuDO = buildMenuDO(MenuTypeEnum.BUTTON, "parent", 0L);
344 232 menuMapper.insert(menuDO);
345 233 // 准备参数
346 234 Long parentId = menuDO.getId();
... ... @@ -353,7 +241,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
353 241 @Test
354 242 public void testValidateMenu_success() {
355 243 // mock 父子菜单
356   - MenuDO sonMenu = initParentAndSonMenu();
  244 + MenuDO sonMenu = createParentAndSonMenu();
357 245 // 准备参数
358 246 Long parentId = sonMenu.getParentId();
359 247 Long otherSonMenuId = randomLongId();
... ... @@ -366,7 +254,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
366 254 @Test
367 255 public void testValidateMenu_sonMenuNameDuplicate() {
368 256 // mock 父子菜单
369   - MenuDO sonMenu = initParentAndSonMenu();
  257 + MenuDO sonMenu = createParentAndSonMenu();
370 258 // 准备参数
371 259 Long parentId = sonMenu.getParentId();
372 260 Long otherSonMenuId = randomLongId();
... ... @@ -380,26 +268,26 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
380 268 // ====================== 初始化方法 ======================
381 269  
382 270 /**
383   - * 构造父子菜单,返回子菜单
  271 + * 插入父子菜单,返回子菜单
384 272 *
385 273 * @return 子菜单
386 274 */
387   - private MenuDO initParentAndSonMenu() {
  275 + private MenuDO createParentAndSonMenu() {
388 276 // 构造父子菜单
389   - MenuDO parentMenuDO = createMenuDO(MenuTypeEnum.MENU, "parent", ID_ROOT);
  277 + MenuDO parentMenuDO = buildMenuDO(MenuTypeEnum.MENU, "parent", ID_ROOT);
390 278 menuMapper.insert(parentMenuDO);
391 279 // 构建子菜单
392   - MenuDO sonMenuDO = createMenuDO(MenuTypeEnum.MENU, "testSonName",
  280 + MenuDO sonMenuDO = buildMenuDO(MenuTypeEnum.MENU, "testSonName",
393 281 parentMenuDO.getParentId());
394 282 menuMapper.insert(sonMenuDO);
395 283 return sonMenuDO;
396 284 }
397 285  
398   - private MenuDO createMenuDO(MenuTypeEnum type, String name, Long parentId) {
399   - return createMenuDO(type, name, parentId, randomCommonStatus());
  286 + private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId) {
  287 + return buildMenuDO(type, name, parentId, randomCommonStatus());
400 288 }
401 289  
402   - private MenuDO createMenuDO(MenuTypeEnum type, String name, Long parentId, Integer status) {
  290 + private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId, Integer status) {
403 291 return randomPojo(MenuDO.class, o -> o.setId(null).setName(name).setParentId(parentId)
404 292 .setType(type.getType()).setStatus(status));
405 293 }
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java
1 1 package cn.iocoder.yudao.module.system.service.permission;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
5 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
7 7 import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
... ... @@ -14,32 +14,28 @@ import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
14 14 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
15 15 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
16 16 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
17   -import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
18 17 import cn.iocoder.yudao.module.system.service.dept.DeptService;
19 18 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
20   -import com.google.common.collect.ImmutableMultimap;
21   -import com.google.common.collect.Multimap;
22 19 import org.junit.jupiter.api.Test;
  20 +import org.mockito.MockedStatic;
23 21 import org.springframework.boot.test.mock.mockito.MockBean;
24 22 import org.springframework.context.annotation.Import;
25 23  
26 24 import javax.annotation.Resource;
27 25 import java.util.Collection;
28 26 import java.util.List;
29   -import java.util.Map;
30 27 import java.util.Set;
31 28  
  29 +import static cn.hutool.core.collection.ListUtil.toList;
32 30 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
33 31 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
34   -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
35   -import static java.util.Arrays.asList;
  32 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
  33 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
36 34 import static java.util.Collections.singleton;
37 35 import static java.util.Collections.singletonList;
38 36 import static org.junit.jupiter.api.Assertions.*;
39 37 import static org.mockito.ArgumentMatchers.eq;
40   -import static org.mockito.ArgumentMatchers.same;
41   -import static org.mockito.Mockito.verify;
42   -import static org.mockito.Mockito.when;
  38 +import static org.mockito.Mockito.*;
43 39  
44 40 @Import({PermissionServiceImpl.class})
45 41 public class PermissionServiceTest extends BaseDbUnitTest {
... ... @@ -61,99 +57,140 @@ public class PermissionServiceTest extends BaseDbUnitTest {
61 57 @MockBean
62 58 private AdminUserService userService;
63 59  
64   - @MockBean
65   - private PermissionProducer permissionProducer;
66   -
67 60 @Test
68   - public void testInitLocalCacheForRoleMenu() {
69   - // mock 数据
70   - RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(10L));
71   - roleMenuMapper.insert(roleMenuDO01);
72   - RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(20L));
73   - roleMenuMapper.insert(roleMenuDO02);
74   -
75   - // 调用
76   - permissionService.initLocalCacheForRoleMenu();
77   - // 断言 roleMenuCache 缓存
78   - assertEquals(1, permissionService.getRoleMenuCache().keySet().size());
79   - assertEquals(asList(10L, 20L), permissionService.getRoleMenuCache().get(1L));
80   - // 断言 menuRoleCache 缓存
81   - assertEquals(2, permissionService.getMenuRoleCache().size());
82   - assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(10L));
83   - assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(20L));
  61 + public void testHasAnyPermissions_superAdmin() {
  62 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  63 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
  64 + .thenReturn(permissionService);
  65 +
  66 + // 准备参数
  67 + Long userId = 1L;
  68 + String[] roles = new String[]{"system:user:query", "system:user:create"};
  69 + // mock 用户登录的角色
  70 + userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
  71 + RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
  72 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  73 + when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
  74 + // mock 其它方法
  75 + when(roleService.hasAnySuperAdmin(eq(asSet(100L)))).thenReturn(true);
  76 +
  77 + // 调用,并断言
  78 + assertTrue(permissionService.hasAnyPermissions(userId, roles));
  79 + }
84 80 }
85 81  
86 82 @Test
87   - public void testInitLocalCacheForUserRole() {
88   - // mock 数据
89   - UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
90   - userRoleMapper.insert(userRoleDO01);
91   - UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
92   - userRoleMapper.insert(roleMenuDO02);
  83 + public void testHasAnyPermissions_normal() {
  84 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  85 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
  86 + .thenReturn(permissionService);
  87 +
  88 + // 准备参数
  89 + Long userId = 1L;
  90 + String[] roles = new String[]{"system:user:query", "system:user:create"};
  91 + // mock 用户登录的角色
  92 + userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
  93 + RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
  94 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  95 + when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
  96 + // mock 菜单
  97 + Long menuId = 1000L;
  98 + when(menuService.getMenuIdListByPermissionFromCache(
  99 + eq("system:user:create"))).thenReturn(singletonList(menuId));
  100 + roleMenuMapper.insert(randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1000L));
  101 +
  102 + // 调用,并断言
  103 + assertTrue(permissionService.hasAnyPermissions(userId, roles));
  104 + }
  105 + }
93 106  
94   - // 调用
95   - permissionService.initLocalCacheForUserRole();
96   - // 断言 roleMenuCache 缓存
97   - assertEquals(1, permissionService.getUserRoleCache().size());
98   - assertEquals(asSet(10L, 20L), permissionService.getUserRoleCache().get(1L));
  107 + @Test
  108 + public void testHasAnyRoles() {
  109 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  110 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
  111 + .thenReturn(permissionService);
  112 +
  113 + // 准备参数
  114 + Long userId = 1L;
  115 + String[] roles = new String[]{"yunai", "tudou"};
  116 + // mock 用户与角色的缓存
  117 + userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
  118 + RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L).setCode("tudou")
  119 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  120 + when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
  121 +
  122 + // 调用,并断言
  123 + assertTrue(permissionService.hasAnyRoles(userId, roles));
  124 + }
99 125 }
100 126  
  127 + // ========== 角色-菜单的相关方法 ==========
  128 +
101 129 @Test
102   - public void testGetRoleMenuListFromCache_superAdmin() {
  130 + public void testAssignRoleMenu() {
103 131 // 准备参数
104   - Collection<Long> roleIds = singletonList(100L);
105   - Collection<Integer> menuTypes = asList(2, 3);
106   - Collection<Integer> menusStatuses = asList(0, 1);
107   - // mock 方法
108   - List<RoleDO> roleList = singletonList(randomPojo(RoleDO.class, o -> o.setId(100L)));
109   - when(roleService.getRoleListFromCache(eq(roleIds))).thenReturn(roleList);
110   - when(roleService.hasAnySuperAdmin(same(roleList))).thenReturn(true);
111   - List<MenuDO> menuList = randomPojoList(MenuDO.class);
112   - when(menuService.getMenuListFromCache(eq(menuTypes), eq(menusStatuses))).thenReturn(menuList);
  132 + Long roleId = 1L;
  133 + Set<Long> menuIds = asSet(200L, 300L);
  134 + // mock 数据
  135 + RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(100L);
  136 + roleMenuMapper.insert(roleMenu01);
  137 + RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(200L);
  138 + roleMenuMapper.insert(roleMenu02);
113 139  
114 140 // 调用
115   - List<MenuDO> result = permissionService.getRoleMenuListFromCache(roleIds, menuTypes, menusStatuses);
  141 + permissionService.assignRoleMenu(roleId, menuIds);
116 142 // 断言
117   - assertSame(menuList, result);
  143 + List<RoleMenuDO> roleMenuList = roleMenuMapper.selectList();
  144 + assertEquals(2, roleMenuList.size());
  145 + assertEquals(1L, roleMenuList.get(0).getRoleId());
  146 + assertEquals(200L, roleMenuList.get(0).getMenuId());
  147 + assertEquals(1L, roleMenuList.get(1).getRoleId());
  148 + assertEquals(300L, roleMenuList.get(1).getMenuId());
118 149 }
119 150  
120 151 @Test
121   - public void testGetRoleMenuListFromCache_normal() {
  152 + public void testProcessRoleDeleted() {
122 153 // 准备参数
123   - Collection<Long> roleIds = asSet(100L, 200L);
124   - Collection<Integer> menuTypes = asList(2, 3);
125   - Collection<Integer> menusStatuses = asList(0, 1);
126   - // mock 方法
127   - Multimap<Long, Long> roleMenuCache = ImmutableMultimap.<Long, Long>builder().put(100L, 1000L)
128   - .put(200L, 2000L).put(200L, 2001L).build();
129   - permissionService.setRoleMenuCache(roleMenuCache);
130   - List<MenuDO> menuList = randomPojoList(MenuDO.class);
131   - when(menuService.getMenuListFromCache(eq(asList(1000L, 2000L, 2001L)), eq(menuTypes), eq(menusStatuses))).thenReturn(menuList);
  154 + Long roleId = randomLongId();
  155 + // mock 数据 UserRole
  156 + UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setRoleId(roleId)); // 被删除
  157 + userRoleMapper.insert(userRoleDO01);
  158 + UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除
  159 + userRoleMapper.insert(userRoleDO02);
  160 + // mock 数据 RoleMenu
  161 + RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(roleId)); // 被删除
  162 + roleMenuMapper.insert(roleMenuDO01);
  163 + RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除
  164 + roleMenuMapper.insert(roleMenuDO02);
132 165  
133 166 // 调用
134   - List<MenuDO> result = permissionService.getRoleMenuListFromCache(roleIds, menuTypes, menusStatuses);
135   - // 断言
136   - assertSame(menuList, result);
  167 + permissionService.processRoleDeleted(roleId);
  168 + // 断言数据 RoleMenuDO
  169 + List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();
  170 + assertEquals(1, dbRoleMenus.size());
  171 + assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);
  172 + // 断言数据 UserRoleDO
  173 + List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
  174 + assertEquals(1, dbUserRoles.size());
  175 + assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
137 176 }
138 177  
139 178 @Test
140   - public void testGetUserRoleIdsFromCache() {
  179 + public void testProcessMenuDeleted() {
141 180 // 准备参数
142   - Long userId = 1L;
143   - Collection<Integer> roleStatuses = singleton(CommonStatusEnum.ENABLE.getStatus());
144   - // mock 方法
145   - Map<Long, Set<Long>> userRoleCache = MapUtil.<Long, Set<Long>>builder()
146   - .put(1L, asSet(10L, 20L)).build();
147   - permissionService.setUserRoleCache(userRoleCache);
148   - RoleDO roleDO01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
149   - when(roleService.getRoleFromCache(eq(10L))).thenReturn(roleDO01);
150   - RoleDO roleDO02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
151   - when(roleService.getRoleFromCache(eq(20L))).thenReturn(roleDO02);
  181 + Long menuId = randomLongId();
  182 + // mock 数据
  183 + RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setMenuId(menuId)); // 被删除
  184 + roleMenuMapper.insert(roleMenuDO01);
  185 + RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除
  186 + roleMenuMapper.insert(roleMenuDO02);
152 187  
153 188 // 调用
154   - Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(userId, roleStatuses);
155   - // 断言
156   - assertEquals(asSet(10L), roleIds);
  189 + permissionService.processMenuDeleted(menuId);
  190 + // 断言数据
  191 + List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();
  192 + assertEquals(1, dbRoleMenus.size());
  193 + assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);
157 194 }
158 195  
159 196 @Test
... ... @@ -166,7 +203,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
166 203 when(menuService.getMenuList()).thenReturn(menuList);
167 204  
168 205 // 调用
169   - Set<Long> menuIds = permissionService.getRoleMenuIds(roleId);
  206 + Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);
170 207 // 断言
171 208 assertEquals(singleton(1L), menuIds);
172 209 }
... ... @@ -182,34 +219,29 @@ public class PermissionServiceTest extends BaseDbUnitTest {
182 219 roleMenuMapper.insert(roleMenu02);
183 220  
184 221 // 调用
185   - Set<Long> menuIds = permissionService.getRoleMenuIds(roleId);
  222 + Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);
186 223 // 断言
187 224 assertEquals(asSet(1L, 2L), menuIds);
188 225 }
189 226  
190 227 @Test
191   - public void testAssignRoleMenu() {
  228 + public void testGetMenuRoleIdListByMenuIdFromCache() {
192 229 // 准备参数
193   - Long roleId = 1L;
194   - Set<Long> menuIds = asSet(200L, 300L);
  230 + Long menuId = 1L;
195 231 // mock 数据
196   - RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(100L);
  232 + RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1L);
197 233 roleMenuMapper.insert(roleMenu01);
198   - RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(200L);
  234 + RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(200L).setMenuId(1L);
199 235 roleMenuMapper.insert(roleMenu02);
200 236  
201 237 // 调用
202   - permissionService.assignRoleMenu(roleId, menuIds);
  238 + Set<Long> roleIds = permissionService.getMenuRoleIdListByMenuIdFromCache(menuId);
203 239 // 断言
204   - List<RoleMenuDO> roleMenuList = roleMenuMapper.selectList();
205   - assertEquals(2, roleMenuList.size());
206   - assertEquals(1L, roleMenuList.get(0).getRoleId());
207   - assertEquals(200L, roleMenuList.get(0).getMenuId());
208   - assertEquals(1L, roleMenuList.get(1).getRoleId());
209   - assertEquals(300L, roleMenuList.get(1).getMenuId());
210   - verify(permissionProducer).sendRoleMenuRefreshMessage();
  240 + assertEquals(asSet(100L, 200L), roleIds);
211 241 }
212 242  
  243 + // ========== 用户-角色的相关方法 ==========
  244 +
213 245 @Test
214 246 public void testAssignUserRole() {
215 247 // 准备参数
... ... @@ -230,7 +262,24 @@ public class PermissionServiceTest extends BaseDbUnitTest {
230 262 assertEquals(200L, userRoleDOList.get(0).getRoleId());
231 263 assertEquals(1L, userRoleDOList.get(1).getUserId());
232 264 assertEquals(300L, userRoleDOList.get(1).getRoleId());
233   - verify(permissionProducer).sendUserRoleRefreshMessage();
  265 + }
  266 +
  267 + @Test
  268 + public void testProcessUserDeleted() {
  269 + // 准备参数
  270 + Long userId = randomLongId();
  271 + // mock 数据
  272 + UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(userId)); // 被删除
  273 + userRoleMapper.insert(userRoleDO01);
  274 + UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除
  275 + userRoleMapper.insert(userRoleDO02);
  276 +
  277 + // 调用
  278 + permissionService.processUserDeleted(userId);
  279 + // 断言数据
  280 + List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
  281 + assertEquals(1, dbUserRoles.size());
  282 + assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
234 283 }
235 284  
236 285 @Test
... ... @@ -250,296 +299,229 @@ public class PermissionServiceTest extends BaseDbUnitTest {
250 299 }
251 300  
252 301 @Test
253   - public void testGetUserRoleIdListByRoleIds() {
  302 + public void testGetUserRoleIdListByUserIdFromCache() {
254 303 // 准备参数
255   - Collection<Long> roleIds = asSet(10L, 20L);
  304 + Long userId = 1L;
256 305 // mock 数据
257 306 UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
258 307 userRoleMapper.insert(userRoleDO01);
259   - UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(2L).setRoleId(20L));
  308 + UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
260 309 userRoleMapper.insert(roleMenuDO02);
261 310  
262 311 // 调用
263   - Set<Long> result = permissionService.getUserRoleIdListByRoleIds(roleIds);
  312 + Set<Long> result = permissionService.getUserRoleIdListByUserIdFromCache(userId);
264 313 // 断言
265   - assertEquals(asSet(1L, 2L), result);
266   - }
267   -
268   - @Test
269   - public void testAssignRoleDataScope() {
270   - // 准备参数
271   - Long roleId = 1L;
272   - Integer dataScope = 2;
273   - Set<Long> dataScopeDeptIds = asSet(10L, 20L);
274   -
275   - // 调用
276   - permissionService.assignRoleDataScope(roleId, dataScope, dataScopeDeptIds);
277   - // 断言
278   - verify(roleService).updateRoleDataScope(eq(roleId), eq(dataScope), eq(dataScopeDeptIds));
279   - }
280   -
281   - @Test
282   - public void testProcessRoleDeleted() {
283   - // 准备参数
284   - Long roleId = randomLongId();
285   - // mock 数据 UserRole
286   - UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setRoleId(roleId)); // 被删除
287   - userRoleMapper.insert(userRoleDO01);
288   - UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除
289   - userRoleMapper.insert(userRoleDO02);
290   - // mock 数据 RoleMenu
291   - RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(roleId)); // 被删除
292   - roleMenuMapper.insert(roleMenuDO01);
293   - RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除
294   - roleMenuMapper.insert(roleMenuDO02);
295   -
296   - // 调用
297   - permissionService.processRoleDeleted(roleId);
298   - // 断言数据 RoleMenuDO
299   - List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();
300   - assertEquals(1, dbRoleMenus.size());
301   - assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);
302   - // 断言数据 UserRoleDO
303   - List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
304   - assertEquals(1, dbUserRoles.size());
305   - assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
306   - // 断言调用
307   - verify(permissionProducer).sendRoleMenuRefreshMessage();
308   - verify(permissionProducer).sendUserRoleRefreshMessage();
  314 + assertEquals(asSet(10L, 20L), result);
309 315 }
310 316  
311 317 @Test
312   - public void testProcessMenuDeleted() {
  318 + public void testGetUserRoleIdsFromCache() {
313 319 // 准备参数
314   - Long menuId = randomLongId();
  320 + Long userId = 1L;
315 321 // mock 数据
316   - RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setMenuId(menuId)); // 被删除
317   - roleMenuMapper.insert(roleMenuDO01);
318   - RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除
319   - roleMenuMapper.insert(roleMenuDO02);
  322 + UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
  323 + userRoleMapper.insert(userRoleDO01);
  324 + UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
  325 + userRoleMapper.insert(roleMenuDO02);
320 326  
321 327 // 调用
322   - permissionService.processMenuDeleted(menuId);
323   - // 断言数据
324   - List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();
325   - assertEquals(1, dbRoleMenus.size());
326   - assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);
327   - // 断言调用
328   - verify(permissionProducer).sendRoleMenuRefreshMessage();
  328 + Set<Long> result = permissionService.getUserRoleIdListByUserIdFromCache(userId);
  329 + // 断言
  330 + assertEquals(asSet(10L, 20L), result);
329 331 }
330 332  
331 333 @Test
332   - public void testProcessUserDeleted() {
  334 + public void testGetUserRoleIdListByRoleId() {
333 335 // 准备参数
334   - Long userId = randomLongId();
  336 + Collection<Long> roleIds = asSet(10L, 20L);
335 337 // mock 数据
336   - UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(userId)); // 被删除
  338 + UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
337 339 userRoleMapper.insert(userRoleDO01);
338   - UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除
339   - userRoleMapper.insert(userRoleDO02);
340   -
341   - // 调用
342   - permissionService.processUserDeleted(userId);
343   - // 断言数据
344   - List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
345   - assertEquals(1, dbUserRoles.size());
346   - assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
347   - // 断言调用
348   - verify(permissionProducer).sendUserRoleRefreshMessage();
349   - }
350   -
351   - @Test
352   - public void testHasAnyPermissions_superAdmin() {
353   - // 准备参数
354   - Long userId = 1L;
355   - String[] roles = new String[]{"system:user:query", "system:user:create"};
356   - // mock 用户与角色的缓存
357   - permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
358   - RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
359   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
360   - when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
361   - // mock 其它方法
362   - when(roleService.hasAnySuperAdmin(eq(asSet(100L)))).thenReturn(true);
  340 + UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(2L).setRoleId(20L));
  341 + userRoleMapper.insert(roleMenuDO02);
363 342  
364 343 // 调用
365   - boolean has = permissionService.hasAnyPermissions(userId, roles);
  344 + Set<Long> result = permissionService.getUserRoleIdListByRoleId(roleIds);
366 345 // 断言
367   - assertTrue(has);
  346 + assertEquals(asSet(1L, 2L), result);
368 347 }
369 348  
370 349 @Test
371   - public void testHasAnyPermissions_normal() {
372   - // 准备参数
373   - Long userId = 1L;
374   - String[] roles = new String[]{"system:user:query", "system:user:create"};
375   - // mock 用户与角色的缓存
376   - permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
377   - RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
378   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
379   - when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
380   - // mock 其它方法
381   - MenuDO menu = randomPojo(MenuDO.class, o -> o.setId(1000L));
382   - when(menuService.getMenuListByPermissionFromCache(eq("system:user:create"))).thenReturn(singletonList(menu));
383   - permissionService.setMenuRoleCache(ImmutableMultimap.<Long, Long>builder().put(1000L, 100L).build());
384   -
385   -
386   - // 调用
387   - boolean has = permissionService.hasAnyPermissions(userId, roles);
388   - // 断言
389   - assertTrue(has);
  350 + public void testGetEnableUserRoleListByUserIdFromCache() {
  351 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  352 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
  353 + .thenReturn(permissionService);
  354 +
  355 + // 准备参数
  356 + Long userId = 1L;
  357 + // mock 用户登录的角色
  358 + userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
  359 + userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(200L));
  360 + RoleDO role01 = randomPojo(RoleDO.class, o -> o.setId(100L)
  361 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  362 + RoleDO role02 = randomPojo(RoleDO.class, o -> o.setId(200L)
  363 + .setStatus(CommonStatusEnum.DISABLE.getStatus()));
  364 + when(roleService.getRoleListFromCache(eq(asSet(100L, 200L))))
  365 + .thenReturn(toList(role01, role02));
  366 +
  367 + // 调用
  368 + List<RoleDO> result = permissionService.getEnableUserRoleListByUserIdFromCache(userId);
  369 + // 断言
  370 + assertEquals(1, result.size());
  371 + assertPojoEquals(role01, result.get(0));
  372 + }
390 373 }
391 374  
392   - @Test
393   - public void testHasAnyRoles_superAdmin() {
394   - // 准备参数
395   - Long userId = 1L;
396   - String[] roles = new String[]{"yunai", "tudou"};
397   - // mock 用户与角色的缓存
398   - permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
399   - RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
400   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
401   - when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
402   - // mock 其它方法
403   - when(roleService.hasAnySuperAdmin(eq(asSet(100L)))).thenReturn(true);
404   -
405   - // 调用
406   - boolean has = permissionService.hasAnyRoles(userId, roles);
407   - // 断言
408   - assertTrue(has);
409   - }
  375 + // ========== 用户-部门的相关方法 ==========
410 376  
411 377 @Test
412   - public void testHasAnyRoles_normal() {
  378 + public void testAssignRoleDataScope() {
413 379 // 准备参数
414   - Long userId = 1L;
415   - String[] roles = new String[]{"yunai", "tudou"};
416   - // mock 用户与角色的缓存
417   - permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
418   - RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L).setCode("yunai")
419   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
420   - when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
421   - // mock 其它方法
422   - when(roleService.getRoleListFromCache(eq(asSet(100L)))).thenReturn(singletonList(role));
  380 + Long roleId = 1L;
  381 + Integer dataScope = 2;
  382 + Set<Long> dataScopeDeptIds = asSet(10L, 20L);
423 383  
424 384 // 调用
425   - boolean has = permissionService.hasAnyRoles(userId, roles);
  385 + permissionService.assignRoleDataScope(roleId, dataScope, dataScopeDeptIds);
426 386 // 断言
427   - assertTrue(has);
  387 + verify(roleService).updateRoleDataScope(eq(roleId), eq(dataScope), eq(dataScopeDeptIds));
428 388 }
429 389  
430 390 @Test
431 391 public void testGetDeptDataPermission_All() {
432   - // 准备参数
433   - Long userId = 1L;
434   - // mock 用户的角色编号
435   - permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
436   - // mock 获得用户的角色
437   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())
438   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
439   - when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
440   - when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
441   -
442   - // 调用
443   - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
444   - // 断言
445   - assertTrue(result.getAll());
446   - assertFalse(result.getSelf());
447   - assertTrue(CollUtil.isEmpty(result.getDeptIds()));
  392 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  393 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
  394 + .thenReturn(permissionService);
  395 +
  396 + // 准备参数
  397 + Long userId = 1L;
  398 + // mock 用户的角色编号
  399 + userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
  400 + // mock 获得用户的角色
  401 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())
  402 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  403 + when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
  404 +
  405 + // 调用
  406 + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
  407 + // 断言
  408 + assertTrue(result.getAll());
  409 + assertFalse(result.getSelf());
  410 + assertTrue(CollUtil.isEmpty(result.getDeptIds()));
  411 + }
448 412 }
449 413  
450 414 @Test
451 415 public void testGetDeptDataPermission_DeptCustom() {
452   - // 准备参数
453   - Long userId = 1L;
454   - // mock 用户的角色编号
455   - permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
456   - // mock 获得用户的角色
457   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
458   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
459   - when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
460   - when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
461   - // mock 部门的返回
462   - when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
463   -
464   - // 调用
465   - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
466   - // 断言
467   - assertFalse(result.getAll());
468   - assertFalse(result.getSelf());
469   - assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());
470   - assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));
471   - assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
  416 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  417 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
  418 + .thenReturn(permissionService);
  419 +
  420 + // 准备参数
  421 + Long userId = 1L;
  422 + // mock 用户的角色编号
  423 + userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
  424 + // mock 获得用户的角色
  425 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
  426 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  427 + when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
  428 + // mock 部门的返回
  429 + when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
  430 + null, null); // 最后返回 null 的目的,看看会不会重复调用
  431 +
  432 + // 调用
  433 + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
  434 + // 断言
  435 + assertFalse(result.getAll());
  436 + assertFalse(result.getSelf());
  437 + assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());
  438 + assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));
  439 + assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
  440 + }
472 441 }
473 442  
474 443 @Test
475 444 public void testGetDeptDataPermission_DeptOnly() {
476   - // 准备参数
477   - Long userId = 1L;
478   - // mock 用户的角色编号
479   - permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
480   - // mock 获得用户的角色
481   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())
482   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
483   - when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
484   - when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
485   - // mock 部门的返回
486   - when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
487   -
488   - // 调用
489   - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
490   - // 断言
491   - assertFalse(result.getAll());
492   - assertFalse(result.getSelf());
493   - assertEquals(1, result.getDeptIds().size());
494   - assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
  445 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  446 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
  447 + .thenReturn(permissionService);
  448 +
  449 + // 准备参数
  450 + Long userId = 1L;
  451 + // mock 用户的角色编号
  452 + userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
  453 + // mock 获得用户的角色
  454 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())
  455 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  456 + when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
  457 + // mock 部门的返回
  458 + when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
  459 + null, null); // 最后返回 null 的目的,看看会不会重复调用
  460 +
  461 + // 调用
  462 + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
  463 + // 断言
  464 + assertFalse(result.getAll());
  465 + assertFalse(result.getSelf());
  466 + assertEquals(1, result.getDeptIds().size());
  467 + assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
  468 + }
495 469 }
496 470  
497 471 @Test
498 472 public void testGetDeptDataPermission_DeptAndChild() {
499   - // 准备参数
500   - Long userId = 1L;
501   - // mock 用户的角色编号
502   - permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
503   - // mock 获得用户的角色
504   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())
505   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
506   - when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
507   - when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
508   - // mock 部门的返回
509   - when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
510   - // mock 方法(部门)
511   - DeptDO deptDO = randomPojo(DeptDO.class);
512   - when(deptService.getDeptListByParentIdFromCache(eq(3L), eq(true)))
513   - .thenReturn(singletonList(deptDO));
514   -
515   - // 调用
516   - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
517   - // 断言
518   - assertFalse(result.getAll());
519   - assertFalse(result.getSelf());
520   - assertEquals(2, result.getDeptIds().size());
521   - assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId()));
522   - assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
  473 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  474 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
  475 + .thenReturn(permissionService);
  476 +
  477 + // 准备参数
  478 + Long userId = 1L;
  479 + // mock 用户的角色编号
  480 + userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
  481 + // mock 获得用户的角色
  482 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())
  483 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  484 + when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
  485 + // mock 部门的返回
  486 + when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
  487 + null, null); // 最后返回 null 的目的,看看会不会重复调用
  488 + // mock 方法(部门)
  489 + DeptDO deptDO = randomPojo(DeptDO.class);
  490 + when(deptService.getChildDeptIdListFromCache(eq(3L))).thenReturn(singleton(deptDO.getId()));
  491 +
  492 + // 调用
  493 + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
  494 + // 断言
  495 + assertFalse(result.getAll());
  496 + assertFalse(result.getSelf());
  497 + assertEquals(2, result.getDeptIds().size());
  498 + assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId()));
  499 + assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
  500 + }
523 501 }
524 502  
525 503 @Test
526 504 public void testGetDeptDataPermission_Self() {
527   - // 准备参数
528   - Long userId = 1L;
529   - // mock 用户的角色编号
530   - permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
531   - // mock 获得用户的角色
532   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())
533   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
534   - when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
535   - when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
536   -
537   - // 调用
538   - DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
539   - // 断言
540   - assertFalse(result.getAll());
541   - assertTrue(result.getSelf());
542   - assertTrue(CollUtil.isEmpty(result.getDeptIds()));
  505 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  506 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
  507 + .thenReturn(permissionService);
  508 +
  509 + // 准备参数
  510 + Long userId = 1L;
  511 + // mock 用户的角色编号
  512 + userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
  513 + // mock 获得用户的角色
  514 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())
  515 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  516 + when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
  517 +
  518 + // 调用
  519 + DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
  520 + // 断言
  521 + assertFalse(result.getAll());
  522 + assertTrue(result.getSelf());
  523 + assertTrue(CollUtil.isEmpty(result.getDeptIds()));
  524 + }
543 525 }
544 526  
545 527 }
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java
1 1 package cn.iocoder.yudao.module.system.service.permission;
2 2  
  3 +import cn.hutool.extra.spring.SpringUtil;
3 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
... ... @@ -11,15 +12,14 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
11 12 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
12 13 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
13 14 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
14   -import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer;
15 15 import org.junit.jupiter.api.Test;
  16 +import org.mockito.MockedStatic;
16 17 import org.springframework.boot.test.mock.mockito.MockBean;
17 18 import org.springframework.context.annotation.Import;
18 19  
19 20 import javax.annotation.Resource;
20 21 import java.util.Collection;
21 22 import java.util.List;
22   -import java.util.Map;
23 23 import java.util.Set;
24 24  
25 25 import static cn.hutool.core.util.RandomUtil.randomEle;
... ... @@ -33,6 +33,8 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
33 33 import static java.util.Collections.singleton;
34 34 import static java.util.Collections.singletonList;
35 35 import static org.junit.jupiter.api.Assertions.*;
  36 +import static org.mockito.ArgumentMatchers.eq;
  37 +import static org.mockito.Mockito.mockStatic;
36 38 import static org.mockito.Mockito.verify;
37 39  
38 40 @Import(RoleServiceImpl.class)
... ... @@ -46,26 +48,9 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
46 48  
47 49 @MockBean
48 50 private PermissionService permissionService;
49   - @MockBean
50   - private RoleProducer roleProducer;
51 51  
52 52 @Test
53   - public void testInitLocalCache() {
54   - RoleDO roleDO1 = randomPojo(RoleDO.class);
55   - roleMapper.insert(roleDO1);
56   - RoleDO roleDO2 = randomPojo(RoleDO.class);
57   - roleMapper.insert(roleDO2);
58   -
59   - // 调用
60   - roleService.initLocalCache();
61   - // 断言 roleCache 缓存
62   - Map<Long, RoleDO> roleCache = roleService.getRoleCache();
63   - assertPojoEquals(roleDO1, roleCache.get(roleDO1.getId()));
64   - assertPojoEquals(roleDO2, roleCache.get(roleDO2.getId()));
65   - }
66   -
67   - @Test
68   - public void testCreateRole_success() {
  53 + public void testCreateRole() {
69 54 // 准备参数
70 55 RoleCreateReqVO reqVO = randomPojo(RoleCreateReqVO.class);
71 56  
... ... @@ -77,12 +62,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
77 62 assertEquals(RoleTypeEnum.CUSTOM.getType(), roleDO.getType());
78 63 assertEquals(CommonStatusEnum.ENABLE.getStatus(), roleDO.getStatus());
79 64 assertEquals(DataScopeEnum.ALL.getScope(), roleDO.getDataScope());
80   - // verify 发送刷新消息
81   - verify(roleProducer).sendRoleRefreshMessage();
82 65 }
83 66  
84 67 @Test
85   - public void testUpdateRole_success() {
  68 + public void testUpdateRole() {
86 69 // mock 数据
87 70 RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
88 71 roleMapper.insert(roleDO);
... ... @@ -95,12 +78,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
95 78 // 断言
96 79 RoleDO newRoleDO = roleMapper.selectById(id);
97 80 assertPojoEquals(reqVO, newRoleDO);
98   - // verify 发送刷新消息
99   - verify(roleProducer).sendRoleRefreshMessage();
100 81 }
101 82  
102 83 @Test
103   - public void testUpdateRoleStatus_success() {
  84 + public void testUpdateRoleStatus() {
104 85 // mock 数据
105 86 RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
106 87 .setType(RoleTypeEnum.CUSTOM.getType()));
... ... @@ -114,12 +95,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
114 95 // 断言
115 96 RoleDO dbRoleDO = roleMapper.selectById(roleId);
116 97 assertEquals(CommonStatusEnum.DISABLE.getStatus(), dbRoleDO.getStatus());
117   - // verify 发送刷新消息
118   - verify(roleProducer).sendRoleRefreshMessage();
119 98 }
120 99  
121 100 @Test
122   - public void testUpdateRoleDataScope_success() {
  101 + public void testUpdateRoleDataScope() {
123 102 // mock 数据
124 103 RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
125 104 roleMapper.insert(roleDO);
... ... @@ -134,12 +113,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
134 113 RoleDO dbRoleDO = roleMapper.selectById(id);
135 114 assertEquals(dataScope, dbRoleDO.getDataScope());
136 115 assertEquals(dataScopeRoleIds, dbRoleDO.getDataScopeDeptIds());
137   - // verify 发送刷新消息
138   - verify(roleProducer).sendRoleRefreshMessage();
139 116 }
140 117  
141 118 @Test
142   - public void testDeleteRole_success() {
  119 + public void testDeleteRole() {
143 120 // mock 数据
144 121 RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
145 122 roleMapper.insert(roleDO);
... ... @@ -152,23 +129,65 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
152 129 assertNull(roleMapper.selectById(id));
153 130 // verify 删除相关数据
154 131 verify(permissionService).processRoleDeleted(id);
155   - // verify 发送刷新消息
156   - verify(roleProducer).sendRoleRefreshMessage();
157 132 }
158 133  
159 134 @Test
160   - public void testGetRoleFromCache() {
161   - // mock 数据(缓存)
  135 + public void testValidateRoleDuplicate_success() {
  136 + // 调用,不会抛异常
  137 + roleService.validateRoleDuplicate(randomString(), randomString(), null);
  138 + }
  139 +
  140 + @Test
  141 + public void testValidateRoleDuplicate_nameDuplicate() {
  142 + // mock 数据
  143 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setName("role_name"));
  144 + roleMapper.insert(roleDO);
  145 + // 准备参数
  146 + String name = "role_name";
  147 +
  148 + // 调用,并断言异常
  149 + assertServiceException(() -> roleService.validateRoleDuplicate(name, randomString(), null),
  150 + ROLE_NAME_DUPLICATE, name);
  151 + }
  152 +
  153 + @Test
  154 + public void testValidateRoleDuplicate_codeDuplicate() {
  155 + // mock 数据
  156 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setCode("code"));
  157 + roleMapper.insert(roleDO);
  158 + // 准备参数
  159 + String code = "code";
  160 +
  161 + // 调用,并断言异常
  162 + assertServiceException(() -> roleService.validateRoleDuplicate(randomString(), code, null),
  163 + ROLE_CODE_DUPLICATE, code);
  164 + }
  165 +
  166 + @Test
  167 + public void testValidateUpdateRole_success() {
162 168 RoleDO roleDO = randomPojo(RoleDO.class);
163 169 roleMapper.insert(roleDO);
164   - roleService.initLocalCache();
165   - // 参数准备
  170 + // 准备参数
166 171 Long id = roleDO.getId();
167 172  
168   - // 调用
169   - RoleDO dbRoleDO = roleService.getRoleFromCache(id);
170   - // 断言
171   - assertPojoEquals(roleDO, dbRoleDO);
  173 + // 调用,无异常
  174 + roleService.validateRoleForUpdate(id);
  175 + }
  176 +
  177 + @Test
  178 + public void testValidateUpdateRole_roleIdNotExist() {
  179 + assertServiceException(() -> roleService.validateRoleForUpdate(randomLongId()), ROLE_NOT_EXISTS);
  180 + }
  181 +
  182 + @Test
  183 + public void testValidateUpdateRole_systemRoleCanNotBeUpdate() {
  184 + RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.SYSTEM.getType()));
  185 + roleMapper.insert(roleDO);
  186 + // 准备参数
  187 + Long id = roleDO.getId();
  188 +
  189 + assertServiceException(() -> roleService.validateRoleForUpdate(id),
  190 + ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE);
172 191 }
173 192  
174 193 @Test
... ... @@ -186,22 +205,21 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
186 205 }
187 206  
188 207 @Test
189   - public void testGetRoleListByStatus_statusNotEmpty() {
190   - // mock 数据
191   - RoleDO dbRole = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
192   - roleMapper.insert(dbRole);
193   - // 测试 status 不匹配
194   - roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
  208 + public void testGetRoleFromCache() {
  209 + // mock 数据(缓存)
  210 + RoleDO roleDO = randomPojo(RoleDO.class);
  211 + roleMapper.insert(roleDO);
  212 + // 参数准备
  213 + Long id = roleDO.getId();
195 214  
196 215 // 调用
197   - List<RoleDO> list = roleService.getRoleListByStatus(singleton(CommonStatusEnum.ENABLE.getStatus()));
  216 + RoleDO dbRoleDO = roleService.getRoleFromCache(id);
198 217 // 断言
199   - assertEquals(1, list.size());
200   - assertPojoEquals(dbRole, list.get(0));
  218 + assertPojoEquals(roleDO, dbRoleDO);
201 219 }
202 220  
203 221 @Test
204   - public void testGetRoleListByStatus_statusEmpty() {
  222 + public void testGetRoleListByStatus() {
205 223 // mock 数据
206 224 RoleDO dbRole01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
207 225 roleMapper.insert(dbRole01);
... ... @@ -209,29 +227,33 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
209 227 roleMapper.insert(dbRole02);
210 228  
211 229 // 调用
212   - List<RoleDO> list = roleService.getRoleListByStatus(null);
  230 + List<RoleDO> list = roleService.getRoleListByStatus(
  231 + singleton(CommonStatusEnum.ENABLE.getStatus()));
213 232 // 断言
214   - assertEquals(2, list.size());
  233 + assertEquals(1, list.size());
215 234 assertPojoEquals(dbRole01, list.get(0));
216   - assertPojoEquals(dbRole02, list.get(1));
217 235 }
218 236  
219 237 @Test
220 238 public void testGetRoleListFromCache() {
221   - // mock 数据
222   - RoleDO dbRole = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
223   - roleMapper.insert(dbRole);
224   - // 测试 id 不匹配
225   - roleMapper.insert(cloneIgnoreId(dbRole, o -> {}));
226   - roleService.initLocalCache();
227   - // 准备参数
228   - Collection<Long> ids = singleton(dbRole.getId());
229   -
230   - // 调用
231   - List<RoleDO> list = roleService.getRoleListFromCache(ids);
232   - // 断言
233   - assertEquals(1, list.size());
234   - assertPojoEquals(dbRole, list.get(0));
  239 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  240 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
  241 + .thenReturn(roleService);
  242 +
  243 + // mock 数据
  244 + RoleDO dbRole = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
  245 + roleMapper.insert(dbRole);
  246 + // 测试 id 不匹配
  247 + roleMapper.insert(cloneIgnoreId(dbRole, o -> {}));
  248 + // 准备参数
  249 + Collection<Long> ids = singleton(dbRole.getId());
  250 +
  251 + // 调用
  252 + List<RoleDO> list = roleService.getRoleListFromCache(ids);
  253 + // 断言
  254 + assertEquals(1, list.size());
  255 + assertPojoEquals(dbRole, list.get(0));
  256 + }
235 257 }
236 258  
237 259 @Test
... ... @@ -296,72 +318,37 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
296 318 }
297 319  
298 320 @Test
299   - public void testHasAnySuperAdmin() {
300   - // 是超级
301   - assertTrue(roleService.hasAnySuperAdmin(singletonList(randomPojo(RoleDO.class,
302   - o -> o.setCode("super_admin")))));
303   - // 非超级
304   - assertFalse(roleService.hasAnySuperAdmin(singletonList(randomPojo(RoleDO.class,
305   - o -> o.setCode("tenant_admin")))));
306   - }
307   -
308   - @Test
309   - public void testValidateRoleDuplicate_success() {
310   - // 调用,不会抛异常
311   - roleService.validateRoleDuplicate(randomString(), randomString(), null);
312   - }
313   -
314   - @Test
315   - public void testValidateRoleDuplicate_nameDuplicate() {
316   - // mock 数据
317   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setName("role_name"));
318   - roleMapper.insert(roleDO);
319   - // 准备参数
320   - String name = "role_name";
321   -
322   - // 调用,并断言异常
323   - assertServiceException(() -> roleService.validateRoleDuplicate(name, randomString(), null),
324   - ROLE_NAME_DUPLICATE, name);
  321 + public void testHasAnySuperAdmin_true() {
  322 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  323 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
  324 + .thenReturn(roleService);
  325 +
  326 + // mock 数据
  327 + RoleDO dbRole = randomPojo(RoleDO.class).setCode("super_admin");
  328 + roleMapper.insert(dbRole);
  329 + // 准备参数
  330 + Long id = dbRole.getId();
  331 +
  332 + // 调用,并调用
  333 + assertTrue(roleService.hasAnySuperAdmin(singletonList(id)));
  334 + }
325 335 }
326 336  
327 337 @Test
328   - public void testValidateRoleDuplicate_codeDuplicate() {
329   - // mock 数据
330   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setCode("code"));
331   - roleMapper.insert(roleDO);
332   - // 准备参数
333   - String code = "code";
334   -
335   - // 调用,并断言异常
336   - assertServiceException(() -> roleService.validateRoleDuplicate(randomString(), code, null),
337   - ROLE_CODE_DUPLICATE, code);
338   - }
339   -
340   - @Test
341   - public void testValidateUpdateRole_success() {
342   - RoleDO roleDO = randomPojo(RoleDO.class);
343   - roleMapper.insert(roleDO);
344   - // 准备参数
345   - Long id = roleDO.getId();
346   -
347   - // 调用,无异常
348   - roleService.validateRoleForUpdate(id);
349   - }
350   -
351   - @Test
352   - public void testValidateUpdateRole_roleIdNotExist() {
353   - assertServiceException(() -> roleService.validateRoleForUpdate(randomLongId()), ROLE_NOT_EXISTS);
354   - }
355   -
356   - @Test
357   - public void testValidateUpdateRole_systemRoleCanNotBeUpdate() {
358   - RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.SYSTEM.getType()));
359   - roleMapper.insert(roleDO);
360   - // 准备参数
361   - Long id = roleDO.getId();
362   -
363   - assertServiceException(() -> roleService.validateRoleForUpdate(id),
364   - ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE);
  338 + public void testHasAnySuperAdmin_false() {
  339 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  340 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
  341 + .thenReturn(roleService);
  342 +
  343 + // mock 数据
  344 + RoleDO dbRole = randomPojo(RoleDO.class).setCode("tenant_admin");
  345 + roleMapper.insert(dbRole);
  346 + // 准备参数
  347 + Long id = dbRole.getId();
  348 +
  349 + // 调用,并调用
  350 + assertFalse(roleService.hasAnySuperAdmin(singletonList(id)));
  351 + }
365 352 }
366 353  
367 354 @Test
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImplTest.java
... ... @@ -10,9 +10,7 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
10 10 import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO;
11 11 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
12 12 import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper;
13   -import cn.iocoder.yudao.module.system.mq.producer.sensitiveword.SensitiveWordProducer;
14 13 import org.junit.jupiter.api.Test;
15   -import org.springframework.boot.test.mock.mockito.MockBean;
16 14 import org.springframework.context.annotation.Import;
17 15  
18 16 import javax.annotation.Resource;
... ... @@ -29,7 +27,6 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
29 27 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS;
30 28 import static java.util.Collections.singletonList;
31 29 import static org.junit.jupiter.api.Assertions.*;
32   -import static org.mockito.Mockito.verify;
33 30  
34 31 /**
35 32 * {@link SensitiveWordServiceImpl} 的单元测试类
... ... @@ -45,9 +42,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
45 42 @Resource
46 43 private SensitiveWordMapper sensitiveWordMapper;
47 44  
48   - @MockBean
49   - private SensitiveWordProducer sensitiveWordProducer;
50   -
51 45 @Test
52 46 public void testInitLocalCache() {
53 47 SensitiveWordDO wordDO1 = randomPojo(SensitiveWordDO.class, o -> o.setName("傻瓜")
... ... @@ -61,6 +55,10 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
61 55 sensitiveWordService.initLocalCache();
62 56 // 断言 sensitiveWordTagsCache 缓存
63 57 assertEquals(SetUtils.asSet("论坛", "蔬菜"), sensitiveWordService.getSensitiveWordTagSet());
  58 + // 断言 sensitiveWordCache
  59 + assertEquals(2, sensitiveWordService.getSensitiveWordCache().size());
  60 + assertPojoEquals(wordDO1, sensitiveWordService.getSensitiveWordCache().get(0));
  61 + assertPojoEquals(wordDO2, sensitiveWordService.getSensitiveWordCache().get(1));
64 62 // 断言 tagSensitiveWordTries 缓存
65 63 assertNotNull(sensitiveWordService.getDefaultSensitiveWordTrie());
66 64 assertEquals(2, sensitiveWordService.getTagSensitiveWordTries().size());
... ... @@ -80,7 +78,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
80 78 // 校验记录的属性是否正确
81 79 SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(sensitiveWordId);
82 80 assertPojoEquals(reqVO, sensitiveWord);
83   - verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();
84 81 }
85 82  
86 83 @Test
... ... @@ -98,7 +95,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
98 95 // 校验是否更新正确
99 96 SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(reqVO.getId()); // 获取最新的
100 97 assertPojoEquals(reqVO, sensitiveWord);
101   - verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();
102 98 }
103 99  
104 100 @Test
... ... @@ -122,7 +118,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
122 118 sensitiveWordService.deleteSensitiveWord(id);
123 119 // 校验数据不存在了
124 120 assertNull(sensitiveWordMapper.selectById(id));
125   - verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();
126 121 }
127 122  
128 123 @Test
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java
... ... @@ -9,13 +9,11 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannel
9 9 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelUpdateReqVO;
10 10 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
11 11 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
12   -import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
13 12 import org.junit.jupiter.api.Test;
14 13 import org.springframework.boot.test.mock.mockito.MockBean;
15 14 import org.springframework.context.annotation.Import;
16 15  
17 16 import javax.annotation.Resource;
18   -
19 17 import java.util.List;
20 18  
21 19 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
... ... @@ -42,8 +40,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
42 40 private SmsClientFactory smsClientFactory;
43 41 @MockBean
44 42 private SmsTemplateService smsTemplateService;
45   - @MockBean
46   - private SmsProducer smsProducer;
47 43  
48 44 @Test
49 45 public void testInitLocalCache_success() {
... ... @@ -60,6 +56,10 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
60 56 argThat(properties -> isPojoEquals(smsChannelDO01, properties)));
61 57 verify(smsClientFactory, times(1)).createOrUpdateSmsClient(
62 58 argThat(properties -> isPojoEquals(smsChannelDO02, properties)));
  59 + // 断言 channelCache 缓存
  60 + assertEquals(2, smsChannelService.getChannelCache().size());
  61 + assertPojoEquals(smsChannelDO01, smsChannelService.getChannelCache().get(0));
  62 + assertPojoEquals(smsChannelDO02, smsChannelService.getChannelCache().get(1));
63 63 }
64 64  
65 65 @Test
... ... @@ -74,8 +74,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
74 74 // 校验记录的属性是否正确
75 75 SmsChannelDO smsChannel = smsChannelMapper.selectById(smsChannelId);
76 76 assertPojoEquals(reqVO, smsChannel);
77   - // 校验调用
78   - verify(smsProducer, times(1)).sendSmsChannelRefreshMessage();
79 77 }
80 78  
81 79 @Test
... ... @@ -95,8 +93,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
95 93 // 校验是否更新正确
96 94 SmsChannelDO smsChannel = smsChannelMapper.selectById(reqVO.getId()); // 获取最新的
97 95 assertPojoEquals(reqVO, smsChannel);
98   - // 校验调用
99   - verify(smsProducer, times(1)).sendSmsChannelRefreshMessage();
100 96 }
101 97  
102 98 @Test
... ... @@ -118,10 +114,8 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
118 114  
119 115 // 调用
120 116 smsChannelService.deleteSmsChannel(id);
121   - // 校验数据不存在了
122   - assertNull(smsChannelMapper.selectById(id));
123   - // 校验调用
124   - verify(smsProducer, times(1)).sendSmsChannelRefreshMessage();
  117 + // 校验数据不存在了
  118 + assertNull(smsChannelMapper.selectById(id));
125 119 }
126 120  
127 121 @Test
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java
... ... @@ -18,7 +18,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
18 18 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
19 19 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper;
20 20 import cn.iocoder.yudao.module.system.enums.sms.SmsTemplateTypeEnum;
21   -import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
22 21 import com.google.common.collect.Lists;
23 22 import org.junit.jupiter.api.Test;
24 23 import org.springframework.boot.test.mock.mockito.MockBean;
... ... @@ -26,7 +25,6 @@ import org.springframework.context.annotation.Import;
26 25  
27 26 import javax.annotation.Resource;
28 27 import java.util.List;
29   -import java.util.Map;
30 28 import java.util.function.Consumer;
31 29  
32 30 import static cn.hutool.core.util.RandomUtil.randomEle;
... ... @@ -55,25 +53,6 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
55 53 private SmsClientFactory smsClientFactory;
56 54 @MockBean
57 55 private SmsClient smsClient;
58   - @MockBean
59   - private SmsProducer smsProducer;
60   -
61   - @Test
62   - void testInitLocalCache() {
63   - // mock 数据
64   - SmsTemplateDO smsTemplate01 = randomSmsTemplateDO();
65   - smsTemplateMapper.insert(smsTemplate01);
66   - SmsTemplateDO smsTemplate02 = randomSmsTemplateDO();
67   - smsTemplateMapper.insert(smsTemplate02);
68   -
69   - // 调用
70   - smsTemplateService.initLocalCache();
71   - // 断言 deptCache 缓存
72   - Map<String, SmsTemplateDO> smsTemplateCache = smsTemplateService.getSmsTemplateCache();
73   - assertEquals(2, smsTemplateCache.size());
74   - assertPojoEquals(smsTemplate01, smsTemplateCache.get(smsTemplate01.getCode()));
75   - assertPojoEquals(smsTemplate02, smsTemplateCache.get(smsTemplate02.getCode()));
76   - }
77 56  
78 57 @Test
79 58 public void testParseTemplateContentParams() {
... ... @@ -116,8 +95,6 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
116 95 assertPojoEquals(reqVO, smsTemplate);
117 96 assertEquals(Lists.newArrayList("operation", "code"), smsTemplate.getParams());
118 97 assertEquals(channelDO.getCode(), smsTemplate.getChannelCode());
119   - // 校验调用
120   - verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
121 98 }
122 99  
123 100 @Test
... ... @@ -151,8 +128,6 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
151 128 assertPojoEquals(reqVO, smsTemplate);
152 129 assertEquals(Lists.newArrayList("operation", "code"), smsTemplate.getParams());
153 130 assertEquals(channelDO.getCode(), smsTemplate.getChannelCode());
154   - // 校验调用
155   - verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
156 131 }
157 132  
158 133 @Test
... ... @@ -174,10 +149,8 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
174 149  
175 150 // 调用
176 151 smsTemplateService.deleteSmsTemplate(id);
177   - // 校验数据不存在了
178   - assertNull(smsTemplateMapper.selectById(id));
179   - // 校验调用
180   - verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
  152 + // 校验数据不存在了
  153 + assertNull(smsTemplateMapper.selectById(id));
181 154 }
182 155  
183 156 @Test
... ... @@ -191,47 +164,47 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
191 164  
192 165 @Test
193 166 public void testGetSmsTemplatePage() {
194   - // mock 数据
195   - SmsTemplateDO dbSmsTemplate = randomPojo(SmsTemplateDO.class, o -> { // 等会查询到
196   - o.setType(SmsTemplateTypeEnum.PROMOTION.getType());
197   - o.setStatus(CommonStatusEnum.ENABLE.getStatus());
198   - o.setCode("tudou");
199   - o.setContent("芋道源码");
200   - o.setApiTemplateId("yunai");
201   - o.setChannelId(1L);
202   - o.setCreateTime(buildTime(2021, 11, 11));
203   - });
204   - smsTemplateMapper.insert(dbSmsTemplate);
205   - // 测试 type 不匹配
206   - smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setType(SmsTemplateTypeEnum.VERIFICATION_CODE.getType())));
207   - // 测试 status 不匹配
208   - smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
209   - // 测试 code 不匹配
210   - smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCode("yuanma")));
211   - // 测试 content 不匹配
212   - smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setContent("源码")));
213   - // 测试 apiTemplateId 不匹配
214   - smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setApiTemplateId("nai")));
215   - // 测试 channelId 不匹配
216   - smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setChannelId(2L)));
217   - // 测试 createTime 不匹配
218   - smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCreateTime(buildTime(2021, 12, 12))));
219   - // 准备参数
220   - SmsTemplatePageReqVO reqVO = new SmsTemplatePageReqVO();
221   - reqVO.setType(SmsTemplateTypeEnum.PROMOTION.getType());
222   - reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
223   - reqVO.setCode("tu");
224   - reqVO.setContent("芋道");
225   - reqVO.setApiTemplateId("yu");
226   - reqVO.setChannelId(1L);
227   - reqVO.setCreateTime(buildBetweenTime(2021, 11, 1, 2021, 12, 1));
228   -
229   - // 调用
230   - PageResult<SmsTemplateDO> pageResult = smsTemplateService.getSmsTemplatePage(reqVO);
231   - // 断言
232   - assertEquals(1, pageResult.getTotal());
233   - assertEquals(1, pageResult.getList().size());
234   - assertPojoEquals(dbSmsTemplate, pageResult.getList().get(0));
  167 + // mock 数据
  168 + SmsTemplateDO dbSmsTemplate = randomPojo(SmsTemplateDO.class, o -> { // 等会查询到
  169 + o.setType(SmsTemplateTypeEnum.PROMOTION.getType());
  170 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  171 + o.setCode("tudou");
  172 + o.setContent("芋道源码");
  173 + o.setApiTemplateId("yunai");
  174 + o.setChannelId(1L);
  175 + o.setCreateTime(buildTime(2021, 11, 11));
  176 + });
  177 + smsTemplateMapper.insert(dbSmsTemplate);
  178 + // 测试 type 不匹配
  179 + smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setType(SmsTemplateTypeEnum.VERIFICATION_CODE.getType())));
  180 + // 测试 status 不匹配
  181 + smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
  182 + // 测试 code 不匹配
  183 + smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCode("yuanma")));
  184 + // 测试 content 不匹配
  185 + smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setContent("源码")));
  186 + // 测试 apiTemplateId 不匹配
  187 + smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setApiTemplateId("nai")));
  188 + // 测试 channelId 不匹配
  189 + smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setChannelId(2L)));
  190 + // 测试 createTime 不匹配
  191 + smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCreateTime(buildTime(2021, 12, 12))));
  192 + // 准备参数
  193 + SmsTemplatePageReqVO reqVO = new SmsTemplatePageReqVO();
  194 + reqVO.setType(SmsTemplateTypeEnum.PROMOTION.getType());
  195 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
  196 + reqVO.setCode("tu");
  197 + reqVO.setContent("芋道");
  198 + reqVO.setApiTemplateId("yu");
  199 + reqVO.setChannelId(1L);
  200 + reqVO.setCreateTime(buildBetweenTime(2021, 11, 1, 2021, 12, 1));
  201 +
  202 + // 调用
  203 + PageResult<SmsTemplateDO> pageResult = smsTemplateService.getSmsTemplatePage(reqVO);
  204 + // 断言
  205 + assertEquals(1, pageResult.getTotal());
  206 + assertEquals(1, pageResult.getList().size());
  207 + assertPojoEquals(dbSmsTemplate, pageResult.getList().get(0));
235 208 }
236 209  
237 210 @Test
... ... @@ -271,11 +244,11 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
271 244 reqVO.setChannelId(1L);
272 245 reqVO.setCreateTime(buildBetweenTime(2021, 11, 1, 2021, 12, 1));
273 246  
274   - // 调用
275   - List<SmsTemplateDO> list = smsTemplateService.getSmsTemplateList(reqVO);
276   - // 断言
277   - assertEquals(1, list.size());
278   - assertPojoEquals(dbSmsTemplate, list.get(0));
  247 + // 调用
  248 + List<SmsTemplateDO> list = smsTemplateService.getSmsTemplateList(reqVO);
  249 + // 断言
  250 + assertEquals(1, list.size());
  251 + assertPojoEquals(dbSmsTemplate, list.get(0));
279 252 }
280 253  
281 254 @Test
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java
... ... @@ -199,7 +199,7 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
199 199 role101.setTenantId(dbTenant.getId());
200 200 when(roleService.getRoleListByStatus(isNull())).thenReturn(asList(role100, role101));
201 201 // mock 每个角色的权限
202   - when(permissionService.getRoleMenuIds(eq(101L))).thenReturn(asSet(201L, 202L));
  202 + when(permissionService.getRoleMenuListByRoleId(eq(101L))).thenReturn(asSet(201L, 202L));
203 203  
204 204 // 调用
205 205 tenantService.updateTenant(reqVO);
... ... @@ -454,7 +454,7 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
454 454 TenantContextHolder.setTenantId(dbTenant.getId());
455 455 // mock 菜单
456 456 when(menuService.getMenuList()).thenReturn(Arrays.asList(randomPojo(MenuDO.class, o -> o.setId(100L)),
457   - randomPojo(MenuDO.class, o -> o.setId(101L))));
  457 + randomPojo(MenuDO.class, o -> o.setId(101L))));
458 458  
459 459 // 调用
460 460 tenantService.handleTenantMenu(handler);
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java
... ... @@ -345,7 +345,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
345 345 reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门
346 346 // mock 方法
347 347 List<DeptDO> deptList = newArrayList(randomPojo(DeptDO.class, o -> o.setId(2L)));
348   - when(deptService.getDeptListByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList);
  348 + when(deptService.getChildDeptList(eq(reqVO.getDeptId()))).thenReturn(deptList);
349 349  
350 350 // 调用
351 351 PageResult<AdminUserDO> pageResult = userService.getUserPage(reqVO);
... ... @@ -368,7 +368,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
368 368 reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门
369 369 // mock 方法
370 370 List<DeptDO> deptList = newArrayList(randomPojo(DeptDO.class, o -> o.setId(2L)));
371   - when(deptService.getDeptListByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList);
  371 + when(deptService.getChildDeptList(eq(reqVO.getDeptId()))).thenReturn(deptList);
372 372  
373 373 // 调用
374 374 List<AdminUserDO> list = userService.getUserList(reqVO);
... ...
yudao-server/src/main/resources/application-dev.yaml
... ... @@ -147,14 +147,14 @@ wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-sta
147 147 # 存储配置,解决 AccessToken 的跨节点的共享
148 148 config-storage:
149 149 type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
150   - key-prefix: wx # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置
  150 + key-prefix: wx # Redis Key 的前缀
151 151 http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
152 152 miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档
153 153 appid: wx63c280fe3248a3e7
154 154 secret: 6f270509224a7ae1296bbf1c8cb97aed
155 155 config-storage:
156 156 type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
157   - key-prefix: wa # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置
  157 + key-prefix: wa # Redis Key 的前缀
158 158 http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
159 159  
160 160 --- #################### 芋道相关配置 ####################
... ... @@ -167,7 +167,8 @@ yudao:
167 167 - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
168 168 - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
169 169 pay:
170   - callback-url: http://yunai.natapp1.cc/admin-api/pay/notify/callback
  170 + order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
  171 + refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
171 172 demo: true # 开启演示模式
172 173  
173 174 justauth:
... ...
yudao-server/src/main/resources/application-local.yaml
... ... @@ -170,14 +170,14 @@ wx:
170 170 # 存储配置,解决 AccessToken 的跨节点的共享
171 171 config-storage:
172 172 type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
173   - key-prefix: wx # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置
  173 + key-prefix: wx # Redis Key 的前缀
174 174 http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
175 175 miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档
176 176 appid: wx62056c0d5e8db250
177 177 secret: 333ae72f41552af1e998fe1f54e1584a
178 178 config-storage:
179 179 type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
180   - key-prefix: wa # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置
  180 + key-prefix: wa # Redis Key 的前缀
181 181 http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
182 182  
183 183 --- #################### 芋道相关配置 ####################
... ...
yudao-ui-admin/package.json
1 1 {
2 2 "name": "yudao-ui-admin",
3   - "version": "1.7.3-snapshot",
  3 + "version": "1.8.0-snapshot",
4 4 "description": "芋道管理系统",
5 5 "author": "芋道",
6 6 "license": "MIT",
... ...
yudao-ui-admin/src/api/menu.js deleted
1   -import request from '@/utils/request'
2   -
3   -// 获取路由
4   -export const getRouters = () => {
5   - return request({
6   - url: '/system/auth/list-menus',
7   - method: 'get'
8   - })
9   -}
yudao-ui-admin/src/api/system/permission.js
... ... @@ -3,7 +3,7 @@ import request from &#39;@/utils/request&#39;
3 3 // 查询角色拥有的菜单数组
4 4 export function listRoleMenus(roleId) {
5 5 return request({
6   - url: '/system/permission/list-role-resources?roleId=' + roleId,
  6 + url: '/system/permission/list-role-menus?roleId=' + roleId,
7 7 method: 'get'
8 8 })
9 9 }
... ...
yudao-ui-admin/src/components/Crontab/week.vue
... ... @@ -185,7 +185,7 @@ export default {
185 185 averageTotal: function () {
186 186 this.average01 = this.checkNum(this.average01, 1, 4)
187 187 this.average02 = this.checkNum(this.average02, 1, 7)
188   - return this.average02 + '#' + this.average01;
  188 + return this.average01 + '#' + this.average02;
189 189 },
190 190 // 最近的工作日(格式)
191 191 weekdayCheck: function () {
... ...
yudao-ui-admin/src/permission.js
... ... @@ -25,9 +25,10 @@ router.beforeEach((to, from, next) =&gt; {
25 25 // 获取字典数据 add by 芋艿
26 26 store.dispatch('dict/loadDictDatas')
27 27 // 判断当前用户是否已拉取完 user_info 信息
28   - store.dispatch('GetInfo').then(() => {
  28 + store.dispatch('GetInfo').then(userInfo => {
29 29 isRelogin.show = false
30   - store.dispatch('GenerateRoutes').then(accessRoutes => {
  30 + // 触发 GenerateRoutes 事件时,将 menus 菜单树传递进去
  31 + store.dispatch('GenerateRoutes', userInfo.menus).then(accessRoutes => {
31 32 // 根据 roles 权限生成可访问的路由表
32 33 router.addRoutes(accessRoutes) // 动态添加可访问路由表
33 34 next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
... ...
yudao-ui-admin/src/store/modules/permission.js
1 1 import {constantRoutes} from '@/router'
2   -import {getRouters} from '@/api/menu'
3 2 import Layout from '@/layout/index'
4 3 import ParentView from '@/components/ParentView';
5 4 import {toCamelCase} from "@/utils";
... ... @@ -27,22 +26,25 @@ const permission = {
27 26 },
28 27 },
29 28 actions: {
30   - // 生成路由
31   - GenerateRoutes({commit}) {
  29 + /**
  30 + * 生成路由
  31 + *
  32 + * @param commit commit 函数
  33 + * @param menus 路由参数
  34 + */
  35 + GenerateRoutes({commit}, menus) {
32 36 return new Promise(resolve => {
33   - // 向后端请求路由数据(菜单)
34   - getRouters().then(res => {
35   - const sdata = JSON.parse(JSON.stringify(res.data)) // 【重要】用于菜单中的数据
36   - const rdata = JSON.parse(JSON.stringify(res.data)) // 用于最后添加到 Router 中的数据
37   - const sidebarRoutes = filterAsyncRouter(sdata)
38   - const rewriteRoutes = filterAsyncRouter(rdata, false, true)
39   - rewriteRoutes.push({path: '*', redirect: '/404', hidden: true})
40   - commit('SET_ROUTES', rewriteRoutes)
41   - commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
42   - commit('SET_DEFAULT_ROUTES', sidebarRoutes)
43   - commit('SET_TOPBAR_ROUTES', sidebarRoutes)
44   - resolve(rewriteRoutes)
45   - })
  37 + // 将 menus 菜单,转换为 route 路由数组
  38 + const sdata = JSON.parse(JSON.stringify(menus)) // 【重要】用于菜单中的数据
  39 + const rdata = JSON.parse(JSON.stringify(menus)) // 用于最后添加到 Router 中的数据
  40 + const sidebarRoutes = filterAsyncRouter(sdata)
  41 + const rewriteRoutes = filterAsyncRouter(rdata, false, true)
  42 + rewriteRoutes.push({path: '*', redirect: '/404', hidden: true})
  43 + commit('SET_ROUTES', rewriteRoutes)
  44 + commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
  45 + commit('SET_DEFAULT_ROUTES', sidebarRoutes)
  46 + commit('SET_TOPBAR_ROUTES', sidebarRoutes)
  47 + resolve(rewriteRoutes)
46 48 })
47 49 }
48 50 }
... ...