通过Jackson实现敏感词过滤和类型转换的功能

背景

目前正在做老项目迁移重构工作(Clojure转Java),因历史原因项目中给到端上的Json数据中和(id/user_id/order_id)等相关的id字段需要以string类型给到端上。

已有clojure实现

祖传项目是通过clojure实现的,clojure是一门弱语言和JavaScript一样任何对象都是看做一个map,所以在clojure里面针对这个long->string的功能,实现起来很简单;只需要递归遍历map判断key是否存在于定义的set里面即可,如果满足条件,直接根据是否为list进行toString的处理;

需要实现的功能

实现一个Java方法,传入一个对象进行返回序列化的Json字符串。序列化的同时如果遇到满足条件的字段需要对该字段进行 toString 处理,如果是List<Objec>需要转换为 List<String>

如何通过Java实现?

因为项目中使用的是jackson序列化json,所以很容易想到的就是使用jackson提供的AnnotationIntrospector,在对应的字段上添加 注解 ,然后自定义AnnotationIntrospector在匹配对应注解的时候返回自定义的Serializer。

tips:仅使用@JsonSerialize(using = ToStringSerializer.class)是不能处理 List<Long> -> List<String>这种情况的

AnnotationIntrospector 实现 long->toString 功能

 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.FIELD})
 public @interface CustomToString {
 
 }
 
 @Data
 public class XxxDTO{
   
     @CustomToString
     private  Long id;
 
     @CustomToString
     private  List<Long>  uids;
   
     private String desc;
   
 }
 
 class IdFieldToStringSerializer extends JsonSerializer<Object> {
 
         public static final IdFieldToStringSerializer INSTANCE = new IdFieldToStringSerializer();
 
         private IdFieldToStringSerializer() {
        }
 
         @Override
         public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
             if (value instanceof Collection) {
                 gen.writeStartArray();
                 for (Object item : (Collection) value) {
                     gen.writeString(String.valueOf(item));
                }
                 gen.writeEndArray();
                 return;
            }
             gen.writeString(String.valueOf(value));
        }
 
    }
 
 public class DefaultAnnotationIntrospector extends JacksonAnnotationIntrospector {
 
   @Override
     public Object findSerializer(Annotated a) {
         if(a.hasAnnotation(CustomToString.class)){
           return IdFieldToStringSerializer.INSTANCE;
        }
         return super.findSerializer(a);
    }
 
 }

扩展-AnnotationIntrospector 实现 字段过滤 功能(也可以注解增加属性,用于指定环境过滤等功能)

 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.FIELD})
 public @interface CustomFilter {
 
 }
 
 @Data
 public class XxxDTO{
   
     @CustomFilter
     private  Long id;
   
     private String desc;
   
 }
 
 public class DefaultAnnotationIntrospector extends JacksonAnnotationIntrospector {
 
       @Override
       public boolean hasIgnoreMarker(AnnotatedMember m) {
           if(m.hasAnnotation(CustomFilter.class)){
             return true;
          }
           return super.hasIgnoreMarker(m);
        }
 
 }

这里字段过滤其实功能只相当于 @JsonIgnore ,需要增加CustomToString的属性,比如 env;然后在DefaultAnnotationIntrospector中根据env值再做判断

AnnotationIntrospector实现分析

副作用

在后续开发过程中,在我们定义新的Object时,需要参照对应的field表对对应字段增加注解。如果此时突然要对某个字段过滤或者toString,那又得从头挨着挨着检查添加注解。增加开发成本和维护成本

优化升级

通过对 new ObjectMapper().writeValueAsString() 的执行流程 debug分析,发现对象的属性都被解析成 BeanProperty了,然后会调用 SerializerProvider 的方法寻找合适的Serializer进行序列化

