| src/main/java/com/zy/ai/mcp/service/WcsDataFacade.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/ai/mcp/tool/WcsMcpTools.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/ai/service/MainProcessPseudocodeService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/ai/service/impl/MainProcessPseudocodeServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/ai/timer/MakeMainProcessPseudocodeScheduler.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/resources/application.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/main/java/com/zy/ai/mcp/service/WcsDataFacade.java
@@ -18,8 +18,7 @@ Object getSystemConfig(JSONObject args); /** * ★ 聚合快照:核心诊断输入 */ Object getSystemPseudocode(JSONObject args); Object buildDiagnosisSnapshot(JSONObject args); } src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java
@@ -6,6 +6,7 @@ import com.zy.ai.entity.DeviceConfigsData; import com.zy.ai.log.AiLogAppender; import com.zy.ai.mcp.service.WcsDataFacade; import com.zy.ai.service.MainProcessPseudocodeService; import com.zy.asrs.entity.BasCrnp; import com.zy.asrs.entity.BasDevp; import com.zy.asrs.entity.BasRgv; @@ -45,6 +46,8 @@ private WrkMastService wrkMastService; @Autowired private ConfigService configService; @Autowired private MainProcessPseudocodeService mainProcessPseudocodeService; @Override public Object getCrnDeviceStatus(JSONObject args) { @@ -276,6 +279,12 @@ } @Override public Object getSystemPseudocode(JSONObject args) { boolean refresh = optBool(args, "refresh", false); return mainProcessPseudocodeService.queryMainProcessPseudocode(refresh); } @Override public Object buildDiagnosisSnapshot(JSONObject args) { String wh = mustStr(args, "warehouseCode"); List<String> crnDeviceNos = optStrList(args, "crnDeviceNos"); src/main/java/com/zy/ai/mcp/tool/WcsMcpTools.java
@@ -87,6 +87,12 @@ return wcsDataFacade.getSystemConfig(json()); } @Tool(name = "system_get_main_process_pseudocode", description = "查询当前WCS系统主流程伪代码") public Object getSystemPseudocode( @ToolParam(description = "是否强制重新生成伪代码,默认 false", required = false) Boolean refresh) { return wcsDataFacade.getSystemPseudocode(json().fluentPut("refresh", refresh)); } private JSONObject json() { return new JSONObject(); } src/main/java/com/zy/ai/service/MainProcessPseudocodeService.java
New file @@ -0,0 +1,8 @@ package com.zy.ai.service; import com.alibaba.fastjson.JSONObject; public interface MainProcessPseudocodeService { JSONObject queryMainProcessPseudocode(boolean refresh); } src/main/java/com/zy/ai/service/impl/MainProcessPseudocodeServiceImpl.java
New file @@ -0,0 +1,302 @@ package com.zy.ai.service.impl; import com.alibaba.fastjson.JSONObject; import com.zy.ai.entity.ChatCompletionRequest; import com.zy.ai.service.LlmChatService; import com.zy.ai.service.MainProcessPseudocodeService; import com.zy.common.utils.RedisUtil; import com.zy.core.News; import com.zy.core.enums.RedisKeyType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @Service("mainProcessPseudocodeService") public class MainProcessPseudocodeServiceImpl implements MainProcessPseudocodeService { private static final long SUCCESS_CACHE_SECONDS = 60 * 60 * 24; private static final long FAILURE_CACHE_SECONDS = 60 * 10; private static final String FAILURE_TEXT = "AI生成伪代码失败"; private static final String PSEUDOCODE_SYSTEM_PROMPT = """ 你现在是一名高级 Java 架构师兼伪代码转换专家,专门负责把复杂的 Java 代码转换成结构清晰、适合大模型阅读与推理的伪代码。 请严格遵守以下要求工作: 核心目标 输入是一段或多段 Java 代码。 输出是一段人类可读、逻辑清晰、尽量语言中立的伪代码。 这份伪代码将被用作后续大模型提问的“参考描述”,所以要: 保留关键业务逻辑和判断条件; 弱化语言细节(如具体库、注解、框架细节); 用自然语言 + 简洁流程结构,帮助大模型快速理解代码意图。 风格要求 使用中文描述逻辑,但可以保留少量关键英文标识(例如类名、方法名、状态枚举)以便跟代码对应。 伪代码要分层分块,尽量按: 类职责说明 重要字段 / 全局变量说明 每个公开方法 / 核心私有方法的伪代码 逻辑上使用类似: 如果 ... 则 ... 否则如果 ... 循环遍历列表 ... 调用服务/方法: ... 返回 ... 不追求严格语法,只追求易懂和准确。 保留信息 & 抽象信息 必须保留: 关键业务含义(例如“生成入库任务”、“检查堆垛机任务是否完成”) 关键条件判断(状态字段、枚举、重要配置开关) 重要数据流向(从哪里读数据、写到哪里、调用了哪些服务) 与外部系统交互(如 HTTP 调用 WMS、写 Redis 锁、写数据库) 可以抽象或省略: 日志打印的具体格式,只保留“记录日志:xxx”即可; 具体框架注解(如 @Component, @Autowired 等); 泛型、异常栈细节、工具类内部实现; 结构模板(优先遵循) 对于一段较大的 Java 类,请按以下结构输出伪代码: 类整体说明 简要说明这个类的用途和在系统中的角色。 重要字段 / 配置说明 列出关键静态变量 / 配置项 / 状态缓存,并用一行解释它们的含义。 主流程方法(例如 run()) 用有序列表或伪代码,按调用顺序描述主要步骤。 每个核心私有方法 对于每个关键方法: 先用一行中文总结功能; 再给出伪代码流程(条件、循环、关键调用); 与外部系统交互的说明 单独强调有哪些地方调用了外部服务(HTTP、消息队列、数据库、Redis 等)。 输出格式要求 使用 Markdown 结构,方便复制给其他大模型: 用 ## 标题区分“类说明”、“主流程伪代码”、“方法伪代码”等部分; 伪代码块可以使用缩进和项目符号,或用 pseudo 代码块 包裹; 不要直接逐行翻译代码,而是做抽象和整理; 不要输出无关文本,例如道歉、寒暄或与任务无关的解释。 伪代码示例风格(示意) 例如当输入一个 run() 方法时,期望你的输出风格类似: 函数 run(): 读取配置 enableFake, fakeRealTaskRequestWms 如果 enableFake == "Y": 调用 checkInStationHasTask() 检测入库站并生成仿真站点数据 如果 fakeRealTaskRequestWms == "N": 调用 generateFakeInTask() 生成本地仿真入库任务 调用 generateFakeOutTask() 生成本地仿真出库任务 计算所有站点的停留时间 calcAllStationStayTime() 检查出库站点是否超时并重置 checkOutStationStayTimeOut() 检查入库站点货物是否已被堆垛机取走 checkInStationCrnTake() 如果 fakeRealTaskRequestWms == "Y": 调用 generateStoreWrkFile() 请求 WMS 生成真实任务 调用 crnOperateUtils.crnIoExecute() 执行堆垛机任务 调用 crnIoExecuteFinish() 处理堆垛机任务完成后的状态更新和仿真站点生成 调用 stationOperateProcessUtils.stationInExecute() 执行输送站入库任务 调用 stationOperateProcessUtils.stationOutExecute() 执行输送站出库任务 调用 stationOperateProcessUtils.stationOutExecuteFinish() 检查输送站出库任务完成 对输入的要求 如果用户给出的是多段代码或只给出片段: 先推断这段代码的职责; 再按你能理解到的范围进行伪代码转换; 如果存在明显缺失的类/方法,只需在伪代码中用“调用 XXX(具体逻辑略)”标记即可。 请始终以「让后续大模型能看懂这段代码逻辑并基于伪代码进行推理和提问」为最高优先级来组织你的输出。 """; @Value("${mainProcessPlugin}") private String mainProcessPlugin; @Autowired private LlmChatService llmChatService; @Autowired private RedisUtil redisUtil; @Override public JSONObject queryMainProcessPseudocode(boolean refresh) { String cacheKey = RedisKeyType.MAIN_PROCESS_PSEUDOCODE.key; String cached = trimToNull(redisUtil.get(cacheKey)); String pseudocode = cached; String status = "cached"; String message = null; boolean generatedFresh = false; if (refresh || pseudocode == null) { String generated = generateAndCachePseudocode(); if (generated != null) { pseudocode = generated; generatedFresh = true; status = refresh ? "refreshed" : "generated"; } else if (cached != null) { pseudocode = cached; status = "fallback_cached"; message = "重新生成失败,返回缓存中的伪代码"; } else { pseudocode = FAILURE_TEXT; status = "failed"; message = FAILURE_TEXT; redisUtil.set(cacheKey, FAILURE_TEXT, FAILURE_CACHE_SECONDS); News.info(FAILURE_TEXT); } } JSONObject result = new JSONObject(); result.put("mainProcessPlugin", resolvePlugin()); result.put("mainProcessPluginClass", resolvePluginClassName()); result.put("refreshRequested", refresh); result.put("generatedFresh", generatedFresh); result.put("cacheHit", !generatedFresh && ("cached".equals(status) || "fallback_cached".equals(status))); result.put("status", status); result.put("expireSeconds", redisUtil.getExpire(cacheKey)); result.put("message", message); result.put("pseudocode", pseudocode); return result; } private String generateAndCachePseudocode() { String code = loadSourceBundle(); if (code == null || code.isEmpty()) { return null; } List<ChatCompletionRequest.Message> messages = new ArrayList<>(); ChatCompletionRequest.Message system = new ChatCompletionRequest.Message(); system.setRole("system"); system.setContent(PSEUDOCODE_SYSTEM_PROMPT); messages.add(system); ChatCompletionRequest.Message user = new ChatCompletionRequest.Message(); user.setRole("user"); user.setContent("主流程插件类源代码:\n\n" + code); messages.add(user); try { String result = trimToNull(llmChatService.chat(messages, 0.2, 2048)); if (result == null) { return null; } redisUtil.set(RedisKeyType.MAIN_PROCESS_PSEUDOCODE.key, result, SUCCESS_CACHE_SECONDS); News.info("AI生成伪代码成功"); return result; } catch (Exception ignore) { return null; } } private String loadSourceBundle() { StringBuilder code = new StringBuilder(); appendSource(code, resolvePluginClassName()); appendSource(code, "com.zy.core.utils.CrnOperateProcessUtils"); appendSource(code, "com.zy.core.utils.StationOperateProcessUtils"); return code.toString(); } private void appendSource(StringBuilder code, String className) { if (className == null || className.trim().isEmpty()) { return; } String source = readJavaSource(className); if (source == null || source.isEmpty()) { return; } if (code.length() > 0) { code.append("\n\n"); } code.append("// ===== ").append(className).append(" =====\n"); code.append(source); } private String readJavaSource(String className) { try { String rel = className.replace('.', '/') + ".java"; Path path = Paths.get(System.getProperty("user.dir"), "src", "main", "java", rel); if (!Files.exists(path)) { return null; } return Files.readString(path, StandardCharsets.UTF_8); } catch (Exception ignore) { return null; } } private String resolvePlugin() { String plugin = trimToNull(mainProcessPlugin); return plugin == null ? "NormalProcess" : plugin; } private String resolvePluginClassName() { String plugin = resolvePlugin(); if (plugin.contains(".")) { return plugin; } return "com.zy.core.plugin." + plugin; } private String trimToNull(Object value) { if (value == null) { return null; } String text = String.valueOf(value).trim(); return text.isEmpty() ? null : text; } } src/main/java/com/zy/ai/timer/MakeMainProcessPseudocodeScheduler.java
@@ -1,239 +1,22 @@ package com.zy.ai.timer; import com.zy.ai.entity.ChatCompletionRequest; import com.zy.ai.service.LlmChatService; import com.zy.common.utils.RedisUtil; import com.zy.core.News; import com.zy.core.enums.RedisKeyType; import com.zy.ai.service.MainProcessPseudocodeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; @Component public class MakeMainProcessPseudocodeScheduler { @Value("${mainProcessPlugin}") private String mainProcessPlugin; @Autowired private LlmChatService llmChatService; @Autowired private RedisUtil redisUtil; private MainProcessPseudocodeService mainProcessPseudocodeService; @Scheduled(cron = "0/10 * * * * ? ") public void refreshPseudocodeDaily() { try { initMainProcessPseudocode(); mainProcessPseudocodeService.queryMainProcessPseudocode(false); } catch (Exception e) { e.printStackTrace(); } } private void initMainProcessPseudocode(){ Object object = redisUtil.get(RedisKeyType.MAIN_PROCESS_PSEUDOCODE.key); if (object != null) { return; } String plugin = mainProcessPlugin; if (plugin == null) plugin = "NormalProcess"; String className = plugin.contains(".") ? plugin : "com.zy.core.plugin." + plugin; String code = null; try { String rel = className.replace('.', '/') + ".java"; java.nio.file.Path p = Paths.get(System.getProperty("user.dir"), "src", "main", "java", rel); if (Files.exists(p)) { code = new String(Files.readAllBytes(p), StandardCharsets.UTF_8); } } catch (Exception ignore) {} String crnOperateProcessUtilsCode = null; try { String utilsClassName = "com.zy.core.utils.CrnOperateProcessUtils"; String rel = utilsClassName.replace('.', '/') + ".java"; java.nio.file.Path p = Paths.get(System.getProperty("user.dir"), "src", "main", "java", rel); if (Files.exists(p)) { crnOperateProcessUtilsCode = new String(Files.readAllBytes(p), StandardCharsets.UTF_8); code += crnOperateProcessUtilsCode; } } catch (Exception ignore) {} String StationOperateProcessUtilsCode = null; try { String utilsClassName = "com.zy.core.utils.StationOperateProcessUtils"; String rel = utilsClassName.replace('.', '/') + ".java"; java.nio.file.Path p = Paths.get(System.getProperty("user.dir"), "src", "main", "java", rel); if (Files.exists(p)) { StationOperateProcessUtilsCode = new String(Files.readAllBytes(p), StandardCharsets.UTF_8); code += StationOperateProcessUtilsCode; } } catch (Exception ignore) {} String result = null; if (code != null && !code.isEmpty()) { List<ChatCompletionRequest.Message> messages = new java.util.ArrayList<>(); ChatCompletionRequest.Message system = new ChatCompletionRequest.Message(); system.setRole("system"); system.setContent("你现在是一名高级 Java 架构师兼伪代码转换专家,专门负责把复杂的 Java 代码转换成结构清晰、适合大模型阅读与推理的伪代码。\n" + "\n" + "请严格遵守以下要求工作:\n" + "\n" + "核心目标\n" + "\n" + "输入是一段或多段 Java 代码。\n" + "\n" + "输出是一段人类可读、逻辑清晰、尽量语言中立的伪代码。\n" + "\n" + "这份伪代码将被用作后续大模型提问的“参考描述”,所以要:\n" + "\n" + "保留关键业务逻辑和判断条件;\n" + "\n" + "弱化语言细节(如具体库、注解、框架细节);\n" + "\n" + "用自然语言 + 简洁流程结构,帮助大模型快速理解代码意图。\n" + "\n" + "风格要求\n" + "\n" + "使用中文描述逻辑,但可以保留少量关键英文标识(例如类名、方法名、状态枚举)以便跟代码对应。\n" + "\n" + "伪代码要分层分块,尽量按:\n" + "\n" + "类职责说明\n" + "\n" + "重要字段 / 全局变量说明\n" + "\n" + "每个公开方法 / 核心私有方法的伪代码\n" + "\n" + "逻辑上使用类似:\n" + "\n" + "如果 ... 则 ...\n" + "\n" + "否则如果 ...\n" + "\n" + "循环遍历列表 ...\n" + "\n" + "调用服务/方法: ...\n" + "\n" + "返回 ...\n" + "\n" + "不追求严格语法,只追求易懂和准确。\n" + "\n" + "保留信息 & 抽象信息\n" + "\n" + "必须保留:\n" + "\n" + "关键业务含义(例如“生成入库任务”、“检查堆垛机任务是否完成”)\n" + "\n" + "关键条件判断(状态字段、枚举、重要配置开关)\n" + "\n" + "重要数据流向(从哪里读数据、写到哪里、调用了哪些服务)\n" + "\n" + "与外部系统交互(如 HTTP 调用 WMS、写 Redis 锁、写数据库)\n" + "\n" + "可以抽象或省略:\n" + "\n" + "日志打印的具体格式,只保留“记录日志:xxx”即可;\n" + "\n" + "具体框架注解(如 @Component, @Autowired 等);\n" + "\n" + "泛型、异常栈细节、工具类内部实现;\n" + "\n" + "结构模板(优先遵循)\n" + "\n" + "对于一段较大的 Java 类,请按以下结构输出伪代码:\n" + "\n" + "类整体说明\n" + "\n" + "简要说明这个类的用途和在系统中的角色。\n" + "\n" + "重要字段 / 配置说明\n" + "\n" + "列出关键静态变量 / 配置项 / 状态缓存,并用一行解释它们的含义。\n" + "\n" + "主流程方法(例如 run())\n" + "\n" + "用有序列表或伪代码,按调用顺序描述主要步骤。\n" + "\n" + "每个核心私有方法\n" + "\n" + "对于每个关键方法:\n" + "\n" + "先用一行中文总结功能;\n" + "\n" + "再给出伪代码流程(条件、循环、关键调用);\n" + "\n" + "与外部系统交互的说明\n" + "\n" + "单独强调有哪些地方调用了外部服务(HTTP、消息队列、数据库、Redis 等)。\n" + "\n" + "输出格式要求\n" + "\n" + "使用 Markdown 结构,方便复制给其他大模型:\n" + "\n" + "用 ## 标题区分“类说明”、“主流程伪代码”、“方法伪代码”等部分;\n" + "\n" + "伪代码块可以使用缩进和项目符号,或用 pseudo 代码块 包裹;\n" + "\n" + "不要直接逐行翻译代码,而是做抽象和整理;\n" + "\n" + "不要输出无关文本,例如道歉、寒暄或与任务无关的解释。\n" + "\n" + "伪代码示例风格(示意)\n" + "\n" + "例如当输入一个 run() 方法时,期望你的输出风格类似:\n" + "\n" + "函数 run():\n" + " 读取配置 enableFake, fakeRealTaskRequestWms\n" + " 如果 enableFake == \"Y\":\n" + " 调用 checkInStationHasTask() 检测入库站并生成仿真站点数据\n" + " 如果 fakeRealTaskRequestWms == \"N\":\n" + " 调用 generateFakeInTask() 生成本地仿真入库任务\n" + " 调用 generateFakeOutTask() 生成本地仿真出库任务\n" + " 计算所有站点的停留时间 calcAllStationStayTime()\n" + " 检查出库站点是否超时并重置 checkOutStationStayTimeOut()\n" + " 检查入库站点货物是否已被堆垛机取走 checkInStationCrnTake()\n" + " 如果 fakeRealTaskRequestWms == \"Y\":\n" + " 调用 generateStoreWrkFile() 请求 WMS 生成真实任务\n" + " 调用 crnOperateUtils.crnIoExecute() 执行堆垛机任务\n" + " 调用 crnIoExecuteFinish() 处理堆垛机任务完成后的状态更新和仿真站点生成\n" + " 调用 stationOperateProcessUtils.stationInExecute() 执行输送站入库任务\n" + " 调用 stationOperateProcessUtils.stationOutExecute() 执行输送站出库任务\n" + " 调用 stationOperateProcessUtils.stationOutExecuteFinish() 检查输送站出库任务完成\n" + "\n" + "\n" + "对输入的要求\n" + "\n" + "如果用户给出的是多段代码或只给出片段:\n" + "\n" + "先推断这段代码的职责;\n" + "\n" + "再按你能理解到的范围进行伪代码转换;\n" + "\n" + "如果存在明显缺失的类/方法,只需在伪代码中用“调用 XXX(具体逻辑略)”标记即可。\n" + "\n" + "请始终以「让后续大模型能看懂这段代码逻辑并基于伪代码进行推理和提问」为最高优先级来组织你的输出。"); messages.add(system); ChatCompletionRequest.Message user = new ChatCompletionRequest.Message(); user.setRole("user"); user.setContent("主流程插件类源代码:\n\n" + code); messages.add(user); try { result = llmChatService.chat(messages, 0.2, 2048); } catch (Exception ignore) {} } if (result == null) { redisUtil.set(RedisKeyType.MAIN_PROCESS_PSEUDOCODE.key, "AI生成伪代码失败", 60 * 10); News.info("AI生成伪代码失败"); }else { redisUtil.set(RedisKeyType.MAIN_PROCESS_PSEUDOCODE.key, result, 60 * 60 * 24); News.info("AI生成伪代码成功"); } } } src/main/resources/application.yml
@@ -63,7 +63,7 @@ sse-message-endpoint: /ai/mcp/message streamable-http: mcp-endpoint: /ai/mcp instructions: 提供 WCS 设备状态、任务、日志和配置查询能力 instructions: 提供 WCS 设备状态、任务、日志、配置和主流程伪代码查询能力 annotation-scanner: enabled: false capabilities: