使用Spring AI配合MCP(Model Context Protocol)构建一个"智能代码审查助手"

创建一个案例:使用Spring AI配合MCP(Model Context Protocol)构建一个"智能代码审查助手",它能够自动分析代码质量、生成改进建议,并与多个外部工具集成。

项目概述

这个案例将创建一个智能代码审查系统,具有以下特色功能:

  • 自动代码质量分析
  • 集成Git仓库管理
  • 连接代码质量工具(如SonarQube)
  • 生成可视化报告
  • 提供重构建议

1. 项目结构

css 复制代码
code-review-ai/
├── src/main/java/
│   ├── config/
│   │   ├── SpringAIConfig.java
│   │   └── MCPConfig.java
│   ├── controller/
│   │   └── CodeReviewController.java
│   ├── service/
│   │   ├── CodeAnalysisService.java
│   │   └── MCPIntegrationService.java
│   ├── mcp/
│   │   ├── tools/
│   │   │   ├── GitTool.java
│   │   │   ├── SonarQubeTool.java
│   │   │   └── FileAnalysisTool.java
│   │   └── server/
│   │       └── MCPServer.java
│   └── model/
│       ├── CodeReviewRequest.java
│       └── CodeReviewResult.java
└── pom.xml

2. Maven依赖配置

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.example</groupId>
    <artifactId>code-review-ai</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
    </parent>
    
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>0.8.1</spring-ai.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Starters -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        
        <!-- Spring AI -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        
        <!-- MCP相关依赖 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        
        <!-- Git集成 -->
        <dependency>
            <groupId>org.eclipse.jgit</groupId>
            <artifactId>org.eclipse.jgit</artifactId>
            <version>6.7.0.202309050840-r</version>
        </dependency>
        
        <!-- HTTP客户端 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        
        <!-- 代码分析工具 -->
        <dependency>
            <groupId>com.github.javaparser</groupId>
            <artifactId>javaparser-core</artifactId>
            <version>3.24.8</version>
        </dependency>
    </dependencies>
</project>

3. 核心配置类

Spring AI配置

java 复制代码
package com.example.config;

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.openai.OpenAiChatClient;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringAIConfig {
    
    @Bean
    public ChatClient chatClient() {
        return OpenAiChatClient.builder()
            .withApiKey(System.getenv("OPENAI_API_KEY"))
            .build();
    }
    
    @Bean
    public OpenAiApi openAiApi() {
        return new OpenAiApi(System.getenv("OPENAI_API_KEY"));
    }
}

MCP配置

java 复制代码
package com.example.config;

import com.example.mcp.server.MCPServer;
import com.example.mcp.tools.GitTool;
import com.example.mcp.tools.SonarQubeTool;
import com.example.mcp.tools.FileAnalysisTool;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MCPConfig {
    
    @Bean
    public MCPServer mcpServer(GitTool gitTool, 
                              SonarQubeTool sonarQubeTool,
                              FileAnalysisTool fileAnalysisTool) {
        MCPServer server = new MCPServer();
        server.registerTool(gitTool);
        server.registerTool(sonarQubeTool);
        server.registerTool(fileAnalysisTool);
        return server;
    }
    
    @Bean
    public GitTool gitTool() {
        return new GitTool();
    }
    
    @Bean
    public SonarQubeTool sonarQubeTool() {
        return new SonarQubeTool();
    }
    
    @Bean
    public FileAnalysisTool fileAnalysisTool() {
        return new FileAnalysisTool();
    }
}

4. MCP工具实现

Git工具

java 复制代码
package com.example.mcp.tools;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.springframework.stereotype.Component;

import java.io.File;
import java.util.List;
import java.util.Map;

@Component
public class GitTool implements MCPTool {
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    public String getName() {
        return "git";
    }
    
    @Override
    public String getDescription() {
        return "Git repository operations for code analysis";
    }
    
    @Override
    public JsonNode getSchema() {
        ObjectNode schema = objectMapper.createObjectNode();
        schema.put("type", "object");
        
        ObjectNode properties = objectMapper.createObjectNode();
        
        ObjectNode actionProp = objectMapper.createObjectNode();
        actionProp.put("type", "string");
        actionProp.put("enum", List.of("clone", "diff", "log", "blame"));
        
        ObjectNode repoProp = objectMapper.createObjectNode();
        repoProp.put("type", "string");
        repoProp.put("description", "Repository URL or path");
        
        ObjectNode pathProp = objectMapper.createObjectNode();
        pathProp.put("type", "string");
        pathProp.put("description", "File or directory path");
        
        properties.set("action", actionProp);
        properties.set("repository", repoProp);
        properties.set("path", pathProp);
        
        schema.set("properties", properties);
        schema.put("required", List.of("action", "repository"));
        
        return schema;
    }
    
    @Override
    public JsonNode execute(JsonNode parameters) {
        try {
            String action = parameters.get("action").asText();
            String repository = parameters.get("repository").asText();
            String path = parameters.has("path") ? parameters.get("path").asText() : "";
            
            return switch (action) {
                case "clone" -> cloneRepository(repository);
                case "diff" -> getRepositoryDiff(repository, path);
                case "log" -> getCommitLog(repository, path);
                case "blame" -> getBlameInfo(repository, path);
                default -> createErrorResponse("Unknown action: " + action);
            };
            
        } catch (Exception e) {
            return createErrorResponse("Git operation failed: " + e.getMessage());
        }
    }
    
    private JsonNode cloneRepository(String repoUrl) throws Exception {
        File localPath = File.createTempFile("repo", "");
        localPath.delete();
        localPath.mkdirs();
        
        Git git = Git.cloneRepository()
            .setURI(repoUrl)
            .setDirectory(localPath)
            .call();
        
        ObjectNode result = objectMapper.createObjectNode();
        result.put("success", true);
        result.put("localPath", localPath.getAbsolutePath());
        result.put("message", "Repository cloned successfully");
        
        return result;
    }
    
    private JsonNode getRepositoryDiff(String repoPath, String filePath) throws Exception {
        Repository repository = new FileRepositoryBuilder()
            .setGitDir(new File(repoPath, ".git"))
            .build();
        
        Git git = new Git(repository);
        
        // 获取最近的diff
        String diff = git.diff()
            .call()
            .stream()
            .findFirst()
            .map(Object::toString)
            .orElse("No changes detected");
        
        ObjectNode result = objectMapper.createObjectNode();
        result.put("success", true);
        result.put("diff", diff);
        
        return result;
    }
    
    private JsonNode getCommitLog(String repoPath, String filePath) throws Exception {
        Repository repository = new FileRepositoryBuilder()
            .setGitDir(new File(repoPath, ".git"))
            .build();
        
        Git git = new Git(repository);
        
        // 获取提交日志(简化版)
        ObjectNode result = objectMapper.createObjectNode();
        result.put("success", true);
        result.put("log", "Commit log implementation here");
        
        return result;
    }
    
    private JsonNode getBlameInfo(String repoPath, String filePath) throws Exception {
        ObjectNode result = objectMapper.createObjectNode();
        result.put("success", true);
        result.put("blame", "Blame information for " + filePath);
        
        return result;
    }
    
    private JsonNode createErrorResponse(String message) {
        ObjectNode error = objectMapper.createObjectNode();
        error.put("success", false);
        error.put("error", message);
        return error;
    }
}

文件分析工具

java 复制代码
package com.example.mcp.tools;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import org.springframework.stereotype.Component;

import java.io.File;
import java.nio.file.Files;
import java.util.List;

@Component
public class FileAnalysisTool implements MCPTool {
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final JavaParser javaParser = new JavaParser();
    
    @Override
    public String getName() {
        return "file_analysis";
    }
    
    @Override
    public String getDescription() {
        return "Analyze source code files for complexity, patterns, and potential issues";
    }
    
    @Override
    public JsonNode getSchema() {
        ObjectNode schema = objectMapper.createObjectNode();
        schema.put("type", "object");
        
        ObjectNode properties = objectMapper.createObjectNode();
        
        ObjectNode fileProp = objectMapper.createObjectNode();
        fileProp.put("type", "string");
        fileProp.put("description", "Path to the source file");
        
        ObjectNode typeProp = objectMapper.createObjectNode();
        typeProp.put("type", "string");
        typeProp.put("enum", List.of("complexity", "structure", "metrics", "issues"));
        
        properties.set("filePath", fileProp);
        properties.set("analysisType", typeProp);
        
        schema.set("properties", properties);
        schema.put("required", List.of("filePath", "analysisType"));
        
        return schema;
    }
    
    @Override
    public JsonNode execute(JsonNode parameters) {
        try {
            String filePath = parameters.get("filePath").asText();
            String analysisType = parameters.get("analysisType").asText();
            
            return switch (analysisType) {
                case "complexity" -> analyzeComplexity(filePath);
                case "structure" -> analyzeStructure(filePath);
                case "metrics" -> calculateMetrics(filePath);
                case "issues" -> detectIssues(filePath);
                default -> createErrorResponse("Unknown analysis type: " + analysisType);
            };
            
        } catch (Exception e) {
            return createErrorResponse("File analysis failed: " + e.getMessage());
        }
    }
    
