编辑推荐: |
本文用一个在线商城的例子,介绍了数据库系统设计,展示了设计思路和设计效果,希望能帮助到大家。
本文来自简书,由火龙果软件Linda编辑、推荐。 |
|
现在有一个需求,做一个在线商城的系统。需要实现主要流程有:用户登陆,浏览商品,商品加入购物车,选择待支付的商品支付,对商品进行评价。
数据库设计:第一感觉是有用户表,商品表,商家表。然后为了支付,会产生账单表。
现实生活中,用户有很多个不止一个,商品也有很多个,用户表和商品表之间的关系是多对多的关系,多个用户对应多个商品,那么就应该加一个中间表map,这个map在现实生活中的映射其实就是购物车。购物车持有对用户和商品的id引用。所以画出来是这样的。 
购物车map作为一个中间表可以解决用户表和商品表的多对多问题
再来看商品表。一个商品,在不同的商家就是不同的商品,不存在一个商品属于两个商家的情况。所以商品和商家应该是多对一的关系。商家表是商品表的父表。商品表是商家表的子表,商品持有商家的id引用。

商品表持有对商家的引用
接下来就是订单表。选好了将要支付的商品,就会生成一张待支付订单。
订单包含的内容包括收货人信息,商品信息。
一张订单对应一个用户,多个商品。
订单和商品的关系是多对多的关系,订单和用户的关系是一对一的关系。
是否订单和商品需要一张中间表关联?先加一张试试。
(一开始我以为订单和购物车关联,因为淘宝的购物流程是从购物车里选择以后提交订单,但后来发现购物车其实只是用户对商品的一个集合的暂存和选择,提交订单并不依赖于购物车的存在与否) 
订单表和商品表我理解它们之间的关系是多对多的关系
想要知道某一笔订单有哪些商品,只要select m.* from map m where id =
order_id 就可以了。
最后在订单表内加上用户的引用,指明某笔订单是哪个用户的订单。

在订单表内增加对于用户的引用
现实生活中,提交订单后生成订单记录,接下来就是支付订单了。支付订单会产生一笔账单,所以这里新增账单表。但是支付订单真的会产生一笔帐单吗?我看了淘宝的设计,好像是在订单里面加上一笔交易号。而且订单不止交易号,还包括很多状态,
例如付款状态,收货状态,评价状态等。所以先不考虑增加账单表,由订单表完成上述所说的功能。
那付款的内容需要记录下来吗?肯定需要,我认为可能需要一张交易表来完成。一笔订单对应一笔交易。谁持有对于对方的访问呢?从逻辑上看应该是先有订单,再有交易吧。所以是订单持有对交易的访问

增加一个交易表,关联订单表
对商品的评价呢?一个商品可能有多个人来评价,现实中有多个人,所以人和商品评价是多对多的关系?应该是有一张商品--用户中间map表来
记录用户对某个商品的评价。
如果是这样的话,
查看某个商品有哪些用户评价的sql语句可能就是这样写:
SELECT m.* FROM map m where map.product_id =
查看用户评价了哪些商品可能是这样写 :
SELECT m.* FROM map m where m.user_id =
先暂时这样处理。看看后续是否还能改进,或者后续怎样改进。
最后加一张商品评价表,用来记录多对多的用户--商品关系

商品评价表,用于记录用户对于商品的评价
实现的效果:
1.查看所有可见的店铺:

可购买商品的店铺
2.选择一家店铺,查看该店铺所有的商品

共有两个商品可以购买,id 分别为8和9
3.加入一件商品到购物车

id为1的用户增加一个id为8的商品成功

数据库内多了一条购物车记录信息
4.查看某用户的购物车添加记录 
id为1的用户分别添加了两条id为3,8的商品记录
5.用户下单购买这两个商品

手动添加两条数量为1的商品

添加订单成功,订单的记录id为6
6.查看订单下有哪些产品 
可以看到,订单下的商品是刚刚的两件商品
7.支付一笔订单

调用支付的接口,修改trade的状态位

修改前的交易单状态位

修改后的交易单状态位
8.评价本次交易

可以看到,本次交易可以评价(由于字符原因,显示为问号)
9.评价某件商品

评价某件商品之前会进行user和商品的校验
10.查看某人的所有商品评价

