前言
前段时间 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种解决方案:
- 升级 redis 服务到 6.0 以上
我的 redis 服务版本为 8.0.3,所以是支持 keepttl 的。
- 重写 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 全局类型白名单的几种方式:
- 实体类实现 sajsontype(推荐)
业务 model 实现标记接口 sajsontype 即可加入白名单,无需额外配置:
public class sysuser implements sajsontype {
// ...
}
- 启动前调用 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);
}
- 通过 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报错内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论