要在使用Mybatis-plus进行数据库查询时准确区分哪些查询需要脱敏,哪些不需要,同时保留获取精确手机号码的能力,可以采用以下方案:
- 自定义注解标记
创建一个自定义注解,用于标记不需要脱敏的查询方法:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NoDesensitize {
}
- 自定义拦截器
编写一个自定义的Mybatis拦截器,在其中实现脱敏逻辑:
@Slf4j
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class DesensitizationInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
Method method = getInvokedMethod(invocation);
if (method.isAnnotationPresent(NoDesensitize.class)) {
// 检查方法是否有@NoDesensitize注解, 有注解的直接返回,不做脱敏处理
return result;
}
if (result instanceof List && DesensitizationContext.isDesensitizationEnabled()) {
for (Object obj : (List) result) {
desensitize(obj);
}
} else if (DesensitizationContext.isDesensitizationEnabled()) {
desensitize(result);
}
return result;
}
private Method getInvokedMethod(Invocation invocation) {
try {
// 获取目标对象
Object target = invocation.getTarget();
// 获取方法名
String methodName = invocation.getMethod().getName();
// 获取方法参数
Object[] args = invocation.getArgs();
// 获取目标对象的所有方法
Method[] methods = target.getClass().getMethods();
// 遍历查找匹配的方法
for (Method method : methods) {
if (method.getName().equals(methodName) && method.getParameterTypes().length == args.length) {
return method;
}
}
// 如果没有找到匹配的方法,抛出异常
throw new NoSuchMethodException("No matching method found for " + methodName);
} catch (Exception e) {
log.error("获取被调用方法失败", e);
return null;
}
}
private void desensitize(Object obj) {
if (obj == null) {
return;
}
// 使用反射获取字段并进行脱敏处理
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Sensitive.class)) {
Sensitive sensitive = field.getAnnotation(Sensitive.class);
field.setAccessible(true);
try {
String value = (String) field.get(obj);
if (value != null) {
field.set(obj, desensitizeValue(value, sensitive.type()));
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
private String desensitizeValue(String value, SensitiveType type) {
switch (type) {
case MOBILE:
return DesensitizedUtil.mobilePhone(value);
case EMAIL:
return DesensitizedUtil.email(value);
case ID_CARD:
return DesensitizedUtil.idCardNum(value, 1, 2);
default:
return value;
}
}
}
- 配置拦截器
在Mybatis-plus配置中添加自定义拦截器:
@Configuration
public class MybatisPlusConfig {
@Bean
public Interceptor desensitizationInterceptor() {
return new DesensitizationInterceptor();
}
//....其他配置省略
}
- 使用方法
- 对于需要脱敏的查询,正常使用Mapper方法即可。
- 对于不需要脱敏的查询,在Mapper接口方法上添加@NoDesensitize注解:
public interface UserMapper extends BaseMapper<User> {
@NoDesensitize
User selectUserWithFullMobile(Long id);
}
- 动态控制脱敏
如果需要更灵活地控制脱敏,可以考虑使用ThreadLocal来存储当前线程的脱敏状态:
public class DesensitizationContext {
private static ThreadLocal<Boolean> desensitizeFlag = ThreadLocal.withInitial(() -> true);
public static void enableDesensitization() {
desensitizeFlag.set(true);
}
public static void disableDesensitization() {
desensitizeFlag.set(false);
}
public static boolean isDesensitizationEnabled() {
return desensitizeFlag.get();
}
public static void clear() {
desensitizeFlag.remove();
}
}
然后在拦截器中根据这个状态决定是否执行脱敏:
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
//省略其他代码.....
if (DesensitizationContext.isDesensitizationEnabled()) {
return desensitizeResult(result);
}
//省略其他代码.....
return result;
}
在需要获取精确手机号码的业务逻辑中,可以这样使用:
try {
DesensitizationContext.disableDesensitization();
User user = userMapper.selectById(id);
// 使用完整的手机号码进行操作
} finally {
DesensitizationContext.enableDesensitization();
}
这种方案既能满足大部分查询的脱敏需求,又能在特定场景下灵活控制是否进行脱敏,同时保留了获取精确手机号码的能力。