From 7a215e51f9f4066f2024d8476e5b5db2c06358a1 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期二, 07 四月 2026 13:51:01 +0800
Subject: [PATCH] #lua锁示例

---
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WmsRedisLuaService.java     |   18 ++
 rsf-server/src/main/resources/wms-lua/location-claim.lua                                         |   19 --
 rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java                 |   57 +++++----
 rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/AsnOrderPressureSchedules.java |  216 ++++++++++++++++++++++++++++++++++++
 rsf-server/src/main/resources/wms-lua/station-claim.lua                                          |    9 +
 rsf-server/src/main/resources/application-dev.yml                                                |    8 +
 6 files changed, 284 insertions(+), 43 deletions(-)

diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/AsnOrderPressureSchedules.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/AsnOrderPressureSchedules.java
new file mode 100644
index 0000000..8c187c6
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/AsnOrderPressureSchedules.java
@@ -0,0 +1,216 @@
+package com.vincent.rsf.server.manager.schedules;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.manager.entity.Matnr;
+import com.vincent.rsf.server.manager.entity.WkOrder;
+import com.vincent.rsf.server.manager.entity.WkOrderItem;
+import com.vincent.rsf.server.manager.service.AsnOrderItemService;
+import com.vincent.rsf.server.manager.service.AsnOrderService;
+import com.vincent.rsf.server.manager.service.MatnrService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * ASN 鍗曟嵁鍘嬫祴鏁版嵁瀹氭椂鐢熸垚鍣ㄣ��
+ */
+@Slf4j
+@Component
+public class AsnOrderPressureSchedules {
+
+    private static final DateTimeFormatter ORDER_CODE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+    private static final String ORDER_TYPE = "in";
+    private static final String ORDER_WORK_TYPE = "71";
+    private static final Integer TENANT_ID = 1;
+    private static final Long USER_ID = 51L;
+    private static final String MEMO = "ASN_PRESSURE_TEST";
+
+    @Autowired
+    private AsnOrderService asnOrderService;
+    @Autowired
+    private AsnOrderItemService asnOrderItemService;
+    @Autowired
+    private MatnrService matnrService;
+
+    @Value("${pressure.asn-order.enabled:false}")
+    private boolean enabled;
+
+    @Value("${pressure.asn-order.order-count-per-run:20}")
+    private int orderCountPerRun;
+
+    @Value("${pressure.asn-order.item-count-per-order:5}")
+    private int itemCountPerOrder;
+
+    @Value("${pressure.asn-order.item-qty:10}")
+    private double itemQty;
+
+    @Scheduled(cron = "${pressure.asn-order.cron:0/10 * * * * ?}")
+    @Transactional(rollbackFor = Exception.class)
+    public synchronized void insertPressureOrders() {
+        if (!enabled) {
+            return;
+        }
+        if (orderCountPerRun <= 0 || itemCountPerOrder <= 0 || itemQty <= 0) {
+            log.warn("ASN鍘嬫祴浠诲姟閰嶇疆鏃犳晥锛岃烦杩囨墽琛�: orderCountPerRun={}, itemCountPerOrder={}, itemQty={}",
+                    orderCountPerRun, itemCountPerOrder, itemQty);
+            return;
+        }
+
+        List<Matnr> matnrs = loadMatnrs();
+        if (matnrs.isEmpty()) {
+            log.warn("ASN鍘嬫祴浠诲姟鏈幏鍙栧埌鍙敤鐗╂枡锛岃烦杩囨墽琛�");
+            return;
+        }
+        Collections.shuffle(matnrs);
+
+        Date now = new Date();
+        LocalDateTime nowTime = LocalDateTime.now();
+        double totalQty = itemCountPerOrder * itemQty;
+
+        List<WkOrder> orders = new ArrayList<>(orderCountPerRun);
+        for (int i = 0; i < orderCountPerRun; i++) {
+            orders.add(buildOrder(now, nowTime, totalQty, i));
+        }
+        if (!asnOrderService.saveBatch(orders, 200)) {
+            throw new CoolException("ASN鍘嬫祴涓诲崟鎻掑叆澶辫触");
+        }
+
+        List<WkOrderItem> items = new ArrayList<>(orderCountPerRun * itemCountPerOrder);
+        for (int orderIndex = 0; orderIndex < orders.size(); orderIndex++) {
+            WkOrder order = orders.get(orderIndex);
+            for (int itemIndex = 0; itemIndex < itemCountPerOrder; itemIndex++) {
+                Matnr matnr = matnrs.get((orderIndex * itemCountPerOrder + itemIndex) % matnrs.size());
+                items.add(buildOrderItem(order, matnr, now, orderIndex, itemIndex));
+            }
+        }
+        if (!asnOrderItemService.saveBatch(items, 500)) {
+            throw new CoolException("ASN鍘嬫祴鏄庣粏鎻掑叆澶辫触");
+        }
+
+        log.info("ASN鍘嬫祴浠诲姟鎵ц瀹屾垚锛屾湰娆℃彃鍏ヤ富鍗� {} 鏉★紝鏄庣粏 {} 鏉�", orders.size(), items.size());
+    }
+
+    private List<Matnr> loadMatnrs() {
+        int needCount = Math.min(Math.max(orderCountPerRun * itemCountPerOrder, 200), 2000);
+        return matnrService.list(new LambdaQueryWrapper<Matnr>()
+                .eq(Matnr::getDeleted, 0)
+                .eq(Matnr::getStatus, 1)
+                .select(Matnr::getId, Matnr::getCode, Matnr::getName, Matnr::getFieldsIndex,
+                        Matnr::getSpec, Matnr::getModel, Matnr::getUnit, Matnr::getPurUnit,
+                        Matnr::getStockUnit, Matnr::getBaseUnit, Matnr::getUseOrgId,
+                        Matnr::getUseOrgName, Matnr::getErpClsId)
+                .orderByDesc(Matnr::getId)
+                .last("limit " + needCount));
+    }
+
+    private WkOrder buildOrder(Date now, LocalDateTime nowTime, double totalQty, int sequence) {
+        String suffix = String.format("%04d", sequence + 1);
+        String code = "erp" + nowTime.format(ORDER_CODE_FORMATTER) + suffix;
+        long serialNo = System.currentTimeMillis() * 1000 + sequence;
+
+        return new WkOrder()
+                .setCode(code)
+                .setPoCode(code)
+                .setPoId(serialNo)
+                .setType(ORDER_TYPE)
+                .setWkType(ORDER_WORK_TYPE)
+                .setAnfme(totalQty)
+                .setQty(totalQty)
+                .setWorkQty(0.0)
+                .setCheckType(0)
+                .setRleStatus((short) 0)
+                .setNtyStatus(0)
+                .setExceStatus((short) 4)
+                .setStatus(1)
+                .setDeleted(0)
+                .setTenantId(TENANT_ID)
+                .setCreateBy(USER_ID)
+                .setCreateTime(now)
+                .setUpdateBy(USER_ID)
+                .setUpdateTime(now)
+                .setMemo(MEMO)
+                .setReportOnce(4)
+                .setBusinessTime(now)
+                .setStationId("1215")
+                .setOrderInternalCode(String.valueOf(serialNo))
+                .setStockDirect("stockDirect")
+                .setCustomerId("custom1")
+                .setCustomerName("瀹㈡埛1")
+                .setSupplierId("gongys1")
+                .setSupplierName("渚涘簲鍟�1")
+                .setStockOrgId("stockYH")
+                .setStockOrgName("娴欐睙閾舵箹绠卞寘鏈夐檺鍏徃浠撳簱")
+                .setPurchaseOrgId("yhcaigou")
+                .setPurchaseOrgName("娴欐睙閾舵箹绠卞寘鏈夐檺鍏徃閲囪喘")
+                .setPurchaseUserId("caigouyuan1")
+                .setPurchaseUserName("閲囪喘鍛�1")
+                .setPrdOrgId("prdYH")
+                .setPrdOrgName("娴欐睙閾舵箹绠卞寘鏈夐檺鍏徃")
+                .setSaleOrgId("sale1")
+                .setSaleOrgName("鐢熶骇缁�1")
+                .setSaleUserId("shengchanyuan1")
+                .setSaleUserName("鐢熶骇鍛�1")
+                .setVersion(0);
+    }
+
+    private WkOrderItem buildOrderItem(WkOrder order, Matnr matnr, Date now, int orderIndex, int itemIndex) {
+        String stockUnit = StringUtils.firstNonBlank(matnr.getStockUnit(), matnr.getPurUnit(), matnr.getUnit(), matnr.getBaseUnit());
+        String purUnit = StringUtils.firstNonBlank(matnr.getPurUnit(), matnr.getUnit(), matnr.getStockUnit(), matnr.getBaseUnit());
+        String baseUnit = StringUtils.firstNonBlank(matnr.getBaseUnit(), matnr.getUnit(), matnr.getStockUnit(), matnr.getPurUnit());
+        String batchCode = "B" + new SimpleDateFormat("yyyyMMddHHmmss").format(now)
+                + String.format("%02d%02d", orderIndex + 1, itemIndex + 1);
+        String trackCode = "T" + System.currentTimeMillis() + String.format("%02d%02d", orderIndex + 1, itemIndex + 1);
+
+        return new WkOrderItem()
+                .setOrderId(order.getId())
+                .setOrderCode(order.getCode())
+                .setPlatItemId("M" + (itemIndex + 1))
+                .setPoCode(order.getPoCode())
+                .setFieldsIndex(matnr.getFieldsIndex())
+                .setMatnrId(matnr.getId())
+                .setMatnrCode(matnr.getCode())
+                .setMaktx(matnr.getName())
+                .setSpec(matnr.getSpec())
+                .setModel(matnr.getModel())
+                .setAnfme(itemQty)
+                .setWorkQty(0.0)
+                .setPurQty(itemQty)
+                .setQty(itemQty)
+                .setStockUnit(stockUnit)
+                .setPurUnit(purUnit)
+                .setBatch(batchCode)
+                .setSplrBatch(batchCode)
+                .setSplrCode("gongys1")
+                .setSplrName("渚涘簲鍟�1")
+                .setTrackCode(trackCode)
+                .setBarcode(trackCode)
+                .setProdTime(new SimpleDateFormat("yyyy-MM-dd").format(now))
+                .setNtyStatus(0)
+                .setStatus(1)
+                .setDeleted(0)
+                .setTenantId(TENANT_ID)
+                .setCreateBy(USER_ID)
+                .setCreateTime(now)
+                .setUpdateBy(USER_ID)
+                .setUpdateTime(now)
+                .setMemo(MEMO)
+                .setBaseUnit(baseUnit)
+                .setUseOrgId(matnr.getUseOrgId())
+                .setUseOrgName(matnr.getUseOrgName())
+                .setErpClsId(matnr.getErpClsId())
+                .setPriceUnitId(baseUnit);
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WmsRedisLuaService.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WmsRedisLuaService.java
index 9b01001..01fed7a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WmsRedisLuaService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WmsRedisLuaService.java
@@ -10,6 +10,7 @@
 import java.math.BigDecimal;
 import java.time.Duration;
 import java.util.Arrays;
+import java.util.List;
 
 @Slf4j
 @Service
@@ -17,16 +18,25 @@
 public class WmsRedisLuaService {
 
     private static final DefaultRedisScript<Long> LOCATION_CLAIM_SCRIPT = createScript("wms-lua/location-claim.lua");
+    private static final DefaultRedisScript<Long> STATION_CLAIM_SCRIPT = createScript("wms-lua/station-claim.lua");
     private static final DefaultRedisScript<Long> INVENTORY_RESERVE_SCRIPT = createScript("wms-lua/inventory-reserve.lua");
 
     private final StringRedisTemplate redisTemplate;
 
-    public boolean claimLocation(String occupyKey, String taskKey, String mode, String occupyValue, String taskValue, Duration ttl) {
+    public boolean claimLocation(String occupyKey, String occupyValue, Duration ttl) {
         Long result = redisTemplate.execute(
                 LOCATION_CLAIM_SCRIPT,
-                Arrays.asList(occupyKey, taskKey),
-                mode,
+                List.of(occupyKey),
                 occupyValue,
+                String.valueOf(ttl.toMillis())
+        );
+        return result != null && result > 0;
+    }
+
+    public boolean claimStation(String stationKey, String taskValue, Duration ttl) {
+        Long result = redisTemplate.execute(
+                STATION_CLAIM_SCRIPT,
+                List.of(stationKey),
                 taskValue,
                 String.valueOf(ttl.toMillis())
         );
@@ -36,7 +46,7 @@
     public boolean reserveInventory(String inventoryKey, String orderKey, BigDecimal initialAvailable, BigDecimal reserveQuantity, Duration ttl) {
         Long result = redisTemplate.execute(
                 INVENTORY_RESERVE_SCRIPT,
-                Arrays.asList(inventoryKey, orderKey),
+                List.of(inventoryKey, orderKey),
                 initialAvailable.toPlainString(),
                 reserveQuantity.toPlainString(),
                 String.valueOf(ttl.toMillis())
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java
index d999743..1814a3e 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java
@@ -12,14 +12,20 @@
 import com.vincent.rsf.server.manager.enums.TaskType;
 import com.vincent.rsf.server.manager.enums.WaveRuleType;
 import com.vincent.rsf.server.manager.service.*;
+import com.vincent.rsf.server.manager.service.impl.WmsRedisLuaService;
 import com.vincent.rsf.server.manager.enums.LocStsType;
 import org.apache.commons.lang3.StringUtils;
 
 import java.math.BigDecimal;
+import java.time.Duration;
 import java.util.*;
 import java.util.stream.Collectors;
 
 public class LocManageUtil {
+
+    private static final int TARGET_LOC_QUERY_LIMIT = 10;
+    private static final String TARGET_LOC_LOCK_KEY_PREFIX = "wms:loc:claim:";
+    private static final Duration TARGET_LOC_LOCK_TTL = Duration.ofMinutes(1);
 
     /**
      * @param
@@ -39,7 +45,7 @@
         Long locType = containerType;
         //TODO 搴撲綅绛栫暐鍚庣画鎺掓湡
         LocService locService = SpringUtils.getBean(LocService.class);
-        Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>()
+        List<Loc> locList = locService.list(new LambdaQueryWrapper<Loc>()
                 .eq(!Objects.isNull(locType), Loc::getType, locType)
                 .eq(Loc::getAreaId, areaId)
                 .eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
@@ -47,45 +53,48 @@
                 .orderByAsc(Loc::getLev)
                 .orderByAsc(Loc::getCol)
                 .orderByAsc(Loc::getRow)
-                .last("LIMIT 1")
+                .last("LIMIT " + TARGET_LOC_QUERY_LIMIT)
         );
 
-        return !Objects.isNull(loc) ? loc.getCode() : null;
+        return claimTargetLoc(locList);
     }
 
     public static String getTargetLoc(Long areaId, Long containerType) {
         Long locType = containerType;
-//        if (!Objects.isNull(containerType)) {
-//            LocTypeService locService = SpringUtils.getBean(LocTypeService.class);
-//            if (containerType.equals(ContainerType.CONTAINER_TYPE_NORMAL.val)) {
-//                LocType low = locService.getOne(new LambdaQueryWrapper<LocType>()
-//                        .eq(LocType::getCode, "L"));
-//                if (Objects.isNull(low)) {
-//                    throw new CoolException("搴綅椤炲瀷涓嶅瓨鍦紒锛�");
-//                }
-//                locType = low.getId();
-//            } else {
-//                LocType low = locService.getOne(new LambdaQueryWrapper<LocType>()
-//                        .eq(LocType::getCode, "H"));
-//                if (Objects.isNull(low)) {
-//                    throw new CoolException("搴綅椤炲瀷涓嶅瓨鍦紒锛�");
-//                }
-//                locType = low.getId();
-//            }
-//        }
         //TODO 搴撲綅绛栫暐鍚庣画鎺掓湡
         LocService locService = SpringUtils.getBean(LocService.class);
-        Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>()
+        List<Loc> locList = locService.list(new LambdaQueryWrapper<Loc>()
                 .eq(!Objects.isNull(locType), Loc::getType, locType)
                 .eq(Loc::getAreaId, areaId)
                 .eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
                 .orderByAsc(Loc::getLev)
                 .orderByAsc(Loc::getCol)
                 .orderByAsc(Loc::getRow)
-                .last("LIMIT 1")
+                .last("LIMIT " + TARGET_LOC_QUERY_LIMIT)
         );
 
-        return !Objects.isNull(loc) ? loc.getCode() : null;
+        return claimTargetLoc(locList);
+    }
+
+    private static String claimTargetLoc(List<Loc> locList) {
+        if (Cools.isEmpty(locList)) {
+            return null;
+        }
+        WmsRedisLuaService wmsRedisLuaService = SpringUtils.getBean(WmsRedisLuaService.class);
+        for (Loc loc : locList) {
+            if (Objects.isNull(loc) || Cools.isEmpty(loc.getCode())) {
+                continue;
+            }
+            boolean claimed = wmsRedisLuaService.claimLocation(buildTargetLocLockKey(loc.getCode()), loc.getCode(), TARGET_LOC_LOCK_TTL);
+            if (claimed) {
+                return loc.getCode();
+            }
+        }
+        return null;
+    }
+
+    private static String buildTargetLocLockKey(String locCode) {
+        return TARGET_LOC_LOCK_KEY_PREFIX + locCode;
     }
 
     /**
diff --git a/rsf-server/src/main/resources/application-dev.yml b/rsf-server/src/main/resources/application-dev.yml
index b0df7ab..c0b446f 100644
--- a/rsf-server/src/main/resources/application-dev.yml
+++ b/rsf-server/src/main/resources/application-dev.yml
@@ -111,3 +111,11 @@
     flagAvailable: true
     #鍒ゆ柇鏄惁鏍¢獙鍚堟牸鍚庯紝鎵嶅厑璁告敹璐�
     flagReceiving: false
+
+pressure:
+  asn-order:
+    enabled: true
+    cron: "0/10 * * * * ?"
+    order-count-per-run: 2000
+    item-count-per-order: 10
+    item-qty: 10.0
diff --git a/rsf-server/src/main/resources/wms-lua/location-claim.lua b/rsf-server/src/main/resources/wms-lua/location-claim.lua
index b6e6896..27a983a 100644
--- a/rsf-server/src/main/resources/wms-lua/location-claim.lua
+++ b/rsf-server/src/main/resources/wms-lua/location-claim.lua
@@ -1,19 +1,8 @@
-local mode = ARGV[1]
-local occupyValue = ARGV[2]
-local taskValue = ARGV[3]
-local ttl = tonumber(ARGV[4])
+local occupyValue = ARGV[1]
+local ttl = tonumber(ARGV[2])
 
-if mode == 'PUTAWAY' then
-  if redis.call('exists', KEYS[1]) == 1 or redis.call('exists', KEYS[2]) == 1 then
-    return 0
-  end
-  redis.call('psetex', KEYS[1], ttl, occupyValue)
-  redis.call('psetex', KEYS[2], ttl, taskValue)
-  return 1
-end
-
-if redis.call('exists', KEYS[2]) == 1 then
+if redis.call('exists', KEYS[1]) == 1 then
   return 0
 end
-redis.call('psetex', KEYS[2], ttl, taskValue)
+redis.call('psetex', KEYS[1], ttl, occupyValue)
 return 1
diff --git a/rsf-server/src/main/resources/wms-lua/station-claim.lua b/rsf-server/src/main/resources/wms-lua/station-claim.lua
new file mode 100644
index 0000000..c13956e
--- /dev/null
+++ b/rsf-server/src/main/resources/wms-lua/station-claim.lua
@@ -0,0 +1,9 @@
+local taskValue = ARGV[1]
+local ttl = tonumber(ARGV[2])
+
+if redis.call('exists', KEYS[1]) == 1 then
+  return 0
+end
+
+redis.call('psetex', KEYS[1], ttl, taskValue)
+return 1

--
Gitblit v1.9.1