项目中必要的,全局脱敏注解,两种使用方式!

1,注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TM {

    /**
     * 脱敏指定属性<br>
     * 在TMStrategy.SELECT使用<br>
     * 代表: @TMX中只需脱敏这些属性
     */
    String[] sxs() default {};

    /**
     * 脱敏指定忽略属性<br>
     * 在TMStrategy.SELECT使用<br>
     * 代表: @TMX中不需脱敏这些属性
     */
    String[] hlSxs() default {};

    /**
     * 脱敏深度<br>
     * 在TMStrategy.DEFAULT使用<br>
     * 代表: 默认深入@TMX成员类型一次 包括集合
     */
    int depth() default 2;

    /**
     * 脱敏分组<br>
     * 在TMStrategy.DEFAULT使用<br>
     * 代表: 只对TMX中含此标识的进行脱敏
     */
    String fenZu() default "";

    /** 脱敏策略: 对不同字段类型【type】的统一脱敏方法 */
    TMStrategy value() default TMStrategy.DEFAULT;
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TMX {

    /** 脱敏字段类型 */
    TMType value() default TMType.DEFAULT;

    /** 脱敏替代字符 */
    char replace() default '*';

    /** 脱敏分组 以逗号分割 fenZus = {”getInfo“,"list"} */
    String[] fenZus() default {};

}

2,环绕切面

@Aspect
@Component
public class TMAspect {
  
    @Autowired
    private TMAdapter handler;

    //匹配所有OpenTuoMin,环绕通知可以修改返回值
    @Around("@annotation(com.yaodong.common.security.annotation.TM)")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        TM openTuoMin = method.getDeclaredAnnotation(TM.class);
        // ...执行前

        // ...执行后
        Object result = joinPoint.proceed();
        if(result == null) return null;

        if (result instanceof TableDataInfo) {

            TableDataInfo info = (TableDataInfo) result;
            handler.handle(info.getRows(),openTuoMin);

        } else if(result instanceof AjaxResult){

            AjaxResult info = (AjaxResult) result;
            handler.handle(info.get("data"),openTuoMin);

        } else if(result instanceof R){

            R info = (R) result;
            handler.handle(info.getData(),openTuoMin);

        } else {
            handler.handle(result, openTuoMin);
        }

        return result;
    }

}

3,策略适配

 

/** 策略适配 */
@Component
public class TMAdapter {

    @Autowired
    @Qualifier("defaultStrategy")
    TMStrategy defaultStrategy;

    @Autowired
    @Qualifier("selectStrategy")
    TMStrategy selectStrategy;

    /**
     * @param org 脱敏(解密)实体
     * @param openTuoMin 开启脱敏注解
     */
    public <T> void handle(T org, TM openTuoMin) {
        if(org == null) return;
        com.yaodong.common.security.tuomin.constant.TMStrategy strategy = openTuoMin.value();
        if(strategy.equals(com.yaodong.common.security.tuomin.constant.TMStrategy.DEFAULT)){
            // 执行
            defaultStrategy.executeAfter(org, openTuoMin);

        }else if(strategy.equals(com.yaodong.common.security.tuomin.constant.TMStrategy.SELECT)){
            // 执行
            selectStrategy.executeAfter(org, openTuoMin);
        }

    }
}

4,策略接口

public interface TMStrategy {

    // 无效的字符串 字段和脱敏类型格式冲突 TODO 可在【对应类型】处理办法补充
    String valid = "";

    /**
     * 不同策略的遍历行为不同 默认深度优先遍历
     * @param target 需脱敏对象
     * @param openTuoMin 方法上的脱敏注解
     */
    <T> void executeAfter(T target, TM openTuoMin);

    /**
     * @param target 需解密对象
     * @param openTuoMin 方法上的脱敏注解
     */
    <T> void executeBefore(T target, TM openTuoMin);



    /**=============================== 脱敏方法 ============================*/

    static String desensitizeName(String name, char replace) {
        int length = name.length();
        if (length == 2) {
            return name.substring(0, 1) + replace;
        } else if (length == 3) {
            return name.substring(0, 1) + replace + name.substring(2);
        }
        return valid; // 其他长度返回无效结果
    }

