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

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

后台解决方案:   使用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. Oracle之集合运算

    交集 INTERSECT :取两个查询结果集的共有部分 SELECT DEPTNO FROM EMP INTERSECT SELECT DEPTNO FROM DEPT;   补集 MINUS :用第 ...

  2. java 执行shell命令遇到的坑

    正常来说java调用shell命令就是用 String[] cmdAry = new String[]{"/bin/bash","-c",cmd} Runtim ...

  3. omnet++:官方文档翻译总结(三)

    翻译总结自:Turning it Into a Real Network - OMNeT++ Technical Articles 接官方文档翻译总结(二),本节主要是真实网络的搭建 Part 4 - ...

  4. 携程applo配置

    1.官网文档 https://github.com/ctripcorp/apollo/wiki/%E5%88%86%E5%B8%83%E5%BC%8F%E9%83%A8%E7%BD%B2%E6%8C% ...

  5. 虚拟地址和物理地址(MMU)以及cache

    最近非常忙,博客很乱也没有更新,这里随便记录点东西,周末有空整理下. cache是一个与CPU很近的高速存储器, 作用:提高内存的访问读写速度 cache属性是指对这部分虚拟地址的读写是使用cache ...

  6. Azure DevOps 介绍

    伴随着敏捷的遍地开花,如今各个开发团队越来越希望可以实现敏捷在自己团队内的落地,但是往往单纯的依赖人力难以实现敏捷的各个环节的管理, 大家开始渐渐的意识到,为了按时交付软件产品和服务,开发和运营工作必 ...

  7. jmeter之如何减负-实现稳定超高并发测试(性能调优)之正确添加监听器

    jmeter之如何减负-实现稳定超高并发测试(性能调优)在测试过程中,初学者使用工具不当,添加众多监控组件,非常想看到实时报告,跑不了一会,jmeter就卡死,只得重启 下面来总结下如何正确使用jme ...

  8. (七)React Ant Design Pro + .Net5 WebApi:后端环境搭建-日志、异常处理

    一.日志 日志具有帮助开发者快速的定位问题,记录各种信息,配合其他分析框架使用等等功能,收集日志的各类框架如:Log4net.NLog.Exceptionless.Serilog等等,百度或园子里介绍 ...

  9. Airtest安装与简介

    一:简介 什么是Airtest 网易的airtest其实是个测试套件,由Airtest框架.poco框架.airtestIDE 组成. Airtest框架 基于图像识别的自动化测试框架,是网易自己团队 ...

  10. 大数据教程-01HDFS的基本组成和原理

    一 Hadoop历史背景 起源于2003年谷歌的Google File System相关论文,随后Doug Cutting(我们下面就叫他切哥吧)基于GFS的论文实现了分布式文件系统,并把它命名为ND ...