当前位置: 首页 > news >正文

【商城实战(15)】订单创建与提交:技术与实战的深度融合

【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配,乃至运营推广策略,102 章内容层层递进。无论是想深入钻研技术细节,还是探寻商城运营之道,本专栏都能提供从 0 到 1 的系统讲解,助力你打造独具竞争力的电商平台,开启电商实战之旅。

目录

  • 一、订单表及订单详情表设计
    • 1.1 订单表设计
    • 1.2 订单详情表设计
  • 二、前端订单创建页面开发
    • 2.1 页面布局搭建
    • 2.2 购物车商品转化逻辑
  • 三、后端订单创建接口编写
    • 3.1 接口功能概述
    • 3.2 订单数据生成
    • 3.3 库存扣减


一、订单表及订单详情表设计

在电商系统中,订单表和订单详情表是核心表之一,用于存储订单的关键信息和订单中商品的详细信息,为订单管理、用户查询、财务结算等功能提供数据支持。

1.1 订单表设计

订单表用于记录订单的总体信息,包括订单编号、用户 ID、订单总金额、订单状态、创建时间等。以 MySQL 为例,建表语句如下:

CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    order_no VARCHAR(50) NOT NULL UNIQUE COMMENT '订单编号,唯一标识一个订单',
    user_id INT NOT NULL COMMENT '下单用户的ID,关联用户表',
    total_amount DECIMAL(10, 2) NOT NULL COMMENT '订单总金额',
    order_status ENUM('created', 'paid','shipped', 'delivered', 'canceled') NOT NULL DEFAULT 'created' COMMENT '订单状态,分别表示创建、已支付、已发货、已送达、已取消',
    create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '订单创建时间',
    update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '订单更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  • id:订单表的主键,自增长整数,用于唯一标识每条订单记录。
  • order_no:订单编号,采用唯一的字符串,方便在系统中追踪和识别订单。
  • user_id:下单用户的 ID,与用户表中的 ID 关联,用于标识订单所属用户。
  • total_amount:订单总金额,精确到小数点后两位,用于记录订单的总费用。
  • order_status:订单状态,使用枚举类型,限制订单状态只能是指定的几种,便于管理和跟踪订单流程。
  • create_time:订单创建时间,在订单创建时自动记录当前时间。
  • update_time:订单更新时间,在订单状态发生变化时自动更新为当前时间。

1.2 订单详情表设计

订单详情表用于记录每个订单中具体商品的详细信息,包括订单详情 ID、订单 ID、商品 ID、商品数量、商品单价等。建表语句如下:

CREATE TABLE order_items (
    id INT AUTO_INCREMENT PRIMARY KEY,
    order_id INT NOT NULL COMMENT '所属订单的ID,关联订单表的id',
    product_id INT NOT NULL COMMENT '商品ID,关联商品表',
    quantity INT NOT NULL COMMENT '商品数量',
    price DECIMAL(10, 2) NOT NULL COMMENT '商品单价',
    INDEX (order_id),
    INDEX (product_id),
    FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  • id:订单详情表的主键,自增长整数,唯一标识每条订单详情记录。
  • order_id:所属订单的 ID,与订单表中的id关联,用于建立订单与订单详情的关系。
  • product_id:商品 ID,与商品表中的 ID 关联,用于标识订单中的商品。
  • quantity:商品数量,记录该商品在订单中的购买数量。
  • price:商品单价,记录该商品的销售单价。
  • INDEX (order_id) 和 INDEX (product_id):分别为order_id和product_id字段创建索引,提高查询订单详情时的效率。
  • FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE ON UPDATE CASCADE:设置外键约束,关联订单表的id字段,当订单表中的订单记录被删除或更新时,订单详情表中对应的记录也会相应地被删除或更新 ,保证数据的一致性和完整性。

二、前端订单创建页面开发

前端订单创建页面是用户将购物车商品转化为订单的关键交互界面,使用 uniapp 开发,结合 Vue.js 的响应式原理和组件化开发模式,为用户提供流畅的下单体验。

2.1 页面布局搭建

首先,在 uniapp 项目的pages目录下创建orderCreate文件夹,并在其中创建orderCreate.vue文件。在orderCreate.vue中,使用view组件搭建基本布局结构,利用flex布局实现页面元素的排列和自适应。

<template>
  <view class="order-create-container">
    <!-- 收货地址区域 -->
    <view class="address-section">
      <view class="address-item" v-for="(address, index) in addressList" :key="index">
        <text>{{ address.name }}: {{ address.phone }}</text>
        <text>{{ address.address }}</text>
      </view>
      <button @click="showAddAddressModal = true">添加新地址</button>
    </view>
    <!-- 商品列表区域 -->
    <view class="product-list-section">
      <view class="product-item" v-for="(product, index) in productList" :key="index">
        <image :src="product.imageUrl" mode="aspectFill"></image>
        <view class="product-info">
          <text>{{ product.name }}</text>
          <text>单价: {{ product.price }}</text>
          <text>数量: {{ product.quantity }}</text>
        </view>
      </view>
    </view>
    <!-- 订单金额区域 -->
    <view class="total-amount-section">
      <text>订单总金额: {{ totalAmount }}</text>
    </view>
    <!-- 提交订单按钮 -->
    <button @click="submitOrder">提交订单</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      addressList: [],
      productList: [],
      totalAmount: 0,
      showAddAddressModal: false
    };
  },
  onLoad() {
    // 初始化页面数据,如获取用户地址、购物车商品等
    this.getAddressList();
    this.getProductList();
  },
  methods: {
    getAddressList() {
      // 模拟从后端获取地址列表
      this.addressList = [
        { name: '张三', phone: '13800138000', address: '北京市朝阳区XX街道XX号' },
        { name: '李四', phone: '13900139000', address: '上海市浦东新区XX街道XX号' }
      ];
    },
    getProductList() {
      // 从购物车获取商品列表,假设购物车数据存储在本地缓存中
      const cartList = uni.getStorageSync('cartList');
      this.productList = cartList.map(product => ({
        name: product.name,
        imageUrl: product.imageUrl,
        price: product.price,
        quantity: product.quantity
      }));
      this.calculateTotalAmount();
    },
    calculateTotalAmount() {
      this.totalAmount = this.productList.reduce((total, product) => {
        return total + product.price * product.quantity;
      }, 0);
    },
    submitOrder() {
      // 提交订单逻辑,发送订单数据到后端
      console.log('提交订单,订单数据:', {
        address: this.addressList[0],
        products: this.productList,
        totalAmount: this.totalAmount
      });
    }
  }
};
</script>

<style lang="scss">
.order-create-container {
  padding: 20rpx;
  background-color: #f5f5f5;

 .address-section {
    margin-bottom: 30rpx;
    background-color: #fff;
    padding: 20rpx;
    border-radius: 10rpx;

   .address-item {
      margin-bottom: 15rpx;
    }
  }

 .product-list-section {
    margin-bottom: 30rpx;
    background-color: #fff;
    padding: 20rpx;
    border-radius: 10rpx;

   .product-item {
      display: flex;
      align-items: center;
      padding: 15rpx 0;
      border-bottom: 1rpx solid #e1e1e1;

      image {
        width: 100rpx;
        height: 100rpx;
        margin-right: 20rpx;
      }

     .product-info {
        flex: 1;

        text {
          display: block;
          margin-bottom: 5rpx;
        }
      }
    }
  }

 .total-amount-section {
    margin-bottom: 30rpx;
    background-color: #fff;
    padding: 20rpx;
    border-radius: 10rpx;
    text-align: right;
    font-size: 30rpx;
    font-weight: bold;
  }

  button {
    width: 100%;
    padding: 20rpx;
    background-color: #49bdfb;
    color: #fff;
    border: none;
    border-radius: 10rpx;
    font-size: 30rpx;
  }
}
</style>

2.2 购物车商品转化逻辑

从购物车页面跳转到订单创建页面时,需要传递购物车中的商品数据。在购物车页面,当用户点击 “去结算” 按钮时,将购物车商品数据通过uni.navigateTo的url参数传递给订单创建页面。

