05 购物车&订单模块
1. 前言
购物车是电商必不可少的功能,课程中电商系统把商品下单的唯一入口放在了购物车商品列表,也就是说只能在购物车商品列表页面选择商品下单购买。课程传送门。
本模块功能包括:
- 购物车
- 订单
2. 功能分析
2.1 购物车
2.1.1 需求分析
- 用户可以将商品SKU添加到购物车、从购物车移除删除商品SKU;
- 用户可以修改自己购物车中商品的数量;
- 下架的商品只能够移除,不能够修改或勾选下单;
附功能效果图如下:
商品加入购物车-效果图
2.1.2 表设计
- 实现逻辑
购物车是用户属性,存放的是商品SKU的合集,处理逻辑很简单,就是普通的增删改查。在显示的时候需要显示商品的详细信息,需要做一些例如库存、商品状态的简单判断。 - 表设计
用户可以在购物车列表页面直接选择商品下单购买,所以购物车中存放的商品信息需包含下单所需的详细商品数据,包括具体的商品SKU、数量;
具体表结构如下:
CREATE TABLE `cart_items` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, # 自增ID
`user_id` int(10) unsigned NOT NULL, # 所属用户ID
`product_sku_id` int(10) unsigned NOT NULL, # 选择的商品SKU
`amount` int(10) unsigned NOT NULL, # 商品数量
PRIMARY KEY (`id`),
KEY `cart_items_user_id_foreign` (`user_id`),
KEY `cart_items_product_sku_id_foreign` (`product_sku_id`),
CONSTRAINT `cart_items_product_sku_id_foreign` FOREIGN KEY (`product_sku_id`) REFERENCES `product_skus` (`id`) ON DELETE CASCADE,
CONSTRAINT `cart_items_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
课程中也有提到,表名和字段名最好能做到见名思意,过个一年半载,看到表名/字段名就能想起它的含义。这里购物车的表名是cart_items
,准确地说,我们建的不是购物车表,一个用户一个购物车,一个购物车可以存放多样商品,我们建的是购物车商品表,就像前面的商品收藏一样,只是在这里,我们形象地理解这些待买的商品的合集叫做购物车,因此表名cart_items
是购物车中商品项的意思。
2.1.3 代码借鉴
课程中,购物车是登录之后才能操作,直接用数据库存储,实现比较简单。可以思考拓展一下用户在登录/非登录状态下购物车数据怎么存放,多端购物车数据怎么同步?
可以参考以下思路:
大型电商项目购物车的实现
购物车实现及原理(仿京东实现原理)
2.2 订单
订单是电商系统必不可少的一环。与之相关的有商品模块、支付模块、物流模块,不同商品种类,不同支付方式,不同物流公司的处理方式都不一样,因此在编码设计和表设计上需要考虑清楚。
2.2.1 需求分析
- 支持多项商品合并成一个订单支付;
- 自动关闭超时未支付订单;
- 一个订单中的多个商品可以分开评分;
- 一个订单中的商品时合并发货,物流信息统一;
- 购买完成后,需将购买商品从购物车中删除;
2.2.2表设计
-
实现逻辑
订单由用户触发创建,不能人为修改,应依据客观事实由程序更新,属于用户操作数据不能被人为删除。
整个的订单生命周期如下:
订单生命周期-流程图
每一个阶段都需要维护订单相应信息:
- 创建订单,此时只更新用户信息、收件地址信息、商品信息,订单状态、物流信息、支付信息只初始化(默认值);
- 支付完成/失败,更新支付信息;
- 商品发货,更新物流信息;
- 用户收货/退货,更新物流信息、支付退款信息、订单状态;
-
表设计
因为一个订单可以包含多项商品,因此采用order 订单表
和order_item 订单子项表
分别保存订单信息和订单商品项信息;order 订单表
用于存储订单整体属性,包括支付信息,物流信息,收件信息等;order_item 订单子项表
存储购买的商品 SKU 信息,包括商品 SKU ,对 SKU 的评分等;其中,在order 订单表
保存了收件人的地址快照而不用户收货地址 ID ,是因为订单具有时效性,而用户收获地址是可以修改的;在order_item 订单子项表
存储 product_id 商品 ID 字段,这是个冗余字段,它可以通过 product_sku_id 查询,但有时候增加冗余字段可以减少查询,提高查询效率。
# 订单表
CREATE TABLE `orders` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, # 自增ID
`no` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, # 订单号
`user_id` int(10) unsigned NOT NULL, # 所属用户ID
`address` text COLLATE utf8mb4_unicode_ci NOT NULL, # 地址快照
`total_amount` decimal(10,2) NOT NULL, # 订单总金额
`remark` text COLLATE utf8mb4_unicode_ci, #备注
`paid_at` datetime DEFAULT NULL, # 支付时间
`payment_method` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, # 支付方式
`payment_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, # 第三方支付单号
`refund_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending', # 退款状态
`refund_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, # 第三方退款单号
`closed` tinyint(1) NOT NULL DEFAULT '0', # 订单是否关闭
`reviewed` tinyint(1) NOT NULL DEFAULT '0', # 订单是否评价
`ship_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending', # 物流状态
`ship_data` text COLLATE utf8mb4_unicode_ci, # 物流信息
`extra` text COLLATE utf8mb4_unicode_ci, # 其他数据
`created_at` timestamp NULL DEFAULT NULL, # 订单创建时间
`updated_at` timestamp NULL DEFAULT NULL, # 订单更新时间
PRIMARY KEY (`id`),
UNIQUE KEY `orders_no_unique` (`no`),
UNIQUE KEY `orders_refund_no_unique` (`refund_no`),
KEY `orders_user_id_foreign` (`user_id`),
KEY `orders_coupon_code_id_foreign` (`coupon_code_id`),
CONSTRAINT `orders_coupon_code_id_foreign` FOREIGN KEY (`coupon_code_id`) REFERENCES `coupon_codes` (`id`) ON DELETE SET NULL,
CONSTRAINT `orders_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
# 订单子项表
CREATE TABLE `order_items` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, # 自增ID
`order_id` int(10) unsigned NOT NULL, # 所属订单ID
`product_id` int(10) unsigned NOT NULL, # 商品ID
`product_sku_id` int(10) unsigned NOT NULL, # 所选商品SKU
`amount` int(10) unsigned NOT NULL, # 数量
`price` decimal(10,2) NOT NULL, # 价额
`rating` int(10) unsigned DEFAULT NULL, # 评分
`review` text COLLATE utf8mb4_unicode_ci, # 评价
`reviewed_at` timestamp NULL DEFAULT NULL, # 评价时间
PRIMARY KEY (`id`),
KEY `order_items_order_id_foreign` (`order_id`),
KEY `order_items_product_id_foreign` (`product_id`),
KEY `order_items_product_sku_id_foreign` (`product_sku_id`),
CONSTRAINT `order_items_order_id_foreign` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE,
CONSTRAINT `order_items_product_id_foreign` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE,
CONSTRAINT `order_items_product_sku_id_foreign` FOREIGN KEY (`product_sku_id`) REFERENCES `product_skus` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=188 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
2.2.3 代码借鉴
订单这一块的代码质量很高,值得反复推敲。包括:
- service的使用,代码封装提供代码复用性;
- 订单流水号的创建方法,加上循环增加代码的健壮性;
- 延迟队列的使用姿势很巧妙;
- 事件监听器;