Initial commit
This commit is contained in:
+75
@@ -0,0 +1,75 @@
|
||||
package com.oneone.common.redis;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Redis缓存配置
|
||||
*
|
||||
* @date 2021/8/19
|
||||
*/
|
||||
@EnableConfigurationProperties(CacheProperties.class)
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class RedisCacheConfig {
|
||||
|
||||
@Bean
|
||||
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
|
||||
|
||||
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
|
||||
|
||||
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
|
||||
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
|
||||
|
||||
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
|
||||
|
||||
if (redisProperties.getTimeToLive() != null) {
|
||||
config = config.entryTtl(redisProperties.getTimeToLive());
|
||||
}
|
||||
if (!redisProperties.isCacheNullValues()) {
|
||||
config = config.disableCachingNullValues();
|
||||
}
|
||||
if (!redisProperties.isUseKeyPrefix()) {
|
||||
config = config.disableKeyPrefix();
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory,CacheProperties cacheProperties) {
|
||||
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 指定要序列化的域(field,get,set),访问修饰符(public,private,protected)
|
||||
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
objectMapper.registerModules(new JavaTimeModule());
|
||||
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
|
||||
|
||||
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
|
||||
.entryTtl(cacheProperties.getRedis().getTimeToLive()) // 设置缓存时间
|
||||
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
|
||||
|
||||
return RedisCacheManager.builder(redisConnectionFactory)
|
||||
.cacheDefaults(cacheConfiguration)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.oneone.common.redis;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.oneone.common.redis.utils.LocalRedisTokenStore;
|
||||
import com.oneone.common.redis.utils.RedisUtils;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.cache.BatchStrategies;
|
||||
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.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Configuration
|
||||
@AutoConfigureBefore({RedisAutoConfiguration.class})
|
||||
public class RedisConfig {
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
|
||||
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 指定要序列化的域(field,get,set),访问修饰符(public,private,protected)
|
||||
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
objectMapper.registerModules(new JavaTimeModule());
|
||||
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
|
||||
|
||||
redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8); // key
|
||||
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); //value
|
||||
|
||||
redisTemplate.setHashKeySerializer(StringRedisSerializer.UTF_8);
|
||||
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
|
||||
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.oneone.common.redis;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.oneone.common.redis.delayqueue.RedisDelayQueueUtil;
|
||||
import com.oneone.common.redis.lock.LockAspect;
|
||||
import lombok.Setter;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.config.SingleServerConfig;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 分布式锁 Redisson 配置
|
||||
*
|
||||
* @author oneone
|
||||
* @date 2021/2/22
|
||||
*/
|
||||
@Configuration
|
||||
public class RedissonConfig {
|
||||
|
||||
@Bean
|
||||
public RedisDelayQueueUtil redisDelayQueueUtil(){
|
||||
return new RedisDelayQueueUtil();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LockAspect LockAspect(){
|
||||
return new LockAspect();
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.oneone.common.redis.captcha;
|
||||
|
||||
import com.xingyuv.captcha.config.AjCaptchaAutoConfiguration;
|
||||
import com.xingyuv.captcha.properties.AjCaptchaProperties;
|
||||
import com.xingyuv.captcha.service.CaptchaCacheService;
|
||||
import com.xingyuv.captcha.service.impl.CaptchaServiceFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(value = AjCaptchaAutoConfiguration.class)
|
||||
public class CaptchaConfiguration {
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Bean
|
||||
public CaptchaCacheService captchaCacheService(AjCaptchaProperties config) {
|
||||
// 缓存类型 redis/local/....
|
||||
CaptchaCacheService ret = CaptchaServiceFactory.getCache(config.getCacheType().name());
|
||||
if (ret instanceof RedisCaptchaServiceImpl) {
|
||||
((RedisCaptchaServiceImpl) ret).setStringRedisTemplate(stringRedisTemplate);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.oneone.common.redis.captcha;
|
||||
|
||||
public interface CaptchaRedisKeyConstants {
|
||||
|
||||
/**
|
||||
* 验证码的请求限流
|
||||
*
|
||||
* KEY 格式:AJ.CAPTCHA.REQ.LIMIT-%s-%s
|
||||
* VALUE 数据类型:String // 例如说:验证失败 5 次,get 接口锁定
|
||||
* 过期时间:60 秒
|
||||
*/
|
||||
String AJ_CAPTCHA_REQ_LIMIT = "AJ.CAPTCHA.REQ.LIMIT-%s-%s";
|
||||
|
||||
/**
|
||||
* 验证码的坐标
|
||||
*
|
||||
* 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";
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.oneone.common.redis.captcha;
|
||||
|
||||
import com.xingyuv.captcha.service.CaptchaCacheService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 基于 Redis 实现验证码的存储
|
||||
*
|
||||
* @author 星语
|
||||
*/
|
||||
@NoArgsConstructor // 保证 aj-captcha 的 SPI 创建
|
||||
@AllArgsConstructor
|
||||
public class RedisCaptchaServiceImpl implements CaptchaCacheService {
|
||||
|
||||
@Resource // 保证 aj-captcha 的 SPI 创建时的注入
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return "redis";
|
||||
}
|
||||
|
||||
public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(String key, String value, long expiresInSeconds) {
|
||||
stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String key) {
|
||||
return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String key) {
|
||||
stringRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(String key) {
|
||||
return stringRedisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long increment(String key, long val) {
|
||||
return stringRedisTemplate.opsForValue().increment(key,val);
|
||||
}
|
||||
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.oneone.common.redis.delayqueue;
|
||||
|
||||
public interface BaseDelayQueueEnum {
|
||||
|
||||
/**
|
||||
* 延迟队列 Redis Key
|
||||
*/
|
||||
String getCode();
|
||||
/**
|
||||
* 中文描述
|
||||
*/
|
||||
String getMsg();
|
||||
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.oneone.common.redis.delayqueue;
|
||||
|
||||
/**
|
||||
* 延迟队列执行器
|
||||
*/
|
||||
public interface RedisDelayQueueHandler<T> {
|
||||
|
||||
void execute(T t);
|
||||
|
||||
BaseDelayQueueEnum getQueueName();
|
||||
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package com.oneone.common.redis.delayqueue;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.redisson.api.RBlockingDeque;
|
||||
import org.redisson.api.RDelayedQueue;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnBean({RedissonClient.class})
|
||||
@RequiredArgsConstructor
|
||||
public class RedisDelayQueueUtil {
|
||||
|
||||
@Resource
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
/**
|
||||
* 添加延迟队列
|
||||
*
|
||||
* @param value 队列值
|
||||
* @param delay 延迟时间
|
||||
* @param timeUnit 时间单位
|
||||
* @param queueCode 队列键
|
||||
* @param <T>
|
||||
*/
|
||||
public <T> boolean addDelayQueue(T value, long delay, TimeUnit timeUnit, String queueCode) {
|
||||
if (StringUtils.isBlank(queueCode) || Objects.isNull(value)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(queueCode);
|
||||
RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
|
||||
delayedQueue.offer(value, delay, timeUnit);
|
||||
//delayedQueue.destroy();
|
||||
log.info("(添加延时队列成功) 队列键:{},队列值:{},延迟时间:{}", queueCode, value, timeUnit.toSeconds(delay) + "秒");
|
||||
} catch (Exception e) {
|
||||
log.error("(添加延时队列失败) {}", e.getMessage());
|
||||
throw new RuntimeException("(添加延时队列失败)");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取延迟队列数据
|
||||
*
|
||||
* @param queueCode
|
||||
* @param <T>
|
||||
*/
|
||||
public <T> T getDelayQueueValue(String queueCode) throws InterruptedException {
|
||||
if (StringUtils.isBlank(queueCode)) {
|
||||
return null;
|
||||
}
|
||||
RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(queueCode);
|
||||
T value = (T) blockingDeque.poll();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取延迟队列
|
||||
*
|
||||
* @param queueCode
|
||||
*/
|
||||
public RBlockingDeque<Object> getDelayQueue(String queueCode) throws InterruptedException {
|
||||
if (StringUtils.isBlank(queueCode)) {
|
||||
return null;
|
||||
}
|
||||
RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(queueCode);
|
||||
return blockingDeque;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定队列中的消息
|
||||
*
|
||||
* @param o 指定删除的消息对象队列值(同队列需保证唯一性)
|
||||
* @param queueCode 指定队列键
|
||||
*/
|
||||
public boolean removeDelayedQueue(Object o, String queueCode) {
|
||||
if (StringUtils.isBlank(queueCode) || Objects.isNull(o)) {
|
||||
return false;
|
||||
}
|
||||
RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(queueCode);
|
||||
RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
|
||||
boolean flag = delayedQueue.remove(o);
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.oneone.common.redis.lock;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-03-31 16:20
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Lock {
|
||||
|
||||
String key();
|
||||
|
||||
String spel() default "";
|
||||
/**
|
||||
* 等待时间
|
||||
* @return
|
||||
*/
|
||||
long waitTime() default 0L;
|
||||
|
||||
/**
|
||||
* 持有时间
|
||||
* @return
|
||||
*/
|
||||
long leaseTime() default 5L;
|
||||
|
||||
/**
|
||||
* unit
|
||||
* @return
|
||||
*/
|
||||
TimeUnit unit() default TimeUnit.SECONDS;
|
||||
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package com.oneone.common.redis.lock;
|
||||
|
||||
import com.oneone.common.exception.BusinessException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class LockAspect {
|
||||
@Resource
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
|
||||
private final ThreadLocal<RLock> LOCK_THREAD_LOCAL = new ThreadLocal<>();
|
||||
|
||||
@Before("@annotation(lock)")
|
||||
public void doBefore(JoinPoint joinPoint, Lock lock) {
|
||||
String key = lock.key();
|
||||
|
||||
String spel = lock.spel();
|
||||
|
||||
if (StringUtils.isNotEmpty(spel)){
|
||||
//获得被切面修饰的方法的参数列表
|
||||
Object[] args = joinPoint.getArgs();
|
||||
// 获得被代理的方法
|
||||
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
|
||||
|
||||
String result = parseKey(spel,method,args);
|
||||
|
||||
if (StringUtils.isNotEmpty(key)){
|
||||
key = StringUtils.appendIfMissing(key,":")+result;
|
||||
}else {
|
||||
key = result;
|
||||
}
|
||||
}
|
||||
|
||||
RLock rLock = redissonClient.getLock(key);
|
||||
|
||||
long waitTime = lock.waitTime();
|
||||
long leaseTime = lock.leaseTime();
|
||||
TimeUnit timeUnit = lock.unit();
|
||||
try {
|
||||
if (!rLock.tryLock(waitTime, leaseTime, timeUnit)) {
|
||||
throw new BusinessException("请求过于频繁");
|
||||
}
|
||||
log.info("获取分布式锁成功:{}",key);
|
||||
} catch (InterruptedException e) {
|
||||
throw new BusinessException("请求过于频繁");
|
||||
}
|
||||
LOCK_THREAD_LOCAL.set(rLock);
|
||||
}
|
||||
|
||||
@AfterReturning(value = "@annotation(lock)", returning = "result")
|
||||
public void doAfterReturning(Object result, Lock lock) {
|
||||
try {
|
||||
RLock rLock = LOCK_THREAD_LOCAL.get();
|
||||
if (null != rLock) {
|
||||
rLock.unlock();
|
||||
log.info("释放分布式锁成功:{}",rLock.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("释放锁失败", e);
|
||||
} finally {
|
||||
// 清除threadlocal
|
||||
LOCK_THREAD_LOCAL.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterThrowing(value = "@annotation(lock)", throwing = "throwable")
|
||||
public void doAfterThrowing(Lock lock, Throwable throwable) {
|
||||
try {
|
||||
RLock rLock = LOCK_THREAD_LOCAL.get();
|
||||
if (null != rLock) {
|
||||
rLock.unlock();
|
||||
log.info("释放分布式锁成功:{}",rLock.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("释放锁失败", e);
|
||||
} finally {
|
||||
// 清除threadlocal
|
||||
LOCK_THREAD_LOCAL.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param spel
|
||||
* @param method
|
||||
* @param args
|
||||
* @return
|
||||
*/
|
||||
private String parseKey(String spel, Method method, Object [] args){
|
||||
|
||||
if(StringUtils.isEmpty(spel)) return null;
|
||||
|
||||
//获取被拦截方法参数名列表(使用Spring支持类库)
|
||||
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
|
||||
String[] paraNameArr = u.getParameterNames(method);
|
||||
|
||||
//使用SPEL进行key的解析
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
//SPEL上下文
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
//把方法参数放入SPEL上下文中
|
||||
for(int i=0;i<paraNameArr.length;i++){
|
||||
context.setVariable(paraNameArr[i], args[i]);
|
||||
}
|
||||
return parser.parseExpression(spel).getValue(context,String.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.oneone.common.redis.lock;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-03-08 11:47
|
||||
*/
|
||||
public interface RedisLock {
|
||||
|
||||
boolean tryLock(String key);
|
||||
|
||||
boolean unLock(String key);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.oneone.common.redis.lua;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-03-30 11:02
|
||||
*/
|
||||
public interface LockScripts {
|
||||
|
||||
/**
|
||||
* 释放锁lua脚本
|
||||
*/
|
||||
String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.oneone.common.redis.lua;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-03-30 11:02
|
||||
*/
|
||||
public interface StockScripts {
|
||||
|
||||
/**
|
||||
* 加减库存 -1 库存不足 -2 未初始化库存 >= 0 成功
|
||||
*/
|
||||
String DEC_STOCK = "if (redis.call('exists', KEYS[1]) == 1) then" +
|
||||
" local stock = tonumber(redis.call('get', KEYS[1]));" +
|
||||
" if (stock <= 0) then" +
|
||||
" return -1;" +
|
||||
" end;" +
|
||||
" local changeNum = tonumber(ARGV[1]);" +
|
||||
" if (stock < changeNum) then" +
|
||||
" return -1;" +
|
||||
" end;" +
|
||||
" stock = redis.call('incrby', KEYS[1], 0-changeNum);" +
|
||||
" return stock;" +
|
||||
"end;" +
|
||||
"return -2;";
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.oneone.common.redis.utils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-03-14 16:35
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DistributedUniqueIdGenerator {
|
||||
private static DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||
|
||||
private final RedisUtils redisUtils;
|
||||
|
||||
/* *//**
|
||||
* 22位 规则:yyyyMMddHHmmss(14)+oderType(2)+6位自增
|
||||
* @return
|
||||
*//*
|
||||
public String generate22(String orderType){
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static String serialNum(String type) {
|
||||
String dateStr = LocalDateTimeUtil.format(LocalDateTime.now(), DATE_TIME_FORMATTER);
|
||||
return serialnum;
|
||||
}*/
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package com.oneone.common.redis.utils;
|
||||
|
||||
import com.oneone.common.base.LocalToken;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-04-02 13:23
|
||||
*/
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class LocalRedisTokenStore {
|
||||
private final RedisUtils redisUtils;
|
||||
|
||||
private Long refreshTokenValid = 1296000L;
|
||||
@Setter
|
||||
private String prefix = "local_token:";
|
||||
|
||||
private final String ACCESS_TOKEN = "access_token:";
|
||||
|
||||
private final String ACCESS_TOKEN_USER = "access_token_user:";
|
||||
|
||||
private final String REFRESH_ACCESS_TOKEN = "refresh_access_token:";
|
||||
|
||||
public void storeAccessToken(LocalToken token) {
|
||||
String access_token_user_key = prefix + ACCESS_TOKEN_USER + token.getMemberId();
|
||||
|
||||
LocalToken oldToken = (LocalToken) redisUtils.get(access_token_user_key);
|
||||
if (oldToken != null) {
|
||||
String token_key = prefix + ACCESS_TOKEN + oldToken.getAccessToken();
|
||||
String refreshTokenKey = prefix + REFRESH_ACCESS_TOKEN + oldToken.getRefreshToken();
|
||||
redisUtils.del(token_key, refreshTokenKey);
|
||||
}
|
||||
|
||||
|
||||
long tokenValid = (token.getExpiresIn() - System.currentTimeMillis()) / 1000l;
|
||||
String token_key = prefix + ACCESS_TOKEN + token.getAccessToken();
|
||||
redisUtils.set(token_key, token, tokenValid);
|
||||
redisUtils.set(access_token_user_key, token, tokenValid);
|
||||
|
||||
String refreshTokenKey = prefix + REFRESH_ACCESS_TOKEN + token.getRefreshToken();
|
||||
redisUtils.set(refreshTokenKey, token, refreshTokenValid);
|
||||
}
|
||||
|
||||
|
||||
public LocalToken readAccessToken(String accessToken) {
|
||||
String token_key = prefix + ACCESS_TOKEN + accessToken;
|
||||
return (LocalToken) redisUtils.get(token_key);
|
||||
}
|
||||
|
||||
public LocalToken readFreshToken(String freshToken) {
|
||||
String refreshTokenKey = prefix + REFRESH_ACCESS_TOKEN + freshToken;
|
||||
|
||||
return (LocalToken) redisUtils.get(refreshTokenKey);
|
||||
}
|
||||
|
||||
public void removeToken(String accessToken){
|
||||
LocalToken token = readAccessToken(accessToken);
|
||||
if (token == null){
|
||||
return;
|
||||
}
|
||||
String access_token_user_key = prefix + ACCESS_TOKEN_USER + token.getMemberId();
|
||||
|
||||
LocalToken oldToken = (LocalToken) redisUtils.get(access_token_user_key);
|
||||
if (oldToken != null) {
|
||||
String token_key = prefix + ACCESS_TOKEN + oldToken.getAccessToken();
|
||||
String refreshTokenKey = prefix + REFRESH_ACCESS_TOKEN + oldToken.getRefreshToken();
|
||||
redisUtils.del(token_key, refreshTokenKey,access_token_user_key);
|
||||
}
|
||||
}
|
||||
|
||||
public LocalToken removeToken(Long memberId){
|
||||
String access_token_user_key = prefix + ACCESS_TOKEN_USER + memberId;
|
||||
|
||||
LocalToken oldToken = (LocalToken) redisUtils.get(access_token_user_key);
|
||||
if (oldToken != null) {
|
||||
String token_key = prefix + ACCESS_TOKEN + oldToken.getAccessToken();
|
||||
String refreshTokenKey = prefix + REFRESH_ACCESS_TOKEN + oldToken.getRefreshToken();
|
||||
redisUtils.del(token_key, refreshTokenKey,access_token_user_key);
|
||||
}else {
|
||||
redisUtils.del(access_token_user_key);
|
||||
}
|
||||
return oldToken;
|
||||
}
|
||||
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
package com.oneone.common.redis.utils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 基于Redis位图的用户签到功能实现类
|
||||
* <p>
|
||||
* 实现功能:
|
||||
* 1. 用户签到
|
||||
* 2. 检查用户是否签到
|
||||
* 3. 获取当月签到次数
|
||||
* 4. 获取当月连续签到次数
|
||||
* 5. 获取当月首次签到日期
|
||||
* 6. 获取当月签到情况
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MonthSignUtil {
|
||||
|
||||
private final RedisUtils redisUtils;
|
||||
|
||||
|
||||
/**
|
||||
* 用户签到
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return 之前的签到状态
|
||||
*/
|
||||
public boolean doSign(String uid, LocalDate date) {
|
||||
int offset = date.getDayOfMonth() - 1;
|
||||
return redisUtils.setBit(buildSignKey(uid, date), offset, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否签到
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return 当前的签到状态
|
||||
*/
|
||||
public boolean checkSign(String uid, LocalDate date) {
|
||||
int offset = date.getDayOfMonth() - 1;
|
||||
return redisUtils.getBit(buildSignKey(uid, date), offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户签到次数
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return 当前的签到次数
|
||||
*/
|
||||
public long getSignCount(String uid, LocalDate date) {
|
||||
return redisUtils.bitCount(buildSignKey(uid, date));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当月连续签到次数
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return 当月连续签到次数
|
||||
*/
|
||||
public long getContinuousSignCount(String uid, LocalDate date) {
|
||||
int signCount = 0;
|
||||
List<Long> list = redisUtils.bitField(buildSignKey(uid, date), date.getDayOfMonth(), 0);
|
||||
if (list != null && list.size() > 0) {
|
||||
// 取低位连续不为0的个数即为连续签到次数,需考虑当天尚未签到的情况
|
||||
long v = list.get(0) == null ? 0 : list.get(0);
|
||||
for (int i = 0; i < date.getDayOfMonth(); i++) {
|
||||
if (v >> 1 << 1 == v) {
|
||||
// 低位为0且非当天说明连续签到中断了
|
||||
if (i > 0) break;
|
||||
} else {
|
||||
signCount += 1;
|
||||
}
|
||||
v >>= 1;
|
||||
}
|
||||
}
|
||||
return signCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当月首次签到日期
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return 首次签到日期
|
||||
*/
|
||||
public LocalDate getFirstSignDate(String uid, LocalDate date) {
|
||||
long pos = redisUtils.bitPos(buildSignKey(uid, date), true);
|
||||
return pos < 0 ? null : date.withDayOfMonth((int) (pos + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当月签到情况
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return Key为签到日期,Value为签到状态的Map
|
||||
*/
|
||||
public Map<Integer, Boolean> getSignInfo(String uid, LocalDate date) {
|
||||
Map<Integer, Boolean> signMap = new HashMap<>(date.getDayOfMonth());
|
||||
List<Long> list = redisUtils.bitField(buildSignKey(uid, date), date.lengthOfMonth(), 0);
|
||||
if (list != null && list.size() > 0) {
|
||||
// 由低位到高位,为0表示未签,为1表示已签
|
||||
long v = list.get(0) == null ? 0 : list.get(0);
|
||||
for (int i = date.lengthOfMonth(); i > 0; i--) {
|
||||
/* LocalDate d = date.withDayOfMonth(i);
|
||||
signMap.put(formatDate(d, "yyyy-MM-dd"), v >> 1 << 1 != v);*/
|
||||
signMap.put(i, v >> 1 << 1 != v);
|
||||
v >>= 1;
|
||||
}
|
||||
}
|
||||
return signMap;
|
||||
}
|
||||
|
||||
private static String formatDate(LocalDate date) {
|
||||
return formatDate(date, "yyyyMM");
|
||||
}
|
||||
|
||||
private static String formatDate(LocalDate date, String pattern) {
|
||||
return date.format(DateTimeFormatter.ofPattern(pattern));
|
||||
}
|
||||
|
||||
private static String buildSignKey(String uid, LocalDate date) {
|
||||
return String.format("u:sign:%s:%s", uid, formatDate(date));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
/* MonthSignUtil demo = new MonthSignUtil();
|
||||
LocalDate today = LocalDate.now();
|
||||
|
||||
{ // doSign
|
||||
boolean signed = demo.doSign("1000", today);
|
||||
if (signed) {
|
||||
System.out.println("您已签到:" + formatDate(today, "yyyy-MM-dd"));
|
||||
} else {
|
||||
System.out.println("签到完成:" + formatDate(today, "yyyy-MM-dd"));
|
||||
}
|
||||
}
|
||||
|
||||
{ // checkSign
|
||||
boolean signed = demo.checkSign("1000", today);
|
||||
if (signed) {
|
||||
System.out.println("您已签到:" + formatDate(today, "yyyy-MM-dd"));
|
||||
} else {
|
||||
System.out.println("尚未签到:" + formatDate(today, "yyyy-MM-dd"));
|
||||
}
|
||||
}
|
||||
|
||||
{ // getSignCount
|
||||
long count = demo.getSignCount("1000", today);
|
||||
System.out.println("本月签到次数:" + count);
|
||||
}
|
||||
|
||||
{ // getContinuousSignCount
|
||||
long count = demo.getContinuousSignCount("1000", today);
|
||||
System.out.println("连续签到次数:" + count);
|
||||
}
|
||||
|
||||
{ // getFirstSignDate
|
||||
LocalDate date = demo.getFirstSignDate("1000", today);
|
||||
System.out.println("本月首次签到:" + formatDate(date, "yyyy-MM-dd"));
|
||||
}
|
||||
|
||||
{ // getSignInfo
|
||||
System.out.println("当月签到情况:");
|
||||
Map<String, Boolean> signInfo = new TreeMap<>(demo.getSignInfo("1000", today));
|
||||
for (Map.Entry<String, Boolean> entry : signInfo.entrySet()) {
|
||||
System.out.println(entry.getKey() + ": " + (entry.getValue() ? "√" : "-"));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
}
|
||||
+849
@@ -0,0 +1,849 @@
|
||||
package com.oneone.common.redis.utils;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.connection.BitFieldSubCommands;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ZSetOperations;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author oneone
|
||||
* @desc spring redis 通用工具类
|
||||
* @date 2021/2/9
|
||||
*/
|
||||
@Component
|
||||
public class RedisUtils {
|
||||
|
||||
/**
|
||||
* 注入redisTemplate bean
|
||||
*/
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* 指定缓存失效时间
|
||||
*
|
||||
* @param key 键
|
||||
* @param time 时间(秒)
|
||||
* @return
|
||||
*/
|
||||
public boolean expire(String key, long time) {
|
||||
try {
|
||||
if (time > 0) {
|
||||
redisTemplate.expire(key, time, TimeUnit.SECONDS);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定缓存失效时间
|
||||
*
|
||||
* @param key 键
|
||||
* @param time 时间(秒)
|
||||
* @return
|
||||
*/
|
||||
public boolean expire(String key, long time,TimeUnit timeUnit) {
|
||||
try {
|
||||
if (time > 0) {
|
||||
redisTemplate.expire(key, time, timeUnit);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据key获取过期时间
|
||||
*
|
||||
* @param key 键 不能为null
|
||||
* @return 时间(秒) 返回0代表为永久有效
|
||||
*/
|
||||
public long getExpire(String key) {
|
||||
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断key是否存在
|
||||
*
|
||||
* @param key 键
|
||||
* @return true 存在 false不存在
|
||||
*/
|
||||
public boolean hasKey(String key) {
|
||||
try {
|
||||
return redisTemplate.hasKey(key);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
*
|
||||
* @param key 可以传一个值 或多个
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void del(String... key) {
|
||||
if (key != null && key.length > 0) {
|
||||
if (key.length == 1) {
|
||||
redisTemplate.delete(key[0]);
|
||||
} else {
|
||||
redisTemplate.delete((List<String>) CollectionUtils.arrayToList(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
*
|
||||
* @param key 可以传一个值 或多个
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean del(String key) {
|
||||
return redisTemplate.delete(key);
|
||||
}
|
||||
// ============================String(字符串)=============================
|
||||
|
||||
/**
|
||||
* 普通缓存获取
|
||||
*
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
public Object get(String key) {
|
||||
return key == null ? null : redisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通缓存放入
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return true成功 false失败
|
||||
*/
|
||||
public boolean set(String key, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForValue().set(key, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通缓存放入并设置时间
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
|
||||
* @return true成功 false 失败
|
||||
*/
|
||||
public boolean set(String key, Object value, long time) {
|
||||
try {
|
||||
if (time > 0) {
|
||||
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
|
||||
} else {
|
||||
set(key, value);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通缓存放入并设置时间
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param time 时间 time要大于0 如果time小于等于0 将设置无限期
|
||||
* @return true成功 false 失败
|
||||
*/
|
||||
public boolean set(String key, Object value, long time,TimeUnit timeUnit) {
|
||||
try {
|
||||
if (time > 0) {
|
||||
redisTemplate.opsForValue().set(key, value, time, timeUnit);
|
||||
} else {
|
||||
set(key, value);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递增
|
||||
*
|
||||
* @param key 键
|
||||
* @param delta 要增加几(大于0)
|
||||
* @return
|
||||
*/
|
||||
public long incr(String key, long delta) {
|
||||
if (delta < 0) {
|
||||
throw new RuntimeException("递增因子必须大于0");
|
||||
}
|
||||
return redisTemplate.opsForValue().increment(key, delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递减
|
||||
*
|
||||
* @param key 键
|
||||
* @param delta 要减少几(小于0)
|
||||
* @return
|
||||
*/
|
||||
public long decr(String key, long delta) {
|
||||
if (delta < 0) {
|
||||
throw new RuntimeException("递减因子必须大于0");
|
||||
}
|
||||
return redisTemplate.opsForValue().increment(key, -delta);
|
||||
}
|
||||
// ================================Hash(哈希)=================================
|
||||
|
||||
/**
|
||||
* HashGet
|
||||
*
|
||||
* @param key 键 不能为null
|
||||
* @param item 项 不能为null
|
||||
* @return 值
|
||||
*/
|
||||
public Object hget(String key, String item) {
|
||||
return redisTemplate.opsForHash().get(key, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取hashKey对应的所有键值
|
||||
*
|
||||
* @param key 键
|
||||
* @return 对应的多个键值
|
||||
*/
|
||||
public Map<Object, Object> hmget(String key) {
|
||||
return redisTemplate.opsForHash().entries(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* HashSet
|
||||
*
|
||||
* @param key 键
|
||||
* @param map 对应多个键值
|
||||
* @return true 成功 false 失败
|
||||
*/
|
||||
public boolean hmset(String key, Map<String, Object> map) {
|
||||
try {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HashSet 并设置时间
|
||||
*
|
||||
* @param key 键
|
||||
* @param map 对应多个键值
|
||||
* @param time 时间(秒)
|
||||
* @return true成功 false失败
|
||||
*/
|
||||
public boolean hmset(String key, Map<String, Object> map, long time) {
|
||||
try {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向一张hash表中放入数据,如果不存在将创建
|
||||
*
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param value 值
|
||||
* @return true 成功 false失败
|
||||
*/
|
||||
public boolean hset(String key, String item, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForHash().put(key, item, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向一张hash表中放入数据,如果不存在将创建
|
||||
*
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param value 值
|
||||
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
|
||||
* @return true 成功 false失败
|
||||
*/
|
||||
public boolean hset(String key, String item, Object value, long time) {
|
||||
try {
|
||||
redisTemplate.opsForHash().put(key, item, value);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除hash表中的值
|
||||
*
|
||||
* @param key 键 不能为null
|
||||
* @param item 项 可以使多个 不能为null
|
||||
*/
|
||||
public void hdel(String key, Object... item) {
|
||||
redisTemplate.opsForHash().delete(key, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断hash表中是否有该项的值
|
||||
*
|
||||
* @param key 键 不能为null
|
||||
* @param item 项 不能为null
|
||||
* @return true 存在 false不存在
|
||||
*/
|
||||
public boolean hHasKey(String key, String item) {
|
||||
return redisTemplate.opsForHash().hasKey(key, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
|
||||
*
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param by 要增加几(大于0)
|
||||
* @return
|
||||
*/
|
||||
public double hincr(String key, String item, double by) {
|
||||
return redisTemplate.opsForHash().increment(key, item, by);
|
||||
}
|
||||
|
||||
/**
|
||||
* hash递减
|
||||
*
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param by 要减少记(小于0)
|
||||
* @return
|
||||
*/
|
||||
public double hdecr(String key, String item, double by) {
|
||||
return redisTemplate.opsForHash().increment(key, item, -by);
|
||||
}
|
||||
// ============================Set(集合)=============================
|
||||
|
||||
/**
|
||||
* 根据key获取Set中的所有值
|
||||
*
|
||||
* @param key 键
|
||||
* @return
|
||||
*/
|
||||
public Set<Object> sGet(String key) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().members(key);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据value从一个set中查询,是否存在
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return true 存在 false不存在
|
||||
*/
|
||||
public boolean sHasKey(String key, Object value) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().isMember(key, value);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据放入set缓存
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 值 可以是多个
|
||||
* @return 成功个数
|
||||
*/
|
||||
public long sSet(String key, Object... values) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().add(key, values);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将set数据放入缓存
|
||||
*
|
||||
* @param key 键
|
||||
* @param time 时间(秒)
|
||||
* @param values 值 可以是多个
|
||||
* @return 成功个数
|
||||
*/
|
||||
public long sSetAndTime(String key, long time, Object... values) {
|
||||
try {
|
||||
Long count = redisTemplate.opsForSet().add(key, values);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return count;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取set缓存的长度
|
||||
*
|
||||
* @param key 键
|
||||
* @return
|
||||
*/
|
||||
public long sGetSetSize(String key) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().size(key);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在
|
||||
* @param key
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public Boolean sContain(String key,Object value) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().isMember(key,value);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除值为value的
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 值 可以是多个
|
||||
* @return 移除的个数
|
||||
*/
|
||||
public long setRemove(String key, Object... values) {
|
||||
try {
|
||||
Long count = redisTemplate.opsForSet().remove(key, values);
|
||||
return count;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// ===============================List(列表)=================================
|
||||
|
||||
|
||||
/**
|
||||
* 获取list缓存的内容
|
||||
*
|
||||
* @param key 键
|
||||
* @param start 开始
|
||||
* @param end 结束 0 到 -1代表所有值
|
||||
* @return
|
||||
*/
|
||||
public <T> List<T> lGet(String key, long start, long end) {
|
||||
try {
|
||||
return (List<T>) redisTemplate.opsForList().range(key, start, end);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取list缓存的长度
|
||||
*
|
||||
* @param key 键
|
||||
* @return
|
||||
*/
|
||||
public long lGetListSize(String key) {
|
||||
try {
|
||||
return redisTemplate.opsForList().size(key);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过索引 获取list中的值
|
||||
*
|
||||
* @param key 键
|
||||
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
|
||||
* @return
|
||||
*/
|
||||
public Object lGetIndex(String key, long index) {
|
||||
try {
|
||||
return redisTemplate.opsForList().index(key, index);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取左边第一个
|
||||
*
|
||||
* @param key 键
|
||||
* @return
|
||||
*/
|
||||
public Object lLeftPop(String key) {
|
||||
return redisTemplate.opsForList().leftPop(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将list放入缓存
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return
|
||||
*/
|
||||
public boolean lSet(String key, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPush(key, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将list放入缓存
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param time 时间(秒)
|
||||
* @return
|
||||
*/
|
||||
public boolean lSet(String key, Object value, long time) {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPush(key, value);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将list放入缓存
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return
|
||||
*/
|
||||
public <T> void lSet(String key, List<T> value) {
|
||||
redisTemplate.opsForList().rightPushAll(key, value.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将list放入缓存
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param time 时间(秒)
|
||||
* @return
|
||||
*/
|
||||
public boolean lSet(String key, List<Object> value, long time) {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPushAll(key, value.toArray());
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据索引修改list中的某条数据
|
||||
*
|
||||
* @param key 键
|
||||
* @param index 索引
|
||||
* @param value 值
|
||||
* @return
|
||||
*/
|
||||
public boolean lUpdateIndex(String key, long index, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForList().set(key, index, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除N个值为value
|
||||
*
|
||||
* @param key 键
|
||||
* @param count 移除多少个
|
||||
* @param value 值
|
||||
* @return 移除的个数
|
||||
*/
|
||||
public long lRemove(String key, long count, Object value) {
|
||||
try {
|
||||
Long remove = redisTemplate.opsForList().remove(key, count, value);
|
||||
return remove;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据正则表达式获取key列表
|
||||
*
|
||||
* @param patternKey 正则表达式
|
||||
* @return 匹配key列表
|
||||
*/
|
||||
public Set<String> keys(String patternKey) {
|
||||
try {
|
||||
Set<String> keys = redisTemplate.keys(patternKey);
|
||||
return keys;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new HashSet<>();
|
||||
}
|
||||
}
|
||||
public List executeLua(String lua, List<String> keys, Object... values) {
|
||||
// 指定 lua 脚本,并且指定返回值类型
|
||||
DefaultRedisScript<List> redisScript = new DefaultRedisScript<>(lua, List.class);
|
||||
return redisTemplate.execute(redisScript, keys, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* bitCount
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public long bitCount(String key) {
|
||||
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
|
||||
}
|
||||
|
||||
/**
|
||||
* bitCount
|
||||
* @param key
|
||||
* @param start
|
||||
* @param end
|
||||
* @return
|
||||
*/
|
||||
public Long bitCount(String key, int start, int end) {
|
||||
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes(), start, end));
|
||||
}
|
||||
|
||||
public Boolean setBit(String key,long offset,boolean value){
|
||||
return redisTemplate.opsForValue().setBit(key, offset, value);
|
||||
}
|
||||
|
||||
public Boolean getBit(String key,long offset){
|
||||
return redisTemplate.opsForValue().getBit(key, offset);
|
||||
}
|
||||
|
||||
public List<Long> bitField(String key, int limit, long offset){
|
||||
return redisTemplate.execute((RedisCallback<List<Long>>) con ->
|
||||
con.bitField(key.getBytes(), BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(limit)).valueAt(offset))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 第一个位置
|
||||
* @param key
|
||||
* @param bit
|
||||
* @return
|
||||
*/
|
||||
public Long bitPos(String key,boolean bit) {
|
||||
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitPos(key.getBytes(),bit));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 添加一个元素, zset与set最大的区别就是每个元素都有一个score,因此有个排序的辅助功能; zadd
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @param score
|
||||
*/
|
||||
public void zsetAdd(String key, String value, double score) {
|
||||
redisTemplate.opsForZSet().add(key, value, score);
|
||||
}
|
||||
/**
|
||||
* 删除元素 zrem
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
public void zsetRemove(String key, String value) {
|
||||
redisTemplate.opsForZSet().remove(key, value);
|
||||
}
|
||||
/**
|
||||
* score的增加or减少 zincrby
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @param score
|
||||
*/
|
||||
public Double zsetIncrScore(String key, String value, double score) {
|
||||
return redisTemplate.opsForZSet().incrementScore(key, value, score);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 查询value对应的score zscore
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public Double zsetScore(String key, String value) {
|
||||
return redisTemplate.opsForZSet().score(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断value在zset中的排名 zrank
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public Long zsetRankAsc(String key, String value) {
|
||||
return redisTemplate.opsForZSet().rank(key, value);
|
||||
}
|
||||
/**
|
||||
* 判断value在zset中的排名 zrank
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public Long zsetRankDesc(String key, String value) {
|
||||
return redisTemplate.opsForZSet().reverseRank(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回集合的长度
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public Long zsetSize(String key) {
|
||||
return redisTemplate.opsForZSet().zCard(key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询集合中指定顺序的值, 0 -1 表示获取全部的集合内容 zrange
|
||||
*
|
||||
* 返回有序的集合,score小的在前面
|
||||
*
|
||||
* @param key
|
||||
* @param start
|
||||
* @param end
|
||||
* @return
|
||||
*/
|
||||
public Set zsetRange(String key, int start, int end) {
|
||||
return redisTemplate.opsForZSet().range(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询集合中指定顺序的值和score,0, -1 表示获取全部的集合内容
|
||||
*
|
||||
* @param key
|
||||
* @param start
|
||||
* @param end
|
||||
* @return
|
||||
*/
|
||||
public Set<ZSetOperations.TypedTuple<Object>> zsetRangeWithScore(String key, int start, int end) {
|
||||
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
|
||||
}
|
||||
|
||||
public Set<ZSetOperations.TypedTuple<Object>> zsetRevRangeWithScore(String key, int start, int end) {
|
||||
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
|
||||
}
|
||||
/**
|
||||
* 查询集合中指定顺序的值 zrevrange
|
||||
*
|
||||
* 返回有序的集合中,score大的在前面
|
||||
*
|
||||
* @param key
|
||||
* @param start
|
||||
* @param end
|
||||
* @return
|
||||
*/
|
||||
public Set zsetRevRange(String key, int start, int end) {
|
||||
return redisTemplate.opsForZSet().reverseRange(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据score的值,来获取满足条件的集合 zrangebyscore
|
||||
*
|
||||
* @param key
|
||||
* @param min
|
||||
* @param max
|
||||
* @return
|
||||
*/
|
||||
public Set zsetSortRange(String key, int min, int max) {
|
||||
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
package com.oneone.common.redis.utils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 基于Redis位图的用户签到功能实现类
|
||||
* <p>
|
||||
* 实现功能:
|
||||
* 1. 用户签到
|
||||
* 2. 检查用户是否签到
|
||||
* 3. 获取本周签到次数
|
||||
* 4. 获取本周连续签到次数
|
||||
* 5. 获取本周首次签到日期
|
||||
* 6. 获取本周签到情况
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class WeekSignUtil {
|
||||
|
||||
private final RedisUtils redisUtils;
|
||||
|
||||
|
||||
/**
|
||||
* 用户签到
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return 之前的签到状态
|
||||
*/
|
||||
public boolean doSign(String uid, LocalDate date) {
|
||||
int offset = date.getDayOfWeek().getValue() - 1;
|
||||
return redisUtils.setBit(buildSignKey(uid, date), offset, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否签到
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return 当前的签到状态
|
||||
*/
|
||||
public boolean checkSign(String uid, LocalDate date) {
|
||||
int offset = date.getDayOfWeek().getValue() - 1;
|
||||
return redisUtils.getBit(buildSignKey(uid, date), offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户签到次数
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return 当前的签到次数
|
||||
*/
|
||||
public long getSignCount(String uid, LocalDate date) {
|
||||
return redisUtils.bitCount(buildSignKey(uid, date));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本周连续签到次数
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return 本周连续签到次数
|
||||
*/
|
||||
public long getContinuousSignCount(String uid, LocalDate date) {
|
||||
int signCount = 0;
|
||||
List<Long> list = redisUtils.bitField(buildSignKey(uid, date), date.getDayOfWeek().getValue(), 0);
|
||||
if (list != null && list.size() > 0) {
|
||||
// 取低位连续不为0的个数即为连续签到次数,需考虑当天尚未签到的情况
|
||||
long v = list.get(0) == null ? 0 : list.get(0);
|
||||
for (int i = 0; i < date.getDayOfWeek().getValue(); i++) {
|
||||
if (v >> 1 << 1 == v) {
|
||||
// 低位为0且非当天说明连续签到中断了
|
||||
if (i > 0) break;
|
||||
} else {
|
||||
signCount += 1;
|
||||
}
|
||||
v >>= 1;
|
||||
}
|
||||
}
|
||||
return signCount;
|
||||
}
|
||||
|
||||
/* *//**
|
||||
* 获取本周首次签到日期
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return 首次签到日期
|
||||
*//*
|
||||
public LocalDate getFirstSignDate(String uid, LocalDate date) {
|
||||
long pos = redisUtils.bitPos(buildSignKey(uid, date), true);
|
||||
|
||||
|
||||
}*/
|
||||
|
||||
/**
|
||||
* 获取本周签到情况
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param date 日期
|
||||
* @return Key为签到日期,Value为签到状态的Map
|
||||
*/
|
||||
public Map<Integer, Boolean> getSignInfo(String uid, LocalDate date) {
|
||||
Map<Integer, Boolean> signMap = new HashMap<>(date.getDayOfWeek().getValue());
|
||||
List<Long> list = redisUtils.bitField(buildSignKey(uid, date), date.getDayOfWeek().getValue(), 0);
|
||||
if (list != null && list.size() > 0) {
|
||||
// 由低位到高位,为0表示未签,为1表示已签
|
||||
long v = list.get(0) == null ? 0 : list.get(0);
|
||||
for (int i = date.getDayOfWeek().getValue(); i > 0; i--) {
|
||||
signMap.put(i, v >> 1 << 1 != v);
|
||||
v >>= 1;
|
||||
}
|
||||
}
|
||||
return signMap;
|
||||
}
|
||||
|
||||
private static String buildSignKey(String uid, LocalDate date) {
|
||||
int year = date.getYear();
|
||||
int week = date.get(WeekFields.ISO.weekOfWeekBasedYear());
|
||||
if (date.getDayOfYear() <= 7) {
|
||||
LocalDate lastYear = LocalDate.of(year - 1, 12, 31);
|
||||
int lastYearWeek = lastYear.get(WeekFields.ISO.weekOfWeekBasedYear());
|
||||
if (lastYearWeek == week) {
|
||||
year = year - 1;
|
||||
}
|
||||
}
|
||||
return String.format("u:sign:%s:%s:%s", uid, year, week);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.oneone.common.redis.RedisConfig,\
|
||||
com.oneone.common.redis.RedisCacheConfig,\
|
||||
com.oneone.common.redis.utils.RedisUtils,\
|
||||
com.oneone.common.redis.RedissonConfig,\
|
||||
com.oneone.common.redis.captcha.CaptchaConfiguration
|
||||
|
||||
|
||||
Reference in New Issue
Block a user