表单重复提价问题

rpc远程调用时候 发生网络延迟  可能有重试机制

MQ消费者幂等(保证唯一)一样

解决方案: token

令牌 保证唯一的并且是临时的  过一段时间失效

分布式: redis+token

注意在getToken() 这种方法代码一定要上锁  保证只有一个线程执行  否则会造成token不唯一

步骤 调用接口之前生成对应的 token,存放在redis中

调用接口的时候,将该令牌放到请求头中 (获取请求头中的令牌)

接口获取对应的令牌,如果能够获取该令牌 (将当前令牌删除掉),执行该方法业务逻辑

如果获取不到对应的令牌。返回提示“老铁 不要重复提交”

哈哈 如果别人获得了你的token 然后拿去做坏事,采用机器模拟去攻击。这时候我们要用验证码来搞定。

从代码开发者的角度看,如果每次请求都要 获取token  然后进行一统校验。代码冗余啊。如果一百个接口 要写一百次

所以采用AOP的方式进行开发,通过注解方式。

如果过滤器的话,所有接口都进行了校验。

框架开发:

自定义一个注解@  作为标记

如果哪个Controller需要进行token的验证加上注解标记

在执行代码时候AOP通过切面类中 写的 作用接口进行 判断,如果这个接口方法有 自定义的@注解  那么进行校验逻辑

校验结果 要么提示给用户 “请勿提交” 要么通过验证 继续往下执行代码

关于表单重复提交:

在表单有个隐藏域 存放token  使用  getParameter 去获取token 然后通过返回的结果进行校验

注意 获取token的这个代码 也是用AOP去解决,实现。 否则每个Controller类都写这段代码就冗余了。前置通知搞定

注解:

首先pom:

org.springframework.boot

spring-boot-starter-parent

2.0.0.RELEASE

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.1.1

mysql

mysql-connector-java

org.projectlombok

lombok

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-tomcat

org.apache.tomcat.embed

tomcat-embed-jasper

org.springframework.boot

spring-boot-starter-log4j

1.3.8.RELEASE

org.springframework.boot

spring-boot-starter-aop

commons-lang

commons-lang

2.6

org.apache.httpcomponents

httpclient

com.alibaba

fastjson

1.2.47

org.springframework.boot

spring-boot-starter-data-redis

javax.servlet

jstl

taglibs

standard

1.1.2

2、关于表单提交的注解的封装

importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;

@Target(value=ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)public @interfaceExtApiIdempotent {

String value();

}

AOP:

importjava.io.IOException;importjava.io.PrintWriter;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importorg.apache.commons.lang.StringUtils;importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Before;importorg.aspectj.lang.annotation.Pointcut;importorg.aspectj.lang.reflect.MethodSignature;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importcom.itmayeidu.ext.ExtApiIdempotent;importcom.itmayeidu.ext.ExtApiToken;importcom.itmayeidu.utils.ConstantUtils;importcom.itmayeidu.utils.RedisTokenUtils;importcom.itmayeidu.utils.TokenUtils;

@Aspect

@Componentpublic classExtApiAopIdempotent {

@AutowiredprivateRedisTokenUtils redisTokenUtils;//需要作用的类

@Pointcut("execution(public * com.itmayiedu.controller.*.*(..))")public voidrlAop() {

}//前置通知转发Token参数 进行拦截的逻辑

@Before("rlAop()")public voidbefore(JoinPoint point) {//获取并判断类上是否有注解

MethodSignature signature = (MethodSignature) point.getSignature();//统一的返回值

ExtApiToken extApiToken = signature.getMethod().getDeclaredAnnotation(ExtApiToken.class);//参数是注解的那个

if (extApiToken != null) { //如果有注解的情况

extApiToken();

}

}//环绕通知验证参数

@Around("rlAop()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throwsThrowable {

MethodSignature signature=(MethodSignature) proceedingJoinPoint.getSignature();

ExtApiIdempotent extApiIdempotent= signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);if (extApiIdempotent != null) { //有注解的情况 有注解的说明需要进行token校验

returnextApiIdempotent(proceedingJoinPoint, signature);

}//放行

Object proceed = proceedingJoinPoint.proceed(); //放行 正常执行后面(Controller)的业务逻辑

returnproceed;

}//验证Token 方法的封装

publicObject extApiIdempotent(ProceedingJoinPoint proceedingJoinPoint, MethodSignature signature)throwsThrowable {

ExtApiIdempotent extApiIdempotent= signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);if (extApiIdempotent == null) {//直接执行程序

Object proceed =proceedingJoinPoint.proceed();returnproceed;

}//代码步骤://1.获取令牌 存放在请求头中

HttpServletRequest request =getRequest();//value就是获取类型 请求头之类的

