使用攔截器+Redis實(shí)現(xiàn)接口冪思路詳解
使用攔截器+Redis實(shí)現(xiàn)接口冪等
1.思路分析
接口冪等有很多種實(shí)現(xiàn)方式,攔截器/AOP+Redis,攔截器/AOP+本地緩存等等,本文講解一下通過(guò)攔截器+Redis實(shí)現(xiàn)冪等的方式。
其原理就是在攔截器中攔截請(qǐng)求,然后根據(jù)一個(gè)標(biāo)識(shí)符去redis中查詢(xún)是否已經(jīng)存在,如果存在,則說(shuō)明當(dāng)前請(qǐng)求正在處理,拋出異常告訴前端請(qǐng)勿重復(fù)請(qǐng)求。
標(biāo)識(shí)符:一般可以使用token+methodType+uri作為標(biāo)識(shí)符,具體業(yè)務(wù)具體分析。
2.具體實(shí)現(xiàn)
2.1 創(chuàng)建redis工具類(lèi)
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工具類(lèi)
*
* @author 譚永強(qiáng)
* @date 2023-08-15
*/
@Component
public class RedisUtils {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 寫(xiě)入緩存
*
* @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;
}
/**
* 寫(xiě)入緩存設(shè)置時(shí)效時(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;
}
/**
* 判斷緩存中是否有對(duì)應(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);
}
/**
* 刪除對(duì)應(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è)置為該注解的屬性,在攔截器中判斷方法上是否有該注解,如果有該注解,則說(shuō)明當(dāng)前方法需要做冪等校驗(yàn)。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自動(dòng)冪等
* 該注解加在需要冪等的方法上,即可自動(dòng)上線(xiàn)方法的冪等。
*
* @author 譚永強(qiáng)
* @date 2023-08-15
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoIdempotent {
/**
* 限定時(shí)間(秒)
* 限制多少秒內(nèi),每個(gè)用戶(hù)只能請(qǐng)求一次該接口。
*/
long seconds() default 1;
}2.3 自定義冪等攔截器
定義冪等接口用于攔截處理請(qǐng)求。
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;
/**
* 自動(dòng)冪等攔截器
*
* @author 譚永強(qiáng)
* @date 2023-08-15
*/
@Component
public class AutoIdempotentInterceptor extends HandlerInterceptorAdapter {
@Resource
private RedisUtils redisUtils;
/**
* @param request 請(qǐng)求
* @param response 響應(yīng)
* @param handler 處理
* @return 結(jié)果
* @throws Exception 異常
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判斷請(qǐng)求是否為方法的請(qǐng)求
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod method = (HandlerMethod) handler;
//獲取方法中是否有冪等性注解
AutoIdempotent anno = method.getMethodAnnotation(AutoIdempotent.class);
//若注解為空則直接返回
if (Objects.isNull(anno)) {
return true;
}
//限定時(shí)間
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長(zhǎng)度太長(zhǎng)了,轉(zhuǎn)成md5短一些,此操作并不是必須的
String md5 = MD5Utils.md5Hex(token, StandardCharsets.UTF_8.toString());
//使用token+method+uri作為key值,此處需要通過(guò)key值確定請(qǐng)求的唯一性,也可以使用其他的組合,只要保證唯一性即可
String key = md5 + ":" + request.getMethod() + ":" + request.getRequestURI();
Object requestCountObj = redisUtils.get(key);
if (!ObjectUtils.isEmpty(requestCountObj)) {
//不為空,說(shuō)明不是第一次請(qǐng)求,直接報(bào)錯(cuò)
ResultData<String> resultData = ResultData.fail(ReturnCode.RC206.getCode(), "請(qǐng)求已提交,請(qǐng)勿重復(fù)請(qǐng)求");
write(response, JSON.toJSONString(resultData));
return false;
}
//若為空則為第一次請(qǐng)求
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 注入攔截器到容器
將攔截器注冊(cè)到容器中。
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 譚永強(qiáng)
* @date 2023-08-15
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private AutoIdempotentInterceptor autoIdempotentInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(autoIdempotentInterceptor);
}
}3.測(cè)試
@RestController
@RequestMapping("user")
public class SysUserController {
/**
* 用戶(hù)新增
*
* @param user 用戶(hù)信息
*/
@AutoIdempotent(seconds = 60)
@PostMapping("add")
public void add(@RequestBody SysUser user) {
//業(yè)務(wù)代碼.....
}
}請(qǐng)求該接口,如果在60s內(nèi)再次請(qǐng)求,就會(huì)返回重復(fù)請(qǐng)求的結(jié)果。seconds具體值設(shè)置多少由該接口的實(shí)際響應(yīng)時(shí)間為標(biāo)準(zhǔn),默認(rèn)值為1秒。

到此這篇關(guān)于使用攔截器+Redis實(shí)現(xiàn)接口冪思路詳解的文章就介紹到這了,更多相關(guān)Redis接口冪內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis在計(jì)數(shù)器和人員記錄的事務(wù)操作應(yīng)用小結(jié)
Redis是一個(gè)高性能的鍵值存儲(chǔ)系統(tǒng),專(zhuān)于處理計(jì)數(shù)器和事務(wù)操作,它提供了INCR、DECR等命令來(lái)進(jìn)行原子遞增或遞減操作,并通過(guò)MULTI、EXEC等命令實(shí)現(xiàn)事務(wù)操作,此外,Redis的Pipeline功能可減少網(wǎng)絡(luò)往返次數(shù),提高性能2024-10-10
Redis 設(shè)置密碼無(wú)效問(wèn)題解決
本文主要介紹了Redis 設(shè)置密碼無(wú)效問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
詳解Redis中key的命名規(guī)范和值的命名規(guī)范
這篇文章主要介紹了詳解Redis中key的命名規(guī)范和值的命名規(guī)范,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
redis簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了redis簡(jiǎn)介,Redis是一個(gè)開(kāi)源的,先進(jìn)的 key-value 存儲(chǔ)可用于構(gòu)建高性能,可擴(kuò)展的 Web 應(yīng)用程序的解決方案,有興趣的可以了解一下2017-08-08
Redis 操作多個(gè)數(shù)據(jù)庫(kù)的配置的方法實(shí)現(xiàn)
本文主要介紹了Redis 操作多個(gè)數(shù)據(jù)庫(kù)的配置的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Redis并發(fā)訪(fǎng)問(wèn)問(wèn)題詳細(xì)講解
本文主要介紹了Redis如何應(yīng)對(duì)并發(fā)訪(fǎng)問(wèn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-12-12
基于Redis無(wú)序集合如何實(shí)現(xiàn)禁止多端登錄功能
這篇文章主要給你大家介紹了關(guān)于基于Redis無(wú)序集合如何實(shí)現(xiàn)禁止多端登錄功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12
Redis遍歷海量數(shù)據(jù)集的幾種實(shí)現(xiàn)方法
Redis作為一個(gè)高性能的鍵值存儲(chǔ)數(shù)據(jù)庫(kù),廣泛應(yīng)用于各種場(chǎng)景,包括緩存、消息隊(duì)列、排行榜,本文主要介紹了Redis遍歷海量數(shù)據(jù)集的幾種實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-02-02
redis使用watch秒殺搶購(gòu)實(shí)現(xiàn)思路
這篇文章主要為大家詳細(xì)介紹了redis使用watch秒殺搶購(gòu)的實(shí)現(xiàn)思路,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02

