SpringBoot + jackson + redis 序列化、反序列化 配置正确姿势

news2025/1/16 6:53:40

文章目录

    • 1.背景
    • 2. 原来项目配置
    • 3.正确配置
    • 4.小结

1.背景

最近项目上 使用 SpringBoot 2.7.7 + jackson + redis 框架实现将javaBean 序列化和反序列化到 redis 中。但是最近在做登陆的时候将LoginUser 序列化到redis 中没问题,不重启服务的话反序列化成对象也没有问题。但是重启服务后报错,错误信息

t [Source: (byte[])"["com.dechnic.oms.common.core.domain.model.LoginUser",{"userId":1,"deptId":1,"token":"40dda8a0-9d33-4097-90b7-fa5a5407f3d4","loginTime":"2023-02-04 16:22:08","expireTime":"2023-02-04 16:52:08","ipaddr":"127.0.0.1","permissions":["java.util.HashSet",["*:*:*"]],"sysUserVO":["com.dechnic.oms.common.core.vo.SysUserVO_$$_jvstb8_0",{"userId":1,"deptId":1,"deptName":"qqqqq","userName":"admin","nickName":"超级管理员","userType":"01","status":"0","expirationDate":"2099-12-31","roleName":"管理员","[truncated 82 bytes]; line: 1, column: 329] (through reference chain: com.dechnic.oms.common.core.domain.model.LoginUser["sysUserVO"])
	at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.deserialize(Jackson2JsonRedisSerializer.java:75)
	at org.springframework.data.redis.core.AbstractOperations.deserializeValue(AbstractOperations.java:360)
	at org.springframework.data.redis.core.AbstractOperations$ValueDeserializingRedisCallback.doInRedis(AbstractOperations.java:62)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:191)
	at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:97)
	at org.springframework.data.redis.core.DefaultValueOperations.get(DefaultValueOperations.java:54)
	at com.dechnic.oms.common.core.redis.RedisCache.getCacheObject(RedisCache.java:104)
	at com.dechnic.oms.framework.service.TokenService.verifyToken(TokenService.java:141)
	at com.dechnic.oms.framework.security.filter.JwtAuthenticationTokenFilter.doFilterInternal(JwtAuthenticationTokenFilter.java:35)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:111)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
	at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:891)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1784)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'com.dechnic.oms.common.core.vo.SysUserVO_$$_jvstb8_0' as a subtype of `com.dechnic.oms.common.core.vo.SysUserVO`: no such class found
 at [Source: (byte[])"["com.dechnic.oms.common.core.domain.model.LoginUser",{"userId":1,"deptId":1,"token":"40dda8a0-9d33-4097-90b7-fa5a5407f3d4","loginTime":"2023-02-04 16:22:08","expireTime":"2023-02-04 16:52:08","ipaddr":"127.0.0.1","permissions":["java.util.HashSet",["*:*:*"]],"sysUserVO":["com.dechnic.oms.common.core.vo.SysUserVO_$$_jvstb8_0",{"userId":1,"deptId":1,"deptName":"qqqqq","userName":"admin","nickName":"超级管理员","userType":"01","status":"0","expirationDate":"2099-12-31","roleName":"管理员","[truncated 82 bytes]; line: 1, column: 329] (through reference chain: com.dechnic.oms.common.core.domain.model.LoginUser["sysUserVO"])
	at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
	at com.fasterxml.jackson.databind.DeserializationContext.invalidTypeIdException(DeserializationContext.java:2073)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownTypeId(DeserializationContext.java:1564)
	at com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver._typeFromId(ClassNameIdResolver.java:76)
	at com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver.typeFromId(ClassNameIdResolver.java:66)
	at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._findDeserializer(TypeDeserializerBase.java:159)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:97)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromObject(AsArrayTypeDeserializer.java:61)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1292)
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:138)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:314)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:120)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromAny(AsArrayTypeDeserializer.java:71)
	at com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer$Vanilla.deserializeWithType(UntypedObjectDeserializer.java:781)
	at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74)
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3731)
	at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.deserialize(Jackson2JsonRedisSerializer.java:73)
	... 66 common frames omitted

