Java日志脱敏(二)——fastjson Filter + 注解 + 工具类实现

背景简介

日志脱敏 是常见的安全需求,最近公司也需要将这一块内容进行推进。看了一圈网上的案例,很少有既轻量又好用的轮子可以让我直接使用。我一直是反对过度设计的,而同样我认为轮子就应该是可以让人拿去直接用的。所以我准备分享两篇博客分别实现两种日志脱敏方案。

方案分析

  • logback MessageConverter + 正则匹配上一篇介绍此方法

    • 优势
      • 侵入性低、工作量极少, 只需要修改xml配置文件,适合老项目
    • 劣势
      • 效率低,会对每一行日志都进行正则匹配检查,效率受日志长度影响,日志越长效率越低,影响日志吞吐量
      • 因基于正则匹配 存在错杀风险,部分内容难以准确识别
  • fastjson Filter + 注解 + 工具类本篇博客介绍

    • 优势
      • 性能损耗低、效率高、扩展性强,精准脱敏,适合QPS较高日志吞吐量较大的项目。
    • 劣势
      • 侵入性较高,需对所有可能的情况进行脱敏判断
      • 存在漏杀风险,全靠开发控制

本篇博客部分代码未贴出,可以在上一篇博客中找到 传送门:Java日志脱敏——基于logback MessageConverter实现

fastjson Filter + 注解 + 工具类

流程图解

依托于 alibaba fastjson 提供的扩展能力,自定义ContextValueFilter,在将对象JSON序列化时,返回脱敏后的value,实现打印日志脱敏
在这里插入图片描述

代码案例

定义注解

定义 元注解 用于标记脱敏策略注解

package com.zhibo.log.sensitive.annotation.metadata;

import java.lang.annotation.*;

/**
 * @Author: Zhibo
 * @Description: 用于自定义 sensitive 脱敏策略注解,
 */
@Inherited
@Documented
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveStrategy {
}

定义 手机号 脱敏策略注解

package com.zhibo.log.sensitive.annotation.strategy;

import com.zhibo.log.sensitive.annotation.metadata.SensitiveStrategy;
import java.lang.annotation.*;

/**
 * @Author: Zhibo
 * @Description: 手机号脱敏注解
 */
@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@SensitiveStrategy
public @interface SensitiveStrategyPhone {
}

定义 身份证号码 脱敏策略注解

package com.zhibo.log.sensitive.annotation.strategy;

import com.zhibo.log.sensitive.annotation.metadata.SensitiveStrategy;
import java.lang.annotation.*;

/**
 * @Author: zhibo
 * @Description: 中国身份证号脱敏注解
 */
@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@SensitiveStrategy
public @interface SensitiveStrategyIdNo {
}

定义 Map 类型扩展注解,考虑到部分自定义Bean 中会有一些Map的成员变量,而Map中可能也有敏感信息需要处理。

package com.zhibo.log.sensitive.annotation;

import java.lang.annotation.*;

/**
 * @Author: Zhibo
 * @Description: 针对Object对象中如果存在 Map参数,而Map存在敏感字段时使用<br></>
 * 如果对象中属性为一个Map,则可以使用这个注解指定,Map中特定Key的加密规则。
 */
@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveMap {
    /**
     * 用于构建一个Map 传递格式 "key1","value1","key2","value2"....
     * key 为Map中需要脱敏的key,value为脱敏规则
     *
     * 案例Demo:
     * @SensitiveMap({"phone", LogSensitiveConstants.STRATEGY_PHONE})
     * private Map<String,Object> map = new HashMap<>();
     *     {
     *         map.put("key", "value");
     *         map.put("name","王大锤");
     *         map.put("phone","18123456789");
     *     }
     */
    String[] value();

}

将注解与对应的脱敏策略方法绑定
package com.zhibo.log.sensitive.core.util.strategy;

import com.zhibo.log.sensitive.annotation.metadata.SensitiveStrategy;
import com.zhibo.log.sensitive.annotation.strategy.*;
import com.zhibo.log.sensitive.api.IStrategy;
import com.zhibo.log.format.LogSensitiveConstants;
import com.zhibo.log.sensitive.core.strategory.*;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: zhibo
 * @Description: 系统中内置的策略映射、注解和实现之间映射
 */
public final class SensitiveStrategyBuiltInUtil {
    private SensitiveStrategyBuiltInUtil(){}

