SpringBoot中使用Jackson自定义注解完成参数的个性化处理

1. 背景

一般前后端使用的是Json格式的报文来进行交互,而SpringBoot默认采用Jackson来帮助我们完成协议报文序列化和反序列化。

而有时我们需要对协议报文一些个性化处理,例如:

  1. 敏感信息(手机号,身份证等)脱敏;
  2. 参数的加密和解密;
  3. 文件防盗链设置(文件上传接口,上传返回临时地址;业务接口中,将临时链接转化成永久链接存储到DB)

这些工作都可以在业务代码中来进行编写,但是会调用非常多的冗余代码。可以通过自定义Jackson注解,在Json序列化成对象,对象反序列化成Json时,来进行动态处理

2. 序列化-对象转Json串处理

例子:手机号脱敏处理

定义注解:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@Target(ElementType.FIELD)
@JsonSerialize(using = SensitiveDataSerializer.class)
public @interface SensitiveData {

    /**
     * 默认的字段脱敏替换字符串
     */
    String DEFAULT_REPLACE_STRING = "*";

    /**
     * 脱敏策略
     */
    Strategy strategy() default Strategy.TOTAL;

    /**
     * 脱敏长度,在Strategy.TOTAL策略下忽略该字段
     */
    int length() default 0;

    /**
     * 脱敏字段替换字符
     */
    String replaceStr() default DEFAULT_REPLACE_STRING;

    enum Strategy {
        /**
         * 全部
         */
        TOTAL,
        /**
         * 从左边开始
         */
        LEFT,
        /**
         * 从右边开始
         */
        RIGHT
    }
}

定义反序列化解析类:

import java.io.IOException;

import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;

/**
 * 脱敏字段序列化器
 */
public class SensitiveDataSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private SensitiveData sensitiveData;

    public SensitiveDataSerializer(SensitiveData sensitiveData) {
        this.sensitiveData = sensitiveData;
    }
    public SensitiveDataSerializer() {
    }


    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (StringUtils.isBlank(value)) {
            gen.writeString(value);
            return;
        }

        if (sensitiveData != null) {
            final SensitiveData.Strategy strategy = sensitiveData.strategy();
            final int length = sensitiveData.length();
            final String replaceString = sensitiveData.replaceStr();
            gen.writeString(getValue(value, strategy, length, replaceString));
        } else {
            gen.writeString(value);
        }

    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
        SensitiveData annotation = property.getAnnotation(SensitiveData.class);

        if (annotation != null) {
            return new SensitiveDataSerializer(annotation);
        }
        return this;
    }

    private String getValue(String rawStr, SensitiveData.Strategy strategy, int length, String replaceString) {
        switch (strategy) {
            case TOTAL:
                return rawStr.replaceAll("[\\s\\S]", replaceString);
            case LEFT:
                return replaceByLength(rawStr, length, replaceString, true);
            case RIGHT:
                return replaceByLength(rawStr, length, replaceString, false);
            default:
                throw new IllegalArgumentException("Illegal Sensitive Strategy");
        }
    }

    private String replaceByLength(String rawStr, int length, String replaceString, boolean fromLeft) {
        if (StringUtils.isBlank(rawStr)) {
            return rawStr;
        }
        if (rawStr.length() <= length) {
            return rawStr.replaceAll("[\\s\\S]", replaceString);
        }

        if (fromLeft) {
            return getSpecStringSequence(length, replaceString) + rawStr.substring(length);
        } else {
            return rawStr.substring(0, length) + getSpecStringSequence(length, replaceString);
        }
    }

    private String getSpecStringSequence(int length, String str) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            stringBuilder.append(str);
        }
        return stringBuilder.toString();
    }
}

3. 反序列化-Json转对象

加密字段解密

定义注解:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@Target(ElementType.FIELD)
@JsonDeserialize(using = HdxDecryptDeserialize.class)
public @interface HdxDecrypt {
    
    String strategy() default "default";

}

定义反序列化处理器:

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;

import lombok.extern.slf4j.Slf4j;


@Slf4j
public class HdxDecryptDeserialize extends JsonDeserializer<String> implements ContextualDeserializer {

    private HdxDecrypt hdxDecrypt;

    public HdxDecryptDeserialize() {
    }

    public HdxDecryptDeserialize(HdxDecrypt hdxDecrypt) {
        this.hdxDecrypt = hdxDecrypt;
    }

    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String encryptedKey = p.getText().trim();
        return "已经解密,解密策略:" + hdxDecrypt.strategy() + " " + encryptedKey;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
            throws JsonMappingException {
        HdxDecrypt annotation = property.getAnnotation(HdxDecrypt.class);
        if (annotation != null) {
            return new HdxDecryptDeserialize(annotation);
        }
        return this;
    }
}

4. 使用方式

定义对象:

import java.time.LocalDate;
import java.time.LocalDateTime;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonUnwrapped;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Person {

    private Long id;

    private String name;

    private String address;

    private Integer age;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createAt;

    private LocalDateTime updateAt;

    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate lastLoginDate;

    @SensitiveData(strategy = SensitiveData.Strategy.LEFT, length = 6, replaceStr = "*")
    private String mobile;

    /**
     * 对象打平输出
     */
    @JsonUnwrapped
    private Role role;

    @HdxDecrypt(strategy = "custom")
    private String key;


    @Data
    public static class Role {

        private long id;

        private String roleName;
    }
}

定义Controller层

@RestController
@RequestMapping("/person")
@Slf4j
public class PersonController {

    @GetMapping("/detail")
    public Person detail() {
        Role role = new Role();
        role.setId(12L);
        role.setRoleName("管理员");
        return Person.builder()
                .id(12L)
                .age(18)
                .name("lis")
                .address("北京")
                .createAt(LocalDateTime.now())
                .updateAt(LocalDateTime.now())
                .lastLoginDate(LocalDate.now())
                .mobile("15824984456")
                .role(role)
                .build();
    }

    @PostMapping("/save")
    public Person save(@RequestBody Person person) {
        log.info("用户信息: {}", person);
        return person;
    }
}

查看结果:


查看结果.png
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。