SpringBoot项目数据脱敏(自定义注解)

文章目录

  • 前言
  • 一.配置
    • 1.脱敏类型枚举:DesensitizeType
    • 2.注解:Desensitize
    • 3.序列化类:DesensitizeJsonSerializer
    • 4.工具类:DesensitizeUtil
  • 二、测试:DesensitizeTest
  • 三、效果展示
  • 总结

前言

在互联网应用中,用户隐私数据(如姓名、身份证号、手机号、邮箱等)的保护至关重要。为了防止敏感信息在日志、接口返回、前端展示等环节泄露,数据脱敏 成为系统开发中的必备功能

一.配置

1.脱敏类型枚举:DesensitizeType

import lombok.Getter;
/*** 脱敏类型枚举*/
@Getter
public enum DesensitizeType {NAME(0, "中文姓名"),ID_CARD(1, "身份证号"),EMAIL(2, "邮箱"),PHONE(3, "手机号"),CUSTOM(4, "自定义");private final int code;private final String description;DesensitizeType(int code, String description) {this.code = code;this.description = description;}/*** 通过 code 反查枚举*/public static DesensitizeType fromCode(int code) {for (DesensitizeType type : values()) {if (type.getCode() == code) {return type;}}throw new IllegalArgumentException("未知的脱敏类型 code: " + code);}
}

使用 @Getter 自动生成 getter,fromCode 支持通过 code 查找枚举,便于后续扩展配置化脱敏策略

2.注解:Desensitize

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fc.enums.DesensitizeType;
import com.fc.serializer.DesensitizeJsonSerializer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 自定义数据脱敏注解*/
@Target(ElementType.FIELD)//用于字段
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizeJsonSerializer.class) // 该注解使用序列化的方式
public @interface Desensitize {DesensitizeType type();//脱敏数据类型(必须指定类型)int prefixNoMaskLen() default 3; // 手机号通常保留前3位int suffixNoMaskLen() default 4; // 手机号通常保留后4位String symbol() default "*";//替换符号
}

