苍穹外卖实战从购物车到支付的完整流程解析附避坑指南外卖平台的核心业务流程中从用户添加商品到购物车、下单再到支付的完整链路是最关键的环节之一。这个流程看似简单但背后涉及复杂的业务逻辑和技术实现。本文将深入剖析这一流程的每个环节帮助开发者掌握其中的技术要点和常见问题。1. 购物车系统的设计与实现购物车作为用户下单前的临时存储区其设计直接影响用户体验。一个高效的购物车系统需要考虑以下几个关键点数据结构设计通常采用Redis或数据库存储用户购物车数据商品去重逻辑相同商品的不同规格需要区分处理实时计算商品总价、优惠金额等需要实时更新购物车表的核心字段设计示例字段名类型说明idbigint主键IDuser_idbigint用户IDdish_idbigint菜品IDsetmeal_idbigint套餐IDdish_flavorvarchar菜品口味numberint数量amountdecimal单价create_timedatetime创建时间// 购物车添加商品示例代码 public void addCartItem(ShoppingCart shoppingCart) { // 查询当前商品是否已存在购物车 ShoppingCart cartItem shoppingCartMapper.selectOne(shoppingCart); if(cartItem ! null) { // 已存在则更新数量 cartItem.setNumber(cartItem.getNumber() shoppingCart.getNumber()); shoppingCartMapper.updateById(cartItem); } else { // 不存在则新增 shoppingCart.setCreateTime(LocalDateTime.now()); shoppingCartMapper.insert(shoppingCart); } }提示购物车数据建议设置过期时间避免长期占用存储空间。通常可设置为30天未更新则自动清除。2. 下单流程的关键技术点用户点击去结算后系统需要完成从购物车到订单的转换。这一过程涉及多个重要环节2.1 订单数据校验在下单前必须进行严格的校验包括收货地址有效性验证购物车商品库存检查用户余额/支付能力评估// 下单前的校验逻辑示例 public void validateBeforeOrder(OrdersSubmitDTO ordersSubmitDTO) { // 检查收货地址 AddressBook addressBook addressBookMapper.selectById(ordersSubmitDTO.getAddressBookId()); if(addressBook null) { throw new BusinessException(收货地址不存在); } // 检查购物车是否为空 Long userId SecurityUtils.getUserId(); ListShoppingCart cartItems shoppingCartMapper.selectByUserId(userId); if(cartItems null || cartItems.isEmpty()) { throw new BusinessException(购物车为空); } // 检查商品库存 for(ShoppingCart item : cartItems) { if(item.getDishId() ! null) { Dish dish dishMapper.selectById(item.getDishId()); if(dish.getStatus() 0) { throw new BusinessException(商品dish.getName()已下架); } } } }2.2 订单数据组装订单数据通常分散在多个表中需要合理设计DTO和VO// 订单提交DTO示例 Data public class OrdersSubmitDTO { private Long addressBookId; // 地址簿ID private int payMethod; // 支付方式 private String remark; // 备注 private LocalDateTime estimatedDeliveryTime; // 预计送达时间 private Integer deliveryStatus; // 配送状态 private Integer tablewareNumber; // 餐具数量 private Integer tablewareStatus; // 餐具状态 private Integer packAmount; // 打包费 private BigDecimal amount; // 总金额 }2.3 事务处理订单创建涉及多表操作必须使用事务保证数据一致性Transactional public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) { // 1. 校验数据 validateBeforeOrder(ordersSubmitDTO); // 2. 保存订单主表 Orders order new Orders(); BeanUtils.copyProperties(ordersSubmitDTO, order); order.setNumber(generateOrderNumber()); orderMapper.insert(order); // 3. 保存订单明细 ListOrderDetail details buildOrderDetails(order.getId()); orderDetailMapper.insertBatch(details); // 4. 清空购物车 shoppingCartMapper.deleteByUserId(SecurityUtils.getUserId()); // 5. 返回VO return buildOrderSubmitVO(order); }注意在高并发场景下需要考虑使用分布式锁防止重复下单问题。3. 支付系统集成实战支付是外卖系统的核心功能之一微信支付是目前最常用的支付方式。集成微信支付需要注意以下要点3.1 支付流程时序商户系统调用微信支付统一下单接口获取prepay_id预支付交易会话标识调起客户端支付接收支付结果通知处理支付结果3.2 安全配置微信支付需要配置以下安全信息配置项说明获取方式appid小程序ID微信公众平台mchid商户号微信支付商户平台mchSerialNo商户证书序列号商户平台-账户中心-API安全privateKey商户私钥使用工具生成apiV3KeyAPIv3密钥商户平台-账户中心-API安全// 微信支付配置示例 ConfigurationProperties(prefix wechat.pay) Data public class WeChatPayProperties { private String appId; private String mchId; private String mchSerialNo; private String privateKeyPath; private String apiV3Key; private String notifyUrl; }3.3 支付结果通知处理支付结果通知需要特别注意数据安全和幂等性处理RestController RequestMapping(/notify) public class PayNotifyController { PostMapping(/payment) public String handleNotify(HttpServletRequest request) { // 1. 获取通知数据 String body getRequestBody(request); // 2. 数据解密 String plainText decryptData(body); // 3. 解析数据 JSONObject result JSON.parseObject(plainText); String outTradeNo result.getString(out_trade_no); // 4. 处理业务逻辑 orderService.handlePaySuccess(outTradeNo); // 5. 返回成功响应 return buildSuccessResponse(); } private String decryptData(String body) throws Exception { // 解密逻辑实现 // ... } }重要支付结果通知处理必须实现幂等性避免重复处理导致业务异常。4. 常见问题与解决方案在实际开发中外卖系统从购物车到支付的流程会遇到各种问题。以下是几个典型问题及解决方案4.1 购物车商品同步问题现象用户添加商品到购物车后商品信息发生变化如下架、价格调整解决方案下单时重新校验商品状态和价格购物车列表展示时添加已失效标识定期清理无效购物车项// 购物车商品同步检查示例 public ListCartItemVO getCartItems(Long userId) { ListShoppingCart cartItems shoppingCartMapper.selectByUserId(userId); ListCartItemVO result new ArrayList(); for(ShoppingCart item : cartItems) { CartItemVO vo new CartItemVO(); BeanUtils.copyProperties(item, vo); // 检查商品状态 if(item.getDishId() ! null) { Dish dish dishMapper.selectById(item.getDishId()); vo.setValid(dish ! null dish.getStatus() 1); vo.setCurrentPrice(dish ! null ? dish.getPrice() : item.getAmount()); } result.add(vo); } return result; }4.2 订单超时未支付处理现象用户下单后长时间未支付占用系统资源解决方案使用延迟队列处理超时订单订单状态设计考虑超时状态前端展示剩余支付时间// 订单超时处理示例 Scheduled(fixedDelay 60000) // 每分钟检查一次 public void processTimeoutOrders() { // 查询待支付超过30分钟的订单 LocalDateTime timeoutTime LocalDateTime.now().minusMinutes(30); ListOrders timeoutOrders orderMapper.selectTimeoutOrders(timeoutTime); for(Orders order : timeoutOrders) { order.setStatus(Orders.CANCELLED); order.setCancelReason(超时未支付); order.setCancelTime(LocalDateTime.now()); orderMapper.updateById(order); // 记录日志 log.info(订单超时取消{}, order.getNumber()); } }4.3 支付结果通知丢失现象支付成功但商户系统未收到通知导致订单状态未更新解决方案实现主动查询机制补偿增加人工处理后台设计对账系统// 支付结果查询补偿示例 public void checkPaymentStatus(String orderNumber) { Orders order orderMapper.selectByNumber(orderNumber); if(order ! null order.getPayStatus() 0) { // 调用微信支付查询接口 PaymentQueryResult result weChatPayService.queryPayment(orderNumber); if(SUCCESS.equals(result.getTradeState())) { // 更新订单状态 order.setPayStatus(1); order.setPayTime(result.getPayTime()); orderMapper.updateById(order); } } }在实际项目中我们还需要考虑分布式环境下的数据一致性问题、高并发下的性能优化等。建议在开发过程中建立完善的监控体系及时发现和处理异常情况。