Spring Boot Json序列化终极配置


现在的Web应用都是通过json序列化,SpringBoot内置Jackson工具提供json -> bean 或者 bean -> json序列化的。在SpringBoot序列化中,我们可以自定义json的序列化过程,比如增加脱敏,忽略字段,格式化时间,类型转化等。

解决数据库bigint类型JS会丢失精度问题

如果我们的数据库主键采用雪花算法生成的id,该id在数据库中是bigint类型,在Java代码中的表现形式是Long类型。但是我们把这个id传给前端的话,前端用JS接受会丢失精度,使得最后两位变成0,这就造成了前端在用这个id进行操作的时候,会给我们一个错误的id,如果要解决这个方法,有两种方式。

  • 方式一

    // 注解处理,这里可以配置公共 baseEntity 处理
    @JsonSerialize(using=ToStringSerializer.class)
    public long getId() {
        return id;
    }
  • 方式二

    //添加对长整型的转换关系
    ObjectMapper objectMapper = new ObjectMapper();
    ToStringSerializer stringSerializer = ToStringSerializer.instance;
    simpleModule.addSerializer(BigInteger.class, stringSerializer);
    simpleModule.addSerializer(Long.class, stringSerializer);
    simpleModule.addSerializer(Long.TYPE, stringSerializer);
    objectMapper.registerModule(simpleModule);

个人更推荐方式二,毕竟我本人并不喜欢在实体类里面加一些关于json的注解。

格式化时间

中国时间的格式一般返回yyyy-MM-dd HH:mm:ss  格式,实现时间格式化转换,也有好几种方式。

  • 方式一

    private static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    
    ObjectMapper objectMapper = new ObjectMapper();
    JavaTimeModule javaTimeModule = new JavaTimeModule();
    javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
    javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
    javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
    javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
    objectMapper.registerModule(javaTimeModule).registerModule(new ParameterNamesModule());
  • 方式二

    spring:
      jackson:
        date-format: yyyy-MM-dd HH:mm:ss
        time-zone: GMT+8
  • 方式三

    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;

个人更推荐方式一,对实体类或者VO、DTO类的代码没有侵入

忽略字段

在返回给前端的字段中,有可能我们会将数据库的实体类返回,实体类中,有一些无关紧要的字段,比如 createTime, updateTime, deleted等通用字段,我们可以在json序列化的时候忽略掉

// 设置过滤字段
SimpleBeanPropertyFilter fieldFilter = SimpleBeanPropertyFilter.serializeAllExcept("create_time", "update_time", "deleted");
SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("defaultValue", fieldFilter);
objectMapper.setFilterProvider(filterProvider).addMixIn(BaseEntity.class, PropertyFilterMixIn.class);

@JsonFilter("defaultValue")
interface PropertyFilterMixIn {
}

除了上述方法,也可以在实体类上添加@JsonIgnore注解,但是我不喜欢。

脱敏

利用json序列化,我们可以定制一些高级的功能,比如敏感字段不全部显示,中间显示 ***等,好比手机号: 156****2570这种效果。

  • 先定义一些枚举,表示要脱敏字段的类型

    public enum SensitiveTypeEnum {
    
    
        /**
         * 身份证号
         */
        ID_CARD,
        /**
         * 密码
         */
        PASSWORD,
        /**
         * 手机号
         */
        MOBILE_PHONE,
        /**
         * 电子邮件
         */
        EMAIL,
        /**
         * 真实姓名
         */
        NAME,
        /**
         * 账户信息
         */
        ACCOUNT_NO;
    }
    
  • 脱敏的工具类

    public class SensitiveUtils {
    
        /**
         * [真实姓名] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
         */
        public static String realName(final String realName) {
            if (StringUtils.isBlank(realName)) {
                return "";
            }
            return dealString(realName, 1, 0);
        }
    
        /**
         * [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
         */
        public static String idCard(final String idCard) {
            if (StringUtils.isBlank(idCard)) {
                return "";
            }
            return dealString(idCard, 3, 4);
        }
    
        /**
         * [手机号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
         */
        public static String mobilePhone(final String idCard) {
            if (StringUtils.isBlank(idCard)) {
                return "";
            }
            return dealString(idCard, 3, 4);
        }
    
        /**
         * [邮箱] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
         */
        public static String email(final String email) {
            if (StringUtils.isBlank(email)) {
                return "";
            }
            int index = email.indexOf("@");
            return dealString(email, 3, email.length() - index);
        }
    
        /**
         * [账号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
         */
        public static String acctNo(final String idCard) {
            if (StringUtils.isBlank(idCard)) {
                return "";
            }
            final String name = StringUtils.left(idCard, 1);
            return StringUtils.rightPad(name, StringUtils.length(idCard), "*");
        }
    
        /**
         * [密码] 隐藏。<例子:*************>
         */
        public static String password(final String password) {
            if (StringUtils.isBlank(password)) {
                return "";
            }
            return "*";
        }
    
    
        private static String dealString(String str, int headOff, int tailOff) {
            int length = str.length();
            StringBuilder sb = new StringBuilder();
            final String head = StringUtils.left(str, headOff);
            String tail = StringUtils.right(str, tailOff);
            sb.append(head);
            int size = length - (headOff + tailOff);
            if (size > 0) {
                while (size > 0) {
                    sb.append("*");
                    size--;
                }
            }
            sb.append(tail);
            return sb.toString();
        }
    
    
        /**
         * 提供给外部进行直接脱敏处理
         * @param type
         * @param value
         * @return
         */
        public static String sensitveValue(SensitiveTypeEnum type, String value) {
            switch (type) {
                case NAME: {
                    return realName(String.valueOf(value));
                }
                case ID_CARD: {
                    return idCard(String.valueOf(value));
                }
                case MOBILE_PHONE: {
                    return mobilePhone(String.valueOf(value));
                }
                case EMAIL: {
                    return email(String.valueOf(value));
                }
                case ACCOUNT_NO: {
                    return acctNo(String.valueOf(value));
                }
                case PASSWORD: {
                    return password(String.valueOf(value));
                }
                default:
                    return String.valueOf(value);
            }
    
        }
    
    
    }
    