    private JsonNode analyzeComplexity(String filePath) throws Exception {
        String content = Files.readString(new File(filePath).toPath());
        CompilationUnit cu = javaParser.parse(content).getResult().orElse(null);
        
        ObjectNode result = objectMapper.createObjectNode();
        result.put("success", true);
        
        if (cu != null) {
            ArrayNode methods = objectMapper.createArrayNode();
            
            cu.findAll(MethodDeclaration.class).forEach(method -> {
                ObjectNode methodInfo = objectMapper.createObjectNode();
                methodInfo.put("name", method.getNameAsString());
                methodInfo.put("lineCount", method.getEnd().get().line - method.getBegin().get().line);
                methodInfo.put("complexity", calculateCyclomaticComplexity(method));
                methods.add(methodInfo);
            });
            
            result.set("methods", methods);
        }
        
        return result;
    }
    
    private JsonNode analyzeStructure(String filePath) throws Exception {
        String content = Files.readString(new File(filePath).toPath());
        CompilationUnit cu = javaParser.parse(content).getResult().orElse(null);
        
        ObjectNode result = objectMapper.createObjectNode();
        result.put("success", true);
        
        if (cu != null) {
            ArrayNode classes = objectMapper.createArrayNode();
            
            cu.findAll(ClassOrInterfaceDeclaration.class).forEach(cls -> {
                ObjectNode classInfo = objectMapper.createObjectNode();
                classInfo.put("name", cls.getNameAsString());
                classInfo.put("isInterface", cls.isInterface());
                classInfo.put("methodCount", cls.getMethods().size());
                classInfo.put("fieldCount", cls.getFields().size());
                classes.add(classInfo);
            });
            
            result.set("classes", classes);
        }
        
        return result;
    }
    
    private JsonNode calculateMetrics(String filePath) throws Exception {
        String content = Files.readString(new File(filePath).toPath());
        
        ObjectNode metrics = objectMapper.createObjectNode();
        metrics.put("success", true);
        metrics.put("lineCount", content.split("\n").length);
        metrics.put("characterCount", content.length());
        metrics.put("wordCount", content.split("\\s+").length);
        
        // 简单的代码质量指标
        metrics.put("commentRatio", calculateCommentRatio(content));
        metrics.put("complexity", "medium"); // 简化实现
        
        return metrics;
    }
    
    private JsonNode detectIssues(String filePath) throws Exception {
        String content = Files.readString(new File(filePath).toPath());
        
        ObjectNode result = objectMapper.createObjectNode();
        result.put("success", true);
        
        ArrayNode issues = objectMapper.createArrayNode();
        
        // 简单的问题检测
        String[] lines = content.split("\n");
        for (int i = 0; i < lines.length; i++) {
            String line = lines[i];
            
            if (line.length() > 120) {
                ObjectNode issue = objectMapper.createObjectNode();
                issue.put("type", "line_too_long");
                issue.put("line", i + 1);
                issue.put("message", "Line exceeds 120 characters");
                issues.add(issue);
            }
            
            if (line.contains("System.out.print")) {
                ObjectNode issue = objectMapper.createObjectNode();
                issue.put("type", "debug_code");
                issue.put("line", i + 1);
                issue.put("message", "Debug print statement found");
                issues.add(issue);
            }
        }
        
        result.set("issues", issues);
        return result;
    }
    
    private int calculateCyclomaticComplexity(MethodDeclaration method) {
        // 简化的圈复杂度计算
        String methodBody = method.toString();
        int complexity = 1; // 基础复杂度
        
        complexity += countOccurrences(methodBody, "if");
        complexity += countOccurrences(methodBody, "else");
        complexity += countOccurrences(methodBody, "while");
        complexity += countOccurrences(methodBody, "for");
        complexity += countOccurrences(methodBody, "switch");
        complexity += countOccurrences(methodBody, "case");
        complexity += countOccurrences(methodBody, "catch");
        
        return complexity;
    }
    
    private int countOccurrences(String text, String pattern) {
        return text.split(pattern).length - 1;
    }
    
    private double calculateCommentRatio(String content) {
        String[] lines = content.split("\n");
        int commentLines = 0;
        
        for (String line : lines) {
            String trimmed = line.trim();
            if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
                commentLines++;
            }
        }
        
        return lines.length > 0 ? (double) commentLines / lines.length : 0.0;
    }
    
    private JsonNode createErrorResponse(String message) {
        ObjectNode error = objectMapper.createObjectNode();
        error.put("success", false);
        error.put("error", message);
        return error;
    }
}

5. MCP服务器实现

java 复制代码
package com.example.mcp.server;

