spring boot 学习(七)小工具篇:表单重复提交

注解 + 拦截器:解决表单重复提交

前言

学习 Spring Boot 中,我想将我在项目中添加几个我在 SpringMVC 框架中常用的工具类(主要都是涉及到 Spring AOP 部分知识)。比如,表单重复提交,?秒防刷新,全局异常捕抓类,IP黑名单(防爬虫设置)…………等等。接下来的时间,我尝试将这些框架整合到 Spring Boot 中(尽可能完成),毕竟项目开发中这些工具是非常有用的。

注意,这些工具基本上都是我以前在 github 之类开源平台找到的小工具类,作者的信息什么的许多都忘了。先说声不好意思了。若有相关信息,麻烦提醒一下~

介绍

这里就不详细介绍相应的知识了,主要提及有关涉及到的术语:

  • 拦截器
    Spring 拦截器有两种实现方法。一种是继承HandlerInterceptorAdapter,拥有preHandle(业务处理器处理请求之前被调用),postHandle(在业务处理器处理请求执行完成后,生成视图之前执行),afterCompletion(在完全处理完请求后被调用,可用于清理资源等)三个方法。
    另一种就是调用 Spring AOP 的方法来实现。而且,我觉得这种方法更加灵活方便,所以我比较经常使用这种方法。

  • AOP( AspectJ— 注解 风格)
    AOP 就是 Aspect Oriented Programming(面向方面编程)。
    1. 连接点(Joinpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点
    2. 前置通知(@Before):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
    3. 抛出异常后通知(@AfterThrowing):方法抛出异常退出时执行的通知
    附上:大神开涛的有关 Spring AOP 博客:http://jinnianshilongnian.iteye.com/blog/1474325

解决问题

什么是表单重复提交?

服务器认为是同一个表单,在短时间内重复(不止一次)提交,或者提交异常。比如,在服务器还没有响应前我们不断点击刷新网页上一个提交按钮,或者通过 ajax 不断对服务器发送请求报文!

防止情况
  1. 不通过正常路径访问页面表单;
  2. session 失效情况下提交表单;
  3. 短时间内不止一次提交表单。
解决方案

一般情况下,是在服务器利用 session 来防止这个问题的。
流程图:
这里写图片描述
1. 网页点击事件,网页提交发送申请;
2. 服务器收到申请,并产生令牌(Token),并存于 Session 中;
3. 服务器将令牌返回给页面,页面将令牌与表单真正提交给服务器。

这种就是 structs 的令牌方式。还有其他方法,就是重定向方法或设置页面过期(前端部分不太了解),不过还是感觉强制跳转不是特别友好,同时也不够灵活多用。

前期准备

新建一个 spring boot 项目(建议 1.3.X 以上版本)。
加入 aop 依赖,默认设置就行了:

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
正式开工
  • 注解类 Token.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Token {
    //生成 Token 标志
    boolean save() default false ;
    //移除 Token 值
    boolean remove() default false ;
}
  • 表单异常类 FormRepeatException.java
public class FormRepeatException extends RuntimeException {

    public FormRepeatException(String message){ super(message);}

    public FormRepeatException(String message, Throwable cause){ super(message, cause);}
}
  • 拦截器 TokenContract.java
    注意:@Aspect@Component两个注解!
@Aspect
@Component
public class TokenContract {

    private static final Logger logger = LoggerFactory.getLogger(TokenContract.class);

    @Before("within(@org.springframework.stereotype.Controller *) && @annotation(token)")
    public void testToken(final JoinPoint joinPoint, Token token){
        try {
            if (token != null) {
                //获取 joinPoint 的全部参数
                Object[] args = joinPoint.getArgs();
                HttpServletRequest request = null;
                HttpServletResponse response = null;
                for (int i = 0; i < args.length; i++) {
                    //获得参数中的 request && response
                    if (args[i] instanceof HttpServletRequest) {
                        request = (HttpServletRequest) args[i];
                    }
                    if (args[i] instanceof HttpServletResponse) {
                        response = (HttpServletResponse) args[i];
                    }
                }

                boolean needSaveSession = token.save();
                if (needSaveSession){
                    String uuid = UUID.randomUUID().toString();
                    request.getSession().setAttribute( "token" , uuid);
                    logger.debug("进入表单页面,Token值为:"+uuid);
                }

                boolean needRemoveSession = token.remove();
                if (needRemoveSession) {
                    if (isRepeatSubmit(request)) {
                        logger.error("表单重复提交");
                        throw new FormRepeatException("表单重复提交");
                    }
                    request.getSession(false).removeAttribute( "token" );
                }
            }

        } catch (FormRepeatException e){
            throw e;
        } catch (Exception e){
            logger.error("token 发生异常 : "+e);
        }
    }

    private boolean isRepeatSubmit(HttpServletRequest request) throws FormRepeatException {
        String serverToken = (String) request.getSession( false ).getAttribute( "token" );
        if (serverToken == null ) {
            //throw new FormRepeatException("session 为空");
            return true;
        }
        String clinetToken = request.getParameter( "token" );
        if (clinetToken == null || clinetToken.equals("")) {
            //throw new FormRepeatException("请从正常页面进入!");
            return true;
        }
        if (!serverToken.equals(clinetToken)) {
            //throw new FormRepeatException("重复表单提交!");
            return true ;
        }
        logger.debug("校验是否重复提交:表单页面Token值为:"+clinetToken + ",Session中的Token值为:"+serverToken);
        return false ;
    }
}
    @Token(save = true)
    @RequestMapping("/savetoken")
    @ResponseBody
    public String getToken(HttpServletRequest request, HttpServletResponse response){
        return (String) request.getSession().getAttribute("token");
    }

    @Token(remove = true)
    @RequestMapping("/removetoken")
    @ResponseBody
    public String removeToken(HttpServletRequest request, HttpServletResponse response){
        return "success";
    }

项目参考地址

参考项目地址:https://github.com/FunriLy/springboot-study/tree/master/%E6%A1%88%E4%BE%8B3

### PyCharm 打开文件显示不全的解决方案 当遇到PyCharm打开文件显示不全的情况时,可以尝试以下几种方法来解决问题。 #### 方法一:清理缓存并重启IDE 有时IDE内部缓存可能导致文件加载异常。通过清除缓存再启动程序能够有效改善此状况。具体操作路径为`File -> Invalidate Caches / Restart...`,之后按照提示完成相应动作即可[^1]。 #### 方法二:调整编辑器字体设置 如果是因为字体原因造成的内容显示问题,则可以通过修改编辑区内的文字样式来进行修复。进入`Settings/Preferences | Editor | Font`选项卡内更改合适的字号大小以及启用抗锯齿功能等参数配置[^2]。 #### 方法三:检查项目结构配置 对于某些特定场景下的源码视图缺失现象,可能是由于当前工作空间未能正确识别全部模块所引起。此时应该核查Project Structure里的Content Roots设定项是否涵盖了整个工程根目录;必要时可手动添加遗漏部分,并保存变更生效[^3]。 ```python # 示例代码用于展示如何获取当前项目的根路径,在实际应用中可根据需求调用该函数辅助排查问题 import os def get_project_root(): current_file = os.path.abspath(__file__) project_dir = os.path.dirname(current_file) while not os.path.exists(os.path.join(project_dir, '.idea')): parent_dir = os.path.dirname(project_dir) if parent_dir == project_dir: break project_dir = parent_dir return project_dir print(f"Current Project Root Directory is {get_project_root()}") ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值