Ver Fonte

feat(order): 新增父订单状态同步更新和订单号重复生成防护机制

- 在订单发货服务中增加父订单状态同步更新逻辑,当子订单状态变更时自动更新父订单状态
- 实现订单创建时的订单号重复防护机制,通过重试逻辑避免因并发导致的唯一索引冲突
- 新增对多种数据库异常类型的捕获和处理,包括DuplicateKeyException和SQLIntegrityConstraintViolationException
- 在PC端订单控制器中自动设置订单分割标识,默认为非子订单状态
- 修复代码中的注释格式和文档字符串语法错误
hurx há 1 mês atrás
pai
commit
5a06993345

+ 6 - 4
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/controller/pc/PcOrderController.java

@@ -9,6 +9,7 @@ import org.apache.dubbo.config.annotation.DubboReference;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.enums.OrderSourceEnum;
 import org.dromara.common.core.enums.OrderSourceEnum;
 import org.dromara.common.core.enums.OrderStatus;
 import org.dromara.common.core.enums.OrderStatus;
+import org.dromara.common.core.enums.SysPlatformYesNo;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.log.enums.BusinessType;
@@ -93,6 +94,7 @@ public class PcOrderController extends BaseController {
         Long customerId = LoginHelper.getLoginUser().getCustomerId();
         Long customerId = LoginHelper.getLoginUser().getCustomerId();
         // 强制设置企业ID,防止越权访问
         // 强制设置企业ID,防止越权访问
         bo.setCustomerId(customerId);
         bo.setCustomerId(customerId);
+        bo.setIsSplitChild(SysPlatformYesNo.NO.getCode());
 
 
         return orderMainService.queryPageList(bo, pageQuery);
         return orderMainService.queryPageList(bo, pageQuery);
     }
     }