import com.example.mcp.tools.MCPTool;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class MCPServer {
    
    private final Map<String, MCPTool> tools = new HashMap<>();
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    public void registerTool(MCPTool tool) {
        tools.put(tool.getName(), tool);
    }
    
    public JsonNode listTools() {
        ObjectNode response = objectMapper.createObjectNode();
        ArrayNode toolsArray = objectMapper.createArrayNode();
        
        tools.values().forEach(tool -> {
            ObjectNode toolInfo = objectMapper.createObjectNode();
            toolInfo.put("name", tool.getName());
            toolInfo.put("description", tool.getDescription());
            toolInfo.set("inputSchema", tool.getSchema());
            toolsArray.add(toolInfo);
        });
        
        response.set("tools", toolsArray);
        return response;
    }
    
    public JsonNode callTool(String toolName, JsonNode parameters) {
        MCPTool tool = tools.get(toolName);
        if (tool == null) {
            ObjectNode error = objectMapper.createObjectNode();
            error.put("error", "Tool not found: " + toolName);
            return error;
        }
        
        try {
            return tool.execute(parameters);
        } catch (Exception e) {
            ObjectNode error = objectMapper.createObjectNode();
            error.put("error", "Tool execution failed: " + e.getMessage());
            return error;
        }
    }
}

// MCP工具接口
interface MCPTool {
    String getName();
    String getDescription();
    JsonNode getSchema();
    JsonNode execute(JsonNode parameters);
}

6. 核心服务类

代码分析服务

java 复制代码
package com.example.service;

import com.example.mcp.server.MCPServer;
import com.example.model.CodeReviewRequest;
import com.example.model.CodeReviewResult;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CodeAnalysisService {
    
    @Autowired
    private ChatClient chatClient;
    
    @Autowired
    private MCPServer mcpServer;
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    public CodeReviewResult analyzeCode(CodeReviewRequest request) {
        try {
            // 1. 使用MCP工具分析代码结构
            JsonNode structureAnalysis = analyzeCodeStructure(request.getFilePath());
            
            // 2. 使用MCP工具检测问题
            JsonNode issuesAnalysis = detectCodeIssues(request.getFilePath());
            
            // 3. 使用Spring AI生成智能建议
            String aiSuggestions = generateAISuggestions(request, structureAnalysis, issuesAnalysis);
            
            // 4. 综合分析结果
            return buildCodeReviewResult(request, structureAnalysis, issuesAnalysis, aiSuggestions);
            
        } catch (Exception e) {
            throw new RuntimeException("Code analysis failed", e);
        }
    }
    
    private JsonNode analyzeCodeStructure(String filePath) {
        ObjectNode parameters = objectMapper.createObjectNode();
        parameters.put("filePath", filePath);
        parameters.put("analysisType", "structure");
        
        return mcpServer.callTool("file_analysis", parameters);
    }
    
    private JsonNode detectCodeIssues(String filePath) {
        ObjectNode parameters = objectMapper.createObjectNode();
        parameters.put("filePath", filePath);
        parameters.put("analysisType", "issues");
        
        return mcpServer.callTool("file_analysis", parameters);
    }
    
    private String generateAISuggestions(CodeReviewRequest request, 
                                       JsonNode structureAnalysis, 
                                       JsonNode issuesAnalysis) {
        String prompt = buildAnalysisPrompt(request, structureAnalysis, issuesAnalysis);
        
        ChatResponse response = chatClient.call(
            new Prompt(new UserMessage(prompt))
        );
        
        return response.getResult().getOutput().getContent();
    }
    
    private String buildAnalysisPrompt(CodeReviewRequest request,
                                     JsonNode structureAnalysis,
                                     JsonNode issuesAnalysis) {
        StringBuilder prompt = new StringBuilder();
        prompt.append("作为一名资深代码审查专家,请分析以下代码信息并提供专业建议:\n\n");
        
        prompt.append("文件路径: ").append(request.getFilePath()).append("\n");
        prompt.append("分析类型: ").append(request.getAnalysisType()).append("\n\n");
        
        prompt.append("代码结构分析结果:\n");
        prompt.append(structureAnalysis.toPrettyString()).append("\n\n");
        
        prompt.append("发现的问题:\n");
        prompt.append(issuesAnalysis.toPrettyString()).append("\n\n");
        
        prompt.append("请基于以上分析提供:\n");
        prompt.append("1. 代码质量评估\n");
        prompt.append("2. 具体的改进建议\n");
        prompt.append("3. 重构建议(如果需要)\n");
        prompt.append("4. 最佳实践推荐\n");
        prompt.append("5. 潜在风险识别\n\n");
        
        prompt.append("请用中文回答,并提供具体可行的建议。");
        
        return prompt.toString();
    }
    
    private CodeReviewResult buildCodeReviewResult(CodeReviewRequest request,
                                                  JsonNode structureAnalysis,
                                                  JsonNode issuesAnalysis,
                                                  String aiSuggestions) {
        CodeReviewResult result = new CodeReviewResult();
        result.setFilePath(request.getFilePath());
        result.setAnalysisType(request.getAnalysisType());
        result.setStructureAnalysis(structureAnalysis.toString());
        result.setIssuesFound(issuesAnalysis.toString());
        result.setAiSuggestions(aiSuggestions);
        result.setTimestamp(System.currentTimeMillis());
        
        // 计算总体评分
        int score = calculateOverallScore(structureAnalysis, issuesAnalysis);
        result.setQualityScore(score);
        
        return result;
    }
    
    private int calculateOverallScore(JsonNode structureAnalysis, JsonNode issuesAnalysis) {
        int baseScore = 100;
        
        // 根据发现的问题数量扣分
        if (issuesAnalysis.has("issues")) {
            int issueCount = issuesAnalysis.get("issues").size();
            baseScore -= Math.min(issueCount * 5, 50); // 每个问题扣5分,最多扣50分
        }
        
        // 根据复杂度调整分数
        // 这里可以添加更复杂的评分逻辑
        
        return Math.max(baseScore, 0);
    }
}

