基于 注解 + AOP 的脱敏方案
一:定义脱敏类型枚举
定义需要脱敏的数据类型,如姓名、身份证号、手机号等。
public enum SensitiveType {
NAME, // 姓名
ID_CARD, // 身份证
MOBILE, // 手机号
AMOUNT // 金额
}
二 :定义脱敏注解
可通过 conditionField + 固定值,或者 condition 表达式两种方式指定条件。
条件字段必须存在于同一实体类中,且可访问。
package org.beizhilib.common.aspect;
import org.beizhilib.common.enums.SensitiveType;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Desensitize {
/**
* 脱敏类型
*/
SensitiveType type();
/**
* 条件字段名(用于判断是否满足脱敏条件,如 status)
*/
String conditionField() default "";
/**
* 条件期望值(如 "完结")
* 支持 SpEL 表达式,例如 "#status == '完结'"
*/
String condition() default "";
}
三:脱敏工具类
package org.beizhilib.common.utils;
import cn.hutool.core.util.DesensitizedUtil;
import org.beizhilib.common.enums.SensitiveType;
import java.math.BigDecimal;
/**
* 脱敏工具类
*
* @author Bryson
* @date 2026/3/19
*/
public class DesensitizeUtils {
public static String desensitize(String value, SensitiveType type) {
if (value == null) return null;
switch (type) {
case NAME:
return DesensitizedUtil.desensitized(value,DesensitizedUtil.DesensitizedType.CHINESE_NAME);
case ID_CARD:
return DesensitizedUtil.desensitized(value,DesensitizedUtil.DesensitizedType.ID_CARD);
case MOBILE:
return DesensitizedUtil.desensitized(value,DesensitizedUtil.DesensitizedType.MOBILE_PHONE);
case AMOUNT:
return desensitize(value);
default:
return value;
}
}
public static String desensitize(String value) {
if (value == null) return null;
// 金额脱敏规则(可自定义)
return "***";
}
}
四:AOP 切面实现
package org.beizhilib.common.aspect;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.beizhilib.common.enums.SensitiveType;
import org.beizhilib.common.utils.DesensitizeUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Collection;
/**
* TODO 类作用描述
*
* @author Bryson
* @date 2026/3/19
*/
@Aspect
@Component
@Slf4j
public class DesensitizeAspect {
/**
* 拦截 Service 方法(可自定义切点)
*/
@Around("execution(* org.beizhilib.biz.service.impl.SysUserServiceImpl.selectPage(..))")
public Object doDesensitize(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed(); // 先执行原方法获取返回值
if (result == null) return null;
// 对返回值进行脱敏处理
processObject(result);
return result;
}
/**
* 递归处理对象(支持集合、数组、分页对象等)
*/
private void processObject(Object obj) {
if (obj == null) return;
// 处理集合
if (obj instanceof Collection) {
((Collection<?>) obj).forEach(this::processObject);
return;
}
// 处理数组
if (obj.getClass().isArray()) {
for (Object item : (Object[]) obj) {
processObject(item);
}
return;
}
// 处理分页对象(MyBatis-Plus Page)
if (obj instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
((com.baomidou.mybatisplus.extension.plugins.pagination.Page<?>) obj).getRecords()
.forEach(this::processObject);
return;
}
// 处理分页对象(PageHelper PageInfo<?>)
if (obj instanceof PageInfo<?>) {
((PageInfo<?>) obj).getList()
.forEach(this::processObject);
return;
}
// 处理普通 Java 对象
Class<?> clazz = obj.getClass();
for (Field field : clazz.getDeclaredFields()) {
Desensitize annotation = field.getAnnotation(Desensitize.class);
if (annotation == null) continue;
field.setAccessible(true);
try {
Object fieldValue = field.get(obj);
if (fieldValue == null) continue;
// 判断是否满足脱敏条件
boolean shouldDesensitize = evaluateCondition(obj, field, annotation);
if (!shouldDesensitize) continue;
// 执行脱敏
Object desensitizedValue = doDesensitizeValue(fieldValue, annotation.type());
field.set(obj, desensitizedValue);
} catch (IllegalAccessException e) {
// log error
}
}
}
/**
* 解析条件表达式
*/
private boolean evaluateCondition(Object target, Field field, Desensitize annotation) {
String condition = annotation.condition();
if (condition.isEmpty()) {
// 无条件,默认脱敏
return true;
}
try {
EvaluationContext context = new StandardEvaluationContext(target);
ExpressionParser parser = new SpelExpressionParser();
Boolean result = parser.parseExpression(condition).getValue(context, Boolean.class);
return result != null && result;
} catch (Exception e) {
log.error("SpEL 评估失败: condition={}", condition, e);
// log error,默认不脱敏
return false;
}
}
/**
* 根据类型脱敏
*/
private Object doDesensitizeValue(Object value, SensitiveType type) {
if (value instanceof String) {
return DesensitizeUtils.desensitize((String) value, type);
}
return value;
}
}
五:配置启用 AOP
在 Spring Boot 启动类上添加 @EnableAspectJAutoProxy 注解
@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
六:实体类中使用注解
/**
* 用户昵称
*/
@Desensitize(type = SensitiveType.NAME, condition = "remark == '完结'")
private String nickName;
/**
* 手机号码
*/
@Desensitize(type = SensitiveType.MOBILE, condition = "remark == '完结'")
private String phonenumber;
/**
* 备注
*/
private String remark;
七:使用示例
@Override
public PageInfo<SysUser> selectPage(UserDto user) {
PageHelper.startPage(user.getPageIndex(), user.getPageSize());
List<SysUser> list = sysUserMapper.selectAll(user);
if (list == null) {
return null;
}
return new PageInfo<>(list);
}
当 Controller 调用 SysUserService.selectPage 返回的用户列表时,AOP 会自动对其中满足条件( remark == '完结')的字段进行脱敏,业务代码完全无需关心脱敏逻辑。

浙公网安备 33010602011771号