介绍
Springboot是一个轻量级的Java Web框架,可以帮助开发者快速构建Web应用程序。在实际的开发中,经常需要对接口进行幂等性控制,以确保接口不会被重复调用,或者出现脏数据等问题。而Redis作为一个优秀的内存数据库,可以帮助我们实现这样的接口幂等性控制。本篇文章将介绍如何利用Redis实现接口幂等性拦截。
Redis介绍
Redis是一个基于内存的键值对存储数据库,提供了丰富的数据结构,支持多种数据操作,如:字符串、列表、集合、有序集合等。Redis的优点包括:高性能、高并发、数据持久化支持、可扩展性好等。在实际的开发中,Redis通常用于缓存、任务队列、分布式锁等场景。
什么是接口幂等性
接口幂等性是指无论调用多少次接口,结果都是一样的。在一些需要保证数据一致性的场景下,接口幂等性尤其重要。比如,一个支付接口,如果用户在支付过程中断网或者重复提交,那么可能会出现重复扣款的情况,这时候就需要对接口进行幂等性控制。
利用Redis实现接口幂等性控制
Step1:添加依赖
在pom.xml文件中添加Redis和Lettuce连接池的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.2</version>
</dependency>
Step2:添加配置
在application.yml文件中添加Redis连接配置:
spring:
redis:
host: 127.0.0.1 # Redis服务器地址
port: 6379 # Redis服务器端口
database: 0 # Redis数据库编号
password: # Redis密码
timeout: 5000 # 连接超时时间
lettuce:
pool:
max-active: 8 # 连接池最大连接数(负数表示不限制)
max-idle: 8 # 连接池最大空闲连接数
min-idle: 0 # 连接池最小空闲连接数
max-wait: -1 # 连接池最大等待时间(单位:毫秒,负数表示不限制)
Step3:编写拦截器
编写一个拦截器,拦截需要进行幂等性控制的接口。在拦截器中,可以通过Redis实现幂等性控制。具体实现思路是,对每个需要幂等性控制的接口生成一个唯一标识,将该标识作为Redis中的key,调用接口的请求参数作为Redis中的value,设置Redis的过期时间,并对该key进行加锁,防止并发问题。
@Slf4j
@Component
public class ApiIdempotentInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!request.getMethod().equals(RequestMethod.POST.name())) {
// 只拦截POST请求
return true;
}
String xAuthToken = request.getHeader("X-Auth-Token");
if (StringUtils.isBlank(xAuthToken)) {
throw new IllegalArgumentException("Missing X-Auth-Token");
}
// 获取接口唯一标识
String apiId = request.getRequestURI();
Map < String, String > paramMap = request.getParameterMap().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> String.join(",", e.getValue())));
String paramValue = paramMap.isEmpty() ? "" : new Gson().toJson(paramMap);
String key = xAuthToken + ":" + apiId + ":" + DigestUtils.md5DigestAsHex(paramValue.getBytes());
// 检查该接口是否已被调用
Boolean exists = redisTemplate.hasKey(key);
if (exists != null && exists) {
throw new IllegalArgumentException("The API [" + apiId + "] has been invoked");
}
ValueOperations < String, Object > valueOps = redisTemplate.opsForValue();
// 添加接口调用记录,并设置过期时间
if (!valueOps.setIfAbsent(key, request.getRequestURI())) {
throw new IllegalArgumentException("The API [" + apiId + "] has been invoked");
}
redisTemplate.expire(key, 10, TimeUnit.MINUTES);
log.info("ApiIdempotentInterceptor preHandle, key: {}, params: {}", key, paramValue);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (!request.getMethod().equals(RequestMethod.POST.name())) {
return;
}
// 删除接口调用记录,并释放锁
String xAuthToken = request.getHeader("X-Auth-Token");
String apiId = request.getRequestURI();
Map < String, String > paramMap = request.getParameterMap().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> String.join(",", e.getValue())));
String paramValue = paramMap.isEmpty() ? "" : new Gson().toJson(paramMap);
String key = xAuthToken + ":" + apiId + ":" + DigestUtils.md5DigestAsHex(paramValue.getBytes());
redisTemplate.delete(key);
log.info("ApiIdempotentInterceptor afterCompletion, key: {}, params: {}", key, paramValue);
}
}
Step4:添加拦截器
在WebMvcConfigurer中添加拦截器:
@Configuration
public class ApiIdempotentConfig implements WebMvcConfigurer {
@Autowired
private ApiIdempotentInterceptor apiIdempotentInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(apiIdempotentInterceptor).addPathPatterns("/api/**");
}
}
总结
本篇文章介绍了如何利用Redis实现接口幂等性控制。拦截器可以对需要进行幂等性控制的接口进行拦截,并通过Redis实现幂等性控制。通过对接口进行幂等性控制,可以避免重复调用接口、脏数据等问题,并提高接口的稳定性和可靠性。