MCP集成服务

java 复制代码
package com.example.service;

import com.example.mcp.server.MCPServer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MCPIntegrationService {
    
    @Autowired
    private MCPServer mcpServer;
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    public JsonNode executeGitOperation(String action, String repository, String path) {
        ObjectNode parameters = objectMapper.createObjectNode();
        parameters.put("action", action);
        parameters.put("repository", repository);
        if (path != null && !path.isEmpty()) {
            parameters.put("path", path);
        }
        
        return mcpServer.callTool("git", parameters);
    }
    
    public JsonNode analyzeWithSonarQube(String projectKey, String filePath) {
        ObjectNode parameters = objectMapper.createObjectNode();
        parameters.put("action", "analyze");
        parameters.put("projectKey", projectKey);
        parameters.put("filePath", filePath);
        
        return mcpServer.callTool("sonarqube", parameters);
    }
    
    public JsonNode getAvailableTools() {
        return mcpServer.listTools();
    }
    
    public JsonNode executeCustomTool(String toolName, JsonNode parameters) {
        return mcpServer.callTool(toolName, parameters);
    }
}

7. REST控制器

java 复制代码
package com.example.controller;

import com.example.model.CodeReviewRequest;
import com.example.model.CodeReviewResult;
import com.example.service.CodeAnalysisService;
import com.example.service.MCPIntegrationService;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/code-review")
@CrossOrigin(origins = "*")
public class CodeReviewController {
    
    @Autowired
    private CodeAnalysisService codeAnalysisService;
    
    @Autowired
    private MCPIntegrationService mcpIntegrationService;
    
    @PostMapping("/analyze")
    public ResponseEntity<CodeReviewResult> analyzeCode(@RequestBody CodeReviewRequest request) {
        try {
            CodeReviewResult result = codeAnalysisService.analyzeCode(request);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            return ResponseEntity.internalServerError().build();
        }
    }
    
    @GetMapping("/tools")
    public ResponseEntity<JsonNode> getAvailableTools() {
        JsonNode tools = mcpIntegrationService.getAvailableTools();
        return ResponseEntity.ok(tools);
    }
    
    @PostMapping("/git/{action}")
    public ResponseEntity<JsonNode> executeGitOperation(
            @PathVariable String action,
            @RequestParam String repository,
            @RequestParam(required = false) String path) {
        
        JsonNode result = mcpIntegrationService.executeGitOperation(action, repository, path);
        return ResponseEntity.ok(result);
    }
    
    @PostMapping("/sonarqube/analyze")
    public ResponseEntity<JsonNode> analyzWithSonarQube(
            @RequestParam String projectKey,
            @RequestParam String filePath) {
        
        JsonNode result = mcpIntegrationService.analyzeWithSonarQube(projectKey, filePath);
        return ResponseEntity.ok(result);
    }
    
    @PostMapping("/tools/{toolName}")
    public ResponseEntity<JsonNode> executeCustomTool(
            @PathVariable String toolName,
            @RequestBody JsonNode parameters) {
        
        JsonNode result = mcpIntegrationService.executeCustomTool(toolName, parameters);
        return ResponseEntity.ok(result);
    }
}

8. 数据模型类

代码审查请求模型

java 复制代码
package com.example.model;

import com.fasterxml.jackson.annotation.JsonProperty;

public class CodeReviewRequest {
    