查看某人的商品的评价
11.查看某件商品的所有人的评价

查看某件商品的所有人的评价以上就是在线商城系统从查看 -- 选中 -- 下单 -- 购买 -- 评价
的主要流程。这条主流程会有很多问题,包括操作的可逆性,例如下单后想删除某个商品,或者从购物车中删除某个商品,是我之前没有考虑过的。还有就是订单的取消,需要商铺的同意,包括发货前取消,发货前取消等区别。
现在围绕主流程进行需求的变更。需求发生变更,需要在主要流程上增加以下功能
1.用户购买一件商品以后,商家对应的该商品的库存量减一。
2.用户取消一笔订单。订单取消了,需要商品的商家确认才可以进行订单取消。
3.删除订单内已存在的商品
4.会员折扣。折扣在订单无法支付的情况会退回到该会员的账户。
5.会员积分,每次购买以后都会有积分记入该会员账户。
6.用户提交一笔订单,锁定该商品,十五分钟内不支付即释放该商品。
先来做稍微简单的一部操作,第一个需求变更:用户购买一件商品以后,商品库存量减一。
步骤:
1.在数据库更改商品表product,增加字段stock表示库存余量
2.在domain里增加商品DO的一条属性,也就是private
Integer stock;
3.在用到DO的地方,也就是购买商品,需要save操作数据库的地方修改剩余量。
程序的业务代码
封装的函数
效果:
商品加入订单前
商品加入订单后,剩余数量被修改
然后来做第二个需求变更:用户取消一笔订单。订单取消了,需要商品的商家确认才可以进行订单取消。
首先我之前定义过了订单Order的状态。我设想过是否可以通过设置order 的state状态来达到以下几个状态:
0,1 待付款,已付款。
2,3 待发货,已发货
4,5 待收货,已收货
6,7 待评价,已评价
8,9 用户发起取消订单请求,商家确认订单已取消。
对应的,交易表的state 可以定义三种状态:
0待付款
1已付款
2交易取消
是否可以通过这样人为的设定state来达到所希望的效果?
如果可以的话,那么状态的取消就可以通过程序中状态位的改变来实现了。
第三个需求变更:删除订单内已存在的商品
数据库内已经定义好了订单order--商品product 的中间表。
我从订单内删除定义的商品,实质就是删除这张中间表map的记录

中间表的定义
将中间表的记录删除后,就达到了删除订单内商品的目的。
需要注意的是,当该笔订单删除完所有的商品,也就是订单内不包含任何商品了以后,订单的状态会发生变化。 
删除产品前的订单状态,包含一瓶红牛

删除时的操作,id=3是红牛的id

删除后的效果,红牛已不在id为22的订单中
第四个需求变更:会员折扣。折扣在订单无法使用折扣的时候会退回到该会员的账户。
这个需求可能需要一张记录用户的会员身份的表了。
对于会员,会有会员积分,会员折扣券等记录。
会员折扣券应该也是一张表,在会员表和折扣券表中间会有一张中间表map用来记录会员和折扣券的多对多关系。然后会员表和用户表关联。

会员模块的数据库概要设计
以下是几点大概思路:
看一个用户是不是会员:select v.* from vip_table v where user_id
= :userId 接收到的是null,则不是会员

判断用户是否为会员的效果
看一个会员有哪些折扣券:
selectc.* from map m , coupons c where m .vip_id
= :vipId and m.coupons_id = c.id

查看一个会员有哪些优惠券
看一种折扣券被哪些会员持有:
selectc.* from map m , vip_table v where m .coupons_id
= :cId and m.vip_id= vipId
用户有了会员,就可以有折扣。在买东西前查看有哪些折扣。
一开始折扣可以是总价减五元,或者总价打八折这样简单的折扣。后续可能会扩展到优惠券叠加等复杂情况。
考虑最简单的情况,传入一个折扣参数discount到计算总价的函数里面。但这样就没有体现用户对于折扣券的选择动作了。如果既想要用户选择折扣券,又可以对于总价进行操作,那么在Controller层传入一个List<Long>
idList 也就是折扣券的id列表可以吗?应该是可以的。
5.会员积分,每次购买以后都会有积分记入该会员账户。
会员积分这个字段现有数据库容纳不下这个字段了。只能改表了。

