spring boot 后端节流实现

spring boot 后端节流实现

一般情况下,表单提交的时候,前端会做节流操作,有些情况下,后端要需要做节流的操作

实现方案

通过 redis setnx px 原理

DebounceRequest

/**
* 防抖节流
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DebounceRequest {
    /**
     * key
     *
     * @return
     */
    String value() default "";

    /**
     * 超时时间
     *
     * @return
     */
    long timeout() default 0;

    /**
     * 时间单位
     *
     * @return
     */
    TimeUnit timeUint() default TimeUnit.MILLISECONDS;

}

DebounceRequestAspect

@Configuration
@Aspect
@Slf4j
public class DebounceRequestAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @Pointcut("@annotation(request)")
    private void pointcut(DebounceRequest request) {
    }


    @Around("pointcut(request)")
    public Mono<Object> record(ProceedingJoinPoint joinPoint, DebounceRequest request) throws Throwable {
        String key = null;
        Boolean flag = null;
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Object[] args = joinPoint.getArgs();
            key = parseExpression(request.value(), method, args);
            if (request.timeout() > 0) {
                flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", request.timeout(), request.timeUint());
            } else {
                flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1");
            }
            if (flag == null || !flag) {
                throw new RuntimeException("重复请求");
            }
            Object proceed = joinPoint.proceed();
            return (Mono<Object>) proceed;
        } catch (ParseException e) {
            throw e;
        } finally {
            if (StrUtil.isNotBlank(key) && flag != null && flag) {
                //删除 key
                stringRedisTemplate.delete(key);
            }
        }
    }

    private String parseExpression(String value, Method method, Object[] args) {
        //获取被拦截方法参数名列表
        LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        ExpressionParser expressionParser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }
        return expressionParser.parseExpression(value).getValue(context, String.class);
    }

}

RedisConst

public interface RedisConst {

    String PREFIX_DEBOUNCE = "'redis:";
    String CONFIRM_KEY = PREFIX_DEBOUNCE + "confirm:order:'";
}

注意 使用 EL表达式的时候,需要字符串 要用 单引号!!

使用

@DebounceRequest(value = RedisConst.CONFIRM_KEY + "+#orderNo")
@GetMapping("/order/confirm/{orderNo}")
public boolean confirm(@PathVariable String orderNo) {
    return "success";
}

你可能感兴趣的:(spring,boot,spring,boot)