String valueType =extApiIdempotent.value();if(StringUtils.isEmpty(valueType)) {

response("参数错误!");return null;

}

String token= null;if (valueType.equals(ConstantUtils.EXTAPIHEAD)) { //如果存在header中 从头中获取

token = request.getHeader("token"); //从头中获取

} else{

token= request.getParameter("token"); //否则从 请求参数获取

}if(StringUtils.isEmpty(token)) {

response("参数错误!");return null;

}if (!redisTokenUtils.findToken(token)) {

response("请勿重复提交!");return null;

}

Object proceed=proceedingJoinPoint.proceed();returnproceed;

}public voidextApiToken() {

String token=redisTokenUtils.getToken();

getRequest().setAttribute("token", token);

}publicHttpServletRequest getRequest() {

ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

HttpServletRequest request=attributes.getRequest();returnrequest;

}public void response(String msg) throwsIOException {

ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

HttpServletResponse response=attributes.getResponse();

response.setHeader("Content-type", "text/html;charset=UTF-8");

PrintWriter writer=response.getWriter();try{

writer.println(msg);

}catch(Exception e) {

}finally{

writer.close();

}

}

}

订单请求接口:

importjavax.servlet.http.HttpServletRequest;importorg.apache.commons.lang.StringUtils;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importcom.itmayeidu.ext.ExtApiIdempotent;importcom.itmayeidu.utils.ConstantUtils;importcom.itmayeidu.utils.RedisTokenUtils;importcom.itmayeidu.utils.TokenUtils;importcom.itmayiedu.entity.OrderEntity;importcom.itmayiedu.mapper.OrderMapper;

@RestControllerpublic classOrderController {

@AutowiredprivateOrderMapper orderMapper;

@AutowiredprivateRedisTokenUtils redisTokenUtils;//从redis中获取Token

@RequestMapping("/redisToken")publicString RedisToken() {returnredisTokenUtils.getToken();

}//验证Token

@RequestMapping(value = "/addOrderExtApiIdempotent", produces = "application/json; charset=utf-8")

@ExtApiIdempotent(value=ConstantUtils.EXTAPIHEAD)publicString addOrderExtApiIdempotent(@RequestBody OrderEntity orderEntity, HttpServletRequest request) {int result =orderMapper.addOrder(orderEntity);return result > 0 ? "添加成功" : "添加失败" + "";

}

}

表单提交的请求接口:

importjavax.servlet.http.HttpServletRequest;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.RequestMapping;importcom.itmayeidu.ext.ExtApiIdempotent;importcom.itmayeidu.ext.ExtApiToken;importcom.itmayeidu.utils.ConstantUtils;importcom.itmayiedu.entity.OrderEntity;importcom.itmayiedu.mapper.OrderMapper;

@Controllerpublic classOrderPageController {

@AutowiredprivateOrderMapper orderMapper;

@RequestMapping("/indexPage")

@ExtApiTokenpublicString indexPage(HttpServletRequest req) {return "indexPage";

}

@RequestMapping("/addOrderPage")

@ExtApiIdempotent(value=ConstantUtils.EXTAPIFROM)publicString addOrder(OrderEntity orderEntity) {int addOrder =orderMapper.addOrder(orderEntity);return addOrder > 0 ? "success" : "fail";

}

}

utils:

redis:

importjava.util.concurrent.TimeUnit;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Component;

@Componentpublic classBaseRedisService {

@AutowiredprivateStringRedisTemplate stringRedisTemplate;public voidsetString(String key, Object data, Long timeout) {if (data instanceofString) {

String value=(String) data;

stringRedisTemplate.opsForValue().set(key, value);

}if (timeout != null) {

stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);

}

}publicObject getString(String key) {returnstringRedisTemplate.opsForValue().get(key);

}public voiddelKey(String key) {

stringRedisTemplate.delete(key);

}

}

常量:

public interfaceConstantUtils {static final String EXTAPIHEAD = "head";static final String EXTAPIFROM = "from";

}

mvc:

importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.ComponentScan;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.EnableWebMvc;importorg.springframework.web.servlet.view.InternalResourceViewResolver;importorg.springframework.web.servlet.view.JstlView;

@Configuration

@EnableWebMvc

@ComponentScan("com.too5.controller")public classMyMvcConfig {

@Bean//出现问题原因 @bean 忘记添加

publicInternalResourceViewResolver viewResolver() {

InternalResourceViewResolver viewResolver= newInternalResourceViewResolver();

viewResolver.setPrefix("/WEB-INF/jsp/");

viewResolver.setSuffix(".jsp");

viewResolver.setViewClass(JstlView.class);returnviewResolver;

}

}

redis操作token工具类:

importorg.apache.commons.lang.StringUtils;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;