会员账户表内新增score字段容纳会员积分数据
然后在每一次支付完成以后修改这个score字段,而不是在添加订单的时候就修改这个字段。

会员增加积分的实现效果
6.用户提交一笔订单,锁定该商品,十五分钟内不支付即释放该商品。
我这里有两种选择,一个是当一笔订单提交时,启动一个线程,在支付结束或者半小时结束那一刻线程结束,修改订单的状态位state为相应的状态。但用线程的话是否会系统资源开销比较大?
还有一个就是做一个定时器,每当有一个新的订单生成时加入订单列表,每隔固定时间遍历订单列表来排除掉十五分钟未支付的订单,置订单状态位为未支付,取消订单。并且释放之前占用的商品资源。达到时间上的资源一致性。
今天的问题(11.4):
1.用户使用何种方式来完成折扣券的使用
2.十五分钟的商品锁定使用哪种方式进行实现?
(11.5) 商品锁定通过定时器来实现。定时器每隔一分钟扫描一次数据库中trade=未支付状态的交易单,并判断该交易单的创建时间是否大于十五分钟。若大于十五分钟,关闭本次交易单的交易,设置state=关闭,然后将商品回退到未锁定的状态。各个商品的数量与原来相比没有发生改变。

交易单的状态发生改变
交易单的状态发生改变以后,我想回过头去修改订单的状态,发现交易单没有持有对于订单的引用。
那么可以遍历订单查找交易单id=?的订单吗?好像不太合理
遂询问亚哥,订单持有交易单的引用合理吗?不合理。
11.6 11.7
新增用户的账户表,用来动态展现用户购买商品/退款后账户内余额的变化。
首先一个用户肯定对应一个账户,所以是用户引用账户还是账户引用用户?
应该是用户引用账户吧。
支付一笔订单时: 
可以在json中添加折扣券的使用
程序将会判断该用户是否是会员,以及是否有对应类型的折扣券,以及是否使用的折扣券数量超过了拥有的数量。
付款后的效果:会对几处产生影响: 
付款时订单的状态受限
会员的积分会有影响。 
会员的积分可以累加
用户的账户余额会有影响(优惠券叠加后的金额) 
账户余额发生了改变
用户的优惠券会被删除 
使用后的优惠券会被置为1
交易单的状态会有影响,包括实际应收,付款状态 
交易单的状态有三处受影响
订单的状态会有影响 
订单的状态受到了影响
付款后,用户等待货物的过程中可以发起退款,发起退款有几种情况,
1.未发货时退款
2.已发货时退款
3.已收货时退款
第一种情况将会直接退款并且关闭订单。
第2,3种情况将会发起退款申请,等待商家的同意。商家的同意将改变订单的状态位。若用户在商家未同意时退款,将会受到非法请求的提示。 
分布在多处的退款限制,只有限定条件下可以退款

商家对已发货订单退款请求的回复
在等待退款的时候,退款将会影响以下几个部分:
交易单,订单的状态: 
退款前的状态为等待发货待态
退款后状态为发起退款态,状态码为111。并且完成退款,不需要经过商家的同意。
会员的积分受到影响 
会员积分因为退款减少了
用户的余额 
用户的余额因为退款而增加
商家的货物库存量 
货物库存量因为退货而增加相应的数量
十五分钟未付款将会影响:
商家的货物库存量
交易单,订单的状态 
回滚超时未付款的影响因子
11.4回过头来看前天写的代码,有一段其实很有问题。
这一段的逻辑大概是:找出历史订单,顺带查出对应订单的商品列表的商品名和商品id。 
service层展现orderVO的逻辑代码

Dao层展现product_order_map的sql语句
我感觉这样的话,逻辑代码就太复杂了。如果把这段逻辑代码放在sql语句里面处理,应该会方便很多
sql语句写得好,程序都可以少写大段。
现在我希望写sql语句,直接returnHistoryOrderVO
但这样好像不太行。因为结果集里面不仅包含一对一的关系,还有一对多的关系。
所以考虑在dao层实现一个功能,根据orderid找出来所有对应该订单的商品DO的list 
根据订单id找到商品的列表sql语句
然后原来的9行代码就可以写为下面的两行: 
改写前后,效果一样
|