Junjie
23 小时以前 411ff551ae7641dfc5c9331e99bf8b6e5770e2fa
#mcp
11个文件已添加
916 ■■■■■ 已修改文件
src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/controller/McpController.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/JsonRpcError.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/JsonRpcRequest.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/JsonRpcResponse.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/McpToolHandler.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/ToolDefinition.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/ToolRegistry.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/service/WcsDataFacade.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java 324 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/WcsQueryService.java 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java
New file
@@ -0,0 +1,232 @@
package com.zy.ai.mcp.config;
import com.alibaba.fastjson.JSONObject;
import com.zy.ai.mcp.dto.McpToolHandler;
import com.zy.ai.mcp.dto.ToolDefinition;
import com.zy.ai.mcp.dto.ToolRegistry;
import com.zy.ai.mcp.service.WcsDataFacade;
import java.util.*;
public class McpToolsBootstrap {
    public static void registerAll(ToolRegistry registry, final WcsDataFacade facade) {
        registry.register(tool(
                "device.get_crn_status",
                "Query realtime status of a crn device by deviceNo.",
                schemaObj(
                        propInt("crnNos", true)
                ),
                schemaObj(
                        propObj("devices", true)
                ),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getCrnDeviceStatus(args);
                    }
                }
        ));
        registry.register(tool(
                "device.get_station_status",
                "Query realtime status of a station device",
                schemaObj(
                ),
                schemaObj(
                        propObj("stations", true)
                ),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getStationDeviceStatus(args);
                    }
                }
        ));
        registry.register(tool(
                "device.get_rgv_status",
                "Query realtime status of a rgv device by deviceNo.",
                schemaObj(
                        propInt("rgvNos", true)
                ),
                schemaObj(
                        propObj("devices", true)
                ),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getRgvDeviceStatus(args);
                    }
                }
        ));
        registry.register(tool(
                "task.list",
                "List tasks by filters (status/CrnDevice/RgvDevice//time window).",
                schemaObj(
                        propInt("crnNo", false),
                        propInt("rgvNo", false),
                        propArr("taskNos", false, "integer"),
                        propInt("limit", false)
                ),
                schemaObj(propArr("tasks", true, "object")),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getTasks(args);
                    }
                }
        ));
        registry.register(tool(
                "log.query",
                "Query logs by keyword/level/time window/device/task. Return clipped log lines.",
                schemaObj(
                        propInt("limit", false)
                ),
                schemaObj(propArr("logs", true, "object")),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getLogs(args);
                    }
                }
        ));
        registry.register(tool(
                "config.get_device_config",
                "Get device config by deviceCode.",
                schemaObj(
                        propArr("crnNos", false, "integer"),
                        propArr("rgvNos", false, "integer"),
                        propArr("devpNos", false, "integer")
                ),
                schemaObj(propObj("deviceConfigs", true)),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getDeviceConfig(args);
                    }
                }
        ));
        registry.register(tool(
                "config.get_system_config",
                "Get key system configs for diagnosis.",
                schemaObj(
                ),
                schemaObj(
                        propObj("systemConfigs", true)
                ),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getSystemConfig(args);
                    }
                }
        ));
