原因:用户或恶意访问,导致一个接口被重复提交多次

业务会出现问题,恶性BUG等

解决方式:

1、在页面提交接口的时候,想办法传输一个当前次接口访问的token令牌
2、使用redis保存这个token令牌,执行接口业务逻辑时,去redis校验这个令牌有没有重复

难点:

redis的操作没有事务回滚,如果想要满足事务的需求,需要自己编写一段lua脚本来实现事务

  1. @Transactional
  2. @Override
  3. public SubmitOrderResponseVo submitOrder(OrderSubmitVo submitVo) {
  4. SubmitOrderResponseVo resp = new SubmitOrderResponseVo();
  5. //下单:去创建订单,验令牌,验价格,锁库存……
  6. MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
  7. confirmVoThreadLocal.set(submitVo);
  8. // 1、验证令牌【令牌的对比和删除必须保证原子性】
  9. String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
  10. String orderToken = submitVo.getOrderToken();
  11. // script 0失败 1成功
  12. Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberResponseVo.getUserId()), orderToken);
  13. // 原子验证令牌和删除令牌
  14. if(result == 1L){
  15. // 令牌验证成功
  16. OrderCreateTo order = createOrder();
  17. // TODO 可以进行验价
  18. BigDecimal payAmount = order.getOrder().getPayAmount();
  19. BigDecimal payPrice = submitVo.getPayPrice();
  20. BigDecimal subtract = payAmount.subtract(payPrice);
  21. if(Math.abs(subtract.doubleValue()) < 0.01){
  22. // 验价成功,保存订单
  23. saveOrder(order);
  24. // 锁库存,只要有异常就回滚订单数据
  25. // 订单号,所有订单项
  26. WareSkuLockVo lockVo = new WareSkuLockVo();
  27. lockVo.setOrderSn(order.getOrder().getOrderSn());
  28. List<OrderItemVo> collect = order.getOrderItems().stream().map(item -> {
  29. OrderItemVo orderItemVo = new OrderItemVo();
  30. orderItemVo.setSkuId(item.getSkuId());
  31. orderItemVo.setCount(item.getSkuQuantity());
  32. orderItemVo.setTitle(item.getSkuName());
  33. return orderItemVo;
  34. }).collect(Collectors.toList());
  35. lockVo.setLocks(collect);
  36. R r = wareFeignService.orderLockStock(lockVo);
  37. if(r.getCode() == 0){
  38. // 锁成功了
  39. resp.setOrder(order.getOrder());
  40. }else{
  41. //锁定失败
  42. resp.setCode(3);
  43. throw new NoStockException();
  44. }
  45. }else{
  46. resp.setCode(2);
  47. }
  48. }else{
  49. // 令牌验证失败
  50. }
  51. return resp;
  52. }