日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
處理接口冪等性的兩種常見方案

1. 接口冪等性實現(xiàn)方案梳理

其實接口冪等性的實現(xiàn)方案還是蠻多的,我這里和小伙伴們分享兩種比較常見的方案。

1.1 基于 Token

基于 Token 這種方案的實現(xiàn)思路很簡單,整個流程分兩步:

  1. 客戶端發(fā)送請求,從服務端獲取一個 Token 令牌,每次請求獲取到的都是一個全新的令牌。
  2. 客戶端發(fā)送請求的時候,攜帶上第一步的令牌,處理請求之前,先校驗令牌是否存在,當請求處理成功,就把令牌刪除掉。

大致的思路就是上面這樣,當然具體的實現(xiàn)則會復雜很多,有很多細節(jié)需要注意,松哥之前也專門錄過這種方案的視頻,小伙伴們可以參考下,錄了兩個視頻,一個是基于攔截器處理的,還有一個是基于 AOP 切面處理的:

基于攔截器處理(視頻一):

基于 AOP 切面處理(視頻二):

1.2 基于請求參數(shù)校驗

最近在 TienChin 項目中使用的是另外一種方案,這種方案是基于請求參數(shù)來判斷的,如果在短時間內(nèi),同一個接口接收到的請求參數(shù)相同,那么就認為這是重復的請求,拒絕處理,大致上就是這么個思路。

相比于第一種方案,第二種方案相對來說省事一些,因為只有一次請求,不需要專門去服務端拿令牌。在高并發(fā)環(huán)境下這種方案優(yōu)勢比較明顯。

所以今天我就來和大家聊聊第二種方案的實現(xiàn),后面在 TienChin 項目視頻中也會和大家細講。

2. 基于請求參數(shù)的校驗

首先我們新建一個 Spring Boot 項目,引入 Web 和 Redis 依賴,新建完成后,先來配置一下 Redis 的基本信息,如下:

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123

為了后續(xù) Redis 操作方便,我們再來對 Redis 進行一個簡單封裝,如下:

@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
public T getCacheObject(final String key) {
ValueOperations operation = redisTemplate.opsForValue();
return operation.get(key);
}
}

這個比較簡單,一個存數(shù)據(jù),一個讀數(shù)據(jù)。

接下來我們自定義一個注解,在需要進行冪等性處理的接口上,添加該注解即可,將來這個接口就會自動的進行冪等性處理。

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
/**
* 間隔時間(ms),小于此時間視為重復提交
*/
public int interval() default 5000;

/**
* 提示消息
*/
public String message() default "不允許重復提交,請稍候再試";
}

這個注解我們通過攔截器來進行解析,解析代碼如下:

public abstract class RepeatSubmitInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
if (annotation != null) {
if (this.isRepeatSubmit(request, annotation)) {
Map map = new HashMap<>();
map.put("status", 500);
map.put("msg", annotation.message());
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(map));
return false;
}
}
return true;
} else {
return true;
}
}

/**
* 驗證是否重復提交由子類實現(xiàn)具體的防重復提交的規(guī)則
*
* @param request
* @return
* @throws Exception
*/
public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
}

這個攔截器是一個抽象類,將接口方法攔截下來,然后找到接口上的 @RepeatSubmit 注解,調(diào)用 isRepeatSubmit 方法去判斷是否是重復提交的數(shù)據(jù),該方法在這里是一個抽象方法,我們需要再定義一個類繼承自這個抽象類,在新的子類中,可以有不同的冪等性判斷邏輯,這里我們就是根據(jù) URL 地址+參數(shù) 來判斷冪等性條件是否滿足:

@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
public final String REPEAT_PARAMS = "repeatParams";

public final String REPEAT_TIME = "repeatTime";
public final static String REPEAT_SUBMIT_KEY = "REPEAT_SUBMIT_KEY";

private String header = "Authorization";

@Autowired
private RedisCache redisCache;

