Aop实战

文章目录

  • 一.基础
      • 1.1 aop时方法的执行一定要使用,增强后的代理target去执行方法,否则等同于this.
  • 二、aop增加redis锁
  • 三、用户鉴权aop(参考五-自定义用户鉴权)
  • 四、异常处理日志aop
  • 五、自定义用户鉴权AOP

一.基础

JoinPoint用于before形切面,ProceedingJoinPoint用于环绕around形切面

  • ProceedingJoinPoint pjp内容
signature: MethodSignature signature = (MethodSignature) jp.getSignature();
		returnType: 方法返回值信息
						name: 全路径返回类的名称com....Xxx
						
		parameterTypes:0:第一个参数
								name: 全路径入参的名称com....Xxx
		
     入参的值:MethodInvocation methodInvocation = (MethodInvocation) jp.getSignature();
        			Object[] arguments = methodInvocation.getArguments(); 

    method: Method method = signature.getMethod();
				name: 方法名称
				
				annotation:   Authentication annotation = method.getAnnotation(Authentication.class);
 						注解的全路径名称:Class<? extends Annotation> annotationType = annotation.annotationType();
 						注解中属性值
  • pjp内容获取(class、resp、req、field、method、annotation)
				MethodSignature signature = (MethodSignature) pjp.getSignature();

        // 作用的类名
        Class resp = signature.getReturnType();
        String respName = resp.getName();
        String respSimpleName = resp.getSimpleName();

        // 作用方法名
        Method method = signature.getMethod();
        String methodName = method.getName();

        // req名
        String[] parameterNames = signature.getParameterNames();
        Class[] parameterTypes = signature.getParameterTypes();
        Class parameterType = parameterTypes[0];
        String reqName = parameterType.getName();
        String reqSimpleName = parameterType.getSimpleName();

        // 方法入参req内容
        Object[] args = pjp.getArgs();//方法所有入参
        Object arg = args[0];//方法第一个入参对象(含有属性值)

        // annocation名称和属性值
        Authentication annotation = method.getAnnotation(Authentication.class);
        Class<? extends Annotation> annotationType = annotation.annotationType();
        String annotationTypeName = annotationType.getName();
        String annotationSimpleName = annotationType.getSimpleName();
        int poiType = annotation.poiType();//注解自定义的属性值

