spring自定义注解+aop+@BindingParam

news/2025/2/16 0:56:23/

1.pom 引入

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

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}/*** 操作类型,用来错误信息提示*/@AllArgsConstructorpublic 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强转为MethodSignatureMethodSignature 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<String, Object> 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<String> pidList =(List<String>)paramData.get(BindingParamTypeEnum.pidList.name());if(StringUtils.isBlank(userId)){log.info("CheckCombinedEditAuthAspect#CheckCombinedEditAuth--->未查询到用户信息");throw new BusinessException("未查询到用户信息");}List<String> userAuthList = getUserAuthList(userId, checkInstructionAuth);if(pidList==null || pidList.size()==0){log.info("CheckCombinedEditAuthAspect#CheckCombinedEditAuth--->未查询到校验的指令");throw new BusinessException("未查询到校验的指令");}List<String> 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<String> 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<String> getCombinedNameByVcRemarksKey(List<String> vcRemarksKeyList){//伪代码return new ArrayList<>();}/*** 比较List<string> 是否相等* @param userAuthList* @param checkDataList* @return*/private boolean compareAuth(List<String> userAuthList,List<String> 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<String,Object> analysisDataSource(Object[] args, CheckInstructionAuth checkCombinedEditAuth, Method method)throws IllegalAccessException, BusinessException {Map<String,Object> result=new HashMap<>();String operateId=null;List<String> pidList=new ArrayList<>();CheckInstructionEditTypeEnum.DataSourceEnum dataSource = checkCombinedEditAuth.dataSource();try {if(CheckInstructionEditTypeEnum.DataSourceEnum.DEFAULT_KEY.equals(dataSource)){//反射,获取默认的 keyfor(Object obj:args){if(obj instanceof Map){Map<String,Object> objMap=(Map<String,Object>)obj;getParamByDefaultKey(objMap,result);}else{//自定义对象默认keyField[] 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,pidListif(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<args.length;i++){Object obj=args[i];Field[] fields = obj.getClass().getDeclaredFields();for(Field field:fields){field.setAccessible(true);//如果字段上有自定义注解@BindingParamBindingParam bindingParam = field.getAnnotation(BindingParam.class);getParamByBindingParam(bindingParam, field.get(obj),result);}}}else if(CheckInstructionEditTypeEnum.DataSourceEnum.THREAD_LOCAL.equals(dataSource)){operateId = (String) ThreadLocalUtils.get(BindingParamTypeEnum.userId.name());List<String> pidListResult = (List<String>)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<String,Object> getParamByDefaultKey(Map<String,Object> map,Map<String,Object> result){if(map==null){return result;}for(Map.Entry<String,Object> 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<Object> objList=(List<Object>)value;if(objList.size()>0 && objList.get(0) instanceof String){result.put(CheckInstructionEditTypeEnum.DataSourceDefaultKeyEnum.pidList.name(),(List<String>)value);}}}}}return result;}/*** 根据注解获取入参* @param bindingParam* @param param* @return*/private Map<String,Object> getParamByBindingParam(BindingParam bindingParam, Object param, Map<String,Object> 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<Object> objList=(List)param;if(objList!=null && objList.size()>0){if(objList.get(0) instanceof String){result.put(BindingParamTypeEnum.pidList.name(),(List<String>)param);}else {log.info("@BindingParam 注解类型值使用错误,pidList只能绑定List<String>类型");throw new BusinessException("@BindingParam 注解类型值使用错误,pidList只能绑定List<String>类型");}}}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<String> getUserAuthList(String userId,CheckInstructionAuth checkCombinedEditAuth){//获取用户权限,业务代码return new ArrayList<>();}/*** 需要校验的组合数据* @param pidList* @param checkCombinedEditAuth* @return*/private List<String> getCheckData(List<String> pidList, CheckInstructionAuth checkCombinedEditAuth){CheckInstructionEditTypeEnum checkInstructionEditTypeEnum = checkCombinedEditAuth.instructionType();List<String> 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<Map<String, Object>> THREAD_LOCAL =ThreadLocal.withInitial(() -> new ConcurrentHashMap<>(16));/*** 获取到ThreadLocal中值** @return ThreadLocal存储的是Map*/public static Map<String, Object> getThreadLocal() {return THREAD_LOCAL.get();}/*** 从ThreadLocal中的Map获取值** @param key Map中的key* @param <T> Map中的value的类型* @return Map中的value值 可能为空*/public static <T> T get(String key) {return get(key, null);}/*** 从ThreadLocal中的Map获取值** @param key          Map中的key* @param defaultValue Map中的value的为null 是 的默认值* @param <T>          Map中的value的类型* @return Map中的value值 可能为空*/@SuppressWarnings("unchecked")public static <T> T get(String key, T defaultValue) {Map<String, Object> 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<String, Object> map = THREAD_LOCAL.get();map.put(key, value);}/*** ThreadLocal中的Map 添加Map** @param keyValueMap 参数map*/public static void set(Map<String, Object> keyValueMap) {Map<String, Object> map = THREAD_LOCAL.get();map.putAll(keyValueMap);}/*** 删除ThreadLocal中的Map 中的value** @param key Map中的key*/public static void delete(String key) {Map<String, Object> 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 <T>    Map中的value的类型* @return 符合条件的Map*/@SuppressWarnings("unchecked")public static <T> Map<String, T> fetchVarsByPrefix(String prefix) {Map<String, T> vars = new HashMap<>(16);if (StringUtils.isBlank(prefix)) {return vars;}Map<String, Object> 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<String, Object> 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)@Overridepublic void getData(Map<String, Object> map,@BindingParam(BindingParamTypeEnum.pidList) String userId) {System.out.println("testAnnotation");}@CheckInstructionAuth(operateType = CheckInstructionEditTypeEnum.OperateTypeEnum.RELEASE)@Overridepublic void getData(List<UserDto> userDto) {System.out.println("testAnnotation");}
}


