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");}
}