赞
踩
在秒杀场景或情况下,用户多次发送请求会导致订单表中有多条数据的情况,为此要实现的功能是接口幂等,用户无论发多少次请求都只能创建一个单子
时序图
首先客户端请求token接口,获取到token,服务端生成token并在redis中存一份,每次请求的时候,客户端将token带过来,由拦截器检验token,token存在redis中则说明是第一次请求,将数据写入数据库中,并删除redis中的token,第二次客户端再携带token时,去redis中查,如果redis中没有,那么说明是第二次请求了返回重复操作提示
首先定义一个幂等性注解
@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(); } /** * 跨域 * * @return */ @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() { //通过UUID来生成token String tokenValue = "idempotent:token:" + UUID.randomUUID().toString(); //将token放入redis中,设置有效期为60S stringRedisTemplate.opsForValue().set(tokenValue, "0", 60, TimeUnit.SECONDS); return tokenValue; } /** * @param request */ @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)) { //没有携带token,抛异常,这里的异常需要全局捕获 throw new Exception("非法参数"); } } //token不存在,说明token已经被其他请求删除或者是非法的token if (!stringRedisTemplate.hasKey(token)) { throw new Exception("请勿重复操作"); } boolean del = stringRedisTemplate.delete(token); if (!del) { //token删除失败,说明token已经被其他请求删除 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,如此
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。