Initial commit

This commit is contained in:
jackyu66git
2026-03-23 11:52:51 +08:00
commit ea8d889fbe
1114 changed files with 92438 additions and 0 deletions
@@ -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();
}
}
@@ -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;
}
}
@@ -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";
}
@@ -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);
}
}
@@ -0,0 +1,14 @@
package com.oneone.common.redis.delayqueue;
public interface BaseDelayQueueEnum {
/**
* 延迟队列 Redis Key
*/
String getCode();
/**
* 中文描述
*/
String getMsg();
}
@@ -0,0 +1,12 @@
package com.oneone.common.redis.delayqueue;
/**
* 延迟队列执行器
*/
public interface RedisDelayQueueHandler<T> {
void execute(T t);
BaseDelayQueueEnum getQueueName();
}
@@ -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;
}
@@ -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";
}
@@ -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;";
}
@@ -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;
}*/
}
@@ -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;
}
}
@@ -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() ? "√" : "-"));
}
}*/
}
}
@@ -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);
}
}
@@ -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