From 411ff551ae7641dfc5c9331e99bf8b6e5770e2fa Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期二, 30 十二月 2025 18:05:14 +0800
Subject: [PATCH] #mcp
---
src/main/java/com/zy/ai/mcp/dto/ToolDefinition.java | 25 +
src/main/java/com/zy/ai/mcp/dto/JsonRpcResponse.java | 25 +
src/main/java/com/zy/ai/service/WcsQueryService.java | 129 ++++++++
src/main/java/com/zy/ai/mcp/dto/McpToolHandler.java | 7
src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java | 324 ++++++++++++++++++++
src/main/java/com/zy/ai/mcp/controller/McpController.java | 90 +++++
src/main/java/com/zy/ai/mcp/dto/ToolRegistry.java | 30 +
src/main/java/com/zy/ai/mcp/service/WcsDataFacade.java | 25 +
src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java | 232 ++++++++++++++
src/main/java/com/zy/ai/mcp/dto/JsonRpcError.java | 16 +
src/main/java/com/zy/ai/mcp/dto/JsonRpcRequest.java | 13
11 files changed, 916 insertions(+), 0 deletions(-)
diff --git a/src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java b/src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java
new file mode 100644
index 0000000..b15119f
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/mcp/controller/McpController.java b/src/main/java/com/zy/ai/mcp/controller/McpController.java
new file mode 100644
index 0000000..bf53c2e
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/controller/McpController.java
@@ -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());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/mcp/dto/JsonRpcError.java b/src/main/java/com/zy/ai/mcp/dto/JsonRpcError.java
new file mode 100644
index 0000000..30da47b
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/dto/JsonRpcError.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/mcp/dto/JsonRpcRequest.java b/src/main/java/com/zy/ai/mcp/dto/JsonRpcRequest.java
new file mode 100644
index 0000000..ee49c4e
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/dto/JsonRpcRequest.java
@@ -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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/mcp/dto/JsonRpcResponse.java b/src/main/java/com/zy/ai/mcp/dto/JsonRpcResponse.java
new file mode 100644
index 0000000..7759350
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/dto/JsonRpcResponse.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/mcp/dto/McpToolHandler.java b/src/main/java/com/zy/ai/mcp/dto/McpToolHandler.java
new file mode 100644
index 0000000..0671298
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/dto/McpToolHandler.java
@@ -0,0 +1,7 @@
+package com.zy.ai.mcp.dto;
+
+import com.alibaba.fastjson.JSONObject;
+
+public interface McpToolHandler {
+ Object handle(JSONObject arguments) throws Exception;
+}
diff --git a/src/main/java/com/zy/ai/mcp/dto/ToolDefinition.java b/src/main/java/com/zy/ai/mcp/dto/ToolDefinition.java
new file mode 100644
index 0000000..ead355f
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/dto/ToolDefinition.java
@@ -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;
+ }
+}
+
diff --git a/src/main/java/com/zy/ai/mcp/dto/ToolRegistry.java b/src/main/java/com/zy/ai/mcp/dto/ToolRegistry.java
new file mode 100644
index 0000000..1f44742
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/dto/ToolRegistry.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/zy/ai/mcp/service/WcsDataFacade.java b/src/main/java/com/zy/ai/mcp/service/WcsDataFacade.java
new file mode 100644
index 0000000..26d459b
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/service/WcsDataFacade.java
@@ -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);
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java b/src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java
new file mode 100644
index 0000000..4cf371b
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/service/WcsQueryService.java b/src/main/java/com/zy/ai/service/WcsQueryService.java
new file mode 100644
index 0000000..b531aab
--- /dev/null
+++ b/src/main/java/com/zy/ai/service/WcsQueryService.java
@@ -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锛歞eviceService/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);
+ }
+}
\ No newline at end of file
--
Gitblit v1.9.1