From 155acd6f08f42f762eaf2c8cf081c8072e7688d1 Mon Sep 17 00:00:00 2001 From: qiufeng Date: Thu, 30 Jul 2026 19:55:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 250 +++++++++--------- .../cn/com/mfish/ai/agent/MfishAssistant.java | 12 +- .../src/main/resources/skills/code-build.md | 109 ++++++++ .../main/resources/skills/page-navigate.md | 89 +++++++ .../common/oauth/service/SsoMenuService.java | 5 +- .../oauth/controller/SsoMenuController.java | 9 +- .../service/impl/SsoMenuServiceImpl.java | 18 +- 7 files changed, 354 insertions(+), 138 deletions(-) create mode 100644 mf-common/mf-common-ai/src/main/resources/skills/code-build.md create mode 100644 mf-common/mf-common-ai/src/main/resources/skills/page-navigate.md diff --git a/README.md b/README.md index da54d757..8d6f3808 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,6 @@ --chart-series-1: #3C2ECA; --chart-series-2: #A9AEFF; --chart-series-3: #6F6FFF; - --chart-series-4: #22A5F7; - --chart-other: #D3D4DA; --accent: #27D2BF; --accent-soft: #EAFBF8; --accent-text: #0F766E; @@ -65,7 +63,6 @@ --spacer-20: 20px; --spacer-24: 24px; --font-sans: "SF Pro Text", "PingFang SC", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; - --font-metric: "Inter", "SF Pro Text", "PingFang SC", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace; --weight-regular: 400; --weight-medium: 500; @@ -86,136 +83,135 @@ --brand-soft: #1A1759; --brand-soft-strong: #3C2ECA; --brand-text: #CFD8FF; - --chart-series-1: #4B3FE3; - --accent-soft: #123F3C; - --accent-text: #CCFBF1; -} -.widget { - color: var(--text); - background: transparent; - font: var(--weight-regular) var(--text-body) var(--font-sans); -} -.arch-card { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-card); - padding: var(--spacer-20); -} -.arch-title { - font: var(--weight-medium) var(--text-title) var(--font-sans); - color: var(--text); - margin: 0 0 var(--spacer-4) 0; -} -.arch-sub { - font: var(--weight-regular) var(--text-caption) var(--font-sans); - color: var(--text-muted); - margin: 0 0 var(--spacer-16) 0; } +.widget { color: var(--text); background: white; font: var(--weight-regular) var(--text-body) var(--font-sans); } .t { fill: var(--text); font: var(--weight-regular) var(--text-body) var(--font-sans); } .th { fill: var(--text); font: var(--weight-medium) var(--text-body) var(--font-sans); } .ts { fill: var(--text-muted); font: var(--weight-regular) var(--text-caption) var(--font-sans); } -.tc { fill: var(--text); font: var(--weight-medium) var(--text-code) var(--font-mono); } +.tcode { fill: var(--text-muted); font: var(--weight-regular) var(--text-code) var(--font-mono); } .arr { stroke: var(--text-muted); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; fill: none; } -.arr-brand { stroke: var(--brand); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; fill: none; } -.boundary { fill: var(--surface-muted); stroke: var(--border); stroke-dasharray: 4 4; } -.boundary-brand { fill: var(--brand-soft); stroke: var(--brand); stroke-dasharray: 4 4; } -.node { fill: var(--surface); stroke: var(--border); } -.node-brand { fill: var(--surface); stroke: var(--brand); } -.legend-row { display: flex; align-items: center; gap: var(--spacer-8); font: var(--weight-regular) var(--text-caption) var(--font-sans); color: var(--text-muted); } -.legend-swatch { width: 14px; height: 10px; border-radius: 3px; flex-shrink: 0; } -.legend-line { width: 18px; height: 2px; flex-shrink: 0; } +.arr-brand { stroke: var(--brand); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; fill: none; } +.boundary { fill: none; stroke: var(--border); stroke-width: 1.2; stroke-dasharray: 5 4; rx: 12; } +.boundary-brand { fill: none; stroke: var(--brand); stroke-width: 1.4; stroke-dasharray: 5 4; rx: 12; opacity: 0.7; } +.chip { fill: var(--surface-muted); stroke: var(--border); stroke-width: 1; rx: 6; } +.chip-brand { fill: var(--brand-soft); stroke: var(--brand); stroke-width: 1; rx: 6; } +rect.c-neutral { fill: var(--surface); stroke: var(--border); stroke-width: 1.2; rx: 8; } +rect.c-brand { fill: var(--brand-soft); stroke: var(--brand); stroke-width: 1.6; rx: 8; } +rect.c-accent { fill: var(--accent-soft); stroke: var(--accent); stroke-width: 1.2; rx: 8; } +.c-brand ~ .th, .c-brand ~ .t { fill: var(--brand-text); } -
-

