spring自定义注解+aop+@BindingParam

1.pom 引入

        
            org.springframework.boot
            spring-boot-starter-aop
        

2.声明自定义注解

2.1 声明切面注解

import java.lang.annotation.*;

/**
 * @author WANGCHENG
 * @version 1.0
 * @Description: 校验组合编辑权限注解
 * @date 2023/08/04 20:12
 */
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckInstructionAuth {
    /**
     * 指令类型  默认:销售
     */
    CheckInstructionEditTypeEnum instructionType()
            default CheckInstructionEditTypeEnum.SALE;

    /**
     * 操作类型,必选,用来错误提示。
     * eg:选择修改,校验提示:没有XX组合的操作权限,不允许修改
     *
     * @return
     */
    CheckInstructionEditTypeEnum.OperateTypeEnum operateType();

    /**
     * 校验数据类型   默认:当前数据
     */
    CheckInstructionEditTypeEnum.CheckDataTypeEnum checkDataType()
            default CheckInstructionEditTypeEnum.CheckDataTypeEnum.CURRENT;

    /**
     * 获取数据方式,默认:@BindingParam 标注
     */
    CheckInstructionEditTypeEnum.DataSourceEnum dataSource()
            default CheckInstructionEditTypeEnum.DataSourceEnum.ANNOTATION;


    /**
     * 是否立即清除副本,默认立即清除副本数据。
     * 如果获取数据方式为 THREAD_LOCAL,后续还需要使用该参数,开发自行清除
     */
    boolean isFlushThreadLocal() default true;

 2.1.1切面对应枚举

@AllArgsConstructor
public enum CheckInstructionEditTypeEnum {
    SALE("SALE","销售", "2"),
    MARKET("MARKET","做市", "1"),
    BID("BID","中标", "2");
    private String instructionCode;
    /**
     * 功能名称
     */
    private String name;
    /**
     * 组合分类标签值
     */
    private String orgType;

    public String getInstructionCode() {
        return instructionCode;
    }

    public String getName() {
        return name;
    }

    public String getOrgType() {
        return orgType;
    }

    /**
     * 参数类型来源
     * DEFAULT_KEY    入参 根据对象的默认key 反射获取,pid,pidList,pids,userId
     * ANNOTATION   入参根据注解获取 和 @BindingParam 配合使用,可以是方法入参注解或者属性注解 ,推荐使用
     * THREAD_LOCAL  副本(需要提前塞值)
     */
    public enum DataSourceEnum {
        DEFAULT_KEY,ANNOTATION,THREAD_LOCAL
    }

    /**
     * DataSourceEnum 为 DEFAULT_KEY 时,默认获取的key
     */
    public enum DataSourceDefaultKeyEnum {
        pid,pidList,pids,userId
    }

    /**
     *   CURRENT 校验当前数据
     *   GROUP   校验整组数据,eg:索引号,code。组合下达指令时,可能 会整组索引指令下达,需校验整组数据
     */
    public enum CheckDataTypeEnum{
        CURRENT,GROUP
    }

    /**
     * 操作类型,用来错误信息提示
     */
    @AllArgsConstructor
    public enum OperateTypeEnum{
        ADD("ADD","新增"),
        UPDATE("UPDATE","修改"),
        DELETE("DELETE","删除"),
        RELEASE("RELEASE","下达"),
        DECILITER("DECILITER","拆合单");
        private String code;
        private String name;

        /**
         * 操作 code
         * @return
         */
        public String getCode() {
            return code;
        }
        /**
         * 操作名称
         * @return
         */
        public String getName() {
            return name;
        }
    }
}
 2.2 声明绑定参数注解
/**
 * @author WANGCHENG
 * @version 1.0
 * @Description: 标注入参的数据类型,配合检验组合权限
 * @date 2023/08/04 20:12
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface BindingParam {
    BindingParamTypeEnum value();
}

绑定参数对应枚举

public enum BindingParamTypeEnum {
    pid,pidList,userId,isConfirm
}

3 切面逻辑


@Aspect
@Component
@Slf4j
public class CheckInstructionAuthAspect {
    // 切点
    @Pointcut(value = "@annotation(com.dsd.study.annotion.CheckInstructionAuth)")
    public void pointcut() {}

    /**
     * 切点配置,CheckCombinedEditAuth 注解的地方
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object CheckCombinedEditAuth(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        String targetClassName = joinPoint.getTarget().getClass().getCanonicalName();
        String targetMethodName = joinPoint.getSignature().getName();
        String target=targetClassName+"#"+targetMethodName;
        log.info("校验指令权限目标方法为={}",target);
        if(!editAuthSwitch()){
            return joinPoint.proceed();
        }
        //获取方法,此处可将signature强转为MethodSignature
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //参数注解,1维是参数,2维是注解
        CheckInstructionAuth checkInstructionAuth = method.getAnnotation(CheckInstructionAuth.class);
        Object[] args = joinPoint.getArgs();
        log.info("校验指令权限目标方法入参={},={}",target,JSON.toJSONString(args));
        CheckInstructionEditTypeEnum checkInstructionEditTypeEnum = checkInstructionAuth.instructionType();
        Map paramData =new HashMap<>();
        if(CheckInstructionEditTypeEnum.SALE.equals(checkInstructionEditTypeEnum)){
            paramData=analysisDataSource(args,checkInstructionAuth,method);
        }
        log.info("{}校验指令权限解析入参为={}",target,JSON.toJSONString(paramData));
        String userId =(String) paramData.get(BindingParamTypeEnum.userId.name());
        List pidList =(List)paramData.get(BindingParamTypeEnum.pidList.name());
        if(StringUtils.isBlank(userId)){
            log.info("CheckCombinedEditAuthAspect#CheckCombinedEditAuth--->未查询到用户信息");
            throw new BusinessException("未查询到用户信息");
        }
        List userAuthList = getUserAuthList(userId, checkInstructionAuth);
        if(pidList==null || pidList.size()==0){
            log.info("CheckCombinedEditAuthAspect#CheckCombinedEditAuth--->未查询到校验的指令");
            throw new BusinessException("未查询到校验的指令");
        }
        List checkDataList = getCheckData(pidList, checkInstructionAuth);
        //权限对比
        if(!compareAuth(userAuthList,checkDataList)){
            log.info("userId={},权限对比,userAuthList={},checkDataList={}",
                    JSON.toJSONString(userAuthList),JSON.toJSONString(checkDataList));
            checkDataList.removeAll(userAuthList);
            if(checkDataList.size()>0){
                //数据权限大于用户拥有权限
                List combinedNameList = getCombinedNameByVcRemarksKey(checkDataList);
                //没有权限的组合
                String combinedNameListStr = combinedNameList.stream()
                        .collect(Collectors.joining(","));
                String operateName = checkInstructionAuth.operateType().getName();
                log.info("userId={},权限不相等,没有"+combinedNameListStr+"组合的操作权限,不允许{}",userId,operateName);
                //没有XX组合的操作权限,不允许新增
                throw new BusinessException("没有"+combinedNameListStr+"组合的操作权限,不允许"+operateName);
            }
        }
        log.info("{}耗时为={}毫秒",target,System.currentTimeMillis()-startTime);
        return joinPoint.proceed();
    }

    /**
     * 根据组合code获取组合名称
     * @param vcRemarksKeyList
     * @return
     */
    private List getCombinedNameByVcRemarksKey(List vcRemarksKeyList){
        //伪代码
        return new ArrayList<>();
    }

    /**
     * 比较List 是否相等
     * @param userAuthList
     * @param checkDataList
     * @return
     */
    private boolean compareAuth(List userAuthList,List checkDataList){
        if(userAuthList.size()!=checkDataList.size()){
            return false;
        }
        String[] userAuthArry = userAuthList.toArray(new String[]{});
        String[] checkDataArry = checkDataList.toArray(new String[]{});
        Arrays.sort(userAuthArry);
        Arrays.sort(checkDataArry);
        return Arrays.equals(userAuthArry, checkDataArry);
    }
    /**
     * 解析入参数据,拿到需要的入参
     * @param args
     * @return
     */
    private Map analysisDataSource(Object[] args, CheckInstructionAuth checkCombinedEditAuth, Method method)
            throws IllegalAccessException, BusinessException {
        Map result=new HashMap<>();
        String operateId=null;
        List pidList=new ArrayList<>();
        CheckInstructionEditTypeEnum.DataSourceEnum dataSource = checkCombinedEditAuth.dataSource();
        try {
            if(CheckInstructionEditTypeEnum.DataSourceEnum.DEFAULT_KEY.equals(dataSource)){
                //反射,获取默认的 key
                for(Object obj:args){
                    if(obj instanceof Map){
                        Map objMap=(Map)obj;
                        getParamByDefaultKey(objMap,result);
                    }else{
                        //自定义对象默认key
                        Field[] fields = obj.getClass().getDeclaredFields();
                        for(Field field:fields){
                            field.setAccessible(true);
                            String name = field.getName();
                            if(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.userId.name().equals(name)){
                                if(obj instanceof String){
                                    operateId=(String)field.get(obj);
                                }
                            }else if(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pid.name().equals(name) ||
                                    CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pidList.name().equals(name) ||
                                    CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pids.name().equals(name)){
                                // key 为 pid,pids,pidList
                                if(obj instanceof String){
                                    pidList.add((String)field.get(obj));
                                }else if(obj instanceof List){
                                    pidList.addAll((List)field.get(obj));
                                }
                            }
                        }
                    }
                }
            }else if(CheckInstructionEditTypeEnum.DataSourceEnum.ANNOTATION.equals(dataSource)){
                //注解,入参加注解
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                int index = 0;
                for(Annotation[] annotationx:parameterAnnotations){
                    Object param=args[index];
                    for(Annotation annotationy:annotationx){
                        if(annotationy instanceof BindingParam){
                            BindingParam bindingParam=(BindingParam)annotationy;
                            getParamByBindingParam(bindingParam, param,result);
                        }
                    }
                    index++;
                }
                //注解,属性加注解,注意:会覆盖入参注解的取值
                for(Integer i=0;i pidListResult = (List)ThreadLocalUtils.get(BindingParamTypeEnum.pidList.name());
                pidList.addAll(pidListResult);
            }
        } catch (BusinessException e) {
            throw e;
        } finally {
            if(CheckInstructionEditTypeEnum.DataSourceEnum.THREAD_LOCAL.equals(dataSource) &&
                    checkCombinedEditAuth.isFlushThreadLocal()){
                ThreadLocalUtils.delete(BindingParamTypeEnum.userId.name());
                ThreadLocalUtils.delete(BindingParamTypeEnum.pidList.name());
            }
        }
        if(StringUtils.isNotBlank(operateId)){
            result.put(BindingParamTypeEnum.userId.name(),operateId);
        }
        if(pidList.size()>0){
            result.put(BindingParamTypeEnum.pidList.name(),pidList);
        }
        return result;
    }

    /**
     * 入参为Map 解析数据
     * @param map
     * @param result
     * @return
     */
    private Map getParamByDefaultKey(Map map,Map result){
        if(map==null){
            return result;
        }
        for(Map.Entry mapx:map.entrySet()){
            String key = mapx.getKey();
            Object value = mapx.getValue();
            if(value==null){
                continue;
            }
            if(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.userId.name().equals(key)){
                if(value instanceof String){
                    result.put(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.userId.name(),(String)value);
                }
            } else if(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pid.name().equals(key)){
                if(value instanceof String){
                    result.put(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pidList.name()
                            ,Arrays.asList((String)value));
                }
            } else if(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pidList.name().equals(key) ||
                    CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pids.name().equals(key)){
                if(value instanceof List){
                    if(value instanceof List){
                        List objList=(List)value;
                        if(objList.size()>0 && objList.get(0) instanceof String){
                            result.put(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pidList.name()
                                    ,(List)value);
                        }
                    }
                }
            }
        }
        return result;
    }

    /**
     * 根据注解获取入参
     * @param bindingParam
     * @param param
     * @return
     */
    private Map getParamByBindingParam(BindingParam bindingParam, Object param, Map result)
            throws BusinessException {
        if(bindingParam==null){
            log.info("没有 @BindingParam 绑定参数");
            return result;
        }
        if(bindingParam!=null){
            BindingParamTypeEnum value = bindingParam.value();
            if(BindingParamTypeEnum.pid.equals(value)){
                if(param instanceof String){
                    result.put(BindingParamTypeEnum.pidList.name()
                            , Arrays.asList((String)param).stream().collect(Collectors.toList()));
                }else{
                    log.info("@BindingParam 注解类型使用错误,pid只能绑定String类型");
                    throw new BusinessException("@BindingParam 注解类型使用错误,pid只能绑定String类型");
                }

            }else if(BindingParamTypeEnum.pidList.equals(value)){
                if(param instanceof List){
                    List objList=(List)param;
                    if(objList!=null && objList.size()>0){
                        if(objList.get(0) instanceof String){
                            result.put(BindingParamTypeEnum.pidList.name(),(List)param);
                        }else {
                            log.info("@BindingParam 注解类型值使用错误,pidList只能绑定List类型");
                            throw new BusinessException("@BindingParam 注解类型值使用错误,pidList只能绑定List类型");
                        }
                    }
                }else{
                    log.info("@BindingParam 注解类型值使用错误,pidList只能绑定List类型");
                    throw new BusinessException("@BindingParam 注解类型值使用错误,pidList只能绑定List类型");
                }
            }else if(BindingParamTypeEnum.userId.equals(value)){
                if(param instanceof String){
                    result.put(BindingParamTypeEnum.userId.name(),(String)param);
                }else{
                    log.info("@BindingParam 注解类型值使用错误,userId只能绑定String类型");
                    throw new BusinessException("@BindingParam 注解类型值使用错误,userId只能绑定String类型");
                }
            }
        }
        return result;
    }

    /**
     * 用户存在的编辑组合权限
     * @param userId
     * @param checkCombinedEditAuth
     * @return
     */
    private List getUserAuthList(String userId,CheckInstructionAuth checkCombinedEditAuth){
        //获取用户权限,业务代码
        return new ArrayList<>();
    }

    /**
     * 需要校验的组合数据
     * @param pidList
     * @param checkCombinedEditAuth
     * @return
     */
    private List getCheckData(List pidList, CheckInstructionAuth checkCombinedEditAuth){
        CheckInstructionEditTypeEnum checkInstructionEditTypeEnum = checkCombinedEditAuth.instructionType();
        List checkDataList=new ArrayList<>();
        if(CheckInstructionEditTypeEnum.SALE.equals(checkInstructionEditTypeEnum)){
            //获取需要校验的数据
        }
        //校验其它
        return checkDataList;
    }

    /**
     * 校验权限开关,redis 控制。默认开启=1;
     * @return
     */
    private boolean editAuthSwitch(){
        return true;
    }
} 
  

 4涉及的Util 

4.1 ThreadLocalUtil 
public class ThreadLocalUtils {
    private static final ThreadLocal> THREAD_LOCAL =
            ThreadLocal.withInitial(() -> new ConcurrentHashMap<>(16));

    /**
     * 获取到ThreadLocal中值
     *
     * @return ThreadLocal存储的是Map
     */
    public static Map getThreadLocal() {
        return THREAD_LOCAL.get();
    }

    /**
     * 从ThreadLocal中的Map获取值
     *
     * @param key Map中的key
     * @param  Map中的value的类型
     * @return Map中的value值 可能为空
     */
    public static  T get(String key) {
        return get(key, null);
    }

    /**
     * 从ThreadLocal中的Map获取值
     *
     * @param key          Map中的key
     * @param defaultValue Map中的value的为null 是 的默认值
     * @param           Map中的value的类型
     * @return Map中的value值 可能为空
     */
    @SuppressWarnings("unchecked")
    public static  T get(String key, T defaultValue) {
        Map map = THREAD_LOCAL.get();
        if (MapUtils.isEmpty(map)) {
            return null;
        }
        return (T) Optional.ofNullable(map.get(key)).orElse(defaultValue);
    }

    /**
     * ThreadLocal中的Map设置值
     *
     * @param key   Map中的key
     * @param value Map中的value
     */
    public static void set(String key, Object value) {
        Map map = THREAD_LOCAL.get();
        map.put(key, value);
    }

    /**
     * ThreadLocal中的Map 添加Map
     *
     * @param keyValueMap 参数map
     */
    public static void set(Map keyValueMap) {
        Map map = THREAD_LOCAL.get();
        map.putAll(keyValueMap);
    }

    /**
     * 删除ThreadLocal中的Map 中的value
     *
     * @param key Map中的key
     */
    public static void delete(String key) {
        Map map = THREAD_LOCAL.get();
        if (MapUtils.isEmpty(map)) {
            return;
        }
        map.remove(key);
    }

    /**
     * 删除ThreadLocal中的Map
     */
    public static void remove() {
        THREAD_LOCAL.remove();
    }

    /**
     * 从ThreadLocal中的Map获取值 根据可key的前缀
     *
     * @param prefix key 的前缀
     * @param     Map中的value的类型
     * @return 符合条件的Map
     */
    @SuppressWarnings("unchecked")
    public static  Map fetchVarsByPrefix(String prefix) {
        Map vars = new HashMap<>(16);
        if (StringUtils.isBlank(prefix)) {
            return vars;
        }
        Map map = THREAD_LOCAL.get();
        if (MapUtils.isEmpty(map)) {
            return vars;
        }
        return map.entrySet().stream().filter(test -> test.getKey().startsWith(prefix))
                .collect(Collectors.toMap(Map.Entry::getKey, time -> (T) time.getValue()));
    }

    /**
     * 删除ThreadLocal中的Map 中的Value  按 Map中的Key的前缀
     *
     * @param prefix Map中的Key的前缀
     */
    public static void deleteVarsByPrefix(String prefix) {
        if (StringUtils.isBlank(prefix)) {
            return;
        }
        Map map = THREAD_LOCAL.get();
        if (MapUtils.isEmpty(map)) {
            return;
        }
        map.keySet().stream().filter(o -> o.startsWith(prefix)).collect(Collectors.toSet()).forEach(map::remove);
    }
}
4.2  自定义异常
public class BusinessException extends Exception{
    private static final long serialVersionUID = -3463054564635276929L;
    /**
     * 错误码
     */
    private String errCode;

    /**
     * 错误描述
     */
    private String errDesc;

    public BusinessException() {
        super();
    }

    public BusinessException(String errDesc) {
        super(errDesc);
        this.errDesc = errDesc;
    }

    public BusinessException(String errCode, String errDesc) {
        super(errCode);
        this.errCode = errCode;
        this.errDesc = errDesc;
    }

    public String getErrCode() {
        return errCode;
    }

    public String getErrDesc() {
        return errDesc;
    }
}

5 使用示例

@Service
public class TestServiceImpl implements TestService {
    @CheckInstructionAuth(operateType = CheckInstructionEditTypeEnum.OperateTypeEnum.RELEASE)
    @Override
    public void getData(Map map,@BindingParam(BindingParamTypeEnum.pidList) String userId) {
        System.out.println("testAnnotation");
    }


    @CheckInstructionAuth(operateType = CheckInstructionEditTypeEnum.OperateTypeEnum.RELEASE)
    @Override
    public void getData(List userDto) {
        System.out.println("testAnnotation");
    }
}

你可能感兴趣的:(spring,java)