    static String desensitizeEmail(String email, char replace) {
        int index = email.indexOf('@');
        if (index == -1 || index < 8) {
            return email; // 如果@符号不存在或位置不对,返回原字符串
        }
        // 例如 155****922@qq.com
        return email.substring(0, 3) + new String(new char[index - 7]).replace('\0', replace) + email.substring(index - 4);
    }

    static String desensitizePhoneNumber(String phoneNumber, char replace) {
        //电话位数
        if (phoneNumber.length() == 11) {
            // 中国大陆
            return phoneNumber.substring(0,3) +new String(new char[4]).replace('\0', replace) + phoneNumber.substring(7,11);
        }else if(phoneNumber.length() == 7){
            // 座机
            return phoneNumber.substring(0,3) +new String(new char[2]).replace('\0', replace) + phoneNumber.substring(5,7);
        }else if(phoneNumber.length() == 10){
            // 台湾
            return phoneNumber.substring(0,3) +new String(new char[3]).replace('\0', replace) + phoneNumber.substring(6,10);
        }
        return valid; // 长度不对返回无效结果
    }

    static String desensitizeCreditCode(String of, char replace) {
        // 营业执照 18位 后六位
        if(of.length() != 16)
        {
            return valid;
        }
        return of.replace(of.substring(12,18),new String(new char[6]).replace('\0', replace));
    }

    static String desensitizeAddress(String address, char replace) {
        // 汉字数字的正则表达式(这里仅列举了部分,可以根据需要添加更多)
        String chineseNumbers = "一|二|三|四|五|六|七|八|九|十";
        Pattern pattern = Pattern.compile(chineseNumbers);
        Matcher matcher = pattern.matcher(address);
        address = matcher.replaceAll("*");
        return address.replaceAll("\\d", String.valueOf(replace)); //只做数字替换
    }

    // 例如,IDENTITY_CARD可能如下:
    static String desensitizeIdentityCard(String idCard, char replace) {
        if (idCard.length() != 18) {
            return valid; // 长度不对返回无效结果
        }
        return  idCard.substring(0, 6) + new String(new char[8]).replace('\0', replace) +idCard.substring(0, 4); // 显示前14位,后四位用*替换
    }

    // 检查对象类型
    static TMFieldType checkClassType(Object instance){
        if(instance.getClass().isPrimitive()){
            return TMFieldType.DEFAULT;
        }else if(instance instanceof Integer || instance instanceof Long || instance instanceof Float || instance instanceof Double ||
                instance instanceof Character || instance instanceof Byte || instance instanceof Short || instance instanceof String){
            return TMFieldType.DEFAULT;
        }else if(instance instanceof Date || instance instanceof BigDecimal || instance instanceof Calendar){
            return TMFieldType.DEFAULT;
        }
        else if(instance instanceof Collection){
            return TMFieldType.COLLECTION;
        }else {
            return TMFieldType.ENTITY;
        }
    }

    /** 获取类及其所有父类声明的字段 */
    static Field[] getAllFields(Class<?> clazz) {
        List<Field> fieldsList = new ArrayList<>();
        Class<?> currentClass = clazz;
        while (currentClass != null) {
            // 获取当前类声明的所有字段
            Field[] declaredFields = currentClass.getDeclaredFields();
            // 将这些字段添加到列表中
            for (Field field : declaredFields) {
                fieldsList.add(field);
            }
            // 移至父类
            currentClass = currentClass.getSuperclass();
        }
        // 将列表转换为数组
        return fieldsList.toArray(new Field[0]);
    }

    /**
     * 处理方法 <p>可以扩充类型,加上处理办法<p/>
     * @param originalValue 需要脱敏属性
     * @param type 类型
     * @param replace 替代字符
     * @return 脱敏后属性
     */
    static String start(Object originalValue, TMType type, char replace) {
        if (originalValue == null || "null".equals(String.valueOf(originalValue))) {
            return valid; // 无效结果
        }
        String of = String.valueOf(originalValue);
        switch (type) {
            case NAME:
                return TMStrategy.desensitizeName(of, replace);
            case EMAIL:
                return TMStrategy.desensitizeEmail(of, replace);
            case ADDRESS:
                return TMStrategy.desensitizeAddress(of, replace);
            case CREDIT_CODE:
                return TMStrategy.desensitizeCreditCode(of,replace);
            case PHONE_NUMBER:
                return TMStrategy.desensitizePhoneNumber(of, replace);
            case IDENTITY_CARD:
                return TMStrategy.desensitizeIdentityCard(of,replace);
            default:
                return valid; // 无效结果或未知类型
        }
    }

