一、订单确认页功能流程图
1、进入登录确认页之前会,先进入登录拦截器
同样是使用ThreadLocal来存储用户的登录信息,从请求的request中获取登录信息
@Component
public class LoginUserInterceptor implements HandlerInterceptor {public static ThreadLocal<MemberRespVo> loginUser = new ThreadLocal<>();/*** 在目标请求之前执行该方法** @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String requestURI = request.getRequestURI();boolean match = new AntPathMatcher().match("/order/order/queryOrderBySn/**", requestURI);if (match) {return true;}// 获取登录的用户MemberRespVo attribute = (MemberRespVo) request.getSession().getAttribute(AuthServerConstant.LOGIN_USER);if (attribute != null) {// 已登录,将登录用户信息存放到threadLocal中,并放行loginUser.set(attribute);return true;} else {// 未登录,重定向到用户登录页request.getSession().setAttribute("msg", "请先进行登录");response.sendRedirect("http://auth.gulimall.com/login.html");return false;}}
}
2、查询订单确认页需要的数据
/*** 查询订单确认页需要的数据** @return* @throws ExecutionException* @throws InterruptedException*/@Overridepublic OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {OrderConfirmVo confirmVo = new OrderConfirmVo();MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();log.info("主线程:{}", Thread.currentThread().getId());// 获取之前的请求RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();// 收货地址列表 异步任务一CompletableFuture<Void> getAddressTask = CompletableFuture.runAsync(() -> {log.info("getAddress线程:{}", Thread.currentThread().getId());// 每一个线程都来共享之前的请求数据RequestContextHolder.setRequestAttributes(requestAttributes);List<MemberAddressVo> address = memberFeginService.getAddress(memberRespVo.getId());confirmVo.setAddress(address);}, executor);// 远程查询购物车中所有选中的购物项 异步任务二CompletableFuture<Void> getCartItemsTask = CompletableFuture.runAsync(() -> {log.info("getUserCartItems线程:{}", Thread.currentThread().getId());// 每一个线程都来共享之前的请求数据,在Feign的拦截器中就不会丢失老请求的数据RequestContextHolder.setRequestAttributes(requestAttributes);List<OrderItemVo> userCartItems = cartFeginService.getUserCartItems();confirmVo.setItems(userCartItems);}, executor).thenRunAsync(() -> {// 查询商品的库存信息,判断该商品是否有库存量List<OrderItemVo> items = confirmVo.getItems();if (items != null && !items.isEmpty()) {List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());// 调用库存服务R hasStock = wareSkuFeginService.getSkuHasStock(collect);if (hasStock != null) {List<SkuHasStockVo> data = hasStock.getData(new TypeReference<List<SkuHasStockVo>>() {});if (CollectionUtils.isNotEmpty(data)) {Map<Long, Boolean> hasStockMap = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock));confirmVo.setStocks(hasStockMap);}}}}, executor);// 用户积分Integer integration = memberRespVo.getIntegration();confirmVo.setIntegration(integration);// 防重令牌String token = UUID.randomUUID().toString().replace("-", "");confirmVo.setOrderToken(token);stringRedisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), token, 30, TimeUnit.MINUTES);// 其他数据自动计算CompletableFuture.allOf(getAddressTask, getCartItemsTask).get();return confirmVo;}
2.1 获取收货地址列表信息
// 收货地址列表 异步任务一CompletableFuture<Void> getAddressTask = CompletableFuture.runAsync(() -> {log.info("getAddress线程:{}", Thread.currentThread().getId());// 每一个线程都来共享之前的请求数据RequestContextHolder.setRequestAttributes(requestAttributes);List<MemberAddressVo> address = memberFeginService.getAddress(memberRespVo.getId());confirmVo.setAddress(address);}, executor);
通过openFeign远程调用获取收货地址信息
@FeignClient("gulimall-member")
public interface MemberFeginService {/*** 返回会员的所有收货地址列表* @param memberId* @return*/@GetMapping("/member/memberreceiveaddress/{memberId}/address")List<MemberAddressVo> getAddress(@PathVariable("memberId") Long memberId);
}
2.2 设置防重令牌
防重令牌的作用在用户下订单的时候为了防止用户重复提交的情况
// 防重令牌String token = UUID.randomUUID().toString().replace("-", "");confirmVo.setOrderToken(token);
二、用户下订单
/*** 下单* @param vo* @return* @Transactional:本地事务,在分布式系统下,只能控制自己的回滚,控制不了其他服务的回滚,这里只能使用分布式事务处理* @GlobalTransactional:全局事务,使用seata解决分布式事务问题。由于seata处理的方式比较麻烦,后改为RabbitMQ的延迟队列的方式处理分布式事务的问题。*/
// @GlobalTransactional@Transactional@Overridepublic SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {SubmitOrderResponseVo response = new SubmitOrderResponseVo();response.setCode(0);MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();orderSubmitLocal.set(vo);/*** 验证令牌的原因是保证接口的幂等性,即防止重复提交订单* 1、验证令牌【令牌的获取、令牌的对比和删除三个操作必须保证原子性】* 0:令牌失败* 1:删除成功*/// 前端传过来的tokenString orderToken = vo.getOrderToken();// 使用redis的lua脚本来保证原子性String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";// 原子验证令牌和删除令牌 【0 -》令牌验证失败,1-》删除成功】Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken);if(result == 0L){// 令牌验证失败response.setCode(1);return response;}else{// 令牌验证成功,准备下单操作// 1、创建订单,订单项等信息OrderCreateTo order = createOrder();// 2、验价BigDecimal payAmount = order.getOrder().getPayAmount();BigDecimal payPrice = vo.getPayPrice();if(Math.abs(payPrice.subtract(payAmount).doubleValue()) <= 0.01){// 金额对比成功// 3、保存订单saveOrder(order);// 4、库存锁定,只要有异常需要回滚订单数据WareSkuLockVo wareSkuLockVo = new WareSkuLockVo();wareSkuLockVo.setOrderSn(order.getOrder().getOrderSn());List<OrderItemEntity> orderItems = order.getOrderItems();List<OrderItemVo> collect = orderItems.stream().map(item -> {OrderItemVo orderItemVo = new OrderItemVo();orderItemVo.setSkuId(item.getSkuId());orderItemVo.setTitle(item.getSkuName());orderItemVo.setCount(item.getSkuQuantity());return orderItemVo;}).collect(Collectors.toList());wareSkuLockVo.setLocks(collect);//TODO 远程锁库存 ,库存成功了,但是网络原因超时了,订单回滚,库存不滚R r = wareSkuFeginService.orderLockStock(wareSkuLockVo);if(r.getCode() == 0){response.setOrder(order.getOrder());// TODO: 远程扣减积分 出现异常 如果使用传统的方式,那么订单回滚,锁定库存不回滚// int i = 10/0;// 将订单消息存入到延迟队列里面,一分钟后(真实情况下,是30分钟未支付,会自动取消订单),如果状态为待支付,则执行关单操作rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",order.getOrder());return response;}else{// 商品库存不足response.setCode(3);return response;}}else{// 金额对比失败response.setCode(2);return response;}}}