@Componentpublic classRedisTokenUtils {private long timeout = 60 * 60; //超时时间

@AutowiredprivateBaseRedisService baseRedisService;//将token存入在redis

publicString getToken() {

String token= "token" +System.currentTimeMillis();

baseRedisService.setString(token, token, timeout); //key: token value: token 时间returntoken;

}public synchronize booleanfindToken(String tokenKey) { //从redis查询对应的token 防止没来得及删除 只有一个线程操作 其实redis已经可以防止了

String token=(String) baseRedisService.getString(tokenKey);if(StringUtils.isEmpty(token)) { //要么被被人使用过了 要么没有对应tokenreturn false;

}//token 获取成功后 删除对应tokenMapstoken

baseRedisService.delKey(token);return true; //保证每个接口对应的token只能访问一次,保证接口幂等性问题

}

}

tokenutils:

importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;importorg.apache.commons.lang.StringUtils;public classTokenUtils {private static Map tokenMaps = new ConcurrentHashMap();//1.什么Token(令牌) 表示是一个零时不允许有重复相同的值(临时且唯一)//2.使用令牌方式防止Token重复提交。//使用场景:在调用第API接口的时候,需要传递令牌,该Api接口 获取到令牌之后,执行当前业务逻辑,让后把当前的令牌删除掉。//在调用第API接口的时候,需要传递令牌 建议15-2小时//代码步骤://1.获取令牌//2.判断令牌是否在缓存中有对应的数据//3.如何缓存没有该令牌的话,直接报错(请勿重复提交)//4.如何缓存有该令牌的话,直接执行该业务逻辑//5.执行完业务逻辑之后,直接删除该令牌。//获取令牌

public static synchronizedString getToken() {//如何在分布式场景下使用分布式全局ID实现

String token = "token" +System.currentTimeMillis();//hashMap好处可以附带

tokenMaps.put(token, token);returntoken;

}//generateToken();

public static booleanfindToken(String tokenKey) {//判断该令牌是否在tokenMap 是否存在

String token =(String) tokenMaps.get(tokenKey);if(StringUtils.isEmpty(token)) {return false;

}//token 获取成功后 删除对应tokenMapstoken

tokenMaps.remove(token);return true;

}

}

实体类:

public classOrderEntity {private intid;privateString orderName;privateString orderDes;public intgetId() {returnid;

}public void setId(intid) {this.id =id;

}publicString getOrderName() {returnorderName;

}public voidsetOrderName(String orderName) {this.orderName =orderName;

}publicString getOrderDes() {returnorderDes;

}public voidsetOrderDes(String orderDes) {this.orderDes =orderDes;

}

}

public classUserEntity {privateLong id;privateString userName;privateString password;

publicLong getId() {returnid;

}

public voidsetId(Long id) {this.id =id;

}

publicString getUserName() {returnuserName;

}

public voidsetUserName(String userName) {this.userName =userName;

}

publicString getPassword() {returnpassword;

}

public voidsetPassword(String password) {this.password =password;

}@OverridepublicString toString() {return "UserEntity [id=" + id + ", userName=" + userName + ", password=" + password + "]";

}

}

Mapper:

importorg.apache.ibatis.annotations.Insert;importcom.itmayiedu.entity.OrderEntity;public interfaceOrderMapper {

@Insert("insert order_info values (null,#{orderName},#{orderDes})")public intaddOrder(OrderEntity OrderEntity);

}

public interfaceUserMapper {

@Select(" SELECT * FROM user_info where userName=#{userName} and password=#{password}")publicUserEntity login(UserEntity userEntity);

@Insert("insert user_info values (null,#{userName},#{password})")public intinsertUser(UserEntity userEntity);

}

yml:

spring:

mvc:

view:

# 页面默认前缀目录

prefix: /WEB-INF/jsp/

# 响应页面默认后缀

suffix: .jsp

spring:

datasource:

url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8

username: root

password: root

driver-class-name: com.mysql.jdbc.Driver

test-while-idle: true

test-on-borrow: true

validation-query: SELECT 1 FROM DUAL

time-between-eviction-runs-millis: 300000

min-evictable-idle-time-millis: 1800000

redis:

database: 1

host: 106.15.185.133

port: 6379

password: meitedu.+@

jedis:

pool:

max-active: 8

max-wait: -1

max-idle: 8

min-idle: 0

timeout: 10000

domain:

name: www.toov5.com

启动类:

@MapperScan(basePackages = { "com.tov5.mapper"})

@SpringBootApplication

@ServletComponentScanpublic classAppB {public static voidmain(String[] args) {

SpringApplication.run(AppB.class, args);

}

}

总结:

核心就是

自定义注解

controller中的方法注解

