自动化立体仓库 - WMS系统
*
lsh
3 天以前 bbf11a79fce0131aa95905c09b00f25e7696d20b
*
10个文件已修改
1个文件已添加
1100 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/ApiLogController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/OrderToSortLineScheduler.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/handler/ArmRulesHandler.java 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/handler/OrderToLineHandler.java 73 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/utils/MultiLockerOptimizerUtils.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/utils/OptimizedLockerPacking3Utils.java 737 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/utils/ToSortLineUtils.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/utils/param/ItemUtilParam.java 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/config/ControllerResAdvice.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/apiLog/apiLog.js 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/apiLog/apiLog.html 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/ApiLogController.java
@@ -56,7 +56,11 @@
                wrapper.ge(entry.getKey(), DateUtils.convert(dates[0]));
                wrapper.le(entry.getKey(), DateUtils.convert(dates[1]));
            } else {
                wrapper.like(entry.getKey(), val);
                if (entry.getKey().equals("url")){
                    wrapper.like(entry.getKey(), val).or().like("client_ip", val);
                } else {
                    wrapper.like(entry.getKey(), val);
                }
            }
        }
    }
src/main/java/com/zy/asrs/task/OrderToSortLineScheduler.java
@@ -5,13 +5,10 @@
import com.zy.asrs.entity.*;
import com.zy.asrs.entity.param.OrderToLine;
import com.zy.asrs.service.*;
import com.zy.asrs.service.impl.OrderDetlServiceImpl;
import com.zy.asrs.task.core.ReturnT;
import com.zy.asrs.task.handler.OrderToLineHandler;
import com.zy.asrs.utils.GroupedLockerOptimizerUtils;
import com.zy.asrs.utils.OptimizedLockerPackingUtils;
import com.zy.asrs.utils.OrderInAndOutUtil;
import com.zy.asrs.utils.ToSortLineUtils;
import com.zy.asrs.utils.param.ItemUtilParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
@@ -48,12 +45,10 @@
        for (String orderNo : orderNos) {
            try{
                List<OrderDetlPakin> orderDetlPakinList = orderDetlPakinService.selectList(new EntityWrapper<OrderDetlPakin>().eq("order_no",orderNo));
//                List<GroupedLockerOptimizerUtils.Item> items = new ArrayList<>();
                if (orderDetlPakinList.size()<1){
                    continue;
                }
                List<OptimizedLockerPackingUtils.Item> items = new ArrayList<>();
                List<ItemUtilParam.Item> items = new ArrayList<>();
                for (OrderDetlPakin orderDetl:orderDetlPakinList){
                    Integer number =  basArmRulesService.getNumber(orderDetl.getWeight(),orderDetl.getVolume(),orderDetl.getManLength(),orderDetl.getWidth(),orderDetl.getHeight());
                    if (number == null) {
@@ -70,20 +65,18 @@
                    String name = ToSortLineUtils.MergerParameter(orderDetl.getMatnr(),orderDetl.getStandby1(),orderDetl.getStandby2());
                    int maxCapacity = number;
                    int stock = orderDetl.getAnfme().intValue();
//                    items.add(new GroupedLockerOptimizerUtils.Item(name, maxCapacity, stock));
                    items.add(new OptimizedLockerPackingUtils.Item(name, maxCapacity, stock));
                    items.add(new ItemUtilParam.Item(name, maxCapacity, stock));
                }
                OrderToLine orderToLine = new OrderToLine();
                orderToLine.setOrderNo(orderNo);  //单据编号
                orderToLine.setCreateTime(System.currentTimeMillis());  //创建时间
//                OrderToLine orderToLineR = ToSortLineUtils.GetOrderToLineGro(items, orderToLine);
                OrderToLine orderToLineR = ToSortLineUtils.GetOrderToLineOpt(items, orderToLine);
                OrderToLine orderToLineR = ToSortLineUtils.GetOrderToLine(items, orderToLine,"Opt3");
                try{
                    ReturnT<String> returnT = orderToLineHandler.start(orderToLineR);
                    if (!returnT.isSuccess()) {
                        log.error("下发单据失败===>"+ JSON.toJSON(orderToLineR));
//                        log.error("下发单据失败===>"+ JSON.toJSON(orderToLineR));
                    } else {
                        try{
                            for (OrderToLine.MatList matList:orderToLineR.getMatList()){
src/main/java/com/zy/asrs/task/handler/ArmRulesHandler.java
@@ -2,13 +2,12 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.Cools;
import com.core.common.DateUtils;
import com.core.common.SpringUtils;
import com.core.exception.CoolException;
import com.zy.asrs.entity.BasArmRules;
import com.zy.asrs.entity.DocType;
import com.zy.asrs.entity.Order;
import com.zy.asrs.entity.OrderDetl;
import com.zy.asrs.entity.*;
import com.zy.asrs.entity.param.ArmPrecomputeParam;
import com.zy.asrs.service.ApiLogService;
import com.zy.asrs.service.BasArmRulesService;
@@ -79,20 +78,40 @@
                    throw new CoolException("获取码垛数量失败");
                }
            } catch (Exception e) {
                log.error("fail", e);
                return FAIL.setMsg(e.getMessage());
                try{
                    log.error("fail==>获取码垛数量:"+e.getLocalizedMessage());
                } catch (Exception e1){
                }
//                return FAIL.setMsg(e.getLocalizedMessage());
                return FAIL;
//                log.error("fail", e);
//                return FAIL.setMsg(e.getMessage());
            } finally {
                try {
                    // 保存接口日志
                    apiLogService.save(
                            "获取码垛数量",
                            URL + QuantityOfPalletizing,
                            null,
                            "127.0.0.1",
                            JSON.toJSONString(armPrecomputeParam),
                            response,
                            success
                    );
                    if (success){
                        // 保存接口日志
                        apiLogService.save(
                                "获取码垛数量",
                                URL +"/"+ QuantityOfPalletizing,
                                null,
                                "127.0.0.1",
                                JSON.toJSONString(armPrecomputeParam),
                                response,
                                success
                        );
                    } else {
                        beforeBodyWriteCallApiLogSave(
                                "获取码垛数量",
                                URL +"/"+ QuantityOfPalletizing,
                                null,
                                "127.0.0.1",
                                JSON.toJSONString(armPrecomputeParam),
                                response,
                                success
                        );
                    }
                } catch (Exception e) { log.error("", e); }
            }
        }catch (Exception e){
@@ -100,5 +119,30 @@
        }
        return SUCCESS;
    }
    public void beforeBodyWriteCallApiLogSave(String name, String url, String appkey, String ip, String request, String response, boolean success) {
        ApiLog apiLog = apiLogService.selectOne(new EntityWrapper<ApiLog>()
                .eq("namespace", name)
                .eq("request", request)
                .eq("response", response)
                .eq("result", success? 1:0)
                .orderBy("create_time", false)
        );
        if (!Cools.isEmpty(apiLog)){
            long parseLong = Long.parseLong(apiLog.getTimestamp());
            if (new Date().getTime()-parseLong<5*1000*60){
                return;
            }
        }
        // 保存接口日志
        apiLogService.save(
                name,
                url,
                appkey,
                ip,
                request,
                response,
                success
        );
    }
}
src/main/java/com/zy/asrs/task/handler/OrderToLineHandler.java
@@ -2,7 +2,11 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.Cools;
import com.core.common.SpringUtils;
import com.core.exception.CoolException;
import com.zy.asrs.entity.ApiLog;
import com.zy.asrs.entity.param.ArmPrecomputeParam;
import com.zy.asrs.entity.param.OrderToLine;
import com.zy.asrs.service.ApiLogService;
@@ -12,12 +16,14 @@
import com.zy.asrs.task.AbstractHandler;
import com.zy.asrs.task.core.ReturnT;
import com.zy.common.utils.HttpHandler;
import com.zy.common.utils.IpTools;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@@ -61,20 +67,37 @@
                    throw new CoolException("下发单据失败");
                }
            } catch (Exception e) {
                log.error("fail", e);
                return FAIL.setMsg(e.getMessage());
                try{
                    log.error("fail==>下发单据至分拣线:"+e.getLocalizedMessage());
                } catch (Exception e1){
                }
//                return FAIL.setMsg(e.getLocalizedMessage());
                return FAIL;
            } finally {
                try {
                    // 保存接口日志
                    apiLogService.save(
                            "下发单据至分拣线",
                            URL + Path,
                            null,
                            "127.0.0.1",
                            JSON.toJSONString(orderToline),
                            response,
                            success
                    );
                    if (success){
                        // 保存接口日志
                        apiLogService.save(
                                "下发单据至分拣线",
                                URL +"/"+ Path,
                                null,
                                "127.0.0.1",
                                JSON.toJSONString(orderToline),
                                response,
                                success
                        );
                    } else {
                        beforeBodyWriteCallApiLogSave(
                                "下发单据至分拣线",
                                URL +"/"+ Path,
                                null,
                                "127.0.0.1",
                                JSON.toJSONString(orderToline),
                                response,
                                success
                        );
                    }
                } catch (Exception e) { log.error("", e); }
            }
        }catch (Exception e){
@@ -82,4 +105,30 @@
        }
        return SUCCESS;
    }
    public void beforeBodyWriteCallApiLogSave(String name, String url, String appkey, String ip, String request, String response, boolean success) {
        ApiLog apiLog = apiLogService.selectOne(new EntityWrapper<ApiLog>()
                .eq("namespace", name)
                .eq("response", response)
                .eq("result", success? 1:0)
                .orderBy("create_time", false)
        );
        if (!Cools.isEmpty(apiLog)){
            long parseLong = Long.parseLong(apiLog.getTimestamp());
            if (new Date().getTime()-parseLong<5*1000*60){
                return;
            }
        }
        // 保存接口日志
        apiLogService.save(
                name,
                url,
                appkey,
                ip,
                request,
                response,
                success
        );
    }
}
src/main/java/com/zy/asrs/utils/MultiLockerOptimizerUtils.java
@@ -1,4 +1,7 @@
package com.zy.asrs.utils;
import com.core.common.SnowflakeIdWorker;
import com.core.common.SpringUtils;
import java.util.*;
public class MultiLockerOptimizerUtils {
@@ -33,12 +36,19 @@
    static class Locker {
        int id;
        double remainingSpace;
        long bindingTags;
        Map<String, Integer> contents;
        Set<String> itemTypes;
        public Locker(int id) {
            this.id = id;
            this.remainingSpace = 1.0;
            this.contents = new HashMap<>();
            this.itemTypes = new HashSet<>();
            SnowflakeIdWorker snowflakeIdWorker = SpringUtils.getBean(SnowflakeIdWorker.class);
//            this.bindingTags = System.currentTimeMillis();
            this.bindingTags = snowflakeIdWorker.nextId();
        }
        // 尝试放入物品
src/main/java/com/zy/asrs/utils/OptimizedLockerPacking3Utils.java
New file
@@ -0,0 +1,737 @@
package com.zy.asrs.utils;
import com.core.common.SnowflakeIdWorker;
import com.core.common.SpringUtils;
import java.util.*;
public class OptimizedLockerPacking3Utils {
    public static class Item {
        String name;
        double unitSpace;
        int maxCapacity;
        int stock;
        int allocated; // 改为已分配数量,避免直接修改remaining导致的混乱
        public Item(String name, int maxCapacity, int stock) {
            this.name = name;
            this.maxCapacity = maxCapacity;
            this.unitSpace = 1.0 / maxCapacity;
            this.stock = stock;
            this.allocated = 0;
        }
        public int getRemaining() {
            return stock - allocated;
        }
        public boolean allocate(int quantity) {
            if (allocated + quantity <= stock) {
                allocated += quantity;
                return true;
            }
            return false;
        }
        @Override
        public String toString() {
            return name + "(单放" + maxCapacity + "个, 库存" + stock + "个)";
        }
    }
    static class Locker {
        int id;
        double remainingSpace;
        long bindingTags;
        Map<String, Integer> contents;
        Set<String> itemTypes;
        public Locker(int id) {
            this.id = id;
            this.remainingSpace = 1.0;
            this.contents = new HashMap<>();
            this.itemTypes = new HashSet<>();
            SnowflakeIdWorker snowflakeIdWorker = SpringUtils.getBean(SnowflakeIdWorker.class);
            this.bindingTags = snowflakeIdWorker.nextId();
        }
        public boolean canAdd(Item item, int quantity) {
            return remainingSpace >= quantity * item.unitSpace;
        }
        public boolean addItem(Item item, int quantity) {
            // 验证分配数量
            if (!item.allocate(quantity)) {
                return false;
            }
            double spaceUsed = quantity * item.unitSpace;
            if (spaceUsed > remainingSpace + 1e-6) {
                // 回滚分配
                item.allocated -= quantity;
                return false;
            }
            remainingSpace -= spaceUsed;
            contents.put(item.name, contents.getOrDefault(item.name, 0) + quantity);
            itemTypes.add(item.name);
            return true;
        }
        public boolean containsItemType(String itemName) {
            return itemTypes.contains(itemName);
        }
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("储物柜").append(id).append(": ");
            sb.append(String.format("剩余空间%.4f", remainingSpace)).append("\n");
            for (Map.Entry<String, Integer> entry : contents.entrySet()) {
                sb.append("  ").append(entry.getKey()).append(": ").append(entry.getValue()).append("个\n");
            }
            return sb.toString();
        }
    }
    static class PackingSolution {
        List<Locker> lockers;
        int totalLockersUsed;
        double overallUtilization;
        int itemTypeChanges;
        int pureLockers;
        public PackingSolution() {
            this.lockers = new ArrayList<>();
        }
        public void addLocker(Locker locker) {
            lockers.add(locker);
            totalLockersUsed = lockers.size();
        }
        public void calculateMetrics() {
            double totalUsedSpace = 0;
            itemTypeChanges = 0;
            pureLockers = 0;
            String lastItemType = null;
            for (Locker locker : lockers) {
                totalUsedSpace += (1.0 - locker.remainingSpace);
                if (locker.itemTypes.size() == 1) {
                    pureLockers++;
                }
                for (String itemType : locker.itemTypes) {
                    if (lastItemType != null && !lastItemType.equals(itemType)) {
                        itemTypeChanges++;
                    }
                    lastItemType = itemType;
                }
            }
            overallUtilization = totalUsedSpace / totalLockersUsed;
        }
        @Override
        public String toString() {
            calculateMetrics();
            StringBuilder sb = new StringBuilder();
            sb.append("=== 优化摆放方案 ===\n");
            sb.append("使用的储物柜数量: ").append(totalLockersUsed).append("个\n");
            sb.append("纯种类储物柜: ").append(pureLockers).append("个\n");
            sb.append("平均空间利用率: ").append(String.format("%.2f", overallUtilization * 100)).append("%\n");
            sb.append("物品种类切换次数: ").append(itemTypeChanges).append("次\n\n");
            List<Locker> pureLockersList = new ArrayList<>();
            List<Locker> mixedLockersList = new ArrayList<>();
            for (Locker locker : lockers) {
                if (locker.itemTypes.size() == 1) {
                    pureLockersList.add(locker);
                } else {
                    mixedLockersList.add(locker);
                }
            }
            sb.append("【纯种类储物柜】:\n");
            for (Locker locker : pureLockersList) {
                sb.append(locker.toString()).append("\n");
            }
            sb.append("【混合储物柜】:\n");
            for (Locker locker : mixedLockersList) {
                sb.append(locker.toString()).append("\n");
            }
            return sb.toString();
        }
    }
    /**
     * 余料物品辅助类
     */
    private static class RemainderItem {
        Item item;
        int quantity;
        RemainderItem(Item item, int quantity) {
            this.item = item;
            this.quantity = quantity;
        }
        @Override
        public String toString() {
            return item.name + "(" + quantity + "个)";
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            RemainderItem that = (RemainderItem) obj;
            return quantity == that.quantity && item.name.equals(that.item.name);
        }
        @Override
        public int hashCode() {
            return Objects.hash(item.name, quantity);
        }
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入物品种类数量:");
        int itemTypes = scanner.nextInt();
        scanner.nextLine();
        List<Item> items = new ArrayList<>();
        for (int i = 0; i < itemTypes; i++) {
            System.out.println("\n请输入第" + (i + 1) + "种物品的信息:");
            System.out.print("物品名称: ");
            String name = scanner.nextLine();
            System.out.print("单种存放最大数量: ");
            int maxCapacity = scanner.nextInt();
            System.out.print("库存数量: ");
            int stock = scanner.nextInt();
            scanner.nextLine();
            items.add(new Item(name, maxCapacity, stock));
        }
        PackingSolution solution = optimizedPacking(items);
        System.out.println("\n" + solution);
        scanner.close();
    }
    /**
     * 优化装箱算法:修复数量管理问题
     */
    public static PackingSolution optimizedPacking(List<Item> items) {
        // 验证输入
        if (items == null || items.isEmpty()) {
            throw new IllegalArgumentException("物品列表不能为空");
        }
        // 创建物品副本用于处理
        List<Item> itemsToPack = new ArrayList<>();
        for (Item item : items) {
            itemsToPack.add(new Item(item.name, item.maxCapacity, item.stock));
        }
        PackingSolution solution = new PackingSolution();
        int lockerId = 1;
        // 第一阶段:对每种物品进行集中处理,分配完整储物柜
        Map<String, Integer> remainders = new HashMap<>();
        for (Item item : itemsToPack) {
            if (item.getRemaining() == 0) continue;
            int fullLockersNeeded = item.getRemaining() / item.maxCapacity;
            int remainder = item.getRemaining() % item.maxCapacity;
            // 分配完整储物柜
            for (int i = 0; i < fullLockersNeeded; i++) {
                Locker locker = new Locker(lockerId++);
                if (locker.addItem(item, item.maxCapacity)) {
                    solution.addLocker(locker);
                } else {
                    System.out.println("警告: 无法分配完整储物柜给物品 " + item.name);
                }
            }
            if (remainder > 0) {
                remainders.put(item.name, remainder);
            }
        }
        // 第二阶段:优化余料分配
        if (!remainders.isEmpty()) {
            uniformRemainderProcessing(remainders, itemsToPack, solution, lockerId);
        }
        // 验证结果
        if (!validateSolution(items, solution)) {
            System.out.println("警告: 数量分配验证失败");
        }
        return solution;
    }
    /**
     * 修复后的均匀余料处理算法
     */
    private static void uniformRemainderProcessing(Map<String, Integer> remainders,
                                                   List<Item> items,
                                                   PackingSolution solution,
                                                   int startLockerId) {
        int lockerId = startLockerId;
        // 将余料转换为可处理的物品列表
        List<RemainderItem> remainderItems = new ArrayList<>();
        for (Item item : items) {
            Integer remainder = remainders.get(item.name);
            if (remainder != null && remainder > 0 && item.getRemaining() > 0) {
                remainderItems.add(new RemainderItem(item, Math.min(remainder, item.getRemaining())));
            }
        }
        // 按物品单位空间从大到小排序
        remainderItems.sort((a, b) -> Double.compare(b.item.unitSpace, a.item.unitSpace));
        // 使用修复后的余料处理算法
        List<Locker> remainderLockers = fixedRemainderArrangement(remainderItems, solution, lockerId);
        // 将最优解添加到解决方案中
        for (Locker locker : remainderLockers) {
            solution.addLocker(locker);
        }
    }
    /**
     * 修复后的余料排列方案
     */
    private static List<Locker> fixedRemainderArrangement(List<RemainderItem> remainderItems,
                                                          PackingSolution solution,
                                                          int startLockerId) {
        if (remainderItems.isEmpty()) {
            return new ArrayList<>();
        }
        // 创建副本避免修改原始数据
        List<RemainderItem> remaining = new ArrayList<>();
        for (RemainderItem ri : remainderItems) {
            if (ri.quantity > 0) {
                remaining.add(new RemainderItem(ri.item, ri.quantity));
            }
        }
        List<Locker> lockers = new ArrayList<>();
        int lockerId = startLockerId;
        // 主要策略:创建高利用率储物柜
        while (!remaining.isEmpty()) {
            Locker locker = createOptimizedLocker(remaining, lockerId);
            if (locker != null) {
                lockers.add(locker);
                lockerId++;
                // 移除已处理完的物品
                Iterator<RemainderItem> iterator = remaining.iterator();
                while (iterator.hasNext()) {
                    RemainderItem ri = iterator.next();
                    if (ri.quantity == 0) {
                        iterator.remove();
                    }
                }
                double utilization = (1.0 - locker.remainingSpace) * 100;
                System.out.println("创建储物柜,利用率: " + String.format("%.2f", utilization) + "%");
            } else {
                break;
            }
        }
        // 处理剩余物品(如果有)
        if (!remaining.isEmpty()) {
            System.out.println("使用标准算法处理剩余 " + remaining.size() + " 种物品");
            List<Locker> standardLockers = standardBestFit(remaining, lockerId);
            lockers.addAll(standardLockers);
        }
        return lockers;
    }
    /**
     * 创建优化储物柜(修复数量验证)
     */
    private static Locker createOptimizedLocker(List<RemainderItem> remaining, int lockerId) {
        if (remaining.isEmpty()) {
            return null;
        }
        Locker locker = new Locker(lockerId);
        double targetUtilization = 0.85;
        double minUtilization = 0.3; // 最低利用率阈值
        // 寻找最佳组合
        List<RemainderItem> bestCombination = findValidCombination(remaining, targetUtilization);
        if (bestCombination.isEmpty()) {
            // 尝试达到最低利用率
            bestCombination = findMinUtilizationCombination(remaining, minUtilization);
        }
        if (bestCombination.isEmpty()) {
            return null;
        }
        // 分配组合到储物柜
        boolean success = allocateValidCombination(remaining, locker, bestCombination);
        if (!success || locker.contents.isEmpty()) {
            return null;
        }
        // 检查利用率是否可接受
        double utilization = 1.0 - locker.remainingSpace;
        if (utilization < minUtilization) {
            // 回滚分配
            rollbackAllocation(remaining, locker);
            return null;
        }
        return locker;
    }
    /**
     * 寻找有效组合(修复数量验证)
     */
    private static List<RemainderItem> findValidCombination(List<RemainderItem> items, double targetUtilization) {
        List<RemainderItem> bestCombination = new ArrayList<>();
        double[] bestDiff = {Double.MAX_VALUE};
        // 过滤有效物品
        List<RemainderItem> availableItems = new ArrayList<>();
        for (RemainderItem ri : items) {
            if (ri.quantity > 0 && ri.item.getRemaining() > 0) {
                availableItems.add(ri);
            }
        }
        if (availableItems.isEmpty()) {
            return bestCombination;
        }
        // 使用DFS搜索有效组合
        validDfsCombination(availableItems, 0, 0.0, targetUtilization,
                new ArrayList<>(), bestCombination, bestDiff);
        return bestCombination;
    }
    /**
     * 有效的DFS组合搜索
     */
    private static void validDfsCombination(List<RemainderItem> items, int index, double currentSpace,
                                            double targetUtilization,
                                            List<RemainderItem> current,
                                            List<RemainderItem> bestCombination, double[] bestDiff) {
        // 检查当前组合的有效性
        if (!current.isEmpty()) {
            double diff = Math.abs(currentSpace - targetUtilization);
            if (currentSpace <= 1.0 + 1e-6 && diff < bestDiff[0] && isCombinationValid(current)) {
                bestCombination.clear();
                for (RemainderItem ri : current) {
                    bestCombination.add(new RemainderItem(ri.item, ri.quantity));
                }
                bestDiff[0] = diff;
                if (diff < 0.01) {
                    return;
                }
            }
        }
        if (index >= items.size() || currentSpace > 1.0 + 1e-6) {
            return;
        }
        RemainderItem currentItem = items.get(index);
        // 不选择当前物品
        validDfsCombination(items, index + 1, currentSpace, targetUtilization,
                current, bestCombination, bestDiff);
        // 选择当前物品
        if (currentItem.quantity > 0) {
            int maxCanAdd = Math.min(currentItem.quantity,
                    (int) Math.floor((1.0 - currentSpace) / currentItem.item.unitSpace));
            maxCanAdd = Math.min(maxCanAdd, currentItem.item.getRemaining()); // 重要修复:不超过剩余数量
            for (int qty = 1; qty <= maxCanAdd; qty++) {
                current.add(new RemainderItem(currentItem.item, qty));
                validDfsCombination(items, index + 1, currentSpace + qty * currentItem.item.unitSpace,
                        targetUtilization, current, bestCombination, bestDiff);
                current.remove(current.size() - 1);
            }
        }
    }
    /**
     * 检查组合是否有效(修复数量验证)
     */
    private static boolean isCombinationValid(List<RemainderItem> combination) {
        Map<String, Integer> totalQuantities = new HashMap<>();
        for (RemainderItem ri : combination) {
            int currentTotal = totalQuantities.getOrDefault(ri.item.name, 0);
            totalQuantities.put(ri.item.name, currentTotal + ri.quantity);
            // 检查是否超过物品剩余数量
            if (currentTotal + ri.quantity > getRemainingQuantity(ri.item)) {
                return false;
            }
        }
        return true;
    }
    /**
     * 获取物品剩余数量
     */
    private static int getRemainingQuantity(Item item) {
        return item.stock - item.allocated;
    }
    /**
     * 寻找达到最低利用率的组合
     */
    private static List<RemainderItem> findMinUtilizationCombination(List<RemainderItem> items, double minUtilization) {
        List<RemainderItem> combination = new ArrayList<>();
        double currentSpace = 0.0;
        // 按单位空间排序
        List<RemainderItem> sortedItems = new ArrayList<>(items);
        sortedItems.sort((a, b) -> Double.compare(b.item.unitSpace, a.item.unitSpace));
        for (RemainderItem ri : sortedItems) {
            if (ri.quantity > 0 && currentSpace < minUtilization) {
                int maxCanAdd = Math.min(ri.quantity,
                        (int) Math.floor((1.0 - currentSpace) / ri.item.unitSpace));
                maxCanAdd = Math.min(maxCanAdd, ri.item.getRemaining()); // 重要修复
                if (maxCanAdd > 0) {
                    combination.add(new RemainderItem(ri.item, maxCanAdd));
                    currentSpace += maxCanAdd * ri.item.unitSpace;
                }
            }
        }
        if (currentSpace >= minUtilization) {
            return combination;
        }
        return new ArrayList<>();
    }
    /**
     * 分配有效组合到储物柜(修复数量验证)
     */
    private static boolean allocateValidCombination(List<RemainderItem> remaining, Locker locker,
                                                    List<RemainderItem> combination) {
        // 验证组合中的所有物品都有足够的数量
        for (RemainderItem combItem : combination) {
            boolean found = false;
            for (RemainderItem remItem : remaining) {
                if (remItem.item.name.equals(combItem.item.name)) {
                    found = true;
                    if (combItem.quantity > remItem.quantity) {
                        return false;
                    }
                    if (combItem.quantity > remItem.item.getRemaining()) {
                        return false;
                    }
                    break;
                }
            }
            if (!found) {
                return false;
            }
        }
        // 实际分配
        for (RemainderItem combItem : combination) {
            for (RemainderItem remItem : remaining) {
                if (remItem.item.name.equals(combItem.item.name)) {
                    if (locker.addItem(combItem.item, combItem.quantity)) {
                        remItem.quantity -= combItem.quantity;
                    } else {
                        return false;
                    }
                    break;
                }
            }
        }
        return true;
    }
    /**
     * 回滚分配
     */
    private static void rollbackAllocation(List<RemainderItem> remaining, Locker locker) {
        for (Map.Entry<String, Integer> entry : locker.contents.entrySet()) {
            String itemName = entry.getKey();
            int quantity = entry.getValue();
            for (RemainderItem ri : remaining) {
                if (ri.item.name.equals(itemName)) {
                    ri.quantity += quantity;
                    ri.item.allocated -= quantity; // 回滚分配数量
                    break;
                }
            }
        }
    }
    /**
     * 标准最佳适应算法
     */
    private static List<Locker> standardBestFit(List<RemainderItem> remainderItems, int startLockerId) {
        List<Locker> lockers = new ArrayList<>();
        int lockerId = startLockerId;
        // 创建副本
        List<RemainderItem> remaining = new ArrayList<>();
        for (RemainderItem ri : remainderItems) {
            if (ri.quantity > 0) {
                remaining.add(new RemainderItem(ri.item, ri.quantity));
            }
        }
        // 按单位空间排序
        remaining.sort((a, b) -> Double.compare(b.item.unitSpace, a.item.unitSpace));
        for (RemainderItem ri : remaining) {
            while (ri.quantity > 0 && ri.item.getRemaining() > 0) {
                Locker bestLocker = null;
                double bestRemainingSpace = Double.MAX_VALUE;
                // 寻找最佳储物柜
                for (Locker locker : lockers) {
                    if (locker.canAdd(ri.item, 1) && locker.remainingSpace < bestRemainingSpace) {
                        bestLocker = locker;
                        bestRemainingSpace = locker.remainingSpace;
                    }
                }
                if (bestLocker != null) {
                    int maxCanAdd = (int) Math.floor(bestLocker.remainingSpace / ri.item.unitSpace);
                    int actualAdd = Math.min(ri.quantity, maxCanAdd);
                    actualAdd = Math.min(actualAdd, ri.item.getRemaining()); // 重要修复
                    if (actualAdd > 0 && bestLocker.addItem(ri.item, actualAdd)) {
                        ri.quantity -= actualAdd;
                    } else {
                        break;
                    }
                } else {
                    // 创建新储物柜
                    Locker newLocker = new Locker(lockerId++);
                    int maxCanAdd = (int) Math.floor(1.0 / ri.item.unitSpace);
                    int actualAdd = Math.min(ri.quantity, maxCanAdd);
                    actualAdd = Math.min(actualAdd, ri.item.getRemaining()); // 重要修复
                    if (actualAdd > 0 && newLocker.addItem(ri.item, actualAdd)) {
                        lockers.add(newLocker);
                        ri.quantity -= actualAdd;
                    } else {
                        break;
                    }
                }
            }
        }
        return lockers;
    }
    /**
     * 验证解决方案(修复验证逻辑)
     */
    private static boolean validateSolution(List<Item> originalItems, PackingSolution solution) {
        Map<String, Integer> allocatedQuantities = new HashMap<>();
        Map<String, Integer> originalQuantities = new HashMap<>();
        // 记录原始数量
        for (Item item : originalItems) {
            originalQuantities.put(item.name, item.stock);
        }
        // 计算分配数量
        for (Locker locker : solution.lockers) {
            for (Map.Entry<String, Integer> entry : locker.contents.entrySet()) {
                String itemName = entry.getKey();
                int quantity = entry.getValue();
                allocatedQuantities.put(itemName, allocatedQuantities.getOrDefault(itemName, 0) + quantity);
            }
        }
        boolean valid = true;
        for (Map.Entry<String, Integer> entry : originalQuantities.entrySet()) {
            String itemName = entry.getKey();
            int originalQty = entry.getValue();
            int allocatedQty = allocatedQuantities.getOrDefault(itemName, 0);
            if (originalQty != allocatedQty) {
                System.out.println("验证失败: " + itemName + " 原始数量=" + originalQty +
                        ", 分配数量=" + allocatedQty);
                valid = false;
            }
        }
        if (valid) {
            System.out.println("数量验证通过");
        }
        return valid;
    }
    /**
     * 性能测试和比较
     */
    public static void performanceTest() {
        System.out.println("=== 性能测试 ===");
        // 测试数据
        List<Item> testItems = Arrays.asList(
                new Item("X", 20, 87),
                new Item("Y", 15, 63),
                new Item("Z", 34, 125),
                new Item("T", 25, 48),
                new Item("U", 40, 92)
        );
        System.out.println("测试物品数量: " + testItems.size());
        int totalItems = testItems.stream().mapToInt(item -> item.stock).sum();
        System.out.println("总物品数量: " + totalItems);
        long startTime = System.currentTimeMillis();
        PackingSolution solution = optimizedPacking(testItems);
        long endTime = System.currentTimeMillis();
        System.out.println("\n优化算法结果:");
        System.out.println("处理时间: " + (endTime - startTime) + "ms");
        System.out.println("储物柜数量: " + solution.totalLockersUsed);
        System.out.println("纯种类储物柜: " + solution.pureLockers);
        System.out.println("空间利用率: " + String.format("%.2f", solution.overallUtilization * 100) + "%");
    }
}
src/main/java/com/zy/asrs/utils/ToSortLineUtils.java
@@ -1,7 +1,7 @@
package com.zy.asrs.utils;
import com.zy.asrs.entity.OrderDetl;
import com.zy.asrs.entity.param.OrderToLine;
import com.zy.asrs.utils.param.ItemUtilParam;
import java.util.ArrayList;
import java.util.List;
@@ -36,34 +36,29 @@
        return sku+sign_F+po+sign_F+upc;
    }
    public static OrderToLine GetOrderToLineGro(List<GroupedLockerOptimizerUtils.Item> items,OrderToLine orderToLine){
        List<OrderToLine.MatList> matLists = new ArrayList<>();
        // 使用分组优先算法
        GroupedLockerOptimizerUtils.PackingSolution solution = GroupedLockerOptimizerUtils.packItemsWithGrouping(items);
        for (GroupedLockerOptimizerUtils.Locker locker:solution.lockers) {
            for (String mantnr : locker.contents.keySet()){
                System.out.println(mantnr+"<===>"+locker.contents.get(mantnr));
                String[] split = mantnr.split(Pattern.quote(sign_F));
                OrderToLine.MatList mat = new OrderToLine.MatList(
                        split[0],  // matnr -> sku
                        split[1],  //  supp -> po -> s1
                        locker.contents.get(mantnr).doubleValue(),   //整料
                        split[2],   //barcode -> upc -> s2
                        1,
                        null,   //origin -> supplier 货源
                        locker.bindingTags
                );
                matLists.add(mat);
            }
    public static OrderToLine GetOrderToLine(List<ItemUtilParam.Item> items, OrderToLine orderToLine,String sign){
        switch (sign){
            case "Opt":
                List<OptimizedLockerPackingUtils.Item> itemsOpt = new ArrayList<>();
                for (ItemUtilParam.Item item:items){
                    itemsOpt.add(new OptimizedLockerPackingUtils.Item(item.getName(),item.getMaxCapacity(),item.getStock()));
                }
                return GetOrderToLineOpt(itemsOpt,orderToLine);
            case "Opt3":
                List<OptimizedLockerPacking3Utils.Item> itemsOpt3 = new ArrayList<>();
                for (ItemUtilParam.Item item:items){
                    itemsOpt3.add(new OptimizedLockerPacking3Utils.Item(item.getName(),item.getMaxCapacity(),item.getStock()));
                }
                return GetOrderToLineOpt3(itemsOpt3,orderToLine);
            default:
                return null;
        }
        orderToLine.setMatList(matLists);
        return orderToLine;
    }
    public static OrderToLine GetOrderToLineOpt(List<OptimizedLockerPackingUtils.Item> items,OrderToLine orderToLine){
        List<OrderToLine.MatList> matLists = new ArrayList<>();
        // 使用分组优先算法
        OptimizedLockerPackingUtils.PackingSolution packingSolution = OptimizedLockerPackingUtils.optimizedPacking(items);
//        System.out.println("\n" + packingSolution);
        for (OptimizedLockerPackingUtils.Locker locker:packingSolution.lockers) {
            for (String mantnr : locker.contents.keySet()){
                System.out.println(mantnr+"<===>"+locker.contents.get(mantnr));
@@ -83,4 +78,30 @@
        orderToLine.setMatList(matLists);
        return orderToLine;
    }
    public static OrderToLine GetOrderToLineOpt3(List<OptimizedLockerPacking3Utils.Item> items,OrderToLine orderToLine){
        List<OrderToLine.MatList> matLists = new ArrayList<>();
        // 使用分组优先算法
        OptimizedLockerPacking3Utils.PackingSolution packingSolution = OptimizedLockerPacking3Utils.optimizedPacking(items);
//        System.out.println("\n" + packingSolution);
        for (OptimizedLockerPacking3Utils.Locker locker:packingSolution.lockers) {
            for (String mantnr : locker.contents.keySet()){
//                System.out.println(mantnr+"<===>"+locker.contents.get(mantnr));
                String[] split = mantnr.split(Pattern.quote(sign_F));
                OrderToLine.MatList mat = new OrderToLine.MatList(
                        split[0],  // matnr -> sku
                        split[1],  //  supp -> po -> s1
                        locker.contents.get(mantnr).doubleValue(),   //整料
                        split[2],   //barcode -> upc -> s2
                        1,
                        null,   //origin -> supplier 货源
                        locker.bindingTags
                );
                matLists.add(mat);
            }
        }
        orderToLine.setMatList(matLists);
        return orderToLine;
    }
}
src/main/java/com/zy/asrs/utils/param/ItemUtilParam.java
@@ -2,24 +2,78 @@
import lombok.Data;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Data
public class ItemUtilParam {
    String name;
    double unitSpace;
    int maxCapacity;
    int stock;
    int remaining;
    public ItemUtilParam(String name, int maxCapacity, int stock) {
        this.name = name;
        this.maxCapacity = maxCapacity;
        this.unitSpace = 1.0 / maxCapacity;
        this.stock = stock;
        this.remaining = stock;
    @Data
    public static class Item {
        String name;
        double unitSpace;
        int maxCapacity;
        int stock;
        int remaining;
        public Item(String name, int maxCapacity, int stock) {
            this.name = name;
            this.maxCapacity = maxCapacity;
            this.unitSpace = 1.0 / maxCapacity;
            this.stock = stock;
            this.remaining = stock;
        }
        @Override
        public String toString() {
            return name + "(单放" + maxCapacity + "个, 库存" + stock + "个)";
        }
    }
    @Override
    public String toString() {
        return name + "(单放" + maxCapacity + "个, 库存" + stock + "个)";
    static class Locker {
        int id;
        double remainingSpace;
        long bindingTags;
        Map<String, Integer> contents;
        Set<String> itemTypes;
        public Locker(int id) {
            this.id = id;
            this.remainingSpace = 1.0;
            this.contents = new HashMap<>();
            this.itemTypes = new HashSet<>();
            this.bindingTags = System.currentTimeMillis();
        }
        public boolean canAdd(ItemUtilParam.Item item, int quantity) {
            return remainingSpace >= quantity * item.unitSpace;
        }
        public void addItem(ItemUtilParam.Item item, int quantity) {
            double spaceUsed = quantity * item.unitSpace;
            remainingSpace -= spaceUsed;
            contents.put(item.name, contents.getOrDefault(item.name, 0) + quantity);
            itemTypes.add(item.name);
            item.remaining -= quantity;
        }
        public boolean containsItemType(String itemName) {
            return itemTypes.contains(itemName);
        }
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("储物柜").append(id).append(": ");
            sb.append(String.format("剩余空间%.4f", remainingSpace)).append("\n");
            for (Map.Entry<String, Integer> entry : contents.entrySet()) {
                sb.append("  ").append(entry.getKey()).append(": ").append(entry.getValue()).append("个\n");
            }
            return sb.toString();
        }
    }
}
src/main/java/com/zy/common/config/ControllerResAdvice.java
@@ -85,7 +85,6 @@
        ApiLog apiLog = apiLogService.selectOne(new EntityWrapper<ApiLog>()
                .eq("namespace", name)
                .eq("request", request)
                .eq("response", response)
                .eq("result", success? 1:0)
                .orderBy("create_time", false)
src/main/webapp/static/js/apiLog/apiLog.js
@@ -21,24 +21,25 @@
        cellMinWidth: 50,
        height: 'full-120',
        cols: [[
            {type: 'checkbox'}
            // {type: 'checkbox'}
            // ,{field: 'id', align: 'center',title: 'ID'}
            // ,{field: 'uuid', align: 'center',title: '日志编号'}
            ,{field: 'namespace', align: 'center',title: '名称空间'}
            ,{field: 'url', align: 'center',title: '表单ID'}
            // ,
            {field: 'namespace', align: 'center',title: '名称空间',hide: false}
            ,{field: 'url', align: 'center',title: '接口地址',hide: false}
            ,{field: 'appkey', align: 'center',title: '平台密钥',hide: true}
            // ,{field: 'timestamp', align: 'center',title: '时间戳'}
            ,{field: 'clientIp', align: 'center',title: 'URL',hide: true}
            ,{field: 'request', align: 'center',title: '请求内容'}
            ,{field: 'response', align: 'center',title: '操作内容'}
            // ,{field: 'err', align: 'center',title: '异常内容'}
            ,{field: 'result$', align: 'center',title: '结果', templet: '#resTpl', width: 80}
            ,{field: 'timestamp', align: 'center',title: '时间戳',hide: true}
            ,{field: 'clientIp', align: 'center',title: '客户端IP',hide: false}
            ,{field: 'request', align: 'center',title: '请求内容',hide: false}
            ,{field: 'response', align: 'center',title: '操作内容',hide: false}
            // ,{field: 'err', align: 'center',title: '异常内容',hide: true}
            ,{field: 'result$', align: 'center',title: '结果', templet: '#resTpl', width: 80,hide: false}
            // ,{field: 'status$', align: 'center',title: '状态'}
            ,{field: 'createTime$', align: 'center',title: '添加时间'}
            ,{field: 'createTime$', align: 'center',title: '添加时间',hide: false}
            // ,{field: 'updateTime$', align: 'center',title: '修改时间'}
            ,{field: 'memo', align: 'center',title: '备注', hide: true}
            ,{fixed: 'right', title:'操作', align: 'center', toolbar: '#operate', width: 80}
            ,{fixed: 'right', title:'操作', align: 'center', toolbar: '#operate', width: 80, hide: true}
        ]],
        request: {
            pageName: 'curr',
src/main/webapp/views/apiLog/apiLog.html
@@ -24,6 +24,12 @@
                        </div>
                    </div>
                    <div class="layui-inline">
                        <label class="layui-form-label">接口地址: </label>
                        <div class="layui-input-inline">
                            <input class="layui-input" type="text" name="url" placeholder="请输入接口地址" autocomplete="off">
                        </div>
                    </div>
                    <div class="layui-inline">
                        <label class="layui-form-label">请求内容:</label>
                        <div class="layui-input-inline">
                            <input class="layui-input" type="text" name="request" placeholder="请输入(订单号或品号)" autocomplete="off">
@@ -47,7 +53,7 @@
<script type="text/html" id="toolbar">
    <div class="layui-btn-container">
<!--        <button class="layui-btn layui-btn-sm" id="btn-add" lay-event="addData">新增</button>-->
        <button class="layui-btn layui-btn-sm layui-btn-danger" id="btn-delete" lay-event="deleteData">删除</button>
<!--        <button class="layui-btn layui-btn-sm layui-btn-danger" id="btn-delete" lay-event="deleteData">删除</button>-->
        <button class="layui-btn layui-btn-primary layui-btn-sm" id="btn-export" lay-event="exportData" style="float: right">导出</button>
    </div>
</script>