此工具类可以单独使用

  • 将脱敏过程绑定到json序列化

    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.databind.BeanProperty;
    import com.fasterxml.jackson.databind.JsonMappingException;
    import com.fasterxml.jackson.databind.JsonSerializer;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import com.fasterxml.jackson.databind.ser.ContextualSerializer;
    import com.storyhasyou.kratos.annotation.Sensitive;
    import com.storyhasyou.kratos.enums.SensitiveTypeEnum;
    import com.storyhasyou.kratos.utils.SensitiveUtils;
    
    import java.io.IOException;
    import java.util.Objects;
    
    
    public class SensitiveSerialize extends JsonSerializer<Object> implements ContextualSerializer {
    
        private final SensitiveTypeEnum type;
    
        public SensitiveSerialize(final SensitiveTypeEnum type) {
            this.type = type;
        }
    
        public SensitiveSerialize() {
            this.type = SensitiveTypeEnum.NAME;
        }
    
    
        @Override
        public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
            switch (this.type) {
                case ID_CARD: {
                    jsonGenerator.writeString(SensitiveUtils.idCard(String.valueOf(value)));
                    break;
                }
                case MOBILE_PHONE: {
                    jsonGenerator.writeString(SensitiveUtils.mobilePhone(String.valueOf(value)));
                    break;
                }
                case EMAIL: {
                    jsonGenerator.writeString(SensitiveUtils.email(String.valueOf(value)));
                    break;
                }
                case ACCOUNT_NO: {
                    jsonGenerator.writeString(SensitiveUtils.acctNo(String.valueOf(value)));
                    break;
                }
                case PASSWORD: {
                    jsonGenerator.writeString(SensitiveUtils.password(String.valueOf(value)));
                    break;
                }
                case NAME: {
                    jsonGenerator.writeString(SensitiveUtils.realName(String.valueOf(value)));
                    break;
                }
                default:
                    jsonGenerator.writeString(String.valueOf(value));
    
            }
    
        }
    
        @Override
        public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
    
            if (beanProperty != null) {
    
                // 非 String 类直接跳过
                if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                    Sensitive sensitiveInfo = beanProperty.getAnnotation(Sensitive.class);
                    if (sensitiveInfo == null) {
                        sensitiveInfo = beanProperty.getContextAnnotation(Sensitive.class);
                    }
                    // 如果能得到注解,就将注解的 value 传入 SensitiveInfoSerialize
                    if (sensitiveInfo != null) {
    
                        return new SensitiveSerialize(sensitiveInfo.value());
                    }
                }
                return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
            }
            return serializerProvider.findNullValueSerializer(beanProperty);
    
        }
    }
    
  • 最后添加一个注解

    import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
    import com.fasterxml.jackson.databind.annotation.JsonSerialize;
    import com.storyhasyou.kratos.toolkit.SensitiveSerialize;
    import com.storyhasyou.kratos.enums.SensitiveTypeEnum;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    
    @Retention(RetentionPolicy.RUNTIME)
    @JacksonAnnotationsInside
    @JsonSerialize(using = SensitiveSerialize.class)
    public @interface Sensitive {
    
        SensitiveTypeEnum value() default SensitiveTypeEnum.PASSWORD;
    
    }
    

将需要脱敏的字段,打上这个注解,就可以在json序列化中,进行脱敏操作。

一个生产环境json配置文件

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
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.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import com.storyhasyou.kratos.base.BaseEntity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;


@Configuration
public class JacksonConfig {

    private static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";

    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        ObjectMapper objectMapper = new ObjectMapper();
        //定义Json转换器
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        // 设置Jackson序列化和反序列化的时候,使用下划线分割
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
        objectMapper.registerModule(javaTimeModule).registerModule(new ParameterNamesModule());

        // 设置过滤字段
        SimpleBeanPropertyFilter fieldFilter = SimpleBeanPropertyFilter.serializeAllExcept("create_time", "update_time", "deleted");
        SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("defaultValue", fieldFilter);
        objectMapper.setFilterProvider(filterProvider).addMixIn(BaseEntity.class, PropertyFilterMixIn.class);

        SimpleModule simpleModule = new SimpleModule();
        //添加对长整型的转换关系
        ToStringSerializer stringSerializer = ToStringSerializer.instance;
        simpleModule.addSerializer(BigInteger.class, stringSerializer);
        simpleModule.addSerializer(Long.class, stringSerializer);
        simpleModule.addSerializer(Long.TYPE, stringSerializer);
        //将对象模型添加至对象映射器
        objectMapper.registerModule(simpleModule);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);
        //将对象映射器添加至Json转换器
        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        return jackson2HttpMessageConverter;
    }

    @JsonFilter("defaultValue")
    interface PropertyFilterMixIn {
    }


}

Author: Re:0
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Re:0 !
 Previous
大数据以及Hadoop介绍 大数据以及Hadoop介绍
大数据概述大数据(Big Data):指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长和多样化的信息资产。主要解决:海量数据的存储和海量数据的分
2022-09-06
Next 
Kubernetes安装 Kubernetes安装
Kubernetes作为生产级别的容器编排系统。 是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。Kubernetes 拥有一个庞大且快速增长的生态系统。Kubernetes 的服务、支持和工具广
2022-06-03
  TOC