首页 > 编程知识 正文

redis做接口幂等,redis 幂等性

时间:2023-05-04 09:19:32 阅读:188883 作者:15

redis实现接口幂等性 1. 说明

幂等性的概念:任意多次执行所产生得影响均与一次执行的影响相同,对数据库的影响只能是一次性的,不能重复处理。在实际项目中,在客户端没限制幂等性,重复调用接口,导致接口数据重复写入数据。
解决接口幂等性有多种方式,建立数据库唯一索引,乐观锁或悲观锁,程序先查询后判断,唯一标识机制等。本文介绍使用redis生成唯一标识来限制接口重复提交,实现接口幂等性。

redis实现流程图:

2. 实现

引入依赖

<!-- 使用springboot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.16</version> </dependency>

yml文件配置

# 添加redis 配置redis: database: 1 host: 127.0.0.1 port: 6379 timeout: 6000ms # 连接超时时长(毫秒) jedis: pool: max-active: 1000 # 连接池最大连接数 max-wait: -1ms # 连接池最大阻塞等待时间 max-idle: 10 # 连接池中的最大空闲连接 min-idle: 5 # 连接池中的最小空闲连接

redisUtil工具类

@Componentpublic class RedisUtil { @Autowired private RedisTemplate redisTemplate; /** * 写入缓存 * * @param key * @param value * @return */ public boolean set(final String key, Object value) { boolean result = false; try { ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); operations.set(key, value); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * 写入缓存设置时间 * * @param key * @param value * @param expireTime * @return */ public boolean setEx(final String key, Object value, long expireTime) { boolean result = false; try { ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * 读取缓存 * * @param key * @return */ public Object get(final String key) { Object result = null; ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); result = operations.get(key); return result; } /** * 删除对应的value * * @param key */ public boolean remove(final String key) { if (exists(key)) { Boolean delete = redisTemplate.delete(key); return delete; } return false; } /** * 判断key是否存在 * * @param key * @return */ public boolean exists(final String key) { boolean result = false; ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); if (Objects.nonNull(operations.get(key))) { result = true; } return result; }}

注解引入,自定义注解,自定义注解作用是使用该注解的方法实现幂等性。通过反射机制扫描到该注解就会在方法上实现自动幂等,使用ElementType.METHOD只能作用在方法上。

@Target({ElementType.METHOD})//作用到方法上@Retention(RetentionPolicy.RUNTIME)public @interface Idempotent {}

创建生成唯一标识方法,后台使用uuid生成唯一标识,存入redis中,并设置过期时间。

//使用uuid为redis的key String ideMark = UUID.randomUUID().toString(); //存入redis 设置过期时间为一天 24 * 60 * 60 * 1000 boolean flag = redisUtil.setEx(ideMark.toString(), ideMark.toString(), 60*1000); if (!flag) { throw new ApiException(ApiCode.REPEAT_OPERATION); } return ideMark;

校验唯一标识,从请求头获取校验幂等的唯一标识,并查找redis,若查询到了,则删除redis中的唯一标识,放行方法业务逻辑,若查询不到redis,则重复请求。

//从请球头获取 String ideMark = request.getHeader(IDEA_MARK); // 请求头header中不存在ideMark if (StringUtils.isEmpty(ideMark)) { throw new ApiException(ApiCode.REPEAT_OPERATION); //return false; } // ideMark从redis查询不到 if (!redisUtil.exists(ideMark)) { throw new ApiException(ApiCode.REPEAT_OPERATION); //return false; } //删除redis中的key boolean remove = redisUtil.remove(ideMark); if (!remove) { throw new ApiException(ApiCode.REPEAT_OPERATION); //return false; } return true;

拦截器

/** * 拦截器的配置:配置幂等性拦截器处理 AuthInterceptor */@Configurationpublic class WebMvcConfiguration extends WebMvcConfigurationSupport { @Bean public AuthInterceptor authInterceptor() { return new AuthInterceptor(); } /** * 拦截器配置 * * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor()); super.addInterceptors(registry); } //静态资源拦截 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { super.addResourceHandlers(registry); }}

拦截处理:扫描拦截到注解@Idempotent方法,调用校验方法校验唯一标识是否正确,若失败则抛出异常信息。

@Slf4jpublic class AuthInterceptor extends HandlerInterceptorAdapter { @Autowired private IdeMarkService ideMarkService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler; //获取到方法 Method method = handlerMethod.getMethod(); //被@Idempotent注解标记的扫描 Idempotent methodAnnotation = method.getAnnotation(Idempotent.class); if (methodAnnotation != null) { ideMarkService.checkIdeMark(request);// 幂等性校验, 校验通过则放行, 校验失败则抛出异常 } return true; }}

编写测试接口

/** * redis 使用幂等性 */@RestController@RequestMapping("/ide/傻傻的鱼")public class IdeMarkController { @Autowired private IdeMarkService ideMarkService; /** * 创建幂等性标识 * @return */ @GetMapping("/createIdeMark") public Object createIdeMark(){ return RData.ok(ideMarkService.createIdeMark()); } /** * 测试幂等性 * @return */ @Idempotent @GetMapping("/test/Idempotence") public Object testIdempotence() { String ideMark = "幂等测试成功"; return RData.ok(ideMark) ; }}

测试校验幂等性,先获取唯一标识,将唯一标识存到调用业务代码的header里面,再调用业务方法测试接口幂等性。

第一次调用业务方法

第二次调用业务方法

3.总结

本文介绍使用redis实现接口幂等性,主要使用redis数据存入,注解拦截处理,校验redis的key,实现逻辑比较简单,实现写入数据接口幂等性十分重要,在接口被客户端调用时,在不影响业务逻辑的情况,保证数据不重复写入数据数据库,防止脏数据和乱数据的写入。

github上源码:
https://github.com/wenxiangmeng/idempotent

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。