SpringBoot專案中新增脫敏功能
專案背景
目前正在開發一個SpringBoot項目,此專案有Web端和微信小程式端。 web端提供給工作人員使用,微信小程式提供給群眾預約作業。專案中有部分敏感資料需要脫敏傳遞給微信小程序,給與群眾查看。
專案需求描述
專案中,由於使用端有兩個,因此兩個端的資料權限並不一樣。 Web端可以查看所有數據,小程式端只能查看脫敏後的數據。
需要開發一個通用脫敏功能
手動進行脫敏操作
支援多種對象,
支援不同字段,並脫敏指定字段
字段的脫敏方式多樣
1.解決方案
使用
,來支援對指定字段,不同字段,多種脫敏操作,並可以脫離物件。
使用工具對象,透過泛型傳參,來支援對不同對象的脫敏操作。 2. 實作程式碼2.1 註解Sensitiveimport 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 去敏型別列舉SensitiveTypepublic enum SensitiveType {
/**
* 自定义
*/
CUSTOMER,
/**
* 名称
**/
CHINESE_NAME,
/**
* 身份证证件号
**/
ID_CARD_NUM,
/**
* 手机号
**/
MOBILE_PHONE,
/**
* 固定电话
*/
FIXED_PHONE,
/**
* 密码
**/
PASSWORD,
/**
* 银行卡号
*/
BANKCARD,
/**
* 邮箱
*/
EMAIL,
/**
* 地址
*/
ADDRESS,
}
2.3 去敏感工具DesensitizedUtilsimport 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 "";
}
}
#2.3 去敏感工具DesensitizedUtilspublic 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 使用實例######3.1 需要註解物件###// 脱敏对象 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);###3.2 去敏感操作###rrreee
以上是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
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

Dreamweaver CS6
視覺化網頁開發工具

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

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