1 下单业务时序图
调用下单服务:远程RPC调用订单服务
预订单:用户不可见
扣减库存:远程RPC调用库存服务
扣减优惠券:远程RPC调用优惠券服务
扣减用户预测:远程RPC调用用户服务
确认订单:将预订单状态改为用户可见
确认订单成功-->生成订单成功-->返回下单结果
确认订单成功-->MQ监听-->回退以上扣减&取消订单
2 下单基本流程(下单成功)
2.1 接口定义
shop-api模块
package com.irun2u.api;
import com.irun2u.entity.Result;
import com.irun2u.shop.pojo.TradeOrder;
/**
* @Author: haifei
* @Date: 2022/12/5 15:20
*/
public interface OrderService {
/**
* 确认订单
* @param order
* @return
*/
Result confirmOrder(TradeOrder order);
}
2.2 业务类实现
shop-order-service模块
package com.irun2u.service.impl;
import com.irun2u.api.OrderService;
import com.irun2u.entity.Result;
import com.irun2u.shop.pojo.TradeOrder;
/**
* @Author: haifei
* @Date: 2022/12/5 15:23
*/
public class OrderServiceImpl implements OrderService {
@Override
public Result confirmOrder(TradeOrder order) {
//1.校验订单
//2.生成预订单
try {
// 保证3-7的原子性
//3.扣减库存
//4.扣减优惠券
//5.使用余额
//6.确认订单
//7.返回成功状态
} catch (Exception e) {
//1.确认订单失败,发送消息
//2.返回失败状态
//以上具体实现详见2.9
}
return null;
}
}
2.3 校验订单
/**
* 校验订单
* @param order
*/
private void checkOrder(TradeOrder order) {
//1.校验订单是否存在
if (order == null){
CastException.cast(ShopCode.SHOP_ORDER_INVALID);
}
//2.校验订单中的商品是否存在
TradeGoods goods = goodsService.findOne(order.getGoodsId());
if (goods == null){
CastException.cast(ShopCode.SHOP_GOODS_NO_EXIST);
}
//3.校验下单用户是否存在
TradeUser user = userService.findOne(order.getUserId());
if (user == null){
CastException.cast(ShopCode.SHOP_USER_NO_EXIST);
}
//4.校验订单金额是否合法
// if (order.getPayAmount().compareTo(goods.getGoodsPrice().multiply(new BigDecimal(order.getGoodsNumber()))) != 0){
if (order.getGoodsPrice().compareTo(goods.getGoodsPrice()) != 0){
CastException.cast(ShopCode.SHOP_GOODS_PRICE_INVALID);
}
//5.校验订单商品数量是否合法
if (order.getGoodsNumber() >= goods.getGoodsNumber()){
CastException.cast(ShopCode.SHOP_GOODS_NUM_NOT_ENOUGH);
}
log.info("校验订单通过");
}
2.4 生成预订单
/**
* 生成预订单
* @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.核算订单运费是否合法
BigDecimal shippingFee = calculateShippinFee(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.判断用户是否使用余额
BigDecimal moneyPaid = order.getMoneyPaid();
if (moneyPaid != null){
//5.1判断订单中余额是否合法
int r = moneyPaid.compareTo(BigDecimal.ZERO);
if (r == -1){ //余额小于0
CastException.cast(ShopCode.SHOP_MONEY_PAID_LESS_ZERO);
}
if (r == 1){ //余额大于0
TradeUser user = userService.findOne(order.getUserId());
if (moneyPaid.compareTo(new BigDecimal(user.getUserMoney())) == 1){ //订单所需money大于用户所拥有余额
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()){
CastException.cast(ShopCode.SHOP_COUPON_ISUSED);
}
}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;
}
2.5 扣减库存
通过dubbo调用商品服务完成扣减库存
/**
* 扣减库存
* @param order
*/
private void reduceGoodsNum(TradeOrder order) {
TradeGoodsNumberLog goodsNumberLog = new TradeGoodsNumberLog();
goodsNumberLog.setOrderId(order.getOrderId());
goodsNumberLog.setGoodsId(order.getGoodsId());
goodsNumberLog.setGoodsNumber(order.getGoodsNumber());
Result result = goodsService.reduceGoodsNum(goodsNumberLog);
if (result.getSuccess().equals(ShopCode.SHOP_FAIL.getCode())){
CastException.cast(ShopCode.SHOP_REDUCE_GOODS_NUM_FAIL);
}
log.info("订单:" + order.getOrderId() + "扣减库存成功");
}
/**
* 扣减库存
* @param goodsNumberLog
* @return
*/
Result reduceGoodsNum(TradeGoodsNumberLog goodsNumberLog);
商品服务GoodsService扣减库存
@Override
public Result reduceGoodsNum(TradeGoodsNumberLog goodsNumberLog) {
if (goodsNumberLog == null ||
goodsNumberLog.getGoodsNumber() == null ||
goodsNumberLog.getGoodsId() == null ||
goodsNumberLog.getOrderId() == null ||
goodsNumberLog.getGoodsNumber() <= 0){
CastException.cast(ShopCode.SHOP_REQUEST_PARAMETER_VALID);
}
TradeGoods goods = goodsMapper.selectByPrimaryKey(goodsNumberLog.getGoodsId());
if (goods.getGoodsNumber() < goodsNumberLog.getGoodsNumber()){
//库存不足
CastException.cast(ShopCode.SHOP_GOODS_NUM_NOT_ENOUGH);
}
//减库存、更新库存
goods.setGoodsNumber(goods.getGoodsNumber() - goodsNumberLog.getGoodsNumber());
goodsMapper.updateByPrimaryKey(goods);
//记录库存操作的日志
goodsNumberLog.setGoodsNumber( -(goodsNumberLog.getGoodsNumber()) );
goodsNumberLog.setLogTime(new Date());
goodsNumberLogMapper.insert(goodsNumberLog);
return new Result(ShopCode.SHOP_SUCCESS.getSuccess(), ShopCode.SHOP_SUCCESS.getMessage());
}
2.6 扣减优惠券
通过dubbo完成扣减优惠券
/**
* 扣减优惠券
* @param order
*/
private void updateCouponStatus(TradeOrder order) {
if(order.getCouponId()!=null){
TradeCoupon coupon = couponService.findOne(order.getCouponId());
coupon.setOrderId(order.getOrderId());
coupon.setIsUsed(ShopCode.SHOP_COUPON_ISUSED.getCode());
coupon.setUsedTime(new Date());
//更新优惠券状态
Result result = couponService.updateCouponStatus(coupon);
if(result.getSuccess().equals(ShopCode.SHOP_FAIL.getSuccess())){
CastException.cast(ShopCode.SHOP_COUPON_USE_FAIL);
}
log.info("订单:"+order.getOrderId()+",使用优惠券");
}
}
/**
* 更新优惠券状态
* @param coupon
* @return
*/
Result updateCouponStatus(TradeCoupon coupon);
优惠券服务CouponService更改优惠券状态
@Override
public Result updateCouponStatus(TradeCoupon coupon) {
if(coupon==null||coupon.getCouponId()==null){
CastException.cast(ShopCode.SHOP_REQUEST_PARAMETER_VALID);
}
//更新优惠券状态
couponMapper.updateByPrimaryKey(coupon);
return new Result(ShopCode.SHOP_SUCCESS.getSuccess(),ShopCode.SHOP_SUCCESS.getMessage());
}
2.7 扣减用户余额
/**
* 扣减余额
* @param order
*/
private void reduceMoneyPaid(TradeOrder order) {
if(order.getMoneyPaid()!=null && order.getMoneyPaid().compareTo(BigDecimal.ZERO)==1){
TradeUserMoneyLog userMoneyLog = new TradeUserMoneyLog();
userMoneyLog.setOrderId(order.getOrderId());
userMoneyLog.setUserId(order.getUserId());
userMoneyLog.setUseMoney(order.getMoneyPaid());
userMoneyLog.setMoneyLogType(ShopCode.SHOP_USER_MONEY_PAID.getCode());
Result result = userService.updateMoneyPaid(userMoneyLog);
if(result.getSuccess().equals(ShopCode.SHOP_FAIL.getSuccess())){
CastException.cast(ShopCode.SHOP_USER_MONEY_REDUCE_FAIL);
}
log.info("订单:"+order.getOrderId()+",扣减余额成功");
}
}
/**
* 更新用户余额
* @param userMoneyLog
* @return
*/
Result updateMoneyPaid(TradeUserMoneyLog userMoneyLog);
@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){ //count查询不可能为复数,所以不会<0,没查到就是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());
}
2.8 确认订单
/**
* 确认订单(修改预订单状态)
* @param order
*/
private void updateOrderStatus(TradeOrder order) {
order.setOrderStatus(ShopCode.SHOP_ORDER_CONFIRM.getCode());
order.setPayStatus(ShopCode.SHOP_ORDER_PAY_STATUS_NO_PAY.getCode());
order.setConfirmTime(new Date());
int r = orderMapper.updateByPrimaryKey(order);
if(r<=0){
CastException.cast(ShopCode.SHOP_ORDER_CONFIRM_FAIL);
}
log.info("订单:"+order.getOrderId()+"确认订单成功");
}
2.9 小结(完整源码)
package com.irun2u.service.impl;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import com.irun2u.api.CouponService;
import com.irun2u.api.GoodsService;
import com.irun2u.api.OrderService;
import com.irun2u.api.UserService;
import com.irun2u.constant.ShopCode;
import com.irun2u.entity.Result;
import com.irun2u.exception.CastException;
import com.irun2u.mapper.TradeOrderMapper;
import com.irun2u.shop.pojo.*;
import com.irun2u.utils.IDWorker;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.Date;
/**
* @Author: haifei
* @Date: 2022/12/5 15:23
*/
@Slf4j
@Component
@Service(interfaceClass = OrderService.class)
public class OrderServiceImpl implements OrderService {
@Reference
private GoodsService goodsService;
@Reference
private UserService userService;
@Reference
private CouponService couponService;
@Autowired
private IDWorker idWorker;
@Autowired
private TradeOrderMapper orderMapper;
@Override
public Result confirmOrder(TradeOrder order) {
//1.校验订单
checkOrder(order);
//2.生成预订单
Long orderId = savePreOrder(order);
try {// 保证3-7的原子性
//3.扣减库存
reduceGoodsNum(order);
//4.扣减优惠券
updateCouponStatus(order);
//5.扣减余额
reduceMoneyPaid(order);
//6.确认订单
updateOrderStatus(order);
//7.返回成功状态
return new Result(ShopCode.SHOP_SUCCESS.getSuccess(),ShopCode.SHOP_SUCCESS.getMessage());
} catch (Exception e) {
//1.确认订单失败,发送消息
//2.返回失败状态
return new Result(ShopCode.SHOP_FAIL.getSuccess(),ShopCode.SHOP_FAIL.getMessage());
}
}
/**
* 确认订单(修改预订单状态)
* @param order
*/
private void updateOrderStatus(TradeOrder order) {
order.setOrderStatus(ShopCode.SHOP_ORDER_CONFIRM.getCode());
order.setPayStatus(ShopCode.SHOP_ORDER_PAY_STATUS_NO_PAY.getCode());
order.setConfirmTime(new Date());
int r = orderMapper.updateByPrimaryKey(order);
if(r<=0){
CastException.cast(ShopCode.SHOP_ORDER_CONFIRM_FAIL);
}
log.info("订单:"+order.getOrderId()+"确认订单成功");
}
/**
* 扣减余额
* @param order
*/
private void reduceMoneyPaid(TradeOrder order) {
if(order.getMoneyPaid()!=null && order.getMoneyPaid().compareTo(BigDecimal.ZERO)==1){
TradeUserMoneyLog userMoneyLog = new TradeUserMoneyLog();
userMoneyLog.setOrderId(order.getOrderId());
userMoneyLog.setUserId(order.getUserId());
userMoneyLog.setUseMoney(order.getMoneyPaid());
userMoneyLog.setMoneyLogType(ShopCode.SHOP_USER_MONEY_PAID.getCode());
Result result = userService.updateMoneyPaid(userMoneyLog);
if(result.getSuccess().equals(ShopCode.SHOP_FAIL.getSuccess())){
CastException.cast(ShopCode.SHOP_USER_MONEY_REDUCE_FAIL);
}
log.info("订单:"+order.getOrderId()+",扣减余额成功");
}
}
/**
* 扣减优惠券
* @param order
*/
private void updateCouponStatus(TradeOrder order) {
if(order.getCouponId()!=null){
TradeCoupon coupon = couponService.findOne(order.getCouponId());
coupon.setOrderId(order.getOrderId());
coupon.setIsUsed(ShopCode.SHOP_COUPON_ISUSED.getCode());
coupon.setUsedTime(new Date());
//更新优惠券状态
Result result = couponService.updateCouponStatus(coupon);
if(result.getSuccess().equals(ShopCode.SHOP_FAIL.getSuccess())){
CastException.cast(ShopCode.SHOP_COUPON_USE_FAIL);
}
log.info("订单:"+order.getOrderId()+",使用优惠券");
}
}
/**
* 扣减库存
* @param order
*/
private void reduceGoodsNum(TradeOrder order) {
TradeGoodsNumberLog goodsNumberLog = new TradeGoodsNumberLog();
goodsNumberLog.setOrderId(order.getOrderId());
goodsNumberLog.setGoodsId(order.getGoodsId());
goodsNumberLog.setGoodsNumber(order.getGoodsNumber());
Result result = goodsService.reduceGoodsNum(goodsNumberLog);
if (result.getSuccess().equals(ShopCode.SHOP_FAIL.getCode())){
CastException.cast(ShopCode.SHOP_REDUCE_GOODS_NUM_FAIL);
}
log.info("订单:" + order.getOrderId() + "扣减库存成功");
}
/**
* 校验订单
* @param order
*/
private void checkOrder(TradeOrder order) {
//1.校验订单是否存在
if (order == null){
CastException.cast(ShopCode.SHOP_ORDER_INVALID);
}
//2.校验订单中的商品是否存在
TradeGoods goods = goodsService.findOne(order.getGoodsId());
if (goods == null){
CastException.cast(ShopCode.SHOP_GOODS_NO_EXIST);
}
//3.校验下单用户是否存在
TradeUser user = userService.findOne(order.getUserId());
if (user == null){
CastException.cast(ShopCode.SHOP_USER_NO_EXIST);
}
//4.校验订单金额是否合法
// if (order.getPayAmount().compareTo(goods.getGoodsPrice().multiply(new BigDecimal(order.getGoodsNumber()))) != 0){
if (order.getGoodsPrice().compareTo(goods.getGoodsPrice()) != 0){
CastException.cast(ShopCode.SHOP_GOODS_PRICE_INVALID);
}
//5.校验订单商品数量是否合法
if (order.getGoodsNumber() >= goods.getGoodsNumber()){
CastException.cast(ShopCode.SHOP_GOODS_NUM_NOT_ENOUGH);
}
log.info("校验订单通过");
}
/**
* 生成预订单
* @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.核算订单运费是否合法
BigDecimal shippingFee = calculateShippinFee(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.判断用户是否使用余额
BigDecimal moneyPaid = order.getMoneyPaid();
if (moneyPaid != null){
//5.1判断订单中余额是否合法
int r = moneyPaid.compareTo(BigDecimal.ZERO);
if (r == -1){ //余额小于0
CastException.cast(ShopCode.SHOP_MONEY_PAID_LESS_ZERO);
}
if (r == 1){ //余额大于0
TradeUser user = userService.findOne(order.getUserId());
if (moneyPaid.compareTo(new BigDecimal(user.getUserMoney())) == 1){ //订单所需money大于用户所拥有余额
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()){
CastException.cast(ShopCode.SHOP_COUPON_ISUSED);
}
}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;
}
/**
* 核算运费
* 【BigDecimal】https://baike.baidu.com/item/BigDecimal/5131707?fr=aladdin
* @param orderAmount
* @return
*/
private BigDecimal calculateShippinFee(BigDecimal orderAmount) {
//小于100不收运费;大于100收10
if (orderAmount.compareTo(new BigDecimal(100)) == 1){
return BigDecimal.ZERO;
}else {
return new BigDecimal(10);
}
}
}
2.10 sb集成junit测试下单流程
zk集群启动
dubbo-admin启动