要在使用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 {@Overridepublic 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 {@Beanpublic Interceptor desensitizationInterceptor() {return new DesensitizationInterceptor();}//....其他配置省略
}
- 使用方法
- 对于需要脱敏的查询,正常使用Mapper方法即可。
- 对于不需要脱敏的查询,在Mapper接口方法上添加@NoDesensitize注解:
public interface UserMapper extends BaseMapper<User> {@NoDesensitizeUser 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();
}
这种方案既能满足大部分查询的脱敏需求,又能在特定场景下灵活控制是否进行脱敏,同时保留了获取精确手机号码的能力。