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