• 保证接口幂等性,表单重复提交

前台解决方案:
提交后按钮禁用、置灰、页面出现遮罩

后台解决方案:   使用token,每个token只能使用一次
1.在调用接口之前生成对应的Token,存放至redis

2.在调用接口时,将生成的令牌放入请求request中

3.接口提交的时候获取对应的令牌,如果能够从redis中获得该令牌(获取后将当前令牌删除),
则继续执行访问的业务逻辑

4.接口提交的时候获取对应的令牌,如果获取不到改令牌,则直接返回请勿提交

工程源码:https://github.com/youxiu326/sb_more_submit

自定义注解

ApiToken注解用于将token保存至request,用于页面取token

ApiRepeatSubmit注解用于标明改方法需要验证token才能提交

package com.huarui.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 生成token注解
*/
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiToken {
}

ApiToken.java

package com.huarui.util;

import com.huarui.common.ConstantUtils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @功能描述 防止重复提交标记注解
*/
@Target(ElementType.METHOD) // 作用到方法上
@Retention(RetentionPolicy.RUNTIME)// 运行时有效
public @interface ApiRepeatSubmit {
ConstantUtils value();
}

ApiRepeatSubmit.java

package com.huarui.common;

/**
* 【定义从哪里取Token的枚举类】
* head 即从请求头中取token,即客户端将token放入请求头来请求后端数据
* body 即直接从请求体中取token
*/
public enum ConstantUtils {
BOOD,HEAD
}

ConstantUtils.java

spring.thymeleaf.cache=false

spring.redis.host=youxiu326.xin
spring.redis.port=6379

application.properties

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.19.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.huarui</groupId>
<artifactId>sb_more_submit</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sb_more_submit</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
</properties> <dependencies> <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

pom.xml

切面拦截,

package com.huarui.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.UUID;
import java.util.concurrent.TimeUnit; /**
* redis工具类
*/
@Component
public class RedisTokenUtils { private long timeout = 2;//过期时间 @Autowired
private RedisTemplate redisTemplate; /**
* 获取Token 并将Token保存至redis
* @return
*/
public String getToken() {
String token = "token_"+ UUID.randomUUID();
redisTemplate.opsForValue().set(token,token,timeout, TimeUnit.MINUTES);
return token;
} /**
* 判断Token是否存在 并且删除Token
* @param tokenKey
* @return
*/
public boolean findToken(String tokenKey){
String token = (String) redisTemplate.opsForValue().get(tokenKey);
if (StringUtils.isEmpty(token)) {
return false;
}
// token 获取成功后 删除对应tokenMapstoken
redisTemplate.delete(tokenKey);
return true;
} }

RedisTokenUtils.java

package com.huarui.aop;

import javax.servlet.http.HttpServletRequest;
import com.huarui.common.ConstantUtils;
import com.huarui.util.ApiToken;
import com.huarui.util.ApiRepeatSubmit;
import com.huarui.util.RedisTokenUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.concurrent.TimeUnit; /**
* @功能描述 aop解析注解
*/
@Aspect
@Component
public class NoRepeatSubmitAop { private Log logger = LogFactory.getLog(getClass()); @Autowired
private RedisTokenUtils redisTokenUtils; /**
* 将token放入请求
* @param pjp
* @param nrs
*/
@Before("execution(* com.huarui.controller.*Controller.*(..)) && @annotation(nrs)")
public void before(JoinPoint pjp, ApiToken nrs){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
request.setAttribute("token", redisTokenUtils.getToken());
} /**
* 拦截带有重复请求的注解的方法
* @param pjp
* @param nrs
* @return
*/
@Around("execution(* com.huarui.controller.*Controller.*(..)) && @annotation(nrs)")
public Object arround(ProceedingJoinPoint pjp, ApiRepeatSubmit nrs) { try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest(); String token = null;
if (nrs.value() == ConstantUtils.BOOD){
//从请求体中取Token
token = (String) request.getAttribute("token");
}else if (nrs.value() == ConstantUtils.HEAD){
//从请求头中取Token
token = request.getHeader("token");
}
if (StringUtils.isEmpty(token)){
return "token 不存在";
}
if (!redisTokenUtils.findToken(token)){
return "请勿重复提交";
}
Object o = pjp.proceed();
return o;
} catch (Throwable e) {
e.printStackTrace();
logger.error("验证重复提交时出现未知异常!");
return "{\"code\":-889,\"message\":\"验证重复提交时出现未知异常!\"}";
} } }
package com.huarui.controller;

import com.huarui.common.ConstantUtils;
import com.huarui.util.ApiRepeatSubmit;
import com.huarui.util.ApiToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; @Controller
public class TestController { /**
* 进入页面
* @return
*/
@GetMapping("/")
@ApiToken
public String index(){
return "index";
} /**
* 测试重复提交接口
* 将Token放入请求头中
* @return
*/
@RequestMapping("/test")
@ApiRepeatSubmit(ConstantUtils.HEAD)
public @ResponseBody String test() {
return ("程序逻辑返回");
} }

前端页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<base th:href="${#httpServletRequest.getContextPath()+'/'}">
<meta charset="UTF-8">
<title>测试表单重复功能</title>
</head>
<body> <td colspan="1"><button type="button" onclick="add()">加购</button></td> </body> <script src="/jquery-1.11.3.min.js"></script>
<script th:inline="javascript"> function add(){
//取得token参数
var token = [[${token}]];
console.log("获取到的token:" + token);
$.ajax({
type: 'POST',
url: "/test",
data: {},
headers: {
"token":token,
},
// dataType: "json",
success: function(response){
alert(response);
},
error:function(response){
alert(response);
console.log(response);
}
});
} </script> </html>

