SpringBoot系列19-防止重复请求,重复表单提交超级简单注解的实现之四(终极版II)

猿份哥 4月前 ⋅ 246 阅读 ⋅ 0 个赞

前言:

根据最新spring boot:2.5.0版本和在《SpringBoot防止重复请求,重复表单提交超级简单的注解实现之四(终极版I)》之上化繁为简抽取更实用的代码,新增超时机制

防重复提交业务流程图如下

在这里插入图片描述

1.简化DuplicateSubmitToken.java代码,只留下标记接口,新增超时设置接口

/**
 * @author 猿份哥
 * @description 防止表单重复提交注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DuplicateSubmitToken {
    /**
     * 保存重复提交标记 默认为需要保存
     */
    boolean save() default true;

    /**
     * 重复失效时间单位毫秒,默认5000毫秒
     * @return
     */
    long  timeOut() default 5000 ;
}

2.改造DuplicateSubmitAspect.java新增超时判断代码

/**
 * @author 猿份哥
 * @description 防止表单重复提交拦截器
 */
@Aspect
@Component
@Slf4j
public class DuplicateSubmitAspect {
    public static final String DUPLICATE_TOKEN_KEY = "duplicate_token_key";

    @Pointcut("execution(public * com.yuanfenge.springboot.duplicatesubmit.controller..*(..))")

    public void webLog() {
    }

    @Before("webLog() && @annotation(token)")
    public void before(final JoinPoint joinPoint, DuplicateSubmitToken token) throws DuplicateSubmitException {
        if (token != null) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();

            boolean isSaveSession = token.save();
            if (isSaveSession) {
                String key = getDuplicateTokenKey(joinPoint);
                Object t = request.getSession().getAttribute(key);
                if (null == t) {
                    createKey(request, key);
                } else if (valid(t,token.timeOut())){
                    throw new DuplicateSubmitException(TextConstants.REQUEST_REPEAT);
                } else {
                    createKey(request, key);
                }
            }

        }
    }

    private void createKey(HttpServletRequest request, String key) {
        String uuid = UUIDUtil.randomUUID();
        long now = System.currentTimeMillis();
        String value = uuid + "_" + now;
        request.getSession().setAttribute(key, value);
        log.info("token-key={};token-value={}",key, value);
    }

    /**
     * 是否超时
     * @param t
     * @return
     */
    private boolean valid(Object t, long timeOut) {
        String token = t.toString();
        String[] arr = token.split("_");
        long before = Long.parseLong(arr[1]);
        long now = System.currentTimeMillis();
        if (now-before<timeOut){
            return true;
        }
        return false;
    }

    /**
     * 获取重复提交key
     * @param joinPoint
     * @return
     */
    public String getDuplicateTokenKey(JoinPoint joinPoint) {

        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.asList(joinPoint.getArgs()).stream().map(i -> String.valueOf(i)).collect(Collectors.joining());
        StringBuilder key = new StringBuilder(DUPLICATE_TOKEN_KEY);
        key.append("_").append(methodName).append(args);
        return key.toString();
    }

    /**
     * 异常
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "webLog()&& @annotation(token)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e, DuplicateSubmitToken token) {
        if (null != token
                && e instanceof DuplicateSubmitException == false) {
            //处理重复提交本身之外的异常
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            boolean isSaveSession = token.save();
            //获得方法名称
            if (isSaveSession) {
                String key = getDuplicateTokenKey(joinPoint);
                Object t = request.getSession().getAttribute(key);
                if (null != t) {
                    //方法执行完毕移除请求重复标记
                    request.getSession(false).removeAttribute(key);
                    log.info("异常情况--移除标记!");
                }
            }
        }
    }
}

3.TestController.java测试:包含restful请求,get请求,post请求


/**
 * @author 猿份哥
 * @description
 */
@RestController
public class TestController {

    @DuplicateSubmitToken
    @RequestMapping(value = "/restful/{num}", method = RequestMethod.GET)
    public Map<String, Object> restful(@PathVariable(value = "num") int num) throws Exception {
        Map<String, Object> map=new HashMap<>();
        if (num == 2) { //手动抛个异常
            throw new Exception("====system exception haha !===");
        }
        map.put("welcome","hello word !");
        map.put("method","restful:num="+num);
        return map;
    }

