This commit is contained in:
ran.shang
2025-06-11 09:42:13 +08:00
commit 7e38b8d971
1935 changed files with 132836 additions and 0 deletions

View File

@@ -0,0 +1,213 @@
package com.newyetai.common.sensitive;
import org.apache.commons.lang3.StringUtils;
/**
* 脱敏基本工具类
*
* @author qpc
*/
public class SensitiveInfoUtils {
/**
* [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**>
*/
public static String chineseName(final String fullName) {
if (StringUtils.isBlank(fullName)) {
return "";
}
final String name = StringUtils.left(fullName, 1);
return StringUtils.rightPad(name, StringUtils.length(fullName), "*");
}
/**
* [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**>
*/
public static String chineseName(final String familyName, final String givenName) {
if (StringUtils.isBlank(familyName) || StringUtils.isBlank(givenName)) {
return "";
}
return chineseName(familyName + givenName);
}
/**
* [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
*/
public static String idCardNum(final String id) {
if (StringUtils.isBlank(id)) {
return "";
}
return StringUtils.left(id, 3).concat(StringUtils
.removeStart(StringUtils.leftPad(StringUtils.right(id, 3), StringUtils.length(id), "*"),
"***"));
}
/**
* 将字符串中某一段替换成*
* <p>
* 比如 呵呵哈哈哈或或 , start:0 end:1 则变成 *呵哈哈哈或或
*
* @param start
* @param end
* @return
*/
public static String exchangeHashKey(String origin, int start, int end) {
if (org.apache.commons.lang3.StringUtils.isBlank(origin)) {
return null;
}
char[] chars = origin.toCharArray();
int startIndex = start - 1;
for (int i = 0; i < chars.length; i++) {
if (i >= startIndex && i < end) {
chars[i] = '*';
}
}
return new String(chars);
}
/**
* 【身份证号】前三位 和后三位
*
* @param front
* @param end
*/
public static String idCardNum(String idCardNum, int front, int end) {
//身份证不能为空
if (StringUtils.isEmpty(idCardNum)) {
return "";
}
//需要截取的长度不能大于身份证号长度
if ((front + end) > idCardNum.length()) {
return "";
}
//需要截取的不能小于0
if (front < 0 || end < 0) {
return "";
}
//计算*的数量
int asteriskCount = idCardNum.length() - (front + end);
StringBuffer asteriskStr = new StringBuffer();
for (int i = 0; i < asteriskCount; i++) {
asteriskStr.append("*");
}
String regex = "(\\w{" + front + "})(\\w+)(\\w{" + end + "})";
return idCardNum.replaceAll(regex, "$1" + asteriskStr + "$3");
}
/**
* 【固定电话 前四位,后两位
*
* @param num
* @return
*/
public static String fixedPhone(String num) {
if (StringUtils.isBlank(num)) {
return "";
}
return StringUtils.left(num, 4).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, 2), StringUtils.length(num), "*"), "****"));
}
/**
* [手机号码] 前三位,后四位,其他隐藏<例子:138******1234>
*/
public static String mobilePhone2(final String num) {
if (StringUtils.isBlank(num)) {
return "";
}
return StringUtils.left(num, 3).concat(StringUtils
.removeStart(StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"),
"***"));
}
/**
* 【手机号码】前三位,后两位,其他隐藏,比如135******10
*
* @param num
* @return
*/
public static String mobilePhone(String num) {
if (StringUtils.isBlank(num)) {
return "";
}
return StringUtils.left(num, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, 2), StringUtils.length(num), "*"), "***"));
}
/**
* [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护<例子:北京市海淀区****>
*
* @param sensitiveSize 敏感信息长度
*/
public static String address(final String address, final int sensitiveSize) {
if (StringUtils.isBlank(address)) {
return "";
}
if (address.length() <= sensitiveSize) {
return address;
}
final int length = StringUtils.length(address);
return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
}
/**
* [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com>
*/
public static String email(final String email) {
if (StringUtils.isBlank(email)) {
return "";
}
final int index = StringUtils.indexOf(email, "@");
if (index <= 1) {
return email;
} else {
return StringUtils.rightPad(StringUtils.left(email, 1), index, "*")
.concat(StringUtils.mid(email, index, StringUtils.length(email)));
}
}
/**
* [银行卡号] 前六位,后四位,其他用星号隐藏每位1个星号<例子:6222600**********1234>
*/
public static String bankCard(final String cardNum) {
if (StringUtils.isBlank(cardNum)) {
return "";
}
return StringUtils.left(cardNum, 6).concat(StringUtils
.removeStart(StringUtils.leftPad(StringUtils.right(cardNum, 4), StringUtils.length(cardNum), "*"),
"***"));
}
/**
* [公司开户银行联号] 公司开户银行联行号,显示前两位,其他用星号隐藏,每位1个星号<例子:12********>
*/
public static String shopsCode(final String code) {
if (StringUtils.isBlank(code)) {
return "";
}
return StringUtils.rightPad(StringUtils.left(code, 2), StringUtils.length(code), "*");
}
/**
* 【密码】密码的全部字符都用*代替,比如:******
*
* @param password
* @return
*/
public static String password(String password) {
if (StringUtils.isBlank(password)) {
return "";
}
String pwd = StringUtils.left(password, 0);
return StringUtils.rightPad(pwd, StringUtils.length(password), "*");
}
public static void main(String[] args) {
String bankCard = "6214851215669289";
System.out.println(bankCard(bankCard));
}
}