3.序列化类:DesensitizeJsonSerializer

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.fc.anno.Desensitize;
import com.fc.enums.DesensitizeType;
import com.fc.utils.DesensitizeUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Objects;
import java.util.function.UnaryOperator;
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DesensitizeJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {private DesensitizeType type;private int prefixLen;private int suffixLen;private String mask;/* 策略表:DesensitizeType → 函数 */private static final EnumMap<DesensitizeType, UnaryOperator<String>> STRATEGY = new EnumMap<>(DesensitizeType.class);static {STRATEGY.put(DesensitizeType.NAME, DesensitizeUtil::hideName);STRATEGY.put(DesensitizeType.ID_CARD, DesensitizeUtil::hideIdCard);STRATEGY.put(DesensitizeType.EMAIL, DesensitizeUtil::hideEmail);STRATEGY.put(DesensitizeType.PHONE, DesensitizeUtil::hidePhone);}@Overridepublic void serialize(String value, JsonGenerator gen, SerializerProvider serializers)throws IOException {if (value == null) {                // 1) 空值透传gen.writeNull();return;}if (type == DesensitizeType.CUSTOM) {gen.writeString(DesensitizeUtil.customMask(value, prefixLen, suffixLen, mask));} else {UnaryOperator<String> func = STRATEGY.get(type);if (func == null) {throw new IllegalArgumentException("未注册的策略:" + type);}gen.writeString(func.apply(value));}}/* 读取注解信息,构造专属序列化器 */@Overridepublic JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)throws JsonMappingException {if (property != null && Objects.equals(property.getType().getRawClass(), String.class)) {Desensitize anno = property.getAnnotation(Desensitize.class);if (anno != null) {return new DesensitizeJsonSerializer(anno.type(), anno.prefixNoMaskLen(), anno.suffixNoMaskLen(), anno.symbol());}}return prov.findValueSerializer(property.getType(), property);}
}

ContextualSerializer 允许在序列化前读取字段的注解信息。
每个带 @Desensitize 的字段都会生成一个“专属”序列化器,携带注解参数。
使用 EnumMap 存储策略,性能优于 if-else 或 switch。

4.工具类:DesensitizeUtil

import org.apache.commons.lang.StringUtils;
import java.util.regex.Pattern;
/*** 脱敏工具类*/
public class DesensitizeUtil {/* 预编译正则表达式(提升性能) */private static final Pattern PHONE_PATTERN = Pattern.compile("(\\d{3})\\d{4}(\\d{4})");private static final Pattern EMAIL_PATTERN = Pattern.compile("(^.)[^@]*(@.*$)");private static final Pattern ID_CARD_PATTERN = Pattern.compile("(\\d{4})\\d{10}(\\w{4})");/*** 中文姓名:张三 → 张**/public static String hideName(String name) {if (StringUtils.isBlank(name)) return name;return name.charAt(0) + "*".repeat(Math.max(0, name.length() - 1));}/*** 手机号:13812345678 → 138****5678*/public static String hidePhone(String phone) {if (StringUtils.isBlank(phone)) return phone;return PHONE_PATTERN.matcher(phone).replaceFirst("$1****$2");}/*** 邮箱:zhangsan@163.com → z****@163.com*/public static String hideEmail(String email) {if (StringUtils.isBlank(email)) return email;return EMAIL_PATTERN.matcher(email).replaceFirst("$1****$2");}/*** 身份证:123456789012345678 → 1234****5678*/public static String hideIdCard(String idCard) {if (StringUtils.isBlank(idCard)) return idCard;return ID_CARD_PATTERN.matcher(idCard).replaceFirst("$1****$2");}/*** 通用脱敏:保留前后指定长度,中间用符号填充*/public static String customMask(String origin, int prefix, int suffix, String mask) {if (origin == null || origin.isEmpty()) return origin;int len = origin.length();if (prefix + suffix >= len) return origin; // 不脱敏String prefixStr = origin.substring(0, prefix);String suffixStr = origin.substring(len - suffix);String masked = mask.repeat(len - prefix - suffix);return prefixStr + masked + suffixStr;}
}

使用 StringUtils.isBlank 来安全判断空值;正则预编译提升性能

二、测试:DesensitizeTest

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fc.anno.Desensitize;
import com.fc.enums.DesensitizeType;
import lombok.Builder;
import lombok.Data;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
public class DesensitizeTest {@Data@Builderpublic static class UserVO {private Long id;private Long roleId;@Desensitize(type = DesensitizeType.ID_CARD)private String idCard;@Desensitize(type = DesensitizeType.NAME)private String name;@Desensitize(type = DesensitizeType.PHONE)private String phoneNumber;@Desensitize(type = DesensitizeType.EMAIL)private String email;@Desensitize(type = DesensitizeType.CUSTOM, prefixNoMaskLen = 2, suffixNoMaskLen = 2, symbol = "#")private String bankCard;private String username;}@Testpublic void testDesensitizeWithInlineVO() throws JsonProcessingException {// 创建测试数据UserVO user = UserVO.builder().id(1L).roleId(101L).idCard("110105199003075678").name("欧阳锋").phoneNumber("13812345678").email("ouyangfeng@shaguyin.com").bankCard("6228480031567890123").username("ofeng").build();// 创建 ObjectMapper(确保注册了自定义序列化器)ObjectMapper mapper = new ObjectMapper();String json = mapper.writeValueAsString(user);System.out.println("序列化结果:");System.out.println(json);// 断言脱敏效果assertThat(json).contains("\"idCard\":\"1101****5678\"");assertThat(json).contains("\"name\":\"欧**\"");assertThat(json).contains("\"phoneNumber\":\"138****5678\"");assertThat(json).contains("\"email\":\"o****@shaguyin.com\"");assertThat(json).contains("\"bankCard\":\"62###############23\"");assertThat(json).contains("\"username\":\"ofeng\"");}
}

在这里插入图片描述

三、效果展示

在这里插入图片描述

import com.fc.anno.Desensitize;
import com.fc.enums.DesensitizeType;
import lombok.Builder;
import lombok.Data;import java.time.LocalDateTime;@Data
@Builder
public class UserVO {private Long id;private Long roleId;@Desensitize(type = DesensitizeType.ID_CARD)private String idCard;private String username;private String name;private String phoneNumber;private LocalDateTime createTime;private LocalDateTime lastLogin;
}

在这里插入图片描述

总结

本文实现了一套基于 Jackson 序列化 + 注解 + 策略模式 的轻量级数据脱敏框架,具备以下优点:
✅ 无侵入性:仅需在 VO 字段添加注解
✅ 高性能:使用 EnumMap + 预编译正则
✅ 易扩展:新增类型只需添加枚举和策略
✅ 灵活配置:支持自定义前后缀与掩码符号
✅ 无缝集成:适用于 Spring Boot + Jackson 项目

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/91776.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/91776.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PSO-TCN-BiLSTM-MATT粒子群优化算法优化时间卷积神经网络-双向长短期记忆神经网络融合多头注意力机制多特征分类预测/故障诊断Matlab实现

基本介绍 1.Matlab实现PSO-TCN-BiLSTM-MATT粒子群算法优化时间卷积神经网络-双向长短期记忆神经网络融合多头注意力机制多特征分类预测&#xff0c;PSO-TCN-BiLSTM-Multihead-Attention&#xff1b; 多头自注意力层 (Multihead-Self-Attention)&#xff1a;Multihead-Self-Atte…

第一篇:Linux 运维入门:虚拟机部署与基础环境配置

目录 一、准备工作与环境规划 二、虚拟机网络配置 1、虚拟网络编辑器设置 2、系统网络配置 3、主机名配置 三、Hosts 文件与 SSH 免密配置 配置 hosts 文件编辑/etc/hosts文件实现主机名解析&#xff1a; 分发 hosts 文件到其他节点 SSH 免密登录配置在 zhangsan101 上…

(一)全栈(react配置/https支持/useState多组件传递/表单提交/React Query/axois封装/Router)

文章目录 项目地址 一、基础配置 1.1 支持https 1. 安装所需要的包 2. 配置 1.2 常用 1. 字符串拼接 二、组件 2.1 useState组件传递 1. App里初始化useState 2. useState和方法的传递 3. 接收传递来的状态和方法 2.2 表单提交 1. 表单组件处理用户输入数据 2. App传来的submit…

【abc417】E - A Path in A Dictionary

Problem StatementYou are given a simple connected undirected graph G with N vertices and M edges. The vertices of G are numbered vertex 1, vertex 2, …, vertex N, and the i-th (1≤i≤M) edge connects vertices Ui​ and Vi​.Find the lexicographically smalle…

linux火焰图

火焰图简介火焰图是一种性能分析的可视化工具&#xff0c;它将CPU的调用栈&#xff08;Call Stack&#xff09;信息以矩形火焰的形式展现出来。Y轴&#xff1a;代表调用栈的深度&#xff08;函数A调用了函数B&#xff0c;B就叠在A上面&#xff09;。X轴&#xff1a;代表CPU的抽…

解剖 .NET 经典:从 Component 到 BackgroundWorker

1️⃣ 背景与定位在 .NET Framework 2.0 时代&#xff0c;微软引入了 BackgroundWorker 来解决 WinForm/WPF 场景下“耗时操作阻塞 UI 线程”的问题&#xff1b;而 Component 早在 1.0 就已存在&#xff0c;是所有可视化/非可视化设计器的“基类”。理解这两者的源码与机制&…

桌面端界面设计 |货物 TMS 系统 - SaaS UI UX 设计:审美积累之境

在物流数字化的浪潮中&#xff0c;货物 TMS 系统的 SaaS 化与 UI/UX 设计正构建着独特的审美坐标系。这不仅是技术与功能的融合&#xff0c;更是一场关于效率美学的深度探索&#xff0c;为行业审美积累注入了鲜活的实践样本。SaaS 模式赋予货物 TMS 系统轻盈而强大的特质&#…

多架构镜像整合全攻略:在Docker中实现单一镜像支持同时支持amd64和arm64架构

多架构支持的挑战 &#xff1a;随着异构计算&#xff08;如 ARM、x86、RISC-V 等&#xff09;的普及&#xff0c;开发者需要为不同硬件平台提供对应的镜像&#xff0c;传统方式需维护多个版本&#xff08;如 image:v1-amd64 和 image:v1-arm64 &#xff09;&#xff0c;导致版本…

Linux730 tr:-d /-s;sort:-r,-n,-R,-o,-t,-k,-u;bash;cut:-d,-c;tee -a;uniq -c -i

回顾 sort sort [选项] 文件-u&#xff1a;唯一&#xff0c;去除重复 -r:按数字大小&#xff0c;倒序排序&#xff0c;大到小 -o:输出文件 -n:按数字大小&#xff0c;顺序排序&#xff0c;小到大 -t: -t后加分割符&#xff0c;按分割符为标准&#xff0c;进行筛选 -k:k后加数字…

力扣457:环形数组是否存在循环

力扣457:环形数组是否存在循环题目思路代码题目 存在一个不含 0 的 环形 数组 nums &#xff0c;每个 nums[i] 都表示位于下标 i 的角色应该向前或向后移动的下标个数&#xff1a; 如果 nums[i] 是正数&#xff0c;向前&#xff08;下标递增方向&#xff09;移动 |nums[i]| 步…

在 Elasticsearch 中落地 Learning to Rank(LTR)

1 为什么要引入 LTR&#xff1f; 常规检索&#xff08;BM25、语义检索、Hybrid、RRF …&#xff09;往往只能基于少量信号&#xff08;关键词命中、向量相似度&#xff09;排序。 Learning-to-Rank 通过机器学习模型把多维度特征&#xff08;文档属性、查询属性、查询-文档相关…

Socket编程——TCP协议

文章目录一、TCP传输二、相关接口三、多进程版本四、多线程版本一、TCP传输 TCP和UDP类似&#xff0c;但是在传输中TCP有输入&#xff0c;输出缓冲区&#xff0c;看下面的传输图片 可以理解为TCP之间的数据传输都是依赖各自的socket&#xff0c;socket就充当传输的中介吧。 而…

GitHub使用小记——本地推送、外部拉取和分支重命名

GitHub 项目推送与拉取等操作使用随记 本小记适用于个人项目或组织项目&#xff0c;涵盖 GitHub 推送、拉取、分支管理、.gitignore 设置等常见需求。 1. 将已有本地工程推送至 GitHub 新仓库 1.1 前提条件 本地项目结构完整&#xff0c;已准备好&#xff1b;本地已安装 Git…

RabbitMQ 延时队列插件安装与使用详解(基于 Delayed Message Plugin)

RabbitMQ 延时队列插件安装与使用详解&#xff08;基于 Delayed Message Plugin&#xff09;&#x1f4cc; 一、什么是 RabbitMQ 延时队列&#xff1f;&#x1f680; 二、安装前准备✅ RabbitMQ 环境要求&#x1f527; 三、安装延时队列插件&#x1f9e9; 插件名称&#xff1a;…

Vue项目使用ssh2-sftp-client实现打包自动上传到服务器(完整教程)

告别手动拖拽上传&#xff01;本教程将手把手教你如何通过ssh2-sftp-client实现Vue项目打包后自动上传到服务器&#xff0c;提升部署效率300%。&#x1f680;一、需求场景与解决方案在Vue项目开发中&#xff0c;每次执行npm run build后都需要手动将dist目录上传到服务器&#…

《质光相济:Three.js中3D视觉的底层交互逻辑》

在Three.js搭建的虚拟维度中,光照与材质的关系远非技术参数的简单叠加,当光线以数字形态穿越虚空,与物体表面相遇的瞬间,便开始书写属于这个世界的物理叙事——每一缕光斑的形状、每一块阴影的浓淡、每一寸肌理的反光,都是对现实光学规律的转译与重构。理解这种交互的深层…

无刷电机在汽车领域的应用与驱动编程技术

文章目录引言一、核心应用场景1. 新能源汽车动力系统2. 底盘控制系统3. 车身与舒适系统4. 智能驾驶与安全系统二、无刷电机的技术优势解析三、无刷电机驱动编程基础1. 驱动原理2. 驱动架构四、核心控制算法与实现1. 六步换向法&#xff08;梯形波控制&#xff09;算法流程图C语…

【游戏引擎之路】登神长阶(十八):3天制作Galgame引擎《Galplayer》——无敌之道心

游戏引擎开发记录&#xff1a;2024年 5月20日-6月4日&#xff1a;攻克2D物理引擎。 2024年 6月4日-6月13日&#xff1a;攻克《3D数学基础》。 2024年 6月13日-6月20日&#xff1a;攻克《3D图形教程》。 2024年 6月21日-6月22日&#xff1a;攻克《Raycasting游戏教程》。 2024年…

kotlin kmp 跨平台环境使用sqldelight

欢迎访问我的主页: https://heeheeaii.github.io/ 1. 项目结构 SQLDelightKMPDemo/ ├── shared/ │ ├── src/ │ │ ├── commonMain/kotlin/ │ │ ├── androidMain/kotlin/ │ │ ├── desktopMain/kotlin/ │ │ └── commonMain/sqldel…

机器学习【五】decision_making tree

决策树是一种通过树形结构进行数据分类或回归的直观算法&#xff0c;其核心是通过层级决策路径模拟规则推理。主要算法包括&#xff1a;ID3算法基于信息熵和信息增益选择划分属性&#xff1b;C4.5算法改进ID3&#xff0c;引入增益率和剪枝技术解决多值特征偏差&#xff1b;CART…