    /**
     * 处理方法 <p>可以扩充类型Constant.Type,加上处理办法<p/>
     * @param originalValue 需要脱敏属性
     * @param type 类型
     * @return 脱敏后属性
     */
    static String start(Object originalValue, TMType type) {
        return start(originalValue, type,'*');
    }
}

5,两个策略

@Component("selectStrategy")
public class TMSelectStrategy implements TMStrategy {

    private static final Logger logger = LoggerFactory.getLogger(TMDefaultStrategy.class);

    @Override
    public <T> void executeAfter(T target, TM openTuoMin) {
        traverse(target, null, null, openTuoMin);
    }

    @Override
    public <T> void executeBefore(T target, TM openTuoMin) { }

    /**
     * @param target 当前检查对象 为null时,代表本身在下一次循环中不会被修改
     * @param use 当前检查对象属性 为null时,代表target在下一次循环中不会被修改
     * @param source 来源对象 当检查对象时脱敏属性用于修改
     * @param openTuoMin 方法注解 存储了过滤信息
     */
    public static void traverse(Object target, Object source, Field use, TM openTuoMin) {
        if (target == null) {
            return;
        }

        TMFieldType fieldType = TMStrategy.checkClassType(target);

        switch (fieldType){
            case COLLECTION:{// 检查是否为集合类型
                for (Object element : (Collection<?>) target) {
                    traverse(element, null, null, openTuoMin);
                }
                break;
            }
            case ENTITY:{// 检查是否为其他类的实例,并递归遍历
                // 获取所有属性
                Field[] fields = TMStrategy.getAllFields(target.getClass());

                for (Field field : fields) {
                    try {
                        field.setAccessible(true);

                        Object value = field.get(target);

                        traverse(value, target, field, openTuoMin);

                    } catch (IllegalAccessException e) {
                        logger.error("【脱敏】属性遍历异常...",e);
                    }finally {
                        field.setAccessible(false);
                    }
                }
                break;
            }
            case DEFAULT:// 真实属性值
            default:
                // 对象 属性 属性对象 方法上注解
                if(use == null)return;
                handle(source, use, target, openTuoMin.sxs(), openTuoMin.hlSxs());
        }

    }

    /**
     * 原始基础类递归,扫描内部属性,进行数据托敏
     * @param target 上层脱敏对象
     * @param sxs 指定脱敏的属性名
     * @param field 内部需要脱敏的字段
     */
    protected static void handle(Object target, Field field, Object value, String[] sxs, String[] hlSxs) {
        try {
            if(target == null) return;
            if(field.getType() != String.class) return;

            // 检查字段上是否有TM注解
            TMX tmAnnotation = field.getDeclaredAnnotation(TMX.class);
            if (tmAnnotation == null) return;

            // 是否在TM的指定的属性中
            if(!(sxs.length != 0 && Arrays.asList(sxs).stream().anyMatch(sx -> sx.equals(field.getName())))) return;

            // 是否不在TM的指定的忽略属性中
            if(hlSxs.length != 0 && Arrays.asList(hlSxs).stream().anyMatch(hlsx -> hlsx.equals(field.getName()))) return;

            TMType type = tmAnnotation.value();
            char replace = tmAnnotation.replace();

            // 脱敏
            Object res = TMStrategy.start(value, type, replace);

            field.set(target, res);

        } catch (Exception e) {
            logger.error("【脱敏】属性遍历异常...",e);
        } finally {
            field.setAccessible(false);
        }
    }

}
@Component("defaultStrategy")
public class TMDefaultStrategy implements TMStrategy {

    private static final Logger logger = LoggerFactory.getLogger(TMDefaultStrategy.class);

    @Override
    public <T> void executeAfter(T target, TM openTm) {
        // 校验传参
        if(openTm.depth() < 0){
            throw new RuntimeException("开启脱敏 参数非法!");
        }
        if(target == null) return;
        try {
            // 初始只为 实体类/集合类
            selector(null,null, target, openTm.fenZu(), openTm.depth());
        } catch (InterruptedException e) {
            logger.info("【脱敏】大数据量处理异常...");
        }
    }

    @Override
    public <T>  void executeBefore(T target, TM openTm){}

