自动化立体仓库 - WMS系统
0eab67f624c2b3349002860b408942265a076b9e..817683f9935a6343e954ffe7fd0eeae46e55d29c
5 天以前 chen.llin
AGV下单
817683 对比 | 目录
5 天以前 chen.llin
AGV回调
aa0c29 对比 | 目录
5 天以前 chen.llin
工作档空指针
103c49 对比 | 目录
5 天以前 chen.llin
库位表缺少id报错,根据DDL修复
373736 对比 | 目录
5 天以前 chen.llin
路径初始化异常修复
17a714 对比 | 目录
10个文件已修改
350 ■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/LocDetlController.java 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/OpenController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/StaDescController.java 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/WrkMastController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/AgvCallbackDto.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/LocDetl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/WrkMast.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java 93 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/handler/AgvHandler.java 129 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/constant/ApiInterfaceConstant.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/LocDetlController.java
@@ -129,7 +129,14 @@
    @RequestMapping(value = "/locDetl/{id}/auth")
    @ManagerAuth
    public R get(@PathVariable("id") String id) {
        return R.ok(locDetlService.selectById(String.valueOf(id)));
        // asr_loc_detl 表没有 id 主键,使用 loc_no 查询
        // 注意:一个 loc_no 可能对应多条记录,返回第一条
        List<LocDetl> list = locDetlService.selectList(new EntityWrapper<LocDetl>().eq("loc_no", id).last("limit 1"));
        if (!list.isEmpty()) {
            return R.ok(list.get(0));
        }
        // 返回 null 而不是错误,避免前端解析失败
        return R.ok(null);
    }
    @RequestMapping(value = "/locDetl/auth")
@@ -234,13 +241,29 @@
    @RequestMapping(value = "/locDetl/update/auth")
    @ManagerAuth(memo = "库位明细修改")
    public R update(LocDetl locDetl) {
        if (Cools.isEmpty(locDetl) || null == locDetl.getMatnr()) {
            return R.error();
        if (Cools.isEmpty(locDetl) || null == locDetl.getMatnr() || Cools.isEmpty(locDetl.getLocNo())) {
            return R.error("参数不完整,需要 locNo 和 matnr");
        }
        locDetl.setModiUser(getUserId());
        locDetl.setModiTime(new Date());
        locDetlService.updateById(locDetl);
        return R.ok();
        // asr_loc_detl 表没有 id 主键,使用 EntityWrapper 根据 loc_no 和 matnr 更新
        EntityWrapper<LocDetl> wrapper = new EntityWrapper<>();
        wrapper.eq("loc_no", locDetl.getLocNo());
        wrapper.eq("matnr", locDetl.getMatnr());
        // 如果有批次信息,也加入条件
        if (!Cools.isEmpty(locDetl.getBatch())) {
            wrapper.eq("batch", locDetl.getBatch());
        } else {
            wrapper.andNew("(batch IS NULL OR batch = '')");
        }
        boolean result = locDetlService.update(locDetl, wrapper);
        if (result) {
            return R.ok();
        }
        return R.error("更新失败,未找到匹配的记录");
    }
    @RequestMapping(value = "/locDetl/delete/auth")
src/main/java/com/zy/asrs/controller/OpenController.java
@@ -54,7 +54,9 @@
    public synchronized R agvCallback(@RequestHeader(required = false) String appkey,
                                      @RequestBody(required = false) AgvCallbackDto param,
                                      HttpServletRequest request) {
        auth(appkey, param, request);
        return openService.agvCallback(param);
    }
src/main/java/com/zy/asrs/controller/StaDescController.java
@@ -12,12 +12,8 @@
import com.core.common.R;
import com.zy.asrs.entity.StaDesc;
import com.zy.asrs.entity.param.StaDescInitParam;
import com.zy.asrs.mapper.StaDescMapper;
import com.zy.asrs.service.StaDescService;
import com.zy.common.web.BaseController;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -31,24 +27,33 @@
    private static final Logger log = LoggerFactory.getLogger(StaDescController.class);
    @Autowired
    private StaDescService staDescService;
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    @RequestMapping(value = "/staDesc/init/auth")
    @ManagerAuth(memo = "初始化站点路径")
    public R init(StaDescInitParam param) {
        try {
            // 参数校验:确保堆垛机号不为空
            if (Cools.isEmpty(param.getCrnNo())) {
                return R.error("堆垛机号不能为空");
            }
            // 格式化开关:只删除当前堆垛机号的数据,而不是全部数据
            if (param.getTypeDesc() == 1) {
                staDescService.delete(new EntityWrapper<>());
                staDescService.delete(new EntityWrapper<StaDesc>()
                        .eq("crn_no", param.getCrnNo())
                );
            }
            String[] startStaList = param.getStartStaList().split(";");
            String[] endStaList = param.getEndStaList().split(";");
            List<StaDesc> staDescList = new ArrayList<>();
            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
            StaDescMapper sqlSessionMapper = sqlSession.getMapper(StaDescMapper.class);
            Date currentTime = new Date();
            Long userId = getUserId();
            int insertCount = 0;
            // SQL Server 批量插入时无法获取自增主键,改为循环单个插入
            for (String startSta : startStaList) {
                for (String endSta : endStaList) {
                    for (Integer type : param.getType()) {
                        // 检查是否已存在
                        int sameRes = staDescService.selectCount(new EntityWrapper<StaDesc>()
                                .eq("type_no", type)
                                .eq("stn_no", Integer.parseInt(startSta))
@@ -57,28 +62,27 @@
                        if (sameRes > 0) {
                            continue;
                        }
                        // 创建并插入单条记录
                        StaDesc staDesc = new StaDesc();
                        staDesc.setCrnNo(param.getCrnNo());
                        staDesc.setTypeNo(type);
                        staDesc.setStnNo(Integer.parseInt(startSta));
                        staDesc.setCrnStn(Integer.parseInt(endSta));
                        staDesc.setModiUser(getUserId());
                        staDesc.setModiTime(new Date());
                        staDesc.setAppeUser(getUserId());
                        staDesc.setAppeTime(new Date());
//                        staDescList.add(staDesc);
                        sqlSessionMapper.insert(staDesc);
                        staDesc.setModiUser(userId);
                        staDesc.setModiTime(currentTime);
                        staDesc.setAppeUser(userId);
                        staDesc.setAppeTime(currentTime);
                        // 单个插入,确保能正确获取自增主键
                        if (staDescService.insert(staDesc)) {
                            insertCount++;
                        }
                    }
                }
            }
            try {
                sqlSession.commit();
                sqlSession.close();
            } catch (Exception e) {
                log.error("初始化站点路径异常===>sql异常:{}", e.getMessage());
            }
//            staDescService.insertBatch(staDescList);
            log.info("初始化站点路径完成,共插入 {} 条记录", insertCount);
        } catch (Exception e) {
            log.error("初始化站点路径异常:{}", e.getMessage());
            return R.error("初始化站点路径异常:" + e.getMessage());
src/main/java/com/zy/asrs/controller/WrkMastController.java
@@ -56,6 +56,10 @@
    private <T> void convert(Map<String, Object> map, EntityWrapper<T> wrapper) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String val = String.valueOf(entry.getValue());
            // 跳过空值和 "null" 字符串
            if (Cools.isEmpty(val) || "null".equals(val)) {
                continue;
            }
            if (val.contains(RANGE_TIME_LINK)) {
                String[] dates = val.split(RANGE_TIME_LINK);
                wrapper.ge(entry.getKey(), DateUtils.convert(dates[0]));
src/main/java/com/zy/asrs/entity/AgvCallbackDto.java
@@ -10,9 +10,14 @@
@Data
public class AgvCallbackDto {
    /**
     * 运单编号
     * 运单编号(兼容旧接口)
     */
    private String id;
    /**
     * 任务id(新接口使用)
     */
    private String taskId;
    /**
     * 运单类型
@@ -39,11 +44,16 @@
    private String toBin;
    /**
     * 分配AGV
     * 分配AGV(兼容旧接口)
     */
    private String robotName;
    /**
     * 机器人组(新接口使用)
     */
    private String robotGroup;
    /**
     * 在库口
     */
    private Boolean atPort;
src/main/java/com/zy/asrs/entity/LocDetl.java
@@ -3,9 +3,7 @@
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.Cools;
import com.core.common.SpringUtils;
@@ -32,9 +30,10 @@
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    // 注意:asr_loc_detl 表中没有 id 列,因此不使用 @TableId 注解
    // 如果后续需要添加主键,请先确认数据库表结构
    // @TableId(value = "id", type = IdType.AUTO)
    // private Long id;
    @TableField("loc_id")
    private Long locId;
src/main/java/com/zy/asrs/entity/WrkMast.java
@@ -345,13 +345,16 @@
    }
    public String getCallAgv$() {
        if (this.callAgv == null) {
            return "";
        }
        switch (this.callAgv) {
            case 0: return "未呼叫";
            case 1: return "准备呼叫";
            case 2: return "已呼叫";
            default:
                return "";
        }
        return "";
    }
    public String getWrkSts$() {
src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java
@@ -59,21 +59,92 @@
    private TaskService taskService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R agvCallback(AgvCallbackDto param) {
        String id = param.getId();
        Task task = taskService.selectById(id);
        log.debug("agvCallback param:{}", param);
        // 优先使用taskId,如果没有则使用id(向后兼容)
        String taskId = Cools.isEmpty(param.getTaskId()) ? param.getId() : param.getTaskId();
        if (Cools.isEmpty(taskId)) {
            return R.error("任务id不能为空");
        }
        log.info("收到AGV任务回调请求,taskId:{},fromBin:{},toBin:{},robotGroup:{},kind:{}",
            taskId, param.getFromBin(), param.getToBin(),
            Cools.isEmpty(param.getRobotGroup()) ? param.getRobotName() : param.getRobotGroup(),
            param.getKind());
        // 根据taskId查询任务
        Task task = null;
        try {
            // 尝试将taskId解析为Long类型的id
            Long taskIdLong = Long.parseLong(taskId);
            task = taskService.selectById(taskIdLong);
        } catch (NumberFormatException e) {
            // 如果不是数字,尝试通过其他字段查询(如sheetNo等)
            log.debug("taskId不是数字格式,尝试通过其他字段查询,taskId:{}", taskId);
            task = taskService.selectOne(
                new EntityWrapper<Task>().eq("sheet_no", taskId)
            );
        }
        if (task == null) {
            return R.parse(id +":id不存在");
            log.warn("未找到对应的任务,taskId:{}", taskId);
            return R.error("任务不存在,taskId:" + taskId);
        }
        switch (param.getStatus()) {
            case "Assigned":task.setInvWh(param.getRobotName());break;
            case "Done":task.setWrkSts(9L);break;
            case "Failed":
            case "Cancelled":
            default:
        // 更新任务信息
        if (!Cools.isEmpty(param.getFromBin())) {
            task.setSourceStaNo(param.getFromBin());
        }
        taskService.updateById(task);
        if (!Cools.isEmpty(param.getToBin())) {
            task.setLocNo(param.getToBin());
        }
        // 优先使用robotGroup,如果没有则使用robotName(向后兼容)
        String robotGroup = Cools.isEmpty(param.getRobotGroup()) ? param.getRobotName() : param.getRobotGroup();
        if (!Cools.isEmpty(robotGroup)) {
            task.setInvWh(robotGroup);
        }
        task.setModiTime(new Date());
        // 如果有status字段,按原有逻辑处理(向后兼容)
        if (!Cools.isEmpty(param.getStatus())) {
            switch (param.getStatus()) {
                case "Assigned":
                    if (Cools.isEmpty(robotGroup)) {
                        task.setInvWh(param.getRobotName());
                    }
                    break;
                case "Done":
                    task.setWrkSts(9L);
                    break;
                case "Failed":
                case "Cancelled":
                default:
                    break;
            }
        }
        // 根据任务类型进行相应处理
        if (!Cools.isEmpty(param.getKind())) {
            String kind = param.getKind();
            if ("货物转运".equals(kind)) {
                log.info("处理货物转运任务,taskId:{}", taskId);
            } else if ("实托入库".equals(kind)) {
                log.info("处理实托入库任务,taskId:{}", taskId);
            } else if ("实托出库".equals(kind)) {
                log.info("处理实托出库任务,taskId:{}", taskId);
            }
        }
        // 保存任务更新
        if (!taskService.updateById(task)) {
            log.error("更新任务失败,taskId:{}", taskId);
            return R.error("更新任务失败");
        }
        log.info("AGV任务回调处理成功,taskId:{}", taskId);
        return R.ok();
    }
src/main/java/com/zy/asrs/task/handler/AgvHandler.java
@@ -3,7 +3,6 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.R;
import com.zy.asrs.entity.Task;
import com.zy.asrs.entity.TaskLog;
import com.zy.asrs.entity.WrkMast;
@@ -22,11 +21,8 @@
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -69,7 +65,7 @@
            // 呼叫agv
            String response = "";
            boolean success = false;
            String url = ApiInterfaceConstant.AGV_IP + ApiInterfaceConstant.AGV_CALL_CARRY_PATH;
            String url = ApiInterfaceConstant.AGV_IP + ApiInterfaceConstant.AGV_CREATE_TASK_PATH;
            String namespace = "";
            switch (task.getIoType()) {
                case 1:
@@ -91,9 +87,10 @@
            }
            String body = getRequest(task,namespace);
            try {
                // 使用仙工M4接口
                response = new HttpHandler.Builder()
                        .setUri(ApiInterfaceConstant.AGV_IP)
                        .setPath(ApiInterfaceConstant.AGV_CALL_CARRY_PATH)
                        .setPath(ApiInterfaceConstant.AGV_CREATE_TASK_PATH)
                        .setJson(body)
                        .build()
                        .doPost();
@@ -128,34 +125,36 @@
    }
    /**
     * 构造请求内容
     * 构造请求内容(仙工M4格式)
     */
    private String getRequest(Task task, String nameSpace) {
        JSONObject object = new JSONObject();
        object.put("entityName", "ContainerTransportOrder");
        JSONObject entityValue = new JSONObject();
        entityValue.put("id", task.getId());
        // taskId使用任务ID,格式:T + 任务ID
        object.put("taskId", "T" + task.getId());
        object.put("fromBin", task.getSourceStaNo());
        object.put("toBin", task.getStaNo());
        // robotGroup从invWh字段获取,如果没有则使用默认值
        String robotGroup = task.getInvWh();
        if (robotGroup == null || robotGroup.isEmpty()) {
            robotGroup = "Group-001"; // 默认机器人组
        }
        object.put("robotGroup", robotGroup);
        // kind根据任务类型映射
        String kind = "";
        switch (nameSpace) {
            case "入库":
                kind = "inBound";
                kind = "实托入库";
                break;
            case "出库":
                kind = "outBound";
                kind = "实托出库";
                break;
            case "转移":
                kind = "moveBound";
                kind = "货物转运";
                break;
            default:
                kind = "货物转运";
        }
        entityValue.put("kind", kind);
        entityValue.put("status", "Created");
        entityValue.put("priority", 0);
        entityValue.put("container", task.getBarcode());
        entityValue.put("fromBin", task.getSourceStaNo());
        entityValue.put("toBin", task.getStaNo());
        entityValue.put("emptyFlag", task.getIoType() == 10 ? 1 : 2); // 空托1,满托2 agv带着告诉输送线plc
        object.put("entityValue", entityValue.toJSONString());
        object.put("kind", kind);
        return object.toJSONString();
    }
@@ -225,4 +224,92 @@
            basStationMapper.updateLocStsBatch( Collections.singletonList(String.valueOf(endSite)), "S");
        }
    }
    /**
     * 取消AGV任务(仙工M4接口)
     * @param task 任务对象
     * @return 是否成功
     */
    public boolean cancelAgvTask(Task task) {
        if (!agvSendTask) {
            return false;
        }
        if (task == null || task.getId() == null) {
            log.error("取消AGV任务失败:任务或任务ID为空");
            return false;
        }
        String response = "";
        boolean success = false;
        String url = ApiInterfaceConstant.AGV_IP + ApiInterfaceConstant.AGV_CANCEL_TASK_PATH;
        String namespace = "";
        String kind = "";
        // 根据任务类型确定kind和namespace
        switch (task.getIoType()) {
            case 1:
            case 10:
            case 53:
            case 57:
                namespace = "入库";
                kind = "实托入库";
                break;
            case 3:
                namespace = "转移";
                kind = "货物转运";
                break;
            case 101:
            case 110:
            case 103:
            case 107:
                namespace = "出库";
                kind = "实托出库";
                break;
            default:
                kind = "货物转运";
        }
        // 构造取消任务请求
        JSONObject cancelRequest = new JSONObject();
        cancelRequest.put("taskId", "T" + task.getId());
        cancelRequest.put("kind", kind);
        String body = cancelRequest.toJSONString();
        try {
            response = new HttpHandler.Builder()
                    .setUri(ApiInterfaceConstant.AGV_IP)
                    .setPath(ApiInterfaceConstant.AGV_CANCEL_TASK_PATH)
                    .setJson(body)
                    .build()
                    .doPost();
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject.getInteger("code") != null && jsonObject.getInteger("code").equals(200)) {
                success = true;
                log.info(namespace + "取消AGV任务成功:{}", task.getId());
            } else {
                log.error(namespace + "取消AGV任务失败!!!url:{};request:{};response:{}", url, body, response);
            }
        } catch (Exception e) {
            log.error(namespace + "取消AGV任务异常", e);
        } finally {
            try {
                // 保存接口日志
                apiLogService.save(
                        namespace + "取消AGV任务",
                        url,
                        null,
                        "127.0.0.1",
                        body,
                        response,
                        success
                );
            } catch (Exception e) {
                log.error(namespace + "取消AGV任务保存接口日志异常:", e);
            }
        }
        return success;
    }
}
src/main/java/com/zy/common/constant/ApiInterfaceConstant.java
@@ -36,8 +36,13 @@
    public static final String AGV_IP = "http://127.0.0.1:8080/yhfzwms/open/asrs";
    /**
     * 呼叫AGV搬运
     * 仙工M4 - 创建任务(货物转运、实托入库,实托出库)
     */
    public static final String AGV_CALL_CARRY_PATH = "/api/entity/create/one";
    public static final String AGV_CREATE_TASK_PATH = "/api/agv/inbond";
    /**
     * 仙工M4 - 取消任务
     */
    public static final String AGV_CANCEL_TASK_PATH = "/api/agv/cancelTransport";
}