Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id ‘sys
完整异常:Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id ‘sys:role:add’ as a subtype of : no such class foundat [Source: (byte[])“[“sys:role:add”,“sys:permi
完整异常:
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id ‘sys:role:add’ as a subtype of 'java.lang.Object'
: no such class found
at [Source: (byte[])“[“sys:role:add”,“sys:permission:delete”,“sys:log:list”,“sys:log:delete”,“sys:permission:list”,“sys:permission:update”,“sys:permission:add”,“sys:role:list”,“sys:user:add”,“sys:role:detail”,“sys:role:delete”,“sys:role:update”,“sys:user:delete”,“sys:user:update”,“sys:user:role:update”,“sys:user:list”,“ROLE_超级管理员”]”; line: 1, col`
很久之前写的代码的异常了,不过一直懒得管,今天尝试着手解决下。根据报错很明显是序列化的相关问题,报错说没有对应 sys:role:add 的这个对象无法进行转换。
这里代码的主要功能是整合Spring Security做了个权限校验,用户登录会去Redis中查询自己拥有的角色,并将拥有的权限返回,如果redis中没有就去数据库中查询,并保存到Redis中。看看涉及到的相关代码:
使用的序列化依赖 (Jackson):
<!--jackson相关注解,实现日期格式转换和类型格式转换并序列化等-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
@Cache
注解缓存配置的相关代码:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author shenyang
* @version 1.0
* @Date 2024/1/4 19:42
*/
@Configuration
@EnableCaching
public class CacheConfig {
/**
* 配置 cacheManager 代替默认的 cacheManager (缓存管理器)
*
* @param factory RedisConnectionFactory
* @return CacheManager
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
// Jackson2JsonRedisSerializer<String> stringSerializer = new Jackson2JsonRedisSerializer<>(String.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//仅仅序列化对象的属性,且属性不可为final修饰
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
serializer.setObjectMapper(objectMapper);
// 配置key value序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
//为了解决权限数组中的元素转换成String类型
// .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(stringSerializer))
//关闭控制存储
.disableCachingNullValues()
//修改前缀与key的间隔符号,默认是::
.computePrefixWith(cacheName -> cacheName + ":");
//设置特有的Redis配置
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
//定制化的Cache 设置过期时间 eg:以role:开头的缓存存活时间为10s
// cacheConfigurations.put("role",customRedisCacheConfiguration(config,Duration.ofSeconds(10)));
// cacheConfigurations.put("stock",customRedisCacheConfiguration(config,Duration.ofSeconds(3000)));
// cacheConfigurations.put("market",customRedisCacheConfiguration(config,Duration.ofSeconds(300)));
//构建redis缓存管理器
//设置过期时间
return RedisCacheManager.builder(factory)
//Cache事务支持
.transactionAware()
.withInitialCacheConfigurations(cacheConfigurations)
.cacheDefaults(config)
.build();
}
/**
* 设置RedisConfiguration配置
*
* @param config
* @param ttl
* @return
*/
public RedisCacheConfiguration customRedisCacheConfiguration(RedisCacheConfiguration config, Duration ttl) {
//设置缓存缺省超时时间
return config.entryTtl(ttl);
}
}
查询权限缓存业务相关代码(这里使用的是Redis做缓存):
import com.google.common.base.Strings;
import com.shen.stock.constant.StockConstant;
import com.shen.stock.face.PermissionCacheFace;
import com.shen.stock.mapper.SysPermissionMapper;
import com.shen.stock.mapper.SysRoleMapper;
import com.shen.stock.pojo.entity.SysPermission;
import com.shen.stock.pojo.entity.SysRole;
import com.shen.stock.service.PermissionService;
import com.shen.stock.vo.resp.MenusRespVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author shenyang
* @version 1.0
* @Date 2024/1/13 19:03
*/
@Component
@CacheConfig(cacheNames = StockConstant.Permission)
public class PermissionCacheFaceImpl implements PermissionCacheFace {
@Autowired
private SysPermissionMapper sysPermissionMapper;
@Autowired
private SysRoleMapper sysRoleMapper;
@Autowired
private PermissionService permissionService;
/**
* 缓存用户权限信息
*
* @param userId 用户id
* @return List<String> 用户的SpringSecurity的权限标识
*/
@Override
@Cacheable(key = "#root.method.getName()+(':')+(#userId)")
public String[] getPermsAndRoles(List<SysPermission> permissionList,String userId) {
//获取权限集合
// List<SysPermission> permissionList = sysPermissionMapper.findSysPermissionAll(userId);
List<String> permsNameList = permissionList.stream().filter(item -> !Strings.isNullOrEmpty(item.getPerms())).map(item -> item.getPerms())
.distinct()
.collect(Collectors.toList());
//获取角色集合 基于角色鉴权注解需要将角色前追加ROLE_
List<SysRole> roleList = sysRoleMapper.getRoleByUserId(Long.valueOf(userId));
List<String> roleNameList = roleList.stream().filter(item -> !Strings.isNullOrEmpty(item.getName()))
.map(item -> "ROLE_" + item.getName()).collect(Collectors.toList());
List<String> auths = new ArrayList<>();
auths.addAll(permsNameList);
auths.addAll(roleNameList);
String[] psArray = auths.toArray(new String[permsNameList.size()+roleNameList.size()]);
return psArray;
}
@Override
// @CacheEvict(key = "'getPermsAndRoles'+(':')+(#userId)")
public void updateUserPermsAndRoles(String userId) {
}
/**
* 缓存侧边栏
*
* @param permissions 权限集合
* @return 侧边栏权限树
*/
@Override
@Cacheable(key = "#root.method.getName()+(':')+(#userId)")
public List<MenusRespVo> getMenuTreeNodes(List<SysPermission> permissions,String userId) {
return permissionService.getTree(permissions, 0L, true);
}
/**
* 更新缓存侧边栏
*/
@Override
// @CacheEvict(key = "'getMenuTreeNodes'+(':')+(#userId)")
public void updateUserMenuTreeNodes(String userId) {
}
/**
* 缓存用户关联的权限按钮集合
* @param permissionList
* @return
*/
@Override
@Cacheable(key = "#root.method.getName()+(':')+(#userId)")
public List<String> getBtnPerms(List<SysPermission> permissionList,String userId) {
if (!CollectionUtils.isEmpty(permissionList)) {
return permissionList.stream().filter(per -> !Strings.isNullOrEmpty(per.getCode()) && per.getType() == 3)
.map(per -> per.getCode()).distinct().collect(Collectors.toList());
}
return null;
}
}
上述 getBtnPerms
、getMenuTreeNodes
在被调用时都没有出现问题,只有getPermsAndRoles
方法出现报错。
为了更好的查看问题的原因,去redis中看看数据的保存情况。
getBtnPerms
:
getMenuTreeNodes
:
getPermsAndRoles
:
当时乍一看没有发现有问题啊,没有乱码,好像没有出错啊!
但是,回忆一下报错,Could not resolve type id 'sys:role:add' as a subtype of 'java.lang.Object'
, sys:role:add
无法转换成对象。它为什么会把 sys:role:add
当成对象类型呢? 再看一下上面的几个截图,最终发现了问题的原因:
可以看到在Jackson
在序列化时,除了保存应有的数据,还保存了一个对应的类型。但是 getPermsAndRoles
方法并没有保存,所以字符串数组的第一个值被当成了数据的类型。
上述的两个返回结果为List的方法可以被正常序列化,但是 getPermsAndRoles
的返回值类型是字符串数组所以出现问题。
问题的原因发现了,那么就很容易解决了。
既然 String[]
不行,我们使用List<String>
作为返回值。
Spring Security中AuthorityUtils.createAuthorityList
中需要String[]
,我们将强转的逻辑放在调用方处就好了。
String[] psArray = permissionCacheFace.getPermsAndRoles(permissions, dbUser.getId().toString()).toArray(new String[0]);
List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(psArray);
好了,问题到这里就解决了,在工作过程中遇到问题一定不要着急,可以慢慢从各种角度分析。问题总会被解决的。

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)