有两件事我最憎恶:没有信仰的博才多学和充满信仰的愚昧无知。——爱默生
使用了jdk
默认序列化方式后报错,清除缓存后又换成了jackson
序列化方式
结果还是报错SerializationException
,提示我LocalDateTime
没有默认构造器
既然我项目中mvc
使用的fastJson
配置过LocalDateTime
的转换,那我就继续用fastJson
进行拓展吧:
首先是配置FastJson
package com.ruben.xchat.config; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializeConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.ruben.xchat.handler.GlobalTimeResolver; import com.ruben.xchat.utils.Opt; import java.time.LocalDateTime; /** * FastJson配置 * * @author <achao1441470436@gmail.com> * @since 2021/10/9 12:42 */ public class FastJsonConfigHandler { /** * 配置 */ private static final FastJsonConfig FAST_JSON_CONFIG; static { FAST_JSON_CONFIG = new FastJsonConfig(); // 配置序列化策略 // ID_WORKER 生成主键太长导致 js 精度丢失 // JavaScript 无法处理 Java 的长整型 Long 导致精度丢失,具体表现为主键最后两位永远为 0,解决思路: Long 转为 String 返回 FAST_JSON_CONFIG.setSerializerFeatures(SerializerFeature.BrowserCompatible, // 处理序列化后出现$ref的坑 SerializerFeature.DisableCircularReferenceDetect, // 列化枚举值为数据库存储值 SerializerFeature.WriteEnumUsingToString); SerializeConfig serializeConfig = SerializeConfig.globalInstance; // 设置全局LocalDateTime转换 serializeConfig.put(LocalDateTime.class, (serializer, object, fieldName, fieldType, features) -> Opt.ofNullable(object).ifPresentOrElse(obj -> serializer.out.writeString(((LocalDateTime) obj).format(GlobalTimeResolver.DATE_TIME_FORMATTER)), serializer.out::writeNull)); FAST_JSON_CONFIG.setSerializeConfig(serializeConfig); ParserConfig parserConfig = ParserConfig.getGlobalInstance(); FAST_JSON_CONFIG.setParserConfig(parserConfig); } private FastJsonConfigHandler() { } /** * 获取配置 * * @return com.alibaba.fastjson.support.config.FastJsonConfig * @author <achao1441470436@gmail.com> * @since 2021/10/9 12:46 */ public static FastJsonConfig getConfig() { return FAST_JSON_CONFIG; } }
然后是注入SaTokenDao
的实现:
package com.ruben.xchat.manager; import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.util.SaFoxUtil; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer; import com.ruben.xchat.config.FastJsonConfigHandler; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; /** * Sa-Token持久层接口 [Redis版] (使用 fastJson 序列化方式) * * @author VampireAchao */ @Component public class SaTokenDaoRedisFastJson implements SaTokenDao { /** * key序列化 */ public static final StringRedisSerializer KEY_SERIALIZER; /** * value序列化 */ public static final FastJsonRedisSerializer<Object> VALUE_SERIALIZER; /** * String专用 */ public StringRedisTemplate stringRedisTemplate; /** * Object专用 */ public RedisTemplate<String, Object> objectRedisTemplate; /** * 标记:是否已初始化成功 */ public boolean isInit; static { // 指定相应的序列化方案 KEY_SERIALIZER = new StringRedisSerializer(); VALUE_SERIALIZER = new FastJsonRedisSerializer<>(Object.class); VALUE_SERIALIZER.setFastJsonConfig(FastJsonConfigHandler.getConfig()); } @Resource public void init(RedisConnectionFactory connectionFactory) { // 构建StringRedisTemplate StringRedisTemplate stringTemplate = new StringRedisTemplate(); stringTemplate.setConnectionFactory(connectionFactory); stringTemplate.afterPropertiesSet(); // 构建RedisTemplate RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); template.setKeySerializer(KEY_SERIALIZER); template.setHashKeySerializer(KEY_SERIALIZER); template.setValueSerializer(VALUE_SERIALIZER); template.setHashValueSerializer(VALUE_SERIALIZER); template.afterPropertiesSet(); // 开始初始化相关组件 if (!this.isInit) { this.stringRedisTemplate = stringTemplate; this.objectRedisTemplate = template; this.isInit = true; } } /** * 获取Value,如无返空 */ @Override public String get(String key) { return stringRedisTemplate.opsForValue().get(key); } /** * 写入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) { stringRedisTemplate.opsForValue().set(key, value); } else { stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); } } /** * 修修改指定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) { stringRedisTemplate.delete(key); } /** * 获取Value的剩余存活时间 (单位: 秒) */ @Override public long getTimeout(String key) { return Optional.ofNullable(stringRedisTemplate).map(t -> t.getExpire(key)).orElseThrow(IllegalStateException::new); } /** * 修改Value的剩余存活时间 (单位: 秒) */ @Override public void updateTimeout(String key, long timeout) { // 判断是否想要设置为永久 if (timeout == SaTokenDao.NEVER_EXPIRE) { long expire = getTimeout(key); if (expire == SaTokenDao.NEVER_EXPIRE) { // 如果其已经被设置为永久,则不作任何处理 return; } // 如果尚未被设置为永久,那么再次set一次 this.set(key, this.get(key), timeout); return; } stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); } /** * 获取Object,如无返空 */ @Override public Object getObject(String key) { return objectRedisTemplate.opsForValue().get(key); } /** * 写入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) { objectRedisTemplate.opsForValue().set(key, object); } else { objectRedisTemplate.opsForValue().set(key, object, timeout, TimeUnit.SECONDS); } } /** * 更新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) { objectRedisTemplate.delete(key); } /** * 获取Object的剩余存活时间 (单位: 秒) */ @Override public long getObjectTimeout(String key) { return Optional.ofNullable(objectRedisTemplate).map(t -> t.getExpire(key)).orElseThrow(IllegalStateException::new); } /** * 修改Object的剩余存活时间 (单位: 秒) */ @Override public void updateObjectTimeout(String key, long timeout) { // 判断是否想要设置为永久 if (timeout == SaTokenDao.NEVER_EXPIRE) { long expire = getObjectTimeout(key); if (expire == SaTokenDao.NEVER_EXPIRE) { // 如果其已经被设置为永久,则不作任何处理 return; } // 如果尚未被设置为永久,那么再次set一次 this.setObject(key, this.getObject(key), timeout); return; } objectRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); } /** * 获取Session,如无返空 * * @param sessionId sessionId * @return SaSession */ @Override public SaSession getSession(String sessionId) { return Optional.ofNullable(getObject(sessionId)).map(o -> ((JSONObject) o).toJavaObject(SaSession.class)).orElse(null); } /** * 搜索数据 */ @Override public List<String> searchData(String prefix, String keyword, int start, int size) { Set<String> keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*"); return Optional.ofNullable(keys).map(ArrayList::new).map(list -> SaFoxUtil.searchList(list, start, size)).orElseGet(ArrayList::new); } }
最后成功实现