2. 原来项目配置

RedisConfig 配置

package com.dechnic.oms.framework.config;

import com.dechnic.oms.common.utils.MapperUtils;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;

/**
 * redis配置
 * @author zhangzs
 * @date 2023/1/4 9:54
 * @Description
 */
@Configuration
@EnableCaching
public class RedisConfig  extends CachingConfigurerSupport {

    @Bean
    @SuppressWarnings(value = {"unchecked", "rawtypes"})
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        //FAIL_ON_UNKNOWN_PROPERTIES在序列化的时候,如果遇到不认识的字段的处理方式
        //默认启用特性,这意味着在遇到未知属性时抛出JsonMappingException。在引入该特性之前,这是默认的默认设置。
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        //FAIL_ON_EMPTY_BEANS决定了在没有找到类型的存取器时发生了什么(并且没有注释表明它是被序列化的)。如果启用(默认),
        // 将抛出一个异常来指明这些是非序列化类型;如果禁用了,它们将被序列化为空对象,即没有任何属性。
        //请注意,这个特性只对那些没有任何识别注释的“空”bean产生影响(如@json序列化):那些有注释的bean不会导致抛出异常。
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);

        //在序列化时,只有那些值为null或被认为为空的值的属性才不会被包含在内。
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

        //objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        //将类名称序列化到json串中,去掉会导致得出来的的是LinkedHashMap对象,直接转换实体对象会失败
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);

        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
        //是否允许单引号
        objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        objectMapper.registerModule(javaTimeModule);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        template.setValueSerializer(jackson2JsonRedisSerializer);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 缓存30分钟
        Duration duration = Duration.ofSeconds(30*60*60);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(duration);
        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }

    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... objects) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                sb.append("&");
                for (Object obj : objects) {
                    sb.append(obj.getClass().getName());
                    sb.append("&");
                    try {
                        sb.append(MapperUtils.obj2json(obj));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    sb.append("&");
                }
                return DigestUtils.sha256Hex(sb.toString());
            }
        };
    }

    @Bean("myKeyGenerator")
    @Primary
    public KeyGenerator myKeyGenerator(){
        return new KeyGenerator(){
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName()+"["+ Arrays.asList(params).toString()+"]";
            }
        };
    }

}

备注:objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
这句是将类名称序列化到json串中,去掉会导致得出来的的是LinkedHashMap对象,直接转换实体对象会失败,同时FINAL 类型的熟悉不序列化和反序列化、将最外层json 串包装成数组类型,在redis 中存的字符串最外层是 [] ,如果配置成JsonTypeInfo.As.WRAPPER_OBJECT 则最外层是{}。

LoginUser 类:

package com.dechnic.oms.common.core.domain.model;

import com.dechnic.oms.common.constant.UserStatus;
import com.dechnic.oms.common.core.vo.SysUserVO;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.bytebuddy.asm.Advice;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Set;

/**
 * @author zhangzs
 * @date 2023/1/3 17:48
 * @Description
 */
