当前位置: 代码网 > it编程>编程语言>Java > 从SpringBoot4升级Sa-Token1.46.0报错与解决方案

从SpringBoot4升级Sa-Token1.46.0报错与解决方案

2026年09月20日 Java 我要评论
前言前段时间 sa-token 发布了 1.46.0 版本,看了官网的更新日志后,发现优化了一些问题和增加了很多的新功能,果断想更新到最新版本。可是更新版本号后启动项目访问却出现了几个问题,这个记录一

前言

前段时间 sa-token 发布了 1.46.0 版本,看了官网的更新日志后,发现优化了一些问题和增加了很多的新功能,果断想更新到最新版本。可是更新版本号后启动项目访问却出现了几个问题,这个记录一下,帮助大家少踩一点坑。

1、keepttl 的问题

redis 6.0 开始,新增了 keepttl 的支持,当你更新 redis 里面的数据时,可以保持原有数据的 ttl 的值,属于原子操作,不需要你先去获取 ttl ,再设置 ttl。

sa-token 中的代码是这样写的:

/**
 * set key value xx keepttl:仅 key 存在时覆写 value,并保留原 ttl
 */
public void setstringandkeepttl(string finalkey, string value) {
	stringredistemplate.execute((rediscallback<boolean>) connection ->
		connection.set(
			stringredistemplate.getstringserializer().serialize(finalkey),
			stringredistemplate.getstringserializer().serialize(value),
			expiration.keepttl(),
			redisstringcommands.setoption.ifpresent()
		)
	);
}

启动代码,进行登录时却报了以下的错误:

caused by: org.redisson.client.redisexception: err invalid expire time in 'set' command. channel: [id: 0x741630d3, l:/127.0.0.1:57730 - r:127.0.0.1/127.0.0.1:6379] command: (set), params: [[65, 117, 116, 104, 111, 114, 105, 122, 97, 116, ...], [123, 34, 64, 99, 108, 97, 115, 115, 34, 58, ...], px, -2000, xx], promise: java.util.concurrent.completablefuture@1ce80367[not completed, 1 dependents]
	at org.redisson.client.handler.commanddecoder.decode(commanddecoder.java:439)
	at org.redisson.client.handler.commanddecoder.decodecommand(commanddecoder.java:220)
	at org.redisson.client.handler.commanddecoder.decode(commanddecoder.java:148)
	at org.redisson.client.handler.commanddecoder.decode(commanddecoder.java:124)
	at io.netty.handler.codec.bytetomessagedecoder.decoderemovalreentryprotection(bytetomessagedecoder.java:545)
	at io.netty.handler.codec.replayingdecoder.calldecode(replayingdecoder.java:366)
	at io.netty.handler.codec.bytetomessagedecoder.channelread(bytetomessagedecoder.java:296)
	at io.netty.channel.abstractchannelhandlercontext.firechannelread(abstractchannelhandlercontext.java:357)
	at io.netty.channel.defaultchannelpipeline$headcontext.channelread(defaultchannelpipeline.java:1429)
	at io.netty.channel.defaultchannelpipeline.firechannelread(defaultchannelpipeline.java:918)
	at io.netty.channel.nio.abstractniobytechannel$niobyteunsafe.read(abstractniobytechannel.java:176)
	at io.netty.channel.nio.abstractniochannel$abstractniounsafe.handle(abstractniochannel.java:445)
	at io.netty.channel.nio.nioiohandler$defaultnioregistration.handle(nioiohandler.java:388)
	at io.netty.channel.nio.nioiohandler.processselectedkey(nioiohandler.java:596)
	at io.netty.channel.nio.nioiohandler.processselectedkeysoptimized(nioiohandler.java:571)
	at io.netty.channel.nio.nioiohandler.processselectedkeys(nioiohandler.java:512)
	at io.netty.channel.nio.nioiohandler.run(nioiohandler.java:484)
	at io.netty.channel.singlethreadioeventloop.runio(singlethreadioeventloop.java:225)
	at io.netty.channel.singlethreadioeventloop.run(singlethreadioeventloop.java:196)
	at io.netty.util.concurrent.singlethreadeventexecutor$5.run(singlethreadeventexecutor.java:1195)
	at io.netty.util.internal.threadexecutormap$2.run(threadexecutormap.java:74)
	at io.netty.util.concurrent.fastthreadlocalrunnable.run(fastthreadlocalrunnable.java:30)
	... 1 common frames omitted