//        // ★ 诊断聚合快照:一次拿全
//        registry.register(tool(
//                "build_diagnosis_snapshot",
//                "Aggregate diagnosis snapshot: tasks + device realtime + configs + clipped logs for diagnosis.",
//                schemaObj(
//                        propStr("warehouseCode", true),
//                        propArr("deviceCodes", false, "string"),     // 不传则按任务涉及设备推导
//                        propStr("taskNo", false),
//                        propStr("fromTime", true),
//                        propStr("toTime", true),
//                        propInt("taskLimit", false),
//                        propInt("logLimit", false),
//                        propArr("logKeywords", false, "string"),
//                        propBool("includeSystemConfig", false),
//                        propBool("includeDeviceConfig", false)
//                ),
//                schemaObj(
//                        propObj("snapshot", true),
//                        propArr("hints", false, "string")
//                ),
//                new McpToolHandler() {
//                    public Object handle(JSONObject args) {
//                        return facade.buildDiagnosisSnapshot(args);
//                    }
//                }
//        ));
    }
    // ---------- schema helpers ----------
    private static ToolDefinition tool(String name, String desc,
                                       Map<String, Object> in, Map<String, Object> out,
                                       McpToolHandler handler) {
        ToolDefinition d = new ToolDefinition();
        d.setName(name);
        d.setDescription(desc);
        d.setInputSchema(in);
        d.setOutputSchema(out);
        d.setHandler(handler);
        return d;
    }
    private static Map<String, Object> schemaObj(Object... props) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("type", "object");
        Map<String, Object> properties = new LinkedHashMap<String, Object>();
        List<String> required = new ArrayList<String>();
        for (Object p : props) {
            @SuppressWarnings("unchecked")
            Map<String, Object> pm = (Map<String, Object>) p;
            String name = String.valueOf(pm.get("name"));
            boolean req = Boolean.TRUE.equals(pm.get("required"));
            pm.remove("name");
            pm.remove("required");
            properties.put(name, pm);
            if (req) required.add(name);
        }
        m.put("properties", properties);
        if (!required.isEmpty()) m.put("required", required);
        return m;
    }
    private static Map<String, Object> propStr(String name, boolean required) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("required", required);
        m.put("type", "string");
        return m;
    }
    private static Map<String, Object> propInt(String name, boolean required) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("required", required);
        m.put("type", "integer");
        return m;
    }
    private static Map<String, Object> propBool(String name, boolean required) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("required", required);
        m.put("type", "boolean");
        return m;
    }
    private static Map<String, Object> propObj(String name, boolean required) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("required", required);
        m.put("type", "object");
        return m;
    }
    private static Map<String, Object> propArr(String name, boolean required, String itemType) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("required", required);
        m.put("type", "array");
        Map<String, Object> items = new LinkedHashMap<String, Object>();
        items.put("type", itemType);
        m.put("items", items);
        return m;
    }
}
src/main/java/com/zy/ai/mcp/controller/McpController.java
New file
@@ -0,0 +1,90 @@
package com.zy.ai.mcp.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zy.ai.mcp.config.McpToolsBootstrap;
import com.zy.ai.mcp.dto.JsonRpcRequest;
import com.zy.ai.mcp.dto.JsonRpcResponse;
import com.zy.ai.mcp.dto.ToolDefinition;
import com.zy.ai.mcp.dto.ToolRegistry;
import com.zy.ai.mcp.service.WcsDataFacade;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import java.util.*;
@Slf4j
@RestController
@RequestMapping("/ai/mcp")
public class McpController {
    private final ToolRegistry registry = new ToolRegistry();
    @Autowired
    private WcsDataFacade wcsDataFacade;
    public McpController(WcsDataFacade wcsDataFacade) {
        this.wcsDataFacade = wcsDataFacade;
    }
    @PostConstruct
    public void init() {
        McpToolsBootstrap.registerAll(registry, wcsDataFacade);
        log.info("MCP initialized, tools={}", registry.listTools().size());
    }
    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public Object handle(@RequestBody JsonRpcRequest req,
                         @RequestHeader(value = "Authorization", required = false) String auth) {
        // (建议)做一个简单鉴权:防止被随便调用生产系统
        // if (!"Bearer your-token".equals(auth)) return JsonRpcResponse.err(null, 401, "Unauthorized", null);
        String id = req.getId();
        String method = req.getMethod();
        JSONObject params = JSON.parseObject(JSON.toJSONString(req.getParams()));
        try {
            if ("initialize".equals(method)) {
                Map<String, Object> result = new LinkedHashMap<String, Object>();
                result.put("serverName", "wcs-mcp");
                result.put("serverVersion", "1.0.0");
                result.put("capabilities", Arrays.asList("tools"));
                return JsonRpcResponse.ok(id, result);
            }
            if ("tools/list".equals(method)) {
                Map<String, Object> result = new LinkedHashMap<String, Object>();
                result.put("tools", registry.listTools());
                // cursor/paging 你后面需要再加
                return JsonRpcResponse.ok(id, result);
            }
            if ("tools/call".equals(method)) {
                String toolName = params.getString("name");
                JSONObject arguments = params.getJSONObject("arguments");
                if (toolName == null || toolName.trim().isEmpty()) {
                    return JsonRpcResponse.err(id, -32602, "Invalid params: missing tool name", null);
                }
                ToolDefinition def = registry.get(toolName);
                if (def == null) {
                    return JsonRpcResponse.err(id, -32601, "Method not found: tool " + toolName, null);
                }
                Object output = def.getHandler().handle(arguments == null ? new JSONObject() : arguments);
                Map<String, Object> result = new LinkedHashMap<String, Object>();
                result.put("content", output); // 你也可以按 MCP 常见返回结构做 text/json 分段
                return JsonRpcResponse.ok(id, result);
            }
            return JsonRpcResponse.err(id, -32601, "Method not found: " + method, null);
        } catch (Exception e) {
            log.error("MCP handle error, method={}, params={}", method, params, e);
            return JsonRpcResponse.err(id, -32000, "Server error", e.getMessage());
        }
    }
}
src/main/java/com/zy/ai/mcp/dto/JsonRpcError.java
New file
@@ -0,0 +1,16 @@
package com.zy.ai.mcp.dto;
import lombok.Data;
@Data
class JsonRpcError {
    private int code;
    private String message;
    private Object data;
    public JsonRpcError(int code, String message, Object data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }
}
src/main/java/com/zy/ai/mcp/dto/JsonRpcRequest.java
New file
@@ -0,0 +1,13 @@
package com.zy.ai.mcp.dto;
import lombok.Data;
import java.util.Map;
@Data
public class JsonRpcRequest {
    private String jsonrpc; // "2.0"
    private String id;      // string/number 都行,这里用 string
    private String method;  // "tools/list" | "tools/call"
    private Map<String, Object> params;
}
src/main/java/com/zy/ai/mcp/dto/JsonRpcResponse.java
New file
@@ -0,0 +1,25 @@
package com.zy.ai.mcp.dto;
import lombok.Data;
@Data
public class JsonRpcResponse {
    private String jsonrpc = "2.0";
    private String id;
    private Object result;
    private JsonRpcError error;
    public static JsonRpcResponse ok(String id, Object result) {
        JsonRpcResponse r = new JsonRpcResponse();
        r.id = id;
        r.result = result;
        return r;
    }
    public static JsonRpcResponse err(String id, int code, String message, Object data) {
        JsonRpcResponse r = new JsonRpcResponse();
        r.id = id;
        r.error = new JsonRpcError(code, message, data);
        return r;
    }
}
src/main/java/com/zy/ai/mcp/dto/McpToolHandler.java
New file
@@ -0,0 +1,7 @@
package com.zy.ai.mcp.dto;
import com.alibaba.fastjson.JSONObject;
public interface McpToolHandler {
    Object handle(JSONObject arguments) throws Exception;
}
src/main/java/com/zy/ai/mcp/dto/ToolDefinition.java
New file
@@ -0,0 +1,25 @@
package com.zy.ai.mcp.dto;
import lombok.Data;
import java.util.*;
@Data
public class ToolDefinition {
    private String name;
    private String description;
    private Map<String, Object> inputSchema;   // JSON Schema as Map
    private Map<String, Object> outputSchema;  // JSON Schema as Map
    private McpToolHandler handler;
    public Map<String, Object> toMcpToolJson() {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("description", description);
        m.put("inputSchema", inputSchema);
        m.put("outputSchema", outputSchema);
        return m;
    }
}
src/main/java/com/zy/ai/mcp/dto/ToolRegistry.java
New file
@@ -0,0 +1,30 @@
package com.zy.ai.mcp.dto;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ToolRegistry {
    private final Map<String, ToolDefinition> tools = new ConcurrentHashMap<String, ToolDefinition>();
    public void register(ToolDefinition def) {
        tools.put(def.getName(), def);
    }
    public List<Map<String, Object>> listTools() {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        for (ToolDefinition def : tools.values()) {
            list.add(def.toMcpToolJson());
        }
        // 为了稳定输出,按 name 排序
        Collections.sort(list, new Comparator<Map<String, Object>>() {
            public int compare(Map<String, Object> a, Map<String, Object> b) {
                return String.valueOf(a.get("name")).compareTo(String.valueOf(b.get("name")));
            }
        });
        return list;
    }
    public ToolDefinition get(String name) {
        return tools.get(name);
    }
}
src/main/java/com/zy/ai/mcp/service/WcsDataFacade.java
New file
@@ -0,0 +1,25 @@
package com.zy.ai.mcp.service;
import com.alibaba.fastjson.JSONObject;
public interface WcsDataFacade {
    Object getCrnDeviceStatus(JSONObject args);
    Object getStationDeviceStatus(JSONObject args);
    Object getRgvDeviceStatus(JSONObject args);
    Object getTasks(JSONObject args);
    Object getLogs(JSONObject args);
    Object getDeviceConfig(JSONObject args);
    Object getSystemConfig(JSONObject args);
    /**
     * ★ 聚合快照:核心诊断输入
     */
    Object buildDiagnosisSnapshot(JSONObject args);
}
src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java
New file
@@ -0,0 +1,324 @@
package com.zy.ai.mcp.service.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.zy.ai.entity.DeviceConfigsData;
import com.zy.ai.log.AiLogAppender;
import com.zy.ai.mcp.service.WcsDataFacade;
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.entity.BasRgv;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasCrnpService;
import com.zy.asrs.service.BasDevpService;
import com.zy.asrs.service.BasRgvService;
import com.zy.asrs.service.WrkMastService;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.SlaveType;
import com.zy.core.model.StationObjModel;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.RgvProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.RgvThread;
import com.zy.core.thread.StationThread;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@Service("wcsDataFacade")
@RequiredArgsConstructor
public class WcsDataFacadeImpl implements WcsDataFacade {
    @Autowired
    private BasCrnpService basCrnpService;
    @Autowired
    private BasDevpService basDevpService;
    @Autowired
    private BasRgvService basRgvService;
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
    private ConfigService configService;
    @Override
    public Object getCrnDeviceStatus(JSONObject args) {
        List<Integer> deviceNoList = optIntList(args, "crnNos");
        EntityWrapper<BasCrnp> wrapper = new EntityWrapper<>();
        if (deviceNoList != null && deviceNoList.size() > 0) {
            wrapper.in("crn_no", deviceNoList);
        }
        JSONObject data = new JSONObject();
        List<Object> deviceList = new ArrayList<>();
        List<BasCrnp> basCrnps = basCrnpService.selectList(wrapper);
        for (BasCrnp basCrnp : basCrnps) {
            CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
            if (crnThread == null) {
                continue;
            }
            CrnProtocol protocol = crnThread.getStatus();
            deviceList.add(protocol);
        }
        data.put("devices", deviceList);
        return data;
    }
    @Override
    public Object getStationDeviceStatus(JSONObject args) {
        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<>());
        JSONObject data = new JSONObject();
        List<Object> stationList = new ArrayList<>();
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> map = stationThread.getStatusMap();
            for (StationObjModel stationObjModel : basDevp.getStationList$()) {
                StationProtocol stationProtocol = map.get(stationObjModel.getStationId());
                if (stationProtocol == null) {
                    continue;
                }
                stationList.add(stationProtocol);
            }
        }
        data.put("stations", stationList);
        return data;
    }
    @Override
    public Object getRgvDeviceStatus(JSONObject args) {
        List<Integer> deviceNoList = optIntList(args, "rgvNos");
        EntityWrapper<BasRgv> wrapper = new EntityWrapper<>();
        if (deviceNoList != null && deviceNoList.size() > 0) {
            wrapper.in("rgv_no", deviceNoList);
        }
        JSONObject data = new JSONObject();
        List<Object> deviceList = new ArrayList<>();
        List<BasRgv> basRgvs = basRgvService.selectList(wrapper);
        for (BasRgv basRgv : basRgvs) {
            RgvThread rgvThread = (RgvThread) SlaveConnection.get(SlaveType.Rgv, basRgv.getRgvNo());
            if (rgvThread == null) {
                continue;
            }
            RgvProtocol rgvProtocol = rgvThread.getStatus();
            deviceList.add(rgvProtocol);
        }
        data.put("devices", deviceList);
        return data;
    }
    @Override
    public Object getTasks(JSONObject args) {
        int crnNo = optInt(args, "crnNo", -1);
        int rgvNo = optInt(args, "rgvNo", -1);
        List<Integer> taskNos = optIntList(args, "taskNos");
        int limit = optInt(args, "limit", 200);
        EntityWrapper<WrkMast> wrapper = new EntityWrapper<>();
        if (taskNos != null && taskNos.size() > 0) {
            wrapper.in("wrk_no", taskNos);
        }
        if (crnNo != -1) {
            wrapper.eq("crn_no", crnNo);
        }
        if (rgvNo != -1) {
            wrapper.eq("rgv_no", rgvNo);
        }
        List<WrkMast> tasks = wrkMastService.selectList(wrapper);
        JSONObject data = new JSONObject();
        data.put("tasks", tasks);
        return data;
    }
    @Override
    public Object getLogs(JSONObject args) {
        int limit = optInt(args, "limit", 500);
        List<String> logs = AiLogAppender.getRecentLogs(limit);
        JSONObject data = new JSONObject();
        data.put("logs", logs);
        return data;
    }
    @Override
    public Object getDeviceConfig(JSONObject args) {
        JSONObject data = new JSONObject();
        List<DeviceConfigsData> deviceConfigsDataList = new ArrayList<>();
        List<Integer> crnNoList = optIntList(args, "crnNos");
        EntityWrapper<BasCrnp> crnWrapper = new EntityWrapper<>();
        if (crnNoList != null && crnNoList.size() > 0) {
            crnWrapper.in("crn_no", crnNoList);
        }
        List<Integer> rgvNoList = optIntList(args, "rgvNos");
        EntityWrapper<BasRgv> rgvWrapper = new EntityWrapper<>();
        if (rgvNoList != null && rgvNoList.size() > 0) {
            rgvWrapper.in("rgv_no", rgvNoList);
        }
        List<Integer> devpNoList = optIntList(args, "devpNos");
        EntityWrapper<BasDevp> devpWrapper = new EntityWrapper<>();
        if (devpNoList != null && devpNoList.size() > 0) {
            devpWrapper.in("devp_no", devpNoList);
        }
        List<BasCrnp> basCrnps = basCrnpService.selectList(crnWrapper);
        for (BasCrnp basCrnp : basCrnps) {
            DeviceConfigsData deviceConfigsData = new DeviceConfigsData();
            deviceConfigsData.setDeviceNo(basCrnp.getCrnNo());
            deviceConfigsData.setDeviceType(String.valueOf(SlaveType.Crn));
            deviceConfigsData.setDeviceData(basCrnp);
            deviceConfigsDataList.add(deviceConfigsData);
        }
        List<BasRgv> basRgvs = basRgvService.selectList(rgvWrapper);
        for (BasRgv basRgv : basRgvs) {
            DeviceConfigsData deviceConfigsData = new DeviceConfigsData();
            deviceConfigsData.setDeviceNo(basRgv.getRgvNo());
            deviceConfigsData.setDeviceType(String.valueOf(SlaveType.Rgv));
            deviceConfigsData.setDeviceData(basRgv);
            deviceConfigsDataList.add(deviceConfigsData);
        }
        List<BasDevp> basDevps = basDevpService.selectList(devpWrapper);
        for (BasDevp basDevp : basDevps) {
            DeviceConfigsData deviceConfigsData = new DeviceConfigsData();
            deviceConfigsData.setDeviceNo(basDevp.getDevpNo());
            deviceConfigsData.setDeviceType(String.valueOf(SlaveType.Devp));
            deviceConfigsData.setDeviceData(basDevp);
            deviceConfigsDataList.add(deviceConfigsData);
        }
        data.put("deviceConfigs", deviceConfigsDataList);
        return data;
    }
    @Override
    public Object getSystemConfig(JSONObject args) {
        JSONObject data = new JSONObject();
        List<Config> systemConfigList = configService.selectList(new EntityWrapper<Config>().notIn("dingdingReportUrl"));
        data.put("systemConfigs", systemConfigList);
        return data;
    }
    @Override
    public Object buildDiagnosisSnapshot(JSONObject args) {
        String wh = mustStr(args, "warehouseCode");
        List<String> crnDeviceNos = optStrList(args, "crnDeviceNos");
        List<String> taskIds = optStrList(args, "taskIds");
        int lookbackSeconds = optInt(args, "lookbackSeconds", 300);
        int logMaxLines = optInt(args, "logMaxLines", 600);
        boolean includeConfig = optBool(args, "includeConfig", true);
        long now = System.currentTimeMillis();
        long fromTs = now - lookbackSeconds * 1000L;
        // 1) crn devices
        JSONObject devArgs = new JSONObject();
        devArgs.put("deviceNos", crnDeviceNos);
        JSONObject devices = (JSONObject) getCrnDeviceStatus(devArgs);
        // 2) tasks
        JSONObject taskArgs = new JSONObject();
        taskArgs.put("warehouseCode", wh);
        taskArgs.put("taskIds", taskIds);
        taskArgs.put("limit", 200);
        JSONObject tasks = (JSONObject) getTasks(taskArgs);
        // 3) logs (一次性取回,然后做分桶+排序+截断)
        JSONObject logArgs = new JSONObject();
        logArgs.put("warehouseCode", wh);
        logArgs.put("fromTs", fromTs);
        logArgs.put("toTs", now);
//        logArgs.put("deviceIds", deviceIds);
        logArgs.put("taskIds", taskIds);
        logArgs.put("maxLines", logMaxLines);
        JSONObject logs = (JSONObject) getLogs(logArgs);
        // 4) 结构化快照输出(建议:分桶)
        JSONObject snapshot = new JSONObject();
        snapshot.put("warehouseCode", wh);
        snapshot.put("generatedTs", now);
        snapshot.put("timeRange", new JSONObject()
                .fluentPut("fromTs", fromTs)
                .fluentPut("toTs", now)
                .fluentPut("lookbackSeconds", lookbackSeconds));
        snapshot.put("devices", devices);
        snapshot.put("tasks", tasks);
        snapshot.put("logs", logs);
        JSONArray hints = new JSONArray();
        hints.add("Prefer diagnosing with snapshot.devices + snapshot.tasks + snapshot.logs");
        hints.add("Logs are already filtered by time range; if missing, expand lookbackSeconds");
        snapshot.put("hints", hints);
        JSONObject data = new JSONObject();
        data.put("snapshot", snapshot);
        return data;
    }
    // --------- helpers ---------
    private String mustStr(JSONObject o, String key) {
        if (o == null || o.getString(key) == null || o.getString(key).trim().isEmpty())
            throw new IllegalArgumentException(key + " is required");
        return o.getString(key).trim();
    }
    private long mustLong(JSONObject o, String key) {
        if (o == null || !o.containsKey(key)) throw new IllegalArgumentException(key + " is required");
        return o.getLongValue(key);
    }
    private int optInt(JSONObject o, String key, int def) {
        if (o == null || !o.containsKey(key)) return def;
        return o.getIntValue(key);
    }
    private boolean optBool(JSONObject o, String key, boolean def) {
        if (o == null || !o.containsKey(key)) return def;
        return o.getBooleanValue(key);
    }
    private List<String> optStrList(JSONObject o, String key) {
        if (o == null || !o.containsKey(key)) return Collections.emptyList();
        JSONArray arr = o.getJSONArray(key);
        if (arr == null) return Collections.emptyList();
        List<String> list = new ArrayList<>();
        for (int i = 0; i < arr.size(); i++) {
            String s = arr.getString(i);
            if (s != null && !s.trim().isEmpty()) list.add(s.trim());
        }
        return list;
    }
    private List<Integer> optIntList(JSONObject o, String key) {
        if (o == null || !o.containsKey(key)) return Collections.emptyList();
        JSONArray arr = o.getJSONArray(key);
        if (arr == null) return Collections.emptyList();
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < arr.size(); i++) {
            String s = arr.getString(i);
            if (s != null && !s.trim().isEmpty()) list.add(Integer.parseInt(s.trim()));
        }
        return list;
    }
}
src/main/java/com/zy/ai/service/WcsQueryService.java
New file
@@ -0,0 +1,129 @@
package com.zy.ai.service;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.*;
@Service
public class WcsQueryService {
    // 这里注入你现有的各类 Service/Mapper:deviceService/taskService/logService/configService...
    public Map<String, Object> buildDiagnosisSnapshot(Map<String, Object> args) {
        Map<String, Object> focus = (Map<String, Object>) args.get("focus");
        List<String> deviceIds = focus == null ? Collections.emptyList() : (List<String>) focus.get("deviceIds");
        List<String> taskIds   = focus == null ? Collections.emptyList() : (List<String>) focus.get("taskIds");
        int timeWindowSec = getInt(args, "timeWindowSec", 600);
        int logLimit      = getInt(args, "logLimit", 400);
        boolean includeConfigs = getBool(args, "includeConfigs", true);
        boolean includeDerived = getBool(args, "includeDerivedSignals", true);
        Date now = new Date();
        Date from = new Date(now.getTime() - timeWindowSec * 1000L);
        // 1) devices
        List<Object> devices = new ArrayList<>();
        for (String deviceId : safeList(deviceIds)) {
            Map<String, Object> a = new HashMap<>();
            a.put("deviceId", deviceId);
            devices.add(getDeviceState(a));
        }
        // 2) tasks
        Map<String, Object> taskArgs = new HashMap<>();
        if (!safeList(taskIds).isEmpty()) {
            // 你可以让 listTasks 支持 taskIds 精确过滤
            taskArgs.put("taskIds", taskIds);
        } else if (!safeList(deviceIds).isEmpty()) {
            taskArgs.put("deviceId", deviceIds.get(0)); // 或改成支持 deviceIds 数组
            taskArgs.put("statuses", Arrays.asList("WAITING","RUNNING","SUSPENDED"));
            taskArgs.put("limit", 200);
        }
        Map<String, Object> taskResult = (Map<String, Object>) listTasks(taskArgs);
        List<Object> tasks = taskResult == null ? new ArrayList<>() : (List<Object>) taskResult.get("tasks");
        // 3) logs
        Map<String, Object> logArgs = new HashMap<>();
        logArgs.put("timeFrom", iso(from));
        logArgs.put("timeTo", iso(now));
        logArgs.put("limit", logLimit);
        if (!safeList(deviceIds).isEmpty()) logArgs.put("deviceId", deviceIds.get(0));
        if (!safeList(taskIds).isEmpty())   logArgs.put("taskId", taskIds.get(0));
        Map<String, Object> logResult = (Map<String, Object>) queryLogs(logArgs);
        List<Object> logs = logResult == null ? new ArrayList<>() : (List<Object>) logResult.get("logs");
        // 4) configs
        Object configs = null;
        if (includeConfigs) {
            configs = getSystemConfig(new HashMap<String, Object>());
        }
        // 5) derived
        Object derived = null;
        if (includeDerived) {
            derived = deriveSignals(devices, tasks, logs);
        }
        // output
        Map<String, Object> out = new LinkedHashMap<>();
        Map<String, Object> meta = new LinkedHashMap<>();
        meta.put("snapshotId", UUID.randomUUID().toString());
        meta.put("generatedAt", iso(now));
        meta.put("timeFrom", iso(from));
        meta.put("timeTo", iso(now));
        out.put("meta", meta);
        out.put("devices", devices);
        out.put("tasks", tasks);
        out.put("logs", logs);
        out.put("configs", configs);
        out.put("derivedSignals", derived);
        return out;
    }
    private Map<String, Object> deriveSignals(List<Object> devices, List<Object> tasks, List<Object> logs) {
        Map<String, Object> d = new LinkedHashMap<>();
        List<String> flags = new ArrayList<>();
        List<String> causes = new ArrayList<>();
        List<String> bottlenecks = new ArrayList<>();
        // TODO:把你 WCS 领域规则塞这里(心跳超时、等待确认、命令无ACK、站台满、下游阻塞等)
        // 先给个示例:
        if (tasks != null && !tasks.isEmpty() && (devices == null || devices.isEmpty())) {
            flags.add("HAS_TASKS_BUT_NO_DEVICE_SNAPSHOT");
        }
        d.put("anomalyFlags", flags);
        d.put("suspectedRootCauses", causes);
        d.put("suspectedBottlenecks", bottlenecks);
        return d;
    }
    // ======= 下面这些函数,你用现有 service 实现即可 =======
    public Map<String, Object> getDeviceState(Map<String, Object> args) { /* ... */ return new HashMap<>(); }
    public Map<String, Object> listTasks(Map<String, Object> args) { /* ... */ return new HashMap<>(); }
    public Map<String, Object> queryLogs(Map<String, Object> args) { /* ... */ return new HashMap<>(); }
    public Object getSystemConfig(Map<String, Object> args) { /* ... */ return new HashMap<>(); }
    // helpers
    private int getInt(Map<String, Object> m, String k, int def) {
        Object v = m.get(k);
        if (v == null) return def;
        if (v instanceof Number) return ((Number) v).intValue();
        return Integer.parseInt(String.valueOf(v));
    }
    private boolean getBool(Map<String, Object> m, String k, boolean def) {
        Object v = m.get(k);
        if (v == null) return def;
        if (v instanceof Boolean) return (Boolean) v;
        return Boolean.parseBoolean(String.valueOf(v));
    }
    private List<String> safeList(List<String> l) { return l == null ? Collections.emptyList() : l; }
    private String iso(Date d) {
        SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
        f.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        return f.format(d);
    }
}