OpenAPI 接口冪等實現(xiàn)1、冪等性是啥?進行一次接口調(diào)用與進行多次相同的接口調(diào)用都能得到與預期相符的結果 。
通俗的講,創(chuàng)建資源或更新資源的操作在多次調(diào)用后只生效一次 。
2、什么情況會需要保證冪等性比如,購物時的下單操作,如前端提交按鈕未做并發(fā)、抖動控制,那么用戶點擊一次 。可能因為某些原因導致 Http 請求了多次,這就會導致用戶生成多個相同訂單 。
再有,在我們的分布式項目中,為了提高通行的可靠性,通信框架/MQ 可能會向數(shù)據(jù)服務推送多條相同的消息,如果不做冪等性控制,消息會被多次消費 。
等等 。。。
上述說了需要保證冪等性的場景,但我們實現(xiàn)冪等還要考慮下述條件:
- 如果服務接受了多個請求,且
冪等 token和請求參數(shù)完全一樣,服務應該保證冪等直接返回相似數(shù)據(jù) 。 - 如果服務接受了多個請求,且
冪等 token和請求參數(shù)不完全一樣,服務應該拒絕冪等 。即:冪等 token 不一致直接拒絕冪等直接走正常邏輯;冪等 token 一致但請求參數(shù)卻不一致,我們返回 token 異常,也可以拒絕冪等 。 - 不同用戶之間的請求不能相互影響 。
- 不同接口之間的請求不能相互影響 。即:不同接口不能被相同 token 影響 。
- 更新接口不能使用緩存數(shù)據(jù),需要特殊處理 。比如:客戶端帶了
冪等 token請求了會員續(xù)費接口,此時響應了新的會員過期時間,然后客戶端又未攜帶了冪等 token請求了會員續(xù)費接口,此時用戶會員到期時間得到了更新,用戶再次攜帶了冪等 token進行請求,響應的緩存的相似數(shù)據(jù)就明顯不對了 。 - 這里為啥說更新不能緩存,而創(chuàng)建未提呢?因為大多數(shù)更新需要考慮緩存一致性問題,而創(chuàng)建本身就是從無到有的過程,一般無需考慮,但也要根據(jù)實際業(yè)務來進行判斷,這里后續(xù)實現(xiàn)方案為:創(chuàng)建直接走緩存,更新為重新查庫 。
冪等表 。
流程解析:
- 客戶端請求時,為相關接口(所有創(chuàng)建資源的接口、部分更新接口)添加一個請求頭參數(shù):clientToken ,clientToken 是一個由客戶端生成的唯一的、大小寫敏感、不超過64個 ASCII 字符的字符串 。例如,clientToken=123e4567-e89b-12d3-a456-426655440000clientToken 可以由服務端提供單獨的接口生成 。生成方式很多這里不做討論 。
- 服務端對相關接口做 AOP 切入,處理進行冪等判斷、冪等記錄、數(shù)據(jù)緩存 。
- 依據(jù) Redis 中 clientToken 的狀態(tài)信息返回相似信息
/** * 冪等注解 * * @author Eajur.wen * @version 1.0 * @date 2022-10-19 11:25:09 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface IdempotentAnnotation {/*** 是否緩存獲取** @return*/boolean cache() default true;/*** 需要特殊處理的接口標識** @return*/String name() default "";}cache 默認為 true ,會緩存第一次響應數(shù)據(jù),后續(xù)冪等的請求直接走緩存響應數(shù)據(jù)name 為需要特殊處理接口的標識,在 cache 為 false 時,根據(jù)此標識做特殊處理
4.2 冪等 AOP 實現(xiàn)【OpenAPI 接口冪等實現(xiàn)】
/** * 冪等 AOP 實現(xiàn) * * @author Eajur.wen * @version 1.0 * @date 2022-10-19 11:26:45 */@Component@Aspect@Slf4jpublic class IdempotentAspect2 {public static final String CLIENT_TOKEN = "clientToken";public static final String RENEWAL_NO_CACHE = "renewal";public static final int CLIENT_TOKEN_MAX_LENGTH = 64;public static final String CLIENT_TOKEN_KEY_PRE = "client:token:";public static final String CLIENT_TOKEN_DATA_KEY_PRE = "client:token:data:";public static final String CLIENT_TOKEN_DATA_ID_KEY_PRE = "client:token:data:id:";public static final String CLIENT_TOKEN_DATA_ABSTRACT_KEY_PRE = "client:token:data:abstract:";public static final long CLIENT_TOKEN_TIMEOUT_MINUTES = 5;/*** 請求中 處理中*/public static final int CLIENT_TOKEN_REQUEST_STATUS = 1;public static final int CLIENT_TOKEN_SUCCESS_STATUS = 2;@Autowiredprivate HttpServletRequest request;@Autowiredprivate RedisTemplate redisTemplate;@Pointcut("@annotation(com.eajur.idempotent.annotation.IdempotentAnnotation)")public void pt() {}@Around("pt()")public Object idempotent(ProceedingJoinPoint joinPoint) throws Throwable {// 沒有注解直接放行MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();IdempotentAnnotation annotation = method.getAnnotation(IdempotentAnnotation.class);if (annotation == null) {return joinPoint.proceed();}boolean cache = annotation.cache();String clientToken = request.getHeader(CLIENT_TOKEN);// 沒有請求頭直接放行if (!StringUtils.hasText(clientToken)) {return joinPoint.proceed();}// clientToken 不能過長if (clientToken.length() > CLIENT_TOKEN_MAX_LENGTH) {return new ViewData(ErrorCodeEnum.REPEATED_REQUEST_ERROR);}// 未登錄接口暫不做冪等Long memberId = SubjectUtil.getMemberId();if (memberId == null) {return joinPoint.proceed();}//獲取參數(shù)名稱和值Map<String, Object> nameAndArgs = CommonUtil.getNameAndValue(joinPoint);String jsonStr = JSONUtil.toJsonStr(nameAndArgs);String abstractData = https://www.huyubaike.com/biancheng/SmUtil.sm3(jsonStr);// 記錄請求 clientTokenString methodName = method.getName();String baseKey = memberId +":" + methodName + ":" + clientToken;String key = CLIENT_TOKEN_KEY_PRE + baseKey;String dataKey = CLIENT_TOKEN_DATA_KEY_PRE + baseKey;String abstractKey = CLIENT_TOKEN_DATA_ABSTRACT_KEY_PRE + baseKey;ValueOperations ops = redisTemplate.opsForValue();Object flag = ops.getAndExpire(key, CLIENT_TOKEN_TIMEOUT_MINUTES, TimeUnit.MINUTES);if (flag == null) {ops.set(key, CLIENT_TOKEN_REQUEST_STATUS, CLIENT_TOKEN_TIMEOUT_MINUTES, TimeUnit.MINUTES);Object proceed;try {proceed = joinPoint.proceed();} catch (Throwable throwable) {// 請求失敗清除冪等信息redisTemplate.delete(key);throw throwable;}ops.set(key, CLIENT_TOKEN_SUCCESS_STATUS, CLIENT_TOKEN_TIMEOUT_MINUTES, TimeUnit.MINUTES);ops.set(abstractKey, abstractData, CLIENT_TOKEN_TIMEOUT_MINUTES, TimeUnit.MINUTES);if (cache) {ops.set(dataKey, proceed, CLIENT_TOKEN_TIMEOUT_MINUTES, TimeUnit.MINUTES);}return proceed;}// 請求參數(shù)不一致不做冪等Object oldAbstractData = https://www.huyubaike.com/biancheng/ops.getAndExpire(abstractKey, CLIENT_TOKEN_TIMEOUT_MINUTES, TimeUnit.MINUTES);if (!abstractData.equals(oldAbstractData)) {Object proceed;try {proceed = joinPoint.proceed();} catch (Throwable throwable) {// 請求失敗清除冪等信息redisTemplate.delete(key);redisTemplate.delete(dataKey);redisTemplate.delete(abstractKey);throw throwable;}ops.set(key, CLIENT_TOKEN_SUCCESS_STATUS, CLIENT_TOKEN_TIMEOUT_MINUTES, TimeUnit.MINUTES);ops.set(abstractKey, abstractData, CLIENT_TOKEN_TIMEOUT_MINUTES, TimeUnit.MINUTES);if (cache) {ops.set(dataKey, proceed, CLIENT_TOKEN_TIMEOUT_MINUTES, TimeUnit.MINUTES);}return proceed;}// 上次請求未完成if (flag.equals(CLIENT_TOKEN_REQUEST_STATUS)) {return new ViewData().error(ErrorCodeEnum.REPEATED_REQUEST_ERROR);}// 響應相似數(shù)據(jù)并刷新過期時間if (flag.equals(CLIENT_TOKEN_SUCCESS_STATUS) && cache) {Object data = ops.getAndExpire(dataKey, CLIENT_TOKEN_TIMEOUT_MINUTES, TimeUnit.MINUTES);return data;} else {String name = annotation.name();switch (name) {case RENEWAL_NO_CACHE:// 特殊處理 我在這的處理是直接查庫獲取最新數(shù)據(jù)返回// 可以通過 CLIENT_TOKEN_DATA_ID_KEY_PRE 緩存主鍵信息,也可以根據(jù)上面的 nameAndArgs 做處理return new ViewData();default:return joinPoint.proceed();}}}}
經(jīng)驗總結擴展閱讀
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Java函數(shù)式編程:一、函數(shù)式接口,lambda表達式和方法引用
- 二 網(wǎng)絡工程知識VLAN的基礎和配置:802.1q幀;Access、Trunk、Hybrid接口工作模式過程與配置;VLANIF的小實驗
- Linux 文件操作接口
- Dubbo 03: 直連式 + 接口工程
- spring boot使用swagger生成api接口文檔
- SpringBoot-JavaMailSender接口實戰(zhàn)
- 王牌對王牌楊冪趙又廷迪麗熱巴是那一期?
- docker搭建yapi接口文檔系統(tǒng)、Idea中上傳接口、在線調(diào)用
- 景甜大冪冪是什么意思?
- 【Java8新特性】- 接口中默認方法修飾為普通方法
