订单业务在整个电商平台中处于核心位置,也是比较复杂的一块业务。是把“物”变为“钱”的一个中转站。
整个订单模块一共分四部分组成:
在购物车列表页面中,有一个结算的按钮,用户一点击这个按钮时,跳转到结算页,结算页展示了用户在购物车中选中的商品数据(商品清单),还要展示用户所有的收货人信息,让用户选择。
为了防止 用户提交订单之后 使用浏览器的回退功能,不刷新的情况下,重复的提交订单,在结算页生成的时候,咱们在结算页中放了个流水号(订单号)。
这个流水号 是后台采用 UUID生成的,把流水号存redis一份,结算页存一份。
用户在结算页点击 提交订单按钮,开始进行下单(订单信息保存),
下单成功,给用户跳转到支付页面,让用户选择支付方式,进行支付。
当支付成功之后,支付服务使用RabbitMQ通知订单服务,订单要修改订单的状态,从未支付改为 已支付。
下单的时候 要校验库存,支付成功后,订单服务使用rabbitMQ通知库存服务,要通知库存去打包,当库存锁定库存 扣减库存,扣减成功之后,库存服务使用rabbitMQ通知订单服务,订单要修改状态,从已支付改为待发货。后续 当库存打包完成,发货了,订单还要修改状态,从待发货改为 已发货,给用户展示物流信息。
入口:购物车点击计算按钮
分析页面需要的数据:
orderInfo :订单表
orderDetail :订单明细
id |
主键。自动生成 |
consignee |
收货人名称。页面获取 |
consignee_tel |
收货人电话。页面获取 |
deliveryAddress |
收货地址。页面获取 |
total_amount |
总金额。计算 |
order_status |
订单状态,用于显示给用户查看。设定初始值。 |
userId |
用户Id。从拦截器已放到请求属性中。 |
payment_way |
支付方式(网上支付、货到付款)。页面获取 |
orderComment |
订单状态。页面获取 |
out_trade_no |
第三方支付编号。按规则生成 |
create_time |
创建时间。设当前时间 |
expire_time |
默认当前时间+1天 |
process_status |
订单进度状态,程序控制、 后台管理查看。设定初始值, |
tracking_no |
物流编号,初始为空,发货后补充 |
parent_order_id |
拆单时产生,默认为空 |
id |
主键,自动生成 |
order_id |
订单编号,主表保存后给从表 |
sku_id |
商品id 页面传递 |
sku_name |
商品名称,后台添加 |
img_url |
图片路径,后台添加 |
order_price |
商品单价,从页面中获取,并验价。 |
sku_num |
商品个数,从页面中获取 |
在进入结算页面是我们生产了一个流水号,然后保存到结算页面的隐藏元素中一份,Redis中存一份,每次用户提交订单时都检查reids中流水号与页面提交的是否相符,如果相等可以提交,当订单保存以后把后台的流水号删除掉。那么第二次用户用同一个页面提交的话流水号就会匹配失败,无法重复保存订单。
实现类 @Override
public String getTradeNo(String userId) {
// 定义key
String tradeNoKey = "user:" + userId + ":tradeCode";
// 定义一个流水号
String tradeNo = UUID.randomUUID().toString().replace("-", "");
redisTemplate.opsForValue().set(tradeNoKey, tradeNo);
return tradeNo;
}
@Override
public boolean checkTradeCode(String userId, String tradeCodeNo) {
// 定义key
String tradeNoKey = "user:" + userId + ":tradeCode";
String redisTradeNo = (String) redisTemplate.opsForValue().get(tradeNoKey);
return tradeCodeNo.equals(redisTradeNo);
}
//删除流水号
@Override
public void deleteTradeNo(String userId) {
// 定义key
String tradeNoKey = "user:" + userId + ":tradeCode";
// 删除数据
redisTemplate.delete(tradeNoKey);
} |
/**
* 提交订单
* @param orderInfo
* @param request
* @return
*/
@PostMapping("auth/submitOrder")
public Result submitOrder(@RequestBody OrderInfo orderInfo, HttpServletRequest request) {
// 获取到用户Id
String userId = AuthContextHolder.getUserId(request);
orderInfo.setUserId(Long.parseLong(userId));
// 获取前台页面的流水号
String tradeNo = request.getParameter("tradeNo");
// 调用服务层的比较方法
boolean flag = orderService.checkTradeCode(userId, tradeNo);
if (!flag) {
// 比较失败!
return Result.fail().message("不能重复提交订单!");
}
// 删除流水号
orderService.deleteTradeNo(userId);
// 验证库存:
List
// 验证库存:
boolean result = orderService.checkStock(orderDetail.getSkuId(), orderDetail.getSkuNum());
if (!result) {
return Result.fail().message(orderDetail.getSkuName() + "库存不足!");
}
// 验证价格:
BigDecimal skuPrice = productFeignClient.getSkuPrice(orderDetail.getSkuId());
if (orderDetail.getOrderPrice().compareTo(skuPrice) != 0) {
// 重新查询价格!
cartFeignClient.loadCartCache(userId);
return Result.fail().message(orderDetail.getSkuName() + "价格有变动!");
}
}
// 验证通过,保存订单!
Long orderId = orderService.saveOrderInfo(orderInfo);
return Result.ok(orderId);
}
通过restful接口查询商品是否有库存
一般电商系统的商品库存,都不由电商系统本身来管理,由另外一套仓库管理系统,或者进销存系统来管理,电商系统通过第三方接口调用该系统。
由于库管系统可能是异构的系统,所以不在微服务体系之内。只支持restful风格的webservice调用和消息队列的调用。
详见《库存管理系统手册》
根据手册中的接口文档,编写调用代码。
查询库存接口
当用户点击结算的时候,这块我们用网关全局过滤器先判断用户是否登录,如果用户没有登录,则跳转到登陆页面,让用户去登录,登录成功之后,跳转到订单结算页面(这块在跳转到登录页面的时候,把之前的请求地址保存下来,作为参数进行跳转,在登录成功之后,查看是否有请求参数,如果有就跳转到对应的url,如果值为null,跳转到首页);如果用户登录了,则跳转到订单结算页面;
当跳转到订单结算页面的时候,首先对收货人地址进行管理,其次选择支付方式,一期的时候只提供了支付宝支付(微信、支付宝),确认订单信息,然后提交数据到后台,生成对应的订单表、订单详情表和订单物流表(当订单生成的时候,我们要调用对应的库存系统针对订单的商品数量进行验库存,还要进行验价格)。当订单创建成功之后,自动跳转到成功页面(将订单数据和到期时间传递过去)。
这块我们设置的订单的有效时间为24小时(这个时间可以自己定,只要合理就行),因为我们利用延时队列实现定时消息发送,消费者到时间后监听到消息,进行订单校验,如果订单是未支付状态,把订单状态修改为关闭订单。
谁负责订单的开发,谁创建订单表。
订单表:
人+流程+金额+时间
收货人信息(收货人电话、地址、名称)、用户的id、总金额、订单状态、订单交易编号(全局唯一不重复,采用字符串+时间戳+随机数)、物流单编号、创建时间、失效时间。
订单明细表:
商品有关的数据
Sku的id,数量、购买的价格、默认图片
看上面的文字描述,把文字描述 变成你的话 面试的时候 说出来了。
有效期 只要合理就行 30分钟、45分、2小时 24小时
10秒钟,365一天 这种的就是不合理了。
咱们使用的是 rabbitMQ的延时消息。下订单时候 发送 了一个消息,这个消息时间不到不能被消费,只有时间到了,消费者才能去消费这个消息,进行订单的关闭,关闭之前需要判断 订单是否支付,只有没支付的订单才能关闭。
这个商品在库存中就一个了,有多个人同时下单,这种问题 你们怎么解决的?
咱们做的:
这个问题没解决,都可以下单成功。
为了多卖货,减少库存积压,增加流动资金。
用户都可以下单成功 也都可以付钱成功,没货了,再去进。
拼多多:2天内发货。
下单的时候 就去做库存扣减,防止超卖:
加锁了
使用分布式锁。
数据库层面的锁。
悲观锁:
Select 库存量 from 库存表 where skuid=? for update;
Update 更新 去减库存。
事务的提交或者回滚的时候 锁释放。
乐观锁
表中加一个version字段
Select 库存量,version from 库存表 where skuid =?
Update 库存表 set (库存量-1,version+1) where skuid =? And version=之前查出的值。