zhou zhou
4 天以前 7a215e51f9f4066f2024d8476e5b5db2c06358a1
#lua锁示例
2个文件已添加
4个文件已修改
327 ■■■■ 已修改文件
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/AsnOrderPressureSchedules.java 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WmsRedisLuaService.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/application-dev.yml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/wms-lua/location-claim.lua 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/wms-lua/station-claim.lua 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/AsnOrderPressureSchedules.java
New file
@@ -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);
    }
}
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())
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;
    }
    /**
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
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
rsf-server/src/main/resources/wms-lua/station-claim.lua
New file
@@ -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