SpringBoot项目中新增脱敏功能
项目背景
目前正在开发一个SpringBoot项目,此项目有Web端和微信小程序端。web端提供给工作人员使用,微信小程序提供给群众进行预约操作。项目中有部分敏感数据需要脱敏传递给微信小程序,给与群众查看。
项目需求描述
项目中,由于使用端有两个,对于两个端的数据权限并不一样。Web端可以查看所有数据,小程序端只能查看脱敏后的数据。
需要开发一个通用脱敏功能
手动进行脱敏操作
支持多种对象,
支持不同字段,并脱敏指定字段
字段的脱敏方式多样
字段的脱敏方式可自定义
项目解决方案
1. 解决方案
使用注解方式
,来支持对指定字段,不同字段,多种脱敏操作,并可以脱离对象。
使用工具对象,通过泛型传参,来支持对不同对象的脱敏操作。
2. 实现代码
2.1 注解 Sensitive
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义数据脱敏 * * 例如: 身份证,手机号等信息进行模糊处理 * * @author lzddddd */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Sensitive { /** * 脱敏数据类型 */ SensitiveType type() default SensitiveType.CUSTOMER; /** * 前置不需要打码的长度 */ int prefixNoMaskLen() default 0; /** * 后置不需要打码的长度 */ int suffixNoMaskLen() default 0; /** * 用什么打码 */ String symbol() default "*"; }
2.1 脱敏类型枚举 SensitiveType
public enum SensitiveType { /** * 自定义 */ CUSTOMER, /** * 名称 **/ CHINESE_NAME, /** * 身份证证件号 **/ ID_CARD_NUM, /** * 手机号 **/ MOBILE_PHONE, /** * 固定电话 */ FIXED_PHONE, /** * 密码 **/ PASSWORD, /** * 银行卡号 */ BANKCARD, /** * 邮箱 */ EMAIL, /** * 地址 */ ADDRESS, }
2.3 脱敏工具 DesensitizedUtils
import com.ruoyi.common.annotation.Sensitive; import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.enums.SensitiveType; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.Field; import java.util.*; @Slf4j public class DesensitizedUtils<T> { /** * 脱敏数据列表 */ private List<T> list; /** * 注解列表 */ private List<Object[]> fields; /** * 实体对象 */ public Class<T> clazz; public DesensitizedUtils(Class<T> clazz) { this.clazz = clazz; } /** * 初始化数据 * * @param list 需要处理数据 */ public void init(List<T> list){ if (list == null) { list = new ArrayList<T>(); } this.list = list; // 得到所有定义字段 createSensitiveField(); } /** * 初始化数据 * * @param t 需要处理数据 */ public void init(T t){ list = new ArrayList<T>(); if (t != null) { list.add(t); } // 得到所有定义字段 createSensitiveField(); } /** * 得到所有定义字段 */ private void createSensitiveField() { this.fields = new ArrayList<Object[]>(); List<Field> tempFields = new ArrayList<>(); tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); for (Field field : tempFields) { // 单注解 if (field.isAnnotationPresent(Sensitive.class)) { putToField(field, field.getAnnotation(Sensitive.class)); } // 多注解 // if (field.isAnnotationPresent(Excels.class)) // { // Excels attrs = field.getAnnotation(Excels.class); // Excel[] excels = attrs.value(); // for (Excel excel : excels) // { // putToField(field, excel); // } // } } } /** * 对list数据源将其里面的数据进行脱敏处理 * * @param list * @return 结果 */ public AjaxResult desensitizedList(List<T> list){ if (list == null){ return AjaxResult.error("脱敏数据为空"); } // 初始化数据 this.init(list); int failTimes = 0; for (T t: this.list) { if ((Integer)desensitization(t).get("code") != HttpStatus.SUCCESS){ failTimes++; } } if (failTimes >0){ return AjaxResult.error("脱敏操作中出现失败",failTimes); } return AjaxResult.success(); } /** * 放到字段集合中 */ private void putToField(Field field, Sensitive attr) { if (attr != null) { this.fields.add(new Object[] { field, attr }); } } /** * 脱敏:JavaBean模式脱敏 * * @param t 需要脱敏的对象 * @return */ public AjaxResult desensitization(T t) { if (t == null){ return AjaxResult.error("脱敏数据为空"); } // 初始化数据 init(t); try { // 遍历处理需要进行 脱敏的字段 for (Object[] os : fields) { Field field = (Field) os[0]; Sensitive sensitive = (Sensitive) os[1]; // 设置实体类私有属性可访问 field.setAccessible(true); desensitizeField(sensitive,t,field); } return AjaxResult.success(t); } catch (Exception e) { e.printStackTrace(); log.error("日志脱敏处理失败,回滚,详细信息:[{}]", e); return AjaxResult.error("脱敏处理失败",e); } } /** * 对类的属性进行脱敏 * * @param attr 脱敏参数 * @param vo 脱敏对象 * @param field 脱敏属性 * @return */ private void desensitizeField(Sensitive attr, T vo, Field field) throws IllegalAccessException { if (attr == null || vo == null || field == null){ return ; } // 读取对象中的属性 Object value = field.get(vo); SensitiveType sensitiveType = attr.type(); int prefixNoMaskLen = attr.prefixNoMaskLen(); int suffixNoMaskLen = attr.suffixNoMaskLen(); String symbol = attr.symbol(); //获取属性后现在默认处理的是String类型,其他类型数据可扩展 Object val = convertByType(sensitiveType, value, prefixNoMaskLen, suffixNoMaskLen, symbol); field.set(vo, val); } /** * 以类的属性的get方法方法形式获取值 * * @param o 对象 * @param name 属性名 * @return value * @throws Exception */ private Object getValue(Object o, String name) throws Exception { if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) { Class<?> clazz = o.getClass(); Field field = clazz.getDeclaredField(name); field.setAccessible(true); o = field.get(o); } return o; } /** * 根据不同注解类型处理不同字段 */ private Object convertByType(SensitiveType sensitiveType, Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) { switch (sensitiveType) { case CUSTOMER: value = customer(value, prefixNoMaskLen, suffixNoMaskLen, symbol); break; case CHINESE_NAME: value = chineseName(value, symbol); break; case ID_CARD_NUM: value = idCardNum(value, symbol); break; case MOBILE_PHONE: value = mobilePhone(value, symbol); break; case FIXED_PHONE: value = fixedPhone(value, symbol); break; case PASSWORD: value = password(value, symbol); break; case BANKCARD: value = bankCard(value, symbol); break; case EMAIL: value = email(value, symbol); break; case ADDRESS: value = address(value, symbol); break; } return value; } /*--------------------------下面的脱敏工具类也可以单独对某一个字段进行使用-------------------------*/ /** * 【自定义】 根据设置进行配置 * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public Object customer(Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) { //针对字符串的处理 if (value instanceof String){ return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return value; } /** * 对字符串进行脱敏处理 * * @param s 需处理数据 * @param prefixNoMaskLen 开头展示字符长度 * @param suffixNoMaskLen 结尾展示字符长度 * @param symbol 填充字符 * @return */ private String handleString(String s, int prefixNoMaskLen, int suffixNoMaskLen, String symbol){ // 是否为空 if (StringUtils.isBlank(s)) { return ""; } // 如果设置为空之类使用 * 代替 if (StringUtils.isBlank(symbol)){ symbol = "*"; } // 对长度进行判断 int length = s.length(); if (length > prefixNoMaskLen + suffixNoMaskLen){ String namePrefix = StringUtils.left(s, prefixNoMaskLen); String nameSuffix = StringUtils.right(s, suffixNoMaskLen); s = StringUtils.rightPad(namePrefix, StringUtils.length(s) - suffixNoMaskLen, symbol).concat(nameSuffix); } return s; } /** * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李** * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String chineseName(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示一个字符 int prefixNoMaskLen = 1; int suffixNoMaskLen = 0; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【身份证号】显示最后四位,其他隐藏。共计18位或者15位,比如:*************1234 * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String idCardNum(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 结尾只展示四个字符 int prefixNoMaskLen = 0; int suffixNoMaskLen = 4; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【固定电话】 显示后四位,其他隐藏,比如:*******3241 * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String fixedPhone(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 结尾只展示四个字符 int prefixNoMaskLen = 0; int suffixNoMaskLen = 4; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【手机号码】前三位,后四位,其他隐藏,比如:135****6810 * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String mobilePhone(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示三个字符 结尾只展示四个字符 int prefixNoMaskLen = 3; int suffixNoMaskLen = 4; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【地址】只显示到地区,不显示详细地址,比如:湖南省长沙市岳麓区*** * 只能处理 省市区的数据 * * @param value 需处理数据 * @param symbol 填充字符 * @return */ public String address(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示九个字符 int prefixNoMaskLen = 9; int suffixNoMaskLen = 0; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【电子邮箱】 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String email(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示一个字符 结尾只展示@及后面的地址 int prefixNoMaskLen = 1; int suffixNoMaskLen = 4; String s = (String) value; if (StringUtils.isBlank(s)) { return ""; } // 获取最后一个@ int lastIndex = StringUtils.lastIndexOf(s, "@"); if (lastIndex <= 1) { return s; } else { suffixNoMaskLen = s.length() - lastIndex; } return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【银行卡号】前六位,后四位,其他用星号隐藏每位1个星号,比如:6222600**********1234 * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String bankCard(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示六个字符 结尾只展示四个字符 int prefixNoMaskLen = 6; int suffixNoMaskLen = 4; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【密码】密码的全部字符都用*代替,比如:****** * * @param value 需处理数据 * @param symbol 填充字符 * @return */ public String password(Object value,String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示六个字符 结尾只展示四个字符 int prefixNoMaskLen = 0; int suffixNoMaskLen = 0; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } }
3 使用实例
3.1 需注解对象
public class User { private static final long serialVersionUID = 1L; /** 普通用户ID */ private Long userId; /** 昵称 */ @Sensitive(type = SensitiveType.CUSTOMER,prefixNoMaskLen = 2,suffixNoMaskLen = 1) private String nickName; /** 姓名 */ @Sensitive(type = SensitiveType.CHINESE_NAME) private String userName; /** 身份证 */ @Sensitive(type = SensitiveType.ID_CARD_NUM) private String identityCard; /** 手机号码 */ @Sensitive(type = SensitiveType.MOBILE_PHONE) private String phoneNumber; }
3.2 脱敏操作
// 脱敏对象 User user = new User(); ...... DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class); desensitizedUtils.desensitization(user); //脱敏队列 List<User> users = new ArrayList<>(); ...... DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class); desensitizedUtils.desensitizedList(users);
以上是SpringBoot怎么新增脱敏功能的详细内容。更多信息请关注PHP中文网其他相关文章!

新兴技术对Java的平台独立性既有威胁也有增强。1)云计算和容器化技术如Docker增强了Java的平台独立性,但需要优化以适应不同云环境。2)WebAssembly通过GraalVM编译Java代码,扩展了其平台独立性,但需与其他语言竞争性能。

