人人妻人人澡人人爽人人精品av_精品乱码一区内射人妻无码_老司机午夜福利视频_精品成品国色天香摄像头_99精品福利国产在线导航_野花社区在线观看视频_大地资源在线影视播放_东北高大肥胖丰满熟女_金门瓶马车内剧烈运动

首頁>國內(nèi) > 正文

一個注解,兩種實現(xiàn)方式完美解決重復提交問題

2023-08-24 10:19:21來源:Spring全家桶實戰(zhàn)案例源碼

環(huán)境:Springboot3.0.5

什么是接口防重

接口防重是指在一定時間內(nèi)只允許執(zhí)行一次接口請求。這是為了防止由于重復提交和重復處理產(chǎn)生重復數(shù)據(jù)或相應錯誤。實現(xiàn)接口防重可以采用以下方法:

使用唯一標識符:在請求中包含一個唯一標識符(例如請求token),然后在對應接口判斷該唯一值在一定時間內(nèi)是否被消費過,如果已被消費,則拒絕該請求。使用時間戳、計數(shù)器等機制:記錄請求的時間或次數(shù),并在一定范圍內(nèi)拒絕重復請求。采用Spring AOP理念:實現(xiàn)請求的切割,在請求執(zhí)行到某個方法或某層時,開始攔截并進行防重處理。

這些方法有助于確保系統(tǒng)的一致性和穩(wěn)定性,防止數(shù)據(jù)的重復提交和處理。


(資料圖片)

冪等與防重

API接口的冪等性和防重性是兩個不同的概念,盡管它們在某些方面有重疊之處。

冪等性冪等性是指一個操作或API請求,無論執(zhí)行一次還是多次,結(jié)果都是相同的。在API設計中,冪等性是一種非常重要的屬性,因為它確保了在重試或并發(fā)請求時,系統(tǒng)狀態(tài)不會出現(xiàn)不一致的情況。

在實現(xiàn)冪等性時,通常采用以下方法:

在請求中包含一個唯一標識符(例如請求ID),以便在處理請求時能夠識別和防止重復處理。使用樂觀鎖或悲觀鎖機制來保證數(shù)據(jù)的一致性。對于更新操作,可以通過比較新舊數(shù)據(jù)來判斷是否有變化,只有當數(shù)據(jù)發(fā)生改變時才執(zhí)行更新操作。防重性防重性是指在一定時間內(nèi)只允許執(zhí)行一次操作或請求。它主要用于防止重復提交和重復處理。與冪等性不同,防重性主要關(guān)注的是防止數(shù)據(jù)重復,而冪等性則關(guān)注任何多次執(zhí)行的結(jié)果都是相同的。技術(shù)實現(xiàn)方式1:通過AOP方式自定義注解
@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface PreventDuplicate {    /**   * 唯一標識通過header傳遞時的key   *    * @return   */  String header() default "token" ;    /**   * 唯一標識通過請求參數(shù)傳遞時的key   *    * @return   */  String param() default "token" ;}
自定義AOP切面
@Component@Aspectpublic class PreventDuplicateAspect {  public static final String PREVENT_PREFIX_KEY = "prevent:" ;    private final StringRedisTemplate stringRedisTemplate ;  private final HttpServletRequest request ;    public PreventDuplicateAspect(StringRedisTemplate stringRedisTemplate, HttpServletRequest request) {    this.stringRedisTemplate = stringRedisTemplate ;    this.request = request ;  }    @Around("@annotation(prevent)")  public Object preventDuplicate(ProceedingJoinPoint pjp, PreventDuplicate prevent) throws Throwable {        String key = prevent.header() ;    String value = null ;    if (key != null && key.length() > 0) {      value = this.request.getHeader(key) ;    } else {      key = prevent.param() ;      if (key != null && key.length() > 0) {        value = this.request.getParameter(key) ;      }    }        if (value == null || "".equals(value.trim())) {      return "非法請求" ;    }    // 拼接rediskey    String prevent_key = PREVENT_PREFIX_KEY + value ;    // 判斷redis中是否存在當前請求中攜帶的唯一標識數(shù)據(jù), 刪除成功則存在    Boolean result = this.stringRedisTemplate.delete(prevent_key) ;    if (result != null && result.booleanValue()) {      return pjp.proceed() ;    } else {      return "請不要重復提交" ;    }  }  }
生成唯一標識接口
@RestController@RequestMapping("/generate")public class GenerateController {  private final StringRedisTemplate stringRedisTemplate ;  public GenerateController(StringRedisTemplate stringRedisTemplate) {    this.stringRedisTemplate = stringRedisTemplate ;  }    @GetMapping("/token")  public String token() {    String token = UUID.randomUUID().toString().replace("-", "") ;    // 將生成的token存入redis中,設置有效期5分鐘    this.stringRedisTemplate.opsForValue().setIfAbsent(PreventDuplicateAspect.PREVENT_PREFIX_KEY + token, token, 5 * 60, TimeUnit.SECONDS) ;    return token ;  }  }
業(yè)務接口
@RestController@RequestMapping("/prevent")public class PreventController {  @PreventDuplicate  @GetMapping("/index")  public Object index() {    return "index success" ;  }  }
測試

先調(diào)用生成唯一接口獲取token值

圖片

調(diào)用業(yè)務接口,攜帶token值

第一次訪問, 正常

再次訪問

方式2:通過攔截器實現(xiàn)自定義攔截器
@Componentpublic class PreventDuplicateInterceptor implements HandlerInterceptor {  private final StringRedisTemplate stringRedisTemplate ;  public PreventDuplicateInterceptor(StringRedisTemplate stringRedisTemplate) {    this.stringRedisTemplate = stringRedisTemplate ;  }    @Override  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {    if (handler instanceof HandlerMethod hm) {      if (hm.hasMethodAnnotation(PreventDuplicate.class)) {        PreventDuplicate pd = hm.getMethodAnnotation(PreventDuplicate.class) ;                String key = pd.header() ;        String value = null ;        if (key != null && key.length() > 0) {          value = request.getHeader(key) ;        } else {          key = pd.param() ;          if (key != null && key.length() > 0) {            value = request.getParameter(key) ;          }        }                if (value == null || "".equals(value.trim())) {          response.setContentType("text/plain;charset=utf-8") ;          response.getWriter().println("非法請求") ;          return false ;        }                // 拼接rediskey        String prevent_key = PreventDuplicateAspect.PREVENT_PREFIX_KEY + value ;        // 判斷redis中是否存在當前請求中攜帶的唯一標識數(shù)據(jù), 刪除成功則存在        Boolean result = this.stringRedisTemplate.delete(prevent_key) ;        if (result != null && result.booleanValue()) {          return true ;        } else {          response.setContentType("text/plain;charset=utf-8") ;          response.getWriter().println("請不要重復提交") ;          return false ;        }      }    }    return true ;  }  }
配置攔截器
@Componentpublic class PreventWebConfig implements WebMvcConfigurer {  private final PreventDuplicateInterceptor duplicateInterceptor ;  public PreventWebConfig(PreventDuplicateInterceptor duplicateInterceptor) {    this.duplicateInterceptor = duplicateInterceptor ;  }    @Override  public void addInterceptors(InterceptorRegistry registry) {    registry.addInterceptor(this.duplicateInterceptor).addPathPatterns("/**") ;  }  }
測試

獲取token

第一次請求

再次請求

完畢?。?!

關(guān)鍵詞:

相關(guān)新聞

Copyright 2015-2020   三好網(wǎng)  版權(quán)所有 聯(lián)系郵箱:435 22 [email protected]  備案號: 京ICP備2022022245號-21