中文字幕理论片,69视频免费在线观看,亚洲成人app,国产1级毛片,刘涛最大尺度戏视频,欧美亚洲美女视频,2021韩国美女仙女屋vip视频

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
如何設(shè)計(jì)一個冪等接口
什么叫冪等接口

冪等性,就是只多次操作的結(jié)果是一致的。這里可能有人會有疑問。

問:為什么要多次操作結(jié)果都一致呢?比如我查詢數(shù)據(jù),每次查出來的都一樣,即使我修改了每次查出來的也都要一樣嗎?

答:我們說的多次,是指同一次請求中的多次操作。這個多次操作可能會在如下情況發(fā)生:

  • 前端重復(fù)提交。比如這個業(yè)務(wù)處理需要2秒鐘,我在2秒之內(nèi),提交按鈕連續(xù)點(diǎn)了3次,如果非冪等性接口,那么后端就會處理3次。如果是查詢,自然是沒有影響的,因?yàn)椴樵儽旧砭褪莾绲炔僮鳎绻切略?,本來只是新?條記錄的,連點(diǎn)3次,就增加了3條,這顯然不行。

  • 響應(yīng)超時而導(dǎo)致請求重試:在微服務(wù)相互調(diào)用的過程中,假如訂單服務(wù)調(diào)用支付服務(wù),支付服務(wù)支付成功了,但是訂單服務(wù)接收支付服務(wù)返回的信息時超時了,于是訂單服務(wù)進(jìn)行重試,又去請求支付服務(wù),結(jié)果支付服務(wù)又扣了一遍用戶的錢。如果真這樣的話,用戶估計(jì)早就提著砍刀來了。

如何設(shè)計(jì)冪等接口

經(jīng)過上面的描述,相信大家已經(jīng)清楚了什么叫接口冪等性及其重要性。那么如何設(shè)計(jì)呢?大致有以下幾種方案:

  • 數(shù)據(jù)庫記錄狀態(tài)機(jī)制:即每次操作前先查詢狀態(tài),根據(jù)數(shù)據(jù)庫記錄的狀態(tài)來判斷是否要繼續(xù)執(zhí)行操作。比如訂單服務(wù)調(diào)用支付服務(wù),每次調(diào)用之前,先查詢該筆訂單的支付狀態(tài),從而避免重復(fù)操作。

  • token機(jī)制:請求業(yè)務(wù)接口之前,先請求token接口(會將生成的token放入redis中)獲取一個token,然后請求業(yè)務(wù)接口時,帶上token。在進(jìn)行業(yè)務(wù)操作之前,我們先獲取請求中攜帶的token,看看在redis中是否有該token,有的話,就刪除,刪除成功說明token校驗(yàn)通過,并且繼續(xù)執(zhí)行業(yè)務(wù)操作;如果redis中沒有該token,說明已經(jīng)被刪除了,也就是已經(jīng)執(zhí)行過業(yè)務(wù)操作了,就不讓其再進(jìn)行業(yè)務(wù)操作。大致流程如下:

  • 其他方案:接口冪等性設(shè)計(jì)還有很多其他方案,比如全局唯一id、樂觀鎖等。本文主要講token機(jī)制的使用,若感興趣可以自行研究。

用token機(jī)制實(shí)現(xiàn)接口自動冪等

1、pom.xml:主要是引入了redis相關(guān)依賴

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId></dependency><!-- spring-boot-starter-data-redis --><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency>    <groupId>org.apache.commons</groupId>    <artifactId>commons-pool2</artifactId></dependency><!-- jedis --><dependency>    <groupId>redis.clients</groupId>    <artifactId>jedis</artifactId></dependency><dependency>    <groupId>org.projectlombok</groupId>    <artifactId>lombok</artifactId>    <optional>true</optional></dependency><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-test</artifactId>    <scope>test</scope></dependency><!-- commons-lang3 --><dependency>    <groupId>org.apache.commons</groupId>    <artifactId>commons-lang3</artifactId></dependency><!-- org.json/json --><dependency>    <groupId>org.json</groupId>    <artifactId>json</artifactId>    <version>20190722</version></dependency>

2、application.yml:主要是配置redis

server:  port: 6666spring:  application:    name: idempotent-api  redis:    host: 192.168.2.43    port: 6379

3、業(yè)務(wù)代碼:

·新建一個枚舉,列出常用返回信息,如下:

@Getter@AllArgsConstructorpublic enum ResultEnum {  REPEATREQUEST(405, "重復(fù)請求"),  OPERATEEXCEPTION(406, "操作異常"),  HEADERNOTOKEN(407, "請求頭未攜帶token"),  ERRORTOKEN(408, "token正確")  ;  private Integer code;  private String msg;}

     ·新建一個JsonUtil,當(dāng)請求異常時往頁面中輸出json:

public class JsonUtil {  private JsonUtil() {}  public static void writeJsonToPage(HttpServletResponse response, String msg) {      PrintWriter writer = null;      response.setCharacterEncoding("UTF-8");      response.setContentType("text/html; charset=utf-8");      try {          writer = response.getWriter();          writer.print(msg);      } catch (IOException e) {      } finally {          if (writer != null)              writer.close();      }  }}
     ·新建一個RedisUtil,用來操作redis:
@Componentpublic class RedisUtil {
private RedisUtil() {}
private static RedisTemplate redisTemplate;
@Autowired public void setRedisTemplate(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) { redisTemplate.setKeySerializer(new StringRedisSerializer()); //設(shè)置序列化Value的實(shí)例化對象 redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); RedisUtil.redisTemplate = redisTemplate; }
/** * 設(shè)置key-value,過期時間為timeout秒 * @param key * @param value * @param timeout */ public static void setString(String key, String value, Long timeout) { redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); }
/** * 設(shè)置key-value * @param key * @param value */ public static void setString(String key, String value) { redisTemplate.opsForValue().set(key, value); }
/** * 獲取key-value * @param key * @return */ public static String getString(String key) { return (String) redisTemplate.opsForValue().get(key); }
/** * 判斷key是否存在 * @param key * @return */ public static boolean isExist(String key) { return redisTemplate.hasKey(key); }
/** * 刪除key * @param key * @return */ public static boolean delKey(String key) { return redisTemplate.delete(key); }}
    ·新建一個TokenUtil,用來生成和校驗(yàn)token:生成token沒什么好說的,這里為了簡單直接用uuid生成,然后放入redis中。校驗(yàn)token,如果用戶沒有攜帶token,直接返回false;如果攜帶了token,但是redis中沒有這個token,說明已經(jīng)被刪除了,即已經(jīng)訪問了,返回false;如果redis中有,但是redis中的token和用戶攜帶的token不一致,也返回false;有且一致,說明是第一次訪問,就將redis中的token刪除,然后返回true。
public class TokenUtil {
private TokenUtil() {}
private static final String KEY = "token"; private static final String CODE = "code"; private static final String MSG = "msg"; private static final String JSON = "json"; private static final String RESULT = "result";
/** * 生成token并放入redis中 * @return */ public static String createToken() { String token = UUID.randomUUID().toString(); RedisUtil.setString(KEY, token, 60L); return RedisUtil.getString(KEY); }
/** * 校驗(yàn)token * @param request * @return * @throws JSONException */ public static Map<String, Object> checkToken(HttpServletRequest request) throws JSONException { String headerToken = request.getHeader(KEY); JSONObject json = new JSONObject(); Map<String, Object> resultMap = new HashMap<>(); // 請求頭中沒有攜帶token,直接返回false if (StringUtils.isEmpty(headerToken)) { json.put(CODE, ResultEnum.HEADERNOTOKEN.getCode()); json.put(MSG, ResultEnum.HEADERNOTOKEN.getMsg()); resultMap.put(RESULT, false); resultMap.put(JSON, json.toString()); return resultMap; }
if (StringUtils.isEmpty(RedisUtil.getString(KEY))) { // 如果redis中沒有token,說明已經(jīng)訪問成功過了,直接返回false json.put(CODE, ResultEnum.REPEATREQUEST.getCode()); json.put(MSG, ResultEnum.REPEATREQUEST.getMsg()); resultMap.put(RESULT, false); resultMap.put(JSON, json.toString()); return resultMap; } else { // 如果redis中有token,就刪除掉,刪除成功返回true,刪除失敗返回false String redisToken = RedisUtil.getString(KEY); boolean result = false; if (!redisToken.equals(headerToken)) { json.put(CODE, ResultEnum.ERRORTOKEN.getCode()); json.put(MSG, ResultEnum.ERRORTOKEN.getMsg()); } else { result = RedisUtil.delKey(KEY); String msg = result ? null : ResultEnum.OPERATEEXCEPTION.getMsg(); json.put(CODE, 400); json.put(MSG, msg); } resultMap.put(RESULT, result); resultMap.put(JSON, json.toString()); return resultMap; } }}
    新建一個注解,用來標(biāo)注需要進(jìn)行冪等的接口:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface NeedIdempotent {}
    接著要新建一個攔截器,對有@NeedIdempotent注解的方法進(jìn)行攔截,進(jìn)行自動冪等。
public class IdempotentInterceptor implements HandlerInterceptor{
@Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,Object object) throws JSONException { // 攔截的不是方法,直接放行 if (!(object instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) object; Method method = handlerMethod.getMethod(); // 如果是方法,并且有@NeedIdempotent注解,就自動冪等 if (method.isAnnotationPresent(NeedIdempotent.class)) { Map<String, Object> resultMap = TokenUtil.checkToken(httpServletRequest); boolean result = (boolean) resultMap.get("result"); String json = (String) resultMap.get("json"); if (!result) { JsonUtil.writeJsonToPage(httpServletResponse, json); } return result; } else { return true; } }
@Override public void postHandle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, Object o,ModelAndView modelAndView) { }
@Override public void afterCompletion(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object o, Exception e) { }}
     然后將這個攔截器配置到spring中去:
@Configurationpublic class InterceptorConfig implements WebMvcConfigurer {
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(idempotentInterceptor()) .addPathPatterns("/**"); } @Bean public IdempotentInterceptor idempotentInterceptor() { return new IdempotentInterceptor(); }
}

·最后新建一個controller,就可以愉快地進(jìn)行測試了:

@RestController@RequestMapping("/idempotent")public class IdempotentApiController {
@NeedIdempotent @GetMapping("/hello") public String hello() { return "are you ok?"; }
@GetMapping("/token") public String token() { return TokenUtil.createToken(); }}

訪問/token,不需要什么校驗(yàn),訪問/hello,就會自動冪等,每一次訪問都要先獲取token,一個token不能用兩次。

-java開發(fā)那些事-
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
什么是接口的冪等性,如何實(shí)現(xiàn)接口冪等性?
『高級篇』docker之開發(fā)用戶服務(wù)EdgeService(13) – IT人故事會
一口氣說出四種冪等性解決方案,面試官露出了姨母笑
快速搭建一個網(wǎng)關(guān)服務(wù),動態(tài)路由、鑒權(quán)的流程,看完秒會(含流程圖)
Java生產(chǎn)驗(yàn)證碼各種工具類
Spring Data Redis 單節(jié)點(diǎn)和集群配置和RedisTemplate用法
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服