| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| New file |
| | |
| | | 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()); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.ai.mcp.dto; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | |
| | | public interface McpToolHandler { |
| | | Object handle(JSONObject arguments) throws Exception; |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |