#
Junjie
9 天以前 dc3f9cc91759823ce59486f19b138be4b296a0f1
src/main/java/com/zy/common/service/CommonService.java
@@ -1,6 +1,7 @@
package com.zy.common.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.core.common.Cools;
import com.core.exception.CoolException;
import com.zy.asrs.domain.Result.CancelTaskBatchResult;
@@ -13,13 +14,16 @@
import com.zy.common.utils.NavigateUtils;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.service.WrkCommandRollbackService;
import com.zy.core.enums.*;
import com.zy.core.model.StationObjModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Random;
@@ -27,6 +31,13 @@
@Slf4j
@Service
public class CommonService {
    private static final long OUT_STATION_ROUTE_CACHE_SECONDS = 60 * 60 * 24 * 7;
    /**
     * 入库目标站的拓扑变化频率很低,允许使用 24h 缓存减少重复可达路径搜索。
     */
    private static final long IN_STATION_ROUTE_CACHE_SECONDS = 60 * 60 * 24;
    private static final long IN_STATION_ROUTE_SLOW_LOG_THRESHOLD_MS = 200L;
    @Autowired
    private WrkMastService wrkMastService;
@@ -44,41 +55,19 @@
    private RedisUtil redisUtil;
    @Autowired
    private BasOutStationAreaService basOutStationAreaService;
    @Autowired
    private WrkCommandRollbackService wrkCommandRollbackService;
    @Autowired
    private WrkAnalysisService wrkAnalysisService;
    /**
     * 生成工作号
     * @return workNo(工作号)
     */
    public synchronized int getWorkNo(Integer wrkMk) {
        WrkLastno wrkLastno = wrkLastnoService.getById(wrkMk);
        if (Cools.isEmpty(wrkLastno)) {
            throw new CoolException("数据异常,请联系管理员");
        }
        int workNo = wrkLastno.getWrkNo();
        int sNo = wrkLastno.getSNo();
        int eNo = wrkLastno.getENo();
        workNo = workNo>=eNo ? sNo : workNo+1;
        while (true) {
            WrkMast wrkMast = wrkMastService.selectByWorkNo(workNo);
            if (null != wrkMast) {
                workNo = workNo>=eNo ? sNo : workNo+1;
            } else {
                break;
            }
        }
        // 修改序号记录
        if (workNo > 0){
            wrkLastno.setWrkNo(workNo);
            wrkLastnoService.updateById(wrkLastno);
        }
        // 检验
        if (workNo == 0) {
    public int getWorkNo(Integer wrkMk) {
        int workNo = wrkLastnoService.allocateNextWorkNo(wrkMk);
        if (workNo <= 0) {
            throw new CoolException("生成工作号失败,请联系管理员");
        } else {
            if (wrkMastService.selectByWorkNo(workNo)!=null) {
                throw new CoolException("生成工作号" + workNo + "在工作档中已存在");
            }
        }
        return workNo;
    }
@@ -100,19 +89,33 @@
            throw new CoolException("任务不存在");
        }
        Long currentWrkSts = wrkMast.getWrkSts();
        Long targetWrkSts;
        if (wrkMast.getIoType() == WrkIoType.IN.id) {
            wrkMast.setWrkSts(WrkStsType.COMPLETE_INBOUND.sts);
            targetWrkSts = WrkStsType.COMPLETE_INBOUND.sts;
        }else if (wrkMast.getIoType() == WrkIoType.OUT.id) {
            wrkMast.setWrkSts(WrkStsType.COMPLETE_OUTBOUND.sts);
            targetWrkSts = WrkStsType.COMPLETE_OUTBOUND.sts;
        } else if (wrkMast.getIoType() == WrkIoType.LOC_MOVE.id) {
            wrkMast.setWrkSts(WrkStsType.COMPLETE_LOC_MOVE.sts);
            targetWrkSts = WrkStsType.COMPLETE_LOC_MOVE.sts;
        } else if (wrkMast.getIoType() == WrkIoType.CRN_MOVE.id) {
            targetWrkSts = WrkStsType.COMPLETE_CRN_MOVE.sts;
        } else {
            throw new CoolException("任务类型不支持完成");
        }
        wrkMast.setModiTime(new Date());
        wrkMastService.updateById(wrkMast);
        boolean updated = wrkMastService.update(null, new UpdateWrapper<WrkMast>()
                .set("wrk_sts", targetWrkSts)
                .set("modi_time", new Date())
                .set("memo", "手动完成")
                .eq("wrk_no", wrkMast.getWrkNo())
                .eq("wrk_sts", currentWrkSts));
        if (!updated) {
            throw new CoolException("任务状态已变化,完成失败");
        }
        return true;
    }
    @Transactional
    public boolean cancelTask(CancelTaskParam param) {
        WrkMast wrkMast = null;
        Integer wrkNo = param.getWrkNo();
@@ -130,23 +133,163 @@
            throw new CoolException("任务不存在");
        }
        boolean cancelSuccess = false;
        if (wrkMast.getIoType().equals(WrkIoType.IN.id) && !wrkMast.getWrkSts().equals(WrkStsType.NEW_INBOUND.sts)) {
            cancelSuccess = true;
        } else if (wrkMast.getIoType().equals(WrkIoType.OUT.id) && !wrkMast.getWrkSts().equals(WrkStsType.NEW_OUTBOUND.sts)) {
            cancelSuccess = true;
        } else if (wrkMast.getIoType().equals(WrkIoType.LOC_MOVE.id) && !wrkMast.getWrkSts().equals(WrkStsType.NEW_LOC_MOVE.sts)) {
            cancelSuccess = true;
        Long expectedWrkSts;
        if (wrkMast.getIoType().equals(WrkIoType.IN.id)) {
            expectedWrkSts = WrkStsType.NEW_INBOUND.sts;
        } else if (wrkMast.getIoType().equals(WrkIoType.OUT.id)) {
            expectedWrkSts = WrkStsType.NEW_OUTBOUND.sts;
        } else if (wrkMast.getIoType().equals(WrkIoType.LOC_MOVE.id)) {
            expectedWrkSts = WrkStsType.NEW_LOC_MOVE.sts;
        } else if (wrkMast.getIoType().equals(WrkIoType.CRN_MOVE.id)) {
            expectedWrkSts = WrkStsType.NEW_CRN_MOVE.sts;
        } else {
            throw new CoolException("任务类型不支持取消");
        }
        if (cancelSuccess) {
        if (!expectedWrkSts.equals(wrkMast.getWrkSts())) {
            throw new CoolException("任务已执行,取消失败");
        }
        wrkMast.setMk("taskCancel");
        boolean updated = wrkMastService.update(null, new UpdateWrapper<WrkMast>()
                .eq("wrk_no", wrkMast.getWrkNo())
                .eq("wrk_sts", expectedWrkSts)
                .set("mk", "taskCancel")
                .set("memo", "手动取消")
                .set("modi_time", new Date()));
        if (!updated) {
            throw new CoolException("任务状态已变化,取消失败");
        }
        return true;
    }
    @Transactional
    public boolean forceCancelTask(CancelTaskParam param) {
        WrkMast wrkMast = null;
        Integer wrkNo = param.getWrkNo();
        String taskNo = param.getTaskNo();//wms任务号
        if (wrkNo == null) {
            if (!Cools.isEmpty(taskNo)) {
                wrkMast = wrkMastService.getOne(new QueryWrapper<WrkMast>().eq("wms_wrk_no", taskNo));
            }
        } else {
            wrkMast = wrkMastService.selectByWorkNo(wrkNo);
        }
        if (wrkMast == null) {
            throw new CoolException("任务不存在");
        }
        boolean updated = wrkMastService.update(null, new UpdateWrapper<WrkMast>()
                .eq("wrk_no", wrkMast.getWrkNo())
                .set("mk", "taskForceCancel")
                .set("memo", "手动完成")
                .set("modi_time", new Date()));
        if (!updated) {
            throw new CoolException("任务强制取消失败");
        }
        return true;
    }
    public boolean updateTaskPriorityAndBatchSeq(UpdateTaskPriorityAndBatchSeqParam param) {
        if (param == null) {
            throw new CoolException("参数不能为空");
        }
        if (Cools.isEmpty(param.getTaskNo())) {
            throw new CoolException("WMS任务号不能为空");
        }
        if (param.getTaskPri() == null && param.getBatchSeq() == null) {
            throw new CoolException("任务优先级和批次序号不能同时为空");
        }
        WrkMast wrkMast = wrkMastService.getOne(new QueryWrapper<WrkMast>().eq("wms_wrk_no", param.getTaskNo()));
        if (wrkMast == null) {
            throw new CoolException("任务不存在");
        }
        if (param.getTaskPri() != null) {
            wrkMast.setIoPri(param.getTaskPri().doubleValue());
        }
        if (param.getBatchSeq() != null) {
            wrkMast.setBatchSeq(param.getBatchSeq());
        }
        wrkMast.setModiTime(new Date());
        wrkMastService.updateById(wrkMast);
        return true;
    }
    public CancelTaskBatchResult cancelOutTaskBatchInfo(CancelTaskBatchParam param) {
        if (param == null) {
            throw new CoolException("参数不能为空");
        }
        List<CancelTaskParam> taskList = param.getTaskList();
        if (taskList == null || taskList.isEmpty()) {
            throw new CoolException("任务参数列表为空");
        }
        List<String> successList = new ArrayList<>();
        List<String> failList = new ArrayList<>();
        for (CancelTaskParam taskParam : taskList) {
            if (taskParam == null) {
                throw new CoolException("任务参数不能为空");
            }
            boolean cancelStatus = false;
            try {
                cancelStatus = cancelSingleOutTaskBatchInfo(taskParam);
            } catch (Exception e) {
            }
            if (cancelStatus) {
                successList.add(resolveTaskIdentifier(taskParam));
            } else {
                failList.add(resolveTaskIdentifier(taskParam));
            }
        }
        CancelTaskBatchResult result = new CancelTaskBatchResult();
        result.setSuccessList(successList);
        result.setFailList(failList);
        return result;
    }
    private boolean cancelSingleOutTaskBatchInfo(CancelTaskParam param) {
        WrkMast wrkMast = null;
        Integer wrkNo = param.getWrkNo();
        String taskNo = param.getTaskNo();
        if (wrkNo == null) {
            if (Cools.isEmpty(taskNo)) {
                throw new CoolException("WMS任务号不能为空");
            }
            wrkMast = wrkMastService.getOne(new QueryWrapper<WrkMast>().eq("wms_wrk_no", taskNo));
        } else {
            wrkMast = wrkMastService.selectByWorkNo(wrkNo);
        }
        if (wrkMast == null) {
            throw new CoolException("任务不存在");
        }
        if (!wrkMast.getIoType().equals(WrkIoType.OUT.id)) {
            throw new CoolException("仅支持出库任务");
        }
        boolean updated = wrkMast.getWrkNo() != null && wrkMastService.update(null, new UpdateWrapper<WrkMast>()
                .eq("wrk_no", wrkMast.getWrkNo())
                .set("batch", null)
                .set("batch_seq", null)
                .set("modi_time", new Date()));
        if (!updated) {
            throw new CoolException("取消出库任务批次和批次序号失败");
        }
        return true;
    }
    private String resolveTaskIdentifier(CancelTaskParam param) {
        if (!Cools.isEmpty(param.getTaskNo())) {
            return param.getTaskNo();
        }
        if (param.getWrkNo() != null) {
            return String.valueOf(param.getWrkNo());
        }
        return "";
    }
    public CancelTaskBatchResult cancelTaskBatch(CancelTaskBatchParam param) {
@@ -184,6 +327,10 @@
        result.setSuccessList(successList);
        result.setFailList(failList);
        return result;
    }
    public boolean manualRollbackTask(ManualRollbackTaskParam param) {
        return wrkCommandRollbackService.manualRollbackTask(param);
    }
    //移库任务
@@ -255,6 +402,7 @@
            News.error("移库任务 --- 保存工作档失败!");
            throw new CoolException("保存工作档失败");
        }
        wrkAnalysisService.initForTask(wrkMast);
        sourceLocMast.setLocSts("R");
        sourceLocMast.setModiTime(new Date());
@@ -323,6 +471,7 @@
            News.error("入库任务 --- 保存工作档失败!");
            throw new CoolException("保存工作档失败");
        }
        wrkAnalysisService.initForTask(wrkMast);
        locMast.setLocSts("S");
        locMast.setModiTime(new Date());