http://www.ppmy.cn/news/1016580.html

相关文章

线程池-手写线程池Linux C简单版本(生产者-消费者模型)

目录 简介手写线程池线程池结构体分析task_ttask_queue_tthread_pool_t 线程池函数分析thread_pool_createthread_pool_postthread_workerthread_pool_destroywait_all_donethread_pool_free 主函数调用 运行结果 简介 本线程池采用C语言实现 线程池的场景&#xff1a; 当某些…

微服务技术栈

微服务技术栈是指在开发和构建微服务架构时使用的一组技术和工具。微服务架构是一种软件开发模式&#xff0c;将一个大型应用程序拆分为一组小型、自治的服务&#xff0c;每个服务独立部署、可独立扩展&#xff0c;并通过轻量级的通信机制进行互相协作。 微服务技术栈通常包括…

迭代器模式-遍历聚合对象中的元素

在开发中&#xff0c;我们经常使用到Iterator这个接口&#xff0c;我们很疑惑于这个接口的作用&#xff0c;认为集合已经实现了数据访问的方法&#xff0c;增加Iterator的意义在哪。本文我们将学习迭代器模式&#xff0c;用以探讨Iterator的作用。 1.1 迭代器模式概述 提供一…

Android T 窗口层级相关的类(更新中)

窗口在App端是以PhoneWindow的形式存在&#xff0c;承载了一个Activity的View层级结构。这里我们探讨一下WMS端窗口的形式。 窗口容器类 —— WindowContainer类 /*** Defines common functionality for classes that can hold windows directly or through their* children …

RISC-V云测平台:Compiling The Fedora Linux Kernel Natively on RISC-V

注释&#xff1a;编译Fedora&#xff0c;HS-2 64核RISC-V服务器比Ryzen5700x快两倍&#xff01; --- 以下是blog 正文 --- # Compiling The Fedora Linux Kernel Natively on RISC-V ## Fedora RISC-V Support There is ongoing work to Fedora to support RISC-V hardwar…

外卖项目,登录设计,nginx反向代理,MD5明文加密

.gitignore文件里的东西是进行排除&#xff0c;不用git进行管理。登录设计&#xff0c; controller 接收并封装参数调用service方法查询数据库封装结果并响应 登录成功后&#xff0c;生成jwt令牌 Service层 调用mapper查询数据库密码比对返回结果Mapper 编写sql语句为什么前端不…

配置nginx负载均衡

搭建负载均衡服务的需求如下&#xff1a; 1 .把单台计算机无法承受的大规模并发访问或数据流量分担到多台节点设备上&#xff0c;分别进行处理&#xff0c;减少用户等待响应的时间&#xff0c;提升用户体验。 2. 单个重负载的运算分担到多台节点设备上做并行处理&#xff0c;每…

pyqt5 编写一段自定义信号和槽的示例。

使用 PyQt5 创建自定义信号和槽时&#xff0c;通常需要创建一个继承自 QObject 的类来作为信号的源。以下是一个简单的示例&#xff0c;演示了如何创建自定义信号和槽&#xff1a; import sys from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot from PyQt5.QtWidgets i…