概述
在秒杀场景或情况下,用户多次发送请求会导致订单表中有多条数据的情况,为此要实现的功能是接口幂等,用户无论发多少次请求都只能创建一个单子
思路
时序图
首先客户端请求token接口,获取到token,服务端生成token并在redis中存一份,每次请求的时候,客户端将token带过来,由拦截器检验token,token存在redis中则说明是第一次请求,将数据写入数据库中,并删除redis中的token,第二次客户端再携带token时,去redis中查,如果redis中没有,那么说明是第二次请求了返回重复操作提示
demo
首先定义一个幂等性注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {
}
幂等性拦截器
public class IdempotentTokenInterceptor implements HandlerInterceptor {
@Autowired
private TokenService tokenService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
ApiIdempotent apiIdempotent = handlerMethod.getMethod().getAnnotation(ApiIdempotent.class);
if (apiIdempotent != null) {
tokenService.checkToken(request);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
拦截器配置类
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(idempotentTokenInterceptor());
}
@Bean
public IdempotentTokenInterceptor idempotentTokenInterceptor() {
return new IdempotentTokenInterceptor();
}
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
token Service类
public interface TokenService {
String createToken();
void checkToken(HttpServletRequest request) throws Exception;
}
token ServiceImpl类
@Service
public class TokenServiceImpl implements TokenService {
private static final String TOKEN_NAME = "token";
@Autowired
StringRedisTemplate stringRedisTemplate;
@Override
public String createToken() {
String tokenValue = "idempotent:token:" + UUID.randomUUID().toString();
stringRedisTemplate.opsForValue().set(tokenValue, "0", 60, TimeUnit.SECONDS);
return tokenValue;
}
@Override
public void checkToken(HttpServletRequest request) throws Exception {
String token = request.getHeader(TOKEN_NAME);
if (StringUtils.isBlank(token)) {
token = request.getParameter(TOKEN_NAME);
if (StringUtils.isBlank(token)) {
throw new Exception("非法参数");
}
}
if (!stringRedisTemplate.hasKey(token)) {
throw new Exception("请勿重复操作");
}
boolean del = stringRedisTemplate.delete(token);
if (!del) {
throw new Exception("请勿重复操作");
}
}
}
测试
这里我们用Jmeter和postfox来做测试,Jmeter做用户并发请求,postfox获取token
首先测试计划中添加变量
接着在Jmeter线程组中右键添加事务控制器
在事务控制器中添加HTTP信息头管理器,设置token,token通过apifox拿 在添加一个HTTP请求,设置好相应的数据参数
最后在线程组处添加一个查看结果树
这里模拟的是一个用户请求n的并发情况,本来想模拟n个用户m次请求,但是疏于对jmeter的使用,为此暂且先考虑1个用户n次请求的情况接下来启动看看
可以看到请求了n次,只有一个数据被写入的状况,也就大体实现了幂等性,往后看看模拟n个用户m次请求的情况,是否是满足的,我想应该是没用问题的 补充几个StringRedisTemplate的使用方法
第一个参数是key,第二个参数是value,第三个参数时间,第四个参数是时间单位,通常为TimeUnit中的枚举类
stringRedisTemplate.opsForValue().set(tokenValue, "0", 60, TimeUnit.SECONDS);
stringRedisTemplate.hasKey:判断相应的key是否在Redis中 stringRedisTemplate.delete(key):在Redis中删除相应的key值,删除成功返回true,否则返回false,如此
|