通过Redis实现防止接口重复提交功能

发布于:2024-06-06 ⋅ 阅读:(131) ⋅ 点赞:(0)

本功能是在切面执行链基础上实现的功能,如果不知道切面执行链的同学,请看一下我之前专门介绍切面执行链的文章。

在SpringBoot项目中实现切面执行链功能-CSDN博客

1.定义防重复提交handler

/**
 * 重复提交handler
 *
 */
@AspectHandlerOrder
public class ResubmitAspectHandler implements AspectHandler {

    private StringRedisTemplate stringRedisTemplate;

    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean execute(ProceedingJoinPoint pjp) throws Exception {
        Method method = getMethod(pjp);
        if (!method.isAnnotationPresent(Resubmit.class)) {
        	return true;
        }
    	Resubmit annotation = method.getAnnotation(Resubmit.class);
    	long ttl = annotation.ttl();
    	String key = getKey();
    	String value = "1";
    	if (lock(key, value, ttl)) {
    		return true;
    	} 
		throw new BaseRuntimeException(ExceptionEnums.ERROR_10012.getCode(), "操作频率过高,请稍后再试!");
    }

    @Override
    public void afterCompletion(ProceedingJoinPoint pjp, Object response, Exception exception) {
        Method method = getMethod(pjp);
        if (method.isAnnotationPresent(Resubmit.class)) {
            unlock(getKey());
        }
    }

    /**
     * redis原子操作:如果key不存在就设置key:value
     *
     * @param key
     * @param value
     * @return true:设置成功拿到锁,false:设置失败未拿到锁
     */
    private boolean lock(final String key, final String value, final long ttl) {
        Boolean result = stringRedisTemplate.boundValueOps(key).setIfAbsent(value, Duration.ofSeconds(ttl));
        return result != null ? result : false;
    }

    /**
     * 解锁:删除key
     *
     * @param key
     */
    private void unlock(String key) {
        if (StringUtils.isNotBlank(key)) {
            stringRedisTemplate.delete(key);
        }
    }

    /**
     * 获取方法
     *
     * @param pjp
     * @return
     */
    private Method getMethod(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        return method;
    }

    /**
     * 获取key
     *
     * @return
     */
    private String getKey() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String url = request.getRequestURI();
        String httpMethod = request.getMethod();

        HttpHeader httpHeader = WebContext.getHttpHeader();
        String deviceId = httpHeader.getDevice_id();

        String key = RedisConstants.REDIS_RESUBMIT_KEY + httpMethod + url + ":" + deviceId;
        return key;
    }
}

2.定义防重复提交注解

/**
 * 防止重复提交
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Resubmit {

    /**
     * 存活时间(秒),当意外情况(例如锁定之后重启服务)
     * 未能执行解锁功能,redis将在${ttl}秒之后自动删除锁标志
     * 默认 10秒
     * @return
     */
    long ttl() default 10;

}

3.在配置类中注入防重复提交切面类

@Bean
public List<AspectHandler> apiAspectHandlers() {
   ResubmitAspectHandler resubmitAspectHandler = new ResubmitAspectHandler();
   resubmitAspectHandler.setStringRedisTemplate(stringRedisTemplate);

   return Arrays.asList(resubmitAspectHandler);
}

4.controller中应用防重复提交注解

@PostMapping("/release")
@Resubmit
public ApiResponse<?> insert(@RequestBody @Valid InsertAppRequestDTO req) {
    // 处理业务逻辑
}


网站公告

今日签到

点亮在社区的每一天
去签到