91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么用token機制實現接口自動冪等

發布時間:2022-03-22 17:00:02 來源:億速云 閱讀:273 作者:iii 欄目:大數據

這篇“怎么用token機制實現接口自動冪等”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“怎么用token機制實現接口自動冪等”文章吧。

什么叫冪等接口

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

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

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


  • 前端重復提交。比如這個業務處理需要2秒鐘,我在2秒之內,提交按鈕連續點了3次,如果非冪等性接口,那么后端就會處理3次。如果是查詢,自然是沒有影響的,因為查詢本身就是冪等操作,但如果是新增,本來只是新增1條記錄的,連點3次,就增加了3條,這顯然不行。



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


如何設計冪等接口

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


  • 數據庫記錄狀態機制:即每次操作前先查詢狀態,根據數據庫記錄的狀態來判斷是否要繼續執行操作。比如訂單服務調用支付服務,每次調用之前,先查詢該筆訂單的支付狀態,從而避免重復操作。



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


怎么用token機制實現接口自動冪等

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


用token機制實現接口自動冪等
   

1、pom.xml:主要是引入了redis相關依賴

<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、業務代碼:

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

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

     ?新建一個JsonUtil,當請求異常時往頁面中輸出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());      //設置序列化Value的實例化對象      redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());      RedisUtil.redisTemplate = redisTemplate;  }
 /**   * 設置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);  }
 /**   * 設置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,用來生成和校驗token:生成token沒什么好說的,這里為了簡單直接用uuid生成,然后放入redis中。校驗token,如果用戶沒有攜帶token,直接返回false;如果攜帶了token,但是redis中沒有這個token,說明已經被刪除了,即已經訪問了,返回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); }
/**  * 校驗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,說明已經訪問成功過了,直接返回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;     } }}
     
         新建一個注解,用來標注需要進行冪等的接口:      
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface NeedIdempotent {}
         接著要新建一個攔截器,對有@NeedIdempotent注解的方法進行攔截,進行自動冪等。      
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,就可以愉快地進行測試了:

@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,不需要什么校驗,訪問/hello,就會自動冪等,每一次訪問都要先獲取token,一個token不能用兩次。

以上就是關于“怎么用token機制實現接口自動冪等”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

湟源县| 山西省| 德化县| 平南县| 泾川县| 理塘县| 隆安县| 静安区| 伊宁县| 平舆县| 康平县| 邯郸县| 卫辉市| 新闻| 海林市| 民勤县| 亳州市| 柳江县| 盐源县| 渭南市| 闸北区| 如东县| 凤凰县| 大名县| 固阳县| 时尚| 台南市| 柞水县| 南木林县| 个旧市| 老河口市| 册亨县| 黄石市| 根河市| 噶尔县| 临清市| 同仁县| 德安县| 泸水县| 鄂托克旗| 曲松县|