<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          太強(qiáng)了,一個(gè)注解搞定接口返回?cái)?shù)據(jù)脫敏

          共 9247字,需瀏覽 19分鐘

           ·

          2022-07-07 21:44

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來,我們一起精進(jìn)!你不來,我和你的競爭對手一起精進(jìn)!

          編輯:業(yè)余草

          juejin.cn/post/7110110794188062727

          推薦:https://www.xttblog.com/?p=5349

          下午愜意時(shí)光,突然產(chǎn)品小姐姐走到我面前,打斷我短暫的摸魚time,企圖與我進(jìn)行深入交流,還好我早有防備沒有閃,打開瑞star的點(diǎn)單頁面,暗示沒有一杯coffee解決不了的需求,需求是某些接口返回的信息,涉及到敏感數(shù)據(jù)的必須進(jìn)行脫敏操作,我思考一反,表示某問題,馬上安排。

          思路

          1.要做成可配置多策略的脫敏操作,要不然一個(gè)個(gè)接口進(jìn)行脫敏操作,重復(fù)的工作量太多,很顯然違背了“多寫一行算我輸”的程序員規(guī)范,思來想去,定義數(shù)據(jù)脫敏注解和數(shù)據(jù)脫敏邏輯的接口, 在返回類上,對需要進(jìn)行脫敏的屬性加上,并指定對應(yīng)的脫敏策略操作。

          2.接下來我只需要攔截控制器返回的數(shù)據(jù),找到帶有脫敏注解的屬性操作即可,一開始打算用@ControllerAdvice去實(shí)現(xiàn),但發(fā)現(xiàn)需要自己去反射類獲取注解,當(dāng)返回對象比較復(fù)雜,需要遞歸去反射,性能一下子就會降低,于是換種思路,我想到平時(shí)使用的@JsonFormat,跟我現(xiàn)在的場景很類似,通過自定義注解跟字段解析器,對字段進(jìn)行自定義解析,tql

          代碼

          1. 自定義數(shù)據(jù)注解,并可以配置數(shù)據(jù)脫敏策略

          @Target({ElementType.FIELD, ElementType.TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public @interface DataMasking {

              DataMaskingFunc maskFunc() default DataMaskingFunc.NO_MASK;

          }

          2. 自定義Serializer,參考jackson的StringSerializer,下面的示例只針對String類型進(jìn)行脫敏


          public interface DataMaskingOperation {

              String MASK_CHAR = "*";
              
              String mask(String content, String maskChar);

          }

          public enum DataMaskingFunc {

               /**
               *  脫敏轉(zhuǎn)換器
               */

               NO_MASK((str, maskChar) -> {
                  return str;
               }),
               ALL_MASK((str, maskChar) -> {
                  if (StringUtils.hasLength(str)) {
                      StringBuilder sb = new StringBuilder();
                      for (int i = 0; i < str.length(); i++) {
                          sb.append(StringUtils.hasLength(maskChar) ? maskChar : DataMaskingOperation.MASK_CHAR);
                      }
                      return sb.toString();
                  } else {
                      return str;
                  }
              });

              private final DataMaskingOperation operation;

              private DataMaskingFunc(DataMaskingOperation operation) {
                  this.operation = operation;
              }

              public DataMaskingOperation operation() {
                  return this.operation;
              }

          }

          public final class DataMaskingSerializer extends StdScalarSerializer<Object{
              private final DataMaskingOperation operation;

              public DataMaskingSerializer() {
                  super(String.classfalse);
                  this.operation = null;
              }

              public DataMaskingSerializer(DataMaskingOperation operation) {
                  super(String.classfalse);
                  this.operation = operation;
              }


              public boolean isEmpty(SerializerProvider prov, Object value) {
                  String str = (String)value;
                  return str.isEmpty();
              }

              public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
                  if (Objects.isNull(operation)) {
                      String content = DataMaskingFunc.ALL_MASK.operation().mask((String) value, null);
                      gen.writeString(content);
                  } else {
                      String content = operation.mask((String) value, null);
                      gen.writeString(content);
                  }
              }

              public final void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throws IOException {
                  this.serialize(value, gen, provider);
              }

              public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
                  return this.createSchemaNode("string"true);
              }

              public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
                  this.visitStringFormat(visitor, typeHint);
              }
          }

          3. 自定義AnnotationIntrospector,適配我們自定義注解返回相應(yīng)的Serializer

          @Slf4j
          public class DataMaskingAnnotationIntrospector extends NopAnnotationIntrospector {

              @Override
              public Object findSerializer(Annotated am) {
                  DataMasking annotation = am.getAnnotation(DataMasking.class);
                  if (annotation != null) {
                      return new DataMaskingSerializer(annotation.maskFunc().operation());
                  }
                  return null;
              }

          }

          4. 覆蓋ObjectMapper

          @Configuration(
                  proxyBeanMethods = false
          )
          public class DataMaskConfiguration {

              @Configuration(
                      proxyBeanMethods = false
              )
              @ConditionalOnClass({Jackson2ObjectMapperBuilder.class})
              static class JacksonObjectMapperConfiguration 
          {
                  JacksonObjectMapperConfiguration() {
                  }

                  @Bean
                  @Primary
                  ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
                      ObjectMapper objectMapper = builder.createXmlMapper(false).build();
                      AnnotationIntrospector ai = objectMapper.getSerializationConfig().getAnnotationIntrospector();
                      AnnotationIntrospector newAi = AnnotationIntrospectorPair.pair(ai, new DataMaskingAnnotationIntrospector());
                      objectMapper.setAnnotationIntrospector(newAi);
                      return objectMapper;
                  }
              }

          }

          5. 返回對象加上注解

          public class User implements Serializable {
              /**
               * 主鍵ID
               */

              private Long id;

              /**
               * 姓名
               */

              @DataMasking(maskFunc = DataMaskingFunc.ALL_MASK)
              private String name;

              /**
               * 年齡
               */

              private Integer age;

              /**
               * 郵箱
               */

              @DataMasking(maskFunc = DataMaskingFunc.ALL_MASK)
              private String email;

          }

          瀏覽 30
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产成人秘 一区二区三区东京热 | 国内三级在线 | 天天干夜夜骑 | 亚洲三级影视 | 99无码视频。 |