mfish-nocode AI 能力架构

-

三支柱编排:Planner 规划 · Capability Engine 能力聚合 · Memory+LLM 上下文路由

- - - - - - - - - - - - 入口层 · Entry Points - - /router - 路由聊天 - - /assist/chat - 垂直助手 - - /agent/chat - 自主规划 - - - - - AgentRuntime 编排器 - Plan → Execute → Emit - - - - - - - Planner 柱 · 规划 - - Planner 任务规划 - - Executor 步骤执行 - - EventBus 事件流 - - - - - Capability Engine · 能力聚合 - - ToolCapability - - McpCapability - - SkillCapability - - WorkflowCapability - - - Memory + LLM 柱 · 上下文 - - ChatMemory - 会话窗口策略 - - LlmModelRouter - 多租户路由 - - - - - 外部系统 · External Systems - - 微服务 Feign+HTTP - - MCP 服务器 - - skills/*.md - - Flowable 工作流 - - - - 核心引擎 - - 支撑层 - - 主调用链 - - 编排流向 - - mf-common-ai · mf-ai - + +
+ + + + + + + + + + +AI 服务架构图(重构后) + + + + + 入口层 Entry + + Chat 入口 + AiController + + Router 路由 + RouterController + + Agent 智能体 + AgentController + + + + + + + + Assistant 层(模板方法) + + BaseAssistant · 按租户实时构建 ChatClient + MfishAssistant / SysAssistant / AgentAssistant … + + + + + + + + 运行时边界 Runtime(新) + + ToolRuntime + 工具聚合·上下文·提示词·前端通道 + resolveServiceIds / buildToolContext + + AgentOrchestrator + Agent 模式编排(规划+执行) + Planner · Executor + + + + + + + + 工具聚合层 + + ApiToolEngine + 按 serviceId 合并工具 + register / replace + + CapabilityEngine + 四子引擎异步注册 + Tool / MCP / Skill / Workflow + + + + + + + + 容错层 + + FaultTolerantToolCallingManager + 无效工具容错 + 按 Skill toolOrder 排序(setter 注入 ToolOrderProvider) + + + + + + + + 横切层 + + 模型路由 + LlmModelRouter + + 会话记忆 + ConversationMemoryStore + + 前端双通道 + FrontendActionHolder + + + + + 新边界(ToolRuntime) + + 标准模块 + + 容错/排序 + + 主数据流 + + 调用/依赖 +
### 智能网关路由 diff --git a/mf-business/mf-ai/src/main/java/cn/com/mfish/ai/agent/MfishAssistant.java b/mf-business/mf-ai/src/main/java/cn/com/mfish/ai/agent/MfishAssistant.java index 38b5d55d..5d204b3f 100644 --- a/mf-business/mf-ai/src/main/java/cn/com/mfish/ai/agent/MfishAssistant.java +++ b/mf-business/mf-ai/src/main/java/cn/com/mfish/ai/agent/MfishAssistant.java @@ -58,7 +58,7 @@ public class MfishAssistant extends BaseAssistant { """ ## 工具使用与交互规则 你可以使用系统提供的工具来帮助用户完成操作。工具分为四类: - - skill 开头的工具:平台领域知识包(如 skill.code-gen-guide 代码生成指导、skill.leave-apply 请假申请指导), + - skill 开头的工具:平台领域知识包(如 skill.code-build 代码生成指导、skill.leave-apply 请假申请指导), 调用后会返回专业操作指南,指南中包含完整的多步操作流程(含前端操作步骤)。 - frontend. 开头的工具:前端操作工具(如 frontend.navigate 路由跳转、frontend.refresh 刷新页面), 用于触发前端 UI 交互。当 skill 指南中包含前端操作步骤时,必须实际调用这些工具。 @@ -69,8 +69,9 @@ public class MfishAssistant extends BaseAssistant { 1. 先分析需求:理解用户想做什么,判断属于哪个领域(代码生成?工作流?权限?请假?) 2. 【强制】优先调用对应 skill 工具: - 用户要"请假/年假/事假/病假"→ 先调用 skill.leave-apply 获取请假流程指南 - - 用户要"创建代码/生成代码"→ 先调用 skill.code-gen-guide 获取代码生成流程指南 + - 用户要"创建代码/生成代码/为表XX生成代码"→ 先调用 skill.code-build 获取代码生成流程指南 - 用户要"工作流/审批流"→ 先调用 skill.workflow-guide 获取工作流指南 + - 用户要"打开/跳转/进入XX页面"→ 先调用 skill.page-navigate 获取页面导航指南 3. 【最关键】调用 skill 后必须继续执行指南步骤: - skill 工具返回的是操作指南,不是最终答案!你尚未完成任何操作! - 获取指南后,必须立即按指南中的步骤顺序,逐个调用对应的工具(包括 frontend.navigate、 @@ -90,9 +91,12 @@ public class MfishAssistant extends BaseAssistant { 调用 demoLeaveApply.submit → 等待返回 → 调用 frontend.refresh → 等待返回 → 汇总反馈。 6. 【关键】前端操作不得跳过:当 skill 指南中包含 frontend.navigate 或 frontend.refresh 步骤时, 必须实际调用对应的 frontend 工具。这些操作会触发前端页面跳转和数据刷新,是完整业务流程的一部分。 - 7. 精确选择工具:仔细阅读工具名和描述,不要把"代码生成"工具和"自助API"工具混淆。 + 7. 【强制】禁止编造路由地址:当需要前端跳转时,必须通过 menu.queryRoutePaths 工具查询真实路由地址, + 严禁根据常识、训练数据或 skill 示例猜测路由地址(如 /screen、/user、/demo 等)。 + frontend.navigate 的 target 参数必须来自 menu.queryRoutePaths 的返回结果。 + 8. 精确选择工具:仔细阅读工具名和描述,不要把"代码生成"工具和"自助API"工具混淆。 工具名前缀代表所属服务:sys=系统服务、oauth=认证服务、nocode=低代码服务、skill=技能包、frontend=前端操作 - 8. 工具结果反馈:所有工具调用完成后,将最终结果用通俗易懂的方式告诉用户 + 9. 工具结果反馈:所有工具调用完成后,将最终结果用通俗易懂的方式告诉用户 """; } diff --git a/mf-common/mf-common-ai/src/main/resources/skills/code-build.md b/mf-common/mf-common-ai/src/main/resources/skills/code-build.md new file mode 100644 index 00000000..ce61469b --- /dev/null +++ b/mf-common/mf-common-ai/src/main/resources/skills/code-build.md @@ -0,0 +1,109 @@ +--- +name: code-build +type: guide +requires: mf-sys,frontend +toolOrder: dbConnect.query,frontend.navigate,codeBuild.add,frontend.refresh +deferredTools: frontend.refresh +description: 指导 AI 完成代码生成的完整流程:查询数据库连接、跳转代码生成页面、创建代码生成记录、刷新列表 +--- + +【执行指令 - 立即执行】这是操作指南,不是最终答案!你尚未执行任何操作! +获取本指南后,必须立即按"执行步骤"中的顺序,逐个调用对应工具(dbConnect.query → frontend.navigate → codeBuild.add → frontend.refresh)。 +严禁仅返回指南内容而不执行!严禁出现"我无法调用"、"请您手动操作"等表述! +工具已就绪,你必须实际调用它们完成代码生成记录的创建。 + +你是摸鱼低代码平台的代码生成助手。当用户表达"生成代码"、"代码生成"、"创建代码生成记录"、"为表XX生成代码"等意图时,按以下流程操作。 + +## 前置信息收集 + +在调用 codeBuild.add 前,需要确认以下信息。**能从用户输入合理推断的字段无需追问**,只有真正缺失且无法推断的字段才需要向用户追问: + +| 信息 | 字段 | 说明 | 推断规则 | +|------|------|------|----------| +| 数据库表名 | tableName | 要生成代码的数据库表名 | 用户必须提供(如"sys_user"),无法推断 | +| 实体类名 | entityName | Java 实体类名 | 不传则系统自动用表名驼峰化(如 sys_user → SysUser),用户未明确指定时不填 | +| 接口路径前缀 | apiPrefix | REST API 路径前缀 | 不传则系统使用包名最底层(如 cn.com.mfish.sys → sys),用户未明确指定时不填 | +| 项目包名 | packageName | 生成代码的项目包名 | 不传则使用默认包名 cn.com.mfish.sys,用户未明确指定时不填 | +| 表描述 | tableComment | 表的中文描述 | 不传则系统自动从数据库表注释获取,用户未明确指定时不填 | +| 数据库连接ID | connectId | 数据库连接的唯一ID | 通过步骤1查询获取,无需用户提供 | + +**推断示例**: +- 用户:"为表 sys_user 生成代码" + - tableName=sys_user,其他字段不填(使用系统默认值),connectId 通过查询获取 + - 所有字段均可推断或使用默认值,无需追问,直接执行步骤1 + +- 用户:"为表 sys_user 生成代码,实体类叫 SysUser,接口前缀 sys" + - tableName=sys_user,entityName=SysUser,apiPrefix=sys + - connectId 通过查询获取,直接执行步骤1 + +**仅当以下信息缺失时才追问**: +- 用户完全没说要为哪张表生成代码(tableName 是必填项) + +## 执行步骤(严格按顺序,每次只调用一个工具,等待返回后再调用下一个) + +### 步骤 1:查询数据库连接列表 +- 工具:`dbConnect.query`(GET /dbConnect) +- 无需参数(查询当前租户下所有数据库连接) +- 从返回结果中取第一条记录的 `id` 字段作为 connectId +- 返回结果结构:`data.list[0].id` 或 `data[0].id`(视分页结构而定) +- 如果返回为空,告知用户需要先配置数据库连接 + +### 步骤 2:路由到代码生成页面 +- 工具:`frontend.navigate` +- target:`/tools/code-build` +- description:`正在为您打开代码生成页面...` +- 必须在创建记录前调用此工具,让用户看到代码生成页面 + +### 步骤 3:创建代码生成记录 +- 工具:`codeBuild.add`(POST /codeBuild) +- body 包含: + - `connectId`:步骤 1 获取的数据库连接 ID + - `tableName`:用户指定的表名(必填) + - `entityName`:用户指定的实体类名(可选,不填则系统自动驼峰化) + - `apiPrefix`:用户指定的接口前缀(可选,不填则系统使用包名) + - `packageName`:用户指定的项目包名(可选,不填则使用默认包名) + - `tableComment`:用户指定的表描述(可选,不填则系统从数据库获取) +- 从返回结果中取出 `data.id`,这是代码生成记录的 ID + +### 步骤 4:刷新页面 +- 工具:`frontend.refresh` +- description:`代码生成记录已创建,正在刷新列表...` +- 创建成功后调用此工具刷新代码生成列表,让用户看到最新记录 + +### 步骤 5:反馈结果 +向用户告知: +- 代码生成记录已创建成功 +- 记录 ID +- 使用的数据库表名 +- 可选操作:查看代码(codeBuild.query)、下载代码(codeBuild.downloadCode)、保存到本地(codeBuild.saveLocal)、生成菜单(codeBuild.createMenu) + +## 可选操作 + +### 查看生成的代码 +- 工具:`codeBuild.query`(GET /codeBuild/view/{id}) +- path 参数 id 为步骤 3 返回的记录 ID +- 返回生成的代码文件列表 + +### 下载生成的代码 +- 工具:`codeBuild.downloadCode`(GET /codeBuild/download/{id}) +- 下载为 ZIP 压缩包 + +### 保存代码到本地 IDE +- 工具:`codeBuild.saveLocal`(GET /codeBuild/saveLocal/{id}) +- 将生成的代码保存到本地项目目录 + +### 创建菜单 +- 工具:`codeBuild.createMenu`(POST /codeBuild/menu) +- 为生成的代码创建对应的系统菜单 + +## 禁止行为 + +- ❌ 不得仅返回本指南内容而不执行任何工具调用 +- ❌ 不得跳过 dbConnect.query(步骤1)直接调用 codeBuild.add(需要真实的 connectId) +- ❌ 不得跳过 frontend.navigate(步骤2)直接创建记录 +- ❌ 不得跳过 frontend.refresh(步骤4)不刷新页面 +- ❌ 不得在 codeBuild.add 完成前调用 frontend.refresh +- ❌ 不得编造 connectId(必须从 dbConnect.query 返回结果中获取) +- ❌ 不得编造 tableName(必须由用户提供) +- ❌ 不得出现"我无法调用"、"请您手动操作"等虚假执行表述 +- ❌ 不得一次性发起多个工具调用(必须串行:查询连接 → 跳转页面 → 创建记录 → 刷新) diff --git a/mf-common/mf-common-ai/src/main/resources/skills/page-navigate.md b/mf-common/mf-common-ai/src/main/resources/skills/page-navigate.md new file mode 100644 index 00000000..085320ea --- /dev/null +++ b/mf-common/mf-common-ai/src/main/resources/skills/page-navigate.md @@ -0,0 +1,89 @@ +--- +name: page-navigate +type: guide +requires: mf-oauth,frontend +toolOrder: menu.queryRoutePaths,frontend.navigate +description: 指导 AI 根据用户意图打开平台页面。分析用户输入生成多个同义词关键词查询路由,匹配后跳转 +--- + +【执行指令 - 立即执行】这是操作指南,不是最终答案!你尚未执行任何操作! +获取本指南后,必须立即按"执行步骤"中的顺序,逐个调用对应工具(menu.queryRoutePaths → frontend.navigate)。 +严禁仅返回指南内容而不执行! + +你是摸鱼低代码平台的页面导航助手。当用户表达"打开XX页面"、"跳转到XX"、"进入XX"等导航意图时,按以下流程操作。 + +## 执行步骤(严格按顺序,每次只调用一个工具,等待返回后再调用下一个) + +### 步骤 1:分析用户意图,生成多个关键词查询路由 +- 工具:`menu.queryRoutePaths`(GET /menu/routePaths) +- **关键**:不要只用一个关键词查询,要分析用户输入,生成多个可能匹配的关键词(含同义词、近义词、英文词),用英文逗号拼接传入 keyword 参数 +- 后端支持逗号分隔的多关键词 OR 匹配,任一关键词命中菜单名称即返回 +- 返回结果为匹配的路由列表,每项包含: + - `menuName`:菜单名称(如"自助大屏") + - `routePath`:完整路由地址(如 `/mf-screen`) + +**关键词生成规则**: +1. **原词提取**:从用户输入中直接提取关键词(如"打开自助大屏"→"大屏") +2. **同义词扩展**:补充同义词(如"大屏"→补"可视化"、"看板") +3. **英文扩展**:补充可能的英文词(如"大屏"→补"screen"、"dashboard") +4. **相关词扩展**:补充相关业务词(如"调度"→补"任务"、"定时"、"job") +5. **拼接**:用英文逗号拼接所有关键词,如 `大屏,可视化,看板,screen,dashboard` + +### 步骤 2:匹配路由并跳转 +- 工具:`frontend.navigate` +- 从步骤 1 返回的路由列表中找到 menuName 最匹配的项 +- 将匹配项的 `routePath` 作为 target 参数传入 frontend.navigate +- description 参数填写操作说明(如"正在为您打开自助大屏页面...") + +## 关键词生成示例 + +| 用户输入 | 生成关键词 | 说明 | +|---------|-----------|------| +| 打开自助大屏 | `大屏,可视化,看板,screen,dashboard` | 原词+同义词+英文 | +| 跳转到请假申请 | `请假,假期,leave,申请` | 原词+同义词+英文 | +| 进入用户管理 | `用户,user,管理,账号` | 原词+英文+相关词 | +| 打开定时任务 | `定时,任务,调度,job,scheduler,task` | 原词+同义词+英文 | +| 跳转到工作流 | `工作流,流程,审批,workflow,flow,process` | 原词+同义词+英文 | +| 进入数据字典 | `字典,dictionary,dict,数据` | 原词+英文+相关词 | +| 打开代码生成 | `代码,生成,code,build,gen` | 原词+同义词+英文 | +| 打开菜单管理 | `菜单,menu,导航,navigation` | 原词+英文+相关词 | +| 打开角色管理 | `角色,role,权限,permission` | 原词+英文+相关词 | +| 打开数据库连接 | `数据库,连接,database,db,connect,数据源` | 原词+同义词+英文 | +| 打开AI模型配置 | `AI,模型,大模型,配置,model,llm` | 原词+同义词+英文 | + +## 匹配规则 + +1. **精确匹配**:用户说的页面名称与 menuName 完全一致 +2. **包含匹配**:用户说的关键词包含在 menuName 中 +3. **未匹配**:如果查询结果为空,用更宽泛的关键词重试一次;仍为空则告知用户未找到对应页面 + +## 推断示例 + +- 用户:"打开自助大屏" + - 生成关键词:`大屏,可视化,看板,screen,dashboard` + - 调用 menu.queryRoutePaths keyword="大屏,可视化,看板,screen,dashboard" + - 返回 [{menuName:"自助大屏", routePath:"<查询返回的真实路由>"}] + - 调用 frontend.navigate target="<查询返回的真实路由>" description="正在为您打开自助大屏页面..." + +- 用户:"跳转到请假申请" + - 生成关键词:`请假,假期,leave,申请` + - 调用 menu.queryRoutePaths keyword="请假,假期,leave,申请" + - 返回 [{menuName:"请假申请", routePath:"<查询返回的真实路由>"}] + - 调用 frontend.navigate target="<查询返回的真实路由>" description="正在为您打开请假申请页面..." + +- 用户:"进入用户管理" + - 生成关键词:`用户,user,管理,账号` + - 调用 menu.queryRoutePaths keyword="用户,user,管理,账号" + - 返回 [{menuName:"用户管理", routePath:"<查询返回的真实路由>"}] + - 调用 frontend.navigate target="<查询返回的真实路由>" description="正在为您打开用户管理页面..." + +## 禁止行为 + +- ❌ 不得跳过步骤 1(查询路由)直接调用 frontend.navigate +- ❌ 不得虚构路由路径,必须从 menu.queryRoutePaths 返回的结果中获取 +- ❌ 不得从本指南示例中复制路由地址(示例中的 `<查询返回的真实路由>` 是占位符,不是真实路由) +- ❌ 不得根据常识或训练数据猜测路由地址(如 /screen、/user、/demo 等) +- ❌ 不得仅返回指南内容而不执行任何工具调用 +- ❌ 不得一次性发起多个工具调用(必须串行:查询路由 → 等待返回 → 跳转页面) +- ❌ 不得只生成一个关键词查询(必须生成多个同义词/英文词提高匹配率) +- ❌ 不得出现"我无法调用"、"请您手动操作"等虚假执行表述 diff --git a/mf-common/mf-common-oauth/src/main/java/cn/com/mfish/common/oauth/service/SsoMenuService.java b/mf-common/mf-common-oauth/src/main/java/cn/com/mfish/common/oauth/service/SsoMenuService.java index 3cbce858..9f35d2da 100644 --- a/mf-common/mf-common-oauth/src/main/java/cn/com/mfish/common/oauth/service/SsoMenuService.java +++ b/mf-common/mf-common-oauth/src/main/java/cn/com/mfish/common/oauth/service/SsoMenuService.java @@ -28,9 +28,10 @@ public interface SsoMenuService extends IService { Result routeExist(String routePath, String parentId); /** - * 查询所有菜单的完整路由地址(子菜单路由拼接父菜单路由) + * 查询菜单的完整路由地址(子菜单路由拼接父菜单路由) * + * @param keyword 菜单名称关键词(可选,为空返回全部) * @return 路由地址列表(含菜单名称) */ - Result> queryRoutePaths(); + Result> queryRoutePaths(String keyword); } diff --git a/mf-oauth/src/main/java/cn/com/mfish/oauth/controller/SsoMenuController.java b/mf-oauth/src/main/java/cn/com/mfish/oauth/controller/SsoMenuController.java index 4d517b1d..baa9d010 100644 --- a/mf-oauth/src/main/java/cn/com/mfish/oauth/controller/SsoMenuController.java +++ b/mf-oauth/src/main/java/cn/com/mfish/oauth/controller/SsoMenuController.java @@ -153,10 +153,11 @@ public class SsoMenuController { * * @return 路由地址列表 */ - @Operation(summary = "获取所有路由地址", description = "查询所有菜单的完整路由地址(含菜单名称),子菜单路由拼接父菜单路由") + @Operation(summary = "获取路由地址", description = "查询菜单的完整路由地址(含菜单名称),子菜单路由拼接父菜单路由。支持多个关键词(逗号分隔,OR 匹配),keyword 为空返回全部。供 AI 导航工具查询可用路由使用") @GetMapping("/routePaths") - @InnerUser - public Result> queryRoutePaths() { - return ssoMenuService.queryRoutePaths(); + public Result> queryRoutePaths( + @Parameter(description = "菜单名称关键词(可选,支持逗号分隔的多关键词 OR 匹配,如 '大屏,可视化,screen',为空返回全部)", example = "大屏,可视化") + @RequestParam(required = false) String keyword) { + return ssoMenuService.queryRoutePaths(keyword); } } diff --git a/mf-oauth/src/main/java/cn/com/mfish/oauth/service/impl/SsoMenuServiceImpl.java b/mf-oauth/src/main/java/cn/com/mfish/oauth/service/impl/SsoMenuServiceImpl.java index 2962c525..dfa6cbed 100644 --- a/mf-oauth/src/main/java/cn/com/mfish/oauth/service/impl/SsoMenuServiceImpl.java +++ b/mf-oauth/src/main/java/cn/com/mfish/oauth/service/impl/SsoMenuServiceImpl.java @@ -21,6 +21,7 @@ import org.springframework.transaction.annotation.Transactional; import jakarta.annotation.Resource; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -178,7 +179,7 @@ public class SsoMenuServiceImpl extends ServiceImpl impl } @Override - public Result> queryRoutePaths() { + public Result> queryRoutePaths(String keyword) { // 查询所有目录和菜单(排除按钮 menuType=2),按钮无路由地址 List menus = baseMapper.selectList(new LambdaQueryWrapper() .lt(SsoMenu::getMenuType, 2) @@ -197,6 +198,21 @@ public class SsoMenuServiceImpl extends ServiceImpl impl routePaths.add(new MenuRouteVo(menu.getMenuName(), fullPath)); } } + // 关键词过滤:支持逗号分隔的多个关键词(OR 匹配,忽略大小写) + // 例如 keyword="大屏,可视化,screen" → 菜单名称包含任一关键词即匹配 + if (StringUtils.isNotEmpty(keyword)) { + String[] keywords = keyword.split("[,,]"); + List lowerKeywords = Arrays.stream(keywords) + .map(String::trim) + .filter(k -> !k.isEmpty()) + .map(String::toLowerCase) + .collect(Collectors.toList()); + routePaths = routePaths.stream() + .filter(vo -> vo.getMenuName() != null + && lowerKeywords.stream() + .anyMatch(kw -> vo.getMenuName().toLowerCase().contains(kw))) + .collect(Collectors.toList()); + } return Result.ok(routePaths, "路由地址查询成功"); }