惯例文档:
这个是一个方式的,失败的话发消息给失败的mq,然后去异步处理失败信息。
快速给第三方平台做响应。第三方支付平台返回支付结果之后直接给商家后台,商家后台然后直接调用响应同时发消息给mq做后续的处理。
---
我们先搞一个starter。
maven-install
要安装的工程在哪里?
代码在哪里?
springboot集成rocketmq的生产者。
在工程里面果然有:
第一步导入依赖:
第二步:配置配置文件
springboot集成rocketmq的生产者消费者代码:
---01-07---
同样下载springboot和dubbo整合的依赖包到本地的maven
还是要install游一下:
zookeeper集群的搭建:
上代码:
说明:服务的接口,服务的提供者实现接口提供服务,服务的消费者调用接口实现的服务。
第一步:加入依赖:
第二步:写配置文件
第三步:启动类
第四步:
zookeeper包括自己多余半数,收到的follower为半数就是一次消息成功。
服务的提供者。
管理平台得到搭建:
服务的消费方(web的方式)
第一步:
第二步:配置文件,根据服务的名称也就是接口的全类名找服务
第三步:
服务的提供者是dubbo要用dubbode注解
消费者:
---08-13---
创建数据库表:
表:优惠券表 商品表 订单表 订单商品日志表(订单和库存的操作) 用户表 用户余额日志表 订单支付表
MQ生产消息表 MQ消费消息表
工程:
---14-15---
mybatis的逆袭工程。
好好看下这个逆向工程。
第一步:
第二步:
第三步:
第四步:解决一个报错的问题:https://blog.csdn.net/qq_41525021/article/details/93048277
第五步:运行main方法。
---16---
公共类的作用:
为什么用雪花算法,要是分库分表的话两个数据库都是自增的话可能是一样的。
---17----
连接:https://www.bilibili.com/video/BV1RE411r75d?p=52
生成预订单。
---18---
lombok使用lock:https://www.cnblogs.com/yanguobin/p/11525584.html
开始编写逻辑了:
整个代码的逻辑结构:
第一步:先写接口
第二步:
zookeeper:
zookeeper://192.168.244.128:2181;zookeeper://192.168.244.129:2181;zookeeper://192.168.244.130:2181
rocketMQ:
rocketmq.name-server=192.168.244.128:9876;192.168.244.129:9876
mysql:
spring.datasource.url=jdbc:mysql://192.168.244.128:3306/trade?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
十分重要的知识点,真这个这个必须是原子成功就成功失败就都失败性质的。有异常在catch模块实现失败的补偿机制。
围绕这个展开:
1.校验订单
注意这里:
不可能是接口必须是一个实现类,spring扫描到这个接口就会调用一个实现类的。
---
生成预订单:
/**
* 生成预订单
*
* @param order
* @return
*/
private Long savePreOrder(TradeOrder order) {
//1. 设置订单状态为不可见
order.setOrderStatus(ShopCode.SHOP_ORDER_NO_CONFIRM.getCode());
//2. 设置订单ID
long orderId = idWorker.nextId();
order.setOrderId(orderId);
//3. 核算订单运费 看是不是满100包邮的
BigDecimal shippingFee = calculateShippingFee(order.getOrderAmount());
if(order.getShippingFee().compareTo(shippingFee)!=0){
CastException.cast(ShopCode.SHOP_ORDER_SHIPPINGFEE_INVALID);
}
//4. 核算订单总金额是否合法
BigDecimal orderAmount = order.getGoodsPrice().multiply(new BigDecimal(order.getGoodsNumber()));
orderAmount.add(shippingFee);
if(order.getOrderAmount().compareTo(orderAmount)!=0){
CastException.cast(ShopCode.SHOP_ORDERAMOUNT_INVALID);
}
//5.判断用户是否使用余额getMoneyPaid这个字段是已经付的金额
BigDecimal moneyPaid = order.getMoneyPaid();
if(moneyPaid!=null){
//5.1 订单中余额是否合法
int r = moneyPaid.compareTo(BigDecimal.ZERO);
//余额小于0
if(r==-1){
CastException.cast(ShopCode.SHOP_MONEY_PAID_LESS_ZERO);
}
//1表示余额大于0getUserMoney这个是余额 和用户余额比较不能大于
if(r==1){
TradeUser user = userService.findOne(order.getUserId());
if(moneyPaid.compareTo(new BigDecimal(user.getUserMoney()))==1){
CastException.cast(ShopCode.SHOP_MONEY_PAID_INVALID);
}
}
}else{
order.setMoneyPaid(BigDecimal.ZERO);
}
//6.判断用户是否使用优惠券 这个就是一个订单只有一个优惠券
Long couponId = order.getCouponId();
if(couponId!=null){//优惠券是和用户绑定的优惠券
TradeCoupon coupon = couponService.findOne(couponId);
//6.1 判断优惠券是否存在
if(coupon==null){
CastException.cast(ShopCode.SHOP_COUPON_NO_EXIST);
}
//6.2 判断优惠券是否已经被使用
if(coupon.getIsUsed().intValue()==ShopCode.SHOP_COUPON_ISUSED.getCode().intValue()){
CastException.cast(ShopCode.SHOP_COUPON_ISUSED);
}
order.setCouponPaid(coupon.getCouponPrice());
}else{
order.setCouponPaid(BigDecimal.ZERO);
}
//7.核算订单支付金额 订单总金额-余额-优惠券金额
BigDecimal payAmount = order.getOrderAmount().subtract(order.getMoneyPaid()).subtract(order.getCouponPaid());
order.setPayAmount(payAmount);
//8.设置下单时间
order.setAddTime(new Date());
//9.保存订单到数据库
orderMapper.insert(order);
//10.返回订单ID
return orderId;
}
这个是订单总价:
分库分表的全局唯一性要用到雪花算法的。
雪花算法。
实现第二步:
优惠券:
要用到了优惠券的接口了。
实现第三步:
这个其实是你买商品的数量
这个是你买了几个
知道哪个订单扣减了哪个商品的什么库存
调用商品的service:
服务端:https://www.bilibili.com/video/BV1RE411r75d?p=61
确实是减去了才会保存日志的。
---19-27---
扣减优惠券。
---28---
扣减余额
这个是重点的。
@Override
public Result updateMoneyPaid(TradeUserMoneyLog userMoneyLog) {
//1.校验参数是否合法
if(userMoneyLog==null ||
userMoneyLog.getUserId()==null ||
userMoneyLog.getOrderId()==null ||
userMoneyLog.getUseMoney()==null||
userMoneyLog.getUseMoney().compareTo(BigDecimal.ZERO)<=0){
CastException.cast(ShopCode.SHOP_REQUEST_PARAMETER_VALID);
}
//2.查询订单余额使用日志
TradeUserMoneyLogExample userMoneyLogExample = new TradeUserMoneyLogExample();
TradeUserMoneyLogExample.Criteria criteria = userMoneyLogExample.createCriteria();
criteria.andOrderIdEqualTo(userMoneyLog.getOrderId());
criteria.andUserIdEqualTo(userMoneyLog.getUserId());
// 看下有没有日志 只有结束了 才会记录日志的
int r = userMoneyLogMapper.countByExample(userMoneyLogExample);
TradeUser tradeUser = userMapper.selectByPrimaryKey(userMoneyLog.getUserId());
//3.扣减余额...
if(userMoneyLog.getMoneyLogType().intValue()==ShopCode.SHOP_USER_MONEY_PAID.getCode().intValue()){
if(r>0){
//已经付款
CastException.cast(ShopCode.SHOP_ORDER_PAY_STATUS_IS_PAY);
}
//减余额
tradeUser.setUserMoney(new BigDecimal(tradeUser.getUserMoney()).subtract(userMoneyLog.getUseMoney()).longValue());
userMapper.updateByPrimaryKey(tradeUser);
}
//4.回退余额...
if(userMoneyLog.getMoneyLogType().intValue()==ShopCode.SHOP_USER_MONEY_REFUND.getCode().intValue()){
if(r<=0){
//如果没有支付,则不能回退余额
CastException.cast(ShopCode.SHOP_ORDER_PAY_STATUS_NO_PAY);
}
//防止多次退款
TradeUserMoneyLogExample userMoneyLogExample2 = new TradeUserMoneyLogExample();
TradeUserMoneyLogExample.Criteria criteria1 = userMoneyLogExample2.createCriteria();
criteria1.andOrderIdEqualTo(userMoneyLog.getOrderId());
criteria1.andUserIdEqualTo(userMoneyLog.getUserId());
criteria1.andMoneyLogTypeEqualTo(ShopCode.SHOP_USER_MONEY_REFUND.getCode());
int r2 = userMoneyLogMapper.countByExample(userMoneyLogExample2);
if(r2>0){
CastException.cast(ShopCode.SHOP_USER_MONEY_REFUND_ALREADY);
}
//退款
tradeUser.setUserMoney(new BigDecimal(tradeUser.getUserMoney()).add(userMoneyLog.getUseMoney()).longValue());
userMapper.updateByPrimaryKey(tradeUser);
}
//5.记录订单余额使用日志
userMoneyLog.setCreateTime(new Date());
userMoneyLogMapper.insert(userMoneyLog);
return new Result(ShopCode.SHOP_SUCCESS.getSuccess(),ShopCode.SHOP_SUCCESS.getMessage());
}
付款才有用户金钱日志表。
mybatis用法:https://blog.csdn.net/u014756578/article/details/86490052
https://www.cnblogs.com/kangping/p/6001519.html
https://www.jianshu.com/p/44158fd09b6d
确认订单:
---29---30---
附录数据库表:
操作数量
---
传入订单不带优惠券金额的最后的金额是商品+运费的。
1.订单校验:订单是不是存在,订单中商品是不是存在,下单用户是不是存在,单价是不是合法(订单的单价和商品的单价是不是一样),订单的数量是不是合法(和库存比较是不是超过了库存)。
2.生成预订单:设置订单状态不可见,设置订单id,核算运费(包邮条件),
核算订单总的金额是不是合法的order.getOrderAmount(就是和运费的金额加之后的),
判断用户是不是使用余额付款是的话就和用户的余额进行比较(order.getMoneyPaid和user.getUserMoney比较),
判断用户是不是使用优惠券,订单里面更新优惠券金额,
核算最后的支付金额减去提货券金额的,这是除去余额需要支付的金额。order.getOrderAmount().subtract(order.getMoneyPaid()).subtract(order.getCouponPaid()),
插入订单。
3.扣减库存:用到商品订单日志(订单id,商品id,买的数量),先记录日志,扣减的是商品的库存,更新日志为负数买的数量。
4.扣减优惠券:原始的优惠券和用户绑定的。更新优惠券是已经使用,优惠券的订单id。
5.扣减用户余额:用到用户余额日志(订单id,商品id,日志类型,操作金额),先记录日志,接着进去更新余额的方法,如果传进来的是付款的日志则,查数据库如果之前存在了这个日志就是已经付款了,否则扣减余额。如果传进来是退款的日志,之前必须有付款的日志才可以,查询退款日志是不是已经付款,没退款就退款,更新日志。
---
测试:
启动微服务:
顺序:
order依赖userService。