View File

@@ -0,0 +1,64 @@
package com.newyetai.common.sensitive;
/**
* 脱敏类型枚举
*
* @author qpc
*/
public enum SensitiveType {
/**
* 中文名
*/
CHINESE_NAME,
/**
* 身份证号
*/
ID_CARD,
/**
* 座机号
*/
FIXED_PHONE,
/**
* 手机号
*/
MOBILE_PHONE,
/**
* 密码
*/
PASSWORD,
/**
* 地址
*/
ADDRESS,
/**
* 电子邮件
*/
EMAIL,
/**
* 银行卡
*/
BANK_CARD,
/**
* 公司开户银行联号
*/
SHOPS_CODE,
/**
* 自定义正则过滤
*/
RGE_EXP,
/**
* 空值
*/
NULL,
/**
* 自定义处理
*/
SELF,
/**
* 公司名称
*/
COMPANY_NAME
}

View File

@@ -0,0 +1,48 @@
package com.newyetai.common.sensitive.jackson;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.newyetai.common.sensitive.SensitiveType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 脱敏AOP注解
*/
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveInfoSerialize.class)
public @interface SensitiveInfo {
SensitiveType value() default SensitiveType.RGE_EXP;
/**
* 前缀(保留多少位)
*
* @return int
*/
int prefix() default -1;
/**
* 后缀(保留多少位)
*
* @return int
*/
int suffix() default -1;
/**
* 自定义正则匹配规则
*/
String regExp() default "";
/**
* 正则替换字符
*/
String regStr() default "*";
/**
* 自定义处理标识
*/
String tag() default "self";
}

View File

