Spring Boot學習總結(21)——SpringBoot集成Redis等緩存以註解的方式優雅實現冪等,防千萬次重複提交實例代碼

前言

在實際的開發項目中,一個對外暴露的接口往往會面臨很多次請求,我們來解釋一下冪等的概念:任意多次執行所產生的影響均與一次執行的影響相同。按照這個含義,最終的含義就是 對數據庫的影響只能是一次性的,不能重複處理。如何保證其冪等性,通常有以下手段:

  1. 數據庫建立唯一性索引,可以保證最終插入數據庫的只有一條數據
  2. token機制,每次接口請求前先獲取一個token,然後再下次請求的時候在請求的header體中加上這個token,後臺進行驗證,如果驗證通過刪除token,下次請求再次判斷token
  3. 悲觀鎖或者樂觀鎖,悲觀鎖可以保證每次for update的時候其他sql無法update數據(在數據庫引擎是innodb的時候,select的條件必須是唯一索引,防止鎖全表)
  4. 先查詢後判斷,首先通過查詢數據庫是否存在數據,如果存在證明已經請求過了,直接拒絕該請求,如果沒有存在,就證明是第一次進來,直接放行。

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";
	}
}

六、總結

對於冪等在實際的開發過程中是十分重要的,因爲一個接口可能會被無數的客戶端調用,如何保證其不影響後臺的業務處理,如何保證其隻影響數據一次是非常重要的,它可以防止產生髒數據或者亂數據,也可以減少併發量,實乃十分有益的一件事。而傳統的做法是每次判斷數據,這種做法不夠智能化和自動化,比較麻煩。通過註解的這種自動化處理也可以提升程序的伸縮性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章