@SuppressWarnings("unchecked")
@Override
public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) {
String nowParams = "";
if (request instanceof RepeatedlyRequestWrapper) {
RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
try {
nowParams = repeatedlyRequest.getReader().readLine();
} catch (IOException e) {
e.printStackTrace();
}
}

// body參數(shù)為空,獲取Parameter的數(shù)據(jù)
if (StringUtils.isEmpty(nowParams)) {
try {
nowParams = new ObjectMapper().writeValueAsString(request.getParameterMap());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
Map nowDataMap = new HashMap();
nowDataMap.put(REPEAT_PARAMS, nowParams);
nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());

// 請求地址(作為存放cache的key值)
String url = request.getRequestURI();

// 唯一值(沒有消息頭則使用請求地址)
String submitKey = request.getHeader(header);

// 唯一標識(指定key + url + 消息頭)
String cacheRepeatKey = REPEAT_SUBMIT_KEY + url + submitKey;

Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
if (sessionObj != null) {
Map sessionMap = (Map) sessionObj;
if (compareParams(nowDataMap, sessionMap) && compareTime(nowDataMap, sessionMap, annotation.interval())) {
return true;
}
}
redisCache.setCacheObject(cacheRepeatKey, nowDataMap, annotation.interval(), TimeUnit.MILLISECONDS);
return false;
}

/**
* 判斷參數(shù)是否相同
*/
private boolean compareParams(Map nowMap, Map preMap) {
String nowParams = (String) nowMap.get(REPEAT_PARAMS);
String preParams = (String) preMap.get(REPEAT_PARAMS);
return nowParams.equals(preParams);
}

/**
* 判斷兩次間隔時間
*/
private boolean compareTime(Map nowMap, Map preMap, int interval) {
long time1 = (Long) nowMap.get(REPEAT_TIME);
long time2 = (Long) preMap.get(REPEAT_TIME);
if ((time1 - time2) < interval) {
return true;
}
return false;
}
}

我們來看下具體的實現(xiàn)邏輯:

  1. 首先判斷當前的請求對象是不是 RepeatedlyRequestWrapper,如果是,說明當前的請求參數(shù)是 JSON,那么就通過 IO 流將參數(shù)讀取出來,這塊小伙伴們要結(jié)合上篇文章共同來理解,否則可能會覺得云里霧里的,傳送門JSON 數(shù)據(jù)讀一次就沒了,怎么辦?。
  2. 如果在第一步中,并沒有拿到參數(shù),那么說明參數(shù)可能并不是 JSON 格式,而是 key-value 格式,那么就以 key-value 的方式讀取出來參數(shù),并將之轉(zhuǎn)為一個 JSON 字符串。
  3. 接下來構(gòu)造一個 Map,將前面讀取到的參數(shù)和當前時間存入到 Map 中。
  4. 接下來構(gòu)造存到 Redis 中的數(shù)據(jù)的 key,這個 key 由固定前綴 + 請求 URL 地址 + 請求頭的認證令牌組成,這塊請求頭的令牌還是非常重要需要有的,只有這樣才能區(qū)分出來當前用戶提交的數(shù)據(jù)(如果是 RESTful 風格的接口,那么為了區(qū)分,也可以將接口的請求方法作為參數(shù)拼接到 key 中)。
  5. 接下來就去 Redis 中獲取數(shù)據(jù),獲取到之后,分別去比較參數(shù)是否相同以及時間是否過期。
  6. 如果判斷都沒問題,返回 true,表示這個請求重復了。
  7. 否則返回說明這是用戶對這個接口第一次提交數(shù)據(jù)或者是已經(jīng)過了時間窗口了,那么就把參數(shù)字符串重新緩存到 Redis 中,并返回 false,表示請求沒問題。

好啦,做完這一切,最后我們再來配置一下攔截器即可:

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Autowired
RepeatSubmitInterceptor repeatSubmitInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(repeatSubmitInterceptor)
.addPathPatterns("/**");
}
}

如此,我們的接口冪等性就處理好啦~在需要的時候,就可以直接在接口上使用啦:

@RestController
public class HelloController {

@PostMapping("/hello")
@RepeatSubmit(interval = 100000)
public String hello(@RequestBody String msg) {
System.out.println("msg = " + msg);
return "hello";
}
}


本文標題:處理接口冪等性的兩種常見方案
網(wǎng)址分享:http://www.5511xx.com/article/dpppgej.html