Browse Source

脱敏

master
zhaoqifeng 3 years ago
parent
commit
262b5b5d9d
  1. 4
      epdc-commons-tools/pom.xml
  2. 35
      epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/annotation/MaskResponse.java
  3. 38
      epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/aspect/MaskResponseAspect.java
  4. 27
      epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/enums/IdCardTypeEnum.java
  5. 239
      epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/processor/MaskProcessor.java
  6. 159
      epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/utils/IdCardRegexUtils.java

4
epdc-commons-tools/pom.xml

@ -1,5 +1,5 @@
<?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"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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>
@ -16,7 +16,7 @@
<hibernate.validator.version>6.0.12.Final</hibernate.validator.version>
<commons.lang.version>3.7</commons.lang.version>
<commons.fileupload.version>1.3.3</commons.fileupload.version>
<hutool.version>4.1.8</hutool.version>
<hutool.version>4.6.1</hutool.version>
<easypoi.version>4.1.0</easypoi.version>
<joda.time.version>2.9.9</joda.time.version>
<fastjson.version>1.2.59</fastjson.version>

35
epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/annotation/MaskResponse.java

@ -0,0 +1,35 @@
package com.elink.esua.epdc.commons.tools.annotation;
import java.lang.annotation.*;
/**
* 标记一个接口它的返回值中的某些字段需要打掩码
*
* @author zqf
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MaskResponse {
/**
* 掩码类型
*/
String MASK_TYPE_ID_CARD = "ID_CARD";
String MASK_TYPE_MOBILE = "MOBILE";
String MASK_TYPE_CHINESE_NAME = "CHINESE_NAME";
/**
* 要打码的字段列表会递归的着这些字段
*
* @return
*/
String[] fieldNames() default {"idCard", "mobile", "phone"};
/**
* 要打码的类型
*
* @return
*/
String[] fieldsMaskType() default {MASK_TYPE_ID_CARD, MASK_TYPE_MOBILE, MASK_TYPE_MOBILE};
}

38
epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/aspect/MaskResponseAspect.java

@ -0,0 +1,38 @@
package com.elink.esua.epdc.commons.tools.aspect;
import com.elink.esua.epdc.commons.tools.annotation.MaskResponse;
import com.elink.esua.epdc.commons.tools.exception.RenException;
import com.elink.esua.epdc.commons.tools.processor.MaskProcessor;
import com.elink.esua.epdc.commons.tools.utils.Result;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author Administrator
*/
@Aspect
@Component
@Order(0)
public class MaskResponseAspect {
@AfterReturning(pointcut = "@annotation(com.elink.esua.epdc.commons.tools.annotation.MaskResponse)", returning = "result")
public Object proceed(JoinPoint point, Result result) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
MaskResponse maskResponseAnno = signature.getMethod().getAnnotation(MaskResponse.class);
String[] fieldNames = maskResponseAnno.fieldNames();
String[] fieldsMaskType = maskResponseAnno.fieldsMaskType();
if (fieldNames.length != fieldsMaskType.length) {
String msg = "掩码配置错误";
throw new RenException(msg);
}
new MaskProcessor(fieldNames, fieldsMaskType).mask(result);
return null;
}
}

27
epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/enums/IdCardTypeEnum.java

@ -0,0 +1,27 @@
package com.elink.esua.epdc.commons.tools.enums;
/**
* 唯一整件类型
*/
public enum IdCardTypeEnum {
OTHERS("0", "其他"),
SFZH("1", "身份证号"),
PASSPORT("2", "护照");
private String type;
private String name;
IdCardTypeEnum(String type, String name) {
this.type = type;
this.name = name;
}
public String getType() {
return type;
}
public String getName() {
return name;
}
}

239
epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/processor/MaskProcessor.java