    /** 注解和实现策略的映射关系 */
    private static final Map<Class<? extends Annotation>, IStrategy> CLASS_MAP = new HashMap<>();

    private static final Map<String, IStrategy> STRATEGY_MAP = new HashMap<>();

    static {
        StrategyAddress strategyAddress =new StrategyAddress();
        StrategyIdNo strategyIdNo = new StrategyIdNo();
        StrategyPhone strategyPhone = new StrategyPhone();

        CLASS_MAP.put(SensitiveStrategyAddress.class, strategyAddress);
        CLASS_MAP.put(SensitiveStrategyIdNo.class, strategyIdNo);
        CLASS_MAP.put(SensitiveStrategyPhone.class, strategyPhone);

        STRATEGY_MAP.put(LogSensitiveConstants.STRATEGY_ADDRESS, strategyAddress);
        STRATEGY_MAP.put(LogSensitiveConstants.STRATEGY_ID_NO, strategyIdNo);
        STRATEGY_MAP.put(LogSensitiveConstants.STRATEGY_PHONE, strategyPhone);
    }

    public static IStrategy getStrategy(String key){
        return STRATEGY_MAP.get(key);
    }

    /**
     * 获取对应的系统内置实现
     * @param annotationClass 注解实现类
     * @return 对应的实现方式
     */
    public static IStrategy require(final Class<? extends Annotation> annotationClass) {
        return CLASS_MAP.get(annotationClass);
    }

    /**
     * 获取策略
     * @param annotations 字段对应注解
     * @return 策略
     */
    public static IStrategy getStrategy(final Annotation[] annotations) {
        for (Annotation annotation : annotations) {
            SensitiveStrategy sensitiveStrategy = annotation.annotationType().getAnnotation(SensitiveStrategy.class);
            if (null != sensitiveStrategy) {
                return SensitiveStrategyBuiltInUtil.require(annotation.annotationType());
            }
        }
        return null;
    }
}
实现JSON序列化过滤器

关键代码了,基本的逻辑都在这里

package com.zhibo.log.sensitive.core.support.filter;

import com.alibaba.fastjson.serializer.BeanContext;
import com.alibaba.fastjson.serializer.ContextValueFilter;
import com.zhibo.log.sensitive.annotation.SensitiveMap;
import com.zhibo.log.sensitive.api.IStrategy;
import com.zhibo.log.sensitive.core.context.SensitiveContext;
import com.zhibo.log.sensitive.core.util.strategy.SensitiveStrategyBuiltInUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @Author: Zhibo
 * @Description: 默认的上下文过滤器,支持处理 对象、数组、集合、Map 等类型数据,支持自定义脱敏策略
 */
public class SensitiveContextValueFilter implements ContextValueFilter {

    /** 脱敏上下文 */
    private final SensitiveContext sensitiveContext;

    public SensitiveContextValueFilter(SensitiveContext context) {
        this.sensitiveContext = context;
    }

    @Override
    public Object process(BeanContext context, Object object, String name, Object value) {
        // 对象为 MAP 的时候,FastJson map 对应的 context 为 NULL
        if(null == context) {
            //对象为MAP则检测是否存在指定脱敏策略
            if (null == sensitiveContext.getMapDesStrategy() || value == null){
                return value;
            }else {
                //执行匹配指定脱敏策略
                return desMapValue(name,value);
            }
        }

        // 信息初始化
        final Field field = context.getField();
        if (field == null){
            return value;
        }

        return handleSensitive(value,field);
    }

    /**
     * Map 类型数据脱敏
     * @param name key
     * @param value 未被脱敏的原对象
     * @return 脱敏后的新对象
     */
    private Object desMapValue(String name, Object value){
        String desStrategy = sensitiveContext.getMapDesStrategy().get(name);
        if (desStrategy != null){
            IStrategy strategy = SensitiveStrategyBuiltInUtil.getStrategy(desStrategy);
            if (strategy != null){
                if (value.getClass().isArray()){
                    return desArray(value,strategy);
                } else if (value instanceof Collection) {
                    return desCollection(value,strategy);
                }else {
                    return strategy.des(value);
                }
            }
        }
        return value;
    }

