diff --git a/sa-token-dependencies/pom.xml b/sa-token-dependencies/pom.xml index 6eda12cd..72a52509 100644 --- a/sa-token-dependencies/pom.xml +++ b/sa-token-dependencies/pom.xml @@ -34,6 +34,7 @@ 0.9.1 1.2.83 2.0.15 + 3.19.0 @@ -129,6 +130,13 @@ spring-boot-starter-data-redis ${springboot.version} + + + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + @@ -192,6 +200,13 @@ spring-boot-starter-aop ${springboot.version} + + + + org.springframework.boot + spring-boot-starter-actuator + ${springboot.version} + diff --git a/sa-token-doc/plugin/dao-extend.md b/sa-token-doc/plugin/dao-extend.md index 0dc0750c..8211ecb1 100644 --- a/sa-token-doc/plugin/dao-extend.md +++ b/sa-token-doc/plugin/dao-extend.md @@ -12,6 +12,7 @@ - sa-token-dao-redisx:Redisx 集成包。 - sa-token-dao-redis-fastjson:Redis集成包,使用 fastjson 序列化方式。 - sa-token-dao-redis-fastjson2:Redis集成包,使用 fastjson2 序列化方式。 +- sa-token-dao-redisson-jackson:Redis集成包,Redisson客户端使用,jackson 序列化方式。 有关 Redis 集成,详细参考:[集成Redis](/up/integ-redis),更多存储方式欢迎提交PR diff --git a/sa-token-plugin/pom.xml b/sa-token-plugin/pom.xml index 71e22215..80213083 100644 --- a/sa-token-plugin/pom.xml +++ b/sa-token-plugin/pom.xml @@ -22,6 +22,7 @@ sa-token-dao-redis-jackson sa-token-dao-redis-fastjson sa-token-dao-redis-fastjson2 + sa-token-dao-redisson-jackson sa-token-dao-redisx sa-token-alone-redis sa-token-dialect-thymeleaf diff --git a/sa-token-plugin/sa-token-dao-redisson-jackson/.gitignore b/sa-token-plugin/sa-token-dao-redisson-jackson/.gitignore new file mode 100644 index 00000000..8122f47c --- /dev/null +++ b/sa-token-plugin/sa-token-dao-redisson-jackson/.gitignore @@ -0,0 +1,13 @@ +target/ + +node_modules/ +bin/ +.settings/ +unpackage/ +.classpath +.project + +.factorypath + +.idea/ +.iml \ No newline at end of file diff --git a/sa-token-plugin/sa-token-dao-redisson-jackson/pom.xml b/sa-token-plugin/sa-token-dao-redisson-jackson/pom.xml new file mode 100644 index 00000000..568c020e --- /dev/null +++ b/sa-token-plugin/sa-token-dao-redisson-jackson/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + + cn.dev33 + sa-token-plugin + ${revision} + ../pom.xml + + jar + + sa-token-dao-redisson-jackson + sa-token-dao-redisson-jackson + sa-token integrate redisson (to jackson) + + + + + cn.dev33 + sa-token-core + + + + org.redisson + redisson-spring-boot-starter + + + + + com.fasterxml.jackson.core + jackson-databind + true + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + true + + + + + + + diff --git a/sa-token-plugin/sa-token-dao-redisson-jackson/src/main/java/cn/dev33/satoken/dao/SaSessionForJacksonCustomized.java b/sa-token-plugin/sa-token-dao-redisson-jackson/src/main/java/cn/dev33/satoken/dao/SaSessionForJacksonCustomized.java new file mode 100644 index 00000000..d4505508 --- /dev/null +++ b/sa-token-plugin/sa-token-dao-redisson-jackson/src/main/java/cn/dev33/satoken/dao/SaSessionForJacksonCustomized.java @@ -0,0 +1,32 @@ +package cn.dev33.satoken.dao; + +import cn.dev33.satoken.session.SaSession; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Jackson定制版SaSession,忽略 timeout 等属性的序列化 + * + * @author kong + * + */ +@JsonIgnoreProperties({"timeout"}) +public class SaSessionForJacksonCustomized extends SaSession { + + /** + * + */ + private static final long serialVersionUID = -7600983549653130681L; + + public SaSessionForJacksonCustomized() { + super(); + } + + /** + * 构建一个Session对象 + * @param id Session的id + */ + public SaSessionForJacksonCustomized(String id) { + super(id); + } + +} diff --git a/sa-token-plugin/sa-token-dao-redisson-jackson/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedissonJackson.java b/sa-token-plugin/sa-token-dao-redisson-jackson/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedissonJackson.java new file mode 100644 index 00000000..652e4571 --- /dev/null +++ b/sa-token-plugin/sa-token-dao-redisson-jackson/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedissonJackson.java @@ -0,0 +1,289 @@ +package cn.dev33.satoken.dao; + +import cn.dev33.satoken.strategy.SaStrategy; +import cn.dev33.satoken.util.SaFoxUtil; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import org.redisson.api.RBatch; +import org.redisson.api.RBucket; +import org.redisson.api.RBucketAsync; +import org.redisson.api.RedissonClient; +import org.redisson.client.codec.Codec; +import org.redisson.codec.JsonJacksonCodec; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Field; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Sa-Token 持久层实现 [Redisson客户端、Redis存储、Jackson序列化] + * + * @author 疯狂的狮子Li + * + */ +@Component +public class SaTokenDaoRedissonJackson implements SaTokenDao { + + public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + public static final String DATE_PATTERN = "yyyy-MM-dd"; + public static final String TIME_PATTERN = "HH:mm:ss"; + public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN); + public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN); + public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(TIME_PATTERN); + + /** + * ObjectMapper对象 (以public作用域暴露出此对象,方便开发者二次更改配置) + */ + public ObjectMapper objectMapper; + + /** + * 序列化方式 + */ + public Codec codec; + + /** + * redisson 客户端 + */ + public RedissonClient redissonClient; + + /** + * 标记:是否已初始化成功 + */ + public boolean isInit; + + @Autowired + public void init(RedissonClient redissonClient) { + // 不重复初始化 + if(this.isInit) { + return; + } + + // 指定相应的序列化方案 + GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(); + // 通过反射获取Mapper对象, 增加一些配置, 增强兼容性 + try { + Field field = GenericJackson2JsonRedisSerializer.class.getDeclaredField("mapper"); + field.setAccessible(true); + ObjectMapper objectMapper = (ObjectMapper) field.get(valueSerializer); + this.objectMapper = objectMapper; + // 配置[忽略未知字段] + this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + // 配置[时间类型转换] + JavaTimeModule timeModule = new JavaTimeModule(); + // LocalDateTime序列化与反序列化 + timeModule.addSerializer(new LocalDateTimeSerializer(DATE_TIME_FORMATTER)); + timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER)); + // LocalDate序列化与反序列化 + timeModule.addSerializer(new LocalDateSerializer(DATE_FORMATTER)); + timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER)); + // LocalTime序列化与反序列化 + timeModule.addSerializer(new LocalTimeSerializer(TIME_FORMATTER)); + timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(TIME_FORMATTER)); + this.objectMapper.registerModule(timeModule); + // 重写 SaSession 生成策略 + SaStrategy.me.createSession = (sessionId) -> new SaSessionForJacksonCustomized(sessionId); + } catch (Exception e) { + System.err.println(e.getMessage()); + } + + // 开始初始化相关组件 + this.codec = new JsonJacksonCodec(objectMapper); + this.redissonClient = redissonClient; + this.isInit = true; + } + + + /** + * 获取Value,如无返空 + */ + @Override + public String get(String key) { + RBucket rBucket = redissonClient.getBucket(key, codec); + return rBucket.get(); + } + + /** + * 写入Value,并设定存活时间 (单位: 秒) + */ + @Override + public void set(String key, String value, long timeout) { + if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + RBucket bucket = redissonClient.getBucket(key, codec); + bucket.set(value); + } else { + RBatch batch = redissonClient.createBatch(); + RBucketAsync bucket = batch.getBucket(key, codec); + bucket.setAsync(value); + bucket.expireAsync(Duration.ofSeconds(timeout)); + batch.execute(); + } + } + + /** + * 修修改指定key-value键值对 (过期时间不变) + */ + @Override + public void update(String key, String value) { + long expire = getTimeout(key); + // -2 = 无此键 + if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + this.set(key, value, expire); + } + + /** + * 删除Value + */ + @Override + public void delete(String key) { + redissonClient.getBucket(key, codec).delete(); + } + + /** + * 获取Value的剩余存活时间 (单位: 秒) + */ + @Override + public long getTimeout(String key) { + RBucket rBucket = redissonClient.getBucket(key, codec); + long timeout = rBucket.remainTimeToLive(); + return timeout < 0 ? timeout : timeout / 1000; + } + + /** + * 修改Value的剩余存活时间 (单位: 秒) + */ + @Override + public void updateTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + long expire = getTimeout(key); + if(expire == SaTokenDao.NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.set(key, this.get(key), timeout); + } + return; + } + RBucket rBucket = redissonClient.getBucket(key, codec); + rBucket.expire(Duration.ofSeconds(timeout)); + } + + + + /** + * 获取Object,如无返空 + */ + @Override + public Object getObject(String key) { + RBucket rBucket = redissonClient.getBucket(key, codec); + return rBucket.get(); + } + + /** + * 写入Object,并设定存活时间 (单位: 秒) + */ + @Override + public void setObject(String key, Object object, long timeout) { + if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + RBucket bucket = redissonClient.getBucket(key, codec); + bucket.set(object); + } else { + RBatch batch = redissonClient.createBatch(); + RBucketAsync bucket = batch.getBucket(key, codec); + bucket.setAsync(object); + bucket.expireAsync(Duration.ofSeconds(timeout)); + batch.execute(); + } + + } + + /** + * 更新Object (过期时间不变) + */ + @Override + public void updateObject(String key, Object object) { + long expire = getObjectTimeout(key); + // -2 = 无此键 + if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + this.setObject(key, object, expire); + } + + /** + * 删除Object + */ + @Override + public void deleteObject(String key) { + redissonClient.getBucket(key, codec).delete(); + } + + /** + * 获取Object的剩余存活时间 (单位: 秒) + */ + @Override + public long getObjectTimeout(String key) { + RBucket rBucket = redissonClient.getBucket(key, codec); + long timeout = rBucket.remainTimeToLive(); + return timeout < 0 ? timeout : timeout / 1000; + } + + /** + * 修改Object的剩余存活时间 (单位: 秒) + */ + @Override + public void updateObjectTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + long expire = getObjectTimeout(key); + if(expire == SaTokenDao.NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.setObject(key, this.getObject(key), timeout); + } + return; + } + RBucket rBucket = redissonClient.getBucket(key, codec); + rBucket.expire(Duration.ofSeconds(timeout)); + } + + + /** + * 搜索数据 + */ + @Override + public List searchData(String prefix, String keyword, int start, int size, boolean sortType) { + Stream stream = redissonClient.getKeys().getKeysStreamByPattern(prefix + "*" + keyword + "*"); + List list = stream.collect(Collectors.toList()); + return SaFoxUtil.searchList(list, start, size, sortType); + } + +} diff --git a/sa-token-plugin/sa-token-dao-redisson-jackson/src/main/resources/META-INF/spring.factories b/sa-token-plugin/sa-token-dao-redisson-jackson/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..751fea99 --- /dev/null +++ b/sa-token-plugin/sa-token-dao-redisson-jackson/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.dao.SaTokenDaoRedissonJackson \ No newline at end of file