springboot幂等性_SpringBoot中幂等性问题

SpringBoot中防止请求重复提交

一、适用的场景

表单/请求重复提交,不得不说幂等性。

幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次。

1.1、常见场景:

•比如订单接口, 不能多次创建订单

•支付接口, 重复支付同一笔订单只能扣一次钱

•支付宝回调接口, 可能会多次回调, 必须处理重复回调

•普通表单提交接口, 因为网络超时,卡顿等原因多次点击提交, 只能成功一次等等

1.2、常见方案

解决思路:从数据库方面考虑,数据设计的时候,如果有唯一性,考虑建立唯一索引。

从应用层面考虑,首先判断是单机服务还是分布式服务?单机服务:考虑一些缓存Cacha,利用缓存,来保证数据的重复提交。

分布式服务,考虑将用户的信息,例如token和请求的url进行组装在一起形成令牌,存储到缓存中,例如redis,并设置超时时间为xx秒,如此来保证数据的唯一性。(利用了redis的分布式锁)

解决方案大致总结为:

- 唯一索引 -- 防止新增脏数据

- token机制 -- 防止页面重复提交,实现接口幂等性校验

- 分布式锁 -- redis(jedis、redisson)或zookeeper实现

二、单体服务项目-防止重复提交

比如你的项目是一个单独springboot项目,SSM项目,或者其他的单体服务,就是打个jar或者war直接扔服务器上跑的。

采用【AOP解析自定义注解+google的Cache缓存机制】来解决表单/请求重复的提交问题。

思路:建立自定义注解 @NoRepeatSubmit 标记所有Controller中的提交请求。

通过AOP机制对所有标记了@NoRepeatSubmit 的方法拦截。

在业务方法执行前,使用google的缓存Cache技术,来保证数据的重复提交。

业务方法执行后,释放缓存。

好了,接下里就是新建一个springboot项目,然后开整了。

2.1 pom.xml新增依赖

需要新增一个google.common.cache.Cache;

源码如下:

com.google.guava

guava

24.0-jre

2.2 新建NoRepeatSubmit.java自定义注解类

一个自定义注解接口类,是interface类哟 ,里面什么都不写,为了就是个重构。

源码如下:

package com.gitee.taven.aop;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* @title: NoRepeatSubmit

* @Description:

* @Author: lw

* @Date: 2020/10/22

*/

@Target(ElementType.METHOD) // 作用到方法上

@Retention(RetentionPolicy.RUNTIME) // 运行时有效

public @interface NoRepeatSubmit {

}

2.3 新建NoRepeatSubmitAop.java

这是个AOP的解析注解类,使用到了Cache缓存机制。

以cache.getIfPresent(key)的url值来进行if判断,如果不为空,证明已经发过请求,那么在规定时间内的再次请求,视为无效,为重复请求。如果为空,则正常响应请求。

源码如下:

package com.gitee.taven.aop;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import org.springframework.web.context.request.RequestContextHolder;

import org.springframework.web.context.request.ServletRequestAttributes;

import com.google.common.cache.Cache;

/**

* @Description: aop解析注解-配合google的Cache缓存机制

* @Author: lw

* @Date: 2020/10/22

*/

@Aspect

@Component

public class NoRepeatSubmitAop {

private Log logger = LogFactory.getLog(getClass());

@Autowired

private Cache cache;

@Pointcut("@annotation(noRepeatSubmit)")

public void pointCut(NoRepeatSubmit noRepeatSubmit) {

}

@Around("pointCut(noRepeatSubmit)")

public Object arround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) {

try {

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

String sessionId = RequestContextHolder.getRequestAttributes().getSessionId();

HttpServletRequest request = attributes.getRequest();

String key = sessionId + "-" + request.getServletPath();

if (cache.getIfPresent(key) == null) {// 如果缓存中有这个url视为重复提交

Object o = pjp.proceed();

cache.put(key, 0);

return o;

} else {

logger.error("重复请求,请稍后在试试。");

return null;

}

} catch (Throwable e) {

e.printStackTrace();

logger.error("验证重复提交时出现未知异常!");

return "{\"code\":-889,\"message\":\"验证重复提交时出现未知异常!\"}";

}

}

}

2.4 新建缓存类UrlCache.java

用来获取缓存和设置有效期,目前设置有效期为2秒。

源码如下:

package com.gitee.taven;

import java.util.concurrent.TimeUnit;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import com.google.common.cache.Cache;

import com.google.common.cache.CacheBuilder;

/**

* @Description: 内存缓存配置类

* @Author: lw

* @Date: 2020/10/22

*/

@Configuration

public class UrlCache {

@Bean

public Cache getCache() {

return CacheBuilder.newBuilder().expireAfterWrite(2L, TimeUnit.SECONDS).build();// 缓存有效期为2秒

}

}

2.5 新建CacheTestController.java

一个请求控制类,用来模拟响应请求和业务处理。

源码如下&#x


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部