    /**
     * 处理脱敏信息
     *
     * @param field      当前字段
     */
    private Object handleSensitive(final Object originalFieldVal, final Field field) {
        // 原始字段值
        IStrategy strategy = null;

        //处理 @SensitiveMap
        SensitiveMap sensitiveMap = field.getAnnotation(SensitiveMap.class);
        if (null != sensitiveMap) {
            String[] entry =sensitiveMap.value();
            if (entry != null && entry.length>0 && (entry.length & 1) == 0){
                // 不为null 且长度一致则将用户指定的脱敏规则加入本次脱敏上下文
                Map<String,String> map = sensitiveContext.getMapDesStrategy();
                if (map == null){
                    map = new HashMap<>();
                    sensitiveContext.setMapDesStrategy(map);
                }
                for (int i = 1; i<entry.length;i+=2){
                    map.put(entry[i-1],entry[i]);
                }
            }
            return originalFieldVal;
        }

        // 系统内置自定义注解的处理,获取所有的注解
        Annotation[] annotations = field.getAnnotations();
        if (null != annotations && annotations.length > 0) {
             strategy = SensitiveStrategyBuiltInUtil.getStrategy(annotations);
        }
        // 判断是否获取到指定脱敏规则,如有则进行脱敏处理
        if (null != strategy){
            Class fieldTypeClass = field.getType();
            if(fieldTypeClass.isArray()) {
                // 为数组类型
                return desArray(originalFieldVal,strategy);
            }else if (Collection.class.isAssignableFrom(fieldTypeClass)){
                // 为集合类型
                return desCollection(originalFieldVal,strategy);
            } else {
                // 普通类型
                return strategy.des(originalFieldVal);
            }
        }
        return originalFieldVal;
    }


    /**
     * 处理数据类型,根据元素依次遍历脱敏
     * @param value 未被脱敏的原对象
     * @param strategy 脱敏策略
     * @return 脱敏后的新对象
     */
    private Object desArray(Object value,IStrategy strategy){
        Object[] arrays = (Object[]) value;
        if (null != arrays && arrays.length > 0) {
            final int arrayLength = arrays.length;
            Object newArray = new Object[arrayLength];
            for (int i = 0; i < arrayLength; i++) {
                Array.set(newArray, i, strategy.des(arrays[i]));
            }
            return newArray;
        }
        return value;
    }

    /**
     * 处理集合类型,根据元素依次遍历脱敏
     * @param value 未被脱敏的原对象
     * @param strategy 脱敏策略
     * @return 脱敏后的新对象
     */
    private Object desCollection(Object value,IStrategy strategy){
        final Collection<Object> entryCollection = (Collection<Object>) value;
        if (null != entryCollection && !entryCollection.isEmpty()) {
            List<Object> newResultList = new ArrayList<>(entryCollection.size());
            for (Object entry : entryCollection) {
                newResultList.add(strategy.des(entry));
            }
            return newResultList;
        }
        return value;
    }
}

上下文对象 SensitiveContext,目前里面只有一个Map,用来存储对map类型元素进行脱敏的策略规则

package com.zhibo.log.sensitive.core.context;

import java.util.Map;

/**
 * @Author: Zhibo
 * @Description: 脱敏上下文
 */
public class SensitiveContext {

    private SensitiveContext(){}
    /**
     * Map中Key的指定脱敏规则
     */
    private Map<String,String> mapDesStrategy;

    public Map<String, String> getMapDesStrategy() {
        return mapDesStrategy;
    }

    public void setMapDesStrategy(Map<String, String> mapDesStrategy) {
        this.mapDesStrategy = mapDesStrategy;
    }

    /**
     * 新建一个对象实例
     * @return this
     */
    public static SensitiveContext newInstance() {
        return new SensitiveContext();
    }
}
脱敏工具类使用入口

支持自定义Bean 脱敏 根据注解规则;
有时候日志打印的直接就是一个Map,没有写注解的地方,这里也支持了;
还有很多场景直接打印 参数 直接就是一个String的文本,所以也支持 直接对文本进行脱敏;
当然还有数组、集合的直接打印需求也都支持了;

package com.zhibo.log.sensitive.core;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.ContextValueFilter;
import com.zhibo.log.format.LogSensitiveConstants;
import com.zhibo.log.sensitive.api.IStrategy;
import com.zhibo.log.sensitive.core.context.SensitiveContext;
import com.zhibo.log.sensitive.core.support.filter.SensitiveContextValueFilter;
import com.zhibo.log.sensitive.core.util.strategy.SensitiveStrategyBuiltInUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @Author: Zhibo
 * @Description: 脱敏工具类
 */
public final class LogDesensitizeUtil {
    private static final Logger log = LoggerFactory.getLogger(LogDesensitizeUtil.class);

    private LogDesensitizeUtil(){}


    /**
     * 返回脱敏后的对象 json
     * null 对象,返回字符串 "null"
     * @param object 对象/map 自定义的对象,依据对象内注解进行脱敏
     * @param entry 用于构建一个Map 传递格式 "key1","value1","key2","value2".... 如非map对象可以忽略
     * @return 结果 json,如处理异常则直接返回原对象
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public static String desJson(Object object, String... entry) {

        try {
            //String类型则直接调用desString
            if (object instanceof String){
                return desString(object.toString());
            }

            Map<String,String> map = null;
            if (object!= null && entry != null && entry.length>0 && (entry.length & 1) == 0){
                map = new HashMap<>();
                for (int i = 1; i<entry.length;i+=2){
                    map.put(entry[i-1],entry[i]);
                }
            }
            return desJson(object,map);
        } catch (Exception e) {
            log.warn("对象脱敏失败 desJson异常 异常信息:",e);
            return JSON.toJSONString(object);
        }
    }

    /**
     * 返回脱敏后的对象 json
     * @param object 需要被脱敏的对象 or Map
     * @param desStrategy Map中指定Key的脱敏策略,此策略只针对String 类型的值进行脱敏,如Map中存储的是对象,请使用注解进行标记
     * @return 结果 json,如处理异常则直接返回原对象
     */
    public static String desJson(Object object, Map<String, String> desStrategy) {
        try {
            if (null == object) {
                return JSON.toJSONString(object);
            }

            final SensitiveContext context = SensitiveContext.newInstance();
            context.setMapDesStrategy(desStrategy);
            ContextValueFilter filter = new SensitiveContextValueFilter(context);

            return JSON.toJSONString(object, filter);
        } catch (Exception e) {
            log.warn("对象脱敏失败 desJson异常 异常信息:",e);
            return JSON.toJSONString(object);
        }
    }

    /**
     * 通过正则匹配,返回脱敏后的内容,当前支持11位手机号、18位身份证号码、地址信息匹配
     * 如已知需脱敏的数据类型,请使用{@link LogDesensitizeUtil#desString(String, String)}方法,
     * @param value 未脱敏的明文
     * @return 结果 已脱敏的文本
     */
    public static String desString(String value) {
        try {
            if (StringUtils.isBlank(value)){
                return value;
            }else if (value.length() == 11){
                // 匹配手机号规则
                if (Pattern.compile(LogSensitiveConstants.PHONE_REGEX).matcher(value).matches()){
                    return desString(value, LogSensitiveConstants.STRATEGY_PHONE);
                }
            }else if (value.length() == 18){
                // 匹配身份证号码规则
                if (Pattern.compile(LogSensitiveConstants.ID_NO_REGEX).matcher(value).matches()){
                    return desString(value, LogSensitiveConstants.STRATEGY_ID_NO);
                }
            }
        } catch (Exception e) {
            log.warn("数据脱敏失败 desString异常 异常信息:",e);
        }
        // 未命中任何规则直接返回明文
        return value;
    }

    /**
     * 依据指定的脱敏策略返回脱敏后的内容
     * @param value 需要被脱敏的文本
     * @param type 指定脱敏策略,详见{@link LogSensitiveConstants},
     *             如脱敏策略不存在,则不进行脱敏处理
     * @return 结果 已脱敏的文本
     */
    public static String desString(String value, String type) {
        try {
            if (StringUtils.isNotBlank(value)){
                IStrategy strategy = SensitiveStrategyBuiltInUtil.getStrategy(type);
                return null == strategy? value : strategy.des(value);
            }
        } catch (Exception e) {
            log.warn("数据脱敏失败 desString异常 异常信息:",e);
        }
        return value;
    }

