办学质量监测教学评价系统
ageer
2025-04-12 9b32e3b3b226723cd43bfa37026e1e4487f6ef69
feat: mcp支持
已修改6个文件
已添加5个文件
415 ■■■■■ 文件已修改
ruoyi-modules-api/ruoyi-chat-api/pom.xml 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/pom.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OllamaServiceImpl.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-mcp-server/pom.xml 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-mcp-server/src/main/resources/application.yaml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/resources/application.yml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/pom.xml
@@ -18,12 +18,44 @@
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>1.0.0-M6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!-- å¯¹è¯åŸºç¡€æ¨¡å— -->
    <dependencies>
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>ruoyi-common-chat</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-mcp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-modules/ruoyi-chat/pom.xml
@@ -114,6 +114,20 @@
            <artifactId>ruoyi-system-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-core</artifactId>
            <version>1.0.0-M6</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-ollama</artifactId>
            <version>1.0.0-M6</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java
@@ -16,4 +16,11 @@
     * @param chatRequest è¯·æ±‚对象
     */
    SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter);
    /**
     * å®¢æˆ·ç«¯å‘送消息到服务端
     * @param chatRequest è¯·æ±‚对象
     */
    SseEmitter mcpChat(ChatRequest chatRequest,SseEmitter emitter);
}
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OllamaServiceImpl.java
@@ -6,8 +6,6 @@
import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
import io.github.ollama4j.models.chat.OllamaChatRequestModel;
import io.github.ollama4j.models.generate.OllamaStreamHandler;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.chat.service.chat.IChatService;
import org.ruoyi.chat.util.SSEUtil;
@@ -15,7 +13,16 @@
import org.ruoyi.common.chat.request.ChatRequest;
import org.ruoyi.domain.vo.ChatModelVo;
import org.ruoyi.service.IChatModelService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.ollama.api.OllamaModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -32,6 +39,13 @@
    @Autowired
    private  IChatModelService chatModelService;
    @Autowired
    private ChatClient chatClient;
    @Autowired
    private ToolCallbackProvider tools;
    private final ChatMemory chatMemory = new InMemoryChatMemory();
    @Override
    public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) {
@@ -77,4 +91,50 @@
        return emitter;
    }
    @Override
    public SseEmitter mcpChat(ChatRequest chatRequest, SseEmitter emitter) {
        List<Message> msgList = chatRequest.getMessages();
        // æ·»åŠ è®°å¿†
        for (int i = 0; i < msgList.size(); i++) {
            org.springframework.ai.chat.messages.Message springAiMessage = new UserMessage(msgList.get(i).getContent().toString());
            chatMemory.add(String.valueOf(i),springAiMessage);
        }
        var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, chatRequest.getUserId(), 10);
        this.chatClient.prompt(chatRequest.getPrompt())
                .advisors(messageChatMemoryAdvisor)
                .tools(tools)
                .options(OllamaOptions.builder()
                        .model(OllamaModel.QWEN_2_5_7B)
                        .temperature(0.4)
                        .build())
                .stream()
                .chatResponse()
                .subscribe(
                        chatResponse -> {
                            try {
                                emitter.send(chatResponse, MediaType.APPLICATION_JSON);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        },
                        error -> {
                            try {
                                emitter.completeWithError(error);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        },
                        () -> {
                            try {
                                emitter.complete();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                );
        return emitter;
    }
}
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java
@@ -5,14 +5,29 @@
import org.ruoyi.chat.listener.SSEEventSourceListener;
import org.ruoyi.chat.service.chat.IChatService;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
import org.ruoyi.common.chat.entity.chat.Message;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
import org.ruoyi.common.chat.request.ChatRequest;
import org.ruoyi.domain.vo.ChatModelVo;
import org.ruoyi.service.IChatModelService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.List;
/**
 * @author ageer
 */
@Service
@Slf4j
public class OpenAIServiceImpl implements IChatService {
@@ -23,6 +38,13 @@
    private ChatConfig chatConfig;
    @Autowired
    private OpenAiStreamClient openAiStreamClient;
    @Autowired
    private ChatClient chatClient;
    @Autowired
    private ToolCallbackProvider tools;
    private final ChatMemory chatMemory = new InMemoryChatMemory();
    @Override
    public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) {
@@ -47,4 +69,46 @@
        return emitter;
    }
    @Override
    public SseEmitter mcpChat(ChatRequest chatRequest, SseEmitter emitter) {
        List<Message> msgList = chatRequest.getMessages();
        // æ·»åŠ è®°å¿†
        for (int i = 0; i < msgList.size(); i++) {
            org.springframework.ai.chat.messages.Message springAiMessage = new UserMessage(msgList.get(i).getContent().toString());
            chatMemory.add(String.valueOf(i),springAiMessage);
        }
        var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, chatRequest.getUserId(), 10);
        this.chatClient.prompt(chatRequest.getPrompt())
                .advisors(messageChatMemoryAdvisor)
                .tools(tools)
                .options(OpenAiChatOptions.builder().model(chatRequest.getModel()).build())
                .stream()
                .chatResponse()
                .subscribe(
                        chatResponse -> {
                            try {
                                emitter.send(chatResponse, MediaType.APPLICATION_JSON);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        },
                        error -> {
                            try {
                                emitter.completeWithError(error);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        },
                        () -> {
                            try {
                                emitter.complete();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                );
        return emitter;
    }
}
ruoyi-modules/ruoyi-mcp-server/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.ruoyi</groupId>
        <artifactId>ruoyi-modules</artifactId>
        <version>${revision}</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>ruoyi-mcp-server</artifactId>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>1.0.0-M6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-mcp</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package org.ruoyi.mcp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * @author ageer
 */
@SpringBootApplication
public class McpServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(McpServerApplication.class, args);
    }
}
ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
package org.ruoyi.mcp.config;
import org.ruoyi.mcp.service.McpCustomService;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
 * @author ageer
 */
@Configuration
@EnableWebMvc
public class McpServerConfig implements WebMvcConfigurer {
    @Bean
    public ToolCallbackProvider openLibraryTools(McpCustomService mcpService) {
        return MethodToolCallbackProvider.builder().toolObjects(mcpService).build();
    }
    @Bean
    public List<McpServerFeatures.SyncResourceRegistration> resourceRegistrations() {
        // Create a resource registration for system information
        var systemInfoResource = new McpSchema.Resource(
                "system://info",
                "System Information",
                "Provides basic system information including Java version, OS, etc.",
                "application/json", null
        );
        var resourceRegistration = new McpServerFeatures.SyncResourceRegistration(systemInfoResource, (request) -> {
            try {
                var systemInfo = Map.of(
                        "javaVersion", System.getProperty("java.version"),
                        "osName", System.getProperty("os.name"),
                        "osVersion", System.getProperty("os.version"),
                        "osArch", System.getProperty("os.arch"),
                        "processors", Runtime.getRuntime().availableProcessors(),
                        "timestamp", System.currentTimeMillis());
                String jsonContent = new ObjectMapper().writeValueAsString(systemInfo);
                return new McpSchema.ReadResourceResult(
                        List.of(new McpSchema.TextResourceContents(request.uri(), "application/json", jsonContent)));
            }
            catch (Exception e) {
                throw new RuntimeException("Failed to generate system info", e);
            }
        });
        return List.of(resourceRegistration);
    }
    @Bean
    public List<McpServerFeatures.SyncPromptRegistration> promptRegistrations() {
        var prompt = new McpSchema.Prompt("greeting", "A friendly greeting prompt",
                List.of(new McpSchema.PromptArgument("name", "The name to greet", true)));
        var promptRegistration = new McpServerFeatures.SyncPromptRegistration(prompt, getPromptRequest -> {
            String nameArgument = (String) getPromptRequest.arguments().get("name");
            if (nameArgument == null) {
                nameArgument = "friend";
            }
            var userMessage = new McpSchema.PromptMessage(McpSchema.Role.USER,
                    new McpSchema.TextContent("Hello " + nameArgument + "! How can I assist you today?"));
            return new McpSchema.GetPromptResult("A personalized greeting message", List.of(userMessage));
        });
        return List.of(promptRegistration);
    }
    @Bean
    public Consumer<List<McpSchema.Root>> rootsChangeConsumer() {
        return roots -> {
            System.out.println("rootsChange");
        };
    }
}
ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package org.ruoyi.mcp.service;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * @author ageer
 */
@Service
public class McpCustomService {
    public record Book(List<String> isbn, String title, List<String> authorName) {
    }
    @Tool(description = "Get list of Books by title")
    public List<Book> getBooks(String title) {
        // è¿™é‡Œæ¨¡æ‹ŸæŸ¥è¯¢DB操作
        return List.of(new Book(List.of("ISBN-888"), "SpringAI教程", List.of("熊猫助手写的书")));
    }
}
ruoyi-modules/ruoyi-mcp-server/src/main/resources/application.yaml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
spring:
  application:
    name: mcp-server
  ai:
    mcp:
      server:
        name: webmvc-mcp-server
        version: 1.0.0
        type: SYNC
        sse-message-endpoint: /mcp/messages
ruoyi-modules/ruoyi-system/src/main/resources/application.yml
@@ -318,5 +318,22 @@
        token:   ''
        aesKey: ''
  #  spring ai配置
spring:
  ai:
    openai:
      api-key: sk-xx
      base-url: https://api.pandarobot.chat/
    mcp:
      client:
        enabled: true
        name: call-mcp-server
        sse:
          connections:
            server1:
              url: http://127.0.0.1:8080
    ollama:
      init:
        pull-model-strategy: always
        timeout: 60s
        max-retries: 1