@@ -0,0 +1,84 @@
package com.newyetai.common.sensitive.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.newyetai.common.sensitive.SensitiveType;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.Objects;
/**
* jackson的脱敏实现
*
* @author doobo
*/
@Slf4j
public class SensitiveInfoObjectSerialize extends JsonSerializer<Object> implements ContextualSerializer {
private SensitiveType type;
public SensitiveInfoObjectSerialize() {
}
public SensitiveInfoObjectSerialize(final SensitiveInfo sensitiveInfo) {
this.type = sensitiveInfo.value();
}
@Override
public void serialize(final Object s, final JsonGenerator jsonGenerator,
final SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
try {
if (this.type == SensitiveType.NULL && !isBaseType(s.getClass())) {
jsonGenerator.writeObject(null);
return;
}
jsonGenerator.writeObject(s);
} catch (Exception e) {
log.error("脱敏数据处理异常", e);
jsonGenerator.writeObject(s);
}
}
@Override
public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
final BeanProperty beanProperty) throws JsonMappingException {
// 为空直接跳过
if (beanProperty != null) {
// 非 String 类直接跳过
if (Objects.equals(beanProperty.getType().getRawClass(), Object.class)) {
SensitiveInfo sensitiveInfo = beanProperty.getAnnotation(SensitiveInfo.class);
if (sensitiveInfo == null) {
sensitiveInfo = beanProperty.getContextAnnotation(SensitiveInfo.class);
}
if (sensitiveInfo != null && sensitiveInfo.value() == SensitiveType.NULL) {
return new SensitiveInfoObjectSerialize(sensitiveInfo);
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
/**
* 判断object是否为基本类型
*
* @param cls
*/
public boolean isBaseType(Class<?> cls) {
return cls.equals(int.class) ||
cls.equals(byte.class) ||
cls.equals(long.class) ||
cls.equals(double.class) ||
cls.equals(float.class) ||
cls.equals(char.class) ||
cls.equals(short.class) ||
cls.equals(boolean.class);
}
}

View File

@@ -0,0 +1,125 @@
package com.newyetai.common.sensitive.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.newyetai.common.sensitive.SensitiveInfoUtils;
import com.newyetai.common.sensitive.SensitiveType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.util.Objects;
/**
* jackson的脱敏实现
*
* @author doobo
*/
@Slf4j
public class SensitiveInfoSerialize extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveType type;
private SensitiveInfo sensitiveInfo;
public SensitiveInfoSerialize() {
}
public SensitiveInfoSerialize(final SensitiveInfo sensitiveInfo) {
this.type = sensitiveInfo.value();
this.sensitiveInfo = sensitiveInfo;
}
private int getOrDefault(int value, int defaultValue) {
return value < 0 ? defaultValue : value;
}
@Override
public void serialize(final String s, final JsonGenerator jsonGenerator,
final SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
try {
//有正则优先使用正则
if (StringUtils.isNotBlank(sensitiveInfo.regExp())) {
jsonGenerator.writeString(s.replaceAll(sensitiveInfo.regExp(), sensitiveInfo.regStr()));
return;
}
switch (this.type) {
case CHINESE_NAME: {
jsonGenerator.writeString(SensitiveInfoUtils.chineseName(s));
break;
}
case ID_CARD:
case MOBILE_PHONE: {
jsonGenerator.writeString(SensitiveInfoUtils.idCardNum(s
, getOrDefault(sensitiveInfo.prefix(), 3), getOrDefault(sensitiveInfo.suffix(), 4)));
break;
}
case FIXED_PHONE: {
jsonGenerator.writeString(SensitiveInfoUtils.fixedPhone(s));
break;
}
case PASSWORD: {
jsonGenerator.writeString(SensitiveInfoUtils.password(s));
break;
}
case ADDRESS: {
jsonGenerator.writeString(SensitiveInfoUtils.address(s, getOrDefault(sensitiveInfo.prefix(), 8)));
break;
}
case EMAIL: {
jsonGenerator.writeString(SensitiveInfoUtils.email(s));
break;
}
case BANK_CARD: {
jsonGenerator.writeString(SensitiveInfoUtils.bankCard(s));
break;
}
case SHOPS_CODE: {
jsonGenerator.writeString(SensitiveInfoUtils.shopsCode(s));
break;
}
case NULL: {
jsonGenerator.writeString((String) null);
break;
}
case COMPANY_NAME: {
jsonGenerator.writeString(SensitiveInfoUtils.exchangeHashKey(s
, getOrDefault(sensitiveInfo.prefix(), 0), getOrDefault(sensitiveInfo.suffix(), 3)));
break;
}
default: {
jsonGenerator.writeString(s);
}
}
} catch (Exception e) {
log.error("脱敏数据处理异常", e);
jsonGenerator.writeString(s);
}
}
@Override
public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
final BeanProperty beanProperty) throws JsonMappingException {
// 为空直接跳过
if (beanProperty != null) {
SensitiveInfo sensitiveInfo = beanProperty.getAnnotation(SensitiveInfo.class);
// 非 String 类直接跳过
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
if (sensitiveInfo != null) {
// 如果能得到注解,就将注解的 value 传入 SensitiveInfoSerialize
return new SensitiveInfoSerialize(sensitiveInfo);
}
}
if (sensitiveInfo != null && sensitiveInfo.value() == SensitiveType.NULL) {
return new SensitiveInfoObjectSerialize(sensitiveInfo);
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
}

View File

@@ -0,0 +1,69 @@
package com.newyetai.common.sensitive.service;
import com.newyetai.common.sensitive.SensitiveInfoUtils;
/**
* 默认脱敏工具抽象类
* 使用时,可继承并重写(覆盖)相关方法
*/
public abstract class AbstractSensitiveService implements SensitiveService {
@Override
public String chineseName(String fullName) {
return SensitiveInfoUtils.chineseName(fullName);
}
@Override
public String chineseName(String familyName, String givenName) {
return SensitiveInfoUtils.chineseName(familyName, givenName);
}
@Override
public String idCardNum(String id) {
return SensitiveInfoUtils.idCardNum(id);
}
@Override
public String idCardNum(String idCardNum, int front, int end) {
return SensitiveInfoUtils.idCardNum(idCardNum, front, end);
}
@Override
public String fixedPhone(String num) {
return SensitiveInfoUtils.fixedPhone(num);
}
@Override
public String mobilePhone2(String num) {
return SensitiveInfoUtils.mobilePhone2(num);
}
@Override
public String mobilePhone(String num) {
return SensitiveInfoUtils.mobilePhone(num);
}
@Override
public String address(String address, int sensitiveSize) {
return SensitiveInfoUtils.address(address, sensitiveSize);
}
@Override
public String email(String email) {
return SensitiveInfoUtils.email(email);
}
@Override
public String bankCard(String cardNum) {
return SensitiveInfoUtils.bankCard(cardNum);
}
@Override
public String shopsCode(String code) {
return SensitiveInfoUtils.shopsCode(code);
}
@Override
public String password(String password) {
return SensitiveInfoUtils.password(password);
}
}

View File

@@ -0,0 +1,69 @@
package com.newyetai.common.sensitive.service;
/**
* 脱敏服务
*/
public interface SensitiveService {
/**
* [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**>
*/
String chineseName(final String fullName);
/**
* [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**>
*/
String chineseName(final String familyName, final String givenName);
/**
* [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
*/
String idCardNum(final String id);
/**
* 【身份证号】前三位 和后三位
*/
String idCardNum(String idCardNum, int front, int end);
/**
* 【固定电话 前四位,后两位
*/
String fixedPhone(String num);
/**
* [手机号码] 前三位,后四位,其他隐藏<例子:138******1234>
*/
String mobilePhone2(final String num);
/**
* 【手机号码】前三位,后两位,其他隐藏,比如135******10
*/
String mobilePhone(String num);
/**
* [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护<例子:北京市海淀区****>
*
* @param sensitiveSize 敏感信息长度
*/
String address(final String address, final int sensitiveSize);
/**
* [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com>
*/
String email(final String email);
/**
* [银行卡号] 前六位,后四位,其他用星号隐藏每位1个星号<例子:6222600**********1234>
*/
String bankCard(final String cardNum);
/**
* [公司开户银行联号] 公司开户银行联行号,显示前两位,其他用星号隐藏,每位1个星号<例子:12********>
*/
String shopsCode(final String code);
/**
* 【密码】密码的全部字符都用*代替,比如:******
*/
String password(String password);
}

View File

@@ -0,0 +1,32 @@
package com.newyetai.common.sensitive.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 脱敏服务工具类
*/
@Component
public class SensitiveServiceUtils {
/**
* 脱敏工具
*/
private static SensitiveService sensitiveService;
@Autowired(required = false)
public void setSensitiveService(SensitiveService sensitiveService) {
SensitiveServiceUtils.sensitiveService = sensitiveService;
}
/**
* 获取脱敏工具类
*/
public static SensitiveService getSensitiveService() {
if (SensitiveServiceUtils.sensitiveService == null) {
SensitiveServiceUtils.sensitiveService = new AbstractSensitiveService() {
};
}
return SensitiveServiceUtils.sensitiveService;
}
}