@@ -440,11 +589,54 @@
            News.error("出库任务 --- 保存工作档失败!");
            throw new CoolException("保存工作档失败");
        }
        wrkAnalysisService.initForTask(wrkMast);
        locMast.setLocSts("R");
        locMast.setModiTime(new Date());
        locMastService.updateById(locMast);
        return true;
    }
    public boolean createOutTaskBatch(CreateOutTaskBatchParam param) {
        if (param == null) {
            throw new CoolException("参数不能为空");
        }
        List<CreateOutTaskParam> taskList = param.getTaskList();
        if (taskList == null || taskList.isEmpty()) {
            throw new CoolException("任务列表不能为空");
        }
        List<CreateOutTaskParam> sortedTaskList = new ArrayList<>(taskList);
        sortedTaskList.sort(Comparator.nullsLast(Comparator
                .comparing(this::getSortableBatch, Comparator.nullsLast(String::compareTo))
                .thenComparing(this::getSortableBatchSeq, Comparator.nullsLast(Integer::compareTo))));
        for (CreateOutTaskParam createOutTaskParam : sortedTaskList) {
            if (createOutTaskParam == null) {
                throw new CoolException("任务参数不能为空");
            }
            createOutTask(createOutTaskParam);
        }
        return true;
    }
    private String getSortableBatch(CreateOutTaskParam param) {
        if (param == null) {
            return null;
        }
        String batch = param.getBatch();
        if (batch == null || batch.trim().isEmpty()) {
            return null;
        }
        return batch;
    }
    private Integer getSortableBatchSeq(CreateOutTaskParam param) {
        if (param == null) {
            return null;
        }
        return param.getBatchSeq();
    }
    public FindCrnNoResult findCrnNoByLocNo(String locNo) {
@@ -477,35 +669,60 @@
    }
    public Integer findInStationId(FindCrnNoResult findCrnNoResult, Integer sourceStationId) {
        return resolveInStationId(findCrnNoResult, sourceStationId).getTargetStationId();
    }
    public InStationResolveResult resolveInStationId(FindCrnNoResult findCrnNoResult, Integer sourceStationId) {
        long resolveStartNs = System.nanoTime();
        if (findCrnNoResult == null || findCrnNoResult.getCrnType() == null
                || findCrnNoResult.getCrnNo() == null || sourceStationId == null) {
            return InStationResolveResult.empty(false, nanosToMillis(resolveStartNs));
        }
        List<StationObjModel> stationList = new ArrayList<>();
        Integer crnNo = findCrnNoResult.getCrnNo();
        if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) {
            BasCrnp basCrnp = basCrnpService.getOne(new QueryWrapper<BasCrnp>().eq("crn_no", crnNo));
            if(basCrnp == null) {
                return null;
                return InStationResolveResult.empty(false, nanosToMillis(resolveStartNs));
            }
            stationList = basCrnp.getInStationList$();
        } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) {
            BasDualCrnp basDualCrnp = basDualCrnpService.getOne(new QueryWrapper<BasDualCrnp>().eq("crn_no", crnNo));
            if(basDualCrnp == null) {
                return null;
                return InStationResolveResult.empty(false, nanosToMillis(resolveStartNs));
            }
            stationList = basDualCrnp.getInStationList$();
        }
        Integer cachedTargetStationId = resolveCachedInStationId(findCrnNoResult, sourceStationId, stationList);
        if (cachedTargetStationId != null) {
            long totalCostMs = nanosToMillis(resolveStartNs);
            if (totalCostMs >= IN_STATION_ROUTE_SLOW_LOG_THRESHOLD_MS) {
                log.info("入库目标站缓存命中耗时较长,sourceStationId={},crnNo={},targetStationId={},totalCost={}ms",
                        sourceStationId, crnNo, cachedTargetStationId, totalCostMs);
            }
            return InStationResolveResult.cacheHit(cachedTargetStationId, totalCostMs);
        }
        long searchStartNs = System.nanoTime();
        Integer targetStationId = null;
        for (StationObjModel stationObjModel : stationList) {
            try {
                List<NavigateNode> navigateNodes = navigateUtils.calcByStationId(sourceStationId, stationObjModel.getStationId());
                List<NavigateNode> navigateNodes = navigateUtils.calcReachablePathByStationId(sourceStationId, stationObjModel.getStationId());
                if(!navigateNodes.isEmpty()) {
                    targetStationId = stationObjModel.getStationId();
                    cacheInStationId(findCrnNoResult, sourceStationId, targetStationId);
                    break;
                }
            } catch (Exception e) {
//                e.printStackTrace();
            }
        }
        return targetStationId;
        long searchCostMs = nanosToMillis(searchStartNs);
        long totalCostMs = nanosToMillis(resolveStartNs);
        log.info("入库目标站缓存未命中,sourceStationId={},crnNo={},targetStationId={},searchCost={}ms,totalCost={}ms",
                sourceStationId, crnNo, targetStationId, searchCostMs, totalCostMs);
        return InStationResolveResult.searchResult(targetStationId, totalCostMs, searchCostMs);
    }
    public Integer findOutStationId(FindCrnNoResult findCrnNoResult, Integer targetStationId) {
@@ -525,12 +742,18 @@
            stationList = basDualCrnp.getOutStationList$();
        }
        Integer cachedSourceStationId = resolveCachedOutStationId(findCrnNoResult, targetStationId, stationList);
        if (cachedSourceStationId != null) {
            return cachedSourceStationId;
        }
        Integer finalSourceStationId = null;
        for (StationObjModel stationObjModel : stationList) {
            try {
                List<NavigateNode> navigateNodes = navigateUtils.calcByStationId(stationObjModel.getStationId(), targetStationId);
                List<NavigateNode> navigateNodes = navigateUtils.calcReachablePathByStationId(stationObjModel.getStationId(), targetStationId);
                if(!navigateNodes.isEmpty()) {
                    finalSourceStationId = stationObjModel.getStationId();
                    cacheOutStationId(findCrnNoResult, targetStationId, finalSourceStationId);
                    break;
                }
            } catch (Exception e) {
@@ -540,4 +763,175 @@
        return finalSourceStationId;
    }
    /**
     * 入库路径搜索同样代价较高,只缓存已确认可达的目标站点结果,并通过 TTL 限制陈旧风险。
     */
    private Integer resolveCachedInStationId(FindCrnNoResult findCrnNoResult,
                                             Integer sourceStationId,
                                             List<StationObjModel> stationList) {
        if (findCrnNoResult == null || findCrnNoResult.getCrnType() == null
                || findCrnNoResult.getCrnNo() == null || sourceStationId == null
                || stationList == null || stationList.isEmpty()) {
            return null;
        }
        Object cacheValue = redisUtil.get(buildInStationRouteCacheKey(findCrnNoResult, sourceStationId));
        if (cacheValue == null) {
            return null;
        }
        Integer cachedStationId = parseInteger(cacheValue);
        if (cachedStationId == null) {
            return null;
        }
        for (StationObjModel stationObjModel : stationList) {
            if (stationObjModel != null && cachedStationId.equals(stationObjModel.getStationId())) {
                return cachedStationId;
            }
        }
        return null;
    }
    private void cacheInStationId(FindCrnNoResult findCrnNoResult,
                                  Integer sourceStationId,
                                  Integer targetStationId) {
        if (findCrnNoResult == null || findCrnNoResult.getCrnType() == null
                || findCrnNoResult.getCrnNo() == null || sourceStationId == null
                || targetStationId == null) {
            return;
        }
        redisUtil.set(buildInStationRouteCacheKey(findCrnNoResult, sourceStationId),
                targetStationId,
                IN_STATION_ROUTE_CACHE_SECONDS);
    }
    private String buildInStationRouteCacheKey(FindCrnNoResult findCrnNoResult, Integer sourceStationId) {
        return RedisKeyType.IN_STATION_ROUTE_CACHE.key
                + findCrnNoResult.getCrnType().name()
                + "_"
                + findCrnNoResult.getCrnNo()
                + "_"
                + sourceStationId;
    }
    /**
     * 出库路径搜索代价较高,只缓存已确认可达的站点结果,并通过 TTL 控制陈旧风险。
     */
    private Integer resolveCachedOutStationId(FindCrnNoResult findCrnNoResult,
                                              Integer targetStationId,
                                              List<StationObjModel> stationList) {
        if (findCrnNoResult == null || findCrnNoResult.getCrnType() == null
                || findCrnNoResult.getCrnNo() == null || targetStationId == null
                || stationList == null || stationList.isEmpty()) {
            return null;
        }
        Object cacheValue = redisUtil.get(buildOutStationRouteCacheKey(findCrnNoResult, targetStationId));
        if (cacheValue == null) {
            return null;
        }
        Integer cachedStationId = parseInteger(cacheValue);
        if (cachedStationId == null) {
            return null;
        }
        for (StationObjModel stationObjModel : stationList) {
            if (stationObjModel != null && cachedStationId.equals(stationObjModel.getStationId())) {
                return cachedStationId;
            }
        }
        return null;
    }
    private void cacheOutStationId(FindCrnNoResult findCrnNoResult,
                                   Integer targetStationId,
                                   Integer sourceStationId) {
        if (findCrnNoResult == null || findCrnNoResult.getCrnType() == null
                || findCrnNoResult.getCrnNo() == null || targetStationId == null
                || sourceStationId == null) {
            return;
        }
        redisUtil.set(buildOutStationRouteCacheKey(findCrnNoResult, targetStationId),
                sourceStationId,
                OUT_STATION_ROUTE_CACHE_SECONDS);
    }
    private String buildOutStationRouteCacheKey(FindCrnNoResult findCrnNoResult, Integer targetStationId) {
        return RedisKeyType.OUT_STATION_ROUTE_CACHE.key
                + findCrnNoResult.getCrnType().name()
                + "_"
                + findCrnNoResult.getCrnNo()
                + "_"
                + targetStationId;
    }
    private Integer parseInteger(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Integer) {
            return (Integer) value;
        }
        if (value instanceof Number) {
            return ((Number) value).intValue();
        }
        try {
            return Integer.parseInt(String.valueOf(value));
        } catch (Exception ignore) {
            return null;
        }
    }
    private long nanosToMillis(long startNs) {
        long elapsedNs = System.nanoTime() - startNs;
        return elapsedNs <= 0L ? 0L : elapsedNs / 1_000_000L;
    }
    public static class InStationResolveResult {
        private final Integer targetStationId;
        private final boolean cacheHit;
        private final long totalCostMs;
        private final long searchCostMs;
        private InStationResolveResult(Integer targetStationId,
                                       boolean cacheHit,
                                       long totalCostMs,
                                       long searchCostMs) {
            this.targetStationId = targetStationId;
            this.cacheHit = cacheHit;
            this.totalCostMs = totalCostMs;
            this.searchCostMs = searchCostMs;
        }
        public static InStationResolveResult cacheHit(Integer targetStationId, long totalCostMs) {
            return new InStationResolveResult(targetStationId, true, totalCostMs, 0L);
        }
        public static InStationResolveResult searchResult(Integer targetStationId, long totalCostMs, long searchCostMs) {
            return new InStationResolveResult(targetStationId, false, totalCostMs, searchCostMs);
        }
        public static InStationResolveResult empty(boolean cacheHit, long totalCostMs) {
            return new InStationResolveResult(null, cacheHit, totalCostMs, 0L);
        }
        public Integer getTargetStationId() {
            return targetStationId;
        }
        public boolean isCacheHit() {
            return cacheHit;
        }
        public long getTotalCostMs() {
            return totalCostMs;
        }
        public long getSearchCostMs() {
            return searchCostMs;
        }
    }
}