    /**
     * 依据指定的脱敏策略返回脱敏后的内容
     * @param values 需要被脱敏的文本
     * @param type 指定脱敏策略,详见{@link LogSensitiveConstants},
     *             如脱敏策略不存在,则不进行脱敏处理
     * @return 结果 已脱敏的文本
     */
    public static String desString(String[] values, String type) {
        try {
            IStrategy strategy = SensitiveStrategyBuiltInUtil.getStrategy(type);
            if (null != values && values.length>0 && null != strategy){
                StringBuilder sbd = new StringBuilder("[\"");
                sbd.append(strategy.des(values[0])).append("\"");
                for (int i = 1;i<values.length;i++){
                    sbd.append(",\"").append(strategy.des(values[i])).append("\"");
                }
                sbd.append("]");
                return sbd.toString();
            }
        } catch (Exception e) {
            log.warn("数据脱敏失败 desString异常 type:{} 异常信息:",type,e);
        }
        return JSON.toJSONString(values);
    }

    /**
     * 依据指定的脱敏策略返回脱敏后的内容
     * @param values 需要被脱敏的文本
     * @param type 指定脱敏策略,详见{@link LogSensitiveConstants},
     *             如脱敏策略不存在,则不进行脱敏处理
     * @return 结果 已脱敏的文本
     */
    public static String desString(Collection<String> values, String type) {
        try {
            IStrategy strategy = SensitiveStrategyBuiltInUtil.getStrategy(type);
            if (null != values && values.size()>0 && null != strategy){
                StringBuilder sbd = new StringBuilder("[");
                for (String entry : values) {
                    sbd.append("\"").append(strategy.des(entry)).append("\",");
                }
                sbd.setCharAt(sbd.length()-1,']');
                return sbd.toString();
            }
        } catch (Exception e) {
            log.warn("数据脱敏失败 desString异常 异常信息:",e);
        }
        return JSON.toJSONString(values);
    }
}

测试Demo

package com.zhibo.demo;

import com.zhibo.log.sensitive.annotation.SensitiveMap;
import com.zhibo.log.sensitive.annotation.strategy.SensitiveStrategyIdNo;
import com.zhibo.log.sensitive.annotation.strategy.SensitiveStrategyPhone;
import com.zhibo.log.sensitive.core.LogDesensitizeUtil;
import com.zhibo.log.format.LogSensitiveConstants;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zhibo
 */
@Slf4j
@Data
public class DesDemoBean{
    /** 对身份证进行脱敏*/
    @SensitiveStrategyIdNo
    private String idNo = "421083202411111111";

    /** 对手机号集合进行脱敏 */
    @SensitiveStrategyPhone
    private List<String> mobileList = Arrays.asList("18611111111","18622222222","18633333333");

    private String adderss = "广东省深圳市南山区牛马大厦";

    /**
     * 对Map进行脱敏
     * 传递格式 "key1","value1","key2","value2".... key 为Map中需要脱敏的key,value为脱敏规则
     */
    @SensitiveMap({"idNo", LogSensitiveConstants.STRATEGY_ID_NO,"phone", LogSensitiveConstants.STRATEGY_PHONE, "phoneArr", LogSensitiveConstants.STRATEGY_PHONE})
    private Map<String,Object> map = new HashMap<>();
    {
        map.put("name","吕志博");
        map.put("phone","18123456789");
        map.put("phoneArr",new String[]{"18123456780","18123456781","18123456782"});
        map.put("idNo","421083202411111111");
    }

