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

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

后台解决方案:   使用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. MySQL让人又爱又恨的多表查询

    1. 前言 在SQL开发当中,多表联查是绝对绕不开的一种技能.同样的查询结果不同的写法其运行效率也是千差万别. 在实际开发当中,我见过(好像还写过~)不少又长又臭的查询SQL,数据量一上来查个十几分钟 ...

  2. 【C# 异常处理】调试器 管理异常

    装载自:https://docs.microsoft.com/zh-cn/visualstudio/debugger/managing-exceptions-with-the-debugger?vie ...

  3. Codeforces Round #726 (Div.2) A-E1 题解

    A. Arithmetic Array 题目大意:一串数,求添加多少个非负整数后平均值为1 代码: //CF726A #include<bits/stdc++.h> using names ...

  4. Spring源码之七registerListeners()及发布订阅模式

    Spring源码之七registerListeners()及发布订阅模式 大家好,我是程序员田同学. 今天带大家解读refresh()方法中的registerListeners()方法,也就是我们经常 ...

  5. JAVA Object类方法

    目录 Object类详解 一.==和equals的对比 1.1 ==是一个比较运算符 1.2 equals方法 二.hashCode方法 三.toString方法 四.finalize方法 Objec ...

  6. php 23种设计模型 - 中介者模式

    中介者模式 中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性.这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护.中介者模式属于行 ...

  7. PHP pdf转png windows版本

    链接:https://pan.baidu.com/s/1Bli-2HkucRTYTeujkcsmjg&shfl=sharepset 提取码:2une 1.php_imagick扩展 (1)下载 ...

  8. tensorflow源码解析之framework-resource

    目录 什么是resource 如何使用resource 如何管理resource 常用resource 其它结构 关系图 涉及的文件 迭代记录 1. 什么是resource 我们知道,TF的计算是由设 ...

  9. NTFS权限概述

    NTFS权限概述 NTFS是我常见的一种磁盘格式,在Windows系统中使用广泛,它打破了FAT的局限性.在我使用ntfs格式分区的时候经常会涉及到ntfs权限设置问题,来帮助我们对文件的处理.那么什 ...

  10. 【基础】tail命令查看日志

    一.tail 命令介绍 tail 命令可以将文件指定位置到文件结束的内容写到标准输出. 如果你不知道tail命令怎样使用,可以在命令行执行命令tail --help就能看到tail命令介绍和详细的参数 ...