大致意思是,设置的 ttl 值为 -2000,redis 服务那边不支持设置负数的 ttl,所以报错了。当我进行排查后,就发现了,springboot 4.1.0 版本中对这种写法已经标注过时了,我觉得就是过时的 api 导致计算出来的 ttl 不对。官网给出了解决方案:

https://sa-token.com/more/common-questions.html#q:redis-6.0-以下版本集成报错:err-syntax-error

给出了2种解决方案:

  1. 升级 redis 服务到 6.0 以上

我的 redis 服务版本为 8.0.3,所以是支持 keepttl 的。

  1. 重写 update 方法
@configuration
public class satokendaoconfig {
	@bean
	@primary
	public satokendao satokendao() {
		return new satokendaoforredistemplate() {
			@override
			public void update(string key, string value) {
				string finalkey = wrapkey(key);
				long expirems = stringredistemplate.getexpire(finalkey, timeunit.milliseconds);
				// -2 = 无此键
				if (expirems == satokendao.not_value_expire) {
					return;
				}
				// -1 = 永不过期
				if (expirems == satokendao.never_expire) {
					stringredistemplate.opsforvalue().set(finalkey, value);
				} else {
					stringredistemplate.opsforvalue().set(finalkey, value, expirems, timeunit.milliseconds);
				}
			}
		};
	}
}

重写的思路大致就是需要自己先获取 ttl 的值,再决定如何去更新数据。但是这样就变成了不是原子操作了,我给出另一种写法如下:

@configuration
public class satokendaoconfig {
    @bean
    @primary
    public satokendao satokendao() {
        return new satokendaoforredistemplate() {
            @override
            public void update(string key, string value) {
                string finalkey = wrapkey(key);
                stringredistemplate.opsforvalue().setifpresent(finalkey, value, expiration.keepttl());
            }
        };
    }
}

2、json 反序列化的问题

官网中说引入了 sa-token-spring-boot4-starter 会自动引入 sa-token-jackson3 作为默认 json 方案。当我往 redis 中写入登录用户的个人信息对象后,取出来的时候却报了以下的错误:

caused by: tools.jackson.databind.exc.invalidtypeidexception: could not resolve type id 'com.biz.common.vo.resp.loginuserrespvo' as a subtype of `java.lang.object`: configured `polymorphictypevalidator` (of type `tools.jackson.databind.jsontype.basicpolymorphictypevalidator`) denied resolution
 at [source: redacted (`streamreadfeature.include_source_in_location` disabled); byte offset: #unknown] (through reference chain: cn.dev33.satoken.session.sasession["datamap"]->java.util.concurrent.concurrenthashmap["user_info"])
	at tools.jackson.databind.exc.invalidtypeidexception.from(invalidtypeidexception.java:41)
	at tools.jackson.databind.deserializationcontext.invalidtypeidexception(deserializationcontext.java:2143)
	at tools.jackson.databind.databindcontext._throwsubtypeclassnotallowed(databindcontext.java:348)
	at tools.jackson.databind.databindcontext.resolveandvalidatesubtype(databindcontext.java:240)
	at tools.jackson.databind.jsontype.impl.classnameidresolver._typefromid(classnameidresolver.java:87)
	at tools.jackson.databind.jsontype.impl.classnameidresolver.typefromid(classnameidresolver.java:70)
	at tools.jackson.databind.jsontype.impl.typedeserializerbase._finddeserializer(typedeserializerbase.java:154)
	at tools.jackson.databind.jsontype.impl.aspropertytypedeserializer._deserializetypedforid(aspropertytypedeserializer.java:119)
	at tools.jackson.databind.jsontype.impl.aspropertytypedeserializer.deserializetypedfromobject(aspropertytypedeserializer.java:103)
	at tools.jackson.databind.jsontype.impl.aspropertytypedeserializer.deserializetypedfromany(aspropertytypedeserializer.java:203)
	at tools.jackson.databind.deser.jdk.untypedobjectdeserializernr.deserializewithtype(untypedobjectdeserializernr.java:98)
	at tools.jackson.databind.deser.jdk.mapdeserializer._deserializenonullchecks(mapdeserializer.java:885)
	at tools.jackson.databind.deser.jdk.mapdeserializer._readandbindstringkeymap(mapdeserializer.java:606)
	at tools.jackson.databind.deser.jdk.mapdeserializer.deserialize(mapdeserializer.java:428)
	at tools.jackson.databind.deser.jdk.mapdeserializer.deserialize(mapdeserializer.java:30)
	at tools.jackson.databind.jsontype.impl.aspropertytypedeserializer._deserializetypedforid(aspropertytypedeserializer.java:138)
	at tools.jackson.databind.jsontype.impl.aspropertytypedeserializer.deserializetypedfromobject(aspropertytypedeserializer.java:103)
	at tools.jackson.databind.deser.jdk.mapdeserializer.deserializewithtype(mapdeserializer.java:471)
	at tools.jackson.databind.deser.impl.methodproperty.deserializeandset(methodproperty.java:129)
	at tools.jackson.databind.deser.bean.beandeserializer.deserializefromobject(beandeserializer.java:659)
	at tools.jackson.databind.deser.bean.beandeserializer._deserializeother(beandeserializer.java:235)
	at tools.jackson.databind.deser.bean.beandeserializer.deserialize(beandeserializer.java:202)
	at tools.jackson.databind.jsontype.impl.aspropertytypedeserializer._deserializetypedforid(aspropertytypedeserializer.java:138)
	at tools.jackson.databind.jsontype.impl.aspropertytypedeserializer.deserializetypedfromobject(aspropertytypedeserializer.java:103)
	at tools.jackson.databind.deser.bean.beandeserializerbase.deserializewithtype(beandeserializerbase.java:1395)
	at tools.jackson.databind.deser.impl.typewrappeddeserializer.deserialize(typewrappeddeserializer.java:72)
	at tools.jackson.databind.deser.deserializationcontextext.readrootvalue(deserializationcontextext.java:266)
	at tools.jackson.databind.objectmapper._readmapandclose(objectmapper.java:2639)
	at tools.jackson.databind.objectmapper.readvalue(objectmapper.java:1544)
	at cn.dev33.satoken.json.sajsontemplateforjackson3.jsontoobject(sajsontemplateforjackson3.java:96)
	... 92 common frames omitted

官网给出了解决方案:

https://sa-token.com/plugin/json-extend.html#json-全局类型白名单机制

注册 json 全局类型白名单的几种方式:

  1. 实体类实现 sajsontype(推荐)

业务 model 实现标记接口 sajsontype 即可加入白名单,无需额外配置:

public class sysuser implements sajsontype {
	// ...
}
  1. 启动前调用 registerallowtype

在 json 插件完成初始化之前 注册(spring boot 请在 main 方法里、springapplication.run 之前;solon 请在 solon.start 之前):

import cn.dev33.satoken.strategy.sajsonstrategy;

public static void main(string[] args) {
	// 在项目启动前,将所有需要反序列化的 bean class 进行注册
	sajsonstrategy.instance.registerallowtype(sysuser.class);

	springapplication.run(application.class, args);
}
  1. 通过 spi 文件批量声明

在 resources/meta-inf/satoken/sa-json-type.list 中按行写入完整类名(# 开头为注释):

# 允许参与多态 json 反序列化的业务类型
com.pj.model.sysuser
com.pj.model.sysrole

到此这篇关于从springboot4升级sa-token1.46.0报错与解决方案的文章就介绍到这了,更多相关springboot4升级sa-token1.46.0报错内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com