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