不同JVM实现都能提供平台独立性,但表现略有不同。1.OracleHotSpot和OpenJDKJVM在平台独立性上表现相似,但OpenJDK可能需额外配置。2.IBMJ9JVM在特定操作系统上表现优化。3.GraalVM支持多语言,需额外配置。4.AzulZingJVM需特定平台调整。

平台独立性通过在多种操作系统上运行同一套代码,降低开发成本和缩短开发时间。具体表现为:1.减少开发时间,只需维护一套代码;2.降低维护成本,统一测试流程;3.快速迭代和团队协作,简化部署过程。

Java'splatformindependencefacilitatescodereusebyallowingbytecodetorunonanyplatformwithaJVM.1)Developerscanwritecodeonceforconsistentbehavioracrossplatforms.2)Maintenanceisreducedascodedoesn'tneedrewriting.3)Librariesandframeworkscanbesharedacrossproj

要解决Java应用程序中的平台特定问题,可以采取以下步骤:1.使用Java的System类查看系统属性以了解运行环境。2.利用File类或java.nio.file包处理文件路径。3.根据操作系统条件加载本地库。4.使用VisualVM或JProfiler优化跨平台性能。5.通过Docker容器化确保测试环境与生产环境一致。6.利用GitHubActions在多个平台上进行自动化测试。这些方法有助于有效地解决Java应用程序中的平台特定问题。

类加载器通过统一的类文件格式、动态加载、双亲委派模型和平台无关的字节码,确保Java程序在不同平台上的一致性和兼容性,实现平台独立性。

Java编译器生成的代码是平台无关的,但最终执行的代码是平台特定的。1.Java源代码编译成平台无关的字节码。2.JVM将字节码转换为特定平台的机器码,确保跨平台运行但性能可能不同。

多线程在现代编程中重要,因为它能提高程序的响应性和资源利用率,并处理复杂的并发任务。JVM通过线程映射、调度机制和同步锁机制,在不同操作系统上确保多线程的一致性和高效性。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

Atom编辑器mac版下载
最流行的的开源编辑器

SublimeText3汉化版
中文版,非常好用

Dreamweaver Mac版
视觉化网页开发工具

禅工作室 13.0.1
功能强大的PHP集成开发环境