@Data
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class LoginUser  implements UserDetails{
    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 部门ID
     */
    private Long deptId;

    /**
     * 用户唯一标识
     */
    private String token;

    /**
     * 登录时间
     */
    private LocalDateTime loginTime;

    /**
     * 过期时间
     */
    private LocalDateTime expireTime;

    /**
     * 登录IP地址
     */
    private String ipaddr;

    /**
     * 登录地点
     */
    private String loginLocation;

    /**
     * 权限列表
     */
    private Set<String> permissions;


    private SysUserVO sysUserVO;


    public LoginUser(SysUserVO user, Set<String> permissions){
        this.userId = user.getUserId();
        this.deptId = user.getDeptId();
        this.sysUserVO = user;
        this.permissions = permissions;
    }


    @JsonIgnore
    @Override
    public String getPassword()
    {
        return sysUserVO.getPassword();
    }

    @Override
    public String getUsername()
    {
        return sysUserVO.getUserName();
    }

    /**
     * 账户是否未过期,过期无法验证
     */
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired()
    {
        return LocalDate.now().isBefore(sysUserVO.getExpirationDate());
    }

    /**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     *
     * @return
     */
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked()
    {
        return sysUserVO.getStatus().equals(UserStatus.OK.getCode());
    }

    /**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     *
     * @return
     */
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired()
    {
        return true;
    }

    /**
     * 是否可用 ,禁用的用户不能身份验证
     *
     * @return
     */
    @JsonIgnore
    @Override
    public boolean isEnabled()
    {
        return true;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
        return null;
    }
}

loginUser 里面封装了一个负责对象SysUserVO

package com.dechnic.oms.common.core.vo;


import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.WRAPPER_ARRAY;

/**
 * @ desc:
 * @ Author:  houqida
 * @ Date  :  2023/1/7
 */
@ApiModel("用户查询VO")
@Data
public class SysUserVO extends BaseVO {
    private static final long serialVersionUID = -8177563824574495L;
    /**
     * 用户ID
     */
    @ApiModelProperty("用户ID")
    private Long userId;

    /**
     * 所在单位ID
     */
    @ApiModelProperty("所属单位ID")
    private Long deptId;

    @ApiModelProperty("所属单位")
    private String deptName;

    /**
     * 用户账号
     */
    @ApiModelProperty("用户账号")
    private String userName;

    /**
     * 用户昵称
     */
    @ApiModelProperty("用户昵称")
    private String nickName;

    /**
     * 用户类型
     */
    @ApiModelProperty("用户类型")
    private String userType;

    /**
     * 用户邮箱
     */
    @ApiModelProperty("邮箱")
    private String email;

    /**
     * 手机号码
     */
    @ApiModelProperty("手机号")
    private String phonenumber;
    /**
     * 帐号状态(0正常 1停用)
     */
    @ApiModelProperty("状态(0正常 1停用)")
    private String status;

    /**
     * 密码
     */
    @JsonIgnore
    private String password;

    /**
     * 密码过期时间
     */
    @JsonIgnore
    private LocalDateTime passwordExpire;

    /**
     * 过期时间
     */
    @ApiModelProperty("有效期限")
    private LocalDate expirationDate;

    @ApiModelProperty("用户角色")
    private String roleName;

    @ApiModelProperty("用户已分配角色id列表")
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private List<Long> roleIdList;
    @ApiModelProperty("用户已分配角色列表")
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private List<SysRoleVO> roleList;


}

后来通过在SysUserVO 上添加

@JsonTypeName("SysUserVO")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)

这 两句的作用是将 SysUserVO 已指定别名的方式配置,而不是以包名加类名的方式,同时是WRAPPER_OBJECT 对象方式。

这样在redis 中存储的时候就变成以下这样,同时反序列化也不会提示以上的异常
在这里插入图片描述

