前言
在實際的開發項目中,一個對外暴露的接口往往會面臨很多次請求,我們來解釋一下冪等的概念:任意多次執行所產生的影響均與一次執行的影響相同。按照這個含義,最終的含義就是 對數據庫的影響只能是一次性的,不能重複處理。如何保證其冪等性,通常有以下手段:
- 數據庫建立唯一性索引,可以保證最終插入數據庫的只有一條數據
- token機制,每次接口請求前先獲取一個token,然後再下次請求的時候在請求的header體中加上這個token,後臺進行驗證,如果驗證通過刪除token,下次請求再次判斷token
- 悲觀鎖或者樂觀鎖,悲觀鎖可以保證每次for update的時候其他sql無法update數據(在數據庫引擎是innodb的時候,select的條件必須是唯一索引,防止鎖全表)
- 先查詢後判斷,首先通過查詢數據庫是否存在數據,如果存在證明已經請求過了,直接拒絕該請求,如果沒有存在,就證明是第一次進來,直接放行。
redis實現自動冪等的原理圖:
一、自定義註解AutoIdempotent
/**
* @ClassName AutoIdempotent
* @Description (自動冪等註解:把它添加在需要實現冪等的方法上,凡是某個方法註解了它,都會實現自動冪等。後臺利用反射如果掃描到這個註解,就會處理這個方法實現自動冪等)
* @author ZHY
* @date 2020年4月1日 上午10:51:10
* @Copyright © 2020【www.zhy.com Inc. All rights reserved.】
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoIdempotent {
}
二、Token的創建與校驗
/**
* @method checkIdempotentToken(校驗冪等的token)
* @param request
* @return Boolean
* @author ZHY
* @date 2020年4月1日 下午6:58:48
*/
public static Boolean checkIdempotentToken(HttpServletRequest request) {
String token = request.getHeader(SysConstants.HTTP_IDEMPOTENT_HEADER_NAME);
// header中不存在Idempotent Token
if (StringUtils.isBlank(token)) {
throw new VipException(ServiceErrorEnum.IDEMPOTENT_TOKEN_FAILURE);
}
boolean exists = J2CacheUtil.existsKey(SysConstants.IDEMPOTENT_TOKEN_REGION, token);
if (!exists) {
// 重複操作
throw new VipException(ServiceErrorEnum.REPETITIVE_OPERATION);
}
J2CacheUtil.remove(SysConstants.IDEMPOTENT_TOKEN_REGION, token);
return true;
}
/**
* @method createIdempotentToken(創建冪等校驗用的token並且緩存)
* @return String
* @author ZHY
* @date 2020年4月1日 下午7:21:21
*/
public static String createIdempotentToken() {
String idepotentToken = TokenUtil.generateToken();
J2CacheUtil.set(SysConstants.IDEMPOTENT_TOKEN_REGION, idepotentToken, idepotentToken);
return idepotentToken;
}
四、攔截器的配置
/**
* @ClassName AutoIdempotentInterceptor
* @Description (自動冪等攔截器)
* @author ZHY
* @date 2020年4月1日 上午10:57:12
* @Copyright © 2020【www.zhy.com Inc. All rights reserved.】
*/
@Component
public class AutoIdempotentInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 獲取帶有ApiIdempotment標記的註解方法
AutoIdempotent methodAnnotation = method.getAnnotation(AutoIdempotent.class);
if (methodAnnotation != null) {
Boolean checkIdempotentToken = VipCoreUtil.checkIdempotentToken(request);
// 冪等性校驗, 校驗通過則放行, 校驗失敗則拋出異常, 並通過統一異常處理返回友好提示
if (!checkIdempotentToken) {
throw new VipException(ServiceErrorEnum.IDEMPOTENT_CHECK_FAIL);
}
}
return true;
}
}
/**
* @ClassName WebMvcConfig
* @Description (webmvc 配置)
* @author ZHY
* @date 2020年4月1日 上午9:21:12
* @Copyright © 2020【www.zhy.com Inc. All rights reserved.】
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthorizationInterceptor authorizationInterceptor;
@Autowired
private AutoIdempotentInterceptor autoIdempotentInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authorizationInterceptor).addPathPatterns("/**");
registry.addInterceptor(autoIdempotentInterceptor);
}
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue);
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
HttpMessageConverter<?> converter = fastJsonHttpMessageConverter;
return new HttpMessageConverters(converter);
}
}
五、測試用例
@RestController
public class TestAutoIdempotentController {
@PostMapping("/get/token")
public String getToken() {
return ZhyUtil.createIdempotentToken();
}
@AutoIdempotent
@PostMapping("/test/Idempotence")
public String testIdempotence() {
return "success";
}
}
六、總結
對於冪等在實際的開發過程中是十分重要的,因爲一個接口可能會被無數的客戶端調用,如何保證其不影響後臺的業務處理,如何保證其隻影響數據一次是非常重要的,它可以防止產生髒數據或者亂數據,也可以減少併發量,實乃十分有益的一件事。而傳統的做法是每次判斷數據,這種做法不夠智能化和自動化,比較麻煩。通過註解的這種自動化處理也可以提升程序的伸縮性。