    @JsonProperty("filePath")
    private String filePath;
    
    @JsonProperty("analysisType")
    private String analysisType;
    
    @JsonProperty("repository")
    private String repository;
    
    @JsonProperty("branch")
    private String branch;
    
    @JsonProperty("includeMetrics")
    private boolean includeMetrics = true;
    
    @JsonProperty("includeSuggestions")
    private boolean includeSuggestions = true;
    
    // 构造函数
    public CodeReviewRequest() {}
    
    public CodeReviewRequest(String filePath, String analysisType) {
        this.filePath = filePath;
        this.analysisType = analysisType;
    }
    
    // Getter和Setter方法
    public String getFilePath() {
        return filePath;
    }
    
    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }
    
    public String getAnalysisType() {
        return analysisType;
    }
    
    public void setAnalysisType(String analysisType) {
        this.analysisType = analysisType;
    }
    
    public String getRepository() {
        return repository;
    }
    
    public void setRepository(String repository) {
        this.repository = repository;
    }
    
    public String getBranch() {
        return branch;
    }
    
    public void setBranch(String branch) {
        this.branch = branch;
    }
    
    public boolean isIncludeMetrics() {
        return includeMetrics;
    }
    
    public void setIncludeMetrics(boolean includeMetrics) {
        this.includeMetrics = includeMetrics;
    }
    
    public boolean isIncludeSuggestions() {
        return includeSuggestions;
    }
    
    public void setIncludeSuggestions(boolean includeSuggestions) {
        this.includeSuggestions = includeSuggestions;
    }
}

代码审查结果模型

java 复制代码
package com.example.model;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;
import java.util.Map;

public class CodeReviewResult {
    
    @JsonProperty("filePath")
    private String filePath;
    
    @JsonProperty("analysisType")
    private String analysisType;
    
    @JsonProperty("qualityScore")
    private int qualityScore;
    
    @JsonProperty("structureAnalysis")
    private String structureAnalysis;
    
    @JsonProperty("issuesFound")
    private String issuesFound;
    
    @JsonProperty("aiSuggestions")
    private String aiSuggestions;
    
    @JsonProperty("timestamp")
    private long timestamp;
    
    @JsonProperty("metrics")
    private Map<String, Object> metrics;
    
    @JsonProperty("recommendations")
    private List<String> recommendations;
    
    // 构造函数
    public CodeReviewResult() {}
    
    // Getter和Setter方法
    public String getFilePath() {
        return filePath;
    }
    
    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }
    
    public String getAnalysisType() {
        return analysisType;
    }
    
    public void setAnalysisType(String analysisType) {
        this.analysisType = analysisType;
    }
    
    public int getQualityScore() {
        return qualityScore;
    }
    
    public void setQualityScore(int qualityScore) {
        this.qualityScore = qualityScore;
    }
    
    public String getStructureAnalysis() {
        return structureAnalysis;
    }
    
    public void setStructureAnalysis(String structureAnalysis) {
        this.structureAnalysis = structureAnalysis;
    }
    
    public String getIssuesFound() {
        return issuesFound;
    }
    
    public void setIssuesFound(String issuesFound) {
        this.issuesFound = issuesFound;
    }
    
    public String getAiSuggestions() {
        return aiSuggestions;
    }
    
    public void setAiSuggestions(String aiSuggestions) {
        this.aiSuggestions = aiSuggestions;
    }
    
    public long getTimestamp() {
        return timestamp;
    }
    
    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }
    
    public Map<String, Object> getMetrics() {
        return metrics;
    }
    
    public void setMetrics(Map<String, Object> metrics) {
        this.metrics = metrics;
    }
    
    public List<String> getRecommendations() {
        return recommendations;
    }
    
    public void setRecommendations(List<String> recommendations) {
        this.recommendations = recommendations;
    }
}

9. SonarQube集成工具

java 复制代码
package com.example.mcp.tools;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
public class SonarQubeTool implements MCPTool {
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final WebClient webClient;
    
    public SonarQubeTool() {
        this.webClient = WebClient.builder()
            .baseUrl(System.getenv().getOrDefault("SONARQUBE_URL", "http://localhost:9000"))
            .build();
    }
    
    @Override
    public String getName() {
        return "sonarqube";
    }
    
    @Override
    public String getDescription() {
        return "Integration with SonarQube for code quality analysis";
    }
    