@@ -105,8 +107,8 @@ public class PcOrderController extends BaseController {
     }
     }
 
 
     /**
     /**
-    * 查询当前用户能审核的订单id
-    * */
+     * 查询当前用户能审核的订单id
+     */
     @GetMapping("/getCheckOrderIds")
     @GetMapping("/getCheckOrderIds")
     public R<List<Long>> getCheckOrderIds() {
     public R<List<Long>> getCheckOrderIds() {
         Long contactId = remoteCustomerService.selectContactIdByUserId(LoginHelper.getUserId());
         Long contactId = remoteCustomerService.selectContactIdByUserId(LoginHelper.getUserId());
@@ -125,8 +127,8 @@ public class PcOrderController extends BaseController {
     }
     }
 
 
     /**
     /**
-    * 查询当前订单的流程节点列表
-    * */
+     * 查询当前订单的流程节点列表
+     */
     @GetMapping("/getOrderFlowNodes/{orderId}")
     @GetMapping("/getOrderFlowNodes/{orderId}")
     public R<List<OrderCustomerFlowNodeLink>> getOrderFlowNodes(@PathVariable("orderId") Long orderId) {
     public R<List<OrderCustomerFlowNodeLink>> getOrderFlowNodes(@PathVariable("orderId") Long orderId) {
         return R.ok(orderCustomerFlowNodeLinkService.list(
         return R.ok(orderCustomerFlowNodeLinkService.list(

+ 23 - 0
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/service/impl/OrderDeliverServiceImpl.java

@@ -219,12 +219,35 @@ public class OrderDeliverServiceImpl extends ServiceImpl<OrderDeliverMapper, Ord
             saveOrderDeliverProducts(deliver.getId(), bo.getOrderDeliverProducts(), false);
             saveOrderDeliverProducts(deliver.getId(), bo.getOrderDeliverProducts(), false);
 
 
             // 9. 更新订单主表状态
             // 9. 更新订单主表状态
+
+
+            OrderMain currentOrder = orderMainMapper.selectById(orderId);
+
+            if (currentOrder == null) {
+                throw new RuntimeException("订单不存在");
+            }
+
+            // 2. 更新当前订单状态
             LambdaUpdateWrapper<OrderMain> updateWrapper = new LambdaUpdateWrapper<OrderMain>()
             LambdaUpdateWrapper<OrderMain> updateWrapper = new LambdaUpdateWrapper<OrderMain>()
                 .eq(OrderMain::getId, orderId)
                 .eq(OrderMain::getId, orderId)
                 .set(OrderMain::getOrderStatus, newStatus);
                 .set(OrderMain::getOrderStatus, newStatus);
 
 
             orderMainMapper.update(null, updateWrapper);
             orderMainMapper.update(null, updateWrapper);
 
 
+            // 3. 【新增逻辑】如果存在父订单,同步更新父订单状态
+            if (currentOrder.getParentOrderId() != null) {
+                // “同步更新”处理,即直接设为相同状态。
+
+                LambdaUpdateWrapper<OrderMain> parentUpdateWrapper = new LambdaUpdateWrapper<OrderMain>()
+                    .eq(OrderMain::getId, currentOrder.getParentOrderId())
+                    .set(OrderMain::getOrderStatus, newStatus);
+
+                orderMainMapper.update(null, parentUpdateWrapper);
+
+                // 可选:如果需要递归更新更上层的父订单(如果存在多级嵌套),可以在此处加循环或递归调用
+            }
+
+
             log.info("新增订单发货单成功:{}", bo.getId());
             log.info("新增订单发货单成功:{}", bo.getId());
             return true;
             return true;
         } catch (RuntimeException e) {
         } catch (RuntimeException e) {

+ 90 - 30
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/service/impl/OrderMainServiceImpl.java

@@ -45,10 +45,13 @@ import org.dromara.order.utils.kd100.domain.TrackVO;
 import org.dromara.system.api.RemoteComLogisticsCompanyService;
 import org.dromara.system.api.RemoteComLogisticsCompanyService;
 import org.dromara.system.api.RemoteDeptService;
 import org.dromara.system.api.RemoteDeptService;
 import org.dromara.system.api.RemoteUserService;
 import org.dromara.system.api.RemoteUserService;
+import org.mybatis.spring.MyBatisSystemException;
+import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
 
 
 import java.math.BigDecimal;
 import java.math.BigDecimal;
+import java.sql.SQLIntegrityConstraintViolationException;
 import java.util.*;
 import java.util.*;
 import java.util.function.Function;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
@@ -307,47 +310,104 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
             // 应付总额 = 商品总价 + 运费
             // 应付总额 = 商品总价 + 运费
             BigDecimal payableAmount = totalAmount.add(bo.getShippingFee());
             BigDecimal payableAmount = totalAmount.add(bo.getShippingFee());
 
 
-            // 2. 生成订单号并转换主单
-            String orderNo = SequenceUtils.generateOrderCode("RS");
-            bo.setOrderNo(orderNo);
+            // 2. 生成订单号并转换主单 (带防重兜底)
+            String orderNo = null;
+            OrderMain orderMain = null;
+            boolean mainSaved = false;
+
+            // 最大重试次数,防止死循环
+            int maxRetries = 5;
+            int retryCount = 0;
+
+            while (!mainSaved && retryCount < maxRetries) {
+                try {
+                    retryCount++;
+
+                    // --- 步骤 A: 生成订单号 ---
+                    orderNo = SequenceUtils.generateOrderCode("RS");
+                    bo.setOrderNo(orderNo);
+
+                    bo.setProductQuantity(productQuantity);
+                    bo.setTotalAmount(totalAmount);
+                    bo.setPayableAmount(payableAmount);
+
+                    // --- 步骤 B: 处理下单时间 ---
+                    Date orderTimeToUse;
+                    if (bo.getOrderTime() != null) {
+                        Calendar cal = Calendar.getInstance();
+                        cal.setTime(bo.getOrderTime());
+                        Calendar now = Calendar.getInstance();
+                        cal.set(Calendar.HOUR_OF_DAY, now.get(Calendar.HOUR_OF_DAY));
+                        cal.set(Calendar.MINUTE, now.get(Calendar.MINUTE));
+                        cal.set(Calendar.SECOND, now.get(Calendar.SECOND));
+                        cal.set(Calendar.MILLISECOND, now.get(Calendar.MILLISECOND));
+                        orderTimeToUse = cal.getTime();
+                    } else {
+                        orderTimeToUse = new Date();
+                    }
+                    bo.setOrderTime(orderTimeToUse);
 
 
-            bo.setProductQuantity(productQuantity);
+                    // --- 步骤 C: 转换对象 ---
+                    orderMain = MapstructUtils.convert(bo, OrderMain.class);
+                    validEntityBeforeSave(orderMain);
 
 
-            bo.setTotalAmount(totalAmount);
+                    // --- 步骤 D: 尝试插入 (关键兜底点) ---
+                    int insertResult = baseMapper.insert(orderMain);
 
 
-            bo.setPayableAmount(payableAmount);
+                    if (insertResult > 0) {
+                        mainSaved = true;
+                    } else {
+                        // 如果返回 0 但不是因为异常(极少见),也视为失败进行重试
+                        throw new RuntimeException("主订单保存返回影响行数为0");
+                    }
 
 
-            // 处理下单时间:前台不传则用当前时间,后台传了则按规则调整
-            Date orderTimeToUse;
-            if (bo.getOrderTime() != null) {
-                // 后台传了时间:保留日期,更新时分秒为当前
-                Calendar cal = Calendar.getInstance();
-                cal.setTime(bo.getOrderTime());
-                Calendar now = Calendar.getInstance();
-                cal.set(Calendar.HOUR_OF_DAY, now.get(Calendar.HOUR_OF_DAY));
-                cal.set(Calendar.MINUTE, now.get(Calendar.MINUTE));
-                cal.set(Calendar.SECOND, now.get(Calendar.SECOND));
-                cal.set(Calendar.MILLISECOND, now.get(Calendar.MILLISECOND));
-                orderTimeToUse = cal.getTime();
-            } else {
-                // 前台未传:直接使用当前时间
-                orderTimeToUse = new Date();
-            }
-            bo.setOrderTime(orderTimeToUse);
+                } catch (Exception e) {
+                    // 判断是否为唯一索引冲突异常
+                    boolean isDuplicate = false;
 
 
+                    // 1. Spring 的 DuplicateKeyException
+                    if (e instanceof DuplicateKeyException) {
+                        isDuplicate = true;
+                    }
+                    // 2. MyBatis 包装的异常,需要解包查看 cause
+                    else if (e instanceof MyBatisSystemException) {
+                        Throwable cause = e.getCause();
+                        if (cause instanceof SQLIntegrityConstraintViolationException) {
+                            isDuplicate = true;
+                        }
+                        // 某些驱动直接报这个
+                        else if (cause != null && cause.getMessage() != null && cause.getMessage().contains("Duplicate entry")) {
+                            isDuplicate = true;
+                        }
+                    }
+                    // 3. 直接捕获 SQL 异常 (视具体驱动而定)
+                    else if (e instanceof java.sql.SQLException) {
+                        if (e.getMessage() != null && e.getMessage().contains("Duplicate entry")) {
+                            isDuplicate = true;
+                        }
+                    }
 
 
-            // 转换主订单
-            OrderMain orderMain = MapstructUtils.convert(bo, OrderMain.class);
-            validEntityBeforeSave(orderMain);
+                    if (isDuplicate) {
+                        // 如果是重复,记录日志(可选),然后继续 while 循环重新生成
+                        continue;
+                    } else {
+                        // 如果是其他异常(如数据库连接断开、字段非空校验失败等),直接抛出,不重试
+                        throw new RuntimeException("创建主订单失败: " + e.getMessage(), e);
+                    }
+                }
+            }
 
 
-            // 3. 插入主订单
-            boolean mainSaved = baseMapper.insert(orderMain) > 0;
             if (!mainSaved) {
             if (!mainSaved) {
-                throw new RuntimeException("主订单保存失败");
+                // 超过最大重试次数仍未成功
+                throw new RuntimeException("生成订单号失败,多次尝试均发生重复,请检查序列生成器或数据库状态");
             }
             }
 
 
+            // 此时 orderMain 已成功入库,且 ID 已回填 (如果使用了自增或雪花算法回填)
             Long orderId = orderMain.getId();
             Long orderId = orderMain.getId();
 
 
+            String orderNum = orderMain.getOrderNo();
+
+
             // 4. 转换并填充子订单商品
             // 4. 转换并填充子订单商品
             List<OrderProduct> orderProducts = orderProductBos.stream()
             List<OrderProduct> orderProducts = orderProductBos.stream()
                 .map(productBo -> {
                 .map(productBo -> {
@@ -364,7 +424,7 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
 
 
                     OrderProduct product = MapstructUtils.convert(productBo, OrderProduct.class);
                     OrderProduct product = MapstructUtils.convert(productBo, OrderProduct.class);
                     product.setOrderId(orderId);
                     product.setOrderId(orderId);
-                    product.setOrderNo(orderNo);
+                    product.setOrderNo(orderNum);
 
 
                     // 小计 = 单价 × 数量
                     // 小计 = 单价 × 数量
                     BigDecimal subtotal = unitPrice.multiply(BigDecimal.valueOf(quantity));
                     BigDecimal subtotal = unitPrice.multiply(BigDecimal.valueOf(quantity));