如何解决重复提交的问题(幂等)
关键点:识别哪些提交是同样的提交
解决方法:在页面设置token,利用redis事务的原子性。比如,在trade页面,点击提交订单,会生成一个token嵌入在页面中。点击结算时会验证token,解决重复提交的问题。
代码:
controller"trade"
@GetMapping("trade") @LoginRequire public String trade(HttpServletRequest request){ String userId =(String)request.getAttribute("userId"); // 用户地址 列表 List<UserAddress> userAddressList = userService.getUserAddressList(userId); request.setAttribute("userAddressList",userAddressList); // 用户需要结账的商品清单 List<CartInfo> checkedCartList = cartService.getCheckedCartList(userId); BigDecimal totalAmount = new BigDecimal("0"); for (CartInfo cartInfo : checkedCartList) { BigDecimal cartInfoAmount = cartInfo.getSkuPrice().multiply(new BigDecimal(cartInfo.getSkuNum())); totalAmount= totalAmount.add(cartInfoAmount); } String token = orderService.genToken(userId);//生成token request.setAttribute("tradeNo",token);//request请求后面放token request.setAttribute("checkedCartList",checkedCartList); request.setAttribute("totalAmount",totalAmount); return "trade"; }
生成token的方法
@Override public String genToken(String userId) { //token type String key user:10201:trade_code value token String token = UUID.randomUUID().toString(); String tokenKey="user:"+userId+":trade_code"; Jedis jedis = redisUtil.getJedis(); jedis.setex(tokenKey,10*60,token); jedis.close(); return token; }
controller"submitOrder"
@PostMapping("submitOrder") @LoginRequire public String submitOrder(OrderInfo orderInfo,HttpServletRequest request){ String userId = (String) request.getAttribute("userId"); String tradeNo = request.getParameter("tradeNo");//拿到token boolean isEnableToken = orderService.verifyToken(userId, tradeNo);//验证token if(!isEnableToken){ request.setAttribute("errMsg","页面已失效,请重新结算!"); return "tradeFail"; } } String orderId = orderService.saveOrder(orderInfo); // 删除购物车信息 // xxxx return "redirect://payment.gmall.com/index?orderId="+orderId; }
验证token service
@Override public boolean verifyToken(String userId, String token) { String tokenKey="user:"+userId+":trade_code"; Jedis jedis = redisUtil.getJedis(); String tokenExists = jedis.get(tokenKey); jedis.watch(tokenKey);//监控tokenkey Transaction transaction = jedis.multi();//开启事务 if(tokenExists!=null&&tokenExists.equals(token)){ transaction.del(tokenKey);//事务的删除,会在queue里面 } List<Object> list = transaction.exec();//事务的提交,成功返回1,不成功会不进行任何操作,返回nill if(list!=null&&list.size()>0&&(Long)list.get(0)==1L){ return true; }else{ return false; } }