aop切面类判断对象是否有相应的注解 如果有 从parameter或者header获取参数 进行校验

mysql的api框架_API接口幂等性框架设计相关推荐

  1. 接口幂等性的设计之————redis分布式锁的应用

    接口幂等性的设计之----redis分布式锁的应用 在集群分布式机器部署的前提下,接口在相同数据高并发的情况下如果没有唯一索引的情况下,可能会有一些问题. 比如: 插入或更新商品的接口,如果没有则插入 ...

  2. python接口自动化实战(框架)_python接口自动化框架实战

    python接口测试的原理,就不解释了,百度一大堆. 先看目录,可能这个框架比较简单,但是麻雀虽小五脏俱全. 各个文件夹下的文件如下: 一.理清思路 我这个自动化框架要实现什么 1.从excel里面提 ...

  3. 接口幂等性设计与实现

    幂等 幂等(idempotent.idempotence), 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同.幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得 ...

  4. Python语言+pytest框架+allure报告+log日志+yaml文件+mysql断言实现接口自动化框架

    目录 前言 实现功能 目录结构 依赖库 安装教程 接口文档 如何创建用例 创建用例步骤 用例中相关字段的介绍 如何发送get请求 如何发送post请求 如何测试上传文件接口 上传文件接口,即需要上传文 ...

  5. python api开发用什么框架_python+requests接口自动化框架

    为什么要做接口自动化框架 1.业务与配置的分离 2.数据与程序的分离:数据的变更不影响程序 3.有日志功能,实现无人值守 4.自动发送测试报告 5.不懂编程的测试人员也可以进行测试 正常接口测试的流程 ...

  6. API接口自动化测试框架搭建(一)-总体需求

    (一)-总体需求 1 实现目的 2 功能需求 3 其他要求 4 适用人员 5 学习周期 6 学习建议 7 内容直达 8 反馈联系 1 实现目的 API接口自动化测试,主要针对http接口协议: 便于回 ...

  7. 一篇文章教你详细搭建API接口自动化测试框架

    目录 1 需求整理 1.1 实现目的 1.2 功能需求 1.3 其他要求 1.4 适用人员 1.5 学习周期 1.6 学习建议 2 详细设计 2.1 需求分析 2.2 技术栈 3 框架设计 3.1 框 ...

  8. python+requests接口自动化测试框架实例详解教程(米兔888)

    来源:https://my.oschina.net/u/3041656/blog/820023 源码:https://pan.baidu.com/s/1lgIEToiczTvvjy--p-N20g 提 ...

  9. python3接口自动化测试_【python3+request】python3+requests接口自动化测试框架实例详解教程...

    前段时间由于公司测试方向的转型,由原来的web页面功能测试转变成接口测试,之前大多都是手工进行,利用postman和jmeter进行的接口测试,后来,组内有人讲原先web自动化的测试框架移驾成接口的自 ...

最新文章

  1. 均值极差图控制上下限_SPC之I-MR控制图
  2. Apache Karaf配置远程调试
  3. Buildroot构建指南——Linux内核
  4. MySQL远程访问报错解决
  5. 解析弱电安防监控管理系统的安装技术重点
  6. linux系统获取光盘信息api,在Visual C#中运用API函数获取系统信息
  7. 【hibernate框架】各种问题用注解Annotation和mxl配置的解决
  8. NullableT类型
  9. 41.D3D数学库 GameProject7
  10. python读取大智慧数据_大智慧数据格式
  11. 【UV打印】UV平板打印机校准教程
  12. Android Studio 实现地图定位(移动开发技术作业)
  13. 能够翻译文档的免费软件-免费翻译整个文档的软件
  14. 十字路口的交通灯控制电路
  15. MAC地址解析-广播地址、组播地址和单播地址的特征
  16. python爬取12306列车信息自动抢票并自动识别验证码(二)selenium登录验证篇
  17. 什么是 CDP 和 LLDP?
  18. okd下gitlab首次启动没有重置密码如何登陆
  19. 数组去重c语言,C语言数组去重
  20. 常用的Python标准库有哪些

热门文章

  1. SpringMVC异常处理机制详解[附带源码分析]
  2. neo4j批量导入数据的两种解决方案
  3. 【玩转server 2019系列】Windows server 2019打开windows defender提示“需要新应用打开windows defender”的解决方法
  4. 记一次前端问题解决历程(Cannot read Property ‘call‘ of undefined)
  5. 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  6. EDAS ScheduleX 问题
  7. 计算机操作系统(八)---虚拟存储器
  8. JavaPairRDD方法中几种存储方式的坑
  9. html5--6-5 CSS选择器2
  10. mysql的性能瓶颈分析、性能指标、性能指标信息的搜集工具与方法、分析调优工具的使用...