在购物车页面cart.vue中:

<template>
  <view class="cart-container">
    <!-- 购物车商品列表 -->
    <view class="cart-item" v-for="(product, index) in cartList" :key="index">
      <image :src="product.imageUrl" mode="aspectFill"></image>
      <view class="cart-item-info">
        <text>{{ product.name }}</text>
        <text>单价: {{ product.price }}</text>
        <text>数量: {{ product.quantity }}</text>
      </view>
    </view>
    <!-- 去结算按钮 -->
    <button @click="goToOrderCreate">去结算</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      cartList: []
    };
  },
  onLoad() {
    // 从本地缓存获取购物车数据
    this.cartList = uni.getStorageSync('cartList');
  },
  methods: {
    goToOrderCreate() {
      const cartData = JSON.stringify(this.cartList);
      uni.navigateTo({
        url: `/pages/orderCreate/orderCreate?cartData=${cartData}`
      });
    }
  }
};
</script>

<style lang="scss">
.cart-container {
  padding: 20rpx;
  background-color: #f5f5f5;

 .cart-item {
    display: flex;
    align-items: center;
    padding: 15rpx 0;
    border-bottom: 1rpx solid #e1e1e1;

    image {
      width: 100rpx;
      height: 100rpx;
      margin-right: 20rpx;
    }

   .cart-item-info {
      flex: 1;

      text {
        display: block;
        margin-bottom: 5rpx;
      }
    }
  }

  button {
    width: 100%;
    padding: 20rpx;
    background-color: #49bdfb;
    color: #fff;
    border: none;
    border-radius: 10rpx;
    font-size: 30rpx;
  }
}
</style>

在订单创建页面orderCreate.vue的onLoad生命周期函数中接收传递过来的商品数据:

onLoad(options) {
  const cartData = JSON.parse(options.cartData);
  this.productList = cartData.map(product => ({
    name: product.name,
    imageUrl: product.imageUrl,
    price: product.price,
    quantity: product.quantity
  }));
  this.calculateTotalAmount();
}

2.3 交互效果实现

  1. 选择商品:在订单创建页面,用户可以对商品数量进行调整。通过在商品列表中添加增减按钮,并绑定点击事件来实现数量的改变。
<view class="product-item" v-for="(product, index) in productList" :key="index">
  <image :src="product.imageUrl" mode="aspectFill"></image>
  <view class="product-info">
    <text>{{ product.name }}</text>
    <text>单价: {{ product.price }}</text>
    <view class="quantity-control">
      <button @click="decreaseQuantity(index)">-</button>
      <text>{{ product.quantity }}</text>
      <button @click="increaseQuantity(index)">+</button>
    </view>
  </view>
</view>

methods: {
  decreaseQuantity(index) {
    if (this.productList[index].quantity > 1) {
      this.productList[index].quantity--;
      this.calculateTotalAmount();
    }
  },
  increaseQuantity(index) {
    this.productList[index].quantity++;
    this.calculateTotalAmount();
  },
  // 其他方法...
}
  1. 修改数量:用户在输入框中直接输入数量时,实时更新商品数量和订单总金额。
<view class="quantity-control">
  <input type="number" v-model="productList[index].quantity" @input="calculateTotalAmount">
</view>
  1. 提交订单:点击 “提交订单” 按钮,触发submitOrder方法,在该方法中收集订单数据,包括收货地址、商品列表、订单总金额等,并通过uni.request发送 POST 请求到后端订单创建接口。
submitOrder() {
  const orderData = {
    address: this.addressList[0],
    products: this.productList,
    totalAmount: this.totalAmount
  };
  uni.request({
    url: 'https://your-backend.com/api/order/create',
    method: 'POST',
    data: orderData,
    success: res => {
      if (res.statusCode === 200) {
        uni.showToast({
          title: '订单提交成功',
          icon:'success'
        });
        // 清空购物车数据
        uni.removeStorageSync('cartList');
        // 跳转到订单详情页面
        uni.navigateTo({
          url: `/pages/orderDetail/orderDetail?orderId=${res.data.orderId}`
        });
      } else {
        uni.showToast({
          title: '订单提交失败',
          icon: 'none'
        });
      }
    },
    fail: err => {
      uni.showToast({
        title: '网络错误,请稍后重试',
        icon: 'none'
      });
    }
  });
}
  1. 添加地址:点击 “添加新地址” 按钮,显示地址添加模态框,用户填写地址信息后,点击保存按钮,将新地址添加到地址列表中,并关闭模态框。
