使用攔截器+Redis實現(xiàn)接口冪思路詳解
使用攔截器+Redis實現(xiàn)接口冪等
1.思路分析
接口冪等有很多種實現(xiàn)方式,攔截器/AOP+Redis,攔截器/AOP+本地緩存等等,本文講解一下通過攔截器+Redis實現(xiàn)冪等的方式。
其原理就是在攔截器中攔截請求,然后根據(jù)一個標(biāo)識符
去redis中查詢是否已經(jīng)存在,如果存在,則說明當(dāng)前請求正在處理,拋出異常告訴前端請勿重復(fù)請求。
標(biāo)識符:一般可以使用token+methodType+uri
作為標(biāo)識符,具體業(yè)務(wù)具體分析。
2.具體實現(xiàn)
2.1 創(chuàng)建redis工具類
import com.yunling.sys.config.exception.ParamValidateException; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; /** * Redis工具類 * * @author 譚永強 * @date 2023-08-15 */ @Component public class RedisUtils { @Resource private RedisTemplate<String, Object> redisTemplate; /** * 寫入緩存 * * @param key 建 * @param value 值 * @return 成功/失敗 */ public boolean set(final String key, Object value) { boolean result = false; try { ValueOperations<String, Object> operations = redisTemplate.opsForValue(); operations.set(key, value); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * 寫入緩存設(shè)置時效時間 * * @param key 鍵 * @param value 值 * @return 成功/失敗 */ public boolean set(final String key, Object value, Long expireTime) { boolean result = false; try { ValueOperations<String, Object> operations = redisTemplate.opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * 判斷緩存中是否有對應(yīng)的value * * @param key 鍵 * @return 成功/失敗 */ public boolean exists(final String key) { return Boolean.TRUE.equals(redisTemplate.hasKey(key)); } /** * 讀取緩存 * * @param key 鍵 * @return 成功/失敗 */ public Object get(final String key) { return redisTemplate.opsForValue().get(key); } /** * 刪除對應(yīng)的value * * @param key 鍵 * @return 成功/失敗 */ public boolean remove(final String key) { if (exists(key)) { return Boolean.TRUE.equals(redisTemplate.delete(key)); } return false; } /** * 遞增 * * @param key 鍵 * @param delta 要增加幾(大于0) * @return 結(jié)果 */ public Long incr(String key, long delta) { if (ObjectUtils.isEmpty(key)) { throw new ParamValidateException("key值不能為空"); } if (delta < 0) { throw new ParamValidateException("遞增因子必須大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 遞減 * * @param key 鍵 * @param delta 要減少幾(小于0) * @return 結(jié)果 */ public Long decr(String key, long delta) { if (ObjectUtils.isEmpty(key)) { throw new ParamValidateException("key值不能為空"); } if (delta < 0) { throw new ParamValidateException("遞減因子必須大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } }
2.2 自定義冪等注解
自定義冪等注解,將seconds
設(shè)置為該注解的屬性,在攔截器中判斷方法上是否有該注解,如果有該注解,則說明當(dāng)前方法需要做冪等校驗。
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自動冪等 * 該注解加在需要冪等的方法上,即可自動上線方法的冪等。 * * @author 譚永強 * @date 2023-08-15 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface AutoIdempotent { /** * 限定時間(秒) * 限制多少秒內(nèi),每個用戶只能請求一次該接口。 */ long seconds() default 1; }
2.3 自定義冪等攔截器
定義冪等接口用于攔截處理請求。
import com.alibaba.fastjson.JSON; import com.alibaba.nacos.common.utils.MD5Utils; import com.yunling.sys.annotate.AutoIdempotent; import com.yunling.sys.common.RedisUtils; import com.yunling.sys.common.ResultData; import com.yunling.sys.common.ReturnCode; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.annotation.Resource; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Objects; /** * 自動冪等攔截器 * * @author 譚永強 * @date 2023-08-15 */ @Component public class AutoIdempotentInterceptor extends HandlerInterceptorAdapter { @Resource private RedisUtils redisUtils; /** * @param request 請求 * @param response 響應(yīng) * @param handler 處理 * @return 結(jié)果 * @throws Exception 異常 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //判斷請求是否為方法的請求 if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod method = (HandlerMethod) handler; //獲取方法中是否有冪等性注解 AutoIdempotent anno = method.getMethodAnnotation(AutoIdempotent.class); //若注解為空則直接返回 if (Objects.isNull(anno)) { return true; } //限定時間 long seconds = anno.seconds(); //token String token = request.getHeader(HttpHeaders.AUTHORIZATION); if (Objects.isNull(token)) { ResultData<String> resultData = ResultData.fail(ReturnCode.ACCESS_DENIED.getCode(), "token不能為空"); write(response, JSON.toJSONString(resultData)); return false; } //此處轉(zhuǎn)MD5的原因就是token長度太長了,轉(zhuǎn)成md5短一些,此操作并不是必須的 String md5 = MD5Utils.md5Hex(token, StandardCharsets.UTF_8.toString()); //使用token+method+uri作為key值,此處需要通過key值確定請求的唯一性,也可以使用其他的組合,只要保證唯一性即可 String key = md5 + ":" + request.getMethod() + ":" + request.getRequestURI(); Object requestCountObj = redisUtils.get(key); if (!ObjectUtils.isEmpty(requestCountObj)) { //不為空,說明不是第一次請求,直接報錯 ResultData<String> resultData = ResultData.fail(ReturnCode.RC206.getCode(), "請求已提交,請勿重復(fù)請求"); write(response, JSON.toJSONString(resultData)); return false; } //若為空則為第一次請求 return redisUtils.set(key, 1, seconds); } /** * 返回結(jié)果到前端 * * @param response 響應(yīng) * @param body 結(jié)果 * @throws IOException 異常 */ private void write(HttpServletResponse response, String body) throws IOException { response.setContentType(MediaType.APPLICATION_JSON_VALUE); ServletOutputStream os = response.getOutputStream(); os.write(body.getBytes()); os.flush(); os.close(); } }
2.4 注入攔截器到容器
將攔截器注冊到容器中。
package com.yunling.sys.config; import com.yunling.sys.config.interceptor.AutoIdempotentInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; /** * 將攔截器注入到容器中 * * @author 譚永強 * @date 2023-08-15 */ @Configuration public class WebConfig implements WebMvcConfigurer { @Resource private AutoIdempotentInterceptor autoIdempotentInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(autoIdempotentInterceptor); } }
3.測試
@RestController @RequestMapping("user") public class SysUserController { /** * 用戶新增 * * @param user 用戶信息 */ @AutoIdempotent(seconds = 60) @PostMapping("add") public void add(@RequestBody SysUser user) { //業(yè)務(wù)代碼..... } }
請求該接口,如果在60s內(nèi)再次請求,就會返回重復(fù)請求的結(jié)果。seconds具體值設(shè)置多少由該接口的實際響應(yīng)時間為標(biāo)準(zhǔn),默認(rèn)值為1秒。
到此這篇關(guān)于使用攔截器+Redis實現(xiàn)接口冪思路詳解的文章就介紹到這了,更多相關(guān)Redis接口冪內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis在計數(shù)器和人員記錄的事務(wù)操作應(yīng)用小結(jié)
Redis是一個高性能的鍵值存儲系統(tǒng),專于處理計數(shù)器和事務(wù)操作,它提供了INCR、DECR等命令來進行原子遞增或遞減操作,并通過MULTI、EXEC等命令實現(xiàn)事務(wù)操作,此外,Redis的Pipeline功能可減少網(wǎng)絡(luò)往返次數(shù),提高性能2024-10-10詳解Redis中key的命名規(guī)范和值的命名規(guī)范
這篇文章主要介紹了詳解Redis中key的命名規(guī)范和值的命名規(guī)范,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12redis簡介_動力節(jié)點Java學(xué)院整理
這篇文章主要介紹了redis簡介,Redis是一個開源的,先進的 key-value 存儲可用于構(gòu)建高性能,可擴展的 Web 應(yīng)用程序的解決方案,有興趣的可以了解一下2017-08-08Redis 操作多個數(shù)據(jù)庫的配置的方法實現(xiàn)
本文主要介紹了Redis 操作多個數(shù)據(jù)庫的配置的方法實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03Redis遍歷海量數(shù)據(jù)集的幾種實現(xiàn)方法
Redis作為一個高性能的鍵值存儲數(shù)據(jù)庫,廣泛應(yīng)用于各種場景,包括緩存、消息隊列、排行榜,本文主要介紹了Redis遍歷海量數(shù)據(jù)集的幾種實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-02-02