diff --git a/pom.xml b/pom.xml
index 6a4a7542..2a0937ef 100644
--- a/pom.xml
+++ b/pom.xml
@@ -164,6 +164,13 @@
${ruoyi.version}
+
+
+ com.ruoyi
+ ruoyi-common-sensitive
+ ${ruoyi.version}
+
+
com.ruoyi
diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
index 42efa9da..3129e2d6 100644
--- a/ruoyi-common/pom.xml
+++ b/ruoyi-common/pom.xml
@@ -15,6 +15,7 @@
ruoyi-common-seata
ruoyi-common-swagger
ruoyi-common-security
+ ruoyi-common-sensitive
ruoyi-common-datascope
ruoyi-common-datasource
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StringUtils.java
index 917bb82f..cc9f12b8 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StringUtils.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StringUtils.java
@@ -20,6 +20,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/** 下划线 */
private static final char SEPARATOR = '_';
+ /** 星号 */
+ private static final char ASTERISK = '*';
+
/**
* 获取参数不为空值
*
@@ -160,6 +163,49 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return (str == null ? "" : str.trim());
}
+ /**
+ * 替换指定字符串的指定区间内字符为"*"
+ *
+ * @param str 字符串
+ * @param startInclude 开始位置(包含)
+ * @param endExclude 结束位置(不包含)
+ * @return 替换后的字符串
+ */
+ public static String hide(CharSequence str, int startInclude, int endExclude)
+ {
+ if (isEmpty(str))
+ {
+ return NULLSTR;
+ }
+ final int strLength = str.length();
+ if (startInclude > strLength)
+ {
+ return NULLSTR;
+ }
+ if (endExclude > strLength)
+ {
+ endExclude = strLength;
+ }
+ if (startInclude > endExclude)
+ {
+ // 如果起始位置大于结束位置,不替换
+ return NULLSTR;
+ }
+ final char[] chars = new char[strLength];
+ for (int i = 0; i < strLength; i++)
+ {
+ if (i >= startInclude && i < endExclude)
+ {
+ chars[i] = ASTERISK;
+ }
+ else
+ {
+ chars[i] = str.charAt(i);
+ }
+ }
+ return new String(chars);
+ }
+
/**
* 截取字符串
*
diff --git a/ruoyi-common/ruoyi-common-sensitive/pom.xml b/ruoyi-common/ruoyi-common-sensitive/pom.xml
new file mode 100644
index 00000000..f5f5c1d3
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sensitive/pom.xml
@@ -0,0 +1,27 @@
+
+
+
+ com.ruoyi
+ ruoyi-common
+ 3.6.4
+
+ 4.0.0
+
+ ruoyi-common-sensitive
+
+
+ ruoyi-common-sensitive数据脱敏
+
+
+
+
+
+
+ com.ruoyi
+ ruoyi-common-security
+
+
+
+
\ No newline at end of file
diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/annotation/Sensitive.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/annotation/Sensitive.java
new file mode 100644
index 00000000..30b24b3d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/annotation/Sensitive.java
@@ -0,0 +1,24 @@
+package com.ruoyi.common.sensitive.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.sensitive.config.SensitiveJsonSerializer;
+import com.ruoyi.common.sensitive.enums.DesensitizedType;
+
+/**
+ * 数据脱敏注解
+ *
+ * @author ruoyi
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@JacksonAnnotationsInside
+@JsonSerialize(using = SensitiveJsonSerializer.class)
+public @interface Sensitive
+{
+ DesensitizedType desensitizedType();
+}
diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/config/SensitiveJsonSerializer.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/config/SensitiveJsonSerializer.java
new file mode 100644
index 00000000..88bfe00b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/config/SensitiveJsonSerializer.java
@@ -0,0 +1,67 @@
+package com.ruoyi.common.sensitive.config;
+
+import java.io.IOException;
+import java.util.Objects;
+import com.fasterxml.jackson.core.JsonGenerator;
+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.ruoyi.common.security.utils.SecurityUtils;
+import com.ruoyi.common.sensitive.annotation.Sensitive;
+import com.ruoyi.common.sensitive.enums.DesensitizedType;
+import com.ruoyi.system.api.model.LoginUser;
+
+/**
+ * 数据脱敏序列化过滤
+ *
+ * @author ruoyi
+ */
+public class SensitiveJsonSerializer extends JsonSerializer implements ContextualSerializer
+{
+ private DesensitizedType desensitizedType;
+
+ @Override
+ public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException
+ {
+ if (desensitization())
+ {
+ gen.writeString(desensitizedType.desensitizer().apply(value));
+ }
+ else
+ {
+ gen.writeString(value);
+ }
+ }
+
+ @Override
+ public JsonSerializer> createContextual(SerializerProvider prov, BeanProperty property)
+ throws JsonMappingException
+ {
+ Sensitive annotation = property.getAnnotation(Sensitive.class);
+ if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass()))
+ {
+ this.desensitizedType = annotation.desensitizedType();
+ return this;
+ }
+ return prov.findValueSerializer(property.getType(), property);
+ }
+
+ /**
+ * 是否需要脱敏处理
+ */
+ private boolean desensitization()
+ {
+ try
+ {
+ LoginUser securityUser = SecurityUtils.getLoginUser();
+ // 管理员不脱敏
+ return !securityUser.getSysUser().isAdmin();
+ }
+ catch (Exception e)
+ {
+ return true;
+ }
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/enums/DesensitizedType.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/enums/DesensitizedType.java
new file mode 100644
index 00000000..94ceada6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/enums/DesensitizedType.java
@@ -0,0 +1,59 @@
+package com.ruoyi.common.sensitive.enums;
+
+import java.util.function.Function;
+import com.ruoyi.common.sensitive.utils.DesensitizedUtil;
+
+/**
+ * 脱敏类型
+ *
+ * @author ruoyi
+ */
+public enum DesensitizedType
+{
+ /**
+ * 姓名,第2位星号替换
+ */
+ USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
+
+ /**
+ * 密码,全部字符都用*代替
+ */
+ PASSWORD(DesensitizedUtil::password),
+
+ /**
+ * 身份证,中间10位星号替换
+ */
+ ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\d{4})", "$1** **** ****$2")),
+
+ /**
+ * 手机号,中间4位星号替换
+ */
+ PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
+
+ /**
+ * 电子邮箱,仅显示第一个字母和@后面的地址显示,其他星号替换
+ */
+ EMAIL(s -> s.replaceAll("(^.)[^@]*(@.*$)", "$1****$2")),
+
+ /**
+ * 银行卡号,保留最后4位,其他星号替换
+ */
+ BANK_CARD(s -> s.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1")),
+
+ /**
+ * 车牌号码,包含普通车辆、新能源车辆
+ */
+ CAR_LICENSE(DesensitizedUtil::carLicense);
+
+ private final Function desensitizer;
+
+ DesensitizedType(Function desensitizer)
+ {
+ this.desensitizer = desensitizer;
+ }
+
+ public Function desensitizer()
+ {
+ return desensitizer;
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/utils/DesensitizedUtil.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/utils/DesensitizedUtil.java
new file mode 100644
index 00000000..9c542d68
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/com/ruoyi/common/sensitive/utils/DesensitizedUtil.java
@@ -0,0 +1,51 @@
+package com.ruoyi.common.sensitive.utils;
+
+import com.ruoyi.common.core.utils.StringUtils;
+
+/**
+ * 脱敏工具类
+ *
+ * @author ruoyi
+ */
+public class DesensitizedUtil
+{
+ /**
+ * 密码的全部字符都用*代替,比如:******
+ *
+ * @param password 密码
+ * @return 脱敏后的密码
+ */
+ public static String password(String password)
+ {
+ if (StringUtils.isBlank(password))
+ {
+ return StringUtils.EMPTY;
+ }
+ return StringUtils.repeat('*', password.length());
+ }
+
+ /**
+ * 车牌中间用*代替,如果是错误的车牌,不处理
+ *
+ * @param carLicense 完整的车牌号
+ * @return 脱敏后的车牌
+ */
+ public static String carLicense(String carLicense)
+ {
+ if (StringUtils.isBlank(carLicense))
+ {
+ return StringUtils.EMPTY;
+ }
+ // 普通车牌
+ if (carLicense.length() == 7)
+ {
+ carLicense = StringUtils.hide(carLicense, 3, 6);
+ }
+ else if (carLicense.length() == 8)
+ {
+ // 新能源车牌
+ carLicense = StringUtils.hide(carLicense, 3, 7);
+ }
+ return carLicense;
+ }
+}