<view class="address-section">
  <!-- 已有地址列表 -->
  <view class="address-item" v-for="(address, index) in addressList" :key="index">
    <text>{{ address.name }}: {{ address.phone }}</text>
    <text>{{ address.address }}</text>
  </view>
  <button @click="showAddAddressModal = true">添加新地址</button>
  <!-- 地址添加模态框 -->
  <view v-if="showAddAddressModal" class="add-address-modal">
    <view class="modal-content">
      <input type="text" placeholder="姓名" v-model="newAddress.name">
      <input type="text" placeholder="电话" v-model="newAddress.phone">
      <input type="text" placeholder="地址" v-model="newAddress.address">
      <button @click="addNewAddress">保存</button>
      <button @click="showAddAddressModal = false">取消</button>
    </view>
  </view>
</view>

data() {
  return {
    addressList: [],
    newAddress: {
      name: '',
      phone: '',
      address: ''
    },
    showAddAddressModal: false
  };
},
methods: {
  addNewAddress() {
    this.addressList.push(this.newAddress);
    this.showAddAddressModal = false;
    this.newAddress = {
      name: '',
      phone: '',
      address: ''
    };
  },
  // 其他方法...
}

通过以上前端开发,实现了订单创建页面的基本功能和交互效果,为用户提供了便捷的下单体验。

三、后端订单创建接口编写

在后端,使用 Spring Boot 框架来编写订单创建接口,确保订单创建过程的可靠性和高效性,同时处理库存扣减和事务管理,以保证数据的一致性。

3.1 接口功能概述

订单创建接口主要负责接收前端传来的订单数据,这些数据包括用户信息、收货地址、购物车商品列表及订单总金额等。接口需要对这些数据进行校验和处理,生成订单数据并插入到订单表和订单详情表中。同时,根据订单中的商品信息,对商品库存进行扣减操作,在整个过程中需要确保事务的一致性,即订单数据插入和库存扣减要么全部成功,要么全部失败,避免出现数据不一致的情况。

3.2 订单数据生成

假设使用 Spring Boot + MyBatis 来开发后端接口,首先定义订单相关的实体类Order和OrderItem,对应数据库中的订单表和订单详情表。

// Order.java
public class Order {
    private Integer id;
    private String orderNo;
    private Integer userId;
    private BigDecimal totalAmount;
    private String orderStatus;
    private Date createTime;
    private Date updateTime;
    // 省略getter和setter方法
}

// OrderItem.java
public class OrderItem {
    private Integer id;
    private Integer orderId;
    private Integer productId;
    private Integer quantity;
    private BigDecimal price;
    // 省略getter和setter方法
}

在订单创建接口的实现方法中,接收前端传递的订单数据,生成Order和OrderItem对象,并插入到数据库中。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.UUID;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderItemMapper orderItemMapper;

    @Transactional
    public String createOrder(OrderDto orderDto) {
        // 生成订单编号
        String orderNo = UUID.randomUUID().toString().replace("-", "");

        // 创建订单对象
        Order order = new Order();
        order.setOrderNo(orderNo);
        order.setUserId(orderDto.getUserId());
        order.setTotalAmount(orderDto.getTotalAmount());
        order.setOrderStatus("created");
        order.setCreateTime(new Date());
        order.setUpdateTime(new Date());

        // 插入订单数据到订单表
        orderMapper.insertOrder(order);

        // 获取订单ID
        Integer orderId = order.getId();

        // 创建订单详情对象并插入到订单详情表
        List<OrderItemDto> orderItemDtoList = orderDto.getOrderItemDtoList();
        for (OrderItemDto orderItemDto : orderItemDtoList) {
            OrderItem orderItem = new OrderItem();
            orderItem.setOrderId(orderId);
            orderItem.setProductId(orderItemDto.getProductId());
            orderItem.setQuantity(orderItemDto.getQuantity());
            orderItem.setPrice(orderItemDto.getPrice());
            orderItemMapper.insertOrderItem(orderItem);
        }

        return orderNo;
    }
}