登陆成功后,redis 里面也存储了LoginUser 的信息,但是当你重启服务后,再次执行别的接口报如下错误

Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.Long` from Array value (token `JsonToken.START_ARRAY`)
 at [Source: (byte[])"["com.dechnic.oms.common.core.domain.model.LoginUser",{"userId":1,"deptId":1,"token":"702ddf92-c2ae-4265-9d27-330096268df4","loginTime":"2023-02-04 16:15:54","expireTime":"2023-02-04 16:45:54","ipaddr":"127.0.0.1","permissions":["java.util.HashSet",["*:*:*"]],"sysUserVO":{"SysUserVO":{"userId":1,"deptId":1,"deptName":"qqqqq","userName":"admin","nickName":"超级管理员","userType":"01","status":"0","expirationDate":"2099-12-31","roleName":"管理员","roleIdList":["java.util.ArrayList",[["java"[truncated 39 bytes]; line: 1, column: 495] (through reference chain: com.dechnic.oms.common.core.domain.model.LoginUser["sysUserVO"]->com.dechnic.oms.common.core.vo.SysUserVO["roleIdList"]->java.util.ArrayList[0])
	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1741)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1515)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromArray(StdDeserializer.java:222)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseLong(StdDeserializer.java:952)
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer.deserialize(NumberDeserializers.java:575)
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer.deserialize(NumberDeserializers.java:550)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:355)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:120)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromArray(AsArrayTypeDeserializer.java:53)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserializeWithType(CollectionDeserializer.java:283)
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:138)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:314)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
	at com.fasterxml.jackson.databind.jsontype.impl.AsWrapperTypeDeserializer._deserialize(AsWrapperTypeDeserializer.java:121)
	at com.fasterxml.jackson.databind.jsontype.impl.AsWrapperTypeDeserializer.deserializeTypedFromObject(AsWrapperTypeDeserializer.java:52)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1292)
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:138)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:314)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:120)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromAny(AsArrayTypeDeserializer.java:71)
	at com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer$Vanilla.deserializeWithType(UntypedObjectDeserializer.java:781)
	at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74)
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3731)
	at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.deserialize(Jackson2JsonRedisSerializer.java:73)
	... 66 common frames omitted

后来分析到是当执行token 解析的时候

    /**
     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
     *
     * @param request
     * @return 令牌
     */
    public LoginUser verifyToken(HttpServletRequest request) {
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token)) {
            try {
                Algorithm algorithm = Algorithm.HMAC512(secret);
                JWTVerifier verifier = JWT.require(algorithm)
                        .withIssuer(isUser)
                        .build();
                DecodedJWT jwt = verifier.verify(token);
                String uuid = jwt.getClaim(Constants.LOGIN_USER_KEY).asString();
                String userKey = getTokenKey(uuid);
                LoginUser user = redisCache.getCacheObject(userKey);
                if (null != user && user.getExpireTime().minusMinutes(MINUTE_TEN).isBefore(LocalDateTime.now())) {
                    refreshToken(user);
                }
                return user;
            } catch (JWTVerificationException exception) {
                log.error("验证Token失败:", exception);
            }
        }
        return null;
    }

执行 LoginUser user = redisCache.getCacheObject(userKey); 这句从redis 中获取登陆用户信息时报错。根据以上异常定位到是解析List roleIdList 这句时报错。定位到可能时解析List 集合的问题。

SysUserVO正确配置如下:

	@ApiModelProperty("用户已分配角色id列表")
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = WRAPPER_ARRAY)
    private List<Long> roleIdList;
    @ApiModelProperty("用户已分配角色列表")
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = WRAPPER_ARRAY)
    private List<SysRoleVO> roleList;

备注: 在List 集合熟悉上添加 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = WRAPPER_ARRAY) 此句的作用是此属性是包装成数组类型。

3.正确配置

RedisConfig 配置

package com.dechnic.oms.framework.config;

import com.dechnic.oms.common.utils.MapperUtils;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;

/**
 * redis配置
 * @author zhangzs
 * @date 2023/1/4 9:54
 * @Description
 */
@Configuration
@EnableCaching
public class RedisConfig  extends CachingConfigurerSupport {

    @Bean
    @SuppressWarnings(value = {"unchecked", "rawtypes"})
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        //FAIL_ON_UNKNOWN_PROPERTIES在序列化的时候,如果遇到不认识的字段的处理方式
        //默认启用特性,这意味着在遇到未知属性时抛出JsonMappingException。在引入该特性之前,这是默认的默认设置。
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        //FAIL_ON_EMPTY_BEANS决定了在没有找到类型的存取器时发生了什么(并且没有注释表明它是被序列化的)。如果启用(默认),
        // 将抛出一个异常来指明这些是非序列化类型;如果禁用了,它们将被序列化为空对象,即没有任何属性。
        //请注意,这个特性只对那些没有任何识别注释的“空”bean产生影响(如@json序列化):那些有注释的bean不会导致抛出异常。
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);

        //在序列化时,只有那些值为null或被认为为空的值的属性才不会被包含在内。
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

        //objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        //将类名称序列化到json串中,去掉会导致得出来的的是LinkedHashMap对象,直接转换实体对象会失败
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);

        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
        //是否允许单引号
        objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        objectMapper.registerModule(javaTimeModule);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        template.setValueSerializer(jackson2JsonRedisSerializer);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 缓存30分钟
        Duration duration = Duration.ofSeconds(30*60*60);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(duration);
        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }

    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... objects) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                sb.append("&");
                for (Object obj : objects) {
                    sb.append(obj.getClass().getName());
                    sb.append("&");
                    try {
                        sb.append(MapperUtils.obj2json(obj));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    sb.append("&");
                }
                return DigestUtils.sha256Hex(sb.toString());
            }
        };
    }

    @Bean("myKeyGenerator")
    @Primary
    public KeyGenerator myKeyGenerator(){
        return new KeyGenerator(){
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName()+"["+ Arrays.asList(params).toString()+"]";
            }
        };
    }

}

SysUserVO:

package com.dechnic.oms.common.core.vo;


import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.WRAPPER_ARRAY;

/**
 * @ desc:
 * @ Author:  houqida
 * @ Date  :  2023/1/7
 */
@ApiModel("用户查询VO")
@Data
@JsonTypeName("SysUserVO")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
public class SysUserVO extends BaseVO {
    private static final long serialVersionUID = -8177563824574495L;
    /**
     * 用户ID
     */
    @ApiModelProperty("用户ID")
    private Long userId;

    /**
     * 所在单位ID
     */
    @ApiModelProperty("所属单位ID")
    private Long deptId;

    @ApiModelProperty("所属单位")
    private String deptName;

    /**
     * 用户账号
     */
    @ApiModelProperty("用户账号")
    private String userName;

    /**
     * 用户昵称
     */
    @ApiModelProperty("用户昵称")
    private String nickName;

    /**
     * 用户类型
     */
    @ApiModelProperty("用户类型")
    private String userType;

    /**
     * 用户邮箱
     */
    @ApiModelProperty("邮箱")
    private String email;

    /**
     * 手机号码
     */
    @ApiModelProperty("手机号")
    private String phonenumber;
    /**
     * 帐号状态(0正常 1停用)
     */
    @ApiModelProperty("状态(0正常 1停用)")
    private String status;

    /**
     * 密码
     */
    @JsonIgnore
    private String password;

    /**
     * 密码过期时间
     */
    @JsonIgnore
    private LocalDateTime passwordExpire;

    /**
     * 过期时间
     */
    @ApiModelProperty("有效期限")
    private LocalDate expirationDate;

    @ApiModelProperty("用户角色")
    private String roleName;

    @ApiModelProperty("用户已分配角色id列表")
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = WRAPPER_ARRAY)
    private List<Long> roleIdList;
    @ApiModelProperty("用户已分配角色列表")
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = WRAPPER_ARRAY)
    private List<SysRoleVO> roleList;


}

4.小结

  1. jackson 主要通过ObjectMapper 来设置一些序列化、反序列化的属性。
  2. redisConfig 里面通过objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY); 做通用配置
    LoginUser 缓存到redis 中的最外层格式为[],但是 SysUserVO 为LoginUser 的一个复杂对象,所有需要通过
@JsonTypeName("SysUserVO")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)

此配置,将SysUserVO 配置成以昵称命名的方式,同时包装成对象类型,
但是SysUserVO 里面又有List roleIdList; 和 List roleList; 等list 类型
需要在这些属性上添加 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = WRAPPER_ARRAY) 同理SysRoleVO 也要做相应的配置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/198212.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java多线程】同步代码块处理线程安全问题

题&#xff1a;创建三个窗口卖票&#xff0c;总票数为100张 1.问题&#xff1a;买票过程中&#xff0c;出现了重票&#xff0c;错票 --> 出现了线程的安全问题 2.问题出现的原因&#xff1a;当某个线程操作车票的过程中&#xff0c;尚未操作完成时&#xff0c;其他线程参与进…

IDEA常用技巧汇总

查看代码历史版本鼠标在需要查看的java类 右键 找到Local History >> Show History 点开即可看到历史版本&#xff0c;常用于自己忘记代码改了哪些内容 或需要恢复至某个版本 (注意 只能看近期修改 太久了也是看不到的)idea设置成eclipse的快捷键这对eclipse转idea的开发…

网络编程套接字

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录Socket 套接字UDP 和 TCPUDP数据报套接字编程DatagramSocket APIDatagramPacket APIUdpEchoServerUdpEchoClientUdpDictServer…

分享62个JS返回顶部特效,总有一款适合您

分享62个JS返回顶部特效&#xff0c;总有一款适合您 62个JS返回顶部特效下载链接&#xff1a;https://pan.baidu.com/s/1X1fSwxibtEDXKeYqj0sHXQ?pwde2kp 提取码&#xff1a;e2kp Python采集代码下载链接&#xff1a;https://wwgn.lanzoul.com/iKGwb0kye3wj import os im…

[Android]Toolbar

Toolbar是由AndroidX库提供的&#xff0c;它的强大之处在于&#xff0c;它不仅继承了ActionBar的所有功能&#xff0c;并且灵活度很高&#xff0c;可以配合其他控件完成一些Material Design的效果。 使用Toolbar替代ActionBar 在themes的两个xml文件中&#xff0c; 都指定一个…

视频连载08 - 这个为生信学习和生信作图打造的开源R教程真香!!!

点击阅读原文跳转完整教案。1 思考题2 R基础2.1 R安装2.2 Rstudio基础2.2.1 Rstudio版本2.2.2 Rstudio安装2.2.3 Rstudio 使用2.3 R基本语法2.3.1 获取帮助文档&#xff0c;查看命令或函数的使用方法、事例或适用范围2.3.2 R中的变量及其初始化2.3.3 变量类型和转换2.3.4 R中矩…

ECharts接收dataset类型数据封装各类型图形组件

数据平台整合matabase图表&#xff0c;调用matabase已有接口使用echarts实现图表展示 目标 将各类型图形独立封装为组件 将多个组件整体封装成一个组件 使用时只需传入组件名和对应数据即可 展示 数据格式 ECharts中dataset配置 公共组件 示例饼图 pie-chart pie-chart comm…

AcWing 1083. Windy数(数位DP)

AcWing 1083. Windy数&#xff08;数位DP&#xff09;一、问题二、分析状态表示状态转移初末状态循环设计注意事项三、代码一、问题 二、分析 这道题考察的是数位DP的知识&#xff0c;对于数位DP的分析方法作者在之前的文章中做过详细地介绍&#xff1a;AcWing 1081. 度的数量…

java面试题(九)集合篇

2.1 Java中有哪些容器&#xff08;集合类&#xff09;&#xff1f; 参考答案 Java中的集合类主要由Collection和Map这两个接口派生而出&#xff0c;其中Collection接口又派生出三个子接口&#xff0c;分别是Set、List、Queue。所有的Java集合类&#xff0c;都是Set、List、Qu…

【LeetCode】Day206-二叉树着色游戏

题目 1145. 二叉树着色游戏【中等】 题解 官解说的实在是抽象了&#xff0c;看了下高赞题解&#xff0c;果然很清晰易懂 以 x 为根&#xff0c;它的三个邻居&#xff08;左儿子、右儿子和父节点&#xff09;就对应着三棵子树&#xff1a; 左子树右子树父节点子树 哪棵子树…

微服务项目框架及多模块开发

目录 项目模式 技术栈 项目架构图 模块 案例演示 主模块 zmall-common子模块 zmall-user子模块 项目模式 电商模式&#xff1a;市面上有5种常见的电商模式&#xff0c;B2B、B2C、 C2B、 C2C、O2O; 1、B2B模式 B2B (Business to Business)&#xff0c;是指 商家与商家…

java递归-八皇后问题(回溯算法)

1.八皇后问题介绍 八皇后问题&#xff0c;是一个古老而著名的问题&#xff0c;是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯贝瑟尔于 1848 年提出&#xff1a;在 88 格的国际象棋上摆放八个皇后&#xff0c;使其不能互相攻击&#xff0c;即&#xff1a;任意两个皇后都…

Day04-数据分析模型

文章目录数据分析模型数据分析流程第一&#xff1a;定性法第二&#xff1a;定量法一、数据分析要解决什么问题&#xff1f;1. 研究历史2. 解释现状4. 洞察商机5. 寻求最佳方案二、数据分析师的工作三、数据分析流程1. 数据分析框架2. 数据获取3. 数据处理4. 数据分析5. 撰写报告…

大数据技术架构(组件)18——Hive:FileFormats(1)

1.5、FileFormats1.5.1、FileFormat对比&#xff1a;1.5.1.1、Text File每一行都是一条记录&#xff0c;每行都以换行符&#xff08;\ n&#xff09;结尾。数据不做压缩&#xff0c;磁盘开销大&#xff0c;数据解析开销大。可结合Gzip、Bzip2使用&#xff08;系统自动检查&…

Python 3 基本数据类型,包含示例演示(初学友好)

嗨害大家好鸭~ 我是小熊猫 有好好学习python吗&#xff1f; Python学习资料电子书 点击此处跳转文末名片获取 Python3 基本数据类型 Python 中的变量不需要声明。每个变量在使用前都必须赋值&#xff0c;变量赋值以后该变量才会被创建。 在 Python 中&#xff0c;变量就是变量…

Redis实现分布式锁

基于Redis实现分布式锁。分为单Redis节点实现和Redis集群实现。 基于单个Redis节点实现分布式锁 作为分布式锁实现过程中的共享存储系统&#xff0c;Redis可以使用键值对来保护锁变量&#xff0c;在接收和处理不同客户端发送的加锁的操作请求。 客户端A、C同时请求加锁&#…

【Linux】基本开发工具的使用-yumvimgcc/g++git

文章目录Linux 软件包管理器-yum什么是软件包window和Linux互传文件的工具: lrzszyum注意事项查看软件包注意事项如何安装软件注意事项如何卸载软件好玩的指令sl 小火车cowsay 打印一只说话的小牛boxes 打印一个ASCII的动画linux_logo 显示linux系统的logocurl http://wttr.in …

2021美赛D题艺术家思路整理

问题整理 使用influence_data数据集或其部分创建音乐影响力的&#xff08;多个&#xff09;定向网络&#xff0c;其中影响者与关注者相连。开发捕捉此网络中“音乐影响的参数”。通过创建定向影响网络的子网络来探索音乐影响力的子集。描述这个子网络。你的“音乐影响”指标在…

((蓝桥杯 刷题全集)【备战(蓝桥杯)算法竞赛-第3天】( 从头开始重新做题,记录备战竞赛路上的每一道题 )距离蓝桥杯还有64天

&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6; 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&a…

PMP知识点1

根据PMBOK和参考书籍自己总结的一些不熟悉知识点&#xff0c;当做笔记放这复习。 1. 项目战略管理、组合管理、项目及管理、项目管理区别 战略管理项目组合管理项目集管理项目管理工作内容明确组织战略目标选择有利于实现战略目标的项目分析并利用各项目之间有机联系规范有序…