likes
comments
collection
share

Jackson-自定义注解及实现数据脱敏、枚举转换

作者站长头像
站长
· 阅读数 21

Hi,大家好,我是抢老婆酸奶的小肥仔。

上一章,我们介绍了下Jackson及相关的注解,其实我们可以通过仿照一些注解来实现自定义以满足我们自己的业务需求,这一章,我们来说说jackson提供的自定义注解及一些应用吧。

废话不多说,撸起来。

1、自定义注解

在Jackson中使用 @JacksonAnnotationsInside来标注当前定义的注解为jackson注解。我们通过一个简单例子来看下。

1.1 自定义注解

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/6/15 10:07
 **/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"id","nickName","password","name"})
public @interface DataOrderAndFilter {
}

上述注解实现了序列化时排列的顺序,以及不显示数值为null的字段。

1.2 创建实体

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/6/15 10:11
 **/
@Data
@AllArgsConstructor
@DataOrderAndFilter
public class CustomDataEnter {
    private Integer id;
    private String name;
    private String nickName;
    private String password;
}

1.3 测试

@Test
public void useJacksonAnnotationsInside() throws JsonProcessingException {
    CustomDataEnter dataEnter = new CustomDataEnter(1, "zhangsan", null, "123456");
    String json = new ObjectMapper().writeValueAsString(dataEnter);
    log.info("使用@JacksonAnnotationsInside实现自定义注解序列化json:" + json);
}

输出:

Jackson-自定义注解及实现数据脱敏、枚举转换

上述代码通过简单的例子说明了@JacksonAnnotationsInside可以自定义注解。下面我们来看看在我们日常工作中在哪些地方可以使用。

2、自定义注解应用

2.1 实现数据脱敏

结合@JsonSerialize在数据进行序列化时,进行数据的脱敏。

需求:用户信息在返回到前端时,对用户名,身份证号,手机号等进行数据脱敏。

  1. 用户名:如果两个字时,只保留姓,后面为*,例如:张三,脱敏后:张*,超过三个字时,则保留前后两个字,中间使用*,例如:张小四,脱敏后:张*四
  2. 身份证号:前后各保留四位,中间用四个连接,例如:450218199103458901,脱敏后:4501***8901
  3. 手机号:保留前三位和后四位,中间四位用连接,例如:13456789012,脱敏后:134***9012

根据上述需求,我们先建一个枚举,实现不同类型数据脱敏,也方便后期实现其他字段脱敏时,只要在这个枚举类中加入就行。

2.1.1 创建脱敏枚举

/**
 * @author: jiangjs
 * @description: 脱敏策略
 * @date: 2022/4/15 16:23
 **/
public enum DesensitizedStrategy {

    /**
     * 用户信息脱敏
     */
    USER_NAME(s -> s.replaceAll("(\S)\S(\S*)", "$1*$2")),
    /**
     * 身份证信息脱敏
     */
    ID_CARD(s -> s.replaceAll("(\d{4})\d{10}(\w{4})", "$1****$2")),
    /**
     * 手机号脱敏
     */

    PHONE(s -> s.replaceAll("(\d{3})\d{4}(\d{4})", "$1****$2"));

    private final Desensitized desensitized;

    DesensitizedStrategy(Desensitized desensitized){
        this.desensitized = desensitized;
    }

    public Desensitized getDesensitize() {
        return desensitized;
    }
}

定义的脱敏字段中采用了Function函数来实现。

/**
 * @author: jiangjs
 * @description: 脱敏接口
 * @date: 2022/4/15 16:22
 **/
public interface Desensitized extends Function<String,String> {
}

2.1.2 创建注解

/**
 * @author: jiangjs
 * @description: 使用jackson进行数据脱敏
 * @date: 2023/6/1 15:50
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = JacksonDataDesensitized.class)
public @interface JsonSensitive {
    DesensitizedStrategy strategy();
}

@JsonSerialize:实现数据的序列化,using则是实现序列化的具体类

2.1.3 创建序列化类

/**
 * @author: jiangjs
 * @description: 通过jackson数据序列化来实现脱敏
 * @date: 2023/6/1 15:54
 **/
@NoArgsConstructor
@AllArgsConstructor
public class JacksonDataDesensitized extends JsonSerializer<String> implements ContextualSerializer {

    /**
     * 脱敏数据
     */
    private DesensitizedStrategy strategy;

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeString(strategy.getDesensitize().apply(value));
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        if (Objects.isNull(property)){
           return prov.findNullValueSerializer(null);
        }
        //校验当前bean是否为String
        if (Objects.equals(property.getType().getRawClass(),String.class)){
            JsonSensitive sensitive = property.getAnnotation(JsonSensitive.class);
            if (Objects.isNull(sensitive)){
                sensitive = property.getContextAnnotation(JsonSensitive.class);
            }
            if (Objects.nonNull(sensitive)){
                return new JacksonDataDesensitized(sensitive.strategy());
            }
        }
        return prov.findValueSerializer(property.getType(), property);
    }
}

创建的序列化类继承了JsonSerializer类,指定了数据的类型,实现了ContextualSerializer上下文序列化来保证JsonSerializer不为空。

JsonGenerator:用来生成Json格式的内容,可以使用JsonFactory来生成一个实例。

2.1.4 使用注解

/**
 * @author: jiangjs
 * @description:
 * @date: 2022/4/15 11:33
 **/
@Data
@Accessors(chain = true)
@EqualsAndHashCode
public class UserInfo implements Serializable{

    /**
     * 主键
     */
    private Integer id;

    /**
     * 用户名
     */
    private String userName;

    /**
     * 昵称
     */
    @JsonSensitive(strategy = DesensitizedStrategy.USER_NAME)
    private String nickName;

    /**
     * 手机号
     */
    @JsonSensitive(strategy = DesensitizedStrategy.PHONE)
    private String phone;

    /**
     * 身份证号
     */
    @JsonSensitive(strategy = DesensitizedStrategy.ID_CARD)
    private String idCard;

    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss",timezone = "GTM+8")
    private Date createTime;

    private static final long serialVersionUID = 1L;
}

在实体中通过注解来指定各字段脱敏的类型。

2.1.4 测试

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/6/15 11:31
 **/
@RestController
@RequestMapping("/desensitized")
public class DesensitizedController {

    @GetMapping("/getUserInfo.do")
    public UserInfo getUserInfo(){
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1).setUserName("zhangsan").setNickName("张三")
                .setIdCard("450218199103458901")
                .setPhone("13456789012");
        return userInfo;
    }
}

输出:

Jackson-自定义注解及实现数据脱敏、枚举转换

从输出结果中,我们可以看到使用注解的字段已经实现数据脱敏。

2.2 通用枚举转换

需求:在实体中有时候会用到枚举,前端往往会传递一个值保存到数据,但是查询返回时往往又是对应的另外的值。因此做一个通用的枚举类型转换,查询数据时进行赋值。

2.2.1 创建注解

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/6/15 16:08
 **/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = TypeChangeSerialize.class)
public @interface TypeChange {
     //值得替换 导出是{a_id,b_id} 导入反过来,所以只用写一个
     String[] replace() default {};
}

replace():仿照Easypoi中@Excel注解中的replace。

2.2.1 创建序列化实现类

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/6/15 16:21
 **/
@AllArgsConstructor
public class TypeChangeSerialize extends JsonSerializer<Object> implements ContextualSerializer {

    private final String[] annotationValues;

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        String[] types = this.annotationValues;
        Map<String, String> typeMap = new ConcurrentHashMap<>();
        if (Objects.nonNull(types) && types.length > 0) {
            for (String type : types) {
                String[] s = type.split("_");
                typeMap.put(s[0],s[1]);
            }
        }
        gen.writeString(typeMap.get(String.valueOf(value)));
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property) throws JsonMappingException {
        if (Objects.isNull(property)){
            return provider.findNullValueSerializer(null);
        }
        //校验当前bean是否为String或Integer
        if (Objects.equals(property.getType().getRawClass(),String.class) ||
                Objects.equals(property.getType().getRawClass(),Integer.class)){
            TypeChange typeChange = property.getAnnotation(TypeChange.class);
            if (Objects.isNull(typeChange)){
                typeChange = property.getContextAnnotation(TypeChange.class);
            }
            if (Objects.nonNull(typeChange)){
                return new TypeChangeSerialize(typeChange.replace());
            }
        }
        return provider.findValueSerializer(property.getType(), property);
    }
}

annotationValues:定义接受注解replace的值。

new TypeChangeSerialize(typeChange.replace()):初始化序列化处理类,将获取到的replace()值赋值给annotationValues。

2.2.3 使用注解

/**
 * @author: jiangjs
 * @description:
 * @date: 2022/4/15 11:33
 **/
@Data
@Accessors(chain = true)
@EqualsAndHashCode
public class UserInfo implements Serializable{

    /**
     * 主键
     */
    private Integer id;

    @TypeChange(replace = {"1_男","2_女"})
    private Integer gender;

    private static final long serialVersionUID = 1L;
}

@TypeChange(replace = {"1_男","2_女"}):按照注解的规则,赋值replace。

2.2.4 测试

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/6/15 11:31
 **/
@RestController
@RequestMapping("/desensitized")
public class DesensitizedController {

    @GetMapping("/getUserInfo.do")
    public UserInfo getUserInfo(){
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1).setGender(1);
        return userInfo;
    }
}

输出:

Jackson-自定义注解及实现数据脱敏、枚举转换

从输出结果来看,gender赋值为1,序列化后返回的是对应的中文:男。也就是说,这个实现了我们需求,即字段进行了替换。

上述就是我在工作中使用jackson的一些应用,大家可以举一反三来实现自己的需求,例如:自定义时间格式化等等。

源码:github.com/lovejiashn/…

工欲善其事,必先利其器,小伙伴们撸起来吧。