    public static void main(String[] args){
        System.out.println("------------- 通过注解为对象脱敏Begin  ------------------");
        DesDemoBean user = new DesDemoBean();
        // LogDesensitizeUtil.desJson 为自定义Bean的专用脱敏方法
        System.out.println(LogDesensitizeUtil.desJson(user));
        System.out.println("------------- 通过注解为对象脱敏End  ------------------");

        System.out.println("------------- 通过工具类为Map脱敏Begin  ------------------");
        Map<String,Object> desDemoMap = new HashMap();
        desDemoMap.put("name","吕志博");
        desDemoMap.put("phone","18888888888");
        desDemoMap.put("idNo","421083202411111111");
        desDemoMap.put("DesDemoBean",user);

        // 写法一 直接传入需要脱敏的key和脱敏规则, 传递格式 map, "key1","value1","key2","value2".... key 为Map中需要脱敏的key,value为脱敏规则
        System.out.println("写法一:" + LogDesensitizeUtil.desJson(desDemoMap,"idNo", LogSensitiveConstants.STRATEGY_ID_NO,"phone", LogSensitiveConstants.STRATEGY_PHONE));

        // 写法二 自行构建脱敏规则,然后以map形式传入
        Map<String,String>  strategyMap = new HashMap<>();
        strategyMap.put("idNo", LogSensitiveConstants.STRATEGY_ID_NO);
        strategyMap.put("phone", LogSensitiveConstants.STRATEGY_PHONE);
        System.out.println("写法二:" + LogDesensitizeUtil.desJson(desDemoMap,strategyMap));
        System.out.println("------------- 通过工具类为Map脱敏End  ------------------");
        /**
         * 指定脱敏策略进行脱敏 支持String、String数组、Collection<String>
         * @param1 需要被脱敏的文本
         * @param2 指定脱敏策略,详见{@link LogSensitiveConstants},脱敏策略不存在,则不进行脱敏处理
         */
        System.out.println("对手机号进行脱敏:"+LogDesensitizeUtil.desString("18888888888",LogSensitiveConstants.STRATEGY_PHONE));
        System.out.println("对手机号集合进行脱敏:" + LogDesensitizeUtil.desString(Arrays.asList("18888888888","18888888889"),LogSensitiveConstants.STRATEGY_PHONE));
        System.out.println("对手机号集合进行脱敏:" + LogDesensitizeUtil.desString(new String[]{"18888888888","18888888889"},LogSensitiveConstants.STRATEGY_PHONE));

        /**
         * 通过正则匹配模式对身份证、手机号、地址进行脱敏
         */
        System.out.println("对身份证号码进行正则匹配脱敏:" + LogDesensitizeUtil.desString("42108320241111111X"));
        System.out.println("对手机号码进行正则匹配脱敏:" + LogDesensitizeUtil.desString("18888888888"));
    }
}

内容输出如下

------------- 通过注解为对象脱敏Begin  ------------------
{"adderss":"广东省深圳市南山区牛马大厦","idNo":"421083********1111","map":{"phoneArr":["181****6780[0907ddf0d173216301559631350fa9ba]","181****6781[54ea4b6a5c8e10eac4ef873e4ce14f25]","181****6782[3f52919f044875b182bc5e6b6ba37271]"],"phone":"181****6789[093b20e8d401ee8309534de0d92eb497]","name":"吕志博","idNo":"421083********1111"},"mobileList":["186****1111[d270298c22d999895d58a1e9fd9d0751]","186****2222[2b035cebca7b1552d48db40778c15863]","186****3333[e2171fb6ec098bd41065098dc7cd6d5b]"]}
------------- 通过注解为对象脱敏End  ------------------
------------- 通过工具类为Map脱敏Begin  ------------------
写法一:{"phone":"188****8888[cbd41c6103064d3f0af848208c20ece2]","name":"吕志博","idNo":"421083********1111","DesDemoBean":{"adderss":"广东省深圳市南山区牛马大厦","idNo":"421083********1111","map":{"phoneArr":["181****6780[0907ddf0d173216301559631350fa9ba]","181****6781[54ea4b6a5c8e10eac4ef873e4ce14f25]","181****6782[3f52919f044875b182bc5e6b6ba37271]"],"phone":"181****6789[093b20e8d401ee8309534de0d92eb497]","name":"吕志博","idNo":"421083********1111"},"mobileList":["186****1111[d270298c22d999895d58a1e9fd9d0751]","186****2222[2b035cebca7b1552d48db40778c15863]","186****3333[e2171fb6ec098bd41065098dc7cd6d5b]"]}}
写法二:{"phone":"188****8888[cbd41c6103064d3f0af848208c20ece2]","name":"吕志博","idNo":"421083********1111","DesDemoBean":{"adderss":"广东省深圳市南山区牛马大厦","idNo":"421083********1111","map":{"phoneArr":["181****6780[0907ddf0d173216301559631350fa9ba]","181****6781[54ea4b6a5c8e10eac4ef873e4ce14f25]","181****6782[3f52919f044875b182bc5e6b6ba37271]"],"phone":"181****6789[093b20e8d401ee8309534de0d92eb497]","name":"吕志博","idNo":"421083********1111"},"mobileList":["186****1111[d270298c22d999895d58a1e9fd9d0751]","186****2222[2b035cebca7b1552d48db40778c15863]","186****3333[e2171fb6ec098bd41065098dc7cd6d5b]"]}}
------------- 通过工具类为Map脱敏End  ------------------
对手机号进行脱敏:188****8888[cbd41c6103064d3f0af848208c20ece2]
对手机号集合进行脱敏:["188****8888[cbd41c6103064d3f0af848208c20ece2]","188****8889[3639d5bc5f940edd8800fb7e7f5a15ba]"]
对手机号集合进行脱敏:["188****8888[cbd41c6103064d3f0af848208c20ece2]","188****8889[3639d5bc5f940edd8800fb7e7f5a15ba]"]
对身份证号码进行正则匹配脱敏:421083********111X
对手机号码进行正则匹配脱敏:188****8888[cbd41c6103064d3f0af848208c20ece2]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/905494.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