@ -0,0 +1,239 @@
package com.elink.esua.epdc.commons.tools.processor;
import cn.hutool.core.util.StrUtil;
import com.elink.esua.epdc.commons.tools.annotation.MaskResponse;
import com.elink.esua.epdc.commons.tools.enums.IdCardTypeEnum;
import com.elink.esua.epdc.commons.tools.exception.ExceptionUtils;
import com.elink.esua.epdc.commons.tools.page.PageData;
import com.elink.esua.epdc.commons.tools.utils.IdCardRegexUtils;
import com.elink.esua.epdc.commons.tools.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* desc:脱敏处理器
*
* @author Administrator
*/
@Slf4j
public class MaskProcessor {
public static final String EPMET_PACKAGE_PREFIX = "com.elink.esua.epdc";
private List<String> fieldNames;
private List<String> fieldsMaskType;
public MaskProcessor(String[] fields, String[] fieldsMaskType) {
if (fields != null && fields.length > 0) {
this.fieldNames = Arrays.asList(fields);
this.fieldsMaskType = Arrays.asList(fieldsMaskType);
}
}
/**
* 唯一整件号打码可能是身份证号或者是护照号
* 将明文字符串打码变为掩码保留前6后面打码
*
* @param originString
* @return
*/
public static String maskIdCard(String originString) {
IdCardRegexUtils regexUtil = IdCardRegexUtils.parse(originString);
if (regexUtil == null) {
// 不匹配任何类型,不码
return originString;
}
if (regexUtil.getTypeEnum() == IdCardTypeEnum.SFZH) {
// 身份证号
// 仅将6位之后的全都打码
int maskedTextLength = 12;
int length = originString.length();
String maskStr = StrUtil.repeatByLength("*", length - maskedTextLength);
return originString.replaceAll("^(\\d{10})\\d+([a-zA-Z0-9]{2})$", new StringBuilder("$1").append(maskStr).append("$2").toString());
} else if (regexUtil.getTypeEnum() == IdCardTypeEnum.PASSPORT) {
// 护照,前两位,后两位为明文,其他*
int clearLength = 4;
int maskedLength = 0;
if ((maskedLength = originString.length() - clearLength) > 0) {
String maskStr = StrUtil.repeatByLength("*", maskedLength);
return originString.replaceAll("^([a-zA-Z0-9]{2})[a-zA-Z0-9]+([a-zA-Z0-9]{2})$", new StringBuilder("$1").append(maskStr).append("$2").toString());
}
}
return originString;
}
public static void main(String[] args) {
String[] idc = {"idCard"};
String[] idct = {MaskResponse.MASK_TYPE_ID_CARD};
String r = new MaskProcessor(idc, idct).maskString("王五(372284152412022222)", MaskResponse.MASK_TYPE_ID_CARD);
System.out.println(r);
String s = MaskProcessor.maskIdCard("372284152412022222");
System.out.println(s);
}
/**
* 为dto中的属性打掩码
*
* @param object
*/
public void mask(Object object) {
if (object == null) {
return;
}
if (object instanceof Result) {
mask(((Result<?>) object).getData());
} else if (object instanceof PageData) {
mask(((PageData<?>) object).getList());
} else if (object instanceof List) {
((List) object).forEach(e -> mask(e));
} else if (object instanceof Map) {
maskMap((Map) object);
} else if (object.getClass().getName().startsWith(EPMET_PACKAGE_PREFIX)) {
// 自定义bean,走反射
maskEpmetBean(object);
}
}
/**
* 为map打码只打value中的码
* - 如果value是epmet的dto那么去反射它
* - 如果value是字符串那么直接给他打码
* - 如果value是其他类型跳过
*
* @param map
*/
private void maskMap(Map<Object, Object> map) {
if (CollectionUtils.isEmpty(map)) {
return;
}
for (Map.Entry<Object, Object> entry : map.entrySet()) {
Object value = entry.getValue();
Object key = entry.getKey();
if (value != null && value.getClass().getName().startsWith(EPMET_PACKAGE_PREFIX)) {
// 是epmet的对象
maskEpmetBean(value);
} else if (value instanceof String) {
int index = fieldNames.indexOf(key);
if (index != -1) {
String maskResult = maskString((String) value, fieldsMaskType.get(index));
entry.setValue(maskResult);
}
} else if (value instanceof List) {
// 列表
((List) value).forEach(e -> mask(e));
}
}
}
/**
* 反射
*
* @param object
*/
private void maskEpmetBean(Object object) {
Field[] declaredFields = object.getClass().getDeclaredFields();
for (Field currentField : declaredFields) {
currentField.setAccessible(true);
try {
String fieldName = currentField.getName();
Object value = currentField.get(object);
// 是epmet的类,继续下钻
if (currentField.getClass().getName().startsWith(EPMET_PACKAGE_PREFIX)) {
maskEpmetBean(value);
continue;
}
// 是字符串
String fieldValue;
if (value instanceof String && StringUtils.isNotBlank(fieldValue = (String) value)) {
int fieldIndexInAnnoAttrs = fieldNames.indexOf(fieldName);
if (fieldIndexInAnnoAttrs != -1) {
String product = maskString(fieldValue, fieldsMaskType.get(fieldIndexInAnnoAttrs));
currentField.set(object, product);
}
continue;
}
// 非字符串,非epmet类的其他类型
mask(value);
} catch (IllegalAccessException e) {
log.error("【mask一些字段报错】{}", ExceptionUtils.getErrorStackTrace(e));
}
}
}
/**
* 把字符串变更为掩码
*
* @param originString
* @return
*/
public String maskString(String originString, String maskType) {
if (MaskResponse.MASK_TYPE_ID_CARD.equals(maskType)) {
return maskIdCard(originString);
} else if (MaskResponse.MASK_TYPE_MOBILE.equals(maskType)) {
return maskMobile(originString);
} else if (MaskResponse.MASK_TYPE_CHINESE_NAME.equals(maskType)) {
return maskChineseName(originString);
} else {
return originString;
}
}
/**
* 对中文人名进行打码
*
* @param originString
* @return
*/
private String maskChineseName(String originString) {
if (StringUtils.isBlank(originString)) {
// 空串,或者只有一个字的,不打码,直接返回
return originString;
}
int length = originString.length();
// 2个字以上的,首位字母明文,中间*
// 中文不能用\\w,要用[\u4e00-\u9fa5]
if (length == 2) {
// return originString.replaceAll("^([\\u4e00-\\u9fa5]).*$", "$1*");
return originString.substring(0).concat("*");
} else {
String maskStr = StrUtil.repeat("*", length - 2);
// return originString.replaceAll("^([\\u4e00-\\u9fa5]).*([\\u4e00-\\u9fa5])$", "$1" + maskStr + "$2");
return originString.charAt(0) + maskStr + originString.charAt(originString.length() - 1);
}
}
/**
* 将明文字符串打码变为掩码保留前3后4中间打码
* 187****3461
*
* @param originString
* @return
*/
private String maskMobile(String originString) {
int length = originString.length();
if (length <= 7) {
return originString;
}
String maskStr = StrUtil.repeatByLength("*", length - 7);
if (length != 11) {
return StringUtils.leftPad(StringUtils.right(originString, 4), length, "*");
}
return originString.replaceAll("^(1\\d{2})\\d*(\\d{4})$", new StringBuilder("$1").append(maskStr).append("$2").toString());
}
}

