diff --git a/README.md b/README.md index 2fd14bffbaefc2559ab7c7f962ff835d26ec73b9..0d7de932c23d7530b0dd86a87821902574384f33 100644 --- a/README.md +++ b/README.md @@ -142,21 +142,22 @@ java -jar viewer-apis-service.jar --spring.config.location=application.yml 1. ✔️MCP SERVER (v1.2.0) 2. ✔️Function Call (目标版本 V1.3.0) 3. ✔️加入AI数据源(deepseek,openai,zhipu) (目标版本 V1.3.0) -4. ⏳MCP CLIENT (计划版本 V1.4.0) -5. 补充支持mybatis \ 注解 -6. 加入查询缓存 -7. 执行算子(lua,js脚本) -8. 支持MCP协议全部功能 -9. 服务器间数据拷贝 -10. 定时任务 -11. 接口认证 -12. 优化导出 -13. 结合jdk25+springboot4优化 \ -14. spring native -15. AI Agent能力 -16. 响应式接口 -17. 并行查询 -18. 在管理端使用自然语言辅助生成查询 +4. ✔️MCP CLIENT (计划版本 V1.4.0) +5. ⏳添加图像,音频,视频类多模态AI模型的支持 +6. ⏳补充支持mybatis \ 注解(支持resultMap中的id,result,association,collection标签;result标签中property,column,javaType;collection中的columnPrefix属性) +7. 加入查询缓存 +8. 执行算子(lua,js脚本) +9. 支持MCP协议全部功能 +10. 服务器间数据拷贝 +11. 定时任务 +12. 接口认证 +13. 优化导出 +14. 结合jdk25+springboot4优化 \ +15. spring native +16. AI Agent能力 +17. 响应式接口 +18. 并行查询 +19. 在管理端使用自然语言辅助生成查询 ## 🤝 参与贡献 diff --git a/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/entity/McpBotInfo.java b/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/entity/McpBotInfo.java index d549f6b54faf9f9e53066815db602a884ac0f85f..333032f4849aa59e81f7f54e82bb91e74aa12c37 100644 --- a/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/entity/McpBotInfo.java +++ b/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/entity/McpBotInfo.java @@ -1,5 +1,7 @@ package xyz.thoughtset.viewer.ai.mcp.client.entity; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.client.McpClient; @@ -22,6 +24,7 @@ public class McpBotInfo extends BaseMeta { protected Integer typeCode = 11;//SSE protected String version; protected String serverTitle; + @TableField(whereStrategy = FieldStrategy.NOT_EMPTY) protected String baseUrl; protected String sseEndpoint; protected Integer requestTimeout; @@ -48,7 +51,9 @@ public class McpBotInfo extends BaseMeta { .sseEndpoint(sseEndpoint) .connectTimeout(Duration.ofSeconds(this.connectTimeout!= null ? this.connectTimeout : 10)) .clientBuilder(HttpClient.newBuilder()) - .objectMapper(objectMapper); + .objectMapper(objectMapper) +// .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) + ; HttpClientSseClientTransport transport = transportBuilder.build(); McpSchema.Implementation clientInfo = new McpSchema.Implementation( this.title, diff --git a/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/entity/McpClientLink.java b/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/entity/McpClientLink.java index aac8c607a7c74b0cc01165bde1994f8a70401a5c..5722885ae07c5031dd0dca981d7930b1c6599379 100644 --- a/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/entity/McpClientLink.java +++ b/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/entity/McpClientLink.java @@ -45,6 +45,10 @@ public record McpClientLink(McpBotInfo mcpBotInfo, mcpSyncClient.close(); } + public void testLink() throws Exception{ + mcpSyncClient.ping(); + } + public static final class Builder { diff --git a/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/factory/McpBotFactory.java b/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/factory/McpBotFactory.java index bf1d03be919e45742a3789481068239f451410a9..8bcf381f2d319bb82b733b0510cd4ba8b8262d26 100644 --- a/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/factory/McpBotFactory.java +++ b/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/factory/McpBotFactory.java @@ -14,6 +14,7 @@ import xyz.thoughtset.viewer.ai.mcp.client.entity.McpClientLink; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; @Component @@ -30,9 +31,10 @@ public class McpBotFactory implements DisposableBean { } } - public void createMcpServer(McpBotInfo clientInfo) { + public McpClientLink createMcpServer(McpBotInfo clientInfo) { McpClientLink link = clientInfo.buildClientLink(objectMapper); MCP_CLIENT_MAP.put(clientInfo.getId(), link); + return link; } @Override @@ -41,9 +43,8 @@ public class McpBotFactory implements DisposableBean { } public ToolCallback[] clientsTools(List serverIds) { - var array = MCP_CLIENT_MAP.entrySet().stream() - .filter(entry -> serverIds == null || serverIds.contains(entry.getKey())) - .map(Map.Entry::getValue) + var array = serverIds.parallelStream() + .map(this::findAndCheck) .flatMap(mcpLink -> mcpLink.mcpTools() .stream() .filter(tool -> mcpLink.defaultToolFilter().test(mcpLink.mcpBotInfo(), tool)) @@ -54,6 +55,19 @@ public class McpBotFactory implements DisposableBean { return array; } + + public List clientsToolNames(String serverId) { + McpClientLink link = findAndCheck(serverId); + List toolNames = link.mcpTools().stream() + .map(tool -> tool.name()) + .collect(Collectors.toList()); + return toolNames; + } + public List clientTools(String serverId) { + McpClientLink link = findAndCheck(serverId); + return link.mcpTools(); + } + private void validateToolCallbacks(ToolCallback[] toolCallbacks) { List duplicateToolNames = ToolUtils.getDuplicateToolNames(toolCallbacks); if (!duplicateToolNames.isEmpty()) { @@ -71,4 +85,17 @@ public class McpBotFactory implements DisposableBean { link.close(); } } + + //todo :临时处理sse断联问题 + private McpClientLink findAndCheck(String serverId){ + McpClientLink link = MCP_CLIENT_MAP.get(serverId); + try { + link.testLink(); + }catch (Exception e){ + link = createMcpServer(link.mcpBotInfo()); + } + return link; + } + + } diff --git a/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/factory/McpToolCallback.java b/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/factory/McpToolCallback.java index 597fb9f32289756d0e7ec4cbd07a5c6b145d4113..c930673b92814d50ecb65039f75466f04ebf3f0a 100644 --- a/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/factory/McpToolCallback.java +++ b/ai/viewer-ai-mcp-client/src/main/java/xyz/thoughtset/viewer/ai/mcp/client/factory/McpToolCallback.java @@ -29,13 +29,14 @@ import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.definition.DefaultToolDefinition; import org.springframework.ai.tool.definition.ToolDefinition; import org.springframework.ai.tool.execution.ToolExecutionException; +import org.springframework.ai.tool.metadata.ToolMetadata; import xyz.thoughtset.viewer.ai.mcp.client.entity.McpClientLink; import java.util.Map; @Slf4j public class McpToolCallback implements ToolCallback { - + private final ToolDefinition toolDefinition; private final McpClientLink mcpClient; private final String toolName; private final Tool tool; @@ -44,17 +45,23 @@ public class McpToolCallback implements ToolCallback { public McpToolCallback(McpClientLink mcpClient, Tool tool) { this.mcpClient = mcpClient; this.tool = tool; - this.toolName = this.mcpClient.clientName()+"#"+this.tool.name(); + this.toolName = this.tool.name()+"#"+this.mcpClient.clientName(); + this.toolDefinition = DefaultToolDefinition.builder() + .name(toolName) + .description(this.tool.description()) + .inputSchema(ModelOptionsUtils.toJsonString(this.tool.inputSchema())) + .build(); } @Override public ToolDefinition getToolDefinition() { - return DefaultToolDefinition.builder() - .name(toolName) - .description(this.tool.description()) - .inputSchema(ModelOptionsUtils.toJsonString(this.tool.inputSchema())) - .build(); + return this.toolDefinition; + } + + @Override + public ToolMetadata getToolMetadata() { + return ToolCallback.super.getToolMetadata(); } public String getOriginalToolName() { diff --git a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/entity/McpServerInfo.java b/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/entity/McpServerInfo.java index 280c24015d4b7f4eddc2a3c5415dd15cd7d2c6a7..195a846910d648044df4ee34357bb1adcbc861fe 100644 --- a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/entity/McpServerInfo.java +++ b/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/entity/McpServerInfo.java @@ -1,5 +1,6 @@ package xyz.thoughtset.viewer.ai.mcp.server.entity; +import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; @@ -20,6 +21,7 @@ public class McpServerInfo extends BaseMeta { protected Integer typeCode; protected String serverTitle; protected String version; + @TableField(whereStrategy = FieldStrategy.NOT_EMPTY) protected String baseUrl; protected String sseEndpoint; protected String messageEndpoint; diff --git a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/factory/McpServerFactory.java b/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/factory/McpServerFactory.java index be644afaf0b11e68f7abd3f1bb3a5bc28f932b3e..46d3bcbb0c6aa5dfcd2cf021fdf674a4032c85fe 100644 --- a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/factory/McpServerFactory.java +++ b/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/factory/McpServerFactory.java @@ -69,6 +69,7 @@ public class McpServerFactory implements DisposableBean { throw UrlConflictException.build(); } WebMvcSseServerTransportProvider.Builder transportProviderBuilder = WebMvcSseServerTransportProvider.builder() +// .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) .objectMapper(objectMapper) .baseUrl(baseUrl) .sseEndpoint(sseEndpoint); diff --git a/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java b/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java index b22ddc5d2788fd23ceba6b6b134a2d3b4b87de13..8a80865e8315957ac5258a6c0a7b383cf1713a72 100644 --- a/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java +++ b/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import xyz.thoughtset.viewer.ai.mcp.client.factory.McpBotFactory; import xyz.thoughtset.viewer.ai.mcp.client.service.McpBotInfoService; import xyz.thoughtset.viewer.common.ai.model.factory.ModelsRegistry; import xyz.thoughtset.viewer.common.api.controller.BaseController; @@ -21,6 +22,8 @@ public class AiSupportController extends BaseController { private McpServerInfoService mcpServerInfoService; @Autowired private McpBotInfoService mcpBotInfoService; + @Autowired + private McpBotFactory mcpBotFactory; @GetMapping(value = "/publishServer") @@ -48,8 +51,8 @@ public class AiSupportController extends BaseController { mcpBotInfoService.unpublishedServer(apiId); } @GetMapping(value = "/mcpTools") - public Object mcpTools(){ - return ExtractConstantFieldUtil.extractEnumToList(xyz.thoughtset.viewer.common.ai.model.entity.purpose.ModelPurposeEnum.class); + public Object mcpTools(@RequestParam String objId){ + return mcpBotFactory.clientTools(objId); } @GetMapping(value = "/workingBots") public Object workingBots(){ diff --git a/apis/viewer-apis-client/src/main/resources/db/schema.sql b/apis/viewer-apis-client/src/main/resources/db/schema.sql index ad756cd2991e9570c4e2ed36bc2c694e9f526835..c5bdf3014fa2ac77dbf9b0cfa1ea729c18cec5ca 100644 --- a/apis/viewer-apis-client/src/main/resources/db/schema.sql +++ b/apis/viewer-apis-client/src/main/resources/db/schema.sql @@ -300,6 +300,28 @@ CREATE TABLE IF NOT EXISTS datarel ( PRIMARY KEY (id) ); +CREATE TABLE IF NOT EXISTS mcpbotinfo ( + id varchar(32) NOT NULL, + title varchar(128) NOT NULL, + pid varchar(32) DEFAULT NULL, + groupId varchar(32) DEFAULT NULL, + remark text, + orderNum int(11) DEFAULT NULL, + createdAt datetime(0) NOT NULL, + updatedAt datetime(0) NOT NULL, + statesCode int(11) NULL DEFAULT 0, + typeCode int(11) NOT NULL DEFAULT 11, + serverTitle varchar(128) DEFAULT NULL, + version varchar(32) DEFAULT NULL, + baseUrl text NOT NULL, + sseEndpoint text NOT NULL, + requestTimeout int(11) DEFAULT NULL, + disableToolNames text, + headers text, + connectTimeout int(11) DEFAULT NULL, + PRIMARY KEY (id) +); + CREATE TABLE IF NOT EXISTS mcpserverinfo ( id varchar(32) NOT NULL, title varchar(128) NOT NULL, diff --git a/apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json b/apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json index 18fa4b4becff19720aa9ff8847bcd2a52ce185e2..63340d65a4480bad1425da9eedf794ff2ba1a598 100644 --- a/apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json +++ b/apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json @@ -9,7 +9,7 @@ "dsType": "api", "syncLocation": false, "primaryField": "id", - "loadType": "pagination", + "loadType": "", "api": { "url": "/q/findList/aiNode", "method": "get", @@ -119,47 +119,7 @@ "position": "static", "flexWrap": "nowrap" }, - "items": [ - { - "type": "container", - "align": "right", - "body": [ - { - "type": "pagination", - "behavior": "Pagination", - "layout": [ - "total", - "perPage", - "pager" - ], - "perPage": 10, - "perPageAvailable": [ - 10, - 20, - 50, - 100 - ], - "align": "right", - "id": "u:7a8aeed01b15", - "size": "", - "mode": "normal" - } - ], - "wrapperBody": false, - "style": { - "flexGrow": 1, - "flex": "1 1 auto", - "position": "static", - "display": "flex", - "flexBasis": "auto", - "flexDirection": "row", - "flexWrap": "nowrap", - "alignItems": "stretch", - "justifyContent": "flex-end" - }, - "id": "u:e4b9e45be11e" - } - ], + "items": [], "id": "u:e7bdb59c323f", "isFixedHeight": false, "isFixedWidth": false @@ -283,7 +243,10 @@ }, "showHeader": true, "resizable": true, - "keepItemSelectionOnPageChange": true + "keepItemSelectionOnPageChange": true, + "footer": null, + "sticky": false, + "title": null } ], "id": "u:3dc63152e7a2", diff --git a/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json b/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json index eb5c1a4886c6e9290216fde7de54481ad5024656..13ffb3a0f4f7c5fb41268591bcd1c1268fb584df 100644 --- a/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json +++ b/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json @@ -344,6 +344,18 @@ "mode": "popOver" } }, + { + "label": "参数描述", + "type": "text", + "id": "u:dff800500b96", + "placeholder": "-", + "name": "remark", + "quickEdit": { + "type": "input-text", + "name": "remark", + "mode": "popOver" + } + }, { "label": "paramType", "name": "paramType", @@ -1503,8 +1515,12 @@ }, { "type": "input-table", - "name": "data.dataParams", "label": "提示预设词参数", + "name": "data.dataParams", + "addable": true, + "removable": true, + "id": "u:4d4f94282765", + "strictMode": true, "columns": [ { "label": "属性名", @@ -1529,17 +1545,13 @@ "id": "u:6d5fd877e447" } ], - "addable": true, "footerAddBtn": { "label": "新增", "icon": "fa fa-plus", "id": "u:1a024cfcbda3" }, - "strictMode": true, - "id": "u:4d4f94282765", "needConfirm": false, "copyable": true, - "removable": true, "showIndex": false, "clearValueOnHidden": true, "hiddenOn": "${!(bodyType==\"FunctionCall\" || bodyType==\"execAI\" || bodyType==\"mcp\" || bodyType==\"tools\")}" diff --git a/apis/viewer-apis-client/src/main/resources/static/html/pages/queryBody.json b/apis/viewer-apis-client/src/main/resources/static/html/pages/queryBody.json index db5cbc7bfe677a899d969538e73b825c4be3908c..72aa2dcfca167d6dcc9f20e3ec9f18bc350a9189 100644 --- a/apis/viewer-apis-client/src/main/resources/static/html/pages/queryBody.json +++ b/apis/viewer-apis-client/src/main/resources/static/html/pages/queryBody.json @@ -1,13 +1,5 @@ { "type": "page", - "id": "u:2100b771c123", - "asideResizor": false, - "pullRefresh": { - "disabled": true - }, - "regions": [ - "body" - ], "body": [ { "type": "service", @@ -82,12 +74,20 @@ }, { "type": "select", - "label": "dsId", + "label": "dataSource", "name": "dsId", "id": "u:5339fadef54e", "size": "full", "multiple": false, - "source": "${dslist}", + "source": { + "method": "get", + "url": "/q/findList/dsConfig", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "cache": 3000, + "silent": true + }, "labelField": "title", "valueField": "id", "clearable": true, @@ -101,7 +101,8 @@ "id": "u:72e09d733521", "size": "full", "required": false, - "behavior": "SimpleQuery" + "behavior": "SimpleQuery", + "visible": false } ], "actions": [ @@ -113,8 +114,8 @@ "actions": [ { "ignoreError": false, - "actionType": "dialog", - "dialog": { + "actionType": "drawer", + "drawer": { "$ref": "modal-ref-1" } } @@ -139,61 +140,7 @@ "feat": "Insert" }, "headerToolbar": [], - "footerToolbar": [ - { - "type": "flex", - "direction": "row", - "justify": "flex-start", - "alignItems": "stretch", - "style": { - "position": "static", - "flexWrap": "nowrap" - }, - "items": [ - { - "type": "container", - "align": "left", - "body": [], - "wrapperBody": false, - "style": { - "flexGrow": 1, - "flex": "1 1 auto", - "position": "static", - "display": "flex", - "flexDirection": "row", - "flexWrap": "nowrap", - "alignItems": "stretch", - "justifyContent": "flex-start", - "flexBasis": "auto" - }, - "id": "u:c0d31881db76" - }, - { - "type": "container", - "align": "right", - "body": [], - "wrapperBody": false, - "style": { - "flexGrow": 1, - "flex": "1 1 auto", - "position": "static", - "display": "flex", - "flexDirection": "row", - "flexWrap": "nowrap", - "alignItems": "stretch", - "justifyContent": "flex-end" - }, - "id": "u:b2c24b67acd1", - "isFixedHeight": false - } - ], - "id": "u:af957ab7ccaf", - "isFixedHeight": false, - "isFixedWidth": false, - "visible": false, - "hidden": true - } - ], + "footerToolbar": [], "columns": [ { "type": "tpl", @@ -217,21 +164,21 @@ { "type": "mapping", "name": "dsId", - "title": "dsId", + "title": "dataSource", "id": "u:031d61d5e9b8", "placeholder": "-", - "source": "${dslist}", + "source": { + "method": "get", + "url": "/q/findList/dsConfig", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "cache": 3000, + "silent": true + }, "labelField": "title", "valueField": "id" }, - { - "type": "tpl", - "title": "statesCode", - "name": "statesCode", - "id": "u:4d4d4fa54913", - "placeholder": "-", - "width": "15%" - }, { "type": "date", "title": "updatedAt", @@ -253,8 +200,8 @@ "click": { "actions": [ { - "actionType": "dialog", - "dialog": { + "actionType": "drawer", + "drawer": { "$ref": "modal-ref-1" } } @@ -309,15 +256,24 @@ "enable": true, "maxDisplayRows": 5 } - } + }, + "showHeader": true } ], + "id": "u:2100b771c123", + "asideResizor": false, + "pullRefresh": { + "disabled": true + }, + "regions": [ + "body" + ], "definitions": { "modal-ref-1": { - "type": "dialog", + "type": "drawer", "body": [ { - "id": "u:e779ca972619", + "id": "u:d18613688d56", "type": "form", "title": "编辑数据", "mode": "flex", @@ -330,7 +286,7 @@ "label": "id", "row": 0, "type": "input-text", - "id": "u:585fd74111ea", + "id": "u:3a1a1802dd11", "visible": false, "colSize": "1" }, @@ -339,50 +295,36 @@ "label": "title", "row": 1, "type": "input-text", - "id": "u:85a819d49f44", + "id": "u:9497f8040448", "colSize": "1" }, { "type": "select", "name": "dsId", "label": "数据源", - "id": "u:de378388ea7c", + "id": "u:f3a4737b7eaa", "required": true, "multiple": false, - "source": "${dslist}", + "source": { + "method": "get", + "url": "/q/findList/dsConfig", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "cache": 3000, + "silent": true + }, "labelField": "title", "valueField": "id", "selectFirst": true }, { - "type": "editor", - "label": "查询sql", - "name": "query", - "id": "u:ee7dfe409a67", - "vendor": "tinymce", - "options": { - "plugins": "advlist,autolink,link,image,lists,charmap,preview,anchor,pagebreak,searchreplace,wordcount,visualblocks,visualchars,code,fullscreen,insertdatetime,media,nonbreaking,table,emoticons,template,help", - "toolbar": "undo redo formatselect bold italic backcolor alignleft aligncenter alignright alignjustify bullist numlist outdent indent removeformat help", - "menubar": true - }, - "language": "xml", - "required": true, - "labelRemark": { - "icon": "fa fa-question-circle", - "trigger": [ - "hover" - ], - "className": "Remark--warning", - "placement": "bottom", - "content": "mybatis格式" - } - }, - { - "name": "params", - "label": "参数", - "row": 4, "type": "input-table", - "id": "u:44d8af082723", + "label": "参数", + "name": "params", + "id": "u:616a87aa6523", + "required": true, + "size": "full", "colSize": "1", "needConfirm": true, "addable": true, @@ -405,12 +347,12 @@ "quickEdit": { "type": "input-text", "name": "title", - "id": "u:10c6282af6da", + "id": "u:5d01e805bc84", "mode": "popOver" }, - "id": "u:5b77a6188d91", + "id": "u:437035be5e5b", "placeholder": "-", - "width": "30%" + "width": "25%" }, { "label": "dataType", @@ -418,13 +360,13 @@ "type": "text", "quickEdit": { "type": "wrapper", - "id": "u:d728f0990025", + "id": "u:1c904fe4010b", "mode": "popOver", "body": [ { "type": "nested-select", "name": "dataType", - "id": "u:d01bd4e90dac", + "id": "u:a05c9afdbcaa", "searchable": false, "onlyLeaf": false, "multiple": false, @@ -447,9 +389,9 @@ "isFixedHeight": false, "isFixedWidth": false }, - "id": "u:cc3432ca0a34", + "id": "u:0b995344b54c", "placeholder": "-", - "width": "30%" + "width": "20%" }, { "label": "defaultVal", @@ -458,19 +400,56 @@ "quickEdit": { "type": "input-text", "name": "defaultVal", - "id": "u:db3b6add0fd7", + "id": "u:dfea141bb408", "mode": "inline" }, - "id": "u:fbde4f0ffdc1", + "id": "u:b8d2e8d0daad", "placeholder": "-", - "width": "30%" + "width": "20%" + }, + { + "label": "说明", + "name": "remark", + "type": "text", + "quickEdit": { + "type": "input-text", + "name": "remark", + "id": "u:c8f458b33b31", + "mode": "inline" + }, + "id": "u:419bb382cc60", + "placeholder": "-", + "width": "35%" } ], "showTableAddBtn": false, "removable": true, "columnsTogglable": false, - "mode": "normal", - "size": "full" + "mode": "normal" + }, + { + "type": "editor", + "label": "执行语句", + "name": "query", + "id": "u:2fa8b92148b7", + "vendor": "tinymce", + "options": { + "plugins": "advlist,autolink,link,image,lists,charmap,preview,anchor,pagebreak,searchreplace,wordcount,visualblocks,visualchars,code,fullscreen,insertdatetime,media,nonbreaking,table,emoticons,template,help", + "toolbar": "undo redo formatselect bold italic backcolor alignleft aligncenter alignright alignjustify bullist numlist outdent indent removeformat help", + "menubar": true + }, + "language": "xml", + "required": true, + "labelRemark": { + "icon": "fa fa-question-circle", + "trigger": [ + "hover" + ], + "className": "Remark--warning", + "placement": "bottom", + "content": "SQL:兼容mybatis格式" + }, + "size": "xl" } ], "api": { @@ -525,25 +504,26 @@ "type": "button", "actionType": "cancel", "label": "取消", - "id": "u:2b884bacd66c" + "id": "u:34788beae76f" }, { "type": "button", "actionType": "submit", "label": "提交", "level": "primary", - "id": "u:c6f42749b2c0" + "id": "u:277ca18a8d1e" } ], - "actionType": "dialog", - "id": "u:57c43f2b5482", + "actionType": "drawer", + "id": "u:db5592d370a2", "showCloseButton": true, "closeOnOutside": false, "closeOnEsc": false, "showErrorMsg": true, "showLoading": true, "draggable": false, - "size": "md" + "size": "xl", + "resizable": false } }, "data": { diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/ModelParam.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/ModelParam.java index 2f90fd8d06105bf162799ab202cbd1fb4e8893dc..aca8aaf5c53bbd700506510f4976885b26f9c1c8 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/ModelParam.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/ModelParam.java @@ -1,5 +1,8 @@ package xyz.thoughtset.viewer.common.ai.model.entity; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.SqlCondition; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; @@ -18,6 +21,7 @@ import java.util.Map; @NoArgsConstructor @ApiCRUDPower(insert = false,save = true,update = false,list = true) public class ModelParam extends BaseMeta { + @TableField(whereStrategy = FieldStrategy.NOT_EMPTY,condition = SqlCondition.LIKE) protected String model; protected ModelPurposeEnum purpose; protected String setting; diff --git a/commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/entity/BaseMeta.java b/commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/entity/BaseMeta.java index e565aaaafc3cf269967c09837993a2aa35da3f63..8078816ff391804d6c5e475a7a9e06cde7ea81fc 100644 --- a/commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/entity/BaseMeta.java +++ b/commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/entity/BaseMeta.java @@ -25,4 +25,8 @@ public class BaseMeta extends TitleMeta{ return baseObj; } + public boolean isActive(){ + return StatusCodeConstant.RUNNING.equals(this.statesCode); + } + } diff --git a/commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/entity/TitleMeta.java b/commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/entity/TitleMeta.java index 7dbf61a1edc2a7b0a1df6bf5d0b232e2efd61b4c..59989d1e80dfcba698391fbf6eec6c3ed3c01498 100644 --- a/commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/entity/TitleMeta.java +++ b/commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/entity/TitleMeta.java @@ -1,8 +1,6 @@ package xyz.thoughtset.viewer.common.core.entity; -import com.baomidou.mybatisplus.annotation.FieldFill; -import com.baomidou.mybatisplus.annotation.OrderBy; -import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.*; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -12,7 +10,9 @@ import lombok.SneakyThrows; @NoArgsConstructor @AllArgsConstructor public class TitleMeta extends IdMeta{ + @TableField(whereStrategy = FieldStrategy.NOT_EMPTY,condition = SqlCondition.LIKE) protected String title; + @TableField(condition = SqlCondition.LIKE) protected String remark; diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AIChatExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AIChatExecutor.java index ca65f1d8894cdcc8fffb9f2d6b5aa9dfa0edec8e..37e7f3bcb5a4b32175e3dce5ce23a422b5a10ef4 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AIChatExecutor.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AIChatExecutor.java @@ -54,16 +54,24 @@ public abstract class AIChatExecutor extends AbstractAI ToolCallingChatOptions.Builder toolCallingBuilder = ToolCallingChatOptions.builder() .internalToolExecutionEnabled(false); String limitSystemPrompt = - "选择适当的方法执行,由于部分参数存储在本地,对于非required参数,可以填入建议值或空值。预期值与方法执行结果不一致时,以方法结果为准。"; + """ + --- + # 所有未知信息都必须使用调用工具来获取。 + # 部分上下文参数存储在本地,在调用参数时对于非required参数,可以填入建议值或空值。预期值与方法执行结果不一致时,以方法结果为准。 + # 当调用多个工具时,必须按顺序依次调用,不允许跳过任何步骤。 + # 工具调用后,必须等待工具返回结果,再根据结果决定是否继续调用下一个工具,不能跳过任何步骤。 + # 当后续要求与当前冲突时,以当前要求为准! + --- + """+ System.lineSeparator(); String systemText = modelParam.chatSystemPrompt(); if (body.getJsonType()!=null && body.getJsonType().booleanValue()){ - systemText = limitSystemPrompt+""" + systemText = """ 最终结果必须按照JSON数组格式返回,不要有其余任何内容,不要有任何拼写错误,不要有任何语法错误,当后续要求与当前冲突时,以当前要求为准! """ + (StringUtils.hasText(systemText) ? systemText : ""); } - String finalSystemText = systemText; + String finalSystemText = limitSystemPrompt + System.lineSeparator() + systemText; builder.defaultSystem(fillAndRenderPromptToStr(finalSystemText, parser, context, filterMap)); loadToolCallbacks(block,body,builder); ChatClient client = builder @@ -113,7 +121,7 @@ public abstract class AIChatExecutor extends AbstractAI // 记录每个工具调用的详细信息 for (ToolResponseMessage.ToolResponse resp : toolMessage.getResponses()) { - log.debug("*** {}#{}#{}",resp.id(),resp.name(),resp.responseData()); + log.info("*** {}#{}#{}",resp.id(),resp.name(),resp.responseData()); } } saveMegToChatMemory(chatId, toolResultMessages, chatMemory); diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java index e4a90f947264faa065ca72e10382bf5870d6e7f7..3ced2db924cb3c4beda664bd585dedcf5d8afba1 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java @@ -35,9 +35,12 @@ public class FunctionCallBlockToolCallbackProvider { .map((funInfo) -> { String funTitle = funInfo.getTitle(); String funRemark = funInfo.getRemark(); + BlockBodyEle bodyEle = funParamMap.get(funInfo.getId()); + String remark = bodyEle.getRemark(); + remark = StringUtils.hasText(remark) ? remark : funRemark; DefaultToolDefinition toolDefinition = (DefaultToolDefinition) DefaultToolDefinition.builder() .name(funTitle) - .description(StringUtils.hasText(funRemark) ? funRemark : funTitle) + .description(StringUtils.hasText(remark) ? remark : funTitle) .inputSchema(ParamJsonSchemaGenerator.generateForFunInput(funInfo)) .build(); String funId = funInfo.getId(); diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/json/ParamJsonSchemaGenerator.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/json/ParamJsonSchemaGenerator.java index a545bfa2e814e3621373b5236db2ca6d42dc6e19..aa5bfa3397b8e20a508dae14e6429453a4796702 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/json/ParamJsonSchemaGenerator.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/json/ParamJsonSchemaGenerator.java @@ -13,6 +13,7 @@ import org.springframework.ai.util.json.schema.SpringAiSchemaModule; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import xyz.thoughtset.viewer.modules.fun.entity.FunInfo; +import xyz.thoughtset.viewer.modules.step.entity.block.BlockBodyEle; import java.lang.reflect.Method; import java.lang.reflect.Type; diff --git a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/AbstractExecutor.java b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/AbstractExecutor.java index 5be09b0d1604945cfb9487a38ba624465ddd3482..278bb3fb0d1606431dc45d685c548054918f4b5b 100644 --- a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/AbstractExecutor.java +++ b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/AbstractExecutor.java @@ -2,13 +2,27 @@ package xyz.thoughtset.viewer.executor.core.base; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.builder.BuilderException; +import org.apache.ibatis.mapping.ResultFlag; +import org.apache.ibatis.parsing.XNode; +import org.apache.ibatis.parsing.XPathParser; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.TypeAliasRegistry; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; import xyz.thoughtset.viewer.executor.core.entity.QueryBody; import xyz.thoughtset.viewer.executor.core.factory.ExecutorRegistry; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +@Slf4j public abstract class AbstractExecutor { + protected TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); + protected final String head_xml = ""; + protected final String tail_xml = ""; @Getter protected final String supportType; @Getter @@ -23,6 +37,77 @@ public abstract class AbstractExecutor { ExecutorRegistry.register(this); } + protected XNode buildRootNode(String baseTest){ + baseTest = baseTest.trim().replaceAll("&", "&"); + //todo:优化点直接使用DocumentBuilderFactory进行创建 + XPathParser parser = new XPathParser(head_xml+baseTest+tail_xml); + XNode rootNode = parser.evalNode("/root"); + return rootNode; + } + + protected DataNode buildResultTree(XNode resultMapNode){ + if (resultMapNode==null) return null; + return buildResultTree(resultMapNode,new DataNode()); + } + protected DataNode buildResultTree(XNode resultMapNode,DataNode parentDataNode){ + List resultChildren = resultMapNode.getChildren(); + boolean hasConfirmKey = false; +// String keyColumns = resultMapNode.getStringAttribute("idColumns"); +// if (StringUtils.hasText(keyColumns)){ +// String[] keyArr = keyColumns.split(","); +// for (String k : keyArr) { +// parentDataNode.putKeyClew(k); +// } +// hasConfirmKey = true; +// } + for (XNode resultChild : resultChildren) { + String nodeName = resultChild.getName(); + String property = resultChild.getStringAttribute("property"); + int nodeType = 0; + switch (nodeName) { + case "collection" : + nodeType = 2; + break; + case "association" : + nodeType = 1; + break; + } + if (nodeType>0){ + DataNode sonDataNode = new DataNode(); + sonDataNode.setNodeKey(property); + sonDataNode.setNodeType(nodeType); + parentDataNode.putSonDataNode(sonDataNode); + buildResultTree(resultChild,sonDataNode); + } + if (hasConfirmKey) continue; + if ("id".equals(resultChild.getName())) { + parentDataNode.putKeyClew(property); + hasConfirmKey = true; + }else { + parentDataNode.putColumnClew(property); + } + } + return parentDataNode; + } + + + protected JdbcType resolveJdbcType(String alias) { + try { + return alias == null ? null : JdbcType.valueOf(alias); + } catch (IllegalArgumentException e) { + throw new BuilderException("Error resolving JdbcType. Cause: " + e, e); + } + } + + protected Class resolveJavaType(String alias) { + try { + return !StringUtils.hasText(alias) ? String.class : typeAliasRegistry.resolveAlias(alias); + } catch (IllegalArgumentException e) { + log.info("Error resolving JdbcType. Cause: " + e, e); + return String.class; + } + } + public Object execute(QueryBody queryBody, Map param){ return exec(queryBody,param); } diff --git a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/DataContext.java b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/DataContext.java deleted file mode 100644 index 4a4ba49e37a00ea92f61d524c85043ab2f253365..0000000000000000000000000000000000000000 --- a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/DataContext.java +++ /dev/null @@ -1,19 +0,0 @@ -package xyz.thoughtset.viewer.executor.core.base; - -import lombok.Data; - -import java.util.HashMap; - -@Data -public class DataContext { - private HashMap dataMap; - private String bodyId; - private String apiId; - private String funId; - - public DataContext() { - this.dataMap = new HashMap<>(); - } - - -} diff --git a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/DataNode.java b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/DataNode.java new file mode 100644 index 0000000000000000000000000000000000000000..baf806f8342fa4e3d4ce51a85916b7315806b5ac --- /dev/null +++ b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/DataNode.java @@ -0,0 +1,64 @@ +package xyz.thoughtset.viewer.executor.core.base; + +import lombok.Data; +import lombok.SneakyThrows; +import org.apache.ibatis.parsing.XNode; +import org.springframework.util.StringUtils; + +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@Data +public class DataNode { + private String nodeKey; + private Integer nodeType = 0; + private boolean useKey = false; + private List keyColumnNames = new ArrayList<>(); + private List sonDataNodes = new ArrayList<>(); + + DataNode putSonDataNode(DataNode dataNode){ + this.sonDataNodes.add(dataNode); + return this; + } + + @SneakyThrows + public List putValue(ResultSet rs, XNode resultChild, String prefix, Class javaTypeClass + ,HashMap dataMap,List key){ + String columnPrefix = StringUtils.hasText(prefix) ? prefix : ""; + String property = resultChild.getStringAttribute("property"); + String column = columnPrefix + resultChild.getStringAttribute("column"); + Object value = rs.getObject(column,javaTypeClass); + dataMap.put(property,value); + int pos = keyColumnNames.indexOf(column); + if (pos>=-1){ + key.add(pos,value.toString()); + } + return key; + } + + @SneakyThrows + public DataNode putColumnClew(String columnName){ + if (useKey) return this; + this.keyColumnNames.add(columnName); + return this; + } + + public DataNode putKeyClew(String columnName){ + if (!useKey) this.keyColumnNames = new ArrayList<>(); + useKey = true; + this.keyColumnNames.add(columnName); + return this; + } + + + public List combineData(List datas){ +// HashMap combineData = new HashMap<>(); +// for (String columnName : this.idNodeColumnNames) { +// combineData.put(columnName,this.idNodeMap.get(columnName)); +// } + return datas; + } + +} diff --git a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/ResultContext.java b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/ResultContext.java new file mode 100644 index 0000000000000000000000000000000000000000..484e9df32d2ff5b22c1780b5f90c94ca42a030c1 --- /dev/null +++ b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/base/ResultContext.java @@ -0,0 +1,101 @@ +package xyz.thoughtset.viewer.executor.core.base; + +import lombok.Data; +import org.apache.ibatis.parsing.XNode; +import org.springframework.util.MultiValueMap; +import org.springframework.util.MultiValueMapAdapter; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.executor.core.base.DataNode; + +import java.util.*; + +@Data +public class ResultContext { + private DataNode node; + private HashMap keyDatas = new HashMap<>(); + private List dataList = new ArrayList<>(); + private static final MultiValueMap keysMap = new MultiValueMapAdapter<>(new HashMap<>()); + + public ResultContext(DataNode dataNode){ + this.node = dataNode; + } + + public void putData(Map data){ + if (node == null) { + dataList.add(data); + return; + } + String keyStr = buildKeyStr(data); + if (!keyDatas.containsKey(keyStr)){ + keyDatas.put(keyStr,data); + dataList.add(data); + return ; + } + Map existingData = keyDatas.get(keyStr); + mergeData(existingData,data,node,keyStr); + return ; + } + +// public void putRsVal(Object data,String rowKey){ +// if (r) +// if (node == null) dataList.add(data); +// String keyStr = buildKeyStr(data); +// if (!keyDatas.containsKey(keyStr)){ +// keyDatas.put(keyStr,data); +// dataList.add(data); +// return ; +// } +// Map existingData = keyDatas.get(keyStr); +// mergeData(existingData,data,node,keyStr); +// return ; +// } + + private String buildKeyStr(Map data){ + return buildKeyStr(data,node); + } + private String buildKeyStr(Map data,DataNode node){ + StringBuilder keyBuilder = new StringBuilder(); + for (String k : node.getKeyColumnNames()) { + Object val = data.get(k); + keyBuilder.append(val!=null?val:"").append("_"); + } + return keyBuilder.toString(); + } + + //原有的map,新的map + private void mergeData(Map oldData, Map newData, DataNode node, String parentKey){ + String existingChildKeyStr = keysMap.getFirst(parentKey); + for (DataNode childNode : node.getSonDataNodes()){ + String nodeName = childNode.getNodeKey(); + Object childDataObj = oldData.get(nodeName); + if (childNode.getNodeType()==1){ + Map childData = (Map) childDataObj; + if (!StringUtils.hasText(existingChildKeyStr)){ + existingChildKeyStr = buildKeyStr(childData,childNode); + } + mergeData(childData,(Map) (newData.get(nodeName)),childNode,parentKey+existingChildKeyStr); + }else if (childNode.getNodeType()==2){ + List childDataList = (List) childDataObj; + Map newChildData = ((List) newData.get(childNode.getNodeKey())).get(0); + String newChildKeyStr = buildKeyStr(newChildData,childNode); + boolean exists = false; + if (!StringUtils.hasText(existingChildKeyStr)){ + existingChildKeyStr = buildKeyStr(childDataList.get(0),childNode); + } + String tmpKey = parentKey + existingChildKeyStr; + for (Map existingChildData : childDataList) { + if (existingChildKeyStr.equals(newChildKeyStr)){ + mergeData(existingChildData,newChildData,childNode,tmpKey); + exists = true; + break; + } + } + if (!exists){ + keysMap.add(parentKey,newChildKeyStr); + childDataList.add(newChildData); + } + } + } + } + +} diff --git a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/entity/QueryBody.java b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/entity/QueryBody.java index df78d807147412cf3988192c0cbe1a51efe6ce76..f2f6fa4150d3c62fae462ba27f741fe53d7a6df5 100644 --- a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/entity/QueryBody.java +++ b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/entity/QueryBody.java @@ -1,5 +1,6 @@ package xyz.thoughtset.viewer.executor.core.entity; +import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.SqlCondition; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; @@ -19,8 +20,9 @@ import java.util.List; @DataOptLog @ApiCRUDPower(insert = false,save = true,update = false,list = true) public class QueryBody extends BaseMeta { + @TableField(whereStrategy = FieldStrategy.NOT_EMPTY) protected String dsId; - @TableField(condition = SqlCondition.LIKE) + @TableField(whereStrategy = FieldStrategy.NOT_EMPTY,condition = SqlCondition.LIKE) protected String query; // protected String type; diff --git a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/factory/ExecutorRegistry.java b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/factory/ExecutorRegistry.java index 024cc12081524d2c46390e26fb5305c10b25c30f..6edf7a13c88b650a0bdfc2286bf421e05d1f1f4a 100644 --- a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/factory/ExecutorRegistry.java +++ b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/factory/ExecutorRegistry.java @@ -1,11 +1,7 @@ package xyz.thoughtset.viewer.executor.core.factory; -import lombok.Getter; import xyz.thoughtset.viewer.executor.core.base.AbstractExecutor; -import xyz.thoughtset.viewer.executor.core.base.DataContext; -import java.util.LinkedHashSet; -import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; diff --git a/modules/viewer-modules-api/src/main/java/xyz/thoughtset/viewer/modules/api/entity/ApiInfo.java b/modules/viewer-modules-api/src/main/java/xyz/thoughtset/viewer/modules/api/entity/ApiInfo.java index 49abfb99be55aefde292015c812f700d736e7ca9..35d115d0ab223e8537c8df5421bfa5e763095d40 100644 --- a/modules/viewer-modules-api/src/main/java/xyz/thoughtset/viewer/modules/api/entity/ApiInfo.java +++ b/modules/viewer-modules-api/src/main/java/xyz/thoughtset/viewer/modules/api/entity/ApiInfo.java @@ -1,5 +1,6 @@ package xyz.thoughtset.viewer.modules.api.entity; +import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import org.springframework.util.ObjectUtils; @@ -20,6 +21,7 @@ import java.util.Map; @ApiCRUDPower(insert = false,save = true,update = false,list = true) public class ApiInfo extends BaseMeta { // private String funId; + @TableField(whereStrategy = FieldStrategy.NOT_EMPTY) private String url; private Integer queryType; private Integer cacheTime; diff --git a/modules/viewer-modules-ds/viewer-modules-ds-core/src/main/java/xyz/thoughtset/viewer/modules/ds/core/factory/ExecutorManager.java b/modules/viewer-modules-ds/viewer-modules-ds-core/src/main/java/xyz/thoughtset/viewer/modules/ds/core/factory/ExecutorManager.java index 9ab85887ada32628ae6e7841a940c677fb6f0d84..1bbf9daff69575c0c1ceed88af82597ffbc57e40 100644 --- a/modules/viewer-modules-ds/viewer-modules-ds-core/src/main/java/xyz/thoughtset/viewer/modules/ds/core/factory/ExecutorManager.java +++ b/modules/viewer-modules-ds/viewer-modules-ds-core/src/main/java/xyz/thoughtset/viewer/modules/ds/core/factory/ExecutorManager.java @@ -1,19 +1,13 @@ package xyz.thoughtset.viewer.modules.ds.core.factory; import cn.hutool.core.convert.Convert; -import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import xyz.thoughtset.viewer.common.cache.executor.CacheExecutorHelper; -import xyz.thoughtset.viewer.common.connector.entity.bo.Linker; import xyz.thoughtset.viewer.common.connector.linker.ConnBuilder; -import xyz.thoughtset.viewer.common.connector.linker.LinkerBuilder; -import xyz.thoughtset.viewer.common.connector.linker.LinkerHelper; import xyz.thoughtset.viewer.common.exc.entity.ExcInfo; import xyz.thoughtset.viewer.common.exc.exceptions.ExecException; import xyz.thoughtset.viewer.executor.core.base.AbstractExecutor; -import xyz.thoughtset.viewer.executor.core.base.DataContext; import xyz.thoughtset.viewer.executor.core.constants.TypeConstants; import xyz.thoughtset.viewer.executor.core.entity.QueryBody; import xyz.thoughtset.viewer.executor.core.entity.QueryParam; @@ -23,10 +17,8 @@ import xyz.thoughtset.viewer.modules.ds.core.dao.DsConfigDao; import xyz.thoughtset.viewer.modules.ds.core.entity.DsConfig; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Collectors; diff --git a/modules/viewer-modules-ds/viewer-modules-ds-http/viewer-modules-ds-http-core/src/main/java/xyz/thoughtset/viewer/modules/ds/http/core/executor/HttpReqExecutor.java b/modules/viewer-modules-ds/viewer-modules-ds-http/viewer-modules-ds-http-core/src/main/java/xyz/thoughtset/viewer/modules/ds/http/core/executor/HttpReqExecutor.java index aa6a35b396feacc3b97bf9e18d9075982deae248..288d3bd433c31c3c2bc701572dfdcceabe1e2f57 100644 --- a/modules/viewer-modules-ds/viewer-modules-ds-http/viewer-modules-ds-http-core/src/main/java/xyz/thoughtset/viewer/modules/ds/http/core/executor/HttpReqExecutor.java +++ b/modules/viewer-modules-ds/viewer-modules-ds-http/viewer-modules-ds-http-core/src/main/java/xyz/thoughtset/viewer/modules/ds/http/core/executor/HttpReqExecutor.java @@ -30,8 +30,6 @@ public class HttpReqExecutor extends AbstractExecutor { super(SupportTypeConstant.SUPPORT_TYPE, true); } private final Configuration configuration = new Configuration(); - private final String head_xml = ""; - private final String tail_xml = ""; @Override protected Object exec(QueryBody queryBody, Map param) { @@ -41,10 +39,7 @@ public class HttpReqExecutor extends AbstractExecutor { @SneakyThrows public Object doQuery(String sqlText, Object param, BaseReq baseReq) { - sqlText = sqlText.trim().replaceAll("&", "&"); - //todo:优化点直接使用DocumentBuilderFactory进行创建 - XPathParser parser = new XPathParser(head_xml+sqlText+tail_xml); - XNode rootNode = parser.evalNode("/root"); + XNode rootNode = buildRootNode(sqlText); XNode xNode = rootNode.evalNode("http|get|post"); XNode payloadNode = rootNode.evalNode("/payload"); Boolean useFormatUrl = xNode.getBooleanAttribute(RequireAttrConstant.USE_FORMAT_URL, RequireAttrConstant.USE_FORMAT_URL_DEFAULT); diff --git a/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-core/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/core/factory/MybatisSqlQueryExecutor.java b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-core/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/core/factory/MybatisSqlQueryExecutor.java index 2b6fed4e8ef5501967b57b50b41eadaa0e5b25e4..37e725068c51551e36fbb92b957c2ce26a9988d1 100644 --- a/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-core/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/core/factory/MybatisSqlQueryExecutor.java +++ b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-core/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/core/factory/MybatisSqlQueryExecutor.java @@ -6,7 +6,6 @@ import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.ParameterMode; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.parsing.XNode; -import org.apache.ibatis.parsing.XPathParser; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.ParamNameResolver; import org.apache.ibatis.scripting.xmltags.XMLScriptBuilder; @@ -16,7 +15,10 @@ import org.apache.ibatis.type.TypeException; import org.apache.ibatis.type.TypeHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import xyz.thoughtset.viewer.executor.core.base.AbstractExecutor; +import xyz.thoughtset.viewer.executor.core.base.DataNode; +import xyz.thoughtset.viewer.executor.core.base.ResultContext; import xyz.thoughtset.viewer.executor.core.entity.QueryBody; import xyz.thoughtset.viewer.modules.ds.core.factory.ConnectFactory; import xyz.thoughtset.viewer.modules.ds.jdbc.core.constant.SupportTypeConstant; @@ -48,13 +50,17 @@ public class MybatisSqlQueryExecutor extends AbstractExecutor { @SneakyThrows public List doQuery(String sqlText, Object param, DataSource ds) { - XPathParser parser = new XPathParser(sqlText); - XNode xNode = parser.evalNode("/select"); + XNode rootNode = buildRootNode(sqlText); + XNode xNode = rootNode.evalNode("select"); + XNode resultNode = rootNode.evalNode("resultMap"); + DataNode dataNode = buildResultTree(resultNode); + boolean useDataMapping = dataNode!=null; +// buildResultTree(resultNode); XMLScriptBuilder builder = new XMLScriptBuilder(new Configuration(), xNode, null); SqlSource sqlSource = builder.parseScriptNode(); Object parameterObject = ParamNameResolver.wrapToMapIfCollection(param, null); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); - List list= new ArrayList<>(); + ResultContext context = new ResultContext(dataNode); try( Connection connection = ds.getConnection(); PreparedStatement statement = prepareStatement(boundSql,connection,parameterObject); @@ -67,11 +73,17 @@ public class MybatisSqlQueryExecutor extends AbstractExecutor { fieldNames.add(data.getColumnLabel(i)); } while (rs.next()) { - Map resultMap = new HashMap<>(); - for(String field : fieldNames){ - resultMap.put(field,rs.getObject(field)); +// list.add(createResultObject(fieldNames,rs,resultNode,dataNode)); + Map resultMap; + if (useDataMapping){ + resultMap = resultMappingObject(rs, resultNode,""); + }else{ + resultMap = new HashMap<>(); + for(String field : fieldNames){ + resultMap.put(field,rs.getObject(field)); + } } - list.add(resultMap); + context.putData(resultMap); } }catch (Exception e){ e.printStackTrace(); @@ -82,7 +94,7 @@ public class MybatisSqlQueryExecutor extends AbstractExecutor { // boundSql); // DynamicSqlSource sqlSource = new DynamicSqlSource(new Configuration(), rootSqlNode); - return list; + return context.getDataList(); } private PreparedStatement prepareStatement(BoundSql boundSql,Connection connection,Object parameterObject) throws SQLException { @@ -168,4 +180,57 @@ public class MybatisSqlQueryExecutor extends AbstractExecutor { return rs; } + + private Map resultMappingObject(ResultSet rs, XNode resultNode,String prefix) throws SQLException { + List resultChildren = resultNode.getChildren(); + HashMap contextMap = new HashMap<>(); + for (XNode resultChild : resultChildren) { + if ("collection".equals(resultChild.getName())) { + doCollectionNode(rs, resultChild,contextMap,prefix); + } else if ("association ".equals(resultChild.getName())) { + doAssociation(rs, resultChild,contextMap,prefix); + } else { + doResultNode(rs, resultChild,contextMap,prefix); + } + } + return contextMap; + } + + private HashMap doResultNode(ResultSet rs, XNode resultChild,HashMap contextMap,String prefix) throws SQLException { + String columnPrefix = StringUtils.hasText(prefix) ? prefix : ""; + String property = resultChild.getStringAttribute("property"); + String column = columnPrefix + resultChild.getStringAttribute("column"); + String javaType = resultChild.getStringAttribute("javaType"); + Class javaTypeClass = resolveJavaType(javaType); + contextMap.put(property,rs.getObject(column,javaTypeClass)); + return contextMap; + } + + private HashMap doCollectionNode(ResultSet rs, XNode resultChild,HashMap contextMap,String prefix) throws SQLException { + String columnPrefix = prefix + resultChild.getStringAttribute("columnPrefix",""); + String property = resultChild.getStringAttribute("property"); + List list = new ArrayList<>(); + list.add(resultMappingObject(rs, resultChild, columnPrefix)); + contextMap.put(property,list); + return contextMap; + } + + private HashMap doAssociation(ResultSet rs, XNode resultChild,HashMap contextMap,String prefix) throws SQLException { + String columnPrefix = prefix + resultChild.getStringAttribute("columnPrefix",""); + String property = resultChild.getStringAttribute("property"); + contextMap.put(property,resultMappingObject(rs, resultChild, columnPrefix)); + return contextMap; + } + +// protected List combineData(List dataNodes,int dataSize){ +// List list = new ArrayList<>(); +// HashMap cacheMap = new HashMap<>(); +// for (DataNode dataNode : dataNodes) { +// HashMap currentData = dataNode.getDataMap(); +// if (dataNode.getNodeType()>1) +// } +// return list; +// } + + } diff --git a/pom.xml b/pom.xml index fe65da7dd26a15cf6c45612967dae2b44787eb03..157b31406454871705b99295ce2918df044f654b 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ - 1.3.0 + 1.4.0 17 2025.0.0 true @@ -53,8 +53,8 @@ 4.11.0 0.2.25 3.2.0 - 1.0.1 - 0.12.0 + 1.0.3 + 0.12.1 diff --git a/sql/mysql/dv.sql b/sql/mysql/dv.sql index 0495e6187c639519b5c017e384e90ae0472c54ef..ff94ce4f534ddc92783bd28a2e029be5442b7808 100644 --- a/sql/mysql/dv.sql +++ b/sql/mysql/dv.sql @@ -299,6 +299,32 @@ CREATE TABLE `loginfo` ( PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; +-- ---------------------------- +-- Table structure for mcpbotinfo +-- ---------------------------- +DROP TABLE IF EXISTS `mcpbotinfo`; +CREATE TABLE `mcpbotinfo` ( + `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `title` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `pid` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `groupId` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `remark` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, + `orderNum` int(11) NULL DEFAULT NULL, + `createdAt` datetime(0) NOT NULL, + `updatedAt` datetime(0) NOT NULL, + `statesCode` int(11) NULL DEFAULT 0, + `typeCode` int(11) NOT NULL DEFAULT 11, + `serverTitle` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `version` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `baseUrl` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, + `sseEndpoint` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `requestTimeout` int(11) NULL DEFAULT NULL, + `disableToolNames` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, + `headers` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, + `connectTimeout` int(11) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + -- ---------------------------- -- Table structure for mcpserverinfo -- ----------------------------