    @DuplicateSubmitToken
    @RequestMapping(value = "/getParam", method = RequestMethod.GET)
    public Map<String, Object> getParam(@RequestParam(value = "num") int num) throws Exception {
        Map<String, Object> map=new HashMap<>();
        if (num == 2) {
            throw new Exception("====system exception haha !===");
        }
        map.put("welcome","hello word !");
        map.put("method","get带参数:num="+num);
        return map;
    }

    @DuplicateSubmitToken
    @RequestMapping(value = "/get", method = RequestMethod.GET)
    public Map<String, Object> get() throws Exception {
        Map<String, Object> map=new HashMap<>();
        map.put("welcome","hello word !");
        map.put("method","get无参");
        return map;
    }

    /**
     * post请求方式
     * 设置30秒内不允许重复请求
     * @param num
     * @return
     * @throws Exception
     */
    @DuplicateSubmitToken(timeOut = 30*1000)
    @RequestMapping(value = "/post", method = RequestMethod.POST)
    public Map<String, Object> post(@RequestParam(value = "num") int num) throws Exception {
        Map<String, Object> map=new HashMap<>();
        map.put("welcome","hello word !");
        map.put("method","post:num="+num);
        return map;
    }
}

4.浏览器测试

http://localhost:8080/restful/1

http://localhost:8080/get?num=1

http://localhost:8080/getParam?num=1

http://localhost:8080/post

在这里插入图片描述


全部评论: 0

    我有话说:

    spring boot系列4-定时任务-springboot自带scheduled超级简单

    需求:创建一个每天凌晨0点执行定时任务1.创建任务 /** * @author 天空蓝蓝 */ @Slf4j @EnableScheduling @Component public class

    SpringBoot系列16-Spring boot2x快速整合swagger2(Open Api3注解

    方式和注解方式生成HTTP请求文档代码各有优...

    Spring Boot系列1-helloword

      使用springboot简单轻松创建helloword SpringBoot系列1-helloword 关于springboot这是摘自官方一段话 Spring Boot

    SpringBoot系列15-mysql-multiple-data-sources1

    springboot 多数据源一个简单示例 多数据源分包加载 新建数据库test1和tbl_user CREATE TABLE `tbl_user` ( `id` int(11) NOT

    SpringBoot系列17-统一异常处理(包含简单JSR303参数校验)

    原文链接:https://www.lskyf.com/post/211 方法1.通过ControllerAdvice实现+简单JSR303参数校验实现 1.1 加入依赖 <

    SpringBoot系列10-文件上传

    文章目录 1.先来最简单 2.设置文件大小,请求大小 3.多文件上传 怎样使用最简单方式上传文件,如何上传多个文件呢 先来最简单 pom.xml文件引入依赖文件 <

    Spring Boot系列5-定时任务-springboot整合quartz实现动态定时任务

    MyJob实现Job接口,重写execute方法在里面操作我们要执行业务逻辑。 @Slf4j public class MyJob implements Job { @Autowired

    Spring Boot系列8-使用jasypt加密配置文件内容简单

    加密内容是username和pwd 5.将加密...

    Spring Boot系列7-SpringBoot+mybatis+druid+TypeHandler

    介绍在SpringBoot中集成mybatis和druid以及自定义TypeHandler 创建数据库 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- --------------------...

    我体重复胖中得到经验

    你用多贵口红别人不一定知道,但是你胖瘦别人一眼就看得出来。 减肥成功 在去年之前我有胖过年左右时间,就是130以上140以下体重,2017开始健身,喜欢跑步,就是任性跑,不做拉伸。结果人还

    转 idea最新免费注册使用步骤

    最近在安装Intellij idea,社区版本比旗舰版本少了很多东西,曲曲折折终于找到了学生可以免费使用旗舰方法: 以下步骤来自Lenyo Lee更新: JetBrains开发工具免费提供学生

    SpringBoot系列9-使用jasypt自定义stater运行时动态传入加密密码

    文章目录 1.新建springboot-encryption-configuration项目实现stater 2.pom文件引入jasypt 3.在resources/support/下配置

    加入公众号
    加入公众号