使用拦截器+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.rhkb.cn/news/99658.html

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

相关文章

Linux —— 进程间通信(管道)

目录 一&#xff0c;进程间通信 二&#xff0c;管道 匿名管道 命名管道 一&#xff0c;进程间通信 进程间通信&#xff08;IPC&#xff0c;InterProcess Communication&#xff09;&#xff0c;即在不同进程之间进行信息的传播或交换&#xff1b;由于一般进程用户地址空间是…

JavaScript 中常用简写技巧总结

平时我们写代码时最高级的境界是自己写的东西别人看不懂&#xff01;哈哈哈&#xff01;分享一些自己常用的js简写技巧&#xff0c;长期更新&#xff0c;会着重挑选一些实用的简写技巧&#xff0c;使自己的代码更简洁优雅~ 这里只会收集一些大多数人不知道的用法&#xff0c;但…

PHP实现轻量级WEB服务器接收HTTP提交的RFID刷卡信息并回应驱动读卡器显示播报语音

本示例使用的读卡器&#xff1a;RFID网络WIFI无线TCP/UDP/HTTP可编程二次开发读卡器POE供电语音-淘宝网 (taobao.com) <?php mb_http_output(utf-8); $port88; $socket socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $bool socket_bind($socket, "0.0.0.0",…

宇宙原理:黑洞基础。

宇宙原理&#xff1a;黑洞基础TOC 黑洞的数理基础&#xff1a;一个由满数组成的数盘&#xff0c;经过自然演进&#xff0c;将会逐步稀疏化、最终会向纯数方案发展&#xff1b;纯数方案虽然只有{2}、无数&#xff08;虚拟&#xff09;、{0,1,2,3}&#xff08;虚拟&#xff09;、…

如何在 Ubuntu 中安装最新的 Python 版本

动动发财的小手&#xff0c;点个赞吧&#xff01; Python 是增长最快的主要通用编程语言。其原因有很多&#xff0c;例如其可读性和灵活性、易于学习和使用、可靠性和效率。 目前使用的 Python 有两个主要版本 – 2 和 3&#xff08;Python 的现在和未来&#xff09;&#xff1…

LeetCode--HOT100题(36)

目录 题目描述&#xff1a;146. LRU 缓存&#xff08;中等&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;146. LRU 缓存&#xff08;中等&#xff09; 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache…

AQS源码解读

文章目录 前言一、AQS是什么&#xff1f;二、解读重点属性statehead、tail 同步变量竞争acquire 同步变量释放 总结 前言 AQS是AbstractQueuedSynchronizer的缩写&#xff0c;也是大神Doug Lea的得意之作。今天我们来进行尽量简化的分析和理解性的代码阅读。 一、AQS是什么&a…

回归预测 | MATLAB实现WOA-SVM鲸鱼算法优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现WOA-SVM鲸鱼算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现WOA-SVM鲸鱼算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基本介绍程…

PostgreSQL中的密码验证方法

假设您想在客户端/服务器协议中实现密码身份验证方法。 您将如何做到这一点以及可能出现的问题是什么&#xff1f; 以下是 PostgreSQL 中如何完成此操作的故事。 password 一开始&#xff0c;PostgreSQL 只有 pg_hba.conf 中现在称为“password”的方法。 这是你能想象到的最…

CloudQuery:更好地管理你的 OceanBase 数据库

前言&#xff1a;作为 OceanBase 的生态合作伙伴&#xff0c;CloudQuery&#xff08;简称“CQ”&#xff09; 最新发布的社区版 2.2.0 新增了 OceanBase 数据库&#xff0c;为企业使用 OceanBase 数据库提供全面的支持。包括连接与认证、查询与分析、数据安全与权限管理&#x…

【仿写tomcat】三、通过socket读取http请求信息

仿写tomcat 建立Socket连接获取连接信息查看HTTP信息 建立Socket连接 这里我们也是创建一个专门管理socket的类 package com.tomcatServer.socket;import java.io.*; import java.net.ServerSocket;/*** 套接字存储** author ez4sterben* date 2023/08/15*/ public class Soc…

【校招VIP】java语言考点之List和扩容

考点介绍&#xff1a; List是最基础的考点&#xff0c;但是很多同学拿不到满分。本专题从两种实现子类的比较&#xff0c;到比较复杂的数组扩容进行分析。 『java语言考点之List和扩容』相关题目及解析内容可点击文章末尾链接查看&#xff01;一、考点题目 1、以下关于集合类…

操作系统练习:在Linux上创建进程,及查看进程状态

说明 进程在执行过程中可以创建多个新的进程。创建进程称为“父进程”&#xff0c;新的进程称为“子进程”。每个新的进程可以再创建其他进程&#xff0c;从而形成进程树。 每个进程都有一个唯一的进程标识符&#xff08;process identifier&#xff0c;pid&#xff09;。在L…

通过远程访问解决家人的电脑难题

家里老人使用电脑、手机等设备遇到问题&#xff0c;子女在其他城市没法现场解决&#xff0c;远程沟通很困难。有没有一种方法可以随时远程帮助家人解决电脑、手机相关问题&#xff1f; 通过远程桌面软件&#xff0c;你可以与父母一起实时解决问题。这样他们就不会一遍遍给你打…

Linux系统的目录结构

file system hierarchy standard文件系统层级标准&#xff0c;定义了在类Unix系统中的目录结构和目录内容。 即让用户了解到已安装软件通常放置于哪个目录下。 Linux目录结构的特点 使用树形目录结构来组织和管理文件。 整个系统只有一个根目录&#xff08;树根&#xff09;&a…

Java虚拟机(JVM):垃圾收集算法

目录 一、分代收集理论 二、标记-清除算法 三、标记-复制算法 四、标记-整理算法 一、分代收集理论 分代收集理论建立在两个分代假说之上&#xff1a; 1、弱分代假说&#xff1a;绝大多数对象都是朝生夕灭的。 2、强分代假说&#xff1a;熬过越多次垃圾收集过程的对象就…

rust入门系列之Rust介绍及开发环境搭建

Rust教程 Rust基本介绍 网站: https://www.rust-lang.org/ rust是什么 开发rust语言的初衷是&#xff1a; 在软件发展速度跟不上硬件发展速度&#xff0c;无法在语言层面充分的利用硬件多核cpu不断提升的性能和 在系统界别软件开发上&#xff0c;C出生比较早&#xff0c;内…

排序(七种排序)

1.插入排序 2.希尔排序 3.选择排序 4.堆排序 5.冒泡排序 6.快速排序 7.归并排序 1.插入排序 1.1思路 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为 止&#xff0c;得到一个新的有序序列 1.2实现 //插入排…

部署LVS-DR群集

目录 DR模式 LVS负载均衡群集 数据包流向分析 DR 模式的特点 LVS-DR中的ARP的问题 问题1 问题2 DR模式 LVS负载均衡群集部署 部署192.168.142.21&#xff08;DR服务器&#xff09; 部署192.168.142.22、192.168.142.23&#xff08;Web 服务器1、Web 服务器2&#xff0…

(成功踩坑)electron-builder打包过程中报错

目录 注意&#xff1a;文中的解决方法2&#xff0c;一定全部看完&#xff0c;再进行操作&#xff0c;有坑 背景 报错1&#xff1a; 报错2&#xff1a; 1.原因&#xff1a;网络连接失败 2.解决方法1&#xff1a; 3.解决方法2&#xff1a; 3.1查看缺少什么资源文件 3.2去淘…