实现 long->toString 功能
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.BeanProperty;
 import com.fasterxml.jackson.databind.JavaType;
 import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.JsonSerializer;
 import com.fasterxml.jackson.databind.SerializationConfig;
 import com.fasterxml.jackson.databind.SerializerProvider;
 import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
 import com.fasterxml.jackson.databind.ser.SerializerFactory;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.util.ObjectUtils;
 
 /**
  * @class-name: IdFieldToStringSerializerProvider
  * @description:
  * @author: Mr.Zeng
  */
 @Slf4j
 public class IdFieldToStringSerializerProvider extends DefaultSerializerProvider {
 
     private static final String FILED_SOURCE = "admin_id、id、msg_id、uid、uids、user_id、member_uids、parent_id";
 
     private static final Set<String> FILED_SET = new HashSet<>(
             Arrays.stream(FILED_SOURCE.split(";|,|;|,|、"))
                    .map(String::trim)
                    .filter(o -> !ObjectUtils.isEmpty(o))
                    .collect(Collectors.toSet())
    );
 
     public IdFieldToStringSerializerProvider() {
         super();
    }
 
     public IdFieldToStringSerializerProvider(IdFieldToStringSerializerProvider src) {
         super(src);
    }
 
     protected IdFieldToStringSerializerProvider(SerializerProvider src, SerializationConfig conf, SerializerFactory f) {
         super(src, conf, f);
    }
 
     @Override
     public DefaultSerializerProvider copy() {
         if (this.getClass() != IdFieldToStringSerializerProvider.class) {
             return super.copy();
        }
         return new IdFieldToStringSerializerProvider(this);
    }
 
     @Override
     public IdFieldToStringSerializerProvider createInstance(SerializationConfig config, SerializerFactory jsf) {
         return new IdFieldToStringSerializerProvider(this, config, jsf);
    }
 
     @Override
     public void serializeValue(JsonGenerator gen, Object value) throws IOException {
         //log.debug("serializeValue[JsonGenerator,Object], {}", value);
         super.serializeValue(gen, value);
    }
 
     @Override
     public JsonSerializer<Object> findValueSerializer(Class<?> valueType, BeanProperty property)
             throws JsonMappingException {
         JsonSerializer<Object> target = findSerializerByBeanProperty(property);
         if (target != null) {
             return target;
        }
         return super.findValueSerializer(valueType, property);
    }
 
     @Override
     public JsonSerializer<Object> findValueSerializer(JavaType valueType, BeanProperty property)
             throws JsonMappingException {
         JsonSerializer<Object> target = findSerializerByBeanProperty(property);
         if (target != null) {
             return target;
        }
         return super.findValueSerializer(valueType, property);
    }
 
     @Override
     public JsonSerializer<Object> findPrimaryPropertySerializer(JavaType valueType, BeanProperty property)
             throws JsonMappingException {
         JsonSerializer<Object> target = findSerializerByBeanProperty(property);
         if (target != null) {
             return target;
        }
         return super.findPrimaryPropertySerializer(valueType, property);
    }
 
     @Override
     public JsonSerializer<Object> findPrimaryPropertySerializer(Class<?> valueType, BeanProperty property)
             throws JsonMappingException {
         JsonSerializer<Object> target = findSerializerByBeanProperty(property);
         if (target != null) {
             return target;
        }
         return super.findPrimaryPropertySerializer(valueType, property);
    }
 
     private JsonSerializer<Object> findSerializerByBeanProperty(BeanProperty property) {
         String propertyName = Optional.ofNullable(property).map(BeanProperty::getName).orElse("");
         //log.debug("findValueSerializer[Class<?>:{},property:{}]", valueType.getName(), propertyName);
         if (FILED_SET.contains(propertyName)) {
             if (log.isDebugEnabled()) {
                 log.debug("property[{}] meets the “ToString“ condition. using IdFieldToStringSerializer", propertyName);
            }
             return IdFieldToStringSerializer.INSTANCE;
        }
         return null;
    }
 
     static class IdFieldToStringSerializer extends JsonSerializer<Object> { 
​ 
       public static final IdFieldToStringSerializer INSTANCE = new IdFieldToStringSerializer(); 
​ 
       private IdFieldToStringSerializer() { 
      } 
​ 
       @Override 
       public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { 
           if (value instanceof Collection) { 
               gen.writeStartArray(); 
               for (Object item : (Collection) value) { 
                   gen.writeString(String.valueOf(item)); 
              } 
               gen.writeEndArray(); 
               return; 
          } 
           gen.writeString(String.valueOf(value)); 
      } 
​ 
  } 
​ 
}
实现 字段过滤 功能 (这种方式仅仅是伪实现,只能实现值转为 * 或者 空字符串,不支持字段过滤)
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.BeanProperty;
 import com.fasterxml.jackson.databind.JavaType;
 import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.JsonSerializer;
 import com.fasterxml.jackson.databind.SerializationConfig;
 import com.fasterxml.jackson.databind.SerializerProvider;
 import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
 import com.fasterxml.jackson.databind.ser.SerializerFactory;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.util.ObjectUtils;
 /**
  * @class-name: MaskFiledSerializerProvider
  * @description:
  * @author: Mr.Zeng
  */
 @Slf4j
 public class MaskFiledSerializerProvider extends DefaultSerializerProvider {
 
     private static final String FILED = "ticket,text,content,oauth,tcp,ws_host,file";
 
     private static final Set<String> FILED_SET = new HashSet<>(
             Arrays.stream(FILED.split(";|,|;|,|、"))
                    .map(String::trim)
                    .filter(o -> !ObjectUtils.isEmpty(o))
                    .collect(Collectors.toSet())
    );
 
     public MaskFiledSerializerProvider() {
         super();
    }
 
     public MaskFiledSerializerProvider(MaskFiledSerializerProvider src) {
         super(src);
    }
 
     protected MaskFiledSerializerProvider(SerializerProvider src, SerializationConfig config, SerializerFactory f) {
         super(src, config, f);
    }
 
     @Override
     public DefaultSerializerProvider copy() {
         if (this.getClass() != MaskFiledSerializerProvider.class) {
             return super.copy();
        }
         return new MaskFiledSerializerProvider(this);
    }
 
     @Override
     public MaskFiledSerializerProvider createInstance(SerializationConfig config, SerializerFactory jsf) {
         return new MaskFiledSerializerProvider(this, config, jsf);
    }
 
     @Override
     public void serializeValue(JsonGenerator gen, Object value) throws IOException {
         //log.debug("serializeValue[JsonGenerator,Object], {}", value);
         super.serializeValue(gen, value);
    }
 
     @Override
     public JsonSerializer<Object> findValueSerializer(Class<?> valueType, BeanProperty property)
             throws JsonMappingException {
         JsonSerializer<Object> target = findSerializerByBeanProperty(property);
         if (target != null) {
             return target;
        }
         return super.findValueSerializer(valueType, property);
    }
 
     @Override
     public JsonSerializer<Object> findValueSerializer(JavaType valueType, BeanProperty property)
             throws JsonMappingException {
         JsonSerializer<Object> target = findSerializerByBeanProperty(property);
         if (target != null) {
             return target;
        }
         return super.findValueSerializer(valueType, property);
    }
 
     @Override
     public JsonSerializer<Object> findPrimaryPropertySerializer(JavaType valueType, BeanProperty property)
             throws JsonMappingException {
         JsonSerializer<Object> target = findSerializerByBeanProperty(property);
         if (target != null) {
             return target;
        }
         return super.findPrimaryPropertySerializer(valueType, property);
    }
 
     @Override
     public JsonSerializer<Object> findPrimaryPropertySerializer(Class<?> valueType, BeanProperty property)
             throws JsonMappingException {
         JsonSerializer<Object> target = findSerializerByBeanProperty(property);
         if (target != null) {
             return target;
        }
         return super.findPrimaryPropertySerializer(valueType, property);
    }
 
     private JsonSerializer<Object> findSerializerByBeanProperty(BeanProperty property) {
         String propertyName = Optional.ofNullable(property).map(BeanProperty::getName).orElse("");
         //log.debug("findValueSerializer[Class<?>:{},property:{}]", valueType.getName(), propertyName);
         if (FILED_SET.contains(propertyName)) {
             if (log.isDebugEnabled()) {
                 log.debug("property[{}] meets the “Mask“ condition. value to '***' ", propertyName);
            }
             return MaskSerializer.INSTANCE;
        }
         return null;
    }
 
     static class MaskSerializer extends JsonSerializer<Object> { 
​ 
       public static final MaskSerializer INSTANCE = new MaskSerializer(); 
​ 
       private MaskSerializer() { 
      } 
​ 
       @Override 
       public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { 
           gen.writeString("***"); 
      } 
​ 
  } 
​ 
}
 
posted @ 2022-04-19 21:26  原则  阅读(537)  评论(0编辑  收藏  举报