Sa-Token中SerializationException

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Sa-Token中SerializationException

有两件事我最憎恶:没有信仰的博才多学和充满信仰的愚昧无知。——爱默生

今天把Sa-Token中的用户状态进行持久层扩展

使用了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);
    }
}


最后成功实现

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
前端开发
【Sa-Token】7、Sa-Token抛出的异常统一处理
在 Sa-Token 的登录,授权,验证过程中,会抛出很多的异常,我们不能将这些异常信息直接返回给用户,因为用户是看不懂这些异常信息的,我们就需要对这些异常信息进行处理,处理之后再返回展示给前端用户
1342 0
|
缓存 NoSQL 中间件
【Sa-Token】6、Sa-Token集成Redis
Sa-Token 支持 Redis、Memcached 等专业的缓存中间件中, 做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性
1735 0
|
4月前
|
API
【Azure Developer】记录一段验证AAD JWT Token时需要设置代理获取openid-configuration内容
【Azure Developer】记录一段验证AAD JWT Token时需要设置代理获取openid-configuration内容
|
6月前
|
人工智能 自然语言处理 区块链
什么是token?3分钟带你看懂
`Token`在人工智能领域指的是文本处理的最小单元,用于大语言模型如LLM,它可以是单词、字母等。在模型运作中,输入的文本被转化为tokens,模型通过分析上下文tokens预测并生成输出。模型的上下文(窗口)长度限制了处理的token数量,影响性能和用户体验。此外,`token`也与收费计量单位相关,大模型服务商常按token量计费。同时,`AI token`在某些场景下代表代币,用于应用程序交易、服务和投资,有时扮演加密货币角色。Token在人机交流中起到桥梁作用,促进了通用人工智能的普及和发展。
|
5月前
|
安全 NoSQL Java
JWT和Security 登录权限判断和token访问和让token失效
JWT和Security 登录权限判断和token访问和让token失效
|
Linux 数据安全/隐私保护
Token如何生成?
Token如何生成?
355 0
|
存储 安全 前端开发
token详解
token详解
3297 0
|
缓存 前端开发 算法
深入理解token
Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位
696 0
|
前端开发 算法 网络安全
深入理解Token
深入理解Token
1132 0
深入理解Token