Loading... ## 案列 > 在一个秒杀系统里面,如果一个用户提交订单后再次点击提交,数据库就会生成两个订单记录,这种该怎么解决? > > 这就是幂等性问题。 ## 解决思路 <div class="tip inlineBlock success"> * 数据库建立唯一索引,可以保证最终插入数据库的只有一条数据 * token机制,每次接口请求前获取一个token,然后再下次请求的时候在请求的header个体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token * 悲观锁或者乐观锁,悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表) * 先查询后判断,首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行。 </div> ## 用第二种思路演示 > 1、redis工具类 ```java /** * redis封装 * * @author Ganhua * https://www.ganboy.blog */ @SuppressWarnings("all") @Component public class RedisEncapsulation { @Autowired private RedisTemplate redisTemplate; /** * 存入redis */ public boolean setValEx(String key, Object value, Long expireTime) { boolean result = false; try { ValueOperations<String,Object> valueOperations = redisTemplate.opsForValue(); valueOperations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * 是否有值 */ public boolean exists(String key){ return redisTemplate.hasKey(key); } /** * 删除key */ public boolean remove(String key){ if (exists(key)){ return redisTemplate.delete(key); } return false; } } ``` > 2、主要业务 ```java /** * @author Ganhua * https://www.ganboy.blog * @date 2020/11/01 22:31 */ @Service public class TokenService { private final RedisEncapsulation redisEncapsulation; public TokenService(RedisEncapsulation redisEncapsulation) { this.redisEncapsulation = redisEncapsulation; } /** * 生成token */ public String createToken(){ String token = UUID.randomUUID().toString(); redisEncapsulation.setValEx(token,token,3600L); return token; } /** * 验证token */ public boolean checkToken(HttpServletRequest req) throws TdempotentException { String token = req.getHeader("token"); if(StringUtils.isEmpty(token)){ token = req.getParameter("token"); if(StringUtils.isEmpty(token)){ throw new TdempotentException("token不存在"); } } if (!redisEncapsulation.exists(token)){ throw new TdempotentException("重复的操作"); } boolean remove = redisEncapsulation.remove(token); if (!remove){ throw new TdempotentException("请重试"); } return true; } } ``` > 3、拦截器 ```java /** * @author Ganhua * https://www.ganboy.blog */ @Component public class IdempotentInterceptor implements HandlerInterceptor { @Autowired private TokenService tokenService; /** * 在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理; */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)){ return true; } Method method = ((HandlerMethod)handler).getMethod(); AutoIdeAnnotation annotation = method.getAnnotation(AutoIdeAnnotation.class); if (annotation!=null) { return tokenService.checkToken(request); } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * 在DispatcherServlet完全处理完请求之后被调用,可用于清理资源 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } } ``` > 4、注册拦截器 ```java /** * @author Ganhua * https://www.ganboy.blog */ @Configuration public class WebSecurityConfig implements WebMvcConfigurer { @Autowired private IdempotentInterceptor idempotentInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { // 注册拦截器 registry.addInterceptor(idempotentInterceptor); } } ``` > 5、其他 ```java /** * 自定义异常 * @author Ganhua * https://www.ganboy.blog */ public class TdempotentException extends Exception{ public TdempotentException(String message){ super(message); } } /** * 自定义注解 * @author Ganhua */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoIdeAnnotation { } /** * @author Ganhua * https://www.ganboy.blog */ @RestController public class DemoController { @Autowired private TokenService tokenService; /** * 获取token */ @GetMapping("/getToken") public String getToken(){ return tokenService.createToken(); } /** * 不允许重复操作 */ @PostMapping("/hello") @AutoIdeAnnotation public String hello(){ return "hello"; } @PostMapping("/hello1") public String hello1(){ return "hello"; } } /** * 全局异常 * @author Ganhua * https://www.ganboy.blog */ @RestControllerAdvice public class GlobalException { @ExceptionHandler(TdempotentException.class) public String idempotentException(TdempotentException e){ return e.getMessage(); } } ``` ## 演示 ![演示](https://cdn.ganhua.work/blog_static/images/2020-11/1.gif) 最后修改:2021 年 10 月 18 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 社会很单纯~复杂滴是人呐~谁能在乎我呀