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

This commit is contained in:
YunaiV
2023-07-29 11:06:59 +08:00
169 changed files with 2205 additions and 4047 deletions

View File

@ -164,7 +164,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;
}

View File

@ -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,

View File

@ -63,9 +63,7 @@
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.5.0</version>
</dependency>
<!-- TODO 芋艿:清理 -->
<!-- Test 测试相关 -->
<dependency>

View File

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

View File

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

View File

@ -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>

View File

@ -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;

View File

@ -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";
}

View File

@ -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>

View File

@ -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);
}
}

View File

@ -1,13 +1,10 @@
package cn.iocoder.yudao.framework.idempotent.core.redis;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
/**
* 幂等 Redis DAO
*
@ -16,9 +13,14 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S
@AllArgsConstructor
public class IdempotentRedisDAO {
private static final RedisKeyDefine IDEMPOTENT = new RedisKeyDefine("幂等操作",
"idempotent:%s", // 参数为 uuid
STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
/**
* 幂等操作
*
* 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);
}
}

View File

@ -1,21 +1,13 @@
package cn.iocoder.yudao.framework.lock4j.config;
import cn.hutool.core.util.ClassUtil;
import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;
import cn.iocoder.yudao.framework.lock4j.core.DefaultLockFailureStrategy;
import cn.iocoder.yudao.framework.lock4j.core.Lock4jRedisKeyConstants;
import 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();

View File

@ -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.classRedisson 的 Lock 锁,使用 Hash 数据结构
* 过期时间:不固定
*/
String LOCK4J = "lock4j:%s";
}

View File

@ -37,6 +37,11 @@
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.redis.config;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -7,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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}

View File

@ -40,6 +40,7 @@ public class BaseDbAndRedisUnitTest {
// Redis 配置类
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
// RedisAutoConfiguration.class, // Spring Redis 自动配置类
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
RedissonAutoConfiguration.class, // Redisson 自动高配置类
})

View File

@ -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;
}