SpringBoot项目实战:难忘外卖开发 —— 下单功能实现【12】

SpringBoot项目实战:难忘外卖开发 —— 下单功能实现【12】

引言

在本篇博客中,我们将深入探讨如何实现一个外卖系统中的下单功能。使用Spring Boot框架和一些常用的技术栈,如MyBatis,进行后端开发。在该功能中,用户能够提交订单,系统会进行一系列的业务处理,包括校验用户信息、购物车内容、地址信息等,最终生成订单并返回给前端。

功能概述

本示例实现了一个简化的外卖系统中的“下单”功能。用户通过提交包含地址、购物车信息的请求,系统会对请求进行验证,创建订单,并将购物车中的商品记录到订单明细表中,最后返回一个订单的简要信息。

主要模块

1. Controller 层

首先,创建一个 UserOrderController,提供一个下单的接口供前端调用。

/**
 * 用户下单
 * @param ordersSubmitDTO
 * @return
 */
@PostMapping("/submit")
public Result<OrderSubmitVO> userOrder(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {
    
    

    OrderSubmitVO orderSubmitVO = userOrderService.submitOrder(ordersSubmitDTO);

    return Result.success(orderSubmitVO);
}

在这个接口中,我们通过 @PostMapping("/submit") 来处理用户的下单请求,接收前端传来的 ordersSubmitDTO 对象。接着调用 userOrderService.submitOrder 方法处理订单逻辑,并返回一个 OrderSubmitVO 作为响应结果。

2. Service 层

服务层的 UserOrderServiceImpl 实现了下单的核心业务逻辑。它包括以下几个步骤:

  • 校验地址信息:判断用户传递的地址是否有效。
  • 校验购物车:检查用户购物车是否为空。
  • 创建订单:向 orders 表插入订单信息。
  • 插入订单明细:向 order_detail 表插入订单明细。
  • 清空购物车:下单成功后,清空用户的购物车。
@Service
public class UserOrderServiceImpl implements UserOrderService {
    
    

    @Autowired
    UserOrderMapper userOrderMapper;

    @Autowired
    UserOrderDetailMapper userOrderDetailMapper;

    @Autowired
    AddressBookMapper addressBookMapper;

    @Autowired
    ShoppingCartMapper shoppingCartMapper;

    /**
     * 用户下单
     * @param ordersSubmitDTO
     * @return
     */
    @Override
    public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {
    
    

        // 1. 校验地址信息
        AddressBook address = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
        if (address == null) {
    
    
            throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
        }

        Long currentId = BaseContext.getCurrentId();

        // 2. 校验购物车是否为空
        ShoppingCart shoppingCart = new ShoppingCart();
        shoppingCart.setUserId(currentId);
        List<ShoppingCart> shoppingCartList = shoppingCartMapper.installshopping_cart(shoppingCart);
        if (shoppingCartList == null || shoppingCartList.isEmpty()) {
    
    
            throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
        }

        // 3. 创建订单
        Orders order = new Orders();
        BeanUtils.copyProperties(ordersSubmitDTO, order);
        order.setOrderTime(LocalDateTime.now());
        order.setPayStatus(Orders.UN_PAID);
        order.setStatus(Orders.PAID);
        order.setNumber(String.valueOf(System.currentTimeMillis()));
        order.setPhone(address.getPhone());
        order.setConsignee(address.getConsignee());
        order.setUserId(currentId);

        Long orderId = userOrderMapper.insteOrder(order);

        // 4. 插入订单明细
        List<OrderDetail> orderDetails = new ArrayList<>();
        for (ShoppingCart cart : shoppingCartList) {
    
    
            OrderDetail orderDetail = new OrderDetail();
            BeanUtils.copyProperties(cart, orderDetail);
            orderDetail.setOrderId(orderId);
            orderDetails.add(orderDetail);
        }
        userOrderDetailMapper.installall(orderDetails);

        // 5. 清空购物车
        shoppingCartMapper.deleteshoppingcartAll(currentId);

        // 6. 封装返回结果
        OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()
                .orderTime(order.getOrderTime())
                .id(order.getId())
                .orderNumber(order.getNumber())
                .orderAmount(order.getAmount())
                .build();

        return orderSubmitVO;
    }
}

3. 核心业务逻辑详解

  1. 校验地址信息

在下单过程中,首先要验证用户提供的收货地址是否有效。系统通过 addressBookMapper.getById 方法查询数据库中是否存在该地址。若不存在,抛出 AddressBookBusinessException 异常。

  1. 校验购物车

接着,系统会查询当前用户的购物车是否为空。如果为空,抛出 ShoppingCartBusinessException 异常,提示用户购物车为空。

  1. 创建订单

如果购物车有商品,系统会创建一个新的 Orders 实体,填充用户提交的订单信息。此时,订单的状态设置为“未支付”,并生成一个唯一的订单号。然后,插入到数据库的 orders 表中。

  1. 插入订单明细

系统会根据购物车中的商品列表,生成对应的 OrderDetail 记录,并批量插入到 order_detail 表中。这些明细记录包含了订单中商品的详细信息。

  1. 清空购物车

订单提交成功后,购物车中的商品会被清空,确保不会重复下单。

  1. 返回结果

最后,系统返回一个简化的订单信息 OrderSubmitVO,供前端显示。该 VO(值对象)包含了订单的基本信息,如订单号、订单金额、订单时间等。

MyBatis 插入订单 SQL 语句解析在本篇博客中,我们将深入探讨MyBatis中的一个 insert 操作。这个操作将用户下单的相关信息插入到 orders 表中,关键点在于如何使用 useGeneratedKeyskeyProperty 来获取插入后生成的主键,并将其设置到实体对象中。

下面是插入订单的 SQL 语句部分:

<insert id="insteOrder" useGeneratedKeys="true" keyProperty="id">
    insert into orders (number, status, user_id, address_book_id, order_time, checkout_time,
                        pay_method, pay_status, amount, remark, phone, address, consignee,
                        estimated_delivery_time, delivery_status, pack_amount, tableware_number, tableware_status)
    values (#{number}, #{status}, #{userId}, #{addressBookId},
            #{orderTime}, #{checkoutTime}, #{payMethod}, #{payStatus}, #{amount}, #{remark},
            #{phone}, #{address}, #{consignee}, #{estimatedDeliveryTime},
            #{deliveryStatus}, #{packAmount}, #{tablewareNumber},
            #{tablewareStatus})
</insert>

关键属性详解

  1. useGeneratedKeys="true":
    • 这个属性告诉 MyBatis 在执行插入操作后自动获取数据库生成的主键值。常用于自增主键的情况。
    • 例如,在 orders 表中,id 字段通常是自增主键,插入新记录时数据库会自动生成这个 id
  2. keyProperty="id":
    • keyProperty 指定插入操作后生成的主键会赋值到哪个 Java 对象的属性上。在这里,id 属性会被自动填充为数据库生成的主键值。
    • 在 Java 实体类中,Order 对象的 id 属性会被设置为插入订单后的自增主键。

SQL 语句解析

insert into orders (number, status, user_id, address_book_id, order_time, checkout_time,
                    pay_method, pay_status, amount, remark, phone, address, consignee,
                    estimated_delivery_time, delivery_status, pack_amount, tableware_number, tableware_status)
values (#{number}, #{status}, #{userId}, #{addressBookId},
        #{orderTime}, #{checkoutTime}, #{payMethod}, #{payStatus}, #{amount}, #{remark},
        #{phone}, #{address}, #{consignee}, #{estimatedDeliveryTime},
        #{deliveryStatus}, #{packAmount}, #{tablewareNumber},
        #{tablewareStatus})
  • insert into orders (...):
    • orders 表中插入数据,指定了多个字段,这些字段将被填充来自 Orders 对象的相应属性值。
  • values (...):
    • 这些值对应于表单中插入的字段。每个 #{} 语法表示从 OrdersSubmitDTO 或实体对象中提取属性并映射到 SQL 中。
    • 例如,#{number} 会取 ordersSubmitDTO 中的 number 字段,#{status} 会取 status 字段,以此类推。

使用 useGeneratedKeys 获取主键

通过使用 useGeneratedKeys="true",MyBatis 会自动获取数据库生成的主键值,并将其填充到 Order 对象的 id 属性中。在插入操作后,调用 orderMapper.insteOrder(order) 进行插入时,order 对象的 id 字段将被赋值为数据库自增生成的主键。

实际应用

在我们的 UserOrderServiceImpl 中,userOrderMapper.insteOrder(order) 调用就会触发这个 SQL 插入操作,并自动将 order 对象的 id 设置为数据库生成的主键值。

Long orderId = userOrderMapper.insteOrder(order);

这里,orderId 将被设置为新插入的订单的主键,并用于后续操作,例如插入订单明细,创建订单的关联数据等。

通过使用 MyBatis 的 useGeneratedKeyskeyProperty 配置,我们能够方便地在插入操作中获取数据库自动生成的主键,并将其赋值给 Java 对象的属性。在实际开发中,这对于处理订单和其他涉及主键生成的业务场景非常有帮助。希望这篇博客能够帮助你更好地理解 MyBatis 的插入操作及其如何自动处理主键。

效果展示

image-20250330173521883

image-20250330173529663

image-20250330174234396

小结

通过本篇文章的学习,我们实现了外卖系统中的订单下单功能。在实现过程中,我们重点讨论了如何进行业务验证、创建订单、处理购物车数据以及返回订单信息等内容。这是一个典型的电商系统中的核心功能,在实际项目中,开发者可以根据实际需求扩展更多功能,如支付、优惠券、积分等。希望本篇文章能为你的开发提供一些思路和参考。