From 02fe48e408dfdfddc8b312b2f7109f7d5e56b42d Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 15 May 2026 08:52:47 +0800 Subject: [PATCH] fix(wms): 简化库存余额并发补行处理 --- yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java | 16 +--------------- yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java index 0ab6f23..cc71d66 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java @@ -149,20 +149,6 @@ public class WmsInventoryServiceImpl implements WmsInventoryService { } private List createMissingInventoryList(List missingInventories) { - // 优先批量插入缺失库存行;并发唯一键冲突时,再逐个插入并回查冲突行。 - List newInventories = convertList(missingInventories, missingInventory -> - new WmsInventoryDO().setSkuId(missingInventory.getSkuId()).setWarehouseId(missingInventory.getWarehouseId()) - .setQuantity(BigDecimal.ZERO)); - try { - inventoryMapper.insertBatch(newInventories); - return newInventories; - } catch (DuplicateKeyException ex) { - // 并发事务可能已经补齐部分库存行,降级为逐个插入并回查唯一键冲突的行。 - log.warn("[createMissingInventoryList][missingInventories({}) 批量插入库存行冲突,降级为逐个插入]", - missingInventories, ex); - } - - // 批量插入失败后逐条补齐;单条唯一键冲突时,回查并使用并发事务创建的库存行。 List createdInventories = new ArrayList<>(missingInventories.size()); for (WmsInventoryDO missingInventory : missingInventories) { WmsInventoryDO inventory = new WmsInventoryDO().setSkuId(missingInventory.getSkuId()) @@ -173,7 +159,7 @@ public class WmsInventoryServiceImpl implements WmsInventoryService { } catch (DuplicateKeyException ex) { inventory = inventoryMapper.selectBySkuIdAndWarehouseId( missingInventory.getSkuId(), missingInventory.getWarehouseId()); - log.warn("[createMissingInventoryList][missingInventory({}) 插入库存行冲突,回查已有库存行]", missingInventory, ex); + log.warn("[createMissingInventoryList][missingInventory({}) 插入库存行冲突,回查已有库存行]", missingInventory); if (inventory == null) { throw ex; } diff --git a/yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java b/yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java index f2a9fb3..451292d 100644 --- a/yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java +++ b/yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java @@ -25,10 +25,16 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.INVENTORY_QUANTITY_NOT_ENOUGH; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.when; @@ -220,6 +226,48 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { } @Test + public void testChangeInventory_concurrentCreateSameInventoryOnlyOneBalance() throws Exception { + // mock 数据 + WmsItemDO item = createItem("ITEM-001", "红富士苹果"); + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装"); + int threadCount = 4; + CountDownLatch readyLatch = new CountDownLatch(threadCount); + CountDownLatch startLatch = new CountDownLatch(1); + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + List> futures = new ArrayList<>(threadCount); + for (int i = 0; i < threadCount; i++) { + futures.add(executorService.submit(() -> { + readyLatch.countDown(); + try { + startLatch.await(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(ex); + } + inventoryService.changeInventory(createChangeReq(sku.getId(), 100L, "1.00")); + })); + } + + try { + // 调用 + assertTrue(readyLatch.await(5, TimeUnit.SECONDS)); + startLatch.countDown(); + for (Future future : futures) { + future.get(10, TimeUnit.SECONDS); + } + } finally { + executorService.shutdownNow(); + } + + // 断言:并发补行后仍只有一条库存余额,数量累计正确 + List inventories = inventoryMapper.selectListByKeys(Collections.singletonList( + new WmsInventoryDO().setSkuId(sku.getId()).setWarehouseId(100L))); + assertEquals(1, inventories.size()); + assertEquals(0, new BigDecimal("4.00").compareTo(inventories.get(0).getQuantity())); + assertEquals(threadCount, inventoryHistoryMapper.selectCount()); + } + + @Test public void testChangeInventory_quantityNotEnough() { // mock 数据 WmsItemDO item = createItem("ITEM-001", "红富士苹果"); -- libgit2 0.21.4