    /**
     * 判断inner类型,选择不同的递归方式
     * @param entity 上层脱敏对象
     * @param field entity内部需要脱敏的字段
     * @param inner entity内部需要脱敏的对象
     * @param groupId 开启脱敏的分组id
     * @param level 脱敏的递归层级
     * @param <T> 保存数据的原始类型
     * @throws InterruptedException 由大数据量list处理式的线程池抛出
     */
    private void selector(Object entity, Field field, Object inner, String groupId, int level) throws InterruptedException {
        TMFieldType fieldType = TMStrategy.checkClassType(inner);// 处理集合字段
        if (fieldType == TMFieldType.COLLECTION) {
            // 处理集合
            handleCollectionClass((Collection<?>) inner, groupId, level);
        } else if (fieldType == TMFieldType.ENTITY) {
            // 处理实体字段
            handlerSelfClass(inner, groupId, level);
        } else {
            // 处理包装类和基本类
            handlerBaseClass(entity, field, groupId);
        }
    }

    /**
     * 集合递归方式,获取内部实体,交替回handleField
     * @param collection 经instanceof判断集合
     * @param groupId 开启脱敏的分组id
     * @param level 脱敏的递归剩余层级
     * @throws InterruptedException 处理式的线程池抛出
     */
    protected void handleCollectionClass(Collection<?> collection, String groupId, int level) throws InterruptedException {
        ExecutorService service = null;
        try {
            // 数据比较多时,使用线程池跑
            if (collection.size() > 1000) {
                service = Executors.newFixedThreadPool(5);
                ExecutorService finalService = service;
                collection.parallelStream().forEach(entity -> {
                    try {
                        finalService.submit(() -> handlerSelfClass(entity, groupId, level));
                    } catch (Exception e) {
                        logger.info("【脱敏】大数据量处理失败...");
                    }
                });
                service.shutdown();
                if (!service.awaitTermination(1, TimeUnit.MINUTES)) {
                    service.shutdownNow();
                }
            } else {
                collection.forEach(entity -> handlerSelfClass(entity, groupId, level));
            }
        } finally {
            if (service != null && !service.isTerminated()) {
                service.shutdownNow();
            }
        }
    }

    /**
     * 实体类递归,扫描内部属性,交回交替回handleField
     * @param entity 上层脱敏对象
     * @param groupId 开启脱敏的分组id
     * @param level  脱敏的递归剩余层级
     */
    protected void handlerSelfClass(Object entity, String groupId, int level){
        if(level < 1) return;
        // 获取当前对象的所有字段,包括继承
        Field[] fields = TMStrategy.getAllFields(entity.getClass());
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                // 此时inner已经失去原本类型
                Object inner = field.get(entity);
                if (inner != null) {
                    selector(entity, field, inner, groupId, level - 1);
                }
            } catch (IllegalAccessException | InterruptedException e) {
                logger.info("【脱敏】属性遍历异常...");
            } finally {
                field.setAccessible(false);
            }
        }
    }

    /**
     * 原始基础类递归,扫描内部属性,进行数据托敏
     * @param target 上层脱敏对象
     * @param groupId 开启脱敏的分组id
     * @param field 内部需要脱敏的字段
     */
    protected void handlerBaseClass(Object target, Field field, String groupId) {
        try {
            // 若需处理非字节类型,需要对返回值类型生成代理对象
            if(target == null) return;
            if(field.getType() != String.class) return;

            // 检查字段上是否有TM注解
            TMX tmAnnotation = field.getDeclaredAnnotation(TMX.class);
            if (tmAnnotation == null) return;

            // 脱敏条件 不配置放行/包含放行
            String[] groupIds = tmAnnotation.fenZus();
            boolean shouldDesensitize =
                    groupIds.length == 0 ||
                    groupId.isEmpty() ||
                    Arrays.stream(groupIds).anyMatch(id -> id.equals(groupId));
            if (!shouldDesensitize) return;

            TMType type = tmAnnotation.value();
            char replace = tmAnnotation.replace();
            Object o = field.get(target);
            //脱敏
            Object res = TMStrategy.start(o, type, replace);

            field.set(target, res);

        } catch (Exception e) {
            logger.info("【脱敏】属性遍历异常...");
        } finally {
            field.setAccessible(false);
        }
    }


}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值