1.1 aop时方法的执行一定要使用,增强后的代理target去执行方法,否则等同于this.

  • aop1

    public class RdcRefundMoveShelfInterceptorManager implements ApplicationContextAware {
        @Autowired RdcRefundMoveShelfInterceptorManager proxy;
          public void executeRdcRefundMoveShelf(MoveShelfContext context) {
            log.info("开始执行移库责任链,context:{}", GsonUtil.toJsonString(context));
            log.info("refundBillNo:{},开始执行移库同步操作", context.getRefundBillNo());
            // 执行同步操作
            doExecuteSyncInterceptors(context);
            log.info("refundBillNo:{},开始执行移库异步操作,context:{}", context.getRefundBillNo(),GsonUtil.toJsonString(context));
            // 执行异步操作
            proxy.doExecuteAsyncInterceptors(context); //!!!!!!这里,要使用proxy代理对象执行
        }
    }
    
  • aop2:

    等效

    ((RefundOutBoundBillStatusChangeJobOptm)AopContext.currentProxy()).processRdcNotSystemBill(bill,dayLimit);
    

    二、aop增加redis锁

  • 加锁

  • 方法

  • 解锁

    1、注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RefundConcurrentControl {
        /** 操作间隔时间,分布式锁场景就是锁的超时时间 */
        int intervalTimeSeconds();
    
        /** 并发控制键计算规则 */
        String keyGenRule();//貌似必须要加#前缀,expression解析的时候需要
    
        /** 是否需要释放,一般分布式锁场景需要释放 */
        boolean needRelease();
    
        /** 指定前缀;如果不指定前缀就是类名+方法名*/
        String specifyPrefix() default "";
    
        /** 指定错误提示;如果不指定就按系统默认值*/
        String specifyErrorMsg() default "";
    }
    

    2、aop实现

    //依赖spring-expression,这个在spring-context包下有,一般spring服务都会有这个依赖
    //还依赖spring-core,一般spring服务都会有

    import lombok.extern.slf4j.Slf4j;
    import org.apache.logging.log4j.util.Strings;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.DefaultParameterNameDiscoverer;
    import org.springframework.core.Ordered;
    import org.springframework.expression.EvaluationContext;
    import org.springframework.expression.Expression;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    import org.springframework.expression.spel.support.StandardEvaluationContext;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    import java.util.Objects;
    
    @Aspect
    @Component
    @Slf4j
    public class ConcurrentControlAspect implements Ordered {
        private static final String CAT_TYPE = ConcurrentControlAspect.class.getSimpleName();
    
        private SpelExpressionParser elExpressionParser = new SpelExpressionParser();
        private DefaultParameterNameDiscoverer parameterNameDiscoverer =
                new DefaultParameterNameDiscoverer();
    
        @Autowired private RefundDistributeLock distributeLock;
        //默认错误提示
        private static final String COMMON_ERROR_MSG = "当前操作过于频繁,请稍后再试";
        // 0.生效的范围是注解,around方式(加锁 - 方法 - 解锁)环绕方式
        @Around(
                "@annotation(com.sankuai.grocerywms.logistics.sharedrefund.annotation.RefundConcurrentControl)")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            // 1、根据方法注解解析接口配置信息
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();
            RefundConcurrentControl controlConfig = method.getAnnotation(RefundConcurrentControl.class);
            // 解析参数
            if (Objects.isNull(controlConfig)) {
                throw new BusinessException(Constants.CONCURRENT_CONTROL_CONFIG_ERROR, "参数为空");
            }
            // 两次操作间隔时间
            int intervalTimeSeconds = controlConfig.intervalTimeSeconds();
            // 生成键规则
            String specifyPrefix = controlConfig.specifyPrefix();
            // 生成键规则
            String keyGenRule = controlConfig.keyGenRule();
            // 是否需要释放
            boolean needRelease = controlConfig.needRelease();
            //错误提示
            String errorMsg = !Strings.isBlank(controlConfig.specifyErrorMsg()) ? controlConfig.specifyErrorMsg() : COMMON_ERROR_MSG;
    
            // 2、计算lockKey
            String lockKey = generateLockKey(pjp, specifyPrefix,keyGenRule);
            // 3、加锁(aop前缀增强)
            boolean lockResult = false;
            try {
                lockResult = distributeLock.lock(lockKey, intervalTimeSeconds);
                if (!lockResult) {
                    Cat.logEvent(CAT_TYPE, "LOCK_FAIL");
                    throw new BusinessException(
                            Constants.CONCURRENT_CONTROL_LOCK_FAIL, errorMsg);
                }
                // 4.执行方法本身(本前缀增强 和 后缀增强环绕)
                Object result = pjp.proceed();
                return result;
            } catch (Exception e) {
                log.warn("方法执行异常,e:{}", e.getMessage());
                throw e;
            } finally {
                // 5、解锁(aop后缀增强)
                if (needRelease && lockResult) {
                    distributeLock.unlock(lockKey);
                }
            }
        }
    
        private String generateLockKey(ProceedingJoinPoint pjp,String specifyPrefix ,String keyGenRule) {
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();
            String methodFullName = pjp.getTarget().getClass().getSimpleName() + method.getName();
            //1、lockKey前缀;用户不指定前缀则默认为类名+方法名
            String prefix = !Strings.isBlank(specifyPrefix) ? specifyPrefix : methodFullName;
    
            Object[] args = pjp.getArgs();
            String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
            EvaluationContext context = new StandardEvaluationContext();
            for (int i = 0; i < args.length; i++) {
                context.setVariable(paramNames[i], args[i]);//key: 方法参数名称,val方法入参参数对象本身(含字段值)。方法参数可能多个,故循环添加
            }
            Expression expression = elExpressionParser.parseExpression(keyGenRule);//el表达式解析"#内容"
            // 2、方法名-参数解析结果
            return prefix + "-" + expression.getValue(context).toString();//等效map.get(key),context为map,key是el表达式
        }
    
        @Override
        public int getOrder() {
            //5、配置为最小值 在事务切面之前执行
            return Integer.MIN_VALUE;
        }
    }
    
    

    3、注解的使用1: key直接使用方法入参某个字段

    @RefundConcurrentControl(
                intervalTimeSeconds = 3,
                keyGenRule = "#operator",//类似于你要加锁的key(misId、orderNo、taskCode等)
                needRelease = true,
                specifyPrefix = "WriteShippingTaskDetail",//描述你加锁操作的目的,具体是对什么操作(创建退货单、导出加量日志等)
                specifyErrorMsg = "该单正在操作中,请稍后重试操作")
        public void confirmPDAShippingTaskDetail(ConfirmShippingTaskDetailRequest request,String operator) {
            
        }
    

    注解的使用2: key间接使用方法入参request中,某个字段名称

        @RefundConcurrentControl(
                specifyPrefix = "changePdaPickingTaskTaker",
                intervalTimeSeconds = 10,
                needRelease = true,
                specifyErrorMsg = "正在更改执行人,请勿重复操作",
                keyGenRule = "#request.newOperator") //ChangeOperatorTRequest request中的newOperator字段作为key
        @Transactional(rollbackFor = Exception.class)
        public void changeOperator(ChangeOperatorTRequest request) {
    
        }
    

    注解的使用3: key间接使用方法入参request中,某2个字段组合

    @RefundConcurrentControl(
                specifyPrefix = "OperateRDCPickingSkuDetail",
                intervalTimeSeconds = 2,
                needRelease = true,
                specifyErrorMsg = "重复性互斥提交,请稍后重试",
                keyGenRule = "#request.pickingTaskNo + '-' + #request.pickingTaskSkuDetailId")//SubmitPickingTaskDetailRequest request中的两个字段拼接成key
        
        public SubmitPickingTaskDetailResponse submitPickingTaskDetail(long poiId, SubmitPickingTaskDetailRequest request) {
         
    }
    

    4、分布式锁实现

    public interface DistributeLockService {
    
        /**
         * 释放锁
         *
         * @param idempotentKey:key
         * @param redisCategory:squirrel上申请的category
         */
        void unlock(String idempotentKey, String redisCategory);
    
        /**
         * 加锁
         *
         * @param idempotentKey:key
         * @param expireTime:锁有效时长
         * @param redisCategory:squirrel上申请的category
         */
        boolean lock(String idempotentKey, int expireTime, String redisCategory);
    }
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.retry.annotation.Retryable;
    import org.springframework.stereotype.Component;
    import javax.annotation.Resource;
    @Component
    public class RedisGateway {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(SquirrelGateway.class);
    
        @Resource(name = "redisClient0")
        private RedisStoreClient redisClient;
    
    
        /**
         * @param storeKey        key
         * @param keySegments     要增加的步长
         * @param expireInSeconds key的过期时间
         * @return
         */
        public Long buildSquirrel(StoreKey storeKey, long keySegments, int expireInSeconds) throws GatewayException {
    
            LOGGER.info("redisClient信息:{}", redisClient.getClusterName());
            try {
                return redisClient.incrBy(storeKey, keySegments, expireInSeconds);
            } catch (Exception e) {
                throw new GatewayException(GATEWAY_EXCEPTION_CODE, "buildSquirrel exception", e);
            }
        }
    
        /**
         * 设置过期
         *
         * @param storeKey        key
         * @param expireInSeconds 过期时间
         */
        @Retryable
        public void expire(StoreKey storeKey, int expireInSeconds) {
            LOGGER.info("expire,storeKey:[{}],expireInSeconds:[{}]", storeKey,expireInSeconds);
            try {
                redisClient.expire(storeKey, expireInSeconds);
            } catch (Exception e) {
                throw new GatewayException(GATEWAY_EXCEPTION_CODE, "expire exception", e);
            }
        }
    
        public boolean setNx(StoreKey storeKey, String value, int expireSeconds) {
            LOGGER.info("setNx,storeKey:[{}],value:[{}]", storeKey, value);
            try {
                return redisClient.setnx(storeKey, value, expireSeconds);
            } catch (Exception e) {
                throw new GatewayException(GATEWAY_EXCEPTION_CODE, "setNx exception", e);
            }
        }
    
    
        /**
         * 释放锁
         * @param idempotentKey
         * @param redisCategory
         */
        public boolean delete(String idempotentKey, String redisCategory) {
            LOGGER.info("Redis delete, key:{}", idempotentKey);
    
            StoreKey storeKey = new StoreKey(redisCategory, idempotentKey);
    
            try {
                if (redisClient.exists(storeKey)) {
                    return redisClient.delete(storeKey);
                }
            } catch (Exception e) {
                LOGGER.error("redis 异常", e);
            }
            return false;
        }
    
        /**
         * 尝试加锁
         * @param key
         * @param redisCategory:Squirrel中申请的category
         * @return
         */
        @Retryable(exclude = GatewayException.class)
        public boolean setNxNew(String key, String value, int expireTime, String redisCategory) {
            LOGGER.info("Redis setNx, key:{}", key);
            StoreKey storeKey = new StoreKey(redisCategory, key);
    
            try {
                return redisClient.setnx(storeKey, value.getBytes(), expireTime);
            } catch (Exception e) {
                if (e.getMessage().contains("ReadTimeout = ")) {
                    // 读取超时需要考虑,可能已经set成功了,重试可能会返回false
                    throw new GatewayException(GATEWAY_READ_TIMEOUT_EXCEPTION_CODE, e.getMessage(), e);
                }
                // 重试三次
                throw e;
            }
        }
    
        /**
         * @param key          : key
         * @param redisCategory:Squirrel中申请的category
         * @return             : set成功
         */
        public boolean set(String key, Object value, int expireTime, String redisCategory) {
            LOGGER.info("Redis set, key:{}", key);
            StoreKey storeKey = new StoreKey(redisCategory, key);
            try {
                return redisClient.set(storeKey, value, expireTime);
            } catch (Exception e) {
                LOGGER.error("RedisGateway set occurs exception", e);
                throw new GatewayException(GATEWAY_EXCEPTION_CODE, e.getMessage());
            }
        }
    
        public String get(String key, String redisCategory) {
            LOGGER.info("Redis get, key:{}", key);
            StoreKey storeKey = new StoreKey(redisCategory, key);
            try {
                return redisClient.get(storeKey);
            } catch (Exception e) {
                LOGGER.error("RedisGateway get occurs exception", e);
                throw new GatewayException(GATEWAY_EXCEPTION_CODE, e.getMessage());
            }
        }
    
        @Retryable(exclude = GatewayException.class)
        public boolean retrySetNx(StoreKey storeKey, String value, int expireSeconds) {
            LOGGER.info("retrySetNx,storeKey:[{}],value:[{}],expireSeconds:[{}]", storeKey, value, expireSeconds);
            try {
                return redisClient.setnx(storeKey, value, expireSeconds);
            } catch (StoreException e) {
                if (e.getMessage().contains("ReadTimeout = ")) {
                     // 读取超时需要考虑,可能已经set成功了,重试可能会返回false
                    throw new GatewayException(GATEWAY_READ_TIMEOUT_EXCEPTION_CODE, e.getMessage(), e);
                }
                // 重试三次
                throw e;
            }
    
        }
    }
    
    

    三、用户鉴权aop(参考五-自定义用户鉴权)

    1、定义注解:可加载方法和类上

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    public @interface AuthConfig {
    }
    

    2、使用

    @Override
    @AuthConfig
        public QueryRdcSupplierRefundBillListResponseDTO queryRdcSupplierRefundBill(QuerySupplierRefundBillRequestDTO request) throws TException {
         
        }
    

    3、pom

    aspectjweaver
            <!--spring的aop支持包-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>4.2.5.RELEASE</version>
            </dependency>
            
           <!--面向切面支持包,Spring要支持AOP必需要导入这个包-->
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.8</version>
            </dependency>
            
            //aop类实现了Ordered接口
                    <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>4.2.5.RELEASE</version>
            </dependency>
    
    
    

    4、aop实现

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;

