关于博主
努力与运动兼备~~~有任何问题可以加我好友或者关注微信公众号,欢迎交流,我们一起进步!
微信公众号: 啃饼思录

QQ: 2810706745(啃饼小白)
写在前面
从本篇开始,我们正式开始项目的搭建,首先介绍数据表的设计,具体包括:表结构,表关系,唯一索引,单索引及组合索引,时间戳这几个内容。
数据表
首先我们创建一个数据库store,然后开始创建数据表:
1、用户表
DROP TABLE IF EXISTS `store_user`;
CREATE TABLE `store_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户表 id',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(50) NOT NULL COMMENT '用户密码,MD5 加密',
`email` varchar(50) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`question` varchar(100) DEFAULT NULL COMMENT '找回密码问题',
`answer` varchar(100) DEFAULT NULL COMMENT '找回密码答案',
`role` int(4) NOT NULL COMMENT '角色 0-管理员,1-普通用户',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '最后一次更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `user_name_unique` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;
上面就是构建我们项目的用户表,其中主键 id 自增长,用户名 username (在注册的时候是不允许用户名重复的,并发的时候,我们可以通过锁的机制来解决这种问题。但是如果单纯查询某个用户名是否存在,我们可以采用同步的方法,但是当我们架构变成了一个分布式的时候,这种方法就不可取了。)因此,最好的办法就是通过数据库的唯一索引UNIQUE KEY 然后通过BTREE这种方式将username设置为唯一索引,这样对于username的唯一性就交由mysql来保证了。这样在业务代码处理时就不需要去加一些锁或者同步方法来校验用户名是否存在了。
然后密码存放的是经MD5加密的值,你通过加密后的字段是看不出原来密码是多少,并且这也是一个非对称加密。
2、商品分类表
DROP TABLE IF EXISTS `store_category`;
CREATE TABLE `store_category` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '类别 Id',
`parent_id` int(11) DEFAULT NULL COMMENT '父类别 id 当 id=0 时说明是根节点,一级类别',
`name` varchar(50) DEFAULT NULL COMMENT '类别名称',
`status` tinyint(1) DEFAULT '1' COMMENT '类别状态 1-正常,2-已废弃',
`sort_order` int(4) DEFAULT NULL COMMENT '排序编号,同类展示顺序,数值相等则自然排序',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100032 DEFAULT CHARSET=utf8;
通常在设计分类表时要考虑一个场景,保证这个表可以递归,并且这个树状结构为了以后扩展,分类有可能是无限层级扩展的。因此,我们在设计递归的时候一定要有一个结束条件。这里的结束条件就是当parent_id 为 0 则认为递归结束。(比如某些一级类目它的parent_id 就是 0 ,就说明它就是根节点。)
3、商品详情表
DROP TABLE IF EXISTS `store_product`;
CREATE TABLE `store_product` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品 id',
`category_id` int(11) NOT NULL COMMENT '分类 id,对应 store_category 表的主键',
`name` varchar(100) NOT NULL COMMENT '商品名称',
`subtitle` varchar(200) DEFAULT NULL COMMENT '商品副标题',
`main_image` varchar(500) DEFAULT NULL COMMENT '产品主图,url 相对地址',
`sub_images` text COMMENT '图片地址,json 格式,扩展用',
`detail` text COMMENT '商品详情',
`price` decimal(20,2) NOT NULL COMMENT '价格,单位-元保留两位小数',
`stock` int(11) NOT NULL COMMENT '库存数量',
`status` int(6) DEFAULT '1' COMMENT '商品状态.1-在售 2-下架 3-删除',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
注意一下这里的decimal(20,2),它表示可以存取整数位18,小数位2精度比较高的数。
4、购物车表
DROP TABLE IF EXISTS `store_cart`;
CREATE TABLE `store_cart` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`product_id` int(11) DEFAULT NULL COMMENT '商品 id',
`quantity` int(11) DEFAULT NULL COMMENT '数量',
`checked` int(11) DEFAULT NULL COMMENT '是否选择,1=已勾选,0=未勾选',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `user_id_index` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=146 DEFAULT CHARSET=utf8;
我们这里之所以加一个 user_id索引,是因为我们这个表l里会经常使用 user_id 来进行查询,这样做的话会提高这个表的查询效率。
5、支付信息表
DROP TABLE IF EXISTS `store_pay_info`;
CREATE TABLE `store_pay_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL COMMENT '用户 id',
`order_no` bigint(20) DEFAULT NULL COMMENT '订单号',
`pay_platform` int(10) DEFAULT NULL COMMENT '支付平台:1-支付宝,2-微信',
`platform_number` varchar(200) DEFAULT NULL COMMENT '支付宝支付流水号',
`platform_status` varchar(20) DEFAULT NULL COMMENT '支付宝支付状态',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=61 DEFAULT CHARSET=utf8;
这个表非常重要,因为里面包含了很多重要信息,我们以后对账,退款,支付查询都会用到这个表。还有就是在我们生成订单并发起支付请求的时候,会生成远程的支付宝订单,而它就存在这个 platform_number 里面,代表远程平台的一个支付流水号。
6、订单表
DROP TABLE IF EXISTS `store_order`;
CREATE TABLE `store_order` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单 id',
`order_no` bigint(20) DEFAULT NULL COMMENT '订单号',
`user_id` int(11) DEFAULT NULL COMMENT '用户 id',
`shipping_id` int(11) DEFAULT NULL,
`payment` decimal(20,2) DEFAULT NULL COMMENT '实际付款金额,单位是元,保留两位小数',
`payment_type` int(4) DEFAULT NULL COMMENT '支付类型,1-在线支付',
`postage` int(10) DEFAULT NULL COMMENT '运费,单位是元',
`status` int(10) DEFAULT NULL COMMENT '订单状态:0-已取消-10-未付款,20-已付款,40-已发货,50-交易成功,60-交易关闭',
`payment_time` datetime DEFAULT NULL COMMENT '支付时间',
`send_time` datetime DEFAULT NULL COMMENT '发货时间',
`end_time` datetime DEFAULT NULL COMMENT '交易完成时间',
`close_time` datetime DEFAULT NULL COMMENT '交易关闭时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `order_no_index` (`order_no`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=118 DEFAULT CHARSET=utf8;
注意一下,我们需要给订单号添加一个唯一索引,因为我们不能生成多个相同的订单。还有这里的payment是实际的付款金额,你千万不要和产品里面的金额做成联动查询,因为产品里面的金额是随着时间不同而不同,实际付款金额在购买以后就确定了,是不可以修改的。
7、订单明细表
DROP TABLE IF EXISTS `store_order_item`;
CREATE TABLE `store_order_item` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单子表 id',
`user_id` int(11) DEFAULT NULL,
`order_no` bigint(20) DEFAULT NULL,
`product_id` int(11) DEFAULT NULL COMMENT '商品 id',
`product_name` varchar(100) DEFAULT NULL COMMENT '商品名称',
`product_image` varchar(500) DEFAULT NULL COMMENT '商品图片地址',
`current_unit_price` decimal(20,2) DEFAULT NULL COMMENT '生成订单时的商品单价,单位是元,保留两位小数',
`quantity` int(10) DEFAULT NULL COMMENT '商品数量',
`total_price` decimal(20,2) DEFAULT NULL COMMENT '商品总价,单位是元,保留两位小数',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `order_no_index` (`order_no`) USING BTREE,
KEY `order_no_user_id_index` (`user_id`,`order_no`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=135 DEFAULT CHARSET=utf8;
注意一下,这里 product_id不能通过链接product表,来查询name和image 的,因为当我们下单的时候商品名称叫xx,极有可能在查询订单明细的时候就变成了xxxx。所以product_name和produc_image这两个字段的设计是非常有必要的。
8、收货地址表
DROP TABLE IF EXISTS `store_shipping`;
CREATE TABLE `store_shipping` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL COMMENT '用户 id',
`receiver_name` varchar(20) DEFAULT NULL COMMENT '收货姓名',
`receiver_phone` varchar(20) DEFAULT NULL COMMENT '收货固定电话',
`receiver_mobile` varchar(20) DEFAULT NULL COMMENT '收货移动电话',
`receiver_province` varchar(20) DEFAULT NULL COMMENT '省份',
`receiver_city` varchar(20) DEFAULT NULL COMMENT '城市',
`receiver_district` varchar(20) DEFAULT NULL COMMENT '区/县',
`receiver_address` varchar(200) DEFAULT NULL COMMENT '详细地址',
`receiver_zip` varchar(6) DEFAULT NULL COMMENT '邮编',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;
这是我们用于收货的地址表,可以这么说是对我们的用户表进行了业务场景的拓展。
至此,我们能想到的数据表就只有这些,其他的等需要的时候我们再进行添加。
表关系
我们先来看一张图:

通过该图,我们知道该项目一共有八张表,蓝色的代表该数据表里存在索引,具体的索引如下:
store_user表的username 是个唯一索引;
store_cart表的user_id是个唯一索引;
store_order表的order_no是个唯一索引;
store_order_item表的order_no是一个唯一索引,而user_id又和order_no组成组合索引。
接下来,我们按照下单的流程介绍一下数据的流向,这个过程大家必须要知道,否则后面就难以进行了。
数据流向分析
首先是注册,注册之后的信息会存到store_user表里面,登录则会读取store_user表中的信息从而进行验证。
然后搜索商品就用到了分类表和商品表,我们会依据传递的关键字或者分类的id来进行查询。
如果传入的分类 id 是一个级别比较高的 id,那么我们会对分类表进行递归查询,之后查出来符合这个分类或者关键字的一个product集合(商品详情集合)。
然后我们将这个商品添加进购物车的时候,就会把商品表中的id和用户表中的id拿过来,存放到购物车表里面,从而生成product_id。
用户登录的 id 放到 user_id,然后购物车中提交订单就来到了订单确认页,订单确认页里面就要填一些收货地址。
然后用户提交订单,生成订单,生成订单之后就来到了order(订单表)和 order_item(订单明细表),order 会生成一个订单号,order_item 表和 product 表是一个一对一的关系,order_item 和 order 表是一个多对一的关系(这个很好理解,一个订单里面可以包含多个商品)。
然后开始对这个订单进行付款,接到支付宝的回调,将信息存到 pay_info 表里面,然后对回调的状态进行判断,如果支付成功会把 order 表的 payment_time 进行写入。
不知道你发现没有,我们这些表中的外键都是没有用的,为什么不用它呢?因为在以后扩展分表的时候,外键的存在会使其扩展变的非常的麻烦,还有在进行清洗数据时也会很麻烦,因此我们这里就不使用外键。同时你必须要明确的一点就是,适当的冗余对数据库的查询是有一定帮助的。
唯一索引
我们知道,唯一索引可以保证数据的唯一性,在这里我们就多次使用了它。
store_user表中的:
UNIQUE KEY `user_name_unique` (`username`) USING BTREE
store_order表中的:
UNIQUE KEY `order_no_index` (`order_no`) USING BTREE
单索引及组合索引
单索引:
store_order_item表中的:
KEY `order_no_index` (`order_no`) USING BTREE,
组合索引:
store_order_item表中的:
KEY `order_no_user_id_index` (`user_id`,`order_no`) USING BTREE
这里的user_id和order_no的组合查询就是提高我们的查询速度。
时间戳
时间戳,又称业务查询问题的后悔药。你可能也发现了这一点,那就是我们所有的表都有create_time(数据创建时间)和 update_time(数据更新时间)这是非常重要的!
因为有时候我们在查一些业务问题的时候,如果日志没有打印,我们就很难知道事情发生的具体时间。如果我们数据库中连时间戳也没有,那更是无从下手了,一旦这个时候会非常后悔,如果当时打印日志就好了,就能知道这个数据是什么时候变化的,至少在查业务问题的时候有一个时间范围。
为什么用这两个字段呢?因为 create_time 我们能知道数据创建时间,而 update_time 我们能知道数据更新时间,但是我们不可能保存每次更新时间(这也不是它应该干的事,否则类似的版本控制器干嘛去了???),它只会保留最新一次的更新时间。另外这两个字段可以便于我们进行时间的排序或者其他业务分析的时候会用到。值得注意的是,我们存的类型是datetime而不是date,因为我们想要知道具体的时间。
至此,我们本篇关于表结构,表关系,唯一索引,单索引及组合索引,时间戳这几个内容的介绍就到此为止了,感谢你的赏阅!
网友评论