    @Override
    public JsonNode getSchema() {
        ObjectNode schema = objectMapper.createObjectNode();
        schema.put("type", "object");
        
        ObjectNode properties = objectMapper.createObjectNode();
        
        ObjectNode actionProp = objectMapper.createObjectNode();
        actionProp.put("type", "string");
        actionProp.put("enum", List.of("analyze", "issues", "metrics", "coverage"));
        
        ObjectNode projectProp = objectMapper.createObjectNode();
        projectProp.put("type", "string");
        projectProp.put("description", "SonarQube project key");
        
        ObjectNode fileProp = objectMapper.createObjectNode();
        fileProp.put("type", "string");
        fileProp.put("description", "File path to analyze");
        
        properties.set("action", actionProp);
        properties.set("projectKey", projectProp);
        properties.set("filePath", fileProp);
        
        schema.set("properties", properties);
        schema.put("required", List.of("action", "projectKey"));
        
        return schema;
    }
    
    @Override
    public JsonNode execute(JsonNode parameters) {
        try {
            String action = parameters.get("action").asText();
            String projectKey = parameters.get("projectKey").asText();
            String filePath = parameters.has("filePath") ? parameters.get("filePath").asText() : "";
            
            return switch (action) {
                case "analyze" -> triggerAnalysis(projectKey, filePath);
                case "issues" -> getIssues(projectKey, filePath);
                case "metrics" -> getMetrics(projectKey, filePath);
                case "coverage" -> getCoverage(projectKey, filePath);
                default -> createErrorResponse("Unknown action: " + action);
            };
            
        } catch (Exception e) {
            return createErrorResponse("SonarQube operation failed: " + e.getMessage());
        }
    }
    
    private JsonNode triggerAnalysis(String projectKey, String filePath) {
        try {
            // 模拟SonarQube分析触发
            ObjectNode result = objectMapper.createObjectNode();
            result.put("success", true);
            result.put("message", "Analysis triggered for project: " + projectKey);
            result.put("taskId", "task-" + System.currentTimeMillis());
            
            return result;
            
        } catch (Exception e) {
            return createErrorResponse("Failed to trigger analysis: " + e.getMessage());
        }
    }
    
    private JsonNode getIssues(String projectKey, String filePath) {
        try {
            // 模拟获取SonarQube问题
            ObjectNode result = objectMapper.createObjectNode();
            result.put("success", true);
            
            ArrayNode issues = objectMapper.createArrayNode();
            
            // 模拟问题数据
            ObjectNode issue1 = objectMapper.createObjectNode();
            issue1.put("key", "issue-1");
            issue1.put("rule", "java:S1481");
            issue1.put("severity", "MAJOR");
            issue1.put("message", "Remove this unused local variable");
            issue1.put("file", filePath);
            issue1.put("line", 42);
            issues.add(issue1);
            
            ObjectNode issue2 = objectMapper.createObjectNode();
            issue2.put("key", "issue-2");
            issue2.put("rule", "java:S1192");
            issue2.put("severity", "MINOR");
            issue2.put("message", "Define a constant instead of duplicating this literal");
            issue2.put("file", filePath);
            issue2.put("line", 15);
            issues.add(issue2);
            
            result.set("issues", issues);
            result.put("totalIssues", issues.size());
            
            return result;
            
        } catch (Exception e) {
            return createErrorResponse("Failed to get issues: " + e.getMessage());
        }
    }
    
    private JsonNode getMetrics(String projectKey, String filePath) {
        try {
            ObjectNode result = objectMapper.createObjectNode();
            result.put("success", true);
            
            ObjectNode metrics = objectMapper.createObjectNode();
            metrics.put("complexity", 15);
            metrics.put("coverage", 78.5);
            metrics.put("duplicatedLines", 12);
            metrics.put("linesOfCode", 450);
            metrics.put("technicalDebt", "2h 30min");
            metrics.put("bugs", 2);
            metrics.put("vulnerabilities", 0);
            metrics.put("codeSmells", 8);
            
            result.set("metrics", metrics);
            
            return result;
            
        } catch (Exception e) {
            return createErrorResponse("Failed to get metrics: " + e.getMessage());
        }
    }
    
    private JsonNode getCoverage(String projectKey, String filePath) {
        try {
            ObjectNode result = objectMapper.createObjectNode();
            result.put("success", true);
            
            ObjectNode coverage = objectMapper.createObjectNode();
            coverage.put("lineCoverage", 85.2);
            coverage.put("branchCoverage", 72.8);
            coverage.put("coveredLines", 382);
            coverage.put("uncoveredLines", 68);
            coverage.put("totalLines", 450);
            
            result.set("coverage", coverage);
            
            return result;
            
        } catch (Exception e) {
            return createErrorResponse("Failed to get coverage: " + e.getMessage());
        }
    }
    
    private JsonNode createErrorResponse(String message) {
        ObjectNode error = objectMapper.createObjectNode();
        error.put("success", false);
        error.put("error", message);
        return error;
    }
}