159
epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/utils/IdCardRegexUtils.java

@ -0,0 +1,159 @@
package com.elink.esua.epdc.commons.tools.utils;
import com.elink.esua.epdc.commons.tools.enums.IdCardTypeEnum;
import com.elink.esua.epdc.commons.tools.exception.ExceptionUtils;
import com.elink.esua.epdc.commons.tools.exception.RenException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.Period;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 唯一整件正则工具
*/
public class IdCardRegexUtils {
/**
* 15位身份证号的正则表达式
*/
private static final Pattern PATTERN_15_ID = Pattern.compile("^\\d{6}(?<year>\\d{2})(?<month>0[1-9]|1[0-2])(?<day>[0-2][0-9]|3[0-1])\\d{2}(?<sex>\\d)$");
/**
* 18位身份证号的正则表达式
*/
private static final Pattern PATTERN_18_ID = Pattern.compile("^\\d{6}(?<year>\\d{4})(?<month>0[1-9]|1[0-2])(?<day>[0-2][0-9]|3[0-1])\\d{2}(?<sex>\\d)[0-9a-xA-X]$");
/**
* 9位护照
*/
private static final Pattern PATTERN_9_PASSPORT = Pattern.compile("^[a-zA-Z0-9]{8,9}$");
private String inputText;
private Matcher matcher;
private IdCardTypeEnum idCardType;
private IdCardRegexUtils(IdCardTypeEnum idCardType, Matcher matcher, String inputText) {
this.idCardType = idCardType;
this.matcher = matcher;
this.inputText = inputText;
}
/**
* desc:校验输入的证件号是否合法
*
* @param input
* @return
*/
public static boolean validateIdCard(String input) {
IdCardRegexUtils parse = IdCardRegexUtils.parse(input);
return parse != null;
}
/**
* 解析正则
*
* @param input
* @return
*/
public static IdCardRegexUtils parse(String input) {
if (input == null || input.trim().length() == 0) {
return null;
}
if (input.length() == 15) {
Matcher matcher = PATTERN_15_ID.matcher(input);
if (matcher.matches()) {
return new IdCardRegexUtils(IdCardTypeEnum.SFZH, matcher, input);
}
}
if (input.length() == 18) {
Matcher matcher = PATTERN_18_ID.matcher(input);
if (matcher.matches()) {
return new IdCardRegexUtils(IdCardTypeEnum.SFZH, matcher, input);
}
}
if (input.length() == 9 || input.length() == 8) {
Matcher matcher = PATTERN_9_PASSPORT.matcher(input);
if (matcher.matches()) {
return new IdCardRegexUtils(IdCardTypeEnum.PASSPORT, matcher, input);
}
}
return null;
}
public static void main(String[] args) {
IdCardRegexUtils parse = IdCardRegexUtils.parse("370282198801303017");
ParsedContent parsedResult = parse.getParsedResult();
System.out.println(parsedResult);
}
/**
* 获取解析结果
*
* @return
*/
public ParsedContent getParsedResult() {
if (matcher == null || idCardType == null) {
return null;
}
if (IdCardTypeEnum.SFZH == idCardType) {
//是身份证号,可以解析
String year;
if (inputText.length() == 15) {
// 15位身份证号,years前需要拼上19
year = "19".concat(matcher.group("year"));
} else {
year = matcher.group("year");
}
String month = matcher.group("month");
String day = matcher.group("day");
String sex = matcher.group("sex");
// ------- 年龄Start----------
Integer age;
try {
LocalDate birthday = LocalDate.of(Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day));
age = Period.between(birthday, LocalDate.now()).getYears();
} catch (DateTimeException e) {
throw new RenException("身份证号解析年龄失败:" + ExceptionUtils.getErrorStackTrace(e));
}
// ------- 年龄End----------
return new ParsedContent(year, month, day, sex, age);
}
// 其他类型暂时不可解析
return null;
}
/**
* 获取类型枚举
*
* @return
*/
public IdCardTypeEnum getTypeEnum() {
return idCardType;
}
/**
* 正则解析结果
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ParsedContent {
private String birthdayYear;
private String birthdayMonth;
private String birthdayDay;
private String sex;
private Integer age;
}
}
Loading…
Cancel
Save