logback - 自定义日志脱敏组件,一种不错的脱敏方案,。
完全借鉴了 https://blog.csdn.net/qq_40885085/article/details/113385261
应该是extends logback的实际Appender,然后,在这个自定义Appender中调用脱敏工具,脱敏工具需要有个配置,也就需要一个logback-desensitize.xml,这个和工具类的指定。读取这个配置类的工具为YmlUtils。感觉也应该可以用该方法来实现log4j2的。
log-desensitization项目
项目目录图片:
DesensitizationAppender.java
import ch.qos.logback.classic.spi.LoggingEvent;
import top.imddy.logdesensitization.utils.DesensitizationUtils;
import java.lang.reflect.Field;
/**
* @ClassName DesensitizationAppender
* @Description 脱敏类 - 将日志进行脱敏
* @Author 柳成荫
* @Date 2021/1/9
*/
public class DesensitizationAppender {
/**
* LoggingEvent的属性 - message
* 格式化前的日志信息,如log.info("your name : {}", "柳成荫")
* message就是"your name : {}"
*/
private static final String MESSAGE = "message";
/**
* LoggingEvent的属性 - formattedMessage
* 格式化后的日志信息,如log.info("your name : {}", "柳成荫")
* formattedMessage就是"your name : 柳成荫"
*/
private static final String FORMATTED_MESSAGE = "formattedMessage";
public void operation(LoggingEvent event) {
// event.getArgumentArray() - 获取日志中的参数数组
// 如:log.info("your name : {}, your id : {}", "柳成荫", 11)
// event.getArgumentArray() => ["柳成荫",11]
if (event.getArgumentArray() != null) {
// 获取格式化后的Message
String eventFormattedMessage = event.getFormattedMessage();
DesensitizationUtils util = new DesensitizationUtils();
// 获取替换后的日志信息
String changeMessage = util.customChange(eventFormattedMessage);
if (!(null == changeMessage || "".equals(changeMessage))) {
try {
// 利用反射的方式,将替换后的日志设置到原event对象中去
Class<? extends LoggingEvent> eventClass = event.getClass();
// 保险起见,将message和formattedMessage都替换了
Field message = eventClass.getDeclaredField(MESSAGE);
message.setAccessible(true);
message.set(event, changeMessage);
Field formattedMessage = eventClass.getDeclaredField(FORMATTED_MESSAGE);
formattedMessage.setAccessible(true);
formattedMessage.set(event, changeMessage);
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
}
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
LcyConsoleAppender.java
package top.imddy.logdesensitization.logbackadvice;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
/**
* @ClassName ConsoleAppenderDS
* @Description
* @Author 柳成荫
* @Date 2021/1/9
*/
public class LcyConsoleAppender extends ConsoleAppender {
@Override
protected void subAppend(Object event) {
DesensitizationAppender appender = new DesensitizationAppender();
appender.operation((LoggingEvent)event);
super.subAppend(event);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
LcyFileAppender.java
package top.imddy.logdesensitization.logbackadvice;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.FileAppender;
/**
* @ClassName FileAppenderDS
* @Description
* @Author 柳成荫
* @Date 2021/1/9
*/
public class LcyFileAppender extends FileAppender {
@Override
protected void subAppend(Object event) {
DesensitizationAppender appender = new DesensitizationAppender();
appender.operation((LoggingEvent) event);
super.subAppend(event);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
LcyRollingFileAppender.java
package top.imddy.logdesensitization.logbackadvice;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.rolling.RollingFileAppender;
/**
* @ClassName RollingFileAppenderDS
* @Description
* @Author 柳成荫
* @Date 2021/1/9
*/
public class LcyRollingFileAppender extends RollingFileAppender {
@Override
protected void subAppend(Object event) {
DesensitizationAppender appender = new DesensitizationAppender();
appender.operation((LoggingEvent)event);
super.subAppend(event);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
DesensitizationUtils.java
package top.imddy.logdesensitization.utils;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @ClassName DesensitizationUtil
* @Description 脱敏工具类
* @Author 柳成荫
* @Date 2021/1/9
*/
public class DesensitizationUtils {
/**
* 正则匹配模式 - 该正则表达式第三个()可能无法匹配以某些特殊符号开头和结尾的(如果像密码这种字段,前后如果有很多特殊字段,则无法匹配,建议密码直接加密,无需脱敏)
*/
public static final Pattern REGEX_PATTERN = Pattern.compile("\\s*([\"]?[\\w]+[\"]?)(\\s*[:=]+[^\\u4e00-\\u9fa5@,.*{\\[\\w]*\\s*)([\\u4e00-\\u9fa5_\\-@.\\w]+)[\\W&&[^\\-@.]]?\\s*");
// 该正则表达式第三个()可以匹配以某些特殊字符开头和结尾的,但是对于日志来说,处理也很麻烦
// public static final Pattern REGEX_PATTERN = Pattern.compile("\\s*([\"]?[\\w]+[\"]?)(\\s*[::=><]+\\s*)([\\S]+[\\u4e00-\\u9fa5\\w]+[\\S]+)[\\W&&[^\\-@.]]?\\s*");
/**
* 匹配非数字
*/
public static final Pattern REGEX_NUM = Pattern.compile("[^0-9]");
/**
* 是否开启脱敏
*/
public static Boolean openFlag = false;
/**
* 是否忽略key的大小写
*/
public static Boolean ignoreFlag = true;
/**
* 作为ignoreFlag初始的标记
*/
private static Boolean initIgnoreFlag = false;
/**
* 作为openFlag初始化的标记
*/
private static Boolean initOpenFlag = false;
/**
* 所有key:value配置匹配对
*/
public static Map<String, Object> allPattern;
/**
* key为全小写的allPattern - pattern和patterns
*/
public static Map<String, Object> lowerCaseAllPattern;
/**
* 手机
*/
public static final String PHONE = "phone";
/**
* 邮箱
*/
public static final String EMAIL = "email";
/**
* 身份证
*/
public static final String IDENTITY = "identity";
/**
* 自定义
*/
public static final String OTHER = "other";
/**
* 密码
*/
public static final String PASSWORD = "password";
/**
* 将event对象的formattedMessage脱敏
*
* @param eventFormattedMessage LoggingEvent的formattedMessage属性
* @return 脱敏后的日志信息
*/
public String customChange(String eventFormattedMessage) {
try {
// 原始信息 - 格式化后的
String originalMessage = eventFormattedMessage;
boolean flag = false;
// 获取Yml配置文件内容 - Map格式
Map<String, Object> patternMap = YmlUtils.patternMap;
if (!CollectionUtils.isEmpty(patternMap)) {
// 如果没有开启脱敏,返回"",则不会做脱敏操作
if (!this.checkOpen(patternMap)) {
return "";
}
// 获取一个原始Message的正则匹配器
Matcher regexMatcher = REGEX_PATTERN.matcher(eventFormattedMessage);
// 如果部分匹配(一个对象/JSON字符串/Map/List<对象/Map>等会有多个匹配),就根据分组来获取key和value
while (regexMatcher.find()) {
// group(1)就是key,group(2)就是分隔符(如:和=),group(3)就是value
try {
// 获取key - 将引号替换掉,去掉两边空格(JSON字符串去引号)
String key = regexMatcher.group(1).replaceAll("\"", "").trim();
// 获取原始Value
String originalValue = regexMatcher.group(3);
// 获取Key对应规则
Object keyPatternValue = this.getKeyIgnoreCase(key);
if (null != keyPatternValue && null != originalValue && !"null".equals(originalValue)) {
// 将原始Value - 引号替换掉,去掉两边空格(JSON字符串去引号)
String value = originalValue.replaceAll("\"", "").trim();
if (!"null".equals(value) || value.equalsIgnoreCase(key)) {
String patternVales = getMultiplePattern(keyPatternValue, value);
if ("".equals(patternVales)) {
// 不符规则/没有规则的不能影响其他符合规则的
continue;
}
patternVales = patternVales.replaceAll(" ", "");
if(PASSWORD.equalsIgnoreCase(patternVales)){
String origin = regexMatcher.group(1) + regexMatcher.group(2) + regexMatcher.group(3);
originalMessage = originalMessage.replace(origin, regexMatcher.group(1) + regexMatcher.group(2) + "******");
flag=true;
// 密码级别的,直接替换为全*,继续下一轮匹配
continue;
}
// 原始的规则(完整)
String originalPatternValues = patternVales;
// 判断这个规则是否带括号,带括号的需要把括号拿出来 - 核心规则
String filterData = this.getBracketPattern(patternVales);
if (!"".equals(filterData)) {
patternVales = filterData;
}
// 以逗号分割
String[] split = patternVales.split(",");
value = getReplaceValue(value, patternVales, split, originalPatternValues);
if (value != null && !"".equals(value)) {
flag = true;
String origin = regexMatcher.group(1) + regexMatcher.group(2) + regexMatcher.group(3);
originalMessage = originalMessage.replace(origin, regexMatcher.group(1) + regexMatcher.group(2) + value);
}
}
}
} catch (Exception e) {
// 捕获到异常,直接返回结果(空字符串) - 这个异常可能发生的场景:同时开启控制台和输出文件的时候
// 当控制台进行一次脱敏之后,文件的再去脱敏,是对脱敏后的message脱敏,则正则匹配会出现错误
// 比如123456789@.com 脱敏后:123***456789@qq.com,正则匹配到123,这个123去substring的时候会出错
return "";
}
}
}
return flag ? originalMessage : "";
} catch (Exception e) {
return "";
}
}
/**
* 获取替换后的value
* @param value value
* @param patternVales 核心规则
* @param split 分割
* @param originalPatternValues 原始规则
* @return
*/
private String getReplaceValue(String value, String patternVales, String[] split, String originalPatternValues) {
if (split.length >= 2 && !"".equals(patternVales)) {
String append = "";
String start = REGEX_NUM.matcher(split[0]).replaceAll("");
String end = REGEX_NUM.matcher(split[1]).replaceAll("");
int startSub = Integer.parseInt(start) - 1;
int endSub = Integer.parseInt(end) - 1;
// 脱敏起点/结尾符下标
int index;
String flagSub;
int indexOf;
int newValueL;
String newValue;
// 脱敏结尾
if (originalPatternValues.contains(">")) {
// 获取>的下标
index = originalPatternValues.indexOf(">");
// 获取标志符号
flagSub = originalPatternValues.substring(0, index);
// 获取标志符号的下标
indexOf = value.indexOf(flagSub);
// 获取标志符号前面数据
newValue = value.substring(0, indexOf);
// 获取数据的长度
newValueL = newValue.length();
// 获取标识符及后面的数据
append = value.substring(indexOf);
value = this.dataDesensitization(Math.max(startSub, 0), endSub >= 0 ? (endSub <= newValueL ? endSub : newValueL - 1) : 0, newValue) + append;
} else if (originalPatternValues.contains("<")) {
// 脱敏起点
index = originalPatternValues.indexOf("<");
flagSub = originalPatternValues.substring(0, index);
indexOf = value.indexOf(flagSub);
newValue = value.substring(indexOf + 1);
newValueL = newValue.length();
append = value.substring(0, indexOf + 1);
value = append + this.dataDesensitization(Math.max(startSub, 0), endSub >= 0 ? (endSub <= newValueL ? endSub : newValueL - 1) : 0, newValue);
} else if (originalPatternValues.contains(",")) {
newValueL = value.length();
value = this.dataDesensitization(Math.max(startSub, 0), endSub >= 0 ? (endSub <= newValueL ? endSub : newValueL - 1) : 0, value);
}
} else if (!"".equals(patternVales)) {
int beforeIndexOf = patternVales.indexOf("*");
int last = patternVales.length() - patternVales.lastIndexOf("*");
int lastIndexOf = value.length() - last;
value = this.dataDesensitization(beforeIndexOf, lastIndexOf, value);
}
return value;
}
/**
* 根据key获取对应的规则(也许是Map,也许是String)
*
* @param key key
* @return key对应的规则(也许是Map , 也许是String)
*/
private Object getKeyIgnoreCase(String key) {
// 获取所有pattern
if (CollectionUtils.isEmpty(allPattern)) {
allPattern = YmlUtils.getAllPattern();
}
// 作为ignoreFlag初始化的标记,第一次ignoreFlag需要从Yml中获取是否开启
// 后面就不用去Yml里获取了
if (!initIgnoreFlag) {
initIgnoreFlag = true;
// 仅在第一次会去获取,无论true还是false(默认是开启忽略大小写)
ignoreFlag = YmlUtils.getIgnore();
if (ignoreFlag) {
// 如果忽略大小写,就去获取一份key小写化的allPattern
lowerCaseAllPattern = this.transformUpperCase(allPattern);
}
}
// 只有忽略大小写的时候,才去从lowerCaseAllPattern里获取
if (ignoreFlag) {
return lowerCaseAllPattern.get(key.toLowerCase());
} else {
// 否则从原始的pattern中取
return allPattern.get(key);
}
}
/**
* 将pattern的key值全部转换为小写
*
* @param pattern pattern
* @return 转换后的pattern
*/
public Map<String, Object> transformUpperCase(Map<String, Object> pattern) {
Map<String, Object> resultMap = new HashMap();
if (pattern != null && !pattern.isEmpty()) {
// 获取Key的Set集合
Set<String> keySet = pattern.keySet();
Iterator<String> iterator = keySet.iterator();
// 黄线强迫症,用for代替while
for (; iterator.hasNext(); ) {
String key = iterator.next();
// 把key转换为小写字符串
String newKey = key.toLowerCase();
// 重新放入
resultMap.put(newKey, pattern.get(key));
}
}
return resultMap;
}
/**
* 获取规则字符串
*
* @param patternVale 规则
* @param newValue key对应的值 - 如 name:liuchengyin 这个参数就是liuchengyn
* @return 规则的字符串
*/
private String getMultiplePattern(Object patternVale, String newValue) {
if (patternVale instanceof String) {
// 如果规则是String类型,直接转换为String类型返回
return (String) patternVale;
} else if (patternVale instanceof Map) {
// 获取规则 - Map类型(不推荐,有风险)
return this.getPatternByMap((Map<String, Object>) patternVale, newValue);
} else { // 获取规则 - List<Map>类型,一个Key可能有多种匹配规则
if (patternVale instanceof List) {
List<Map<String, Object>> list = (List<Map<String, Object>>) patternVale;
if (!CollectionUtils.isEmpty(list)) {
Iterator<Map<String, Object>> iterator = list.iterator();
// 遍历每一种规则
for (; iterator.hasNext(); ) {
Map<String, Object> map = iterator.next();
String patternValue = this.getPatternByMap(map, newValue);
// 如果是空的,表示没匹配上该规则,去匹配下一个规则
if (!"".equals(patternValue)) {
return patternValue;
}
}
}
}
return "";
}
}
/**
* 获取规则
*
* @param map 规则
* @param value key对应的值 - 如 name:liuchengyin 这个参数就是liuchengyn
* @return
*/
private String getPatternByMap(Map<String, Object> map, String value) {
if (CollectionUtils.isEmpty(map)) {
// 为空就是无规则
return "";
} else {
// 获取匹配规则 - 自定义规则(正则)
Object customRegexObj = map.get("customRegex");
// 获取脱敏方式
Object positionObj = map.get("position");
// 获取匹配规则 - 自定义规则(正则)
String customRegex = "";
// position必须有
String position = "";
if (customRegexObj instanceof String) {
customRegex = (String) customRegexObj;
}
if (positionObj instanceof String) {
position = (String) positionObj;
}
// 如果日志中的值能够匹配,直接返回其对应的规则
if (!"".equals(customRegex) && value.matches(customRegex)) {
return position;
} else {
// 如果不能匹配到正则,就看他是不是内置规则
Object defaultRegexObj = map.get("defaultRegex");
String defaultRegex = "";
if (defaultRegexObj instanceof String) {
defaultRegex = (String) defaultRegexObj;
}
// 这段代码写的多多少少感觉有点问题,可以写在一个if里,但是阿里检测代码的工具会警告
if (!"".equals(defaultRegex)) {
if(IDENTITY.equals(defaultRegex) && isIdentity(value)){
return position;
}else if(EMAIL.equals(defaultRegex) && isEmail(value)){
return position;
}else if(PHONE.equals(defaultRegex) && isMobile(value)){
return position;
}else if(OTHER.equals(defaultRegex)){
return position;
}
}
return "";
}
}
}
/**
* 获取规则 - 判断是否带括号,带括号则返回括号内数据
* @param patternVales 规则
* @return 规则
*/
private String getBracketPattern(String patternVales) {
// 是否存在括号
if (patternVales.contains("(")) {
int startCons = patternVales.indexOf("(");
int endCons = patternVales.indexOf(")");
patternVales = patternVales.substring(startCons + 1, endCons);
return patternVales;
} else {
return "";
}
}
public static boolean isEmail(String str) {
return str.matches("^[\\w-]+@[\\w-]+(\\.[\\w-]+)+$");
}
public static boolean isIdentity(String str) {
return str.matches("(^\\d{18}$)|(^\\d{15}$)");
}
public static boolean isMobile(String str) {
return str.matches("^1[0-9]{10}$");
}
/**
* 检查是否开启脱敏
*
* @param pattern Yml配置文件内容 - Map格式
* @return 是否开启脱敏
*/
private Boolean checkOpen(Map<String, Object> pattern) {
// 作为openFlag初始化的标记,第一次openFlag需要从Yml中获取是否开启
// 后面就不用去Yml里获取了
if (!initOpenFlag) {
initOpenFlag = true;
// 仅在第一次会去获取
openFlag = YmlUtils.getOpen();
}
// 第二次以后openFlag已经有值,无论true还是false(默认是未开启)
return openFlag;
}
/**
* 脱敏处理
* @param start 脱敏开始下标
* @param end 脱敏结束下标
* @param value value
* @return
*/
public String dataDesensitization(int start, int end, String value) {
char[] chars;
int i;
// 正常情况 - end在数组长度内
if (start >= 0 && end + 1 <= value.length()) {
chars = value.toCharArray();
// 脱敏替换
for (i = start; i < chars.length && i < end + 1; ++i) {
chars[i] = '*';
}
return new String(chars);
} else if (start >= 0 && end >= value.length()) {
// 非正常情况 - end在数组长度外
chars = value.toCharArray();
for (i = start; i < chars.length; ++i) {
chars[i] = '*';
}
return new String(chars);
} else {
// 不符要求,不脱敏
return value;
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.
- 258.
- 259.
- 260.
- 261.
- 262.
- 263.
- 264.
- 265.
- 266.
- 267.
- 268.
- 269.
- 270.
- 271.
- 272.
- 273.
- 274.
- 275.
- 276.
- 277.
- 278.
- 279.
- 280.
- 281.
- 282.
- 283.
- 284.
- 285.
- 286.
- 287.
- 288.
- 289.
- 290.
- 291.
- 292.
- 293.
- 294.
- 295.
- 296.
- 297.
- 298.
- 299.
- 300.
- 301.
- 302.
- 303.
- 304.
- 305.
- 306.
- 307.
- 308.
- 309.
- 310.
- 311.
- 312.
- 313.
- 314.
- 315.
- 316.
- 317.
- 318.
- 319.
- 320.
- 321.
- 322.
- 323.
- 324.
- 325.
- 326.
- 327.
- 328.
- 329.
- 330.
- 331.
- 332.
- 333.
- 334.
- 335.
- 336.
- 337.
- 338.
- 339.
- 340.
- 341.
- 342.
- 343.
- 344.
- 345.
- 346.
- 347.
- 348.
- 349.
- 350.
- 351.
- 352.
- 353.
- 354.
- 355.
- 356.
- 357.
- 358.
- 359.
- 360.
- 361.
- 362.
- 363.
- 364.
- 365.
- 366.
- 367.
- 368.
- 369.
- 370.
- 371.
- 372.
- 373.
- 374.
- 375.
- 376.
- 377.
- 378.
- 379.
- 380.
- 381.
- 382.
- 383.
- 384.
- 385.
- 386.
- 387.
- 388.
- 389.
- 390.
- 391.
- 392.
- 393.
- 394.
- 395.
- 396.
- 397.
- 398.
- 399.
- 400.
- 401.
- 402.
- 403.
- 404.
- 405.
- 406.
- 407.
- 408.
- 409.
- 410.
- 411.
- 412.
- 413.
- 414.
- 415.
- 416.
- 417.
- 418.
- 419.
- 420.
- 421.
- 422.
- 423.
- 424.
- 425.
- 426.
- 427.
- 428.
- 429.
- 430.
- 431.
- 432.
- 433.
- 434.
- 435.
YmlUtils.java
package top.imddy.logdesensitization.utils;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.springframework.util.CollectionUtils;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
/**
* @ClassName YmlUtils
* @Description Yml配置文件操作相关
* @Author 柳成荫
* @Date 2021/1/9
*/
public class YmlUtils {
/** 默认脱敏配置文件名 - 默认在resources目录下 */
public static String PROPERTY_NAME = "logback-desensitize.yml";
/** Key:pattern - 单规则 */
public static final String PATTERN = "pattern";
/** Key:patterns - 多规则 */
public static final String PATTERNS = "patterns";
/** Key:open - 是否开启脱敏 */
public static final String OPEN_FLAG = "open";
/** Key:ignore - 是否开启忽略大小写匹配 */
public static final String IGNORE = "ignore";
/** Key:脱敏配置文件头Key */
public static final String YML_HEAD_KEY = "log-desensitize";
/** key:patterns对应key下的规则Key */
public static final String CUSTOM = "custom";
/** Yml脱敏配置文件内容 - Map格式 */
public static Map<String, Object> patternMap;
public static final DumperOptions OPTIONS = new DumperOptions();
static {
OPTIONS.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
patternMap = getYmlByName(PROPERTY_NAME);
}
/**
* 获取Yml配置文件的内容 - 以Map的格式
* @param fileName Yml配置文件名
* @return 配置信息(Map格式)
*/
private static Map<String, Object> getYmlByName(String fileName) {
if (CollectionUtils.isEmpty(patternMap)) {
Object fromYml = null;
try {
// 获取Yml配置文件的Map对象
fromYml = getFromYml(fileName, YML_HEAD_KEY);
// LinkedHashMap,如果不是Map类型(比如配置文件里只有log-desensitize=123456),直接返回patternMap本身
if (fromYml instanceof Map) {
return (Map)fromYml;
}
} catch (Exception e) {
return null;
}
}
return patternMap;
}
/**
* 通过key获取value从yml配置文件
* @param fileName Yml文件名
* @param key key
* @return value或者map本身
*/
public static Object getFromYml(String fileName, String key){
// 创建一个Yaml对象
Yaml yaml = new Yaml(OPTIONS);
// 获得流
InputStream inputStream = YmlUtils.class.getClassLoader().getResourceAsStream(fileName);
HashMap<String, Object> map = (HashMap<String, Object>)yaml.loadAs(inputStream, HashMap.class);
// 如果map内有值,直接返回key对应的Value,否则返回map本身
return Objects.nonNull(map) && map.size() > 0 ? map.get(key) : map;
}
/**
* 获取key为pattern的值
* @return pattern对应的map,或者null(如pattern=123这种情况)
*/
public static Map<String, Object> getPattern() {
Object pattern = patternMap.get(PATTERN);
if (pattern instanceof Map) {
return (Map<String, Object>)pattern;
} else {
return null;
}
}
/**
* 获取所有pattern,含key为pattern,key为patterns
* @return pattern
*/
public static Map<String, Object> getAllPattern() {
Map<String, Object> allPattern = new HashMap<String, Object>();
Map<String, Object> pattern = getPattern();
Map<String, Object> patterns = getPatterns();
if (!CollectionUtils.isEmpty(patterns)) {
allPattern.putAll(patterns);
}
// 注意:patterns中的key与pattern的key重复,patterns中的不生效(Map无重复Key)
if (!CollectionUtils.isEmpty(pattern)) {
allPattern.putAll(pattern);
}
return allPattern;
}
/**
* 获取key为patterns的值
* @return patterns对应的map,或者null(如patterns=123这种情况)
*/
public static Map<String, Object> getPatterns() {
Map<String, Object> map = new HashMap<String, Object>();
Object patterns = patternMap.get(PATTERNS);
// patterns下有多个key的时候(List)
if (patterns instanceof List) {
// 获取key为"patterns"的值(List<Map<String, Object>>)
List<Map<String, Object>> list = (List<Map<String, Object>>)patterns;
if (!CollectionUtils.isEmpty(list)) {
Iterator<Map<String, Object>> iterator = list.iterator();
// 黄线强迫症,用for代替while
for (;iterator.hasNext();){
Map<String, Object> maps = (Map<String, Object>)iterator.next();
assembleMap(map, maps);
}
return map;
}
}
// patterns只有一个key的时候,且非List
if (patterns instanceof Map) {
assembleMap(map, (Map<String, Object>)patterns);
return map;
} else {
return null;
}
}
/**
* 将patterns中每个key对应的规则按<key,规则>的方式放入map
* @param map map
* @param patterns patterns
*/
private static void assembleMap(Map<String, Object> map, Map<String, Object> patterns) {
// 获取patterns里key值为"key"的值(脱敏关键字)
Object key = patterns.get("key");
if (key instanceof String) {
// 清除空格
String keyWords = ((String) key).replace(" ", "");
// 以逗号分隔出一个key数组
String[] keyArr = keyWords.split(",");
for(String keyStr : keyArr){
map.put(keyStr, patterns.get(CUSTOM));
}
}
}
/**
* 是否开启脱敏,默认不开启
* @return 是否开启脱敏
*/
public static Boolean getOpen() {
Object flag = patternMap.get(OPEN_FLAG);
return flag instanceof Boolean ? (Boolean)flag : false;
}
/**
* 是否忽略大小写匹配,默认开启
* @return 是否忽略大小写匹配
*/
public static Boolean getIgnore() {
Object flag = patternMap.get(IGNORE);
return flag instanceof Boolean ? (Boolean)flag : true;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>top.imddy</groupId>
<artifactId>log-desensitization</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.15</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.8</version>
</dependency>
</dependencies>
</project>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
log-test项目(用于测试)
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>top.imddy</groupId>
<artifactId>log-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.15</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>top.imddy</groupId>
<artifactId>log-desensitization</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<finalName>log-test</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.15</version>
<configuration>
<fork>true</fork>
<includeSystemScope>true</includeSystemScope>
<!--fork : 如果没有该项配置,肯呢个devtools不会起作用,即应用不会restart -->
<!--这里写上main方法所在类的路径-->
<mainClass>top.imddy.logtest.Application</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<encoding>UTF-8</encoding>
<!-- 过滤后缀为pem、pfx的证书文件 -->
<nonFilteredFileExtensions>
<nonFilteredFileExtension>p12</nonFilteredFileExtension>
<nonFilteredFileExtension>cer</nonFilteredFileExtension>
<nonFilteredFileExtension>pem</nonFilteredFileExtension>
<nonFilteredFileExtension>pfx</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
Test01Controller.java
package top.imddy.logtest.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
@RestController
@RequestMapping("/test01")
public class Test01Controller {
private static final Logger log = LoggerFactory.getLogger(Test01Controller.class);
@GetMapping("/info")
public String info() {
log.info("localMobile:{},localMobile:{}", "023-55327459", "123456");//规范语法 -推荐,格外推荐使用=分割。
log.info("your email:{},your phone:{}", "123456789@q9.com", "15310763497");
log.info("your email={},your cellphone={}", "123456789@q9.com", "15310763497");
log.info("email:{},mobile:}", "123456789@qq.com", "023-55327459");
log.info("identity:{}", "123456789012345");
// Map类型
HashMap<String, String> map = new HashMap<>();
map.put("phone", "15310763497");
map.put("email", "123456789@qq.com");
log.info("one map={}", map);
log.info("one json={}", "{" + "\t\"email\":\"123456789@gq.com\"," + "\t\"phone\":\"15310764682\"" + "}");// 对象类型
log.info("student:{}", new Student("柳成荫", "15372746384", "530365199703153648")); // 对于需要完全密文的
log.info("password:{}", "189498asd6489s");
//非规范 -但也能用 - 不推荐
log.info(" mobile :{}.email:{}", 123456, "123456789@qq.com");
log.info(" mobile :{}email:{}", 123456, "123456789@qq.com");
log.info(" mobile :{}: email:{}", 123456, "123456789@qg.com");
return "柳成荫";
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
logback-spring.xml
2、替换日志文件配置类(logback.xml)
日志打印方式都只需要替换成脱敏的类即可,如果你的业务不需要,则无需替换。
①ConsoleAppender - 控制台脱敏
// 原类
ch.qos.logback.core.ConsoleAppender
// 替换类
pers.liuchengyin.logbackadvice.LcyConsoleAppender
②RollingFileAppender - 滚动文件
// 原类
ch.qos.logback.core.rolling.RollingFileAppender
// 替换类
pers.liuchengyin.logbackadvice.LcyRollingFileAppender
③FileAppender - 文件
// 原类
ch.qos.logback.core.FileAppender
// 替换类
pers.liuchengyin.logbackadvice.LcyFileAppender
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<property name="logback.logdir" value="/logs/logtest" />
<property name="logback.appname" value="logtest"/>
<appender name="consoleLog"
class="top.imddy.logdesensitization.logbackadvice.LcyConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
</pattern>
</layout>
</appender>
<appender name="fileInfoLog"
class="top.imddy.logdesensitization.logbackadvice.LcyRollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>
%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
</pattern>
</encoder>
<!-- 滚动策略 -->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>${logback.logdir}/info.${logback.appname}.%d.log</fileNamePattern>
<MaxHistory>7</MaxHistory>
</rollingPolicy>
</appender>
<appender name="fileDebugLog"
class="top.imddy.logdesensitization.logbackadvice.LcyRollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<pattern>
%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
</pattern>
</encoder>
<!-- 设置滚动策略 -->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>${logback.logdir}/debug.${logback.appname}.%d.log</fileNamePattern>
<MaxHistory>7</MaxHistory>
</rollingPolicy>
</appender>
<appender name="fileWarnLog"
class="top.imddy.logdesensitization.logbackadvice.LcyRollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
<encoder>
<pattern>
%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
</pattern>
</encoder>
<!-- 设置滚动策略 -->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>${logback.logdir}/warn.${logback.appname}.%d.log</fileNamePattern>
<MaxHistory>7</MaxHistory>
</rollingPolicy>
</appender>
<appender name="fileErrorLog"
class="top.imddy.logdesensitization.logbackadvice.LcyRollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>
%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
</pattern>
</encoder>
<!-- 设置滚动策略 -->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>${logback.logdir}/error.${logback.appname}.%d.log</fileNamePattern>
<MaxHistory>7</MaxHistory>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="consoleLog" />
<appender-ref ref="fileInfoLog" />
<appender-ref ref="fileDebugLog" />
<appender-ref ref="fileWarnLog" />
<appender-ref ref="fileErrorLog" />
</root>
</configuration>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
logback-desensitize.yml
# 日志脱敏
log-desensitize:
# 是否忽略大小写匹配,默认为true
ignore: true
# 是否开启脱敏,默认为false
open: true
# pattern下的key/value为固定脱敏规则
pattern:
# 邮箱 - @前第4-7位脱敏
email: "@>(4,7)"
# qq邮箱 - @后1-3位脱敏
qqemail: "@<(1,3)"
# 姓名 - 姓脱敏,如*杰伦
name: 1,1
# 密码 - 所有需要完全脱敏的都可以使用内置的password
password: password
patterns:
# 身份证号,key后面的字段都可以匹配以下规则(用逗号分隔)
- key: identity,idcard
# 定义规则的标识
custom:
# defaultRegex表示使用组件内置的规则:identity表示身份证号 - 内置的18/15位
- defaultRegex: identity
position: 9,13
# 内置的other表示如果其他规则都无法匹配到,则按该规则处理
- defaultRegex: other
position: 9,10
# 电话号码,key后面的字段都可以匹配以下规则(用逗号分隔)
- key: phone,cellphone,mobile
custom:
# 手机号 - 内置的11位手机匹配规则
- defaultRegex: phone
position: 4,7
# 自定义正则匹配表达式:座机号(带区号,号码七位|八位)
- customRegex: "^0[0-9]{2,3}-[0-9]{7,8}"
# -后面的1-4位脱敏
position: "-<(1,4)"
# 自定义正则匹配表达式:座机号(不带区号)
- customRegex: "^[0-9]{7,8}"
position: 3,5
# 内置的other表示如果其他规则都无法匹配到,则按该规则处理
- defaultRegex: other
position: 1,3
# 这种方式不太推荐 - 一旦匹配不上,就不会脱敏
- key: localMobile
custom:
customRegex: "^0[0-9]{2,3}-[0-9]{7,8}"
position: 1,3
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.