使用Redis实现限制前端重复提交

在开发过程中,前端进行提交后,需要对用户提交进行限制,例如几秒内只能提交一次。本文实现了简易版的后台限制某用户在一定时间段内的提交阈值。

主要思路是对用户提交的参数取出关键字段生成key,利用Redis的setIfAbsent特性,如果键设置成功,则正常进行业务逻辑,键设置失败的话则证明该键存在于redis中,禁止进行业务逻辑。本文基于微服务的具体服务端实现。

需要注意的一点是,在使用微服务的情况下,如果使用非集中的redis,因为负载均衡策略,请求会打到不同的机器上,导致限制失效。此时可参考该思路在Gateway入口处实现限制逻辑

首先定义Redis key的生成策略,基于反射实现

public interface KeyGenerateStrategy {    
   String generate(ProceedingJoinPoint jp);
}

定义Lock注解,用于Controller使用

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Lock {

    /**
     * 锁key前缀
     * @return
     */
    String prefix() default "commit_lock";

    /**
     * key失效时间
     * @return
     */
    int expire() default 1;

    /**
     * key失效默认单位
     * @return
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * Key的分隔符
     * @return
     */
    String splitChar() default ":";

    //操作错误提示
    String tips() default "系统正在处理请求,请勿重复提交";

}

定义LockField注解,基于ParamEntity实现参数接收,该注解用于entity内的field

@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LockField {}

具体实现

@Component
public class SimpleKeyGenerater implements KeyGenerateStrategy {

@Override
public String generate(ProceedingJoinPoint jp) {

    MethodSignature signature = (MethodSignature) jp.getSignature();
    Method method = signature.getMethod();
    Lock lockAnnotation = method.getAnnotation(Lock.class);
    final Object[] args = jp.getArgs();

    StringBuilder builder = new StringBuilder();
    final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
    for (int i = 0; i < parameterAnnotations.length; i++) {
        final Object object = args[i];
        if (object instanceof BaseRequestParam) {
            BaseRequestParam param = (BaseRequestParam)object;
            final Field[] fields = param.getRequestInfo().getClass().getDeclaredFields();
            Arrays.stream(fields).forEach(field -> {
                //获取被LockField注解的参数
                final LockField annotation = field.getAnnotation(LockField.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    builder.append(lockAnnotation.splitChar()).append(ReflectionUtils.getField(field, param.getRequestInfo()));
                }

            });
        } else {
            final Field[] fields = object.getClass().getDeclaredFields();
            Arrays.stream(fields).forEach(field -> {
                //获取被LockField获取的参数
                final LockField annotation = field.getAnnotation(LockField.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    builder.append(lockAnnotation.splitChar()).append(ReflectionUtils.getField(field, object));
                }
            });
        }
    }

    return lockAnnotation.prefix() + lockAnnotation.splitChar() + DigestUtils.md5Hex(builder.toString());
}

}

定义切面

@Aspect
@Configuration
public class CommmitLockAspect {

    private final RedisUtil redisUtil;
    private final KeyGenerateStrategy cacheKeyGenerator;

    @Autowired
    public CommmitLockAspect(RedisUtil redisUtil, KeyGenerateStrategy cacheKeyGenerator) {
        this.redisUtil = redisUtil;
        this.cacheKeyGenerator = cacheKeyGenerator;
    }

    @Around("@annotation(xx.xx.xx.live.lock.Lock)") //切入使用Lock注解的函数
    public Object interceptor(ProceedingJoinPoint jp) {
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Method method = signature.getMethod();
        Lock lock = method.getAnnotation(Lock.class);

        //生成key
        final String lockKey = cacheKeyGenerator.generate(jp);
        //setIfAbsent key
        final boolean success = redisUtil.setIfAbsent(lockKey,System.currentTimeMillis() + "",lock.expire(),lock.timeUnit());
        //如果键已存在,则不执行后续逻辑,返回错误
        if (!success) {
            return ResponseData.error(ResponseEnum.FORBIDDEN,lock.tips());
        }
        try {
            return jp.proceed();
        } catch (Throwable ctx) {
            throw new RuntimeException("unknow error");
        }
    }

}

使用


@Lock(expire = 2, tips = "系统正在处理,请勿频繁点击")
@PostMapping("/createMeetingRoom")
public ResponseData<XXXXXX> createRoom(@Valid @RequestBody AppRequestParam<CreateXXXXParam> param) throws Exception {}

接收request参数的实体类

@Data
@NoArgsConstructor
public class CreatexxxxParam {

    @LockField  //此处使用LockField注解
    private String title;

    @LockField
    @NotNull(message = "cant be null")
    private Integer type;

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。