All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fun.tusi.limit.aspect.LimitCatsAspect Maven / Gradle / Ivy

There is a newer version: 1.0.5-sb3
Show newest version
package fun.tusi.limit.aspect;

import fun.tusi.limit.annotation.LimitCat;
import fun.tusi.limit.annotation.LimitCats;
import fun.tusi.limit.service.LimitCatException;
import fun.tusi.limit.service.LimitCatService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * 业务频率控制记录切面
 * @author xy783
 */
@Slf4j
@Order(-100)
@Aspect
public class LimitCatsAspect {

    @Autowired
    private LimitCatService limitCatService;

    // 20220514 切换为  @annotation(annotation.limit.com.cat.LimitCats) 方式获取,用于兼容 单个 和 多个 @LimitCat一起使用的场景
    // @Pointcut("@annotation(limitCats)")
    // public void pointCut1(LimitCats limitCats) {}

    @Pointcut("@annotation(fun.tusi.limit.annotation.LimitCats) || @annotation(fun.tusi.limit.annotation.LimitCat)")
    public void pointCut() {}

    /**
     * 响应正常
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointCut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        check(proceedingJoinPoint);

        Object proceed = proceedingJoinPoint.proceed();

        update(proceedingJoinPoint,null);

        return proceed;
    }

    /**
     * 响应异常
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "pointCut()", throwing = "e")
    public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
        update(joinPoint,e);
    }

    /**
     * 检查频率计数
     * @param joinPoint
     */
    private void check(JoinPoint joinPoint) {

        log.info("LimitCatsAspect:check");

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        Method method = methodSignature.getMethod();

        LimitCats limitCats = method.getAnnotation(LimitCats.class);

        if(limitCats == null) {

            limitCats = new LimitCats() {
                @Override
                public Class annotationType() {
                    return LimitCats.class;
                }

                @Override
                public LimitCat[] value() {
                    return new LimitCat[]{method.getAnnotation(LimitCat.class)};
                }
            };
        }

        Arrays.stream(limitCats.value()).forEach(limitCat -> {

            log.info("limitCat:check -> {}",limitCat.scene());

            String scene = StringUtils.hasText(limitCat.scene())?limitCat.scene():methodSignature.getName();
            String key = getKey(joinPoint, scene, limitCat.key());

            // 调用频率验证
            limitCatService.checkFrequency(scene, key, limitCat);

        });

    }

    /**
     * 更新频率计数
     * @param joinPoint
     * @param e
     */
    private void update(JoinPoint joinPoint, RuntimeException e) {

        log.info("LimitCatsAspect:update e = {}",e);

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        Method method = methodSignature.getMethod();

        LimitCats limitCats = method.getAnnotation(LimitCats.class);

        if(limitCats == null) {

            limitCats = new LimitCats() {
                @Override
                public Class annotationType() {
                    return LimitCats.class;
                }

                @Override
                public LimitCat[] value() {
                    return new LimitCat[]{method.getAnnotation(LimitCat.class)};
                }
            };
        }

        Arrays.stream(limitCats.value()).forEach(limitCat -> {
            // 根据异常类型来判断是否触发计数
            // 1、e!=null && instanceofOneof(item,e)      > 异常,且异常类型满足触发条件
            // 2、e==null && item.triggerFor().length==0  > 正常,且未设置 triggerFor
            if((e!=null && instanceofOneof(limitCat,e)) || (e==null && limitCat.triggerFor().length==0)) {

                log.info("limitCat:update -> {}",limitCat.scene());

                String scene = StringUtils.hasText(limitCat.scene())?limitCat.scene():joinPoint.getSignature().getName();
                String key = getKey(joinPoint,scene,limitCat.key());
                limitCatService.updateFrequency(scene,key,limitCat.rules());
            }
        });

    }

    /**
     * 判断 异常 和 异常中指定参数 是否满足
     * @param limitCat
     * @param e
     * @return
     */
    private boolean instanceofOneof(LimitCat limitCat, RuntimeException e) {

        Class[] triggerFor = limitCat.triggerFor();

        if(triggerFor.length==0) {
            return false;
        }

        Set codes = new HashSet<>(Arrays.asList(limitCat.triggerForCode()));

        Object codeObj = null;

        if(!codes.isEmpty()) {

            // 获取异常的属性值
            Method method = ReflectionUtils.findMethod(e.getClass(), "get"+StringUtils.capitalize(limitCat.triggerForCodeField()));

            if(method != null) {
                codeObj = ReflectionUtils.invokeMethod(method, e);
            }
        }

        for (Class eClass : triggerFor) {

            if(eClass.isInstance(e)) {

                // triggerForCode 不设置时,只检查 Exception 是否满足
                if(codes.isEmpty()) {

                    return true;

                } else if(codeObj!=null) {

                    if(codes.contains(String.valueOf(codeObj))) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    /**
     * 获取频率限制场景 key
     * @param joinPoint
     * @param key
     * @return
     */
    private String getKey(JoinPoint joinPoint, String scene, String key) {

        // TODO key 默认为客户端 ip + userAgent 是否合理?
        // TODO 对于周期内频率限制的场景,考虑使用 redis 临牌桶?是否合理?

        // 获取方法签名(通过此签名获取目标方法信息)
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();

        // 获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer localVariableTable = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = localVariableTable.getParameterNames(methodSignature.getMethod());

        // 使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();

        // SPEL上下文 使用 StandardEvaluationContext 有注入的隐患, SimpleEvaluationContext 比较安全
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

        //把方法参数放入SPEL上下文中
        for(int i=0; i




© 2015 - 2025 Weber Informatics LLC | Privacy Policy