Sa-Token中SerializationException

简介: 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);
    }
}


最后成功实现

相关文章
|
自然语言处理 API Swift
Qwen1.5开源!魔搭最佳实践来啦!
近几个月来,通义千问团队一直在努力探索如何构建一个“好”的模型,同时优化开发者体验。就在刚刚,中国新年前夕,通义千问团队分享了Qwen开源系列的下一个版本,Qwen1.5。
|
9月前
|
开发工具 git 开发者
Git流程控制:远程仓库操作的实用指南
通过遵循这些步骤和策略,你将能够更有效地与远程仓库进行交互,确保代码变更的透明度和项目历史的干净。同时,良好的版本控制习惯可以减少潜在的冲突,并帮助保持代码库的整洁。在日常工作中应用这些实用的Git流程控制技巧将是非常有益的。
275 0
|
移动开发 JavaScript 应用服务中间件
Taro——H5项目如何修改静态文件入口
这里我们说两种情况,一种是在静态资源引入的时候加入前缀,另一种是真正的将静态资源输出到指定的目录下。
289 2
|
测试技术 API 数据安全/隐私保护
API 调试与管理工具选型思考:Apifox vs Apipost,企业究竟该如何抉择?
API开发管理工具选型建议:Apifox:适合个人开发者或小团队;系统需求侧重“调试”阶段;Apipost :适合需要实现 API 的全生命周期管理的各类大中型企业。
578 15
|
11月前
|
API
揭秘电商API接口:商品订单支付全解析
电商API接口种类繁多,涵盖商品、订单、支付、营销、用户等多方面。常见的有:商品相关(如item_get获取详情、item_search搜索商品)、订单相关(如buyer_order_list获取订单列表)、支付接口(处理支付请求)、营销相关(优惠券、促销活动接口)、用户相关(用户信息、授权、地址管理)及其他常用接口(评价、上下架、购物车管理等)。不同平台提供的API可能有所差异,需参考具体文档。
404 0
|
人工智能 缓存 自然语言处理
阿里云 × 天润融通:基于智能体的企业营销与客户服务实践分享
本次分享由阿里云与天润融通联合呈现,主题为“基于智能体的企业营销与客户服务实践”。主讲人安静波(北京天润融通科技股份有限公司CTO)将介绍天润融通的智能体平台架构及其在企业营销和客服场景中的应用。内容涵盖天润融通的发展历程、基于阿里云的AICC架构、智能体平台的技术细节及优化实践,并通过客户案例展示如何通过智能体提升营销转化率和客户满意度。重点探讨了智能体在实时响应、打断处理等方面的优化措施,以及大模型的应用经验。
1122 0
[simulink] --- simulink信号
[simulink] --- simulink信号
658 0
|
JSON 数据格式
Echarts设置背景的网格线为虚线
Echarts设置背景的网格线为虚线
1067 0