在Spring Boot应用中,Jackson作为默认的JSON处理器,提供了强大的序列化/反序列化能力。本文将详细解析如何通过Jackson解决三个常见需求:数据脱敏日期格式化数据精度控制,并提供完整代码示例。

一、数据脱敏:保护敏感信息

场景:隐藏手机号、身份证号、银行卡等敏感数据的部分字段。

1. 实现方案:自定义注解 + 序列化器

  • 脱敏注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerializer.class)
public @interface Sensitive {
    SensitiveStrategy strategy();
}
  •  脱敏策略
@AllArgsConstructor
public enum SensitiveStrategy {

    /**
     * 身份证脱敏
     */
    ID_CARD(s -> DesensitizedUtil.idCardNum(s, 3, 4)),

    /**
     * 手机号脱敏
     */
    PHONE(DesensitizedUtil::mobilePhone),

    /**
     * 地址脱敏
     */
    ADDRESS(s -> DesensitizedUtil.address(s, 8)),

    /**
     * 邮箱脱敏
     */
    EMAIL(DesensitizedUtil::email),

    /**
     * 银行卡
     */
    BANK_CARD(DesensitizedUtil::bankCard);

    //可自行添加其他脱敏策略

    private final Function<String, String> desensitizer;

    public Function<String, String> desensitizer() {
        return desensitizer;
    }
}
  •  脱敏序列化工具
@Slf4j
public class SensitiveSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private SensitiveStrategy strategy;

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        try {
            if (// 判断是否执行脱敏) {
                gen.writeString(strategy.desensitizer().apply(value));
            } else {
                gen.writeString(value);
            }
        } catch (BeansException e) {
            log.error("脱敏实现不存在, 采用默认处理 => {}", e.getMessage());
            gen.writeString(value);
        }
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        Sensitive annotation = property.getAnnotation(Sensitive.class);
        if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
            this.strategy = annotation.strategy();
            return this;
        }
        return prov.findValueSerializer(property.getType(), property);
    }
}
  •  使用示例
@Data
public class TestSensitive {
	/**
     * 身份证
     */
    @Sensitive(strategy = SensitiveStrategy.ID_CARD)
    private String idCard;

    /**
     * 电话
     */
    @Sensitive(strategy = SensitiveStrategy.PHONE)
    private String phone;

    /**
     * 地址
     */
    @Sensitive(strategy = SensitiveStrategy.ADDRESS)
    private String address;

    /**
     * 邮箱
     */
    @Sensitive(strategy = SensitiveStrategy.EMAIL)
    private String email;

    /**
     * 银行卡
     */
    @Sensitive(strategy = SensitiveStrategy.BANK_CARD)
    private String bankCard;
}
  •  测试结果
{
"idCard" : "210***********7435",
"phone" : "186****1234",
"address" : "和平市大桥区某某********",
"email" : "1**********@163.com",
"bankCard" : "6221 **** **** *** 3456"
}

二、日期格式化:统一时间格式

问题:Jackson默认将Date类型序列化为长整型时间戳,需转为可读格式。

1. 全局配置(application.yml)

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

2. 局部自定义格式

public class UserDTO {
    @JsonFormat(pattern = "yyyy/MM/dd", timezone = "GMT+8")
    private Date createTime;
    
    // 处理LocalDateTime
    @JsonFormat(pattern = "HH:mm:ss")
    private LocalDateTime payTime; 
}

3. 全局配置序列化返回

@Configuration
public class JacksonConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer customizer() {
        return builder -> {
            JavaTimeModule javaTimeModule = new JavaTimeModule();
            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
            javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
            javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
            javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
            javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
            javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
            builder.modules(javaTimeModule);
            builder.timeZone(TimeZone.getDefault());
        };
    }

}

三、数据精度控制:解决浮点数精度丢失

问题Double类型可能出现0.1+0.2=0.30000000000000004等精度问题。

1. 使用BigDecimal代替Double

public class ProductDTO {
    // 序列化时保留两位小数
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    private BigDecimal price; 
}

2. 自定义序列化器(精确小数位数)

public class DoubleSerializer extends StdSerializer<Double> {
    public DoubleSerializer() { super(Double.class); }

    @Override
    public void serialize(Double value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        if (value == null) gen.writeNull();
        else gen.writeNumber(BigDecimal.valueOf(value).setScale(2, RoundingMode.HALF_UP));
    }
}

 3. 使用示例

// 在字段上使用
public class AccountDTO {
    @JsonSerialize(using = DoubleSerializer.class)
    private Double balance;
}

4. 解决返回前端精度丢失问题

自定义数值类型序列化处理器

@JacksonStdImpl
public class BigNumberSerializer extends NumberSerializer {

    /**
     * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来
     */
    private static final long MAX_SAFE_INTEGER = 9007199254740991L;
    private static final long MIN_SAFE_INTEGER = -9007199254740991L;

    /**
     * 提供实例
     */
    public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);

    public BigNumberSerializer(Class<? extends Number> rawType) {
        super(rawType);
    }

    @Override
    public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        // 超出范围 序列化位字符串
        if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
            super.serialize(value, gen, provider);
        } else {
            gen.writeString(value.toString());
        }
    }
}

 全局配置BigDecimal序列化

@Configuration
public class JacksonConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer customizer() {
        return builder -> {
            JavaTimeModule javaTimeModule = new JavaTimeModule();
            javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
            javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
            javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
            javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
            // 日期序列化和反序列化
            builder.modules(javaTimeModule);
            builder.timeZone(TimeZone.getDefault());
        };
    }

}

四、总结

  1. 数据脱敏:通过自定义注解+序列化器实现动态掩码

  2. 日期格式化

    • 全局配置:spring.jackson.date-format

    • 局部注解:@JsonFormat(pattern="...")

  3. 精度控制

    • 优先使用BigDecimal代替浮点类型

    • 自定义序列化器或@JsonFormat(shape=STRING)

最佳实践建议

  • 敏感信息脱敏应在序列化层实现,避免侵入业务逻辑

  • 日期格式统一通过全局配置,特殊场景使用注解覆盖

  • 金融类数据强制使用BigDecimal并指定精度

Logo

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

更多推荐