@Slf4j
@Aspect
@Component
public class AuthAspect implements Ordered {
    
    @Autowired
    private PoiAuthGateway poiAuthGateway;

     //1、想要生效的java下的路径(接口实现类下的方法都可以)
    @Before("execution(* com.sankuai.grocerywms.logistics.sharedrefund.service..*TServiceImpl.*(..))")
    public void before(JoinPoint joinPoint) throws BusinessException {
        
        // 2、根据方法注解解析接口配置信息(方法上加AuthConfig这个注解的)
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        AuthConfig config = method.getAnnotation(AuthConfig.class);

        // 3、根据类注解解析接口配置信息
        if (config == null) {
            config = method.getDeclaringClass().getAnnotation(AuthConfig.class);
        }
        
        // 4、没有配置权限注解, 默认为不需要验证
        if (config == null) {
            return;
        }
        //5、如果接口、类上添加了此注解,则走下面逻辑(通过公司组件获取misId + 通过公司组件获取poiId)
    
        // 5.1(可以不用关注)校验渠道, 同时获取到操作的用户-获取misId
        LoginInfo loginInfo = LoginUtil.getLoginInfo();
        if (!LionUtil.poiAuthSwitch()) {
            log.info("未开启仓库ID权限校验, 跳过校验");
            return;
        }
        // 没有配置需要检验的环境
        if (loginInfo == null || StringUtils.isBlank(loginInfo.getLogin())) {
            log.info(String.format("未获得登陆信息,方法名:%s", method.getName()));
            return;
        }
        log.info("operator:{}", GsonUtil.toJsonString(loginInfo));
        // 5.2 获取仓id
        String poiIdStr = Tracer.getContext("poi.id");
        if (StringUtils.isBlank(poiIdStr)) {
            log.info(String.format("未获得poi信息,方法名:%s", method.getName()));
            return;
        }
        Long poiId = Long.parseLong(poiIdStr);
        // 5.3加了开关,判断这个仓是否需要鉴权
        if (!LionUtil.allPoiAuthSwitch() && !LionUtil.isPoiInAuthList(poiId)) {
            log.info(String.format("poi不在灰度列表,方法名:%s", method.getName()));
            return;
        }

        // 6、校验仓库是否合法(根据具体业务判断,这里也可以获取用户手机号,然后查询db是否有数据,有则鉴权通过)具体业务定制具体校验
        checkPoiInfo(loginInfo, poiId);
    }

    /**
     * 校验当前操作的仓库ID是否有权限
     */
    private void checkPoiInfo(LoginInfo loginInfo, Long poiId) throws BusinessException {

        // 1、仓库权限查询(公司内部提供的根据misId + poiId + 仓类型,判定这个人是否有这个仓的权限)
        boolean hasPermission = poiAuthGateway.havePoiPermission(loginInfo.getLogin(), poiId);

        // 2、无权限抛出异常
        if (!hasPermission) {
            throw new BusinessException(Constants.OPERATE_FORBIDDEN, "当前用户无仓库[" + poiId + "]操作权限");
        }
    }

    @Override
    public int getOrder() {
        return Integer.MAX_VALUE;//优先级最高
    }
}

四、异常处理日志aop

1、正常情况下,我们写代码是这样的

@Override
    public QueryStcBlackListTResp queryStcBlackList(QueryStcBlackListTReq request) {
        QueryStcBlackListTResp response = new QueryStcBlackListTResp();
        response.setCode(RespCodeEnum.SUCCESS_CODE.code);
        try {
            //业务
        } catch (IllegalArgumentException e) {
            log.warn("queryStcBlackList...参数错误 param: [{}]", GsonUtils.toJsonString(request), e);
            response.setCode(RespCodeEnum.PARAM_ERROR.code);
            response.setErrMsg(e.getMessage());
        } catch (BusinessException e) {
            log.error("queryStcBlackList...业务异常 param: [{}]", GsonUtils.toJsonString(request), e);
            response.setCode(RespCodeEnum.BIZ_ERROR.code);
            response.setErrMsg(e.getMessage());
        } catch (Exception e) {
            log.error("queryStcBlackList...内部错误 param: [{}]", GsonUtils.toJsonString(request), e);
            response.setCode(RespCodeEnum.SERVICE_ERROR.code);
            response.setErrMsg(RespCodeEnum.SERVICE_ERROR.desc);
        }
        return response;
    }
  • 问题

每个接口实现类,都要写catch逻辑,根据不同的Exception然后打印不同的日志,所有impl中方法代码都是重复的,都这样

  • 我们想实现这样写代码,只写业务逻辑,不写try和catch块
    @Override
    public QueryStcBlackListTResp queryStcBlackList(QueryStcBlackListTReq request) {
        //业务
    }
    
    //或这样
    @Override
    public CreateRefundTResponse createRefundBill(CreateRefundTRequest request) throws TException {
        if (request == null) {
            throw new IllegalArgumentException("入参不能为空");
        }
        log.info("创单入参数:{}", GsonUtil.toJsonString(request));
        Preconditions.checkArgument(Objects.nonNull(request.getPoiId())&&request.getPoiId() > 0, "仓id不能为空");
        Preconditions.checkArgument(Objects.nonNull(request.getRefundBillNo()), "单号不能为空");
        // 赋值
        if (request.getBillTypeValue().equals(RefundBillTypeEnum.TAKE.code)) {
            request.setBillType(RefundBillTypeEnum.TAKE.desc);
            request.setTarget(RefundTargetEnum.TAKE.getValue());
        } else if (request.getBillTypeValue().equals(RefundBillTypeEnum.DESTROY.code)) {
            request.setBillType(RefundBillTypeEnum.DESTROY.desc);
            request.setTarget(RefundTargetEnum.DESTROY.getValue());
        } else {
            throw new BusinessException(Constants.INTERNAL_ERROR, "bill type error");
        }
        //业务
        return refundCmdLocalService.createPcRefundBill(createRefundBillBO, createRefundSkuDetailDTOS);
    }

2、aop实现

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @description: 全局异常处理
 */
@Aspect
@Component
@Slf4j
public class TServiceAspect implements Ordered {
    //生效的范围是TServiceImpl下所有方法
    @Around("execution(* com.xx.xx.xx.xx.service..*TServiceImpl.*(..))")
    public Object doAop(ProceedingJoinPoint pjp) throws Throwable {
        String targetClassName = pjp.getTarget().getClass().getSimpleName();
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        String methodName = signature.getName();
        String methodFullName = targetClassName + "." + methodName;
        Object[] args = pjp.getArgs();


        try {
            log.info("method called:{},param:{}", methodFullName, GsonUtil.toJsonString(args));
        } catch (Exception e) {
            log.error("打印请求参数错误", e);
        }

        Method setCode = Arrays.stream(signature.getReturnType().getMethods())
                .filter(method -> "setCode".equalsIgnoreCase(method.getName()))
                .findFirst()
                .orElse(null);

        Method setMsg = Arrays.stream(signature.getReturnType().getMethods())
                .filter(method -> "setMsg".equalsIgnoreCase(method.getName()))
                .findFirst()
                .orElse(null);

        if (setMsg == null) {
            setMsg = Arrays.stream(signature.getReturnType().getMethods())
                    .filter(method -> "setMessage".equalsIgnoreCase(method.getName()))
                    .findFirst()
                    .orElse(null);
        }
        LoginInfo loginInfo = LoginUtil.getLoginInfo();
        try {
            Object result = pjp.proceed();
            log.info("method called:{},param:{}, return:{}, loginInfo = {}.", methodFullName, GsonUtil.toJsonString(args), GsonUtil.toJsonString(result), loginInfo);
            return result;
        } catch (IllegalArgumentException e) {
            int code = Constants.BAD_PARAMETERS;
            String message = e.getMessage();
            log.warn("TServiceExceptionAspect catch IllegalArgumentException, method:{},param:{}, code:{}, message:{}, loginInfo = {}, exception : {} ",
                    methodFullName, args, code, message, loginInfo, e);
            if (setMsg == null || setCode == null) {
                throw e;
            } else {
                Object result = signature.getReturnType().newInstance();
                setMsg.invoke(result, e.getMessage());
                setCode.invoke(result, code);
                return result;
            }
        } catch (BusinessException e) {
            int code = e.getCode();
            String message = e.getMessage();
            String errorKey = e.getErrorKey();
            boolean block = e.isBlock();
            log.warn("TServiceExceptionAspect catch BusinessException, method:{},param:{}, code:{}, message:{}, block:{}, errorKey:{}, loginInfo = {}, exception : {}.",
                    methodFullName, args, code, message, block, errorKey, loginInfo, e);

            doCatLog(methodFullName, e);
            if (setMsg == null || setCode == null) {
                throw e;
            } else {
                Object result = signature.getReturnType().newInstance();
                setMsg.invoke(result, e.getMessage());
                setCode.invoke(result, e.getCode());
                return result;
            }
        } catch (Exception e) {
            log.error("TServiceExceptionAspect catch Throwable, method:{},param:{}, loginInfo = {}", methodFullName, args, loginInfo, e);
            doCatLog(methodFullName, e);
            if (setMsg == null || setCode == null) {
                throw e;
            } else {
                Object result = signature.getReturnType().newInstance();
                String message = String.format("系统内部错误,错误方法:%s[traceId=%s]", methodFullName, Tracer.id());
                setMsg.invoke(result, message);
                setCode.invoke(result, Constants.INTERNAL_ERROR);
                return result;
            }
        }
    }

    private void doCatLog(String methodFullName, Throwable e) {
        if (e instanceof BusinessException) {
            BusinessException ex = (BusinessException) e;
            int code = ex.getCode();
            String message = ex.getMessage();
            String errorKey = ex.getErrorKey();
            boolean block = ex.isBlock();

            logBusinessException(methodFullName, ex, code, message, errorKey, block);
        } else {
        }
    }

    private void logBusinessException(String methodFullName, Exception ex, int code, String message, String errorKey, boolean block) {

        if (code == Constants.GATEWAY_ERROR) {

        }

        if (block) {

        }
        if (StringUtils.isNotBlank(errorKey)) {

        }
    }


    @Override
    public int getOrder() {
        return Integer.MAX_VALUE - 1;
    }
}

五、自定义用户鉴权AOP

1、定义注解

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

    /**
     * 字段含义: 仓类型
     * 是否必填: 是
     * 内容   : 2:Rdc 、6: 协同仓,、7:PC加工仓、4: 网店
     */
    int poiType();

    /**
     * 字段含义 : 仓id 或 网店id
     * 是否必填 : 是
     * 内容    : poiType值为2、6、7时,id值为仓id ;poiType值为4时,id值为网店id
     * example: "#request.poiId"、"#request.netPoiId"
     *          补充:#,为了el表达式解析
     *              request,方法入参名称
     *              poiId或netPoiId是request中字段名称
     */
    String id();

}

2、使用注解

		@Override
    @Authentication(
            poiType = 7,
            id = "#req.poiId"
    )
    public StcEditProcessConfigTResp editProcessConfig(StcEditProcessConfigTReq req) {
       // 业务
    }

3、req

@TypeDoc(description = "编辑-加量链路开关请求")
@ThriftStruct
public class StcEditProcessConfigTReq {
    @FieldDoc(description = "仓id", requiredness = Requiredness.REQUIRED)
    private Long poiId;

    @ThriftField(1)
    public Long getPoiId() {
        return this.poiId;
    }

    @ThriftField
    public void setPoiId(Long poiId) {
        this.poiId = poiId;
    }

}

4、aop类

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;

@Slf4j
@Aspect
@Component
public class AuthenticationAspect implements Ordered {

    private SpelExpressionParser elExpressionParser = new SpelExpressionParser();

    @Resource
    private DataAuthService dataAuthService;
    
    @Around(
            "@annotation(com.x.x.x.x.biz.service.aop.Authentication)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        // 1、反射获取方法信息(方法、注解等)
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        Authentication annotation = method.getAnnotation(Authentication.class);

        // 2.获取注解信息(仓类型 和 仓|网店id)
        int poiType = annotation.poiType();
        String poiIdOrNetPoiId = expressPoiOrNetPoiId(pjp, annotation.id());

        // 3、获取misId
        String misId = UserUtil.getMis();

        // 4.根据misId 和 仓|网店id 鉴权
        ExceptionMessageCollector collector = new ExceptionMessageCollector();
        if (dataAuthService.checkPcXtcAuth(misId, Lists.newArrayList(Long.valueOf(poiIdOrNetPoiId)), poiType, collector)) {
            return pjp.proceed();
        }
        throw new BusinessException(ResultCodeEnum.AUTH_ERROR.getCode(), collector.toString());
    }

    private String expressPoiOrNetPoiId(ProceedingJoinPoint pjp, String id) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        
        // 1.获取增强方法的所有请求参数对象
        Object[] args = pjp.getArgs();
        
        // 2.获取增强方法的所有请求参数名称
        String[] paramNames = signature.getParameterNames();
        
        // 3.map即context添加 key(请求参数名称) - val(请求参数对象)
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }

        // 4.参数解析结果,类似map.get(key),只不过map为context, key为转换为el表达式的expression
        Expression expression = elExpressionParser.parseExpression(id);
        Object poiOrNetPoiId = expression.getValue(context);
        
        if (poiOrNetPoiId != null) {
            return poiOrNetPoiId.toString();
        }
        return StringUtils.EMPTY;
    }


    @Override
    public int getOrder() {
        return Integer.MAX_VALUE;
    }

}

5、test

    @Resource
    private StcSellOutIncreaseProcessTService stcSellOutIncreaseProcessTService;

    @Test
    public void testAop() {
        StcEditProcessConfigTReq req = new StcEditProcessConfigTReq();
        req.setPoiId(143180124832199L);
        req.setSellOutOrTime("19:45");
        req.setSwitchStatus(1);
       
        // 用户有仓的权限
        Tracer.putContext("mall.sso.user", "{\"login\": \"zhangsan\"}");
        StcEditProcessConfigTResp resp = stcSellOutIncreaseProcessTService.editProcessConfig(req);
      
      // 用户没有仓的权限
      racer.putContext("mall.sso.user", "{\"login\": \"zhangsanaaa\"}");
      StcEditProcessConfigTResp resp = stcSellOutIncreaseProcessTService.editProcessConfig(req);
    }

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