使用拦截器+Redis实现接口幂等

文章目录

  • 使用拦截器+Redis实现接口幂等
    • 1.思路分析
    • 2.具体实现
      • 2.1 创建redis工具类
      • 2.2 自定义幂等注解
      • 2.2 自定义幂等拦截器
      • 2.3 注入拦截器到容器
    • 3.测试

使用拦截器+Redis实现接口幂等

1.思路分析

接口幂等有很多种实现方式,拦截器/AOP+Redis,拦截器/AOP+本地缓存等等,本文讲解一下通过拦截器+Redis实现幂等的方式。

其原理就是在拦截器中拦截请求,然后根据一个标识符去redis中查询是否已经存在,如果存在,则说明当前请求正在处理,抛出异常告诉前端请勿重复请求。

标识符:一般可以使用token+methodType+uri作为标识符,具体业务具体分析。

2.具体实现

2.1 创建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 {@Resourceprivate 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;}/*** 写入缓存设置时效时间** @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;}/*** 判断缓存中是否有对应的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);}/*** 删除对应的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 结果*/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 结果*/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设置为该注解的属性,在拦截器中判断方法上是否有该注解,如果有该注解,则说明当前方法需要做幂等校验。

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 {/*** 限定时间(秒)* 限制多少秒内,每个用户只能请求一次该接口。*/long seconds() default 1;
}

2.2 自定义幂等拦截器

定义幂等接口用于拦截处理请求。

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 {@Resourceprivate RedisUtils redisUtils;/*** @param request  请求* @param response 响应* @param handler  处理* @return 结果* @throws Exception 异常*/@Overridepublic 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();//tokenString 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;}//此处转MD5的原因就是token长度太长了,转成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(), "请求已提交,请勿重复请求");write(response, JSON.toJSONString(resultData));return false;}//若为空则为第一次请求return redisUtils.set(key, 1, seconds);}/*** 返回结果到前端** @param response 响应* @param body     结果* @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.3 注入拦截器到容器

将拦截器注册到容器中。

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 {@Resourceprivate AutoIdempotentInterceptor autoIdempotentInterceptor;@Overridepublic 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) {//业务代码.....}
}

请求该接口,如果在60s内再次请求,就会返回重复请求的结果。seconds具体值设置多少由该接口的实际响应时间为标准,默认值为1秒。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/73904.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NLP的tokenization

GPT3.5的tokenization流程如上图所示&#xff0c;以下是chatGPT对BPE算法的解释&#xff1a; BPE&#xff08;Byte Pair Encoding&#xff09;编码算法是一种基于统计的无监督分词方法&#xff0c;用于将文本分解为子词单元。它的原理如下&#xff1a; 1. 初始化&#xff1a;将…

Docker vs. Kubernetes:选择合适的场景

在决定使用 Docker 还是 Kubernetes 之前&#xff0c;让我们看看一些实际的场景&#xff0c;以便更好地理解它们的适用性。 使用 Docker 的场景 假设您正在开发一个微服务应用程序&#xff0c;其中每个微服务都需要一些特定的依赖项和环境。在这种情况下&#xff0c;Docker 是一…

CSS 背景属性

前言 背景属性 属性说明background-color背景颜色background-image背景图background-repeat背景图平铺方式background-position背景图位置background-size背景图缩放background-attachment背景图固定background背景复合属性 背景颜色 可以使用background-color属性来设置背景…

opencv直方图与模板匹配

import cv2 #opencv读取的格式是BGR import numpy as np import matplotlib.pyplot as plt#Matplotlib是RGB %matplotlib inline def cv_show(img,name):cv2.imshow(name,img)cv2.waitKey()cv2.destroyAllWindows() 直方图 cv2.calcHist(images,channels,mask,histSize,ran…

Web菜鸟入门教程 - Swagger实现自动生成文档

如果是一个人把啥都开发了&#xff0c;那用不到Swagger-UI&#xff0c;但一般情况是前后端分离的&#xff0c;所以就需要告诉前端开发人员都有哪些接口&#xff0c;传入什么参数&#xff0c;怎么调用&#xff0c;返回什么。有了Swagger-UI就能把这部分文档编写的业务给省去了。…

高效反编译luac文件

对于游戏开发人员,有时候希望从一些游戏apk中反编译出源代码,进行学习,但是如果你触碰到法律边缘,那么你要非常小心。 这篇文章,我针对一些用lua写客户端或者服务器的编译过的luac文件进行反编译,获取其源代码的过程。 这里我不赘述如何反编译解压apk包的过程了,只说重点…

Spring解决循环依赖问题

一、什么是循环依赖&#xff1f; 例如&#xff0c;就是A对象依赖了B对象&#xff0c;B对象依赖了A对象。&#xff08;下面的代码属于属性的循环依赖&#xff0c;也就是初始化阶段的循环依赖&#xff0c;区别与底下构造器的循环依赖&#xff09; // A依赖了Bclass A{public B b;…

深入理解 go协程 调度机制

Thread VS Groutine 这里主要介绍一下Go的并发协程相比于传统的线程 的不同点&#xff1a; 创建时默认的stack大小 JDK5 以后Java thread stack默认大小为1MC 的thread stack 默认大小为8MGrountine 的 Stack初始化大小为2K 所以Grountine 大批量创建的时候速度会更快 和 …

Photoshop制作漂亮光泽感3D按钮

原文链接(https://img-blog.csdnimg.cn/45472c07f29944458570b59fe1f9a0e0.png)

【数据结构与算法】十大经典排序算法-归并排序

&#x1f31f;个人博客&#xff1a;www.hellocode.top &#x1f3f0;Java知识导航&#xff1a;Java-Navigate &#x1f525;CSDN&#xff1a;HelloCode. &#x1f31e;知乎&#xff1a;HelloCode &#x1f334;掘金&#xff1a;HelloCode ⚡如有问题&#xff0c;欢迎指正&#…

在vue中使用swiper轮播图(搭配watch和$nextTick())

在组件中使用轮播图展示图片信息&#xff1a; 1.下载swiper,5版本为稳定版本 cnpm install swiper5 2.在组件中引入swiper包和对应样式&#xff0c;若多组件使用swiper&#xff0c;可以把swiper引入到main.js入口文件中&#xff1a; import swiper/css/swiper.css //引入swipe…

Redis基础概念和数据类型详解

目录 1.什么是Redis&#xff1f; 2.为什么要使用Redis&#xff1f; 3.Redis为什么这么快&#xff1f; 4.Redis的使用场景有哪些&#xff1f; 5.Redis的基本数据类型 5.1 5种基础数据类型 5.1.1 String字符串 5.1.2 List列表 5.1.3 Set集合 5.1.4 Hash散列 5.1.5 Zset有序集…