篡改猴 (Tampermonkey) 安装与使用

一、下载篡改猴 (Tampermonkey) 步骤1---官网&#xff1a;篡改猴 - Microsoft Edge Addons

无人机之感知避让技术篇

无人机的感知避让技术是无人机安全飞行的重要保障&#xff0c;它依赖于多种传感器和算法来实现对周围环境的感知和判断。 一、主要技术类型 视觉避障 原理&#xff1a;通过安装在无人机上的摄像头捕捉周围环境的图像&#xff0c;利用计算机视觉技术对图像进行处理和分析&…

动态规划路径问题(不同路径 不同路径2 珠宝的最大价值 下降路径最小和 最小路径和)

本期题型&#xff1a; 1. 不同路径. - 力扣&#xff08;LeetCode&#xff09; 2. 不同路径2. - 力扣&#xff08;LeetCode&#xff09; 3. 珠宝的最大价值 . - 力扣&#xff08;LeetCode&#xff09; 4. 下降路径最小和. - 力扣&#xff08;LeetCode&#xff09; …

ARM base instruction -- bfxil

Bitfield Extract and Insert Low copies a bitfield of <width> bits starting from bit position <lsb> in the source register to the least significant bits of the destination register, leaving the other destination bits unchanged. 位域提取并插入低位…

1.探索WebSocket:实时网络的心跳!

序言 你可能听说过"WebSokcet"这个词&#xff0c;感觉它好像很高深&#xff0c;但其实它是一个超级酷的小工具&#xff0c;让我们在Web应用里实现实时通信。想象一下&#xff0c;你可以像聊天一样&#xff0c;在浏览器和服务器之间来回“畅聊“&#xff0c;没有延迟…

springboot 修复 Spring Framework 特定条件下目录遍历漏洞(CVE-2024-38819)

刚解决Spring Framework 特定条件下目录遍历漏洞&#xff08;CVE-2024-38816&#xff09;没几天&#xff0c;又来一个新的&#xff0c;真是哭笑不得啊。 springboot 修复 Spring Framework 特定条件下目录遍历漏洞&#xff08;CVE-2024-38816&#xff09;https://blog.csdn.ne…

嵌入式硬件电子电路设计(二)开关电源BOOST升压电路

目录 升压电路原理 BOOST电路基本结构 BOOST电路工作过程分析 1. 开关导通阶段 2. 开关关断阶段 3. 稳定输出电压 BOOST电路工作的实际调研分析 1. 非同步BOOST电路 2. 同步BOOST电路 XL6009电路分析 SX1308电路分析 引言&#xff1a;前面已经讲述了Buck电路&#…

如何安装和使用PowerDesigner

教程目录 一、安装二、使用 一、安装 1、启动安装程序。 2、Trial&#xff0c;然后Next。 3、选PRC&#xff0c;同意协议&#xff0c;Next。 4、设置安装路径&#xff0c;Next。 5、Next。 6、全选&#xff0c;Next。 7、Next。 8、Next。 9、等待安装。 10、…

SQL进阶技巧:巧用异或运算解决经典换座位问题

目录 0 问题描述 1 数据准备 2 问题分析 2.1 什么是异或 2.2异或有什么特性? 2.3 异或应用 2.4 本问题采用异或SQL解决方案 3 小结 0 问题描述 表 seat中有2个字段id和student id 是该表的主键(唯一值)列,student表示学生姓名。 该表的每一行都表示学生的姓名和 ID。…

PAT甲级-1074 Reversing Linked List

题目 题目大意 给一个链表的头结点和总节点个数&#xff0c;以及k。每k个节点的链表都要翻转。 思路 链表可以用一个结构体数组来存储&#xff0c;先遍历一遍&#xff0c;过滤掉不在链表中的节点。然后将过滤好的节点放入res数组中&#xff0c;每k个元素用一次reverse()&…

