简单注解实现表单重复提交处理

1.控制层

@ApiOperation(value = "学生信息保存")
//重复提交控制
@StopFormRepeatSubmitAnnotation("student:save")
@StopFormRepeatSubmitAnnotation(value = "student:save",second = 12)//自定义秒数
@PostMapping("/save")
public R<Boolean> save(@RequestBody @Valid Student student) {
    //保存
    return R.ok();
}

2.注解

package com.jiayou.internethospital.common.extapi.annotation;

import java.lang.annotation.*;

/**
 * <p>
 * Api接口幂等性(防止表单重复提交)注解
 * </p>
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface StopFormRepeatSubmitAnnotation {

	/**
	 * 描述
	 *
	 * @return {String}
	 */
	String value() default "";

	/**
	 * 缓存秒数
	 * @return {Integer}
	 */
	int second() default 30;
}

3.注解AOP拦截器

package test;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.redis.core.RedisTemplate;

import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.concurrent.TimeUnit;


/**
 * <p>
 * Api接口幂等性(防止表单重复提交)注解AOP拦截器
 * </p>
 */
@SuppressWarnings("unchecked")
@Slf4j
@Aspect
@AllArgsConstructor
public class StopFormRepeatSubmitInterceptor {


    private final RedisTemplate redisTemplate;

    @SneakyThrows
    @Around("@annotation(stopFormRepeatSubmitAnnotation)")
    public Object apiAopIdempotentInterceptor(ProceedingJoinPoint proceedingJoinPoint, StopFormRepeatSubmitAnnotation stopFormRepeatSubmitAnnotation) {
        Object proceed = null;

        //获取需要执行幂等注解方法参数、列如 @StopFormRepeatSubmitAnnotation("testExpApi")
        String extApi = stopFormRepeatSubmitAnnotation.value();
        int second = stopFormRepeatSubmitAnnotation.second();
        String userId = getUserId();

        //判断获取用户ID是否为空
        if (StrUtil.isEmpty(userId)) {
            HttpServletResponse response = HttpUtils.getHttpServletResponse();
            HttpResultUtils.responseError(response, ResultEnum.TOKEN_LOSE_EFFICACY.getCode(), ResultEnum.TOKEN_LOSE_EFFICACY.getMessage());
            return null;
        }

        //组装幂等性唯一key
        String extApiKey = extApi + getUserId();

        //判断redis缓存中是否存在key、存在key需要抛出异常提示
        if (!findRepectKey(extApiKey)) {
            HttpServletResponse response = HttpUtils.getHttpServletResponse();
            HttpResultUtils.responseError(response, ResultEnum.REPECT_SUBMIT.getCode(), ResultEnum.REPECT_SUBMIT.getMessage());
            return null;
        }
        //满足条件将其放行
        try {
            proceed = proceedingJoinPoint.proceed();
        } catch (Throwable e) {
            log.error("stopFormRepeatSubmitInterceptor :{}", ExceptionUtil.stacktraceToString(e));
            //将key从Redis中删除
            redisTemplate.delete(extApiKey);
            //调用异常工具类抛出异常
            ExtApiExceptionUtils.throwThrowableException(e);
        } finally {
            //将key从Redis中删除
            redisTemplate.delete(extApiKey);
        }
        return proceed;
    }

    /**
     * 1.在调用接口之前获取用户ID
     * 2.根据当前用户ID查询Redis缓存中是否有幂等性key、如果key不存在返回true,进入Controller方法执行api
     * 3.如果幂等性key在Redis缓存中,抛出异常提示客户不能重复提交
     *
     * @param extApiKey : 更新接口唯一key,由方法名+userId组成
     * @param second 缓存秒数
     * @return 如果Redis中不存在该key返回true、否则返回false,为重复提交表单
     */
    private Boolean findRepectKey(String extApiKey,int second) {
        final Object objectValueByKey = redisTemplate.opsForValue().get(extApiKey);
        if (Objects.isNull(objectValueByKey)) {
            //设置幂等性key、过期时间为30s
            redisTemplate.opsForValue().set(extApiKey, extApiKey, second, TimeUnit.SECONDS);
            return Boolean.TRUE;
        }
        //保证每个接口对应的repectKey 只能访问一次,保证接口幂等性问题
        return Boolean.FALSE;
    }

    /**
     * 获取用户ID
     *
     * @return 返回当前userId
     */
    private String getUserId() {
        SystemUser user = SecurityUtils.getUser().getId();
        if (Objects.isNull(user)) {
            return null;
        }
        return user.getId().toString();
    }
}

4.其他

4.1HttpUtils请求工具类

package test;

import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.jiayou.internethospital.common.core.entity.HttpResult;
import lombok.experimental.UtilityClass;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/**
 * <p>
 * HttpUtils请求工具类
 * </p>
 */
@UtilityClass
public class HttpUtils {

    /**
     * 获取HttpServletRequest对象
     *
     * @return
     */
    public HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }

    /**
     * 获取HttpServletResponse对象
     *
     * @return
     */
    public HttpServletResponse getHttpServletResponse() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    }

}

4.2 ResultEnum 返回结果枚举工具类

package test;
import lombok.Getter;

/**
 * <p>
 * 返回结果枚举工具类
 * </p>
 */
@Getter
public enum ResultEnum {
    REPECT_SUBMIT(4, "系统正在处理、请勿重复提交!"),
    TOKEN_LOSE_EFFICACY(10, "当前会话失效、请退出重登录再试"),
    ;

    private Integer code;

    private String message;

    ResultEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值