其中,OrderDto和OrderItemDto是用于接收前端数据的 DTO(数据传输对象),OrderMapper和OrderItemMapper是 MyBatis 的 Mapper 接口,用于执行数据库操作。

3.3 库存扣减

库存扣减是订单创建过程中的关键环节,为了确保库存数据的准确性,避免超卖情况的发生,需要在订单创建时对商品库存进行扣减。扣减库存的实现方式通常是在数据库中对商品库存表进行更新操作。在上述createOrder方法中,在插入订单详情数据后,添加库存扣减逻辑。

@Autowired
private ProductMapper productMapper;

@Transactional
public String createOrder(OrderDto orderDto) {
    // 生成订单编号...
    // 创建订单对象并插入订单表...
    // 创建订单详情对象并插入订单详情表...

    // 扣减库存
    List<OrderItemDto> orderItemDtoList = orderDto.getOrderItemDtoList();
    for (OrderItemDto orderItemDto : orderItemDtoList) {
        Integer productId = orderItemDto.getProductId();
        Integer quantity = orderItemDto.getQuantity();
        productMapper.reduceStock(productId, quantity);
    }

    return orderNo;
}

ProductMapper是用于操作商品表的 Mapper 接口,reduceStock方法用于扣减商品库存。

<!-- ProductMapper.xml -->
<mapper namespace="com.example.demo.mapper.ProductMapper">
    <update id="reduceStock">
        UPDATE products
        SET stock = stock - #{quantity}
        WHERE id = #{productId} AND stock >= #{quantity}
    </update>
</mapper>

在这个 SQL 语句中,使用UPDATE语句更新商品表中的库存字段,只有当库存大于等于要扣减的数量时才执行更新操作,从而避免超卖。

在整个订单创建过程中,使用@Transactional注解来声明事务。该注解会将被注解的方法包装在一个事务中,如果方法执行过程中出现异常,事务会自动回滚,确保订单数据插入和库存扣减操作的原子性和一致性,保证数据的完整性。通过以上后端代码的实现,完成了订单创建接口的开发,实现了订单数据生成和库存扣减的功能 ,并保证了事务的正确处理。

相关文章:

  • Java:LocalDatTime(代替Calendar)、ZoneDateTime(时区时间)
  • Python 中 `sort()` 和 `sorted()` 的用法与区别
  • 课程《Deep Learning Specialization》
  • Python项目在 Cursor 编辑器中 Conda 环境配置问题
  • 文件系统文件管理
  • 十进制转任意负进制【C语言】
  • Stream特性(踩坑):惰性执行、不修改原始数据源
  • 万字总结数据分析思维
  • 洛谷 P3092 [USACO13NOV] No Change G
  • STM32如何精准控制步进电机?
  • TIA弱电流测量措施和经验汇总
  • QT项目初步认识(对象树)
  • 模块17. 多线程
  • 达梦数据库查看字符集编码
  • suricata安装测试
  • 统计3次函数3a6的种类和数量
  • 数字电子技术基础(二十六)——TTL门电路的输入特性和扇出系数
  • 关于Vue23的$emit和$on发布订阅模式
  • Ubuntu工控卫士在制造企业中的应用案例
  • 【算法】图论 —— Dijkstra算法 python
  • 辽宁省大学生创新创业平台/抖音seo系统
  • 室内设计网站有哪些比较好/网络推广员上班靠谱吗
  • 学校网站建设协议模板/上海seo优化
  • 沧州网站建设申梦/百度客户端下载安装
  • 政府做网站要什么资质/游戏推广代理加盟
  • 网站及建设中页面/香港疫情最新消息