PHP + Windows小皮面板 + VScode 安装教程

目录 1. 小皮面板安装包 下载 2、配置MySQL 可以在cmd命令框中使用 3. VScode安装 如有错误&#xff0c;烦请批评指正 1. 小皮面板安装包 下载 官方地址https://old.xp.cn/download.html 下载完后&#xff0c;一路next&#xff0c;文件路径自定义 2、配置MySQL 可以在cm…

ESP8266联网

目录 1.ESP8266连接热点 2.ESP8266创建热点 创建热点的ESP8266 连接热点的ESP8266 3.发送网络请求 案例一 案例二 4.连接服务器 接收信息开关灯 发布消息开关灯 5.ESP8266创建HTTP服务 ​编辑 ​编辑 ​编辑 6.ESP8266HTTP请求控制灯 ​编辑 7.ESP8266接收后端数据…

聊一聊Qt中的按钮

目录 QAbstractButton 功能概述 快捷键 默认按钮 按钮状态 自动重复功能 切换按钮 信号 子类化 API列表 QPushButton 按钮外观与功能 默认按钮 按钮的状态与模式 使用建议 菜单按钮 API QToolButton 创建工具按钮 用途示例 自动抬起功能 图标设置 外观与…

使用RabbitMQ实现微服务间的异步消息传递

使用RabbitMQ实现微服务间的异步消息传递 RabbitMQ简介 安装RabbitMQ 在Ubuntu上安装RabbitMQ 在CentOS上安装RabbitMQ 配置RabbitMQ 创建微服务 生产者服务 安装依赖 生产者代码 消费者服务 消费者代码 运行微服务 消息模式 直接模式 生产者代码 消费者代码 扇出模式 生产…

「实战应用」如何在 DHTMLX Scheduler 中实现动态主题切换?

创建响应式、直观的 UI 需要适应用户对应用程序各个方面的偏好。其中一项可显著提升用户体验的热门功能是能够在明暗主题之间切换。它在日程安排日历等综合组件中尤其有用。 本文将指导您在 DHTMLX Scheduler 中实现动态主题切换&#xff0c;使其适应用户设置的首选系统主题。…

Marin说PCB之电源的Surface Current Density知多少?

小编我是一位资深的国漫迷&#xff0c;像什么仙逆&#xff0c;斗破&#xff0c;斗罗&#xff0c;完美世界&#xff0c;遮天&#xff0c;凡人修仙传&#xff0c;少年歌行等&#xff0c;为了可以看这些视频小编我不惜花费了攒了很多年的私房钱去开了这个三个平台的会员啊&#xf…

【YApi】接口管理平台

一、简介 YApi 是一个用于前后端开发团队协作的 API 管理平台&#xff0c;帮助团队更加高效地进行 API 接口的设计、测试、文档管理和版本控制等工作。 YApi 主要功能&#xff1a; API 设计和管理&#xff1a;提供 API 设计和文档生成工具&#xff0c;使开发者能够轻松创建、…

【C/C++】字符/字符串函数(1)——由string.h提供

零.导言 什么是字符/字符串函数呢&#xff1f; 其实就是一类用于处理字符和字符串的函数。 而其中一部分函数包含在头文件 string.h 中&#xff0c;有 strlen strcpy strcat strcmp strncpy strncat strncmp strstr strtok strerror 等等 接下来我将逐个讲解这些函数。 一.str…

简单的kafkaredis学习之redis

简单的kafka&redis学习之redis 2. Redis 2.1 什么是Redis Redis是一种面向 “Key-Value” 数据类型的内存数据库&#xff0c;可以满足我们对海量数据的快速读写需求&#xff0c;Redis是一个 NoSQL 数据库&#xff0c;NoSQL的全称是not only sql&#xff0c;不仅仅是SQL&…

在 Visual Studio 中使用 Eigen 库

在 Visual Studio 中使用 Eigen 库 参考教程&#xff1a; 在 Visual Studio 中配置 Eigen库_vs调用eigen-CSDN博客 Eigen 是一个开源的 C 库&#xff0c;主要用来支持线性代数&#xff0c;矩阵和矢量运算&#xff0c;数值分析及其相关的算法。Eigen 除了需要 C 标准库以外&am…