HM-RocketMQ2.5【下單業務】
阿新 • • 發佈:2022-12-08
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啟動