10. 应用程序主类

java 复制代码
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CodeReviewAIApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(CodeReviewAIApplication.class, args);
    }
}

11. 配置文件

application.yml

yaml 复制代码
server:
  port: 8080

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4
          temperature: 0.1
          max-tokens: 2000

logging:
  level:
    com.example: DEBUG
    org.springframework.ai: DEBUG

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

# 自定义配置
code-review:
  sonarqube:
    url: ${SONARQUBE_URL:http://localhost:9000}
    token: ${SONARQUBE_TOKEN:}
  git:
    temp-directory: ${java.io.tmpdir}/code-review-repos
  analysis:
    max-file-size: 1048576  # 1MB
    supported-extensions: java,js,ts,py,go,cpp,c,cs

12. 使用示例

前端调用示例(JavaScript)

javascript 复制代码
// 分析代码示例
async function analyzeCode() {
    const request = {
        filePath: "/path/to/your/source/file.java",
        analysisType: "comprehensive",
        repository: "https://github.com/user/repo.git",
        includeMetrics: true,
        includeSuggestions: true
    };
    
    try {
        const response = await fetch('/api/code-review/analyze', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(request)
        });
        
        const result = await response.json();
        console.log('分析结果:', result);
        
        // 显示质量评分
        console.log(`代码质量评分: ${result.qualityScore}/100`);
        
        // 显示AI建议
        console.log('AI建议:', result.aiSuggestions);
        
    } catch (error) {
        console.error('分析失败:', error);
    }
}

// 获取可用工具
async function getAvailableTools() {
    try {
        const response = await fetch('/api/code-review/tools');
        const tools = await response.json();
        console.log('可用工具:', tools);
    } catch (error) {
        console.error('获取工具失败:', error);
    }
}

// Git操作示例
async function cloneRepository() {
    const params = new URLSearchParams({
        repository: 'https://github.com/user/repo.git'
    });
    
    try {
        const response = await fetch(`/api/code-review/git/clone?${params}`, {
            method: 'POST'
        });
        
        const result = await response.json();
        console.log('克隆结果:', result);
        
    } catch (error) {
        console.error('克隆失败:', error);
    }
}

命令行使用示例(curl)

bash 复制代码
# 分析代码
curl -X POST http://localhost:8080/api/code-review/analyze \
  -H "Content-Type: application/json" \
  -d '{
    "filePath": "/path/to/source/file.java",
    "analysisType": "comprehensive",
    "includeMetrics": true,
    "includeSuggestions": true
  }'

# 获取可用工具
curl http://localhost:8080/api/code-review/tools

# 执行Git操作
curl -X POST "http://localhost:8080/api/code-review/git/clone?repository=https://github.com/user/repo.git"

# SonarQube分析
curl -X POST "http://localhost:8080/api/code-review/sonarqube/analyze?projectKey=my-project&filePath=/src/main/java/Main.java"

13. 项目特色功能

智能代码评审流程

  1. 多维度分析:结合静态代码分析、复杂度计算、问题检测
  2. AI增强建议:使用Spring AI生成智能化的改进建议
  3. 工具集成:通过MCP协议集成多种代码质量工具
  4. 实时反馈:提供即时的代码质量评估和建议

扩展性设计

  • 插件化架构:通过MCP协议轻松添加新的分析工具
  • 多语言支持:可扩展支持更多编程语言
  • 自定义规则:允许定义项目特定的代码质量规则

实际应用价值

  • 提高代码质量:自动化识别常见问题和反模式
  • 知识传播:AI建议帮助开发者学习最佳实践
  • 效率提升:减少人工代码审查的工作量
  • 标准化:统一团队的代码质量标准

这个案例展示了Spring AI与MCP的强大结合,创建了一个智能、可扩展的代码审查系统。

相关推荐
IT毕设实战小研3 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
甄超锋4 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
Jimmy4 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
墨风如雪5 小时前
不再是指令的奴隶:智元Genie,让机器人拥有了想象力
aigc
迈火5 小时前
ComfyUI-3D-Pack:3D创作的AI神器
人工智能·gpt·3d·ai·stable diffusion·aigc·midjourney
武昌库里写JAVA6 小时前
JAVA面试汇总(四)JVM(一)
java·vue.js·spring boot·sql·学习
Pitayafruit7 小时前
Spring AI 进阶之路03:集成RAG构建高效知识库
spring boot·后端·llm
zru_96028 小时前
Spring Boot 单元测试:@SpyBean 使用教程
spring boot·单元测试·log4j
用户5191495848458 小时前
HITCON CTF 2018 - 单行PHP挑战:会话上传与流过滤器链的极致利用
人工智能·aigc