工程源码:https://github.com/youxiu326/sb_more_submit

java后端使用token处理表单重复提交的更多相关文章

  1. PHP简单利用token防止表单重复提交

    <?php /* * PHP简单利用token防止表单重复提交 * 此处理方法纯粹是为了给初学者参考 */ session_start(); function set_token() { $_S ...

  2. PHP生成token防止表单重复提交

    .提交按钮置disabled 当用户提交后,立即把按钮置为不可用状态.这种用js来实现. 提交前代码如下: $()  {  $exec="insert into student (user_ ...

  3. PHP简单利用token防止表单重复提交(转)

    <?php/* * PHP简单利用token防止表单重复提交 */function set_token() { $_SESSION['token'] = md5(microtime(true)) ...

  4. php通过token验证表单重复提交

    PHP防止重复提交表单 2016-11-08 轻松学PHP 我们提交表单的时候,不能忽视的一个限制是防止用户重复提交表单,因为有可能用户连续点击了提交按钮或者是攻击者恶意提交数据,那么我们在提交数据后 ...

  5. AOP+Token防止表单重复提交

    表单重复提交: 由于用户误操作,多次点击表单提交按钮 由于网速等原因造成页面卡顿,用户重复刷新提交页面 避免表单重复提交的方式: 1.页面上的按钮做防重复点击操作 2.在数据库中可以做唯一约束 3.利 ...

  6. PHP使用token防止表单重复提交的方法

    本文实例讲述了PHP使用token防止表单重复提交的方法.分享给大家供大家参考,具体如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2 ...

  7. token防止表单重复提交

    出现表单重复提交的三种情况: 一.服务器响应缓慢,用户多次点击提交按钮. 二.提交成功后刷新页面. 三.提交成功后返回表单页面再次点击提交. package com.jalja.token; impo ...

  8. java struts2入门学习--防止表单重复提交.OGNL语言学习

    一.知识点回顾 防止表单重复提交核心思想: 客户端和服务器端和写一个token,比较两个token的值相同,则非重复提交;不同,则是重复提交. 1.getSession三种方式比较: request. ...

  9. struts2 自带的 token防止表单重复提交拦截器

    在struts2中,我们可以利用struts2自带的token拦截器轻松实现防止表单重复提交功能! 1. 在相应的action配置中增加:  <interceptor-ref name=&quo ...

随机推荐

  1. selenium+python自动化105 - selenium 如何在已打开的浏览器上继续运行自动化脚本?

    前言 使用selenium 做web自动化的时候,经常会遇到这样一种需求,是否可以在已经打开的浏览器基础上继续运行自动化脚本? 这样前面的验证码登录可以手工点过去,后面页面使用脚本继续执行,这样可以解 ...

  2. Docker安装与基本命令使用

    1. 卸载旧版本 Docker在CentOS上的安装 官方文档:https://docs.docker.com/engine/install/centos/ sudo yum remove docke ...

  3. 关于stationary 和non-stationary signals 的区别和定义

    结论:实际上在生活中是没有静态信号(stationary signals)的.而我们之所以把随机信号分为stationary and non-stationary 完全是根据信号产生的特征(chara ...

  4. echarts可视显示已租未租

    1:菜鸟引入js <!-- 引入 echarts.js --> <script src="https://cdn.staticfile.org/echarts/4.3.0/ ...

  5. i++ 反编译码

    1.特点: 操作数栈,主要用于保存计算过程中的结果,同时作为集计算过程中变量临时的存储空间. 操作数栈就是JVM执行引擎的一个工作区,当方法执行开始,一个新栈帧也会随之被创建,这个方法的操作数栈是空的 ...

  6. BBS项目分布搭建二(个人站点相关)

    BBS项目分布搭建二 1. 首页详情补充 # 在home.html文件中 body标签内补充: <div class="container-fluid"> <di ...

  7. Docker——容器数据卷

    为什么需要容器数据卷 角度:遇到问题,尝试以朴素的道理解决问题.问题复杂化,解决的方式也变得复杂 问题的提出:docker将应用和环境打包成一个镜像,但是对于容器内的数据,如果不进行外部的保存,那么当 ...

  8. Linux 环境下安装 Nexus 私服存储库

    镜像下载.域名解析.时间同步请点击阿里云开源镜像站 一.nexus私服存储库简介 Nexus 是一个强大的maven仓库管理器,它极大地简化了本地内部仓库的维护和外部仓库的访问.,还可以用来创建yum ...

  9. 数字IC笔试题-芯源

    前言 由于最近开始找数字IC的工作,所以准备多练笔试题,下面贴上芯源笔试题,来源微信公众号<数字IC打工人> 参考资源: 1. mu_guang_ 2.  李锐博恩 3. 长弓的坚持 4. ...

  10. 四旋翼中的PID调节方法 | betaflight固件如何调节PID

    roll横滚,pitch俯仰,yaw航向 一.PID的作用概述 1.P产生响应速度和力度,是I和D的基础 过小响应慢(虽然无震荡) 过大会产生振荡且不断发散 2.D抑制过冲和振荡,抵抗外界的突发干扰, ...