• 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();
     						注解中属性值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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代理对象执行
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • aop2:

      等效

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

      二、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 "";
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

      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;
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104

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

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

      注解的使用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) {
      
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      注解的使用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) {
           
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      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);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      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;
              }
      
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109
      • 110
      • 111
      • 112
      • 113
      • 114
      • 115
      • 116
      • 117
      • 118
      • 119
      • 120
      • 121
      • 122
      • 123
      • 124
      • 125
      • 126
      • 127
      • 128
      • 129
      • 130
      • 131
      • 132
      • 133
      • 134
      • 135
      • 136
      • 137
      • 138
      • 139
      • 140
      • 141
      • 142
      • 143

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

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

      @Documented
      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.METHOD, ElementType.TYPE})
      public @interface AuthConfig {
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      2、使用

      @Override
      @AuthConfig
          public QueryRdcSupplierRefundBillListResponseDTO queryRdcSupplierRefundBill(QuerySupplierRefundBillRequestDTO request) throws TException {
           
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      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>
      
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23

      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;//优先级最高
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    四、异常处理日志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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 问题

    每个接口实现类,都要写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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    五、自定义用户鉴权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();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2、使用注解

    		@Override
        @Authentication(
                poiType = 7,
                id = "#req.poiId"
        )
        public StcEditProcessConfigTResp editProcessConfig(StcEditProcessConfigTReq req) {
           // 业务
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
  • 相关阅读:
    逆向-beginners之函数指针
    docker安装消息队列(rabbitmq)及数据库(mongo、mysql)
    LeetCode刷题之HOT100之搜索旋转排序数组
    CentOS 安装 tomcat 并设置 开机自启动
    常用SQL总结
    Python实现整蛊恶搞程序生成exe文件小弹窗祝福发给好兄弟好闺蜜好室友
    《vtk9 book》 官方web版 第3章 - 计算机图形基础 (3 / 5)
    优思学院|如何领导六西格玛变革?学习哈佛商学院的八步变革模型
    230920_整合微信支付宝支付
    项目-SpringBoot
  • 原文地址:https://blog.csdn.net/tmax52HZ/article/details/133000148