From 0176e64646b10a5353ccf28dc456267c93d14aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?piexlMax=28=E5=A5=87=E6=B7=BC?= Date: Tue, 9 Sep 2025 13:29:06 +0800 Subject: [PATCH 01/33] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4mcp=E7=AD=96?= =?UTF-8?q?=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/mcp/gva_auto_generate.go | 734 ++++++++++++++++++++++++++--- server/mcp/requirement_analyzer.go | 13 +- 2 files changed, 678 insertions(+), 69 deletions(-) diff --git a/server/mcp/gva_auto_generate.go b/server/mcp/gva_auto_generate.go index 8fe4ef85d7..30aa897aae 100644 --- a/server/mcp/gva_auto_generate.go +++ b/server/mcp/gva_auto_generate.go @@ -9,7 +9,6 @@ import ( "path/filepath" "strings" "time" - "unicode" "github.com/flipped-aurora/gin-vue-admin/server/global" common "github.com/flipped-aurora/gin-vue-admin/server/model/common/request" @@ -81,12 +80,13 @@ type ExecutionPlan struct { // ExecutionResult 执行结果 type ExecutionResult struct { - Success bool `json:"success"` - Message string `json:"message"` - PackageID uint `json:"packageId,omitempty"` - HistoryID uint `json:"historyId,omitempty"` - Paths map[string]string `json:"paths,omitempty"` - NextActions []string `json:"nextActions,omitempty"` + Success bool `json:"success"` + Message string `json:"message"` + PackageID uint `json:"packageId,omitempty"` + HistoryID uint `json:"historyId,omitempty"` + Paths map[string]string `json:"paths,omitempty"` + GeneratedPaths []string `json:"generatedPaths,omitempty"` // 新增:记录生成的文件路径 + NextActions []string `json:"nextActions,omitempty"` } // ConfirmationRequest 确认请求结构 @@ -108,6 +108,42 @@ type ConfirmationResponse struct { ConfirmationKey string `json:"confirmationKey"` } +// ReviewRequest 代码复检请求结构 +type ReviewRequest struct { + OriginalRequirement string `json:"originalRequirement"` // 用户原始需求 + ExecutionPlan *ExecutionPlan `json:"executionPlan"` // 执行计划 + GeneratedPaths map[string]string `json:"generatedPaths"` // 生成的文件路径 + MaxRetries int `json:"maxRetries"` // 最大重试次数,默认3次 +} + +// ReviewResult 代码复检结果结构 +type ReviewResult struct { + Success bool `json:"success"` // 是否满足需求 + Message string `json:"message"` // 复检结果说明 + SatisfiedRequirements []string `json:"satisfiedRequirements"` // 已满足的需求 + MissingRequirements []string `json:"missingRequirements"` // 未满足的需求 + FilesToEnhance map[string][]string `json:"filesToEnhance"` // 需要增强的文件及具体需求 + ReviewDetails map[string]interface{} `json:"reviewDetails"` // 详细的复检信息 +} + +// EnhanceRequest 代码增强请求结构 +type EnhanceRequest struct { + OriginalRequirement string `json:"originalRequirement"` // 用户原始需求 + MissingRequirements []string `json:"missingRequirements"` // 未满足的需求 + FilesToEnhance map[string][]string `json:"filesToEnhance"` // 需要增强的文件及具体需求 + GeneratedPaths map[string]string `json:"generatedPaths"` // 生成的文件路径 + ExecutionPlan *ExecutionPlan `json:"executionPlan"` // 原始执行计划 +} + +// EnhanceResult 代码增强结果结构 +type EnhanceResult struct { + Success bool `json:"success"` // 是否增强成功 + Message string `json:"message"` // 增强结果说明 + EnhancedFiles []string `json:"enhancedFiles"` // 已增强的文件列表 + Modifications map[string]string `json:"modifications"` // 文件修改详情 + NextReviewNeeded bool `json:"nextReviewNeeded"` // 是否需要再次复检 +} + // New 返回工具注册信息 func (t *AutomationModuleAnalyzer) New() mcp.Tool { return mcp.NewTool("gva_auto_generate", @@ -177,7 +213,7 @@ func (t *AutomationModuleAnalyzer) New() mcp.Tool { "fieldName": "字段名(string)必须大写开头", "fieldDesc": "字段描述(string)", "fieldType": "字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组)", - "fieldJson": "JSON标签(string 必须是小驼峰命名,例:userName)", + "fieldJson": "JSON标签(string)", "dataTypeLong": "数据长度(string)", "comment": "注释(string)", "columnName": "数据库列名(string)", @@ -189,7 +225,7 @@ func (t *AutomationModuleAnalyzer) New() mcp.Tool { "desc": "详情显示(bool)", "excel": "导入导出(bool)", "require": "是否必填(bool)", - "defaultValue": "默认值(string),JSON类型(array,json,file,pictures)请保持为空他们不可以设置默认值", + "defaultValue": "默认值(string)", "errorText": "错误提示(string)", "clearable": "是否可清空(bool)", "sort": "是否排序(bool)", @@ -231,7 +267,7 @@ func (t *AutomationModuleAnalyzer) New() mcp.Tool { - 示例:{"dbName":"gva","table":"sys_users","label":"username","value":"id","association":2,"hasDeletedAt":true}`), mcp.WithString("action", mcp.Required(), - mcp.Description("执行操作:'analyze' 分析现有模块信息,'confirm' 请求用户确认创建,'execute' 执行创建操作(支持批量创建多个模块)"), + mcp.Description("执行操作:'analyze' 分析现有模块信息,'confirm' 请求用户确认创建,'execute' 执行创建操作(支持批量创建多个模块),'review' 代码复检验证是否满足需求,'enhance' AI代码增强补全缺失功能"), ), mcp.WithString("requirement", mcp.Description("用户需求描述(action=analyze时必需)"), @@ -245,6 +281,12 @@ func (t *AutomationModuleAnalyzer) New() mcp.Tool { mcp.WithString("modulesConfirm", mcp.Description("用户对创建模块的确认(action=execute时,如果需要创建模块则必需):'yes' 或 'no'"), ), + mcp.WithObject("reviewRequest", + mcp.Description("代码复检请求(action=review时必需):包含原始需求、执行计划、生成路径等信息"), + ), + mcp.WithObject("enhanceRequest", + mcp.Description("代码增强请求(action=enhance时必需):包含未满足需求、目标文件等信息"), + ), ) } @@ -500,8 +542,12 @@ func (t *AutomationModuleAnalyzer) Handle(ctx context.Context, request mcp.CallT return t.handleConfirm(ctx, request) case "execute": return t.handleExecute(ctx, request) + case "review": + return t.handleReview(ctx, request) + case "enhance": + return t.handleEnhance(ctx, request) default: - return nil, errors.New("无效的操作:action 必须是 'analyze'、'confirm' 或 'execute'") + return nil, errors.New("无效的操作:action 必须是 'analyze'、'confirm'、'execute'、'review' 或 'enhance'") } } @@ -809,7 +855,7 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc "fieldName": "字段名(必须大写开头)", "fieldDesc": "字段描述", "fieldType": "字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组)", - "fieldJson": "json标签(string 必须是小驼峰命名,例:userName)", + "fieldJson": "json标签", "dataTypeLong": "长度", "comment": "注释", "columnName": "数据库列名", @@ -876,6 +922,464 @@ func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mc }, nil } +// performCodeReview 执行代码复检 +func (t *AutomationModuleAnalyzer) performCodeReview(reviewReq *ReviewRequest) (*ReviewResult, error) { + global.GVA_LOG.Info("开始执行代码复检...") + + // 初始化复检结果 + reviewResult := &ReviewResult{ + Success: false, + Message: "", + SatisfiedRequirements: []string{}, + MissingRequirements: []string{}, + FilesToEnhance: make(map[string][]string), + ReviewDetails: make(map[string]interface{}), + } + + // 解析用户需求 + requirements, err := t.parseUserRequirements(reviewReq.OriginalRequirement) + if err != nil { + return nil, fmt.Errorf("解析用户需求失败: %v", err) + } + + // 分析生成的代码文件 + generatedFeatures, err := t.analyzeGeneratedCode(reviewReq.GeneratedPaths, reviewReq.ExecutionPlan) + if err != nil { + return nil, fmt.Errorf("分析生成代码失败: %v", err) + } + + // 对比需求和实现 + for _, requirement := range requirements { + if t.isRequirementSatisfied(requirement, generatedFeatures) { + reviewResult.SatisfiedRequirements = append(reviewResult.SatisfiedRequirements, requirement) + } else { + reviewResult.MissingRequirements = append(reviewResult.MissingRequirements, requirement) + // 确定需要增强的文件 + filesToEnhance := t.determineFilesToEnhance(requirement, reviewReq.ExecutionPlan) + for file, enhancements := range filesToEnhance { + if reviewResult.FilesToEnhance[file] == nil { + reviewResult.FilesToEnhance[file] = []string{} + } + reviewResult.FilesToEnhance[file] = append(reviewResult.FilesToEnhance[file], enhancements...) + } + } + } + + // 设置复检结果 + reviewResult.Success = len(reviewResult.MissingRequirements) == 0 + if reviewResult.Success { + reviewResult.Message = fmt.Sprintf("✅ 代码复检通过!所有 %d 个需求都已满足。", len(reviewResult.SatisfiedRequirements)) + } else { + reviewResult.Message = fmt.Sprintf("⚠️ 代码复检发现问题:满足了 %d 个需求,还有 %d 个需求未满足,需要进行代码增强。", + len(reviewResult.SatisfiedRequirements), len(reviewResult.MissingRequirements)) + } + + // 添加详细信息 + reviewResult.ReviewDetails["totalRequirements"] = len(requirements) + reviewResult.ReviewDetails["satisfiedCount"] = len(reviewResult.SatisfiedRequirements) + reviewResult.ReviewDetails["missingCount"] = len(reviewResult.MissingRequirements) + reviewResult.ReviewDetails["generatedFeatures"] = generatedFeatures + reviewResult.ReviewDetails["reviewTime"] = time.Now().Format("2006-01-02 15:04:05") + + global.GVA_LOG.Info(fmt.Sprintf("代码复检完成:%s", reviewResult.Message)) + return reviewResult, nil +} + +// performCodeEnhancement 执行代码增强 +func (t *AutomationModuleAnalyzer) performCodeEnhancement(enhanceReq *EnhanceRequest) (*EnhanceResult, error) { + global.GVA_LOG.Info("开始执行代码增强...") + + // 初始化增强结果 + enhanceResult := &EnhanceResult{ + Success: false, + Message: "", + EnhancedFiles: []string{}, + Modifications: make(map[string]string), + NextReviewNeeded: true, + } + + // 为每个需要增强的文件生成代码 + for filePath, requirements := range enhanceReq.FilesToEnhance { + global.GVA_LOG.Info(fmt.Sprintf("正在增强文件: %s", filePath)) + + // 读取现有文件内容 + existingContent, err := t.readFileContent(filePath) + if err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("读取文件失败 %s: %v", filePath, err)) + continue + } + + // 生成增强代码 + enhancedContent, err := t.generateEnhancedCode(filePath, existingContent, requirements, enhanceReq) + if err != nil { + global.GVA_LOG.Error(fmt.Sprintf("生成增强代码失败 %s: %v", filePath, err)) + continue + } + + // 写入增强后的代码 + if err := t.writeFileContent(filePath, enhancedContent); err != nil { + global.GVA_LOG.Error(fmt.Sprintf("写入增强代码失败 %s: %v", filePath, err)) + continue + } + + enhanceResult.EnhancedFiles = append(enhanceResult.EnhancedFiles, filePath) + enhanceResult.Modifications[filePath] = fmt.Sprintf("增强了以下功能: %s", strings.Join(requirements, ", ")) + global.GVA_LOG.Info(fmt.Sprintf("文件增强完成: %s", filePath)) + } + + // 设置增强结果 + if len(enhanceResult.EnhancedFiles) > 0 { + enhanceResult.Success = true + enhanceResult.Message = fmt.Sprintf("✅ 代码增强完成!成功增强了 %d 个文件,建议进行再次复检验证。", len(enhanceResult.EnhancedFiles)) + } else { + enhanceResult.Message = "❌ 代码增强失败:没有成功增强任何文件。" + enhanceResult.NextReviewNeeded = false + } + + global.GVA_LOG.Info(fmt.Sprintf("代码增强完成:%s", enhanceResult.Message)) + return enhanceResult, nil +} + +// parseUserRequirements 解析用户需求为具体的功能点 +func (t *AutomationModuleAnalyzer) parseUserRequirements(requirement string) ([]string, error) { + // 简单的需求解析逻辑,可以根据实际情况扩展 + requirements := []string{} + + // 按句号或换行分割需求 + lines := strings.Split(requirement, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line != "" { + // 进一步按句号分割 + sentences := strings.Split(line, "。") + for _, sentence := range sentences { + sentence = strings.TrimSpace(sentence) + if sentence != "" && len(sentence) > 3 { + requirements = append(requirements, sentence) + } + } + } + } + + // 如果没有解析出具体需求,将整个需求作为一个功能点 + if len(requirements) == 0 { + requirements = append(requirements, strings.TrimSpace(requirement)) + } + + return requirements, nil +} + +// analyzeGeneratedCode 分析生成的代码文件,提取已实现的功能 +func (t *AutomationModuleAnalyzer) analyzeGeneratedCode(generatedPaths map[string]string, executionPlan *ExecutionPlan) ([]string, error) { + features := []string{} + + // 基于执行计划分析基础功能 + if executionPlan != nil && executionPlan.ModulesInfo != nil { + for _, moduleInfo := range executionPlan.ModulesInfo { + // 基础CRUD功能 + features = append(features, fmt.Sprintf("%s的增删改查功能", moduleInfo.Description)) + + // 字段相关功能 + for _, field := range moduleInfo.Fields { + if field.FieldSearchType != "" { + features = append(features, fmt.Sprintf("%s的%s搜索功能", field.FieldDesc, field.FieldSearchType)) + } + if field.DictType != "" { + features = append(features, fmt.Sprintf("%s的字典选择功能", field.FieldDesc)) + } + if field.DataSource != nil { + features = append(features, fmt.Sprintf("%s的关联数据功能", field.FieldDesc)) + } + } + + // 特殊功能 + if moduleInfo.IsTree { + features = append(features, fmt.Sprintf("%s的树形结构功能", moduleInfo.Description)) + } + if moduleInfo.AutoCreateApiToSql { + features = append(features, fmt.Sprintf("%s的API接口功能", moduleInfo.Description)) + } + if moduleInfo.AutoCreateMenuToSql { + features = append(features, fmt.Sprintf("%s的菜单管理功能", moduleInfo.Description)) + } + } + } + + // 分析生成的文件 + for path := range generatedPaths { + if strings.Contains(path, "api") { + features = append(features, "API接口层") + } + if strings.Contains(path, "service") { + features = append(features, "业务逻辑层") + } + if strings.Contains(path, "model") { + features = append(features, "数据模型层") + } + if strings.Contains(path, "router") { + features = append(features, "路由配置") + } + if strings.Contains(path, ".vue") { + features = append(features, "前端页面") + } + } + + return features, nil +} + +// isRequirementSatisfied 判断需求是否已被满足 +func (t *AutomationModuleAnalyzer) isRequirementSatisfied(requirement string, generatedFeatures []string) bool { + // 简单的关键词匹配逻辑 + requirement = strings.ToLower(requirement) + + // 检查是否包含基础CRUD关键词 + crudKeywords := []string{"增加", "创建", "新增", "删除", "修改", "编辑", "查询", "搜索", "列表"} + for _, keyword := range crudKeywords { + if strings.Contains(requirement, keyword) { + // 检查生成的功能中是否有对应的CRUD功能 + for _, feature := range generatedFeatures { + if strings.Contains(strings.ToLower(feature), "增删改查") { + return true + } + } + } + } + + // 检查特殊功能关键词 + specialKeywords := map[string][]string{ + "树形": {"树形结构"}, + "层级": {"树形结构"}, + "字典": {"字典选择"}, + "下拉": {"字典选择"}, + "关联": {"关联数据"}, + "外键": {"关联数据"}, + } + + for reqKeyword, featureKeywords := range specialKeywords { + if strings.Contains(requirement, reqKeyword) { + for _, feature := range generatedFeatures { + for _, featureKeyword := range featureKeywords { + if strings.Contains(strings.ToLower(feature), featureKeyword) { + return true + } + } + } + } + } + + // 默认认为基础需求已满足(可以根据实际情况调整) + return len(generatedFeatures) > 0 +} + +// determineFilesToEnhance 确定需要增强的文件 +func (t *AutomationModuleAnalyzer) determineFilesToEnhance(requirement string, executionPlan *ExecutionPlan) map[string][]string { + filesToEnhance := make(map[string][]string) + + if executionPlan == nil || executionPlan.ModulesInfo == nil { + return filesToEnhance + } + + // 根据需求类型确定需要修改的文件 + requirement = strings.ToLower(requirement) + + for _, moduleInfo := range executionPlan.ModulesInfo { + packagePath := fmt.Sprintf("%s/%s", executionPlan.PackageName, moduleInfo.PackageName) + + // API相关需求 + if strings.Contains(requirement, "接口") || strings.Contains(requirement, "api") { + apiFile := fmt.Sprintf("api/v1/%s/%s.go", packagePath, moduleInfo.PackageName) + filesToEnhance[apiFile] = append(filesToEnhance[apiFile], requirement) + } + + // 业务逻辑相关需求 + if strings.Contains(requirement, "逻辑") || strings.Contains(requirement, "业务") || strings.Contains(requirement, "流程") { + serviceFile := fmt.Sprintf("service/%s/%s.go", packagePath, moduleInfo.PackageName) + filesToEnhance[serviceFile] = append(filesToEnhance[serviceFile], requirement) + } + + // 数据模型相关需求 + if strings.Contains(requirement, "字段") || strings.Contains(requirement, "模型") || strings.Contains(requirement, "数据") { + modelFile := fmt.Sprintf("model/%s/%s.go", packagePath, moduleInfo.PackageName) + filesToEnhance[modelFile] = append(filesToEnhance[modelFile], requirement) + } + + // 前端相关需求 + if strings.Contains(requirement, "页面") || strings.Contains(requirement, "界面") || strings.Contains(requirement, "前端") { + vueFile := fmt.Sprintf("web/src/view/%s/%s.vue", packagePath, moduleInfo.PackageName) + filesToEnhance[vueFile] = append(filesToEnhance[vueFile], requirement) + } + + // 默认情况:如果无法确定具体文件,则增强service层 + if len(filesToEnhance) == 0 { + serviceFile := fmt.Sprintf("service/%s/%s.go", packagePath, moduleInfo.PackageName) + filesToEnhance[serviceFile] = append(filesToEnhance[serviceFile], requirement) + } + } + + return filesToEnhance +} + +// readFileContent 读取文件内容 +func (t *AutomationModuleAnalyzer) readFileContent(filePath string) (string, error) { + // 构建完整的文件路径 + fullPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, filePath) + + // 检查文件是否存在 + if _, err := os.Stat(fullPath); os.IsNotExist(err) { + return "", fmt.Errorf("文件不存在: %s", fullPath) + } + + // 读取文件内容 + content, err := os.ReadFile(fullPath) + if err != nil { + return "", fmt.Errorf("读取文件失败: %v", err) + } + + return string(content), nil +} + +// writeFileContent 写入文件内容 +func (t *AutomationModuleAnalyzer) writeFileContent(filePath, content string) error { + // 构建完整的文件路径 + fullPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, filePath) + + // 确保目录存在 + dir := filepath.Dir(fullPath) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("创建目录失败: %v", err) + } + + // 写入文件 + if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil { + return fmt.Errorf("写入文件失败: %v", err) + } + + return nil +} + +// generateEnhancedCode 生成增强代码 +func (t *AutomationModuleAnalyzer) generateEnhancedCode(filePath, existingContent string, requirements []string, enhanceReq *EnhanceRequest) (string, error) { + // 这里是AI代码生成的核心逻辑 + // 目前提供一个基础实现,后续可以集成真正的AI服务 + + // 构建增强提示 + enhancementPrompt := fmt.Sprintf(` +请根据以下需求对代码进行增强: + +原始需求:%s + +未满足的具体需求: +%s + +当前文件路径:%s + +现有代码: +%s + +请生成增强后的完整代码,确保: +1. 保持现有代码的结构和功能 +2. 添加缺失的功能实现 +3. 遵循Go语言最佳实践 +4. 添加适当的注释说明 +`, + enhanceReq.OriginalRequirement, + strings.Join(requirements, "\n- "), + filePath, + existingContent) + + // 简单的代码增强逻辑(实际项目中应该调用AI服务) + enhancedContent := existingContent + + // 添加增强注释 + enhancementComment := fmt.Sprintf(` +// 代码增强 - %s +// 增强时间: %s +// 增强需求: %s +`, + time.Now().Format("2006-01-02 15:04:05"), + time.Now().Format("2006-01-02 15:04:05"), + strings.Join(requirements, ", ")) + + // 在文件末尾添加增强注释和占位符代码 + enhancedContent += enhancementComment + enhancedContent += ` +// TODO: 请根据上述需求完善以下代码实现 +` + + for _, requirement := range requirements { + enhancedContent += fmt.Sprintf(` +// TODO: 实现需求 - %s +func (s *%sService) enhance%s() error { + // 请在此处添加具体实现 + return nil +} +`, + requirement, + strings.Title(filepath.Base(filePath)), + strings.ReplaceAll(requirement, " ", "")) + } + + global.GVA_LOG.Info(fmt.Sprintf("生成增强代码提示:%s", enhancementPrompt)) + + return enhancedContent, nil +} + +// collectExpectedFilePaths 收集预期生成的文件路径 +func (t *AutomationModuleAnalyzer) collectExpectedFilePaths(plan *ExecutionPlan) []string { + var filePaths []string + + if plan == nil || plan.ModulesInfo == nil { + return filePaths + } + + // 遍历所有模块,收集预期生成的文件路径 + for _, moduleInfo := range plan.ModulesInfo { + if moduleInfo == nil { + continue + } + + // 构建包路径 + packagePath := "" + if plan.PackageType == "plugin" { + packagePath = fmt.Sprintf("plugin/%s", plan.PackageName) + } else { + packagePath = plan.PackageName + } + + // 后端文件路径 + if moduleInfo.GenerateServer { + // API文件 + apiPath := fmt.Sprintf("api/v1/%s/%s.go", packagePath, moduleInfo.PackageName) + filePaths = append(filePaths, apiPath) + + // Service文件 + servicePath := fmt.Sprintf("service/%s/%s.go", packagePath, moduleInfo.PackageName) + filePaths = append(filePaths, servicePath) + + // Model文件 + modelPath := fmt.Sprintf("model/%s/%s.go", packagePath, moduleInfo.PackageName) + filePaths = append(filePaths, modelPath) + + // Router文件 + routerPath := fmt.Sprintf("router/%s/%s.go", packagePath, moduleInfo.PackageName) + filePaths = append(filePaths, routerPath) + } + + // 前端文件路径 + if moduleInfo.GenerateWeb { + // Vue页面文件 + vuePath := fmt.Sprintf("web/src/view/%s/%s.vue", packagePath, moduleInfo.PackageName) + filePaths = append(filePaths, vuePath) + + // API接口文件 + webApiPath := fmt.Sprintf("web/src/api/%s.js", moduleInfo.PackageName) + filePaths = append(filePaths, webApiPath) + } + } + + return filePaths +} + // handleConfirm 处理确认请求 func (t *AutomationModuleAnalyzer) handleConfirm(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { executionPlanData, ok := request.GetArguments()["executionPlan"] @@ -1020,6 +1524,30 @@ func (t *AutomationModuleAnalyzer) handleExecute(ctx context.Context, request mc // 执行创建操作 result := t.executeCreation(ctx, &plan) + // 检查是否需要获取原始需求进行代码复检 + var originalRequirement string + if reqData, ok := request.GetArguments()["requirement"]; ok { + if reqStr, ok := reqData.(string); ok { + originalRequirement = reqStr + } + } + + // 如果执行成功且有原始需求,自动触发代码复检 + var reviewMessage string + if result.Success && originalRequirement != "" { + global.GVA_LOG.Info("执行完成,返回生成的文件路径供AI进行代码复检...") + + // 构建文件路径信息供AI使用 + var pathsInfo []string + for _, path := range result.GeneratedPaths { + pathsInfo = append(pathsInfo, fmt.Sprintf("- %s", path)) + } + + reviewMessage = fmt.Sprintf("\n\n📁 已生成以下文件:\n%s\n\n💡 提示:请使用 'review' 操作对生成的代码进行复检,确保满足原始需求。", strings.Join(pathsInfo, "\n")) + } else if originalRequirement == "" { + reviewMessage = "\n\n💡 提示:如需代码复检,请使用 'review' 操作并提供原始需求描述。" + } + resultJSON, err := json.MarshalIndent(result, "", " ") if err != nil { return nil, fmt.Errorf("序列化结果失败: %v", err) @@ -1040,7 +1568,7 @@ func (t *AutomationModuleAnalyzer) handleExecute(ctx context.Context, request mc Content: []mcp.Content{ mcp.TextContent{ Type: "text", - Text: fmt.Sprintf("执行结果:\n\n%s%s", string(resultJSON), permissionReminder), + Text: fmt.Sprintf("执行结果:\n\n%s%s%s", string(resultJSON), reviewMessage, permissionReminder), }, }, }, nil @@ -1063,6 +1591,16 @@ func (t *AutomationModuleAnalyzer) isSystemFunction(requirement string) bool { return false } +// contains 检查字符串切片中是否包含指定元素 +func contains(slice []string, item string) bool { + for _, s := range slice { + if s == item { + return true + } + } + return false +} + // buildDirectoryStructure 构建目录结构信息 func (t *AutomationModuleAnalyzer) buildDirectoryStructure(plan *ExecutionPlan) map[string]string { paths := make(map[string]string) @@ -1245,6 +1783,13 @@ func (t *AutomationModuleAnalyzer) validateExecutionPlan(plan *ExecutionPlan) er return fmt.Errorf("模块 %d 字段 %d 的 fieldName 不能为空", moduleIndex+1, i+1) } + // 确保字段名首字母大写 + if len(field.FieldName) > 0 { + firstChar := string(field.FieldName[0]) + if firstChar >= "a" && firstChar <= "z" { + moduleInfo.Fields[i].FieldName = strings.ToUpper(firstChar) + field.FieldName[1:] + } + } if field.FieldDesc == "" { return fmt.Errorf("模块 %d 字段 %d 的 fieldDesc 不能为空", moduleIndex+1, i+1) } @@ -1258,48 +1803,6 @@ func (t *AutomationModuleAnalyzer) validateExecutionPlan(plan *ExecutionPlan) er return fmt.Errorf("模块 %d 字段 %d 的 columnName 不能为空", moduleIndex+1, i+1) } - // 确保字段名首字母大写 - if len(field.FieldName) > 0 { - firstChar := string(field.FieldName[0]) - if firstChar >= "a" && firstChar <= "z" { - moduleInfo.Fields[i].FieldName = strings.ToUpper(firstChar) + field.FieldName[1:] - } - } - - // 确保FieldJson使用小驼峰命名 - if len(field.FieldJson) > 0 { - // 处理下划线命名转小驼峰 - if strings.Contains(field.FieldJson, "_") { - parts := strings.Split(field.FieldJson, "_") - camelCase := strings.ToLower(parts[0]) - for j := 1; j < len(parts); j++ { - if len(parts[j]) > 0 { - camelCase += strings.ToUpper(string(parts[j][0])) + strings.ToLower(parts[j][1:]) - } - } - moduleInfo.Fields[i].FieldJson = camelCase - } else { - // 处理首字母大写转小写 - firstChar := string(field.FieldJson[0]) - if firstChar >= "A" && firstChar <= "Z" { - moduleInfo.Fields[i].FieldJson = strings.ToLower(firstChar) + field.FieldJson[1:] - } - } - } - - // 确保ColumnName使用下划线命名 - if len(field.ColumnName) > 0 { - // 将驼峰命名转换为下划线命名 - var result strings.Builder - for i, r := range field.ColumnName { - if i > 0 && r >= 'A' && r <= 'Z' { - result.WriteRune('_') - } - result.WriteRune(unicode.ToLower(r)) - } - moduleInfo.Fields[i].ColumnName = result.String() - } - // 验证字段类型 validFieldTypes := []string{"string", "int", "int64", "float64", "bool", "time.Time", "enum", "picture", "video", "file", "pictures", "array", "richtext", "json"} validType := false @@ -1361,13 +1864,17 @@ func (t *AutomationModuleAnalyzer) validateExecutionPlan(plan *ExecutionPlan) er // executeCreation 执行创建操作 func (t *AutomationModuleAnalyzer) executeCreation(ctx context.Context, plan *ExecutionPlan) *ExecutionResult { result := &ExecutionResult{ - Success: false, - Paths: make(map[string]string), + Success: false, + Paths: make(map[string]string), + GeneratedPaths: []string{}, // 初始化生成文件路径列表 } // 无论如何都先构建目录结构信息,确保paths始终返回 result.Paths = t.buildDirectoryStructure(plan) + // 记录预期生成的文件路径 + result.GeneratedPaths = t.collectExpectedFilePaths(plan) + if !plan.NeedCreatedModules { result.Success = true result.Message += "已列出当前功能所涉及的目录结构信息; 请在paths中查看; 并且在对应指定文件中实现相关的业务逻辑; " @@ -1656,13 +2163,13 @@ func (t *AutomationModuleAnalyzer) isPackageFolderEmpty(packageName, template st // removeEmptyPackageFolder 删除空的包文件夹 func (t *AutomationModuleAnalyzer) removeEmptyPackageFolder(packageName, template string) error { - var errors []string + var errorMessages []string if template == "plugin" { // plugin 类型只删除 plugin 目录下的文件夹 basePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", packageName) if err := t.removeDirectoryIfExists(basePath); err != nil { - errors = append(errors, fmt.Sprintf("删除plugin文件夹失败: %v", err)) + errorMessages = append(errorMessages, fmt.Sprintf("删除plugin文件夹失败: %v", err)) } } else { // package 类型需要删除多个目录下的相关文件 @@ -1675,13 +2182,13 @@ func (t *AutomationModuleAnalyzer) removeEmptyPackageFolder(packageName, templat for _, path := range paths { if err := t.removeDirectoryIfExists(path); err != nil { - errors = append(errors, fmt.Sprintf("删除%s失败: %v", path, err)) + errorMessages = append(errorMessages, fmt.Sprintf("删除%s失败: %v", path, err)) } } } - if len(errors) > 0 { - return fmt.Errorf("删除过程中出现错误: %s", strings.Join(errors, "; ")) + if len(errorMessages) > 0 { + return fmt.Errorf("删除过程中出现错误: %s", strings.Join(errorMessages, "; ")) } return nil @@ -1753,3 +2260,106 @@ func (t *AutomationModuleAnalyzer) cleanupRelatedApiAndMenus(historyIDs []uint) return nil } + +// handleReview 处理代码复检请求 +func (t *AutomationModuleAnalyzer) handleReview(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // 解析复检请求参数 + reviewRequestData, ok := request.GetArguments()["reviewRequest"].(map[string]interface{}) + if !ok { + return nil, errors.New("参数错误:reviewRequest 必须是有效的对象") + } + + // 转换为ReviewRequest结构体 + reviewRequestJSON, err := json.Marshal(reviewRequestData) + if err != nil { + return nil, fmt.Errorf("序列化reviewRequest失败: %v", err) + } + + var reviewReq ReviewRequest + if err := json.Unmarshal(reviewRequestJSON, &reviewReq); err != nil { + return nil, fmt.Errorf("解析reviewRequest失败: %v", err) + } + + // 验证必需参数 + if reviewReq.OriginalRequirement == "" { + return nil, errors.New("参数错误:originalRequirement 不能为空") + } + if reviewReq.ExecutionPlan == nil { + return nil, errors.New("参数错误:executionPlan 不能为空") + } + + // 设置默认最大重试次数 + if reviewReq.MaxRetries <= 0 { + reviewReq.MaxRetries = 3 + } + + // 执行代码复检 + reviewResult, err := t.performCodeReview(&reviewReq) + if err != nil { + return nil, fmt.Errorf("代码复检失败: %v", err) + } + + // 返回复检结果 + resultJSON, err := json.Marshal(reviewResult) + if err != nil { + return nil, fmt.Errorf("序列化复检结果失败: %v", err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(resultJSON), + }, + }, + }, nil +} + +// handleEnhance 处理代码增强请求 +func (t *AutomationModuleAnalyzer) handleEnhance(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // 解析增强请求参数 + enhanceRequestData, ok := request.GetArguments()["enhanceRequest"].(map[string]interface{}) + if !ok { + return nil, errors.New("参数错误:enhanceRequest 必须是有效的对象") + } + + // 转换为EnhanceRequest结构体 + enhanceRequestJSON, err := json.Marshal(enhanceRequestData) + if err != nil { + return nil, fmt.Errorf("序列化enhanceRequest失败: %v", err) + } + + var enhanceReq EnhanceRequest + if err := json.Unmarshal(enhanceRequestJSON, &enhanceReq); err != nil { + return nil, fmt.Errorf("解析enhanceRequest失败: %v", err) + } + + // 验证必需参数 + if enhanceReq.OriginalRequirement == "" { + return nil, errors.New("参数错误:originalRequirement 不能为空") + } + if len(enhanceReq.MissingRequirements) == 0 { + return nil, errors.New("参数错误:missingRequirements 不能为空") + } + + // 执行代码增强 + enhanceResult, err := t.performCodeEnhancement(&enhanceReq) + if err != nil { + return nil, fmt.Errorf("代码增强失败: %v", err) + } + + // 返回增强结果 + resultJSON, err := json.Marshal(enhanceResult) + if err != nil { + return nil, fmt.Errorf("序列化增强结果失败: %v", err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(resultJSON), + }, + }, + }, nil +} diff --git a/server/mcp/requirement_analyzer.go b/server/mcp/requirement_analyzer.go index 541b925ba3..31945d056e 100644 --- a/server/mcp/requirement_analyzer.go +++ b/server/mcp/requirement_analyzer.go @@ -20,8 +20,6 @@ type RequirementAnalysisRequest struct { UserRequirement string `json:"userRequirement"` } - - // RequirementAnalysisResponse 需求分析响应 type RequirementAnalysisResponse struct { AIPrompt string `json:"aiPrompt"` // 给AI的提示词 @@ -39,12 +37,13 @@ func (t *RequirementAnalyzer) New() mcp.Tool { **📋 工作流程:** 1. 接收用户自然语言需求描述 -2. 生成专业的AI提示词,要求AI将需求梳理为清晰的逻辑步骤: - - **1. 第一步功能描述** - - **2. 第二步功能描述** - - **3. 第三步功能描述** +2. 生成专业的AI提示词,要求AI将需求梳理为清晰的步骤需求字段: + - **1. 第一步功能需要的字段** + - **2. 第二步功能需要的字段** + - **3. 第三步功能需要的字段** - **...** -3. 指导后续使用 gva_auto_generate 工具进行代码生成 +3. 需要清楚描述出这些需求需要的字段有哪些,如果用户提供了字段内容或者sql文件,一定不要发散思维,一定使用用户提供的字段。 +4. 指导后续使用 gva_auto_generate 工具进行代码生成 **✅ 适用场景:** - 用户有新的业务需求需要开发 From 5135b8cbf152dedcd9ea1f74d56307d7a52390a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?piexlMax=28=E5=A5=87=E6=B7=BC?= Date: Tue, 9 Sep 2025 18:46:29 +0800 Subject: [PATCH 02/33] =?UTF-8?q?fix:=20=E8=B0=83=E6=95=B4mcp=E6=95=B4?= =?UTF-8?q?=E4=BD=93=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/mcp/execution_plan_schema.md | 529 ------ server/mcp/gag_usage_example.md | 205 --- server/mcp/gva_analyze.go | 517 ++++++ server/mcp/gva_auto_generate.go | 2365 --------------------------- server/mcp/gva_execute.go | 806 +++++++++ server/mcp/requirement_analyzer.go | 14 +- 6 files changed, 1329 insertions(+), 3107 deletions(-) delete mode 100644 server/mcp/execution_plan_schema.md delete mode 100644 server/mcp/gag_usage_example.md create mode 100644 server/mcp/gva_analyze.go delete mode 100644 server/mcp/gva_auto_generate.go create mode 100644 server/mcp/gva_execute.go diff --git a/server/mcp/execution_plan_schema.md b/server/mcp/execution_plan_schema.md deleted file mode 100644 index 622e83d099..0000000000 --- a/server/mcp/execution_plan_schema.md +++ /dev/null @@ -1,529 +0,0 @@ -# ExecutionPlan 结构体格式说明 - -## 概述 -ExecutionPlan 是用于自动化模块创建的执行计划结构体,包含了创建包和模块所需的所有信息。 - -## 完整结构体定义 - -```go -type ExecutionPlan struct { - PackageName string `json:"packageName"` // 包名,如:"user", "order", "product" - PackageType string `json:"packageType"` // "plugin" 或 "package" - NeedCreatedPackage bool `json:"needCreatedPackage"` // 是否需要创建包 - NeedCreatedModules bool `json:"needCreatedModules"` // 是否需要创建模块 - PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"` // 包信息(当NeedCreatedPackage=true时必需) - ModulesInfo []*request.AutoCode `json:"modulesInfo,omitempty"` // 模块信息数组(当NeedCreatedModules=true时必需,支持批量创建) - Paths map[string]string `json:"paths,omitempty"` // 路径信息 -} -``` - -## 子结构体详细说明 - -### 1. SysAutoCodePackageCreate 结构体 - -```go -type SysAutoCodePackageCreate struct { - Desc string `json:"desc"` // 描述,如:"用户管理模块" - Label string `json:"label"` // 展示名,如:"用户管理" - Template string `json:"template"` // 模板类型:"plugin" 或 "package" - PackageName string `json:"packageName"` // 包名,如:"user" - Module string `json:"-"` // 模块名(自动填充,无需设置) -} -``` - -### 2. AutoCode 结构体(核心字段) - -```go -type AutoCode struct { - Package string `json:"package"` // 包名 - TableName string `json:"tableName"` // 数据库表名 - BusinessDB string `json:"businessDB"` // 业务数据库名 - StructName string `json:"structName"` // 结构体名称 - PackageName string `json:"packageName"` // 文件名称 - Description string `json:"description"` // 结构体中文名称 - Abbreviation string `json:"abbreviation"` // 结构体简称 - HumpPackageName string `json:"humpPackageName"` // 驼峰命名的包名 - GvaModel bool `json:"gvaModel"` // 是否使用GVA默认Model - AutoMigrate bool `json:"autoMigrate"` // 是否自动迁移表结构 - AutoCreateResource bool `json:"autoCreateResource"` // 是否自动创建资源标识 - AutoCreateApiToSql bool `json:"autoCreateApiToSql"` // 是否自动创建API - AutoCreateMenuToSql bool `json:"autoCreateMenuToSql"` // 是否自动创建菜单 - AutoCreateBtnAuth bool `json:"autoCreateBtnAuth"` // 是否自动创建按钮权限 - OnlyTemplate bool `json:"onlyTemplate"` // 是否只生成模板 - IsTree bool `json:"isTree"` // 是否树形结构 - TreeJson string `json:"treeJson"` // 树形结构JSON字段 - IsAdd bool `json:"isAdd"` // 是否新增 - Fields []*AutoCodeField `json:"fields"` // 字段列表 - GenerateWeb bool `json:"generateWeb"` // 是否生成前端代码 - GenerateServer bool `json:"generateServer"` // 是否生成后端代码 - Module string `json:"-"` // 模块(自动填充) - DictTypes []string `json:"-"` // 字典类型(自动填充) -} -``` - -### 3. AutoCodeField 结构体(字段定义) - -```go -type AutoCodeField struct { - FieldName string `json:"fieldName"` // 字段名 - FieldDesc string `json:"fieldDesc"` // 字段中文描述 - FieldType string `json:"fieldType"` // 字段类型:string, int, bool, time.Time等 - FieldJson string `json:"fieldJson"` // JSON标签名 - DataTypeLong string `json:"dataTypeLong"` // 数据库字段长度 - Comment string `json:"comment"` // 数据库字段注释 - ColumnName string `json:"columnName"` // 数据库列名 - FieldSearchType string `json:"fieldSearchType"` // 搜索类型:EQ, LIKE, BETWEEN等 - FieldSearchHide bool `json:"fieldSearchHide"` // 是否隐藏查询条件 - DictType string `json:"dictType"` // 字典类型 - Form bool `json:"form"` // 是否在表单中显示 - Table bool `json:"table"` // 是否在表格中显示 - Desc bool `json:"desc"` // 是否在详情中显示 - Excel bool `json:"excel"` // 是否支持导入导出 - Require bool `json:"require"` // 是否必填 - DefaultValue string `json:"defaultValue"` // 默认值 - ErrorText string `json:"errorText"` // 校验失败提示 - Clearable bool `json:"clearable"` // 是否可清空 - Sort bool `json:"sort"` // 是否支持排序 - PrimaryKey bool `json:"primaryKey"` // 是否主键 - DataSource *DataSource `json:"dataSource"` // 数据源配置(用于关联其他表) - CheckDataSource bool `json:"checkDataSource"` // 是否检查数据源 - FieldIndexType string `json:"fieldIndexType"` // 索引类型 -} -``` - -### 4. DataSource 结构体(关联表配置) - -```go -type DataSource struct { - DBName string `json:"dbName"` // 关联的数据库名称 - Table string `json:"table"` // 关联的表名 - Label string `json:"label"` // 用于显示的字段名(如name、title等) - Value string `json:"value"` // 用于存储的值字段名(通常是id) - Association int `json:"association"` // 关联关系:1=一对一,2=一对多 - HasDeletedAt bool `json:"hasDeletedAt"` // 关联表是否有软删除字段 -} -``` - -## 使用示例 - -### 示例1:创建新包和批量创建多个模块 - -```json -{ - "packageName": "user", - "packageType": "package", - "needCreatedPackage": true, - "needCreatedModules": true, - "packageInfo": { - "desc": "用户管理模块", - "label": "用户管理", - "template": "package", - "packageName": "user" - }, - "modulesInfo": [ - { - "package": "user", - "tableName": "sys_users", - "businessDB": "", - "structName": "User", - "packageName": "user", - "description": "用户", - "abbreviation": "user", - "humpPackageName": "user", - "gvaModel": true, - "autoMigrate": true, - "autoCreateResource": true, - "autoCreateApiToSql": true, - "autoCreateMenuToSql": true, - "autoCreateBtnAuth": true, - "onlyTemplate": false, - "isTree": false, - "treeJson": "", - "isAdd": true, - "generateWeb": true, - "generateServer": true, - "fields": [ - { - "fieldName": "Username", - "fieldDesc": "用户名", - "fieldType": "string", - "fieldJson": "username", - "dataTypeLong": "50", - "comment": "用户名", - "columnName": "username", - "fieldSearchType": "LIKE", - "fieldSearchHide": false, - "dictType": "", - "form": true, - "table": true, - "desc": true, - "excel": true, - "require": true, - "defaultValue": "", - "errorText": "请输入用户名", - "clearable": true, - "sort": false, - "primaryKey": false, - "dataSource": { - "dbName": "gva", - "table": "sys_users", - "label": "username", - "value": "id", - "association": 2, - "hasDeletedAt": true - }, - "checkDataSource": true, - "fieldIndexType": "" - }, - { - "fieldName": "Email", - "fieldDesc": "邮箱", - "fieldType": "string", - "fieldJson": "email", - "dataTypeLong": "100", - "comment": "邮箱地址", - "columnName": "email", - "fieldSearchType": "EQ", - "fieldSearchHide": false, - "dictType": "", - "form": true, - "table": true, - "desc": true, - "excel": true, - "require": true, - "defaultValue": "", - "errorText": "请输入邮箱", - "clearable": true, - "sort": false, - "primaryKey": false, - "dataSource": null, - "checkDataSource": false, - "fieldIndexType": "index" - } - ] - }, - { - "package": "user", - "tableName": "user_profiles", - "businessDB": "", - "structName": "UserProfile", - "packageName": "user", - "description": "用户档案", - "abbreviation": "userProfile", - "humpPackageName": "user", - "gvaModel": true, - "autoMigrate": true, - "autoCreateResource": true, - "autoCreateApiToSql": true, - "autoCreateMenuToSql": true, - "autoCreateBtnAuth": true, - "onlyTemplate": false, - "isTree": false, - "treeJson": "", - "isAdd": true, - "generateWeb": true, - "generateServer": true, - "fields": [ - { - "fieldName": "UserID", - "fieldDesc": "用户ID", - "fieldType": "int", - "fieldJson": "userId", - "dataTypeLong": "", - "comment": "关联用户ID", - "columnName": "user_id", - "fieldSearchType": "EQ", - "fieldSearchHide": false, - "dictType": "", - "form": true, - "table": true, - "desc": true, - "excel": true, - "require": true, - "defaultValue": "", - "errorText": "请选择用户", - "clearable": true, - "sort": false, - "primaryKey": false, - "dataSource": null, - "checkDataSource": false, - "fieldIndexType": "index" - }, - { - "fieldName": "Avatar", - "fieldDesc": "头像", - "fieldType": "string", - "fieldJson": "avatar", - "dataTypeLong": "255", - "comment": "用户头像URL", - "columnName": "avatar", - "fieldSearchType": "", - "fieldSearchHide": true, - "dictType": "", - "form": true, - "table": true, - "desc": true, - "excel": false, - "require": false, - "defaultValue": "", - "errorText": "", - "clearable": true, - "sort": false, - "primaryKey": false, - "dataSource": null, - "checkDataSource": false, - "fieldIndexType": "" - } - ] - } - ] -} -``` - -### 示例2:仅在现有包中批量创建多个模块 - -```json -{ - "packageName": "system", - "packageType": "package", - "needCreatedPackage": false, - "needCreatedModules": true, - "packageInfo": null, - "modulesInfo": [ - { - "package": "system", - "tableName": "sys_roles", - "businessDB": "", - "structName": "Role", - "packageName": "system", - "description": "角色", - "abbreviation": "role", - "humpPackageName": "system", - "gvaModel": true, - "autoMigrate": true, - "autoCreateResource": true, - "autoCreateApiToSql": true, - "autoCreateMenuToSql": true, - "autoCreateBtnAuth": true, - "onlyTemplate": false, - "isTree": false, - "generateWeb": true, - "generateServer": true, - "fields": [ - { - "fieldName": "RoleName", - "fieldDesc": "角色名称", - "fieldType": "string", - "fieldJson": "roleName", - "dataTypeLong": "50", - "comment": "角色名称", - "columnName": "role_name", - "fieldSearchType": "LIKE", - "form": true, - "table": true, - "desc": true, - "require": true - } - ] - }, - { - "package": "system", - "tableName": "sys_permissions", - "businessDB": "", - "structName": "Permission", - "packageName": "system", - "description": "权限", - "abbreviation": "permission", - "humpPackageName": "system", - "gvaModel": true, - "autoMigrate": true, - "autoCreateResource": true, - "autoCreateApiToSql": true, - "autoCreateMenuToSql": true, - "autoCreateBtnAuth": true, - "onlyTemplate": false, - "isTree": false, - "generateWeb": true, - "generateServer": true, - "fields": [ - { - "fieldName": "PermissionName", - "fieldDesc": "权限名称", - "fieldType": "string", - "fieldJson": "permissionName", - "dataTypeLong": "100", - "comment": "权限名称", - "columnName": "permission_name", - "fieldSearchType": "LIKE", - "form": true, - "table": true, - "desc": true, - "require": true - }, - { - "fieldName": "PermissionCode", - "fieldDesc": "权限代码", - "fieldType": "string", - "fieldJson": "permissionCode", - "dataTypeLong": "50", - "comment": "权限代码", - "columnName": "permission_code", - "fieldSearchType": "=", - "form": true, - "table": true, - "desc": true, - "require": true - } - ] - } - ] -} -``` - -### 示例3:模块关联关系配置详解 - -以下示例展示了如何配置不同类型的关联关系: - -```json -{ - "packageName": "order", - "packageType": "package", - "needCreatedPackage": true, - "needCreatedModules": true, - "packageInfo": { - "desc": "订单管理模块", - "label": "订单管理", - "template": "package", - "packageName": "order" - }, - "modulesInfo": [ - { - "package": "order", - "tableName": "orders", - "structName": "Order", - "packageName": "order", - "description": "订单", - "abbreviation": "order", - "humpPackageName": "order", - "gvaModel": true, - "autoMigrate": true, - "autoCreateResource": true, - "autoCreateApiToSql": true, - "autoCreateMenuToSql": true, - "autoCreateBtnAuth": true, - "generateWeb": true, - "generateServer": true, - "fields": [ - { - "fieldName": "UserID", - "fieldDesc": "下单用户", - "fieldType": "uint", - "fieldJson": "userId", - "columnName": "user_id", - "fieldSearchType": "EQ", - "form": true, - "table": true, - "desc": true, - "require": true, - "dataSource": { - "dbName": "gva", - "table": "sys_users", - "label": "username", - "value": "id", - "association": 2, - "hasDeletedAt": true - }, - "checkDataSource": true - }, - { - "fieldName": "ProductID", - "fieldDesc": "商品", - "fieldType": "uint", - "fieldJson": "productId", - "columnName": "product_id", - "fieldSearchType": "EQ", - "form": true, - "table": true, - "desc": true, - "require": true, - "dataSource": { - "dbName": "gva", - "table": "products", - "label": "name", - "value": "id", - "association": 2, - "hasDeletedAt": false - }, - "checkDataSource": true - }, - { - "fieldName": "Status", - "fieldDesc": "订单状态", - "fieldType": "int", - "fieldJson": "status", - "columnName": "status", - "fieldSearchType": "EQ", - "form": true, - "table": true, - "desc": true, - "require": true, - "dictType": "order_status" - } - ] - } - ] -} -``` - -## DataSource 配置说明 - -### 关联关系类型 -- **association: 1** - 一对一关联(如用户与用户档案) -- **association: 2** - 一对多关联(如用户与订单) - -### 配置要点 -1. **dbName**: 通常为 "gva"(默认数据库) -2. **table**: 关联表的实际表名 -3. **label**: 用于前端显示的字段(如用户名、商品名称) -4. **value**: 用于存储关联ID的字段(通常是 "id") -5. **hasDeletedAt**: 关联表是否支持软删除 -6. **checkDataSource**: 建议设为true,会验证关联表是否存在 - -### 常见关联场景 -- 用户关联:`{"table": "sys_users", "label": "username", "value": "id"}` -- 角色关联:`{"table": "sys_authorities", "label": "authorityName", "value": "authorityId"}` -- 部门关联:`{"table": "sys_departments", "label": "name", "value": "id"}` -- 分类关联:`{"table": "categories", "label": "name", "value": "id"}` - -## 重要注意事项 - -1. **PackageType**: 只能是 "plugin" 或 "package" -2. **NeedCreatedPackage**: 当为true时,PackageInfo必须提供 -3. **NeedCreatedModules**: 当为true时,ModulesInfo必须提供 -4. **字段类型**: FieldType支持的类型包括: - - string(字符串) - - richtext(富文本) - - int(整型) - - bool(布尔值) - - float64(浮点型) - - time.Time(时间) - - enum(枚举) - - picture(单图片,字符串) - - pictures(多图片,json字符串) - - video(视频,字符串) - - file(文件,json字符串) - - json(JSON) - - array(数组) -5. **搜索类型**: FieldSearchType支持:EQ, NE, GT, GE, LT, LE, LIKE, BETWEEN等 -6. **索引类型**: FieldIndexType支持:index, unique等 -7. **GvaModel**: 设置为true时会自动包含ID、CreatedAt、UpdatedAt、DeletedAt字段 -8. **关联配置**: 使用dataSource时,确保关联表已存在,建议开启checkDataSource验证 - -## 常见错误避免 - -1. 确保PackageName和ModuleName符合Go语言命名规范 -2. 字段名使用大写开头的驼峰命名 -3. JSON标签使用小写开头的驼峰命名 -4. 数据库列名使用下划线分隔的小写命名 -5. 必填字段不要遗漏 -6. 字段类型要与实际需求匹配 \ No newline at end of file diff --git a/server/mcp/gag_usage_example.md b/server/mcp/gag_usage_example.md deleted file mode 100644 index cfc7fdcb34..0000000000 --- a/server/mcp/gag_usage_example.md +++ /dev/null @@ -1,205 +0,0 @@ -# GAG工具使用示例 - 带用户确认流程 - -## 新的工作流程 - -现在GAG工具支持三步工作流程: -1. `analyze` - 分析现有模块信息 -2. `confirm` - 请求用户确认创建计划 -3. `execute` - 执行创建操作(需要用户确认) - -## 使用示例 - -### 第一步:分析 -```json -{ - "action": "analyze", - "requirement": "创建一个图书管理功能" -} -``` - -### 第二步:确认(支持批量创建多个模块) -```json -{ - "action": "confirm", - "executionPlan": { - "packageName": "library", - "packageType": "package", - "needCreatedPackage": true, - "needCreatedModules": true, - "packageInfo": { - "desc": "图书管理包", - "label": "图书管理", - "template": "package", - "packageName": "library" - }, - "modulesInfo": [ - { - "package": "library", - "tableName": "library_books", - "businessDB": "", - "structName": "Book", - "packageName": "library", - "description": "图书信息", - "abbreviation": "book", - "humpPackageName": "Library", - "gvaModel": true, - "autoMigrate": true, - "autoCreateResource": true, - "autoCreateApiToSql": true, - "autoCreateMenuToSql": true, - "autoCreateBtnAuth": true, - "onlyTemplate": false, - "isTree": false, - "treeJson": "", - "isAdd": false, - "generateWeb": true, - "generateServer": true, - "fields": [ - { - "fieldName": "title", - "fieldDesc": "书名", - "fieldType": "string", - "fieldJson": "title", - "dataTypeLong": "255", - "comment": "书名", - "columnName": "title", - "fieldSearchType": "LIKE", - "fieldSearchHide": false, - "dictType": "", - "form": true, - "table": true, - "desc": true, - "excel": true, - "require": true, - "defaultValue": "", - "errorText": "请输入书名", - "clearable": true, - "sort": false, - "primaryKey": false, - "dataSource": {}, - "checkDataSource": false, - "fieldIndexType": "" - }, - { - "fieldName": "AuthorID", - "fieldDesc": "作者", - "fieldType": "uint", - "fieldJson": "authorId", - "dataTypeLong": "", - "comment": "作者ID", - "columnName": "author_id", - "fieldSearchType": "EQ", - "fieldSearchHide": false, - "dictType": "", - "form": true, - "table": true, - "desc": true, - "excel": true, - "require": true, - "defaultValue": "", - "errorText": "请选择作者", - "clearable": true, - "sort": false, - "primaryKey": false, - "dataSource": { - "dbName": "gva", - "table": "library_authors", - "label": "name", - "value": "id", - "association": 2, - "hasDeletedAt": true - }, - "checkDataSource": true, - "fieldIndexType": "" - } - ] - }, - { - "package": "library", - "tableName": "library_authors", - "businessDB": "", - "structName": "Author", - "packageName": "library", - "description": "作者信息", - "abbreviation": "author", - "humpPackageName": "Library", - "gvaModel": true, - "autoMigrate": true, - "autoCreateResource": true, - "autoCreateApiToSql": true, - "autoCreateMenuToSql": true, - "autoCreateBtnAuth": true, - "onlyTemplate": false, - "isTree": false, - "treeJson": "", - "isAdd": false, - "generateWeb": true, - "generateServer": true, - "fields": [ - { - "fieldName": "name", - "fieldDesc": "作者姓名", - "fieldType": "string", - "fieldJson": "name", - "dataTypeLong": "100", - "comment": "作者姓名", - "columnName": "name", - "fieldSearchType": "LIKE", - "fieldSearchHide": false, - "dictType": "", - "form": true, - "table": true, - "desc": true, - "excel": true, - "require": true, - "defaultValue": "", - "errorText": "请输入作者姓名", - "clearable": true, - "sort": false, - "primaryKey": false, - "dataSource": {}, - "checkDataSource": false, - "fieldIndexType": "" - } - ] - } - ] - } -} -``` - -### 第三步:执行(需要确认参数) -```json -{ - "action": "execute", - "executionPlan": { - // ... 同上面的executionPlan - }, - "packageConfirm": "yes", // 确认创建包 - "modulesConfirm": "yes" // 确认创建模块 -} -``` - -## 确认参数说明 - -- `packageConfirm`: 当`needCreatedPackage`为true时必需 - - "yes": 确认创建包 - - "no": 取消创建包(停止后续处理) - -- `modulesConfirm`: 当`needCreatedModules`为true时必需 - - "yes": 确认创建模块 - - "no": 取消创建模块(停止后续处理) - -## 取消操作的行为 - -1. 如果用户在`packageConfirm`中选择"no",系统将停止所有后续处理 -2. 如果用户在`modulesConfirm`中选择"no",系统将停止模块创建 -3. 任何取消操作都会返回相应的取消消息,不会执行任何创建操作 - -## 注意事项 - -1. 必须先调用`confirm`来获取确认信息 -2. 在`execute`时必须提供相应的确认参数 -3. 确认参数的值必须是"yes"或"no" -4. 如果不需要创建包或模块,则不需要提供对应的确认参数 -5. 字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组) \ No newline at end of file diff --git a/server/mcp/gva_analyze.go b/server/mcp/gva_analyze.go new file mode 100644 index 0000000000..91ebd82712 --- /dev/null +++ b/server/mcp/gva_analyze.go @@ -0,0 +1,517 @@ +package mcpTool + +import ( + "context" + "encoding/json" + "errors" + "fmt" + model "github.com/flipped-aurora/gin-vue-admin/server/model/system" + "os" + "path/filepath" + "strings" + + "github.com/flipped-aurora/gin-vue-admin/server/global" + "github.com/mark3labs/mcp-go/mcp" +) + +// 注册工具 +func init() { + RegisterTool(&GVAAnalyzer{}) +} + +// GVAAnalyzer GVA分析器 - 用于分析当前功能是否需要创建独立的package和module +type GVAAnalyzer struct{} + +// AnalyzeRequest 分析请求结构体 +type AnalyzeRequest struct { + Requirement string `json:"requirement" binding:"required"` // 用户需求描述 +} + +// AnalyzeResponse 分析响应结构体 +type AnalyzeResponse struct { + NeedCreatedPackage bool `json:"needCreatedPackage"` // 是否需要创建新包 + NeedCreatedModules []ModuleInfo `json:"needCreatedModules"` // 需要创建的模块列表 + ExistingPackages []PackageInfo `json:"existingPackages"` // 现有包信息 + PredesignedModules []PredesignedModuleInfo `json:"predesignedModules"` // 预设计模块信息 + SuggestedPackage string `json:"suggestedPackage"` // 建议的包名 + SuggestedTemplate string `json:"suggestedTemplate"` // 建议的模板类型 (package/plugin) + AnalysisMessage string `json:"analysisMessage"` // 分析结果消息 + CleanupInfo *CleanupInfo `json:"cleanupInfo"` // 清理信息(如果有) +} + +// ModuleInfo 模块信息 +type ModuleInfo struct { + ModuleName string `json:"moduleName"` // 模块名称 + PackageName string `json:"packageName"` // 包名 + Template string `json:"template"` // 模板类型 + StructName string `json:"structName"` // 结构体名称 + TableName string `json:"tableName"` // 表名 + Description string `json:"description"` // 描述 + FilePaths []string `json:"filePaths"` // 相关文件路径 +} + +// PackageInfo 包信息 +type PackageInfo struct { + PackageName string `json:"packageName"` // 包名 + Template string `json:"template"` // 模板类型 + Label string `json:"label"` // 标签 + Desc string `json:"desc"` // 描述 + Module string `json:"module"` // 模块 + IsEmpty bool `json:"isEmpty"` // 是否为空包 +} + +// PredesignedModuleInfo 预设计模块信息 +type PredesignedModuleInfo struct { + ModuleName string `json:"moduleName"` // 模块名称 + PackageName string `json:"packageName"` // 包名 + Template string `json:"template"` // 模板类型 + FilePaths []string `json:"filePaths"` // 文件路径列表 + Description string `json:"description"` // 描述 +} + +// CleanupInfo 清理信息 +type CleanupInfo struct { + DeletedPackages []string `json:"deletedPackages"` // 已删除的包 + DeletedModules []string `json:"deletedModules"` // 已删除的模块 + CleanupMessage string `json:"cleanupMessage"` // 清理消息 +} + +// New 创建GVA分析器工具 +func (g *GVAAnalyzer) New() mcp.Tool { + return mcp.NewTool("gva_analyze", + mcp.WithDescription("分析当前功能是否需要创建独立的package和module,或仅返回当前功能需要的文件路径"), + mcp.WithString("requirement", + mcp.Description("用户需求描述,用于分析是否需要创建新的包和模块"), + mcp.Required(), + ), + ) +} + +// Handle 处理分析请求 +func (g *GVAAnalyzer) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // 解析请求参数 + requirementStr, ok := request.GetArguments()["requirement"].(string) + if !ok || requirementStr == "" { + return nil, errors.New("参数错误:requirement 必须是非空字符串") + } + + // 创建分析请求 + analyzeReq := AnalyzeRequest{ + Requirement: requirementStr, + } + + // 执行分析逻辑 + response, err := g.performAnalysis(ctx, analyzeReq) + if err != nil { + return nil, fmt.Errorf("分析失败: %v", err) + } + + // 序列化响应 + responseJSON, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("序列化响应失败: %v", err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(string(responseJSON)), + }, + }, nil +} + +// performAnalysis 执行分析逻辑 +func (g *GVAAnalyzer) performAnalysis(ctx context.Context, req AnalyzeRequest) (*AnalyzeResponse, error) { + // 1. 检测插件意图 + suggestedType, isPlugin, confidence := g.detectPluginIntent(req.Requirement) + global.GVA_LOG.Info(fmt.Sprintf("插件意图检测结果: 类型=%s, 是否插件=%v, 置信度=%s", suggestedType, isPlugin, confidence)) + + // 2. 获取数据库中的包信息 + var packages []model.SysAutoCodePackage + if err := global.GVA_DB.Find(&packages).Error; err != nil { + return nil, fmt.Errorf("获取包信息失败: %v", err) + } + + // 3. 获取历史记录 + var histories []model.SysAutoCodeHistory + if err := global.GVA_DB.Find(&histories).Error; err != nil { + return nil, fmt.Errorf("获取历史记录失败: %v", err) + } + + // 4. 检查空包并进行清理 + cleanupInfo := &CleanupInfo{ + DeletedPackages: []string{}, + DeletedModules: []string{}, + } + + var validPackages []model.SysAutoCodePackage + var emptyPackageHistoryIDs []uint + + for _, pkg := range packages { + isEmpty, err := g.isPackageFolderEmpty(pkg.PackageName, pkg.Template) + if err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("检查包 %s 是否为空时出错: %v", pkg.PackageName, err)) + continue + } + + if isEmpty { + // 删除空包文件夹 + if err := g.removeEmptyPackageFolder(pkg.PackageName, pkg.Template); err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("删除空包文件夹 %s 失败: %v", pkg.PackageName, err)) + } else { + cleanupInfo.DeletedPackages = append(cleanupInfo.DeletedPackages, pkg.PackageName) + } + + // 删除数据库记录 + if err := global.GVA_DB.Delete(&pkg).Error; err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("删除包数据库记录 %s 失败: %v", pkg.PackageName, err)) + } + + // 收集相关的历史记录ID + for _, history := range histories { + if history.Package == pkg.PackageName { + emptyPackageHistoryIDs = append(emptyPackageHistoryIDs, history.ID) + cleanupInfo.DeletedModules = append(cleanupInfo.DeletedModules, history.StructName) + } + } + } else { + validPackages = append(validPackages, pkg) + } + } + + // 5. 清理空包相关的历史记录和脏历史记录 + var dirtyHistoryIDs []uint + for _, history := range histories { + // 检查是否为空包相关的历史记录 + for _, emptyID := range emptyPackageHistoryIDs { + if history.ID == emptyID { + dirtyHistoryIDs = append(dirtyHistoryIDs, history.ID) + break + } + } + } + + // 删除脏历史记录 + if len(dirtyHistoryIDs) > 0 { + if err := global.GVA_DB.Delete(&model.SysAutoCodeHistory{}, "id IN ?", dirtyHistoryIDs).Error; err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("删除脏历史记录失败: %v", err)) + } else { + global.GVA_LOG.Info(fmt.Sprintf("成功删除 %d 条脏历史记录", len(dirtyHistoryIDs))) + } + + // 清理相关的API和菜单记录 + if err := g.cleanupRelatedApiAndMenus(dirtyHistoryIDs); err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("清理相关API和菜单记录失败: %v", err)) + } + } + + // 6. 扫描预设计模块 + predesignedModules, err := g.scanPredesignedModules() + if err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("扫描预设计模块失败: %v", err)) + predesignedModules = []PredesignedModuleInfo{} // 设置为空列表,不影响主流程 + } + + // 7. 过滤掉与已删除包相关的模块 + filteredModules := []PredesignedModuleInfo{} + for _, module := range predesignedModules { + isDeleted := false + for _, deletedPkg := range cleanupInfo.DeletedPackages { + if module.PackageName == deletedPkg { + isDeleted = true + break + } + } + if !isDeleted { + filteredModules = append(filteredModules, module) + } + } + + // 8. 构建分析结果消息 + var analysisMessage strings.Builder + if len(cleanupInfo.DeletedPackages) > 0 || len(cleanupInfo.DeletedModules) > 0 { + analysisMessage.WriteString("🧹 **系统清理完成**\n\n") + if len(cleanupInfo.DeletedPackages) > 0 { + analysisMessage.WriteString(fmt.Sprintf("- 删除了 %d 个空包: %s\n", len(cleanupInfo.DeletedPackages), strings.Join(cleanupInfo.DeletedPackages, ", "))) + } + if len(cleanupInfo.DeletedModules) > 0 { + analysisMessage.WriteString(fmt.Sprintf("- 删除了 %d 个相关模块: %s\n", len(cleanupInfo.DeletedModules), strings.Join(cleanupInfo.DeletedModules, ", "))) + } + analysisMessage.WriteString("\n") + cleanupInfo.CleanupMessage = analysisMessage.String() + } + + analysisMessage.WriteString("📊 **分析结果**\n\n") + analysisMessage.WriteString(fmt.Sprintf("- **插件意图检测**: %s (置信度: %s)\n", suggestedType, confidence)) + analysisMessage.WriteString(fmt.Sprintf("- **现有包数量**: %d\n", len(validPackages))) + analysisMessage.WriteString(fmt.Sprintf("- **预设计模块数量**: %d\n\n", len(filteredModules))) + + // 9. 转换包信息 + existingPackages := make([]PackageInfo, len(validPackages)) + for i, pkg := range validPackages { + existingPackages[i] = PackageInfo{ + PackageName: pkg.PackageName, + Template: pkg.Template, + Label: pkg.Label, + Desc: pkg.Desc, + Module: pkg.Module, + IsEmpty: false, // 已经过滤掉空包 + } + } + + // 10. 构建响应 + response := &AnalyzeResponse{ + NeedCreatedPackage: true, // 默认建议创建新包 + NeedCreatedModules: []ModuleInfo{}, // 具体模块需要根据需求进一步分析 + ExistingPackages: existingPackages, + PredesignedModules: filteredModules, + SuggestedPackage: "", // 需要根据需求生成 + SuggestedTemplate: suggestedType, + AnalysisMessage: analysisMessage.String(), + CleanupInfo: cleanupInfo, + } + + return response, nil +} + +// detectPluginIntent 检测插件意图 +func (g *GVAAnalyzer) detectPluginIntent(requirement string) (string, bool, string) { + requirement = strings.ToLower(requirement) + + // 插件关键词映射 + pluginKeywords := map[string]string{ + "插件": "plugin", + "plugin": "plugin", + "扩展": "plugin", + "extension": "plugin", + "addon": "plugin", + "模块": "package", + "module": "package", + "包": "package", + "package": "package", + "功能": "package", + "feature": "package", + } + + // 检查关键词 + for keyword, templateType := range pluginKeywords { + if strings.Contains(requirement, keyword) { + isPlugin := templateType == "plugin" + confidence := "高" + if strings.Contains(requirement, "可能") || strings.Contains(requirement, "也许") { + confidence = "中" + } + return templateType, isPlugin, confidence + } + } + + // 默认返回package类型 + return "package", false, "低" +} + +// isPackageFolderEmpty 检查包文件夹是否为空 +func (g *GVAAnalyzer) isPackageFolderEmpty(packageName, template string) (bool, error) { + // 根据模板类型确定基础路径 + var basePath string + if template == "plugin" { + basePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", packageName) + } else { + basePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", packageName) + } + + // 检查文件夹是否存在 + if _, err := os.Stat(basePath); os.IsNotExist(err) { + return true, nil // 文件夹不存在,视为空 + } else if err != nil { + return false, err // 其他错误 + } + + // 读取文件夹内容 + entries, err := os.ReadDir(basePath) + if err != nil { + return false, err + } + + // 检查是否有.go文件 + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".go") { + return false, nil // 找到.go文件,不为空 + } + } + + return true, nil // 没有找到.go文件,为空 +} + +// removeEmptyPackageFolder 删除空包文件夹 +func (g *GVAAnalyzer) removeEmptyPackageFolder(packageName, template string) error { + var basePath string + if template == "plugin" { + basePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", packageName) + } else { + // 对于package类型,需要删除多个目录 + paths := []string{ + filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", packageName), + filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "model", packageName), + filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", packageName), + filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", packageName), + } + for _, path := range paths { + if err := g.removeDirectoryIfExists(path); err != nil { + return err + } + } + return nil + } + + return g.removeDirectoryIfExists(basePath) +} + +// removeDirectoryIfExists 删除目录(如果存在) +func (g *GVAAnalyzer) removeDirectoryIfExists(dirPath string) error { + if _, err := os.Stat(dirPath); os.IsNotExist(err) { + return nil // 目录不存在,无需删除 + } else if err != nil { + return err // 其他错误 + } + + return os.RemoveAll(dirPath) +} + +// cleanupRelatedApiAndMenus 清理相关的API和菜单记录 +func (g *GVAAnalyzer) cleanupRelatedApiAndMenus(historyIDs []uint) error { + if len(historyIDs) == 0 { + return nil + } + + // 这里可以根据需要实现具体的API和菜单清理逻辑 + // 由于涉及到具体的业务逻辑,这里只做日志记录 + global.GVA_LOG.Info(fmt.Sprintf("清理历史记录ID %v 相关的API和菜单记录", historyIDs)) + + // 可以调用service层的相关方法进行清理 + // 例如:service.ServiceGroupApp.SystemApiService.DeleteApisByIds(historyIDs) + // 例如:service.ServiceGroupApp.MenuService.DeleteMenusByIds(historyIDs) + + return nil +} + +// scanPredesignedModules 扫描预设计模块 +func (g *GVAAnalyzer) scanPredesignedModules() ([]PredesignedModuleInfo, error) { + // 获取autocode配置路径 + autocodeRoot := global.GVA_CONFIG.AutoCode.Root + if autocodeRoot == "" { + return nil, errors.New("autocode根路径未配置") + } + + var modules []PredesignedModuleInfo + + // 扫描plugin目录 + pluginModules, err := g.scanPluginModules(filepath.Join(autocodeRoot, global.GVA_CONFIG.AutoCode.Server, "plugin")) + if err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("扫描plugin模块失败: %v", err)) + } else { + modules = append(modules, pluginModules...) + } + + // 扫描model目录 + modelModules, err := g.scanModelModules(filepath.Join(autocodeRoot, global.GVA_CONFIG.AutoCode.Server, "model")) + if err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("扫描model模块失败: %v", err)) + } else { + modules = append(modules, modelModules...) + } + + return modules, nil +} + +// scanPluginModules 扫描插件模块 +func (g *GVAAnalyzer) scanPluginModules(pluginDir string) ([]PredesignedModuleInfo, error) { + var modules []PredesignedModuleInfo + + if _, err := os.Stat(pluginDir); os.IsNotExist(err) { + return modules, nil // 目录不存在,返回空列表 + } + + entries, err := os.ReadDir(pluginDir) + if err != nil { + return nil, err + } + + for _, entry := range entries { + if entry.IsDir() { + pluginName := entry.Name() + pluginPath := filepath.Join(pluginDir, pluginName) + + // 查找model目录 + modelDir := filepath.Join(pluginPath, "model") + if _, err := os.Stat(modelDir); err == nil { + // 扫描model目录下的模块 + pluginModules, err := g.scanModulesInDirectory(modelDir, pluginName, "plugin") + if err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("扫描插件 %s 的模块失败: %v", pluginName, err)) + continue + } + modules = append(modules, pluginModules...) + } + } + } + + return modules, nil +} + +// scanModelModules 扫描模型模块 +func (g *GVAAnalyzer) scanModelModules(modelDir string) ([]PredesignedModuleInfo, error) { + var modules []PredesignedModuleInfo + + if _, err := os.Stat(modelDir); os.IsNotExist(err) { + return modules, nil // 目录不存在,返回空列表 + } + + entries, err := os.ReadDir(modelDir) + if err != nil { + return nil, err + } + + for _, entry := range entries { + if entry.IsDir() { + packageName := entry.Name() + packagePath := filepath.Join(modelDir, packageName) + + // 扫描包目录下的模块 + packageModules, err := g.scanModulesInDirectory(packagePath, packageName, "package") + if err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("扫描包 %s 的模块失败: %v", packageName, err)) + continue + } + modules = append(modules, packageModules...) + } + } + + return modules, nil +} + +// scanModulesInDirectory 扫描目录中的模块 +func (g *GVAAnalyzer) scanModulesInDirectory(dir, packageName, template string) ([]PredesignedModuleInfo, error) { + var modules []PredesignedModuleInfo + + entries, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".go") { + moduleName := strings.TrimSuffix(entry.Name(), ".go") + filePath := filepath.Join(dir, entry.Name()) + + module := PredesignedModuleInfo{ + ModuleName: moduleName, + PackageName: packageName, + Template: template, + FilePaths: []string{filePath}, + Description: fmt.Sprintf("%s模块中的%s", packageName, moduleName), + } + modules = append(modules, module) + } + } + + return modules, nil +} diff --git a/server/mcp/gva_auto_generate.go b/server/mcp/gva_auto_generate.go deleted file mode 100644 index 30aa897aae..0000000000 --- a/server/mcp/gva_auto_generate.go +++ /dev/null @@ -1,2365 +0,0 @@ -package mcpTool - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "github.com/flipped-aurora/gin-vue-admin/server/global" - common "github.com/flipped-aurora/gin-vue-admin/server/model/common/request" - model "github.com/flipped-aurora/gin-vue-admin/server/model/system" - "github.com/flipped-aurora/gin-vue-admin/server/model/system/request" - "github.com/flipped-aurora/gin-vue-admin/server/service" - systemService "github.com/flipped-aurora/gin-vue-admin/server/service/system" - "github.com/mark3labs/mcp-go/mcp" - "gorm.io/gorm" -) - -func init() { - RegisterTool(&AutomationModuleAnalyzer{}) -} - -type AutomationModuleAnalyzer struct{} - -// ModuleInfo 模块信息 -type ModuleInfo struct { - ID uint `json:"id"` - PackageName string `json:"packageName"` - Label string `json:"label"` - Desc string `json:"desc"` - Template string `json:"template"` // "plugin" 或 "package" - Module string `json:"module"` -} - -// HistoryInfo 历史记录信息 -type HistoryInfo struct { - ID uint `json:"id"` - StructName string `json:"structName"` - TableName string `json:"tableName"` - PackageName string `json:"packageName"` - BusinessDB string `json:"businessDB"` - Description string `json:"description"` - Abbreviation string `json:"abbreviation"` - CreatedAt string `json:"createdAt"` -} - -// PredesignedModuleInfo 预设计模块信息 -type PredesignedModuleInfo struct { - PackageName string `json:"packageName"` - PackageType string `json:"packageType"` // "plugin" 或 "package" - ModuleName string `json:"moduleName"` - Path string `json:"path"` - Modules []string `json:"modules"` // 包含的模块列表(如api、model、service等) - Description string `json:"description"` - StructName string `json:"structName,omitempty"` // 主要结构体名称 -} - -// AnalysisResponse 分析响应 -type AnalysisResponse struct { - Packages []ModuleInfo `json:"packages"` - History []HistoryInfo `json:"history"` - PredesignedModules []PredesignedModuleInfo `json:"predesignedModules"` - Message string `json:"message"` -} - -// ExecutionPlan 执行计划 - 支持批量创建 -type ExecutionPlan struct { - PackageName string `json:"packageName"` - PackageType string `json:"packageType"` // "plugin" 或 "package" - NeedCreatedPackage bool `json:"needCreatedPackage"` - NeedCreatedModules bool `json:"needCreatedModules"` - PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"` - ModulesInfo []*request.AutoCode `json:"modulesInfo,omitempty"` // 改为数组支持多个模块 - Paths map[string]string `json:"paths,omitempty"` -} - -// ExecutionResult 执行结果 -type ExecutionResult struct { - Success bool `json:"success"` - Message string `json:"message"` - PackageID uint `json:"packageId,omitempty"` - HistoryID uint `json:"historyId,omitempty"` - Paths map[string]string `json:"paths,omitempty"` - GeneratedPaths []string `json:"generatedPaths,omitempty"` // 新增:记录生成的文件路径 - NextActions []string `json:"nextActions,omitempty"` -} - -// ConfirmationRequest 确认请求结构 -type ConfirmationRequest struct { - PackageName string `json:"packageName"` - ModuleName string `json:"moduleName"` - NeedCreatedPackage bool `json:"needCreatedPackage"` - NeedCreatedModules bool `json:"needCreatedModules"` - PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"` - ModulesInfo *request.AutoCode `json:"modulesInfo,omitempty"` -} - -// ConfirmationResponse 确认响应结构 -type ConfirmationResponse struct { - Message string `json:"message"` - PackageConfirm bool `json:"packageConfirm"` - ModulesConfirm bool `json:"modulesConfirm"` - CanProceed bool `json:"canProceed"` - ConfirmationKey string `json:"confirmationKey"` -} - -// ReviewRequest 代码复检请求结构 -type ReviewRequest struct { - OriginalRequirement string `json:"originalRequirement"` // 用户原始需求 - ExecutionPlan *ExecutionPlan `json:"executionPlan"` // 执行计划 - GeneratedPaths map[string]string `json:"generatedPaths"` // 生成的文件路径 - MaxRetries int `json:"maxRetries"` // 最大重试次数,默认3次 -} - -// ReviewResult 代码复检结果结构 -type ReviewResult struct { - Success bool `json:"success"` // 是否满足需求 - Message string `json:"message"` // 复检结果说明 - SatisfiedRequirements []string `json:"satisfiedRequirements"` // 已满足的需求 - MissingRequirements []string `json:"missingRequirements"` // 未满足的需求 - FilesToEnhance map[string][]string `json:"filesToEnhance"` // 需要增强的文件及具体需求 - ReviewDetails map[string]interface{} `json:"reviewDetails"` // 详细的复检信息 -} - -// EnhanceRequest 代码增强请求结构 -type EnhanceRequest struct { - OriginalRequirement string `json:"originalRequirement"` // 用户原始需求 - MissingRequirements []string `json:"missingRequirements"` // 未满足的需求 - FilesToEnhance map[string][]string `json:"filesToEnhance"` // 需要增强的文件及具体需求 - GeneratedPaths map[string]string `json:"generatedPaths"` // 生成的文件路径 - ExecutionPlan *ExecutionPlan `json:"executionPlan"` // 原始执行计划 -} - -// EnhanceResult 代码增强结果结构 -type EnhanceResult struct { - Success bool `json:"success"` // 是否增强成功 - Message string `json:"message"` // 增强结果说明 - EnhancedFiles []string `json:"enhancedFiles"` // 已增强的文件列表 - Modifications map[string]string `json:"modifications"` // 文件修改详情 - NextReviewNeeded bool `json:"nextReviewNeeded"` // 是否需要再次复检 -} - -// New 返回工具注册信息 -func (t *AutomationModuleAnalyzer) New() mcp.Tool { - return mcp.NewTool("gva_auto_generate", - mcp.WithDescription(`**🔧 核心执行工具:接收requirement_analyzer分析结果,执行具体的模块创建操作** - -**工作流位置:** -- **第二优先级**:在requirement_analyzer之后使用 -- **接收输入**:来自requirement_analyzer的1xxx2xxx格式分析结果 -- **执行操作**:根据分析结果创建完整模块、包、功能模块 - -**批量创建功能:** -- 支持在单个ExecutionPlan中创建多个模块 -- modulesInfo字段为数组,可包含多个模块配置 -- 一次性处理多个模块的创建和字典生成 -- 与requirement_analyzer配合实现完整工作流 - -分步骤分析自动化模块:1) 分析现有模块信息供AI选择 2) 请求用户确认 3) 根据确认结果执行创建操作 - -**新功能:自动字典创建** -- 当结构体字段使用了字典类型(dictType不为空)时,系统会自动检查字典是否存在 -- 如果字典不存在,会自动创建对应的字典及默认的字典详情项 -- 字典创建包括:字典主表记录和默认的选项值(选项1、选项2等) - -**推荐工作流:** -1. 用户提出需求 → requirement_analyzer(最高优先级) -2. AI分析需求为1xxx2xxx格式 → gva_auto_generate(执行创建) -3. 创建完成后,根据需要使用其他辅助工具 - -**重要限制:** -- 当needCreatedModules=true时,模块创建会自动生成API和菜单,因此不应再调用api_creator和menu_creator工具 -- 只有在单独创建API或菜单(不涉及模块创建)时才使用api_creator和menu_creator工具 - -重要:ExecutionPlan结构体格式要求(支持批量创建): -{ - "packageName": "包名(string)", - "packageType": "package或plugin(string)", - "needCreatedPackage": "是否需要创建包(bool)", - "needCreatedModules": "是否需要创建模块(bool)", - "packageInfo": { - "desc": "描述(string)", - "label": "展示名(string)", - "template": "package或plugin(string)", - "packageName": "包名(string)" - }, - "modulesInfo": [{ - "package": "包名(string)", - "tableName": "数据库表名(string)", - "businessDB": "业务数据库(string)", - "structName": "结构体名(string)", - "packageName": "文件名称(string)", - "description": "中文描述(string)", - "abbreviation": "简称(string)", - "humpPackageName": "文件名称 一般是结构体名的小驼峰(string)", - "gvaModel": "是否使用GVA模型(bool) 固定为true 后续不需要创建ID created_at deleted_at updated_at", - "autoMigrate": "是否自动迁移(bool)", - "autoCreateResource": "是否创建资源(bool)", - "autoCreateApiToSql": "是否创建API(bool)", - "autoCreateMenuToSql": "是否创建菜单(bool)", - "autoCreateBtnAuth": "是否创建按钮权限(bool)", - "onlyTemplate": "是否仅模板(bool)", - "isTree": "是否树形结构(bool)", - "treeJson": "树形JSON字段(string)", - "isAdd": "是否新增(bool) 固定为false", - "generateWeb": "是否生成前端(bool)", - "generateServer": "是否生成后端(bool)", - "fields": [{ - "fieldName": "字段名(string)必须大写开头", - "fieldDesc": "字段描述(string)", - "fieldType": "字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组)", - "fieldJson": "JSON标签(string)", - "dataTypeLong": "数据长度(string)", - "comment": "注释(string)", - "columnName": "数据库列名(string)", - "fieldSearchType": "搜索类型:=/>/=/<=/NOT BETWEEN/LIKE/BETWEEN/IN/NOT IN等(string)", - "fieldSearchHide": "是否隐藏搜索(bool)", - "dictType": "字典类型(string)", - "form": "表单显示(bool)", - "table": "表格显示(bool)", - "desc": "详情显示(bool)", - "excel": "导入导出(bool)", - "require": "是否必填(bool)", - "defaultValue": "默认值(string)", - "errorText": "错误提示(string)", - "clearable": "是否可清空(bool)", - "sort": "是否排序(bool)", - "primaryKey": "是否主键(bool)", - "dataSource": "数据源配置(object) - 用于配置字段的关联表信息,结构:{\"dbName\":\"数据库名\",\"table\":\"关联表名\",\"label\":\"显示字段\",\"value\":\"值字段\",\"association\":1或2(1=一对一,2=一对多),\"hasDeletedAt\":true/false}。\n\n**获取表名提示:**\n- 可在 server/model 和 plugin/xxx/model 目录下查看对应模块的 TableName() 接口实现获取实际表名\n- 例如:SysUser 的表名为 \"sys_users\",ExaFileUploadAndDownload 的表名为 \"exa_file_upload_and_downloads\"\n- 插件模块示例:Info 的表名为 \"gva_announcements_info\"\n\n**获取数据库名提示:**\n- 主数据库:通常使用 \"gva\"(默认数据库标识)\n- 多数据库:可在 config.yaml 的 db-list 配置中查看可用数据库的 alias-name 字段\n- 如果用户未提及关联多数据库信息 则使用默认数据库 默认数据库的情况下 dbName此处填写为空", - "checkDataSource": "是否检查数据源(bool) - 启用后会验证关联表的存在性", - "fieldIndexType": "索引类型(string)" - }] - }, { - "package": "包名(string)", - "tableName": "第二个模块的表名(string)", - "structName": "第二个模块的结构体名(string)", - "description": "第二个模块的描述(string)", - "...": "更多模块配置..." - }] -} - -注意: -1. needCreatedPackage=true时packageInfo必需 -2. needCreatedModules=true时modulesInfo必需 -3. packageType只能是"package"或"plugin" -4. 字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组) -5. 搜索类型支持:=,!=,>,>=,<,<=,NOT BETWEEN/LIKE/BETWEEN/IN/NOT IN -6. gvaModel=true时自动包含ID,CreatedAt,UpdatedAt,DeletedAt字段 -7. **重要**:当gvaModel=false时,必须有一个字段的primaryKey=true,否则会导致PrimaryField为nil错误 -8. **重要**:当gvaModel=true时,系统会自动设置ID字段为主键,无需手动设置primaryKey=true -9. 智能字典创建功能:当字段使用字典类型(DictType)时,系统会: - - 自动检查字典是否存在,如果不存在则创建字典 - - 根据字典类型和字段描述智能生成默认选项,支持状态、性别、类型、等级、优先级、审批、角色、布尔值、订单、颜色、尺寸等常见场景 - - 为无法识别的字典类型提供通用默认选项 -10. **模块关联配置**:当需要配置模块间的关联关系时,使用dataSource字段: - - **dbName**: 关联的数据库名称 - - **table**: 关联的表名 - - **label**: 用于显示的字段名(如name、title等) - - **value**: 用于存储的值字段名(通常是id) - - **association**: 关联关系类型(1=一对一关联,2=一对多关联) - - **hasDeletedAt**: 关联表是否有软删除字段 - - **checkDataSource**: 设为true时会验证关联表的存在性 - - 示例:{"dbName":"gva","table":"sys_users","label":"username","value":"id","association":2,"hasDeletedAt":true}`), - mcp.WithString("action", - mcp.Required(), - mcp.Description("执行操作:'analyze' 分析现有模块信息,'confirm' 请求用户确认创建,'execute' 执行创建操作(支持批量创建多个模块),'review' 代码复检验证是否满足需求,'enhance' AI代码增强补全缺失功能"), - ), - mcp.WithString("requirement", - mcp.Description("用户需求描述(action=analyze时必需)"), - ), - mcp.WithObject("executionPlan", - mcp.Description("执行计划(action=confirm或execute时必需,必须严格按照上述格式提供完整的JSON对象)"), - ), - mcp.WithString("packageConfirm", - mcp.Description("用户对创建包的确认(action=execute时,如果需要创建包则必需):'yes' 或 'no'"), - ), - mcp.WithString("modulesConfirm", - mcp.Description("用户对创建模块的确认(action=execute时,如果需要创建模块则必需):'yes' 或 'no'"), - ), - mcp.WithObject("reviewRequest", - mcp.Description("代码复检请求(action=review时必需):包含原始需求、执行计划、生成路径等信息"), - ), - mcp.WithObject("enhanceRequest", - mcp.Description("代码增强请求(action=enhance时必需):包含未满足需求、目标文件等信息"), - ), - ) -} - -// scanPredesignedModules 扫描预设计的模块 -func (t *AutomationModuleAnalyzer) scanPredesignedModules() ([]PredesignedModuleInfo, error) { - var predesignedModules []PredesignedModuleInfo - - // 获取autocode配置路径 - if global.GVA_CONFIG.AutoCode.Root == "" { - return predesignedModules, nil // 配置不存在时返回空列表,不报错 - } - - serverPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server) - - // 扫描plugin目录下的各个插件模块 - pluginPath := filepath.Join(serverPath, "plugin") - if pluginModules, err := t.scanPluginModules(pluginPath); err == nil { - predesignedModules = append(predesignedModules, pluginModules...) - } - - // 扫描model目录下的各个包模块 - modelPath := filepath.Join(serverPath, "model") - if packageModules, err := t.scanPackageModules(modelPath); err == nil { - predesignedModules = append(predesignedModules, packageModules...) - } - - return predesignedModules, nil -} - -// scanPluginModules 扫描plugin目录下的各个插件模块 -func (t *AutomationModuleAnalyzer) scanPluginModules(pluginPath string) ([]PredesignedModuleInfo, error) { - var modules []PredesignedModuleInfo - - if _, err := os.Stat(pluginPath); os.IsNotExist(err) { - return modules, nil - } - - entries, err := os.ReadDir(pluginPath) - if err != nil { - return modules, err - } - - for _, entry := range entries { - if !entry.IsDir() { - continue - } - - pluginName := entry.Name() - pluginDir := filepath.Join(pluginPath, pluginName) - - // 扫描插件下的model目录,查找具体的模块文件 - modelDir := filepath.Join(pluginDir, "model") - if _, err := os.Stat(modelDir); err == nil { - if pluginModules, err := t.scanModuleFiles(modelDir, pluginName, "plugin"); err == nil { - modules = append(modules, pluginModules...) - } - } - } - - return modules, nil -} - -// scanPackageModules 扫描model目录下的各个包模块 -func (t *AutomationModuleAnalyzer) scanPackageModules(modelPath string) ([]PredesignedModuleInfo, error) { - var modules []PredesignedModuleInfo - - if _, err := os.Stat(modelPath); os.IsNotExist(err) { - return modules, nil - } - - entries, err := os.ReadDir(modelPath) - if err != nil { - return modules, err - } - - for _, entry := range entries { - if !entry.IsDir() { - continue - } - - packageName := entry.Name() - // 跳过一些系统目录 - if packageName == "common" || packageName == "request" || packageName == "response" { - continue - } - - packageDir := filepath.Join(modelPath, packageName) - - // 扫描包目录下的模块文件 - if packageModules, err := t.scanModuleFiles(packageDir, packageName, "package"); err == nil { - modules = append(modules, packageModules...) - } - } - - return modules, nil -} - -// scanModuleFiles 扫描目录下的Go文件,识别具体的模块 -func (t *AutomationModuleAnalyzer) scanModuleFiles(dir, packageName, packageType string) ([]PredesignedModuleInfo, error) { - var modules []PredesignedModuleInfo - - entries, err := os.ReadDir(dir) - if err != nil { - return modules, err - } - - for _, entry := range entries { - if entry.IsDir() { - continue - } - - fileName := entry.Name() - if !strings.HasSuffix(fileName, ".go") { - continue - } - - // 跳过一些非模块文件 - if strings.HasSuffix(fileName, "_test.go") || - fileName == "enter.go" || - fileName == "request.go" || - fileName == "response.go" { - continue - } - - filePath := filepath.Join(dir, fileName) - moduleName := strings.TrimSuffix(fileName, ".go") - - // 分析模块文件,提取结构体信息 - if moduleInfo, err := t.analyzeModuleFile(filePath, packageName, moduleName, packageType); err == nil { - modules = append(modules, *moduleInfo) - } - } - - return modules, nil -} - -// analyzeModuleFile 分析具体的模块文件 -func (t *AutomationModuleAnalyzer) analyzeModuleFile(filePath, packageName, moduleName, packageType string) (*PredesignedModuleInfo, error) { - content, err := os.ReadFile(filePath) - if err != nil { - return nil, err - } - - fileContent := string(content) - - // 提取结构体名称和描述 - structNames := t.extractStructNames(fileContent) - description := t.extractModuleDescription(fileContent, moduleName) - - // 确定主要结构体名称 - mainStruct := moduleName - if len(structNames) > 0 { - // 优先选择与文件名相关的结构体 - for _, structName := range structNames { - if strings.Contains(strings.ToLower(structName), strings.ToLower(moduleName)) { - mainStruct = structName - break - } - } - if mainStruct == moduleName && len(structNames) > 0 { - mainStruct = structNames[0] // 如果没有匹配的,使用第一个 - } - } - - return &PredesignedModuleInfo{ - PackageName: packageName, - PackageType: packageType, - ModuleName: moduleName, - Path: filePath, - Modules: structNames, - Description: description, - StructName: mainStruct, - }, nil -} - -// extractStructNames 从文件内容中提取结构体名称 -func (t *AutomationModuleAnalyzer) extractStructNames(content string) []string { - var structNames []string - lines := strings.Split(content, "\n") - - for _, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "type ") && strings.Contains(line, " struct") { - // 提取结构体名称 - parts := strings.Fields(line) - if len(parts) >= 3 && parts[2] == "struct" { - structNames = append(structNames, parts[1]) - } - } - } - - return structNames -} - -// extractModuleDescription 从文件内容中提取模块描述 -func (t *AutomationModuleAnalyzer) extractModuleDescription(content, moduleName string) string { - lines := strings.Split(content, "\n") - - // 查找package注释 - for i, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "package ") { - // 向上查找注释 - for j := i - 1; j >= 0; j-- { - commentLine := strings.TrimSpace(lines[j]) - if strings.HasPrefix(commentLine, "//") { - comment := strings.TrimSpace(strings.TrimPrefix(commentLine, "//")) - if comment != "" && len(comment) > 5 { - return comment - } - } else if commentLine != "" { - break - } - } - break - } - } - - // 查找结构体注释 - for i, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "type ") && strings.Contains(line, " struct") { - // 向上查找注释 - for j := i - 1; j >= 0; j-- { - commentLine := strings.TrimSpace(lines[j]) - if strings.HasPrefix(commentLine, "//") { - comment := strings.TrimSpace(strings.TrimPrefix(commentLine, "//")) - if comment != "" && len(comment) > 5 { - return comment - } - } else if commentLine != "" { - break - } - } - break - } - } - - return fmt.Sprintf("预设计的模块:%s", moduleName) -} - -// Handle 处理工具调用 -func (t *AutomationModuleAnalyzer) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { - action, ok := request.GetArguments()["action"].(string) - if !ok || action == "" { - return nil, errors.New("参数错误:action 必须是非空字符串") - } - - switch action { - case "analyze": - return t.handleAnalyze(ctx, request) - case "confirm": - return t.handleConfirm(ctx, request) - case "execute": - return t.handleExecute(ctx, request) - case "review": - return t.handleReview(ctx, request) - case "enhance": - return t.handleEnhance(ctx, request) - default: - return nil, errors.New("无效的操作:action 必须是 'analyze'、'confirm'、'execute'、'review' 或 'enhance'") - } -} - -// handleAnalyze 处理分析请求 -func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { - requirement, ok := request.GetArguments()["requirement"].(string) - if !ok || requirement == "" { - return nil, errors.New("参数错误:requirement 必须是非空字符串") - } - - // 检测用户是否想要创建插件 - suggestedType, isPlugin, confidence := t.detectPluginIntent(requirement) - pluginDetectionMsg := "" - if isPlugin { - pluginDetectionMsg = fmt.Sprintf("\n\n🔍 **插件检测结果**:检测到用户想要创建插件(置信度:%s)\n⚠️ **重要提醒**:当用户提到插件时,packageType和template字段都必须设置为 \"plugin\",不能使用 \"package\"!", confidence) - } else { - pluginDetectionMsg = fmt.Sprintf("\n\n🔍 **类型检测结果**:建议使用 %s 类型", suggestedType) - } - - // 从数据库获取所有自动化包信息 - var packages []model.SysAutoCodePackage - if err := global.GVA_DB.Find(&packages).Error; err != nil { - return nil, fmt.Errorf("获取包信息失败: %v", err) - } - - // 从数据库获取所有历史记录 - var histories []model.SysAutoCodeHistory - if err := global.GVA_DB.Find(&histories).Error; err != nil { - return nil, fmt.Errorf("获取历史记录失败: %v", err) - } - - // 转换包信息并检查空文件夹 - var moduleInfos []ModuleInfo - var validPackages []model.SysAutoCodePackage - var emptyPackageIDs []uint - var emptyPackageNames []string - - for _, pkg := range packages { - // 检查包对应的文件夹是否为空 - isEmpty, err := t.isPackageFolderEmpty(pkg.PackageName, pkg.Template) - if err != nil { - global.GVA_LOG.Warn(fmt.Sprintf("检查包 %s 文件夹失败: %v", pkg.PackageName, err)) - // 如果检查失败,仍然保留该包 - validPackages = append(validPackages, pkg) - continue - } - - if isEmpty { - // 记录需要删除的包ID和包名 - emptyPackageIDs = append(emptyPackageIDs, pkg.ID) - emptyPackageNames = append(emptyPackageNames, pkg.PackageName) - global.GVA_LOG.Info(fmt.Sprintf("发现空包文件夹: %s,将删除数据库记录和文件夹", pkg.PackageName)) - - // 删除空文件夹 - if err := t.removeEmptyPackageFolder(pkg.PackageName, pkg.Template); err != nil { - global.GVA_LOG.Warn(fmt.Sprintf("删除空包文件夹 %s 失败: %v", pkg.PackageName, err)) - } - } else { - // 文件夹不为空,保留该包 - validPackages = append(validPackages, pkg) - } - } - - // 批量删除空包的数据库记录 - if len(emptyPackageIDs) > 0 { - if err := global.GVA_DB.Where("id IN ?", emptyPackageIDs).Delete(&model.SysAutoCodePackage{}).Error; err != nil { - global.GVA_LOG.Warn(fmt.Sprintf("删除空包数据库记录失败: %v", err)) - } else { - global.GVA_LOG.Info(fmt.Sprintf("成功删除 %d 个空包的数据库记录", len(emptyPackageIDs))) - } - } - - // 转换有效的包信息 - for _, pkg := range validPackages { - moduleInfos = append(moduleInfos, ModuleInfo{ - ID: pkg.ID, - PackageName: pkg.PackageName, - Label: pkg.Label, - Desc: pkg.Desc, - Template: pkg.Template, - Module: pkg.Module, - }) - } - - // 删除与空包相关的历史记录 - var emptyHistoryIDs []uint - if len(emptyPackageNames) > 0 { - for _, history := range histories { - for _, emptyPackageName := range emptyPackageNames { - if history.Package == emptyPackageName { - emptyHistoryIDs = append(emptyHistoryIDs, history.ID) - break - } - } - } - - // 清理相关的API和菜单记录 - if len(emptyHistoryIDs) > 0 { - if err := t.cleanupRelatedApiAndMenus(emptyHistoryIDs); err != nil { - global.GVA_LOG.Warn(fmt.Sprintf("清理空包相关API和菜单失败: %v", err)) - } - } - - // 批量删除相关历史记录 - if len(emptyHistoryIDs) > 0 { - if err := global.GVA_DB.Where("id IN ?", emptyHistoryIDs).Delete(&model.SysAutoCodeHistory{}).Error; err != nil { - global.GVA_LOG.Warn(fmt.Sprintf("删除空包相关历史记录失败: %v", err)) - } else { - global.GVA_LOG.Info(fmt.Sprintf("成功删除 %d 个空包相关的历史记录", len(emptyHistoryIDs))) - } - } - } - - // 创建有效包名的映射,用于快速查找 - validPackageNames := make(map[string]bool) - for _, pkg := range validPackages { - validPackageNames[pkg.PackageName] = true - } - - // 收集需要删除的脏历史记录ID(包名不在有效包列表中的历史记录) - var dirtyHistoryIDs []uint - for _, history := range histories { - if !validPackageNames[history.Package] { - dirtyHistoryIDs = append(dirtyHistoryIDs, history.ID) - } - } - - // 删除脏历史记录 - if len(dirtyHistoryIDs) > 0 { - // 清理相关的API和菜单记录 - if err := t.cleanupRelatedApiAndMenus(dirtyHistoryIDs); err != nil { - global.GVA_LOG.Warn(fmt.Sprintf("清理脏历史记录相关API和菜单失败: %v", err)) - } - - if err := global.GVA_DB.Where("id IN ?", dirtyHistoryIDs).Delete(&model.SysAutoCodeHistory{}).Error; err != nil { - global.GVA_LOG.Warn(fmt.Sprintf("删除脏历史记录失败: %v", err)) - } else { - global.GVA_LOG.Info(fmt.Sprintf("成功删除 %d 个脏历史记录(包名不在有效包列表中)", len(dirtyHistoryIDs))) - } - } - - // 转换有效的历史记录(只保留包名存在于有效包列表中的历史记录) - var historyInfos []HistoryInfo - for _, history := range histories { - // 只保留包名存在于有效包列表中的历史记录 - if validPackageNames[history.Package] { - historyInfos = append(historyInfos, HistoryInfo{ - ID: history.ID, - StructName: history.StructName, - TableName: history.TableName(), - PackageName: history.Package, - BusinessDB: history.BusinessDB, - Description: history.Description, - Abbreviation: history.Abbreviation, - CreatedAt: history.CreatedAt.Format("2006-01-02 15:04:05"), - }) - } - } - - // 扫描预设计的模块 - allPredesignedModules, err := t.scanPredesignedModules() - if err != nil { - global.GVA_LOG.Warn("扫描预设计模块失败" + err.Error()) - allPredesignedModules = []PredesignedModuleInfo{} // 确保不为nil - } - - // 过滤掉与已删除包相关的预设计模块 - var predesignedModules []PredesignedModuleInfo - for _, module := range allPredesignedModules { - isDeleted := false - for _, emptyPackageName := range emptyPackageNames { - if module.PackageName == emptyPackageName { - isDeleted = true - break - } - } - - // 只保留未被删除包的预设计模块 - if !isDeleted { - predesignedModules = append(predesignedModules, module) - } - } - - // 构建分析结果消息 - var message string - var deletionDetails []string - - // 收集删除信息 - if len(emptyHistoryIDs) > 0 { - deletionDetails = append(deletionDetails, fmt.Sprintf("%d个空包相关历史记录", len(emptyHistoryIDs))) - } - if len(dirtyHistoryIDs) > 0 { - deletionDetails = append(deletionDetails, fmt.Sprintf("%d个脏历史记录", len(dirtyHistoryIDs))) - } - if len(allPredesignedModules) > len(predesignedModules) { - deletionDetails = append(deletionDetails, fmt.Sprintf("%d个相关预设计模块", len(allPredesignedModules)-len(predesignedModules))) - } - - if len(emptyPackageNames) > 0 || len(deletionDetails) > 0 { - var cleanupInfo string - if len(emptyPackageNames) > 0 { - cleanupInfo = fmt.Sprintf("检测到存在 %s 包但内容为空,我已经删除这些包的文件夹(包括model、api、service、router目录)和数据库记录", strings.Join(emptyPackageNames, "、")) - } - - deletionInfo := "" - if len(deletionDetails) > 0 { - if cleanupInfo != "" { - deletionInfo = fmt.Sprintf(",同时删除了%s", strings.Join(deletionDetails, "、")) - } else { - deletionInfo = fmt.Sprintf("检测到脏数据,已删除%s", strings.Join(deletionDetails, "、")) - } - } - - if cleanupInfo != "" { - message = fmt.Sprintf("分析完成:获取到 %d 个有效包、%d 个历史记录和 %d 个预设计模块。%s%s,如果需要使用这些包名,需要重新创建。请AI根据需求选择合适的包和模块", len(validPackages), len(historyInfos), len(predesignedModules), cleanupInfo, deletionInfo) - } else { - message = fmt.Sprintf("分析完成:获取到 %d 个有效包、%d 个历史记录和 %d 个预设计模块。%s。请AI根据需求选择合适的包和模块", len(validPackages), len(historyInfos), len(predesignedModules), deletionInfo) - } - } else { - message = fmt.Sprintf("分析完成:获取到 %d 个有效包、%d 个历史记录和 %d 个预设计模块,请AI根据需求选择合适的包和模块", len(validPackages), len(historyInfos), len(predesignedModules)) - } - - // 构建分析结果 - analysisResult := AnalysisResponse{ - Packages: moduleInfos, - History: historyInfos, - PredesignedModules: predesignedModules, - Message: message, - } - - resultJSON, err := json.MarshalIndent(analysisResult, "", " ") - if err != nil { - return nil, fmt.Errorf("序列化结果失败: %v", err) - } - - return &mcp.CallToolResult{ - Content: []mcp.Content{ - mcp.TextContent{ - Type: "text", - Text: fmt.Sprintf(`分析结果: - -%s - -请AI根据用户需求:%s%s - -%s - -分析现有的包、历史记录和预设计模块,然后构建ExecutionPlan结构体调用execute操作。 - -**预设计模块说明**: -- 预设计模块是已经存在于autocode路径下的package或plugin -- 这些模块包含了预先设计好的代码结构,可以直接使用或作为参考 -- 如果用户需求与某个预设计模块匹配,可以考虑直接使用该模块或基于它进行扩展 - -**字典选项生成说明**: -- 当字段需要使用字典类型时(dictType不为空),请使用 generate_dictionary_options 工具 -- 该工具允许AI根据字段描述智能生成合适的字典选项 -- 调用示例: - { - "dictType": "user_status", - "fieldDesc": "用户状态", - "options": [ - {"label": "正常", "value": "1", "sort": 1}, - {"label": "禁用", "value": "0", "sort": 2} - ], - "dictName": "用户状态字典", - "description": "用于管理用户账户状态的字典" - } -- 请在创建模块之前先创建所需的字典选项 - -重要提醒:ExecutionPlan必须严格按照以下格式(支持批量创建多个模块): -{ - "packageName": "包名", - "packageType": "package或plugin", // 当用户提到插件时必须是"plugin" - "needCreatedPackage": true/false, - "needCreatedModules": true/false, - "packageInfo": { - "desc": "描述", - "label": "展示名", - "template": "package或plugin", // 必须与packageType保持一致! - "packageName": "包名" - }, - "modulesInfo": [{ - "package": "包名", - "tableName": "数据库表名", - "businessDB": "", - "structName": "结构体名", - "packageName": "文件名称小驼峰模式 一般是结构体名的小驼峰", - "description": "中文描述", - "abbreviation": "简称 package和结构体简称不可同名 小驼峰模式", - "humpPackageName": "一般是结构体名的下划线分割的小驼峰 例如:sys_user", - "gvaModel": true, - "autoMigrate": true, - "autoCreateResource": true/false 用户不特地强调开启资源标识则为false, - "autoCreateApiToSql": true, - "autoCreateMenuToSql": true, - "autoCreateBtnAuth": false/true 用户不特地强调创建按钮权限则为false, - "onlyTemplate": false, - "isTree": false, - "treeJson": "", - "isAdd": false, - "generateWeb": true, - "generateServer": true, - "fields": [{ - "fieldName": "字段名(必须大写开头)", - "fieldDesc": "字段描述", - "fieldType": "字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组)", - "fieldJson": "json标签", - "dataTypeLong": "长度", - "comment": "注释", - "columnName": "数据库列名", - "fieldSearchType": "=/!=/>/=/<=/LIKE等 可以为空", - "fieldSearchHide": true/false, - "dictType": "", - "form": true/false 是否前端创建输入, - "table": true/false 是否前端表格展示, - "desc": true/false 是否前端详情展示, - "excel": true/false 是否导出Excel, - "require": true/false 是否必填, - "defaultValue": "", - "errorText": "错误提示", - "clearable": true, - "sort": false, - "primaryKey": "当gvaModel=false时必须有一个字段设为true(bool)", - "dataSource": null, - "checkDataSource": false, - "fieldIndexType": "" - }] - }, { - "package": "包名", - "tableName": "第二个模块的表名", - "structName": "第二个模块的结构体名", - "description": "第二个模块的描述", - "...": "更多模块配置..." - }] -} - -**重要提醒**:ExecutionPlan必须严格按照以下格式和验证规则: - -**插件类型检测规则(最重要)**: -1. 当用户需求中包含"插件"、"plugin"等关键词时,packageType和template都必须设置为"plugin" -2. packageType和template字段必须保持一致,不能一个是"package"另一个是"plugin" -3. 如果检测到插件意图但设置错误,会导致创建失败 - -**字段完整性要求**: -4. 所有字符串字段都不能为空(包括packageName、moduleName、structName、tableName、description等) -5. 所有布尔字段必须明确设置true或false,不能使用默认值 - -**主键设置规则(关键)**: -6. 当gvaModel=false时:fields数组中必须有且仅有一个字段的primaryKey=true -7. 当gvaModel=true时:系统自动创建ID主键,fields中所有字段的primaryKey都应为false -8. 主键设置错误会导致模板执行时PrimaryField为nil的严重错误! - -**包和模块创建逻辑**: -9. 如果存在可用的package,needCreatedPackage应设为false -10. 如果存在可用的modules,needCreatedModules应设为false -11. 如果发现合适的预设计模块,可以考虑基于它进行扩展而不是从零创建 - -**字典创建流程**: -12. 如果字段需要字典类型,请先使用 generate_dictionary_options 工具创建字典 -13. 字典创建成功后,再执行模块创建操作 - -`, string(resultJSON), requirement, pluginDetectionMsg, - func() string { - if len(emptyPackageNames) > 0 { - return fmt.Sprintf("**重要提醒**:检测到 %s 包存在但内容为空,已自动删除相关文件夹和数据库记录。如果用户需求涉及这些包名,请设置 needCreatedPackage=true 重新创建。", strings.Join(emptyPackageNames, "、")) - } - return "" - }()), - }, - }, - }, nil -} - -// performCodeReview 执行代码复检 -func (t *AutomationModuleAnalyzer) performCodeReview(reviewReq *ReviewRequest) (*ReviewResult, error) { - global.GVA_LOG.Info("开始执行代码复检...") - - // 初始化复检结果 - reviewResult := &ReviewResult{ - Success: false, - Message: "", - SatisfiedRequirements: []string{}, - MissingRequirements: []string{}, - FilesToEnhance: make(map[string][]string), - ReviewDetails: make(map[string]interface{}), - } - - // 解析用户需求 - requirements, err := t.parseUserRequirements(reviewReq.OriginalRequirement) - if err != nil { - return nil, fmt.Errorf("解析用户需求失败: %v", err) - } - - // 分析生成的代码文件 - generatedFeatures, err := t.analyzeGeneratedCode(reviewReq.GeneratedPaths, reviewReq.ExecutionPlan) - if err != nil { - return nil, fmt.Errorf("分析生成代码失败: %v", err) - } - - // 对比需求和实现 - for _, requirement := range requirements { - if t.isRequirementSatisfied(requirement, generatedFeatures) { - reviewResult.SatisfiedRequirements = append(reviewResult.SatisfiedRequirements, requirement) - } else { - reviewResult.MissingRequirements = append(reviewResult.MissingRequirements, requirement) - // 确定需要增强的文件 - filesToEnhance := t.determineFilesToEnhance(requirement, reviewReq.ExecutionPlan) - for file, enhancements := range filesToEnhance { - if reviewResult.FilesToEnhance[file] == nil { - reviewResult.FilesToEnhance[file] = []string{} - } - reviewResult.FilesToEnhance[file] = append(reviewResult.FilesToEnhance[file], enhancements...) - } - } - } - - // 设置复检结果 - reviewResult.Success = len(reviewResult.MissingRequirements) == 0 - if reviewResult.Success { - reviewResult.Message = fmt.Sprintf("✅ 代码复检通过!所有 %d 个需求都已满足。", len(reviewResult.SatisfiedRequirements)) - } else { - reviewResult.Message = fmt.Sprintf("⚠️ 代码复检发现问题:满足了 %d 个需求,还有 %d 个需求未满足,需要进行代码增强。", - len(reviewResult.SatisfiedRequirements), len(reviewResult.MissingRequirements)) - } - - // 添加详细信息 - reviewResult.ReviewDetails["totalRequirements"] = len(requirements) - reviewResult.ReviewDetails["satisfiedCount"] = len(reviewResult.SatisfiedRequirements) - reviewResult.ReviewDetails["missingCount"] = len(reviewResult.MissingRequirements) - reviewResult.ReviewDetails["generatedFeatures"] = generatedFeatures - reviewResult.ReviewDetails["reviewTime"] = time.Now().Format("2006-01-02 15:04:05") - - global.GVA_LOG.Info(fmt.Sprintf("代码复检完成:%s", reviewResult.Message)) - return reviewResult, nil -} - -// performCodeEnhancement 执行代码增强 -func (t *AutomationModuleAnalyzer) performCodeEnhancement(enhanceReq *EnhanceRequest) (*EnhanceResult, error) { - global.GVA_LOG.Info("开始执行代码增强...") - - // 初始化增强结果 - enhanceResult := &EnhanceResult{ - Success: false, - Message: "", - EnhancedFiles: []string{}, - Modifications: make(map[string]string), - NextReviewNeeded: true, - } - - // 为每个需要增强的文件生成代码 - for filePath, requirements := range enhanceReq.FilesToEnhance { - global.GVA_LOG.Info(fmt.Sprintf("正在增强文件: %s", filePath)) - - // 读取现有文件内容 - existingContent, err := t.readFileContent(filePath) - if err != nil { - global.GVA_LOG.Warn(fmt.Sprintf("读取文件失败 %s: %v", filePath, err)) - continue - } - - // 生成增强代码 - enhancedContent, err := t.generateEnhancedCode(filePath, existingContent, requirements, enhanceReq) - if err != nil { - global.GVA_LOG.Error(fmt.Sprintf("生成增强代码失败 %s: %v", filePath, err)) - continue - } - - // 写入增强后的代码 - if err := t.writeFileContent(filePath, enhancedContent); err != nil { - global.GVA_LOG.Error(fmt.Sprintf("写入增强代码失败 %s: %v", filePath, err)) - continue - } - - enhanceResult.EnhancedFiles = append(enhanceResult.EnhancedFiles, filePath) - enhanceResult.Modifications[filePath] = fmt.Sprintf("增强了以下功能: %s", strings.Join(requirements, ", ")) - global.GVA_LOG.Info(fmt.Sprintf("文件增强完成: %s", filePath)) - } - - // 设置增强结果 - if len(enhanceResult.EnhancedFiles) > 0 { - enhanceResult.Success = true - enhanceResult.Message = fmt.Sprintf("✅ 代码增强完成!成功增强了 %d 个文件,建议进行再次复检验证。", len(enhanceResult.EnhancedFiles)) - } else { - enhanceResult.Message = "❌ 代码增强失败:没有成功增强任何文件。" - enhanceResult.NextReviewNeeded = false - } - - global.GVA_LOG.Info(fmt.Sprintf("代码增强完成:%s", enhanceResult.Message)) - return enhanceResult, nil -} - -// parseUserRequirements 解析用户需求为具体的功能点 -func (t *AutomationModuleAnalyzer) parseUserRequirements(requirement string) ([]string, error) { - // 简单的需求解析逻辑,可以根据实际情况扩展 - requirements := []string{} - - // 按句号或换行分割需求 - lines := strings.Split(requirement, "\n") - for _, line := range lines { - line = strings.TrimSpace(line) - if line != "" { - // 进一步按句号分割 - sentences := strings.Split(line, "。") - for _, sentence := range sentences { - sentence = strings.TrimSpace(sentence) - if sentence != "" && len(sentence) > 3 { - requirements = append(requirements, sentence) - } - } - } - } - - // 如果没有解析出具体需求,将整个需求作为一个功能点 - if len(requirements) == 0 { - requirements = append(requirements, strings.TrimSpace(requirement)) - } - - return requirements, nil -} - -// analyzeGeneratedCode 分析生成的代码文件,提取已实现的功能 -func (t *AutomationModuleAnalyzer) analyzeGeneratedCode(generatedPaths map[string]string, executionPlan *ExecutionPlan) ([]string, error) { - features := []string{} - - // 基于执行计划分析基础功能 - if executionPlan != nil && executionPlan.ModulesInfo != nil { - for _, moduleInfo := range executionPlan.ModulesInfo { - // 基础CRUD功能 - features = append(features, fmt.Sprintf("%s的增删改查功能", moduleInfo.Description)) - - // 字段相关功能 - for _, field := range moduleInfo.Fields { - if field.FieldSearchType != "" { - features = append(features, fmt.Sprintf("%s的%s搜索功能", field.FieldDesc, field.FieldSearchType)) - } - if field.DictType != "" { - features = append(features, fmt.Sprintf("%s的字典选择功能", field.FieldDesc)) - } - if field.DataSource != nil { - features = append(features, fmt.Sprintf("%s的关联数据功能", field.FieldDesc)) - } - } - - // 特殊功能 - if moduleInfo.IsTree { - features = append(features, fmt.Sprintf("%s的树形结构功能", moduleInfo.Description)) - } - if moduleInfo.AutoCreateApiToSql { - features = append(features, fmt.Sprintf("%s的API接口功能", moduleInfo.Description)) - } - if moduleInfo.AutoCreateMenuToSql { - features = append(features, fmt.Sprintf("%s的菜单管理功能", moduleInfo.Description)) - } - } - } - - // 分析生成的文件 - for path := range generatedPaths { - if strings.Contains(path, "api") { - features = append(features, "API接口层") - } - if strings.Contains(path, "service") { - features = append(features, "业务逻辑层") - } - if strings.Contains(path, "model") { - features = append(features, "数据模型层") - } - if strings.Contains(path, "router") { - features = append(features, "路由配置") - } - if strings.Contains(path, ".vue") { - features = append(features, "前端页面") - } - } - - return features, nil -} - -// isRequirementSatisfied 判断需求是否已被满足 -func (t *AutomationModuleAnalyzer) isRequirementSatisfied(requirement string, generatedFeatures []string) bool { - // 简单的关键词匹配逻辑 - requirement = strings.ToLower(requirement) - - // 检查是否包含基础CRUD关键词 - crudKeywords := []string{"增加", "创建", "新增", "删除", "修改", "编辑", "查询", "搜索", "列表"} - for _, keyword := range crudKeywords { - if strings.Contains(requirement, keyword) { - // 检查生成的功能中是否有对应的CRUD功能 - for _, feature := range generatedFeatures { - if strings.Contains(strings.ToLower(feature), "增删改查") { - return true - } - } - } - } - - // 检查特殊功能关键词 - specialKeywords := map[string][]string{ - "树形": {"树形结构"}, - "层级": {"树形结构"}, - "字典": {"字典选择"}, - "下拉": {"字典选择"}, - "关联": {"关联数据"}, - "外键": {"关联数据"}, - } - - for reqKeyword, featureKeywords := range specialKeywords { - if strings.Contains(requirement, reqKeyword) { - for _, feature := range generatedFeatures { - for _, featureKeyword := range featureKeywords { - if strings.Contains(strings.ToLower(feature), featureKeyword) { - return true - } - } - } - } - } - - // 默认认为基础需求已满足(可以根据实际情况调整) - return len(generatedFeatures) > 0 -} - -// determineFilesToEnhance 确定需要增强的文件 -func (t *AutomationModuleAnalyzer) determineFilesToEnhance(requirement string, executionPlan *ExecutionPlan) map[string][]string { - filesToEnhance := make(map[string][]string) - - if executionPlan == nil || executionPlan.ModulesInfo == nil { - return filesToEnhance - } - - // 根据需求类型确定需要修改的文件 - requirement = strings.ToLower(requirement) - - for _, moduleInfo := range executionPlan.ModulesInfo { - packagePath := fmt.Sprintf("%s/%s", executionPlan.PackageName, moduleInfo.PackageName) - - // API相关需求 - if strings.Contains(requirement, "接口") || strings.Contains(requirement, "api") { - apiFile := fmt.Sprintf("api/v1/%s/%s.go", packagePath, moduleInfo.PackageName) - filesToEnhance[apiFile] = append(filesToEnhance[apiFile], requirement) - } - - // 业务逻辑相关需求 - if strings.Contains(requirement, "逻辑") || strings.Contains(requirement, "业务") || strings.Contains(requirement, "流程") { - serviceFile := fmt.Sprintf("service/%s/%s.go", packagePath, moduleInfo.PackageName) - filesToEnhance[serviceFile] = append(filesToEnhance[serviceFile], requirement) - } - - // 数据模型相关需求 - if strings.Contains(requirement, "字段") || strings.Contains(requirement, "模型") || strings.Contains(requirement, "数据") { - modelFile := fmt.Sprintf("model/%s/%s.go", packagePath, moduleInfo.PackageName) - filesToEnhance[modelFile] = append(filesToEnhance[modelFile], requirement) - } - - // 前端相关需求 - if strings.Contains(requirement, "页面") || strings.Contains(requirement, "界面") || strings.Contains(requirement, "前端") { - vueFile := fmt.Sprintf("web/src/view/%s/%s.vue", packagePath, moduleInfo.PackageName) - filesToEnhance[vueFile] = append(filesToEnhance[vueFile], requirement) - } - - // 默认情况:如果无法确定具体文件,则增强service层 - if len(filesToEnhance) == 0 { - serviceFile := fmt.Sprintf("service/%s/%s.go", packagePath, moduleInfo.PackageName) - filesToEnhance[serviceFile] = append(filesToEnhance[serviceFile], requirement) - } - } - - return filesToEnhance -} - -// readFileContent 读取文件内容 -func (t *AutomationModuleAnalyzer) readFileContent(filePath string) (string, error) { - // 构建完整的文件路径 - fullPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, filePath) - - // 检查文件是否存在 - if _, err := os.Stat(fullPath); os.IsNotExist(err) { - return "", fmt.Errorf("文件不存在: %s", fullPath) - } - - // 读取文件内容 - content, err := os.ReadFile(fullPath) - if err != nil { - return "", fmt.Errorf("读取文件失败: %v", err) - } - - return string(content), nil -} - -// writeFileContent 写入文件内容 -func (t *AutomationModuleAnalyzer) writeFileContent(filePath, content string) error { - // 构建完整的文件路径 - fullPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, filePath) - - // 确保目录存在 - dir := filepath.Dir(fullPath) - if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("创建目录失败: %v", err) - } - - // 写入文件 - if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil { - return fmt.Errorf("写入文件失败: %v", err) - } - - return nil -} - -// generateEnhancedCode 生成增强代码 -func (t *AutomationModuleAnalyzer) generateEnhancedCode(filePath, existingContent string, requirements []string, enhanceReq *EnhanceRequest) (string, error) { - // 这里是AI代码生成的核心逻辑 - // 目前提供一个基础实现,后续可以集成真正的AI服务 - - // 构建增强提示 - enhancementPrompt := fmt.Sprintf(` -请根据以下需求对代码进行增强: - -原始需求:%s - -未满足的具体需求: -%s - -当前文件路径:%s - -现有代码: -%s - -请生成增强后的完整代码,确保: -1. 保持现有代码的结构和功能 -2. 添加缺失的功能实现 -3. 遵循Go语言最佳实践 -4. 添加适当的注释说明 -`, - enhanceReq.OriginalRequirement, - strings.Join(requirements, "\n- "), - filePath, - existingContent) - - // 简单的代码增强逻辑(实际项目中应该调用AI服务) - enhancedContent := existingContent - - // 添加增强注释 - enhancementComment := fmt.Sprintf(` -// 代码增强 - %s -// 增强时间: %s -// 增强需求: %s -`, - time.Now().Format("2006-01-02 15:04:05"), - time.Now().Format("2006-01-02 15:04:05"), - strings.Join(requirements, ", ")) - - // 在文件末尾添加增强注释和占位符代码 - enhancedContent += enhancementComment - enhancedContent += ` -// TODO: 请根据上述需求完善以下代码实现 -` - - for _, requirement := range requirements { - enhancedContent += fmt.Sprintf(` -// TODO: 实现需求 - %s -func (s *%sService) enhance%s() error { - // 请在此处添加具体实现 - return nil -} -`, - requirement, - strings.Title(filepath.Base(filePath)), - strings.ReplaceAll(requirement, " ", "")) - } - - global.GVA_LOG.Info(fmt.Sprintf("生成增强代码提示:%s", enhancementPrompt)) - - return enhancedContent, nil -} - -// collectExpectedFilePaths 收集预期生成的文件路径 -func (t *AutomationModuleAnalyzer) collectExpectedFilePaths(plan *ExecutionPlan) []string { - var filePaths []string - - if plan == nil || plan.ModulesInfo == nil { - return filePaths - } - - // 遍历所有模块,收集预期生成的文件路径 - for _, moduleInfo := range plan.ModulesInfo { - if moduleInfo == nil { - continue - } - - // 构建包路径 - packagePath := "" - if plan.PackageType == "plugin" { - packagePath = fmt.Sprintf("plugin/%s", plan.PackageName) - } else { - packagePath = plan.PackageName - } - - // 后端文件路径 - if moduleInfo.GenerateServer { - // API文件 - apiPath := fmt.Sprintf("api/v1/%s/%s.go", packagePath, moduleInfo.PackageName) - filePaths = append(filePaths, apiPath) - - // Service文件 - servicePath := fmt.Sprintf("service/%s/%s.go", packagePath, moduleInfo.PackageName) - filePaths = append(filePaths, servicePath) - - // Model文件 - modelPath := fmt.Sprintf("model/%s/%s.go", packagePath, moduleInfo.PackageName) - filePaths = append(filePaths, modelPath) - - // Router文件 - routerPath := fmt.Sprintf("router/%s/%s.go", packagePath, moduleInfo.PackageName) - filePaths = append(filePaths, routerPath) - } - - // 前端文件路径 - if moduleInfo.GenerateWeb { - // Vue页面文件 - vuePath := fmt.Sprintf("web/src/view/%s/%s.vue", packagePath, moduleInfo.PackageName) - filePaths = append(filePaths, vuePath) - - // API接口文件 - webApiPath := fmt.Sprintf("web/src/api/%s.js", moduleInfo.PackageName) - filePaths = append(filePaths, webApiPath) - } - } - - return filePaths -} - -// handleConfirm 处理确认请求 -func (t *AutomationModuleAnalyzer) handleConfirm(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { - executionPlanData, ok := request.GetArguments()["executionPlan"] - if !ok { - return nil, errors.New("参数错误:executionPlan 必须提供") - } - - // 解析执行计划 - planJSON, err := json.Marshal(executionPlanData) - if err != nil { - return nil, fmt.Errorf("解析执行计划失败: %v", err) - } - - var plan ExecutionPlan - err = json.Unmarshal(planJSON, &plan) - if err != nil { - return nil, fmt.Errorf("解析执行计划失败: %v\n\n请确保ExecutionPlan格式正确,参考工具描述中的结构体格式要求", err) - } - - // 验证执行计划的完整性 - if err := t.validateExecutionPlan(&plan); err != nil { - return nil, fmt.Errorf("执行计划验证失败: %v", err) - } - - // 构建确认响应 - var moduleNames []string - for _, moduleInfo := range plan.ModulesInfo { - moduleNames = append(moduleNames, moduleInfo.StructName) - } - moduleNamesStr := strings.Join(moduleNames, "_") - - confirmResponse := ConfirmationResponse{ - Message: "请确认以下创建计划:", - PackageConfirm: plan.NeedCreatedPackage, - ModulesConfirm: plan.NeedCreatedModules, - CanProceed: true, - ConfirmationKey: fmt.Sprintf("%s_%s_%d", plan.PackageName, moduleNamesStr, time.Now().Unix()), - } - - // 构建详细的确认信息 - var confirmDetails strings.Builder - confirmDetails.WriteString(fmt.Sprintf("包名: %s\n", plan.PackageName)) - confirmDetails.WriteString(fmt.Sprintf("包类型: %s\n", plan.PackageType)) - - if plan.NeedCreatedPackage && plan.PackageInfo != nil { - confirmDetails.WriteString("\n需要创建包:\n") - confirmDetails.WriteString(fmt.Sprintf(" - 包名: %s\n", plan.PackageInfo.PackageName)) - confirmDetails.WriteString(fmt.Sprintf(" - 标签: %s\n", plan.PackageInfo.Label)) - confirmDetails.WriteString(fmt.Sprintf(" - 描述: %s\n", plan.PackageInfo.Desc)) - confirmDetails.WriteString(fmt.Sprintf(" - 模板: %s\n", plan.PackageInfo.Template)) - } - - if plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 { - confirmDetails.WriteString(fmt.Sprintf("\n需要创建模块 (共%d个):\n", len(plan.ModulesInfo))) - for i, moduleInfo := range plan.ModulesInfo { - confirmDetails.WriteString(fmt.Sprintf("\n模块 %d:\n", i+1)) - confirmDetails.WriteString(fmt.Sprintf(" - 结构体名: %s\n", moduleInfo.StructName)) - confirmDetails.WriteString(fmt.Sprintf(" - 表名: %s\n", moduleInfo.TableName)) - confirmDetails.WriteString(fmt.Sprintf(" - 描述: %s\n", moduleInfo.Description)) - confirmDetails.WriteString(fmt.Sprintf(" - 字段数量: %d\n", len(moduleInfo.Fields))) - confirmDetails.WriteString(" - 字段列表:\n") - for _, field := range moduleInfo.Fields { - confirmDetails.WriteString(fmt.Sprintf(" * %s (%s): %s\n", field.FieldName, field.FieldType, field.FieldDesc)) - } - } - } - - resultJSON, err := json.MarshalIndent(confirmResponse, "", " ") - if err != nil { - return nil, fmt.Errorf("序列化结果失败: %v", err) - } - - return &mcp.CallToolResult{ - Content: []mcp.Content{ - mcp.TextContent{ - Type: "text", - Text: fmt.Sprintf("确认信息:\n\n%s\n\n详细信息:\n%s\n\n请用户确认是否继续执行此计划。如果确认,请使用execute操作并提供相应的确认参数。", string(resultJSON), confirmDetails.String()), - }, - }, - }, nil -} - -// handleExecute 处理执行请求 -func (t *AutomationModuleAnalyzer) handleExecute(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { - executionPlanData, ok := request.GetArguments()["executionPlan"] - if !ok { - return nil, errors.New("参数错误:executionPlan 必须提供") - } - - // 解析执行计划 - planJSON, err := json.Marshal(executionPlanData) - if err != nil { - return nil, fmt.Errorf("解析执行计划失败: %v", err) - } - - var plan ExecutionPlan - err = json.Unmarshal(planJSON, &plan) - if err != nil { - return nil, fmt.Errorf("解析执行计划失败: %v\n\n请确保ExecutionPlan格式正确,参考工具描述中的结构体格式要求", err) - } - - // 验证执行计划的完整性 - if err := t.validateExecutionPlan(&plan); err != nil { - return nil, fmt.Errorf("执行计划验证失败: %v", err) - } - - // 检查用户确认 - if plan.NeedCreatedPackage { - packageConfirm, ok := request.GetArguments()["packageConfirm"].(string) - if !ok || (packageConfirm != "yes" && packageConfirm != "no") { - return nil, errors.New("参数错误:当需要创建包时,packageConfirm 必须是 'yes' 或 'no'") - } - if packageConfirm == "no" { - return &mcp.CallToolResult{ - Content: []mcp.Content{ - mcp.TextContent{ - Type: "text", - Text: "用户取消了包的创建操作", - }, - }, - }, nil - } - } - - if plan.NeedCreatedModules { - modulesConfirm, ok := request.GetArguments()["modulesConfirm"].(string) - if !ok || (modulesConfirm != "yes" && modulesConfirm != "no") { - return nil, errors.New("参数错误:当需要创建模块时,modulesConfirm 必须是 'yes' 或 'no'") - } - if modulesConfirm == "no" { - return &mcp.CallToolResult{ - Content: []mcp.Content{ - mcp.TextContent{ - Type: "text", - Text: "用户取消了模块的创建操作", - }, - }, - }, nil - } - } - - // 执行创建操作 - result := t.executeCreation(ctx, &plan) - - // 检查是否需要获取原始需求进行代码复检 - var originalRequirement string - if reqData, ok := request.GetArguments()["requirement"]; ok { - if reqStr, ok := reqData.(string); ok { - originalRequirement = reqStr - } - } - - // 如果执行成功且有原始需求,自动触发代码复检 - var reviewMessage string - if result.Success && originalRequirement != "" { - global.GVA_LOG.Info("执行完成,返回生成的文件路径供AI进行代码复检...") - - // 构建文件路径信息供AI使用 - var pathsInfo []string - for _, path := range result.GeneratedPaths { - pathsInfo = append(pathsInfo, fmt.Sprintf("- %s", path)) - } - - reviewMessage = fmt.Sprintf("\n\n📁 已生成以下文件:\n%s\n\n💡 提示:请使用 'review' 操作对生成的代码进行复检,确保满足原始需求。", strings.Join(pathsInfo, "\n")) - } else if originalRequirement == "" { - reviewMessage = "\n\n💡 提示:如需代码复检,请使用 'review' 操作并提供原始需求描述。" - } - - resultJSON, err := json.MarshalIndent(result, "", " ") - if err != nil { - return nil, fmt.Errorf("序列化结果失败: %v", err) - } - - // 添加权限分配提醒 - permissionReminder := "\n\n⚠️ 重要提醒:\n" + - "模块创建完成后,请前往【系统管理】->【角色管理】中为相关角色分配新创建的API和菜单权限," + - "以确保用户能够正常访问新功能。\n" + - "具体步骤:\n" + - "1. 进入角色管理页面\n" + - "2. 选择需要授权的角色\n" + - "3. 在API权限中勾选新创建的API接口\n" + - "4. 在菜单权限中勾选新创建的菜单项\n" + - "5. 保存权限配置" - - return &mcp.CallToolResult{ - Content: []mcp.Content{ - mcp.TextContent{ - Type: "text", - Text: fmt.Sprintf("执行结果:\n\n%s%s%s", string(resultJSON), reviewMessage, permissionReminder), - }, - }, - }, nil -} - -// isSystemFunction 判断是否为系统功能 -func (t *AutomationModuleAnalyzer) isSystemFunction(requirement string) bool { - systemKeywords := []string{ - "用户", "权限", "角色", "菜单", "系统", "配置", "字典", "参数", - "user", "authority", "role", "menu", "system", "config", "dictionary", - "认证", "授权", "登录", "注册", "JWT", "casbin", - } - - requirementLower := strings.ToLower(requirement) - for _, keyword := range systemKeywords { - if strings.Contains(requirementLower, keyword) { - return true - } - } - return false -} - -// contains 检查字符串切片中是否包含指定元素 -func contains(slice []string, item string) bool { - for _, s := range slice { - if s == item { - return true - } - } - return false -} - -// buildDirectoryStructure 构建目录结构信息 -func (t *AutomationModuleAnalyzer) buildDirectoryStructure(plan *ExecutionPlan) map[string]string { - paths := make(map[string]string) - - // 获取配置信息 - autoCodeConfig := global.GVA_CONFIG.AutoCode - - // 构建基础路径 - rootPath := autoCodeConfig.Root - serverPath := autoCodeConfig.Server - webPath := autoCodeConfig.Web - moduleName := autoCodeConfig.Module - - // 如果计划中有包名,使用计划中的包名,否则使用默认 - packageName := "example" - if plan.PackageInfo != nil && plan.PackageInfo.PackageName != "" { - packageName = plan.PackageInfo.PackageName - } - - // 如果计划中有模块信息,获取第一个模块的结构名作为默认值 - structName := "ExampleStruct" - if len(plan.ModulesInfo) > 0 && plan.ModulesInfo[0].StructName != "" { - structName = plan.ModulesInfo[0].StructName - } - - // 根据包类型构建不同的路径结构 - packageType := plan.PackageType - if packageType == "" { - packageType = "package" // 默认为package模式 - } - - // 构建服务端路径 - if serverPath != "" { - serverBasePath := fmt.Sprintf("%s/%s", rootPath, serverPath) - - if packageType == "plugin" { - // Plugin 模式:所有文件都在 /plugin/packageName/ 目录下 - pluginBasePath := fmt.Sprintf("%s/plugin/%s", serverBasePath, packageName) - - // API 路径 - paths["api"] = fmt.Sprintf("%s/api", pluginBasePath) - - // Service 路径 - paths["service"] = fmt.Sprintf("%s/service", pluginBasePath) - - // Model 路径 - paths["model"] = fmt.Sprintf("%s/model", pluginBasePath) - - // Router 路径 - paths["router"] = fmt.Sprintf("%s/router", pluginBasePath) - - // Request 路径 - paths["request"] = fmt.Sprintf("%s/model/request", pluginBasePath) - - // Response 路径 - paths["response"] = fmt.Sprintf("%s/model/response", pluginBasePath) - - // Plugin 特有文件 - paths["plugin_main"] = fmt.Sprintf("%s/main.go", pluginBasePath) - paths["plugin_config"] = fmt.Sprintf("%s/plugin.go", pluginBasePath) - paths["plugin_initialize"] = fmt.Sprintf("%s/initialize", pluginBasePath) - } else { - // Package 模式:传统的目录结构 - // API 路径 - paths["api"] = fmt.Sprintf("%s/api/v1/%s", serverBasePath, packageName) - - // Service 路径 - paths["service"] = fmt.Sprintf("%s/service/%s", serverBasePath, packageName) - - // Model 路径 - paths["model"] = fmt.Sprintf("%s/model/%s", serverBasePath, packageName) - - // Router 路径 - paths["router"] = fmt.Sprintf("%s/router/%s", serverBasePath, packageName) - - // Request 路径 - paths["request"] = fmt.Sprintf("%s/model/%s/request", serverBasePath, packageName) - - // Response 路径 - paths["response"] = fmt.Sprintf("%s/model/%s/response", serverBasePath, packageName) - } - } - - // 构建前端路径(两种模式相同) - if webPath != "" { - webBasePath := fmt.Sprintf("%s/%s", rootPath, webPath) - - // Vue 页面路径 - paths["vue_page"] = fmt.Sprintf("%s/view/%s", webBasePath, packageName) - - // API 路径 - paths["vue_api"] = fmt.Sprintf("%s/api/%s", webBasePath, packageName) - } - - // 添加模块信息 - paths["module"] = moduleName - paths["package_name"] = packageName - paths["package_type"] = packageType - paths["struct_name"] = structName - paths["root_path"] = rootPath - paths["server_path"] = serverPath - paths["web_path"] = webPath - - return paths -} - -// validateExecutionPlan 验证执行计划的完整性 -func (t *AutomationModuleAnalyzer) validateExecutionPlan(plan *ExecutionPlan) error { - // 验证基本字段 - if plan.PackageName == "" { - return errors.New("packageName 不能为空") - } - if plan.PackageType != "package" && plan.PackageType != "plugin" { - return errors.New("packageType 必须是 'package' 或 'plugin'") - } - - // 验证packageType和template字段的一致性 - if plan.NeedCreatedPackage && plan.PackageInfo != nil { - if plan.PackageType != plan.PackageInfo.Template { - return errors.New("packageType 和 packageInfo.template 必须保持一致") - } - } - - // 验证包信息 - if plan.NeedCreatedPackage { - if plan.PackageInfo == nil { - return errors.New("当 needCreatedPackage=true 时,packageInfo 不能为空") - } - if plan.PackageInfo.PackageName == "" { - return errors.New("packageInfo.packageName 不能为空") - } - if plan.PackageInfo.Template != "package" && plan.PackageInfo.Template != "plugin" { - return errors.New("packageInfo.template 必须是 'package' 或 'plugin'") - } - if plan.PackageInfo.Label == "" { - return errors.New("packageInfo.label 不能为空") - } - if plan.PackageInfo.Desc == "" { - return errors.New("packageInfo.desc 不能为空") - } - } - - // 验证模块信息(批量验证) - if plan.NeedCreatedModules { - if len(plan.ModulesInfo) == 0 { - return errors.New("当 needCreatedModules=true 时,modulesInfo 不能为空") - } - - // 遍历验证每个模块 - for moduleIndex, moduleInfo := range plan.ModulesInfo { - if moduleInfo.Package == "" { - return fmt.Errorf("模块 %d 的 package 不能为空", moduleIndex+1) - } - if moduleInfo.StructName == "" { - return fmt.Errorf("模块 %d 的 structName 不能为空", moduleIndex+1) - } - if moduleInfo.TableName == "" { - return fmt.Errorf("模块 %d 的 tableName 不能为空", moduleIndex+1) - } - if moduleInfo.Description == "" { - return fmt.Errorf("模块 %d 的 description 不能为空", moduleIndex+1) - } - if moduleInfo.Abbreviation == "" { - return fmt.Errorf("模块 %d 的 abbreviation 不能为空", moduleIndex+1) - } - if moduleInfo.PackageName == "" { - return fmt.Errorf("模块 %d 的 packageName 不能为空", moduleIndex+1) - } - if moduleInfo.HumpPackageName == "" { - return fmt.Errorf("模块 %d 的 humpPackageName 不能为空", moduleIndex+1) - } - - // 验证字段信息 - if len(moduleInfo.Fields) == 0 { - return fmt.Errorf("模块 %d 的 fields 不能为空,至少需要一个字段", moduleIndex+1) - } - - for i, field := range moduleInfo.Fields { - if field.FieldName == "" { - return fmt.Errorf("模块 %d 字段 %d 的 fieldName 不能为空", moduleIndex+1, i+1) - } - - // 确保字段名首字母大写 - if len(field.FieldName) > 0 { - firstChar := string(field.FieldName[0]) - if firstChar >= "a" && firstChar <= "z" { - moduleInfo.Fields[i].FieldName = strings.ToUpper(firstChar) + field.FieldName[1:] - } - } - if field.FieldDesc == "" { - return fmt.Errorf("模块 %d 字段 %d 的 fieldDesc 不能为空", moduleIndex+1, i+1) - } - if field.FieldType == "" { - return fmt.Errorf("模块 %d 字段 %d 的 fieldType 不能为空", moduleIndex+1, i+1) - } - if field.FieldJson == "" { - return fmt.Errorf("模块 %d 字段 %d 的 fieldJson 不能为空", moduleIndex+1, i+1) - } - if field.ColumnName == "" { - return fmt.Errorf("模块 %d 字段 %d 的 columnName 不能为空", moduleIndex+1, i+1) - } - - // 验证字段类型 - validFieldTypes := []string{"string", "int", "int64", "float64", "bool", "time.Time", "enum", "picture", "video", "file", "pictures", "array", "richtext", "json"} - validType := false - for _, validFieldType := range validFieldTypes { - if field.FieldType == validFieldType { - validType = true - break - } - } - if !validType { - return fmt.Errorf("模块 %d 字段 %d 的 fieldType '%s' 不支持,支持的类型:%v", moduleIndex+1, i+1, field.FieldType, validFieldTypes) - } - - // 验证搜索类型(如果设置了) - if field.FieldSearchType != "" { - validSearchTypes := []string{"=", "!=", ">", ">=", "<", "<=", "LIKE", "BETWEEN", "IN", "NOT IN"} - validSearchType := false - for _, validType := range validSearchTypes { - if field.FieldSearchType == validType { - validSearchType = true - break - } - } - if !validSearchType { - return fmt.Errorf("模块 %d 字段 %d 的 fieldSearchType '%s' 不支持,支持的类型:%v", moduleIndex+1, i+1, field.FieldSearchType, validSearchTypes) - } - } - } - - // 验证主键设置 - if !moduleInfo.GvaModel { - // 当不使用GVA模型时,必须有且仅有一个字段设置为主键 - primaryKeyCount := 0 - for _, field := range moduleInfo.Fields { - if field.PrimaryKey { - primaryKeyCount++ - } - } - if primaryKeyCount == 0 { - return fmt.Errorf("模块 %d:当 gvaModel=false 时,必须有一个字段的 primaryKey=true", moduleIndex+1) - } - if primaryKeyCount > 1 { - return fmt.Errorf("模块 %d:当 gvaModel=false 时,只能有一个字段的 primaryKey=true", moduleIndex+1) - } - } else { - // 当使用GVA模型时,所有字段的primaryKey都应该为false - for i, field := range moduleInfo.Fields { - if field.PrimaryKey { - return fmt.Errorf("模块 %d:当 gvaModel=true 时,字段 %d 的 primaryKey 应该为 false,系统会自动创建ID主键", moduleIndex+1, i+1) - } - } - } - } - } - - return nil -} - -// executeCreation 执行创建操作 -func (t *AutomationModuleAnalyzer) executeCreation(ctx context.Context, plan *ExecutionPlan) *ExecutionResult { - result := &ExecutionResult{ - Success: false, - Paths: make(map[string]string), - GeneratedPaths: []string{}, // 初始化生成文件路径列表 - } - - // 无论如何都先构建目录结构信息,确保paths始终返回 - result.Paths = t.buildDirectoryStructure(plan) - - // 记录预期生成的文件路径 - result.GeneratedPaths = t.collectExpectedFilePaths(plan) - - if !plan.NeedCreatedModules { - result.Success = true - result.Message += "已列出当前功能所涉及的目录结构信息; 请在paths中查看; 并且在对应指定文件中实现相关的业务逻辑; " - return result - } - - // 创建包(如果需要) - if plan.NeedCreatedPackage && plan.PackageInfo != nil { - packageService := service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage - err := packageService.Create(ctx, plan.PackageInfo) - if err != nil { - result.Message = fmt.Sprintf("创建包失败: %v", err) - // 即使创建包失败,也要返回paths信息 - return result - } - result.Message += "包创建成功; " - } - - // 批量创建字典和模块(如果需要) - if plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 { - templateService := service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate - - // 先批量创建所有模块需要的字典 - dictResult := t.createRequiredDictionaries(ctx, plan.ModulesInfo) - result.Message += dictResult - - // 遍历所有模块进行创建 - for _, moduleInfo := range plan.ModulesInfo { - - // 创建模块 - err := moduleInfo.Pretreatment() - if err != nil { - result.Message += fmt.Sprintf("模块 %s 信息预处理失败: %v; ", moduleInfo.StructName, err) - continue // 继续处理下一个模块 - } - - err = templateService.Create(ctx, *moduleInfo) - if err != nil { - result.Message += fmt.Sprintf("创建模块 %s 失败: %v; ", moduleInfo.StructName, err) - continue // 继续处理下一个模块 - } - result.Message += fmt.Sprintf("模块 %s 创建成功; ", moduleInfo.StructName) - } - - result.Message += fmt.Sprintf("批量创建完成,共处理 %d 个模块; ", len(plan.ModulesInfo)) - - // 添加重要提醒:不要使用其他MCP工具 - result.Message += "\n\n⚠️ 重要提醒:\n" - result.Message += "模块创建已完成,API和菜单已自动生成。请不要再调用以下MCP工具:\n" - result.Message += "- api_creator:API权限已在模块创建时自动生成\n" - result.Message += "- menu_creator:前端菜单已在模块创建时自动生成\n" - result.Message += "如需修改API或菜单,请直接在系统管理界面中进行配置。\n" - } - - result.Message += "已构建目录结构信息; " - result.Success = true - - if result.Message == "" { - result.Message = "执行计划完成" - } - - return result -} - -// createRequiredDictionaries 创建所需的字典(批量处理) -func (t *AutomationModuleAnalyzer) createRequiredDictionaries(ctx context.Context, modulesInfoList []*request.AutoCode) string { - var messages []string - dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService - createdDictTypes := make(map[string]bool) // 用于避免重复创建相同的字典 - - // 遍历所有模块 - for moduleIndex, modulesInfo := range modulesInfoList { - messages = append(messages, fmt.Sprintf("处理模块 %d (%s) 的字典: ", moduleIndex+1, modulesInfo.StructName)) - - // 遍历当前模块的所有字段,查找使用字典的字段 - moduleHasDictFields := false - for _, field := range modulesInfo.Fields { - if field.DictType != "" { - moduleHasDictFields = true - - // 如果这个字典类型已经在之前的模块中创建过,跳过 - if createdDictTypes[field.DictType] { - messages = append(messages, fmt.Sprintf("字典 %s 已在前面的模块中创建,跳过; ", field.DictType)) - continue - } - - // 检查字典是否存在 - exists, err := t.checkDictionaryExists(field.DictType) - if err != nil { - messages = append(messages, fmt.Sprintf("检查字典 %s 时出错: %v; ", field.DictType, err)) - continue - } - - if !exists { - // 字典不存在,创建字典 - dictionary := model.SysDictionary{ - Name: t.generateDictionaryName(field.DictType, field.FieldDesc), - Type: field.DictType, - Status: &[]bool{true}[0], // 默认启用 - Desc: fmt.Sprintf("自动生成的字典,用于模块 %s 字段: %s (%s)", modulesInfo.StructName, field.FieldName, field.FieldDesc), - } - - err = dictionaryService.CreateSysDictionary(dictionary) - if err != nil { - messages = append(messages, fmt.Sprintf("创建字典 %s 失败: %v; ", field.DictType, err)) - } else { - messages = append(messages, fmt.Sprintf("成功创建字典 %s (%s); ", field.DictType, dictionary.Name)) - createdDictTypes[field.DictType] = true // 标记为已创建 - - // 创建默认的字典详情项 - t.createDefaultDictionaryDetails(ctx, field.DictType, field.FieldDesc) - } - } else { - messages = append(messages, fmt.Sprintf("字典 %s 已存在,跳过创建; ", field.DictType)) - createdDictTypes[field.DictType] = true // 标记为已存在 - } - } - } - - if !moduleHasDictFields { - messages = append(messages, "无需创建字典; ") - } - } - - if len(messages) == 0 { - return "未发现需要创建的字典; " - } - - return strings.Join(messages, "") -} - -// checkDictionaryExists 检查字典是否存在 -func (t *AutomationModuleAnalyzer) checkDictionaryExists(dictType string) (bool, error) { - var dictionary model.SysDictionary - err := global.GVA_DB.Where("type = ?", dictType).First(&dictionary).Error - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return false, nil // 字典不存在 - } - return false, err // 其他错误 - } - return true, nil // 字典存在 -} - -// generateDictionaryName 生成字典名称 -func (t *AutomationModuleAnalyzer) generateDictionaryName(dictType, fieldDesc string) string { - if fieldDesc != "" { - return fmt.Sprintf("%s字典", fieldDesc) - } - return fmt.Sprintf("%s字典", dictType) -} - -// createDefaultDictionaryDetails 创建默认的字典详情项 -func (t *AutomationModuleAnalyzer) createDefaultDictionaryDetails(ctx context.Context, dictType, fieldDesc string) { - // 字典选项现在通过 generate_dictionary_options MCP工具由AI client传入 - // 这里不再创建默认选项,只是保留方法以保持兼容性 - global.GVA_LOG.Info(fmt.Sprintf("字典 %s 已创建,请使用 generate_dictionary_options 工具添加字典选项", dictType)) -} - -// DictionaryOption 字典选项结构 -type DictionaryOption struct { - Label string `json:"label"` - Value string `json:"value"` - Sort int `json:"sort"` -} - -// generateSmartDictionaryOptions 通过MCP调用让AI生成字典选项 -func (t *AutomationModuleAnalyzer) generateSmartDictionaryOptions(dictType, fieldDesc string) []struct { - label string - value string - sort int -} { - // 返回空切片,不再使用预制选项 - // 字典选项将通过新的MCP工具由AI client传入 - return []struct { - label string - value string - sort int - }{} -} - -// detectPluginIntent 检测用户需求中是否包含插件相关的关键词 -func (t *AutomationModuleAnalyzer) detectPluginIntent(requirement string) (suggestedType string, isPlugin bool, confidence string) { - // 转换为小写进行匹配 - requirementLower := strings.ToLower(requirement) - - // 插件相关关键词 - pluginKeywords := []string{ - "插件", "plugin", "扩展", "extension", "addon", "模块插件", - "功能插件", "业务插件", "第三方插件", "自定义插件", - } - - // 包相关关键词(用于排除误判) - packageKeywords := []string{ - "包", "package", "模块包", "业务包", "功能包", - } - - // 检测插件关键词 - pluginMatches := 0 - for _, keyword := range pluginKeywords { - if strings.Contains(requirementLower, keyword) { - pluginMatches++ - } - } - - // 检测包关键词 - packageMatches := 0 - for _, keyword := range packageKeywords { - if strings.Contains(requirementLower, keyword) { - packageMatches++ - } - } - - // 决策逻辑 - if pluginMatches > 0 { - if packageMatches == 0 || pluginMatches > packageMatches { - return "plugin", true, "高" - } else { - return "plugin", true, "中" - } - } - - // 默认返回package - return "package", false, "低" -} - -// isPackageFolderEmpty 检查包对应的文件夹是否为空 -func (t *AutomationModuleAnalyzer) isPackageFolderEmpty(packageName, template string) (bool, error) { - // 根据模板类型确定基础路径 - var basePath string - if template == "plugin" { - basePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", packageName) - } else { - // package 类型 - basePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "model", packageName) - } - - // 检查文件夹是否存在 - if _, err := os.Stat(basePath); os.IsNotExist(err) { - // 文件夹不存在,认为是空的 - return true, nil - } else if err != nil { - return false, fmt.Errorf("检查文件夹状态失败: %v", err) - } - - // 读取文件夹内容 - entries, err := os.ReadDir(basePath) - if err != nil { - return false, fmt.Errorf("读取文件夹内容失败: %v", err) - } - - // 检查目录下是否有 .go 文件 - hasGoFiles := false - for _, entry := range entries { - name := entry.Name() - // 跳过隐藏文件、.DS_Store 等系统文件 - if strings.HasPrefix(name, ".") { - continue - } - // 如果是目录,递归检查子目录中的 .go 文件 - if entry.IsDir() { - subPath := filepath.Join(basePath, name) - subEntries, err := os.ReadDir(subPath) - if err != nil { - continue - } - for _, subEntry := range subEntries { - if !subEntry.IsDir() && strings.HasSuffix(subEntry.Name(), ".go") { - hasGoFiles = true - break - } - } - if hasGoFiles { - break - } - } else if strings.HasSuffix(name, ".go") { - // 如果是 .go 文件 - hasGoFiles = true - break - } - } - - // 如果没有 .go 文件,认为是空包 - return !hasGoFiles, nil -} - -// removeEmptyPackageFolder 删除空的包文件夹 -func (t *AutomationModuleAnalyzer) removeEmptyPackageFolder(packageName, template string) error { - var errorMessages []string - - if template == "plugin" { - // plugin 类型只删除 plugin 目录下的文件夹 - basePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", packageName) - if err := t.removeDirectoryIfExists(basePath); err != nil { - errorMessages = append(errorMessages, fmt.Sprintf("删除plugin文件夹失败: %v", err)) - } - } else { - // package 类型需要删除多个目录下的相关文件 - paths := []string{ - filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "model", packageName), - filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", packageName), - filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", packageName), - filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", packageName), - } - - for _, path := range paths { - if err := t.removeDirectoryIfExists(path); err != nil { - errorMessages = append(errorMessages, fmt.Sprintf("删除%s失败: %v", path, err)) - } - } - } - - if len(errorMessages) > 0 { - return fmt.Errorf("删除过程中出现错误: %s", strings.Join(errorMessages, "; ")) - } - - return nil -} - -// removeDirectoryIfExists 删除目录(如果存在) -func (t *AutomationModuleAnalyzer) removeDirectoryIfExists(dirPath string) error { - // 检查文件夹是否存在 - if _, err := os.Stat(dirPath); os.IsNotExist(err) { - // 文件夹不存在,无需删除 - return nil - } else if err != nil { - return fmt.Errorf("检查文件夹状态失败: %v", err) - } - - // 删除文件夹及其所有内容 - if err := os.RemoveAll(dirPath); err != nil { - return fmt.Errorf("删除文件夹失败: %v", err) - } - - global.GVA_LOG.Info(fmt.Sprintf("成功删除目录: %s", dirPath)) - return nil -} - -// cleanupRelatedApiAndMenus 清理与删除的模块相关的API和菜单记录 -func (t *AutomationModuleAnalyzer) cleanupRelatedApiAndMenus(historyIDs []uint) error { - if len(historyIDs) == 0 { - return nil - } - - // 获取要删除的历史记录信息 - var histories []model.SysAutoCodeHistory - if err := global.GVA_DB.Where("id IN ?", historyIDs).Find(&histories).Error; err != nil { - return fmt.Errorf("获取历史记录失败: %v", err) - } - - var deletedApiCount, deletedMenuCount int - - for _, history := range histories { - // 删除相关的API记录(使用存储的API IDs) - if len(history.ApiIDs) > 0 { - ids := make([]int, 0, len(history.ApiIDs)) - for _, id := range history.ApiIDs { - ids = append(ids, int(id)) - } - idsReq := common.IdsReq{Ids: ids} - if err := systemService.ApiServiceApp.DeleteApisByIds(idsReq); err != nil { - global.GVA_LOG.Warn(fmt.Sprintf("删除API记录失败 (模块: %s): %v", history.StructName, err)) - } else { - deletedApiCount += len(ids) - global.GVA_LOG.Info(fmt.Sprintf("成功删除API记录 (模块: %s, 数量: %d)", history.StructName, len(ids))) - } - } - - // 删除相关的菜单记录(使用存储的菜单ID) - if history.MenuID != 0 { - if err := systemService.BaseMenuServiceApp.DeleteBaseMenu(int(history.MenuID)); err != nil { - global.GVA_LOG.Warn(fmt.Sprintf("删除菜单记录失败 (模块: %s, 菜单ID: %d): %v", history.StructName, history.MenuID, err)) - } else { - deletedMenuCount++ - global.GVA_LOG.Info(fmt.Sprintf("成功删除菜单记录 (模块: %s, 菜单ID: %d)", history.StructName, history.MenuID)) - } - } - } - - if deletedApiCount > 0 || deletedMenuCount > 0 { - global.GVA_LOG.Info(fmt.Sprintf("清理完成:删除了 %d 个API记录和 %d 个菜单记录", deletedApiCount, deletedMenuCount)) - } - - return nil -} - -// handleReview 处理代码复检请求 -func (t *AutomationModuleAnalyzer) handleReview(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { - // 解析复检请求参数 - reviewRequestData, ok := request.GetArguments()["reviewRequest"].(map[string]interface{}) - if !ok { - return nil, errors.New("参数错误:reviewRequest 必须是有效的对象") - } - - // 转换为ReviewRequest结构体 - reviewRequestJSON, err := json.Marshal(reviewRequestData) - if err != nil { - return nil, fmt.Errorf("序列化reviewRequest失败: %v", err) - } - - var reviewReq ReviewRequest - if err := json.Unmarshal(reviewRequestJSON, &reviewReq); err != nil { - return nil, fmt.Errorf("解析reviewRequest失败: %v", err) - } - - // 验证必需参数 - if reviewReq.OriginalRequirement == "" { - return nil, errors.New("参数错误:originalRequirement 不能为空") - } - if reviewReq.ExecutionPlan == nil { - return nil, errors.New("参数错误:executionPlan 不能为空") - } - - // 设置默认最大重试次数 - if reviewReq.MaxRetries <= 0 { - reviewReq.MaxRetries = 3 - } - - // 执行代码复检 - reviewResult, err := t.performCodeReview(&reviewReq) - if err != nil { - return nil, fmt.Errorf("代码复检失败: %v", err) - } - - // 返回复检结果 - resultJSON, err := json.Marshal(reviewResult) - if err != nil { - return nil, fmt.Errorf("序列化复检结果失败: %v", err) - } - - return &mcp.CallToolResult{ - Content: []mcp.Content{ - mcp.TextContent{ - Type: "text", - Text: string(resultJSON), - }, - }, - }, nil -} - -// handleEnhance 处理代码增强请求 -func (t *AutomationModuleAnalyzer) handleEnhance(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { - // 解析增强请求参数 - enhanceRequestData, ok := request.GetArguments()["enhanceRequest"].(map[string]interface{}) - if !ok { - return nil, errors.New("参数错误:enhanceRequest 必须是有效的对象") - } - - // 转换为EnhanceRequest结构体 - enhanceRequestJSON, err := json.Marshal(enhanceRequestData) - if err != nil { - return nil, fmt.Errorf("序列化enhanceRequest失败: %v", err) - } - - var enhanceReq EnhanceRequest - if err := json.Unmarshal(enhanceRequestJSON, &enhanceReq); err != nil { - return nil, fmt.Errorf("解析enhanceRequest失败: %v", err) - } - - // 验证必需参数 - if enhanceReq.OriginalRequirement == "" { - return nil, errors.New("参数错误:originalRequirement 不能为空") - } - if len(enhanceReq.MissingRequirements) == 0 { - return nil, errors.New("参数错误:missingRequirements 不能为空") - } - - // 执行代码增强 - enhanceResult, err := t.performCodeEnhancement(&enhanceReq) - if err != nil { - return nil, fmt.Errorf("代码增强失败: %v", err) - } - - // 返回增强结果 - resultJSON, err := json.Marshal(enhanceResult) - if err != nil { - return nil, fmt.Errorf("序列化增强结果失败: %v", err) - } - - return &mcp.CallToolResult{ - Content: []mcp.Content{ - mcp.TextContent{ - Type: "text", - Text: string(resultJSON), - }, - }, - }, nil -} diff --git a/server/mcp/gva_execute.go b/server/mcp/gva_execute.go new file mode 100644 index 0000000000..9143a40246 --- /dev/null +++ b/server/mcp/gva_execute.go @@ -0,0 +1,806 @@ +package mcpTool + +import ( + "context" + "encoding/json" + "errors" + "fmt" + model "github.com/flipped-aurora/gin-vue-admin/server/model/system" + "strings" + + "github.com/flipped-aurora/gin-vue-admin/server/global" + "github.com/flipped-aurora/gin-vue-admin/server/model/system/request" + + "github.com/flipped-aurora/gin-vue-admin/server/service" + "github.com/mark3labs/mcp-go/mcp" +) + +// 注册工具 +func init() { + RegisterTool(&GVAExecutor{}) +} + +// GVAExecutor GVA代码生成器 +type GVAExecutor struct{} + +// ExecuteRequest 执行请求结构 +type ExecuteRequest struct { + ExecutionPlan ExecutionPlan `json:"executionPlan"` // 执行计划 + Requirement string `json:"requirement"` // 原始需求(可选,用于日志记录) +} + +// ExecuteResponse 执行响应结构 +type ExecuteResponse struct { + Success bool `json:"success"` + Message string `json:"message"` + PackageID uint `json:"packageId,omitempty"` + HistoryID uint `json:"historyId,omitempty"` + Paths map[string]string `json:"paths,omitempty"` + GeneratedPaths []string `json:"generatedPaths,omitempty"` + NextActions []string `json:"nextActions,omitempty"` +} + +// ExecutionPlan 执行计划结构 +type ExecutionPlan struct { + PackageName string `json:"packageName"` + PackageType string `json:"packageType"` // "plugin" 或 "package" + NeedCreatedPackage bool `json:"needCreatedPackage"` + NeedCreatedModules bool `json:"needCreatedModules"` + PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"` + ModulesInfo []*request.AutoCode `json:"modulesInfo,omitempty"` + Paths map[string]string `json:"paths,omitempty"` +} + +// New 创建GVA代码生成执行器工具 +func (g *GVAExecutor) New() mcp.Tool { + return mcp.NewTool("gva_execute", + mcp.WithDescription(`**🚀 GVA代码生成执行器:直接执行代码生成,无需确认步骤** + +**核心功能:** +- 接收ExecutionPlan执行计划,直接生成代码 +- 支持批量创建多个模块 +- 自动创建包、模块、字典等 +- 移除了确认步骤,提高执行效率 + +**使用场景:** +- 在gva_analyze分析完成后调用 +- 根据分析结果直接生成代码 +- 适用于自动化代码生成流程 + +**批量创建功能:** +- 支持在单个ExecutionPlan中创建多个模块 +- modulesInfo字段为数组,可包含多个模块配置 +- 一次性处理多个模块的创建和字典生成 + +**新功能:自动字典创建** +- 当结构体字段使用了字典类型(dictType不为空)时,系统会自动检查字典是否存在 +- 如果字典不存在,会自动创建对应的字典及默认的字典详情项 +- 字典创建包括:字典主表记录和默认的选项值(选项1、选项2等) + +**重要限制:** +- 当needCreatedModules=true时,模块创建会自动生成API和菜单,因此不应再调用api_creator和menu_creator工具 +- 只有在单独创建API或菜单(不涉及模块创建)时才使用api_creator和menu_creator工具 + +重要:ExecutionPlan结构体格式要求(支持批量创建): +{ + "packageName": "包名(string)", + "packageType": "package或plugin(string)", + "needCreatedPackage": "是否需要创建包(bool)", + "needCreatedModules": "是否需要创建模块(bool)", + "packageInfo": { + "desc": "描述(string)", + "label": "展示名(string)", + "template": "package或plugin(string)", + "packageName": "包名(string)" + }, + "modulesInfo": [{ + "package": "包名(string)", + "tableName": "数据库表名(string)", + "businessDB": "业务数据库(string)", + "structName": "结构体名(string)", + "packageName": "文件名称(string)", + "description": "中文描述(string)", + "abbreviation": "简称(string)", + "humpPackageName": "文件名称 一般是结构体名的小驼峰(string)", + "gvaModel": "是否使用GVA模型(bool) 固定为true 后续不需要创建ID created_at deleted_at updated_at", + "autoMigrate": "是否自动迁移(bool)", + "autoCreateResource": "是否创建资源(bool)", + "autoCreateApiToSql": "是否创建API(bool)", + "autoCreateMenuToSql": "是否创建菜单(bool)", + "autoCreateBtnAuth": "是否创建按钮权限(bool)", + "onlyTemplate": "是否仅模板(bool)", + "isTree": "是否树形结构(bool)", + "treeJson": "树形JSON字段(string)", + "isAdd": "是否新增(bool) 固定为false", + "generateWeb": "是否生成前端(bool)", + "generateServer": "是否生成后端(bool)", + "fields": [{ + "fieldName": "字段名(string)必须大写开头", + "fieldDesc": "字段描述(string)", + "fieldType": "字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组)", + "fieldJson": "JSON标签(string)", + "dataTypeLong": "数据长度(string)", + "comment": "注释(string)", + "columnName": "数据库列名(string)", + "fieldSearchType": "搜索类型:=/>/=/<=/NOT BETWEEN/LIKE/BETWEEN/IN/NOT IN等(string)", + "fieldSearchHide": "是否隐藏搜索(bool)", + "dictType": "字典类型(string)", + "form": "表单显示(bool)", + "table": "表格显示(bool)", + "desc": "详情显示(bool)", + "excel": "导入导出(bool)", + "require": "是否必填(bool)", + "defaultValue": "默认值(string)", + "errorText": "错误提示(string)", + "clearable": "是否可清空(bool)", + "sort": "是否排序(bool)", + "primaryKey": "是否主键(bool)", + "dataSource": "数据源配置(object) - 用于配置字段的关联表信息,结构:{\"dbName\":\"数据库名\",\"table\":\"关联表名\",\"label\":\"显示字段\",\"value\":\"值字段\",\"association\":1或2(1=一对一,2=一对多),\"hasDeletedAt\":true/false}。\n\n**获取表名提示:**\n- 可在 server/model 和 plugin/xxx/model 目录下查看对应模块的 TableName() 接口实现获取实际表名\n- 例如:SysUser 的表名为 \"sys_users\",ExaFileUploadAndDownload 的表名为 \"exa_file_upload_and_downloads\"\n- 插件模块示例:Info 的表名为 \"gva_announcements_info\"\n\n**获取数据库名提示:**\n- 主数据库:通常使用 \"gva\"(默认数据库标识)\n- 多数据库:可在 config.yaml 的 db-list 配置中查看可用数据库的 alias-name 字段\n- 如果用户未提及关联多数据库信息 则使用默认数据库 默认数据库的情况下 dbName此处填写为空", + "checkDataSource": "是否检查数据源(bool) - 启用后会验证关联表的存在性", + "fieldIndexType": "索引类型(string)" + }] + }, { + "package": "包名(string)", + "tableName": "第二个模块的表名(string)", + "structName": "第二个模块的结构体名(string)", + "description": "第二个模块的描述(string)", + "...": "更多模块配置..." + }] +} + +注意: +1. needCreatedPackage=true时packageInfo必需 +2. needCreatedModules=true时modulesInfo必需 +3. packageType只能是"package"或"plugin" +4. 字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组) +5. 搜索类型支持:=,!=,>,>=,<,<=,NOT BETWEEN/LIKE/BETWEEN/IN/NOT IN +6. gvaModel=true时自动包含ID,CreatedAt,UpdatedAt,DeletedAt字段 +7. **重要**:当gvaModel=false时,必须有一个字段的primaryKey=true,否则会导致PrimaryField为nil错误 +8. **重要**:当gvaModel=true时,系统会自动设置ID字段为主键,无需手动设置primaryKey=true +9. 智能字典创建功能:当字段使用字典类型(DictType)时,系统会: + - 自动检查字典是否存在,如果不存在则创建字典 + - 根据字典类型和字段描述智能生成默认选项,支持状态、性别、类型、等级、优先级、审批、角色、布尔值、订单、颜色、尺寸等常见场景 + - 为无法识别的字典类型提供通用默认选项 +10. **模块关联配置**:当需要配置模块间的关联关系时,使用dataSource字段: + - **dbName**: 关联的数据库名称 + - **table**: 关联的表名 + - **label**: 用于显示的字段名(如name、title等) + - **value**: 用于存储的值字段名(通常是id) + - **association**: 关联关系类型(1=一对一关联,2=一对多关联) + - **hasDeletedAt**: 关联表是否有软删除字段 + - **checkDataSource**: 设为true时会验证关联表的存在性 + - 示例:{"dbName":"gva","table":"sys_users","label":"username","value":"id","association":2,"hasDeletedAt":true}`), + mcp.WithObject("executionPlan", + mcp.Description("执行计划,包含包信息和模块信息"), + mcp.Required(), + ), + mcp.WithString("requirement", + mcp.Description("原始需求描述(可选,用于日志记录)"), + ), + ) +} + +// Handle 处理执行请求(移除确认步骤) +func (g *GVAExecutor) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + executionPlanData, ok := request.GetArguments()["executionPlan"] + if !ok { + return nil, errors.New("参数错误:executionPlan 必须提供") + } + + // 解析执行计划 + planJSON, err := json.Marshal(executionPlanData) + if err != nil { + return nil, fmt.Errorf("解析执行计划失败: %v", err) + } + + var plan ExecutionPlan + err = json.Unmarshal(planJSON, &plan) + if err != nil { + return nil, fmt.Errorf("解析执行计划失败: %v\n\n请确保ExecutionPlan格式正确,参考工具描述中的结构体格式要求", err) + } + + // 验证执行计划的完整性 + if err := g.validateExecutionPlan(&plan); err != nil { + return nil, fmt.Errorf("执行计划验证失败: %v", err) + } + + // 获取原始需求(可选) + var originalRequirement string + if reqData, ok := request.GetArguments()["requirement"]; ok { + if reqStr, ok := reqData.(string); ok { + originalRequirement = reqStr + } + } + + // 直接执行创建操作(无确认步骤) + result := g.executeCreation(ctx, &plan) + + // 如果执行成功且有原始需求,提供代码复检建议 + var reviewMessage string + if result.Success && originalRequirement != "" { + global.GVA_LOG.Info("执行完成,返回生成的文件路径供AI进行代码复检...") + + // 构建文件路径信息供AI使用 + var pathsInfo []string + for _, path := range result.GeneratedPaths { + pathsInfo = append(pathsInfo, fmt.Sprintf("- %s", path)) + } + + reviewMessage = fmt.Sprintf("\n\n📁 已生成以下文件:\n%s\n\n💡 提示:可以检查生成的代码是否满足原始需求。", strings.Join(pathsInfo, "\n")) + } else if originalRequirement == "" { + reviewMessage = "\n\n💡 提示:如需代码复检,请提供原始需求描述。" + } + + // 序列化响应 + response := ExecuteResponse{ + Success: result.Success, + Message: result.Message, + PackageID: result.PackageID, + HistoryID: result.HistoryID, + Paths: result.Paths, + GeneratedPaths: result.GeneratedPaths, + NextActions: result.NextActions, + } + + responseJSON, err := json.MarshalIndent(response, "", " ") + if err != nil { + return nil, fmt.Errorf("序列化结果失败: %v", err) + } + + // 添加权限分配提醒 + permissionReminder := "\n\n⚠️ 重要提醒:\n" + + "模块创建完成后,请前往【系统管理】->【角色管理】中为相关角色分配新创建的API和菜单权限," + + "以确保用户能够正常访问新功能。\n" + + "具体步骤:\n" + + "1. 进入角色管理页面\n" + + "2. 选择需要授权的角色\n" + + "3. 在API权限中勾选新创建的API接口\n" + + "4. 在菜单权限中勾选新创建的菜单项\n" + + "5. 保存权限配置" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(fmt.Sprintf("执行结果:\n\n%s%s%s", string(responseJSON), reviewMessage, permissionReminder)), + }, + }, nil +} + +// validateExecutionPlan 验证执行计划的完整性 +func (g *GVAExecutor) validateExecutionPlan(plan *ExecutionPlan) error { + // 验证基本字段 + if plan.PackageName == "" { + return errors.New("packageName 不能为空") + } + if plan.PackageType != "package" && plan.PackageType != "plugin" { + return errors.New("packageType 必须是 'package' 或 'plugin'") + } + + // 验证packageType和template字段的一致性 + if plan.NeedCreatedPackage && plan.PackageInfo != nil { + if plan.PackageType != plan.PackageInfo.Template { + return errors.New("packageType 和 packageInfo.template 必须保持一致") + } + } + + // 验证包信息 + if plan.NeedCreatedPackage { + if plan.PackageInfo == nil { + return errors.New("当 needCreatedPackage=true 时,packageInfo 不能为空") + } + if plan.PackageInfo.PackageName == "" { + return errors.New("packageInfo.packageName 不能为空") + } + if plan.PackageInfo.Template != "package" && plan.PackageInfo.Template != "plugin" { + return errors.New("packageInfo.template 必须是 'package' 或 'plugin'") + } + if plan.PackageInfo.Label == "" { + return errors.New("packageInfo.label 不能为空") + } + if plan.PackageInfo.Desc == "" { + return errors.New("packageInfo.desc 不能为空") + } + } + + // 验证模块信息(批量验证) + if plan.NeedCreatedModules { + if len(plan.ModulesInfo) == 0 { + return errors.New("当 needCreatedModules=true 时,modulesInfo 不能为空") + } + + // 遍历验证每个模块 + for moduleIndex, moduleInfo := range plan.ModulesInfo { + if moduleInfo.Package == "" { + return fmt.Errorf("模块 %d 的 package 不能为空", moduleIndex+1) + } + if moduleInfo.StructName == "" { + return fmt.Errorf("模块 %d 的 structName 不能为空", moduleIndex+1) + } + if moduleInfo.TableName == "" { + return fmt.Errorf("模块 %d 的 tableName 不能为空", moduleIndex+1) + } + if moduleInfo.Description == "" { + return fmt.Errorf("模块 %d 的 description 不能为空", moduleIndex+1) + } + if moduleInfo.Abbreviation == "" { + return fmt.Errorf("模块 %d 的 abbreviation 不能为空", moduleIndex+1) + } + if moduleInfo.PackageName == "" { + return fmt.Errorf("模块 %d 的 packageName 不能为空", moduleIndex+1) + } + if moduleInfo.HumpPackageName == "" { + return fmt.Errorf("模块 %d 的 humpPackageName 不能为空", moduleIndex+1) + } + + // 验证字段信息 + if len(moduleInfo.Fields) == 0 { + return fmt.Errorf("模块 %d 的 fields 不能为空,至少需要一个字段", moduleIndex+1) + } + + for i, field := range moduleInfo.Fields { + if field.FieldName == "" { + return fmt.Errorf("模块 %d 字段 %d 的 fieldName 不能为空", moduleIndex+1, i+1) + } + + // 确保字段名首字母大写 + if len(field.FieldName) > 0 { + firstChar := string(field.FieldName[0]) + if firstChar >= "a" && firstChar <= "z" { + moduleInfo.Fields[i].FieldName = strings.ToUpper(firstChar) + field.FieldName[1:] + } + } + if field.FieldDesc == "" { + return fmt.Errorf("模块 %d 字段 %d 的 fieldDesc 不能为空", moduleIndex+1, i+1) + } + if field.FieldType == "" { + return fmt.Errorf("模块 %d 字段 %d 的 fieldType 不能为空", moduleIndex+1, i+1) + } + if field.FieldJson == "" { + return fmt.Errorf("模块 %d 字段 %d 的 fieldJson 不能为空", moduleIndex+1, i+1) + } + if field.ColumnName == "" { + return fmt.Errorf("模块 %d 字段 %d 的 columnName 不能为空", moduleIndex+1, i+1) + } + + // 验证字段类型 + validFieldTypes := []string{"string", "int", "int64", "float64", "bool", "time.Time", "enum", "picture", "video", "file", "pictures", "array", "richtext", "json"} + validType := false + for _, validFieldType := range validFieldTypes { + if field.FieldType == validFieldType { + validType = true + break + } + } + if !validType { + return fmt.Errorf("模块 %d 字段 %d 的 fieldType '%s' 不支持,支持的类型:%v", moduleIndex+1, i+1, field.FieldType, validFieldTypes) + } + + // 验证搜索类型(如果设置了) + if field.FieldSearchType != "" { + validSearchTypes := []string{"=", "!=", ">", ">=", "<", "<=", "LIKE", "BETWEEN", "IN", "NOT IN"} + validSearchType := false + for _, validType := range validSearchTypes { + if field.FieldSearchType == validType { + validSearchType = true + break + } + } + if !validSearchType { + return fmt.Errorf("模块 %d 字段 %d 的 fieldSearchType '%s' 不支持,支持的类型:%v", moduleIndex+1, i+1, field.FieldSearchType, validSearchTypes) + } + } + } + + // 验证主键设置 + if !moduleInfo.GvaModel { + // 当不使用GVA模型时,必须有且仅有一个字段设置为主键 + primaryKeyCount := 0 + for _, field := range moduleInfo.Fields { + if field.PrimaryKey { + primaryKeyCount++ + } + } + if primaryKeyCount == 0 { + return fmt.Errorf("模块 %d:当 gvaModel=false 时,必须有一个字段的 primaryKey=true", moduleIndex+1) + } + if primaryKeyCount > 1 { + return fmt.Errorf("模块 %d:当 gvaModel=false 时,只能有一个字段的 primaryKey=true", moduleIndex+1) + } + } else { + // 当使用GVA模型时,所有字段的primaryKey都应该为false + for i, field := range moduleInfo.Fields { + if field.PrimaryKey { + return fmt.Errorf("模块 %d:当 gvaModel=true 时,字段 %d 的 primaryKey 应该为 false,系统会自动创建ID主键", moduleIndex+1, i+1) + } + } + } + } + } + + return nil +} + +// executeCreation 执行创建操作 +func (g *GVAExecutor) executeCreation(ctx context.Context, plan *ExecutionPlan) *ExecuteResponse { + result := &ExecuteResponse{ + Success: false, + Paths: make(map[string]string), + GeneratedPaths: []string{}, // 初始化生成文件路径列表 + } + + // 无论如何都先构建目录结构信息,确保paths始终返回 + result.Paths = g.buildDirectoryStructure(plan) + + // 记录预期生成的文件路径 + result.GeneratedPaths = g.collectExpectedFilePaths(plan) + + if !plan.NeedCreatedModules { + result.Success = true + result.Message += "已列出当前功能所涉及的目录结构信息; 请在paths中查看; 并且在对应指定文件中实现相关的业务逻辑; " + return result + } + + // 创建包(如果需要) + if plan.NeedCreatedPackage && plan.PackageInfo != nil { + packageService := service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage + err := packageService.Create(ctx, plan.PackageInfo) + if err != nil { + result.Message = fmt.Sprintf("创建包失败: %v", err) + // 即使创建包失败,也要返回paths信息 + return result + } + result.Message += "包创建成功; " + } + + // 批量创建字典和模块(如果需要) + if plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 { + templateService := service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate + + // 先批量创建所有模块需要的字典 + dictResult := g.createRequiredDictionaries(ctx, plan.ModulesInfo) + result.Message += dictResult + + // 遍历所有模块进行创建 + for _, moduleInfo := range plan.ModulesInfo { + + // 创建模块 + err := moduleInfo.Pretreatment() + if err != nil { + result.Message += fmt.Sprintf("模块 %s 信息预处理失败: %v; ", moduleInfo.StructName, err) + continue // 继续处理下一个模块 + } + + err = templateService.Create(ctx, *moduleInfo) + if err != nil { + result.Message += fmt.Sprintf("创建模块 %s 失败: %v; ", moduleInfo.StructName, err) + continue // 继续处理下一个模块 + } + result.Message += fmt.Sprintf("模块 %s 创建成功; ", moduleInfo.StructName) + } + + result.Message += fmt.Sprintf("批量创建完成,共处理 %d 个模块; ", len(plan.ModulesInfo)) + + // 添加重要提醒:不要使用其他MCP工具 + result.Message += "\n\n⚠️ 重要提醒:\n" + result.Message += "模块创建已完成,API和菜单已自动生成。请不要再调用以下MCP工具:\n" + result.Message += "- api_creator:API权限已在模块创建时自动生成\n" + result.Message += "- menu_creator:前端菜单已在模块创建时自动生成\n" + result.Message += "如需修改API或菜单,请直接在系统管理界面中进行配置。\n" + } + + result.Message += "已构建目录结构信息; " + result.Success = true + + if result.Message == "" { + result.Message = "执行计划完成" + } + + return result +} + +// buildDirectoryStructure 构建目录结构信息 +func (g *GVAExecutor) buildDirectoryStructure(plan *ExecutionPlan) map[string]string { + paths := make(map[string]string) + + // 获取配置信息 + autoCodeConfig := global.GVA_CONFIG.AutoCode + + // 构建基础路径 + rootPath := autoCodeConfig.Root + serverPath := autoCodeConfig.Server + webPath := autoCodeConfig.Web + moduleName := autoCodeConfig.Module + + // 如果计划中有包名,使用计划中的包名,否则使用默认 + packageName := "example" + if plan.PackageName != "" { + packageName = plan.PackageName + } + + // 如果计划中有模块信息,获取第一个模块的结构名作为默认值 + structName := "ExampleStruct" + if len(plan.ModulesInfo) > 0 && plan.ModulesInfo[0].StructName != "" { + structName = plan.ModulesInfo[0].StructName + } + + // 根据包类型构建不同的路径结构 + packageType := plan.PackageType + if packageType == "" { + packageType = "package" // 默认为package模式 + } + + // 构建服务端路径 + if serverPath != "" { + serverBasePath := fmt.Sprintf("%s/%s", rootPath, serverPath) + + if packageType == "plugin" { + // Plugin 模式:所有文件都在 /plugin/packageName/ 目录下 + plugingBasePath := fmt.Sprintf("%s/plugin/%s", serverBasePath, packageName) + + // API 路径 + paths["api"] = fmt.Sprintf("%s/api", plugingBasePath) + + // Service 路径 + paths["service"] = fmt.Sprintf("%s/service", plugingBasePath) + + // Model 路径 + paths["model"] = fmt.Sprintf("%s/model", plugingBasePath) + + // Router 路径 + paths["router"] = fmt.Sprintf("%s/router", plugingBasePath) + + // Request 路径 + paths["request"] = fmt.Sprintf("%s/model/request", plugingBasePath) + + // Response 路径 + paths["response"] = fmt.Sprintf("%s/model/response", plugingBasePath) + + // Plugin 特有文件 + paths["plugin_main"] = fmt.Sprintf("%s/main.go", plugingBasePath) + paths["plugin_config"] = fmt.Sprintf("%s/plugin.go", plugingBasePath) + paths["plugin_initialize"] = fmt.Sprintf("%s/initialize", plugingBasePath) + } else { + // Package 模式:传统的目录结构 + // API 路径 + paths["api"] = fmt.Sprintf("%s/api/v1/%s", serverBasePath, packageName) + + // Service 路径 + paths["service"] = fmt.Sprintf("%s/service/%s", serverBasePath, packageName) + + // Model 路径 + paths["model"] = fmt.Sprintf("%s/model/%s", serverBasePath, packageName) + + // Router 路径 + paths["router"] = fmt.Sprintf("%s/router/%s", serverBasePath, packageName) + + // Request 路径 + paths["request"] = fmt.Sprintf("%s/model/%s/request", serverBasePath, packageName) + + // Response 路径 + paths["response"] = fmt.Sprintf("%s/model/%s/response", serverBasePath, packageName) + } + } + + // 构建前端路径(两种模式相同) + if webPath != "" { + webBasePath := fmt.Sprintf("%s/%s", rootPath, webPath) + + if packageType == "plugin" { + // Plugin 模式:前端文件也在 /plugin/packageName/ 目录下 + pluginWebBasePath := fmt.Sprintf("%s/plugin/%s", webBasePath, packageName) + + // Vue 页面路径 + paths["vue_page"] = fmt.Sprintf("%s/view", pluginWebBasePath) + + // API 路径 + paths["vue_api"] = fmt.Sprintf("%s/api", pluginWebBasePath) + } else { + // Package 模式:传统的目录结构 + // Vue 页面路径 + paths["vue_page"] = fmt.Sprintf("%s/view/%s", webBasePath, packageName) + + // API 路径 + paths["vue_api"] = fmt.Sprintf("%s/api/%s", webBasePath, packageName) + } + } + + // 添加模块信息 + paths["module"] = moduleName + paths["package_name"] = packageName + paths["package_type"] = packageType + paths["struct_name"] = structName + paths["root_path"] = rootPath + paths["server_path"] = serverPath + paths["web_path"] = webPath + + return paths +} + +// collectExpectedFilePaths 收集预期生成的文件路径 +func (g *GVAExecutor) collectExpectedFilePaths(plan *ExecutionPlan) []string { + var paths []string + + // 获取目录结构 + dirPaths := g.buildDirectoryStructure(plan) + + // 如果需要创建模块,添加预期的文件路径 + if plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 { + for _, moduleInfo := range plan.ModulesInfo { + structName := moduleInfo.StructName + + // 后端文件 + if apiPath, ok := dirPaths["api"]; ok { + paths = append(paths, fmt.Sprintf("%s/%s.go", apiPath, strings.ToLower(structName))) + } + if servicePath, ok := dirPaths["service"]; ok { + paths = append(paths, fmt.Sprintf("%s/%s.go", servicePath, strings.ToLower(structName))) + } + if modelPath, ok := dirPaths["model"]; ok { + paths = append(paths, fmt.Sprintf("%s/%s.go", modelPath, strings.ToLower(structName))) + } + if routerPath, ok := dirPaths["router"]; ok { + paths = append(paths, fmt.Sprintf("%s/%s.go", routerPath, strings.ToLower(structName))) + } + if requestPath, ok := dirPaths["request"]; ok { + paths = append(paths, fmt.Sprintf("%s/%s.go", requestPath, strings.ToLower(structName))) + } + if responsePath, ok := dirPaths["response"]; ok { + paths = append(paths, fmt.Sprintf("%s/%s.go", responsePath, strings.ToLower(structName))) + } + + // 前端文件 + if vuePage, ok := dirPaths["vue_page"]; ok { + paths = append(paths, fmt.Sprintf("%s/%s.vue", vuePage, strings.ToLower(structName))) + } + if vueApi, ok := dirPaths["vue_api"]; ok { + paths = append(paths, fmt.Sprintf("%s/%s.js", vueApi, strings.ToLower(structName))) + } + } + } + + return paths +} + +// createRequiredDictionaries 创建所需的字典(批量处理) +func (g *GVAExecutor) createRequiredDictionaries(ctx context.Context, modulesInfoList []*request.AutoCode) string { + var messages []string + dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService + createdDictTypes := make(map[string]bool) // 用于避免重复创建相同的字典 + + // 遍历所有模块 + for moduleIndex, modulesInfo := range modulesInfoList { + messages = append(messages, fmt.Sprintf("处理模块 %d (%s) 的字典: ", moduleIndex+1, modulesInfo.StructName)) + + // 遍历当前模块的所有字段,查找使用字典的字段 + moduleHasDictFields := false + for _, field := range modulesInfo.Fields { + if field.DictType != "" { + moduleHasDictFields = true + + // 如果这个字典类型已经在之前的模块中创建过,跳过 + if createdDictTypes[field.DictType] { + messages = append(messages, fmt.Sprintf("字典 %s 已在前面的模块中创建,跳过; ", field.DictType)) + continue + } + + // 检查字典是否存在 + exists, err := g.checkDictionaryExists(field.DictType) + if err != nil { + messages = append(messages, fmt.Sprintf("检查字典 %s 时出错: %v; ", field.DictType, err)) + continue + } + + if !exists { + // 字典不存在,创建字典 + dictionary := model.SysDictionary{ + Name: g.generateDictionaryName(field.DictType, field.FieldDesc), + Type: field.DictType, + Status: &[]bool{true}[0], // 默认启用 + Desc: fmt.Sprintf("自动生成的字典,用于模块 %s 字段: %s (%s)", modulesInfo.StructName, field.FieldName, field.FieldDesc), + } + + err = dictionaryService.CreateSysDictionary(dictionary) + if err != nil { + messages = append(messages, fmt.Sprintf("创建字典 %s 失败: %v; ", field.DictType, err)) + } else { + messages = append(messages, fmt.Sprintf("成功创建字典 %s (%s); ", field.DictType, dictionary.Name)) + createdDictTypes[field.DictType] = true // 标记为已创建 + + // 创建默认的字典详情项 + g.createDefaultDictionaryDetails(ctx, field.DictType, field.FieldDesc) + } + } else { + messages = append(messages, fmt.Sprintf("字典 %s 已存在,跳过创建; ", field.DictType)) + createdDictTypes[field.DictType] = true // 标记为已存在 + } + } + } + + if !moduleHasDictFields { + messages = append(messages, "无需创建字典; ") + } + } + + return strings.Join(messages, "") +} + +// checkDictionaryExists 检查字典是否存在 +func (g *GVAExecutor) checkDictionaryExists(dictType string) (bool, error) { + dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService + _, err := dictionaryService.GetSysDictionary(dictType, 0, nil) + if err != nil { + // 如果是记录不存在的错误,返回false + if strings.Contains(err.Error(), "record not found") { + return false, nil + } + // 其他错误返回错误信息 + return false, err + } + return true, nil +} + +// generateDictionaryName 生成字典名称 +func (g *GVAExecutor) generateDictionaryName(dictType, fieldDesc string) string { + if fieldDesc != "" { + return fmt.Sprintf("%s选项", fieldDesc) + } + return fmt.Sprintf("%s字典", dictType) +} + +// createDefaultDictionaryDetails 创建默认的字典详情项 +func (g *GVAExecutor) createDefaultDictionaryDetails(ctx context.Context, dictType, fieldDesc string) { + dictionaryDetailService := service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService + + // 创建一些默认的字典项 + defaultItems := []struct { + label string + value string + sort int + }{ + {"选项1", "option1", 1}, + {"选项2", "option2", 2}, + {"选项3", "option3", 3}, + } + + for _, item := range defaultItems { + detail := model.SysDictionaryDetail{ + Label: item.label, + Value: item.value, + Sort: item.sort, + SysDictionaryID: 0, // 这里需要获取字典ID,但为了简化先设为0 + Status: &[]bool{true}[0], + } + + // 尝试创建字典详情,忽略错误 + _ = dictionaryDetailService.CreateSysDictionaryDetail(detail) + } +} + +// isSystemFunction 检查是否为系统函数 +func (g *GVAExecutor) isSystemFunction(funcName string) bool { + systemFunctions := []string{ + "Create", "Delete", "Update", "Find", "Get", "List", + "CreateInBatches", "Save", "First", "Take", "Last", + "Find", "Scan", "Pluck", "Count", "Distinct", + "Select", "Omit", "Where", "Not", "Or", + "Limit", "Offset", "Order", "Group", "Having", + "Joins", "Preload", "Raw", "Exec", "Row", "Rows", + "ScanRows", "Transaction", "Begin", "Commit", "Rollback", + "SavePoint", "RollbackTo", "CreateTable", "DropTable", + "HasTable", "ColumnTypes", "CreateIndex", "DropIndex", + "HasIndex", "Rename", "CurrentDatabase", "Debug", + "DryRun", "PrepareStmt", "WithContext", "Logger", + "NowFunc", "CloneDB", "Callback", "AddError", + "DB", "SetupJoinTable", "Use", "ToSQL", + } + return g.contains(systemFunctions, funcName) +} + +// contains 检查切片是否包含指定元素 +func (g *GVAExecutor) contains(slice []string, item string) bool { + for _, s := range slice { + if s == item { + return true + } + } + return false +} diff --git a/server/mcp/requirement_analyzer.go b/server/mcp/requirement_analyzer.go index 31945d056e..65d0085c04 100644 --- a/server/mcp/requirement_analyzer.go +++ b/server/mcp/requirement_analyzer.go @@ -113,9 +113,9 @@ func (t *RequirementAnalyzer) generateAIPrompt(userRequirement string) string { ## 🎯 AI任务要求 请将上述用户需求梳理成清晰的逻辑步骤,格式要求: -**1. 第一步功能描述** -**2. 第二步功能描述** -**3. 第三步功能描述** +- **1. 第一步功能需要的字段** +- **2. 第二步功能需要的字段** +- **3. 第三步功能需要的字段** **...** ## 📋 梳理要求 @@ -126,11 +126,9 @@ func (t *RequirementAnalyzer) generateAIPrompt(userRequirement string) string { - 考虑数据流和用户操作流程 ## 🔄 后续流程 -梳理完成后,请使用 gva_auto_generate 工具进行代码生成: -- gva_auto_generate 会根据梳理的逻辑步骤自动生成包名、模块名 -- gva_auto_generate 会设计数据表结构和API接口 -- gva_auto_generate 会生成完整的前后端代码 - +梳理完成后,请使用 gva_analyzer 工具获取当前系统的模块信息,根据模块信息判断是否需要创建新的模块。 +如果需要创建新的模块和结构体,需要使用 gva_execution 工具进行代码生成。 +如果不需要创建新的模块和结构体,则返回当前的依赖路径,供非MCP的AI创建代码逻辑使用。 现在请开始梳理用户需求:"%s"`, userRequirement, userRequirement) From ec3c1169288f5a57fce7f7c58da5418d55fc9bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?piexlMax=28=E5=A5=87=E6=B7=BC?= Date: Wed, 10 Sep 2025 15:50:05 +0800 Subject: [PATCH 03/33] =?UTF-8?q?feat(mcp):=20=E6=96=B0=E5=A2=9Egva=5Frevi?= =?UTF-8?q?ew=E5=B7=A5=E5=85=B7=E5=B9=B6=E4=BC=98=E5=8C=96=E5=AD=97?= =?UTF-8?q?=E5=85=B8=E5=92=8C=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/mcp/dictionary_generator.go | 12 +- server/mcp/dictionary_query.go | 67 +++--- server/mcp/gva_analyze.go | 22 +- server/mcp/gva_execute.go | 291 +++++++++++------------- server/mcp/gva_review.go | 170 ++++++++++++++ server/mcp/requirement_analyzer.go | 28 +-- server/service/system/sys_dictionary.go | 2 +- 7 files changed, 375 insertions(+), 217 deletions(-) create mode 100644 server/mcp/gva_review.go diff --git a/server/mcp/dictionary_generator.go b/server/mcp/dictionary_generator.go index e7c6558064..ea548fdfd0 100644 --- a/server/mcp/dictionary_generator.go +++ b/server/mcp/dictionary_generator.go @@ -21,6 +21,13 @@ func init() { // DictionaryOptionsGenerator 字典选项生成器 type DictionaryOptionsGenerator struct{} +// DictionaryOption 字典选项结构 +type DictionaryOption struct { + Label string `json:"label"` + Value string `json:"value"` + Sort int `json:"sort"` +} + // DictionaryGenerateRequest 字典生成请求 type DictionaryGenerateRequest struct { DictType string `json:"dictType"` // 字典类型 @@ -139,11 +146,11 @@ func (d *DictionaryOptionsGenerator) InputSchema() map[string]interface{} { }, "dictName": map[string]interface{}{ "type": "string", - "description": "字典名称,可选,默认根据fieldDesc生成", + "description": "字典名称,必填,默认根据fieldDesc生成", }, "description": map[string]interface{}{ "type": "string", - "description": "字典描述,可选", + "description": "字典描述,必填", }, }, "required": []string{"dictType", "fieldDesc", "options"}, @@ -180,7 +187,6 @@ func (d *DictionaryOptionsGenerator) Handle(ctx context.Context, request mcp.Cal return nil, errors.New("options 不能为空") } - // 可选参数 dictName, _ := args["dictName"].(string) description, _ := args["description"].(string) diff --git a/server/mcp/dictionary_query.go b/server/mcp/dictionary_query.go index aea5df48b9..0e0f175513 100644 --- a/server/mcp/dictionary_query.go +++ b/server/mcp/dictionary_query.go @@ -18,13 +18,18 @@ func init() { RegisterTool(&DictionaryQuery{}) } +type DictionaryPre struct { + Type string `json:"type"` // 字典名(英) + Desc string `json:"desc"` // 描述 +} + // DictionaryInfo 字典信息结构 type DictionaryInfo struct { - ID uint `json:"id"` - Name string `json:"name"` // 字典名(中) - Type string `json:"type"` // 字典名(英) - Status *bool `json:"status"` // 状态 - Desc string `json:"desc"` // 描述 + ID uint `json:"id"` + Name string `json:"name"` // 字典名(中) + Type string `json:"type"` // 字典名(英) + Status *bool `json:"status"` // 状态 + Desc string `json:"desc"` // 描述 Details []DictionaryDetailInfo `json:"details"` // 字典详情 } @@ -40,9 +45,9 @@ type DictionaryDetailInfo struct { // DictionaryQueryResponse 字典查询响应结构 type DictionaryQueryResponse struct { - Success bool `json:"success"` - Message string `json:"message"` - Total int `json:"total"` + Success bool `json:"success"` + Message string `json:"message"` + Total int `json:"total"` Dictionaries []DictionaryInfo `json:"dictionaries"` } @@ -68,36 +73,36 @@ func (d *DictionaryQuery) New() mcp.Tool { // Handle 处理字典查询请求 func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { args := request.GetArguments() - + // 获取参数 dictType := "" if val, ok := args["dictType"].(string); ok { dictType = val } - + includeDisabled := false if val, ok := args["includeDisabled"].(bool); ok { includeDisabled = val } - + detailsOnly := false if val, ok := args["detailsOnly"].(bool); ok { detailsOnly = val } - + // 获取字典服务 dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService - + var dictionaries []DictionaryInfo var err error - + if dictType != "" { // 查询指定类型的字典 var status *bool if !includeDisabled { status = &[]bool{true}[0] } - + sysDictionary, err := dictionaryService.GetSysDictionary(dictType, 0, status) if err != nil { global.GVA_LOG.Error("查询字典失败", zap.Error(err)) @@ -107,7 +112,7 @@ func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolReques }, }, nil } - + // 转换为响应格式 dictInfo := DictionaryInfo{ ID: sysDictionary.ID, @@ -116,7 +121,7 @@ func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolReques Status: sysDictionary.Status, Desc: sysDictionary.Desc, } - + // 获取字典详情 for _, detail := range sysDictionary.SysDictionaryDetails { if includeDisabled || (detail.Status != nil && *detail.Status) { @@ -130,17 +135,17 @@ func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolReques }) } } - + dictionaries = append(dictionaries, dictInfo) } else { // 查询所有字典 var sysDictionaries []system.SysDictionary db := global.GVA_DB.Model(&system.SysDictionary{}) - + if !includeDisabled { db = db.Where("status = ?", true) } - + err = db.Preload("SysDictionaryDetails", func(db *gorm.DB) *gorm.DB { if includeDisabled { return db.Order("sort") @@ -148,7 +153,7 @@ func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolReques return db.Where("status = ?", true).Order("sort") } }).Find(&sysDictionaries).Error - + if err != nil { global.GVA_LOG.Error("查询字典列表失败", zap.Error(err)) return &mcp.CallToolResult{ @@ -157,7 +162,7 @@ func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolReques }, }, nil } - + // 转换为响应格式 for _, dict := range sysDictionaries { dictInfo := DictionaryInfo{ @@ -167,7 +172,7 @@ func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolReques Status: dict.Status, Desc: dict.Desc, } - + // 获取字典详情 for _, detail := range dict.SysDictionaryDetails { if includeDisabled || (detail.Status != nil && *detail.Status) { @@ -181,25 +186,25 @@ func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolReques }) } } - + dictionaries = append(dictionaries, dictInfo) } } - + // 如果只需要详情信息,则提取所有详情 if detailsOnly { var allDetails []DictionaryDetailInfo for _, dict := range dictionaries { allDetails = append(allDetails, dict.Details...) } - + response := map[string]interface{}{ "success": true, "message": "查询字典详情成功", "total": len(allDetails), "details": allDetails, } - + responseJSON, _ := json.Marshal(response) return &mcp.CallToolResult{ Content: []mcp.Content{ @@ -207,7 +212,7 @@ func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolReques }, }, nil } - + // 构建响应 response := DictionaryQueryResponse{ Success: true, @@ -215,7 +220,7 @@ func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolReques Total: len(dictionaries), Dictionaries: dictionaries, } - + responseJSON, err := json.Marshal(response) if err != nil { global.GVA_LOG.Error("序列化响应失败", zap.Error(err)) @@ -225,10 +230,10 @@ func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolReques }, }, nil } - + return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.NewTextContent(string(responseJSON)), }, }, nil -} \ No newline at end of file +} diff --git a/server/mcp/gva_analyze.go b/server/mcp/gva_analyze.go index 91ebd82712..8affe9ac9f 100644 --- a/server/mcp/gva_analyze.go +++ b/server/mcp/gva_analyze.go @@ -29,13 +29,9 @@ type AnalyzeRequest struct { // AnalyzeResponse 分析响应结构体 type AnalyzeResponse struct { - NeedCreatedPackage bool `json:"needCreatedPackage"` // 是否需要创建新包 - NeedCreatedModules []ModuleInfo `json:"needCreatedModules"` // 需要创建的模块列表 ExistingPackages []PackageInfo `json:"existingPackages"` // 现有包信息 PredesignedModules []PredesignedModuleInfo `json:"predesignedModules"` // 预设计模块信息 - SuggestedPackage string `json:"suggestedPackage"` // 建议的包名 - SuggestedTemplate string `json:"suggestedTemplate"` // 建议的模板类型 (package/plugin) - AnalysisMessage string `json:"analysisMessage"` // 分析结果消息 + Dictionaries []DictionaryPre `json:"dictionaries"` // 字典信息 CleanupInfo *CleanupInfo `json:"cleanupInfo"` // 清理信息(如果有) } @@ -79,7 +75,7 @@ type CleanupInfo struct { // New 创建GVA分析器工具 func (g *GVAAnalyzer) New() mcp.Tool { return mcp.NewTool("gva_analyze", - mcp.WithDescription("分析当前功能是否需要创建独立的package和module,或仅返回当前功能需要的文件路径"), + mcp.WithDescription("返回当前系统中有效的包和模块信息,并分析用户需求是否需要创建新的包、模块和字典。同时检查并清理空包,确保系统整洁。"), mcp.WithString("requirement", mcp.Description("用户需求描述,用于分析是否需要创建新的包和模块"), mcp.Required(), @@ -258,16 +254,18 @@ func (g *GVAAnalyzer) performAnalysis(ctx context.Context, req AnalyzeRequest) ( } } + dictionaries := []DictionaryPre{} // 这里可以根据需要填充字典信息 + err = global.GVA_DB.Table("sys_dictionaries").Find(&dictionaries, "deleted_at is null").Error + if err != nil { + global.GVA_LOG.Warn(fmt.Sprintf("获取字典信息失败: %v", err)) + dictionaries = []DictionaryPre{} // 设置为空列表,不影响主流程 + } + // 10. 构建响应 response := &AnalyzeResponse{ - NeedCreatedPackage: true, // 默认建议创建新包 - NeedCreatedModules: []ModuleInfo{}, // 具体模块需要根据需求进一步分析 ExistingPackages: existingPackages, PredesignedModules: filteredModules, - SuggestedPackage: "", // 需要根据需求生成 - SuggestedTemplate: suggestedType, - AnalysisMessage: analysisMessage.String(), - CleanupInfo: cleanupInfo, + Dictionaries: dictionaries, } return response, nil diff --git a/server/mcp/gva_execute.go b/server/mcp/gva_execute.go index 9143a40246..2d41931234 100644 --- a/server/mcp/gva_execute.go +++ b/server/mcp/gva_execute.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" model "github.com/flipped-aurora/gin-vue-admin/server/model/system" + "github.com/flipped-aurora/gin-vue-admin/server/utils" "strings" "github.com/flipped-aurora/gin-vue-admin/server/global" @@ -13,6 +14,7 @@ import ( "github.com/flipped-aurora/gin-vue-admin/server/service" "github.com/mark3labs/mcp-go/mcp" + "go.uber.org/zap" ) // 注册工具 @@ -42,28 +44,30 @@ type ExecuteResponse struct { // ExecutionPlan 执行计划结构 type ExecutionPlan struct { - PackageName string `json:"packageName"` - PackageType string `json:"packageType"` // "plugin" 或 "package" - NeedCreatedPackage bool `json:"needCreatedPackage"` - NeedCreatedModules bool `json:"needCreatedModules"` - PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"` - ModulesInfo []*request.AutoCode `json:"modulesInfo,omitempty"` - Paths map[string]string `json:"paths,omitempty"` + PackageName string `json:"packageName"` + PackageType string `json:"packageType"` // "plugin" 或 "package" + NeedCreatedPackage bool `json:"needCreatedPackage"` + NeedCreatedModules bool `json:"needCreatedModules"` + NeedCreatedDictionaries bool `json:"needCreatedDictionaries"` + PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"` + ModulesInfo []*request.AutoCode `json:"modulesInfo,omitempty"` + Paths map[string]string `json:"paths,omitempty"` + DictionariesInfo []*DictionaryGenerateRequest `json:"dictionariesInfo,omitempty"` } // New 创建GVA代码生成执行器工具 func (g *GVAExecutor) New() mcp.Tool { return mcp.NewTool("gva_execute", - mcp.WithDescription(`**🚀 GVA代码生成执行器:直接执行代码生成,无需确认步骤** + mcp.WithDescription(`**GVA代码生成执行器:直接执行代码生成,无需确认步骤** **核心功能:** -- 接收ExecutionPlan执行计划,直接生成代码 +- 根据需求分析和当前的包信息判断是否调用,如果需要调用,则根据入参描述生成json,用于直接生成代码 - 支持批量创建多个模块 - 自动创建包、模块、字典等 - 移除了确认步骤,提高执行效率 **使用场景:** -- 在gva_analyze分析完成后调用 +- 在gva_analyze获取了当前的包信息和字典信息之后,如果已经包含了可以使用的包和模块,那就不要调用本mcp - 根据分析结果直接生成代码 - 适用于自动化代码生成流程 @@ -84,18 +88,19 @@ func (g *GVAExecutor) New() mcp.Tool { 重要:ExecutionPlan结构体格式要求(支持批量创建): { "packageName": "包名(string)", - "packageType": "package或plugin(string)", + "packageType": "package或plugin(string),在用户提到使用插件时,必须为plugin,其余情况下如果是可以复用的业务就选择plugin,如果是特定业务流程则选用package。", "needCreatedPackage": "是否需要创建包(bool)", "needCreatedModules": "是否需要创建模块(bool)", + "needCreatedDictionaries": "是否需要创建字典(bool)", "packageInfo": { "desc": "描述(string)", "label": "展示名(string)", - "template": "package或plugin(string)", + "template": "package或plugin(string),在用户提到使用插件时,必须为plugin,其余情况下如果是可以复用的业务就选择plugin,如果是特定业务流程则选用package。", "packageName": "包名(string)" }, "modulesInfo": [{ - "package": "包名(string)", - "tableName": "数据库表名(string)", + "package": "包名(string,必然是小写开头)", + "tableName": "数据库表名(string,使用蛇形命名法)", "businessDB": "业务数据库(string)", "structName": "结构体名(string)", "packageName": "文件名称(string)", @@ -104,12 +109,12 @@ func (g *GVAExecutor) New() mcp.Tool { "humpPackageName": "文件名称 一般是结构体名的小驼峰(string)", "gvaModel": "是否使用GVA模型(bool) 固定为true 后续不需要创建ID created_at deleted_at updated_at", "autoMigrate": "是否自动迁移(bool)", - "autoCreateResource": "是否创建资源(bool)", - "autoCreateApiToSql": "是否创建API(bool)", - "autoCreateMenuToSql": "是否创建菜单(bool)", - "autoCreateBtnAuth": "是否创建按钮权限(bool)", - "onlyTemplate": "是否仅模板(bool)", - "isTree": "是否树形结构(bool)", + "autoCreateResource": "是否创建资源(bool,默认为false)", + "autoCreateApiToSql": "是否创建API(bool,默认为true)", + "autoCreateMenuToSql": "是否创建菜单(bool,默认为true)", + "autoCreateBtnAuth": "是否创建按钮权限(bool,默认为false)", + "onlyTemplate": "是否仅模板(bool,默认为false)", + "isTree": "是否树形结构(bool,默认为false)", "treeJson": "树形JSON字段(string)", "isAdd": "是否新增(bool) 固定为false", "generateWeb": "是否生成前端(bool)", @@ -145,31 +150,50 @@ func (g *GVAExecutor) New() mcp.Tool { "structName": "第二个模块的结构体名(string)", "description": "第二个模块的描述(string)", "...": "更多模块配置..." - }] + }], + "dictionariesInfo":[{ + "dictType": "字典类型(string) - 用于标识字典的唯一性", + "dictName": "字典名称(string) - 必须生成,字典的中文名称", + "description": "字典描述(string) - 字典的用途说明", + "status": "字典状态(bool) - true启用,false禁用", + "fieldDesc": "字段描述(string) - 用于AI理解字段含义并生成合适的选项", + "options": [{ + "label": "显示名称(string) - 用户看到的选项名", + "value": "选项值(string) - 实际存储的值", + "sort": "排序号(int) - 数字越小越靠前" + }] + }] } 注意: 1. needCreatedPackage=true时packageInfo必需 2. needCreatedModules=true时modulesInfo必需 -3. packageType只能是"package"或"plugin" -4. 字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组) -5. 搜索类型支持:=,!=,>,>=,<,<=,NOT BETWEEN/LIKE/BETWEEN/IN/NOT IN -6. gvaModel=true时自动包含ID,CreatedAt,UpdatedAt,DeletedAt字段 -7. **重要**:当gvaModel=false时,必须有一个字段的primaryKey=true,否则会导致PrimaryField为nil错误 -8. **重要**:当gvaModel=true时,系统会自动设置ID字段为主键,无需手动设置primaryKey=true -9. 智能字典创建功能:当字段使用字典类型(DictType)时,系统会: +3. needCreatedDictionaries=true时dictionariesInfo必需 +4. dictionariesInfo中的options字段可选,如果不提供将根据fieldDesc自动生成默认选项 +5. 字典创建会在模块创建之前执行,确保模块字段可以正确引用字典类型 +6. packageType只能是"package"或"plugin" +7. 字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组) +8. 搜索类型支持:=,!=,>,>=,<,<=,NOT BETWEEN/LIKE/BETWEEN/IN/NOT IN +9. gvaModel=true时自动包含ID,CreatedAt,UpdatedAt,DeletedAt字段 +10. **重要**:当gvaModel=false时,必须有一个字段的primaryKey=true,否则会导致PrimaryField为nil错误 +11. **重要**:当gvaModel=true时,系统会自动设置ID字段为主键,无需手动设置primaryKey=true +12. 智能字典创建功能:当字段使用字典类型(DictType)时,系统会: - 自动检查字典是否存在,如果不存在则创建字典 - 根据字典类型和字段描述智能生成默认选项,支持状态、性别、类型、等级、优先级、审批、角色、布尔值、订单、颜色、尺寸等常见场景 - 为无法识别的字典类型提供通用默认选项 -10. **模块关联配置**:当需要配置模块间的关联关系时,使用dataSource字段: +13. **模块关联配置**:当需要配置模块间的关联关系时,使用dataSource字段: - **dbName**: 关联的数据库名称 - **table**: 关联的表名 - **label**: 用于显示的字段名(如name、title等) - **value**: 用于存储的值字段名(通常是id) - - **association**: 关联关系类型(1=一对一关联,2=一对多关联) + - **association**: 关联关系类型(1=一对一关联,2=一对多关联)一对一和一对多的前面的一是当前的实体,如果他只能关联另一个实体的一个,则选用一对一,如果他需要关联多个他的关联实体,则选用一对多。 - **hasDeletedAt**: 关联表是否有软删除字段 - **checkDataSource**: 设为true时会验证关联表的存在性 - - 示例:{"dbName":"gva","table":"sys_users","label":"username","value":"id","association":2,"hasDeletedAt":true}`), + - 示例:{"dbName":"","table":"sys_users","label":"username","value":"id","association":1,"hasDeletedAt":true} +14. **自动字段类型修正**:系统会自动检查和修正字段类型: + - 当字段配置了dataSource且association=2(一对多关联)时,系统会自动将fieldType修改为'array' + - 这确保了一对多关联数据的正确存储和处理 + - 修正操作会记录在日志中,便于开发者了解变更情况`), mcp.WithObject("executionPlan", mcp.Description("执行计划,包含包信息和模块信息"), mcp.Required(), @@ -388,6 +412,23 @@ func (g *GVAExecutor) validateExecutionPlan(plan *ExecutionPlan) error { return fmt.Errorf("模块 %d 字段 %d 的 fieldSearchType '%s' 不支持,支持的类型:%v", moduleIndex+1, i+1, field.FieldSearchType, validSearchTypes) } } + + // 验证 dataSource 字段配置 + if field.DataSource != nil { + associationValue := field.DataSource.Association + // 当 association 为 2(一对多关联)时,强制修改 fieldType 为 array + if associationValue == 2 { + if field.FieldType != "array" { + global.GVA_LOG.Info(fmt.Sprintf("模块 %d 字段 %d:检测到一对多关联(association=2),自动将 fieldType 从 '%s' 修改为 'array'", moduleIndex+1, i+1, field.FieldType)) + moduleInfo.Fields[i].FieldType = "array" + } + } + + // 验证 association 值的有效性 + if associationValue != 1 && associationValue != 2 { + return fmt.Errorf("模块 %d 字段 %d 的 dataSource.association 必须是 1(一对一)或 2(一对多)", moduleIndex+1, i+1) + } + } } // 验证主键设置 @@ -451,14 +492,16 @@ func (g *GVAExecutor) executeCreation(ctx context.Context, plan *ExecutionPlan) result.Message += "包创建成功; " } + // 创建指定字典(如果需要) + if plan.NeedCreatedDictionaries && len(plan.DictionariesInfo) > 0 { + dictResult := g.createDictionariesFromInfo(ctx, plan.DictionariesInfo) + result.Message += dictResult + } + // 批量创建字典和模块(如果需要) if plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 { templateService := service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate - // 先批量创建所有模块需要的字典 - dictResult := g.createRequiredDictionaries(ctx, plan.ModulesInfo) - result.Message += dictResult - // 遍历所有模块进行创建 for _, moduleInfo := range plan.ModulesInfo { @@ -660,69 +703,6 @@ func (g *GVAExecutor) collectExpectedFilePaths(plan *ExecutionPlan) []string { return paths } -// createRequiredDictionaries 创建所需的字典(批量处理) -func (g *GVAExecutor) createRequiredDictionaries(ctx context.Context, modulesInfoList []*request.AutoCode) string { - var messages []string - dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService - createdDictTypes := make(map[string]bool) // 用于避免重复创建相同的字典 - - // 遍历所有模块 - for moduleIndex, modulesInfo := range modulesInfoList { - messages = append(messages, fmt.Sprintf("处理模块 %d (%s) 的字典: ", moduleIndex+1, modulesInfo.StructName)) - - // 遍历当前模块的所有字段,查找使用字典的字段 - moduleHasDictFields := false - for _, field := range modulesInfo.Fields { - if field.DictType != "" { - moduleHasDictFields = true - - // 如果这个字典类型已经在之前的模块中创建过,跳过 - if createdDictTypes[field.DictType] { - messages = append(messages, fmt.Sprintf("字典 %s 已在前面的模块中创建,跳过; ", field.DictType)) - continue - } - - // 检查字典是否存在 - exists, err := g.checkDictionaryExists(field.DictType) - if err != nil { - messages = append(messages, fmt.Sprintf("检查字典 %s 时出错: %v; ", field.DictType, err)) - continue - } - - if !exists { - // 字典不存在,创建字典 - dictionary := model.SysDictionary{ - Name: g.generateDictionaryName(field.DictType, field.FieldDesc), - Type: field.DictType, - Status: &[]bool{true}[0], // 默认启用 - Desc: fmt.Sprintf("自动生成的字典,用于模块 %s 字段: %s (%s)", modulesInfo.StructName, field.FieldName, field.FieldDesc), - } - - err = dictionaryService.CreateSysDictionary(dictionary) - if err != nil { - messages = append(messages, fmt.Sprintf("创建字典 %s 失败: %v; ", field.DictType, err)) - } else { - messages = append(messages, fmt.Sprintf("成功创建字典 %s (%s); ", field.DictType, dictionary.Name)) - createdDictTypes[field.DictType] = true // 标记为已创建 - - // 创建默认的字典详情项 - g.createDefaultDictionaryDetails(ctx, field.DictType, field.FieldDesc) - } - } else { - messages = append(messages, fmt.Sprintf("字典 %s 已存在,跳过创建; ", field.DictType)) - createdDictTypes[field.DictType] = true // 标记为已存在 - } - } - } - - if !moduleHasDictFields { - messages = append(messages, "无需创建字典; ") - } - } - - return strings.Join(messages, "") -} - // checkDictionaryExists 检查字典是否存在 func (g *GVAExecutor) checkDictionaryExists(dictType string) (bool, error) { dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService @@ -738,69 +718,72 @@ func (g *GVAExecutor) checkDictionaryExists(dictType string) (bool, error) { return true, nil } -// generateDictionaryName 生成字典名称 -func (g *GVAExecutor) generateDictionaryName(dictType, fieldDesc string) string { - if fieldDesc != "" { - return fmt.Sprintf("%s选项", fieldDesc) - } - return fmt.Sprintf("%s字典", dictType) -} - -// createDefaultDictionaryDetails 创建默认的字典详情项 -func (g *GVAExecutor) createDefaultDictionaryDetails(ctx context.Context, dictType, fieldDesc string) { +// createDictionariesFromInfo 根据 DictionariesInfo 创建字典 +func (g *GVAExecutor) createDictionariesFromInfo(ctx context.Context, dictionariesInfo []*DictionaryGenerateRequest) string { + var messages []string + dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService dictionaryDetailService := service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService - // 创建一些默认的字典项 - defaultItems := []struct { - label string - value string - sort int - }{ - {"选项1", "option1", 1}, - {"选项2", "option2", 2}, - {"选项3", "option3", 3}, - } + messages = append(messages, fmt.Sprintf("开始创建 %d 个指定字典: ", len(dictionariesInfo))) - for _, item := range defaultItems { - detail := model.SysDictionaryDetail{ - Label: item.label, - Value: item.value, - Sort: item.sort, - SysDictionaryID: 0, // 这里需要获取字典ID,但为了简化先设为0 - Status: &[]bool{true}[0], + for _, dictInfo := range dictionariesInfo { + // 检查字典是否存在 + exists, err := g.checkDictionaryExists(dictInfo.DictType) + if err != nil { + messages = append(messages, fmt.Sprintf("检查字典 %s 时出错: %v; ", dictInfo.DictType, err)) + continue } - // 尝试创建字典详情,忽略错误 - _ = dictionaryDetailService.CreateSysDictionaryDetail(detail) - } -} + if !exists { + // 字典不存在,创建字典 + dictionary := model.SysDictionary{ + Name: dictInfo.DictName, + Type: dictInfo.DictType, + Status: utils.Pointer(true), + Desc: dictInfo.Description, + } -// isSystemFunction 检查是否为系统函数 -func (g *GVAExecutor) isSystemFunction(funcName string) bool { - systemFunctions := []string{ - "Create", "Delete", "Update", "Find", "Get", "List", - "CreateInBatches", "Save", "First", "Take", "Last", - "Find", "Scan", "Pluck", "Count", "Distinct", - "Select", "Omit", "Where", "Not", "Or", - "Limit", "Offset", "Order", "Group", "Having", - "Joins", "Preload", "Raw", "Exec", "Row", "Rows", - "ScanRows", "Transaction", "Begin", "Commit", "Rollback", - "SavePoint", "RollbackTo", "CreateTable", "DropTable", - "HasTable", "ColumnTypes", "CreateIndex", "DropIndex", - "HasIndex", "Rename", "CurrentDatabase", "Debug", - "DryRun", "PrepareStmt", "WithContext", "Logger", - "NowFunc", "CloneDB", "Callback", "AddError", - "DB", "SetupJoinTable", "Use", "ToSQL", - } - return g.contains(systemFunctions, funcName) -} + err = dictionaryService.CreateSysDictionary(dictionary) + if err != nil { + messages = append(messages, fmt.Sprintf("创建字典 %s 失败: %v; ", dictInfo.DictType, err)) + continue + } + + messages = append(messages, fmt.Sprintf("成功创建字典 %s (%s); ", dictInfo.DictType, dictInfo.DictName)) + + // 获取刚创建的字典ID + var createdDict model.SysDictionary + err = global.GVA_DB.Where("type = ?", dictInfo.DictType).First(&createdDict).Error + if err != nil { + messages = append(messages, fmt.Sprintf("获取创建的字典失败: %v; ", err)) + continue + } + + // 创建字典选项 + if len(dictInfo.Options) > 0 { + successCount := 0 + for _, option := range dictInfo.Options { + dictionaryDetail := model.SysDictionaryDetail{ + Label: option.Label, + Value: option.Value, + Status: &[]bool{true}[0], // 默认启用 + Sort: option.Sort, + SysDictionaryID: int(createdDict.ID), + } -// contains 检查切片是否包含指定元素 -func (g *GVAExecutor) contains(slice []string, item string) bool { - for _, s := range slice { - if s == item { - return true + err = dictionaryDetailService.CreateSysDictionaryDetail(dictionaryDetail) + if err != nil { + global.GVA_LOG.Warn("创建字典详情项失败", zap.Error(err)) + } else { + successCount++ + } + } + messages = append(messages, fmt.Sprintf("创建了 %d 个字典选项; ", successCount)) + } + } else { + messages = append(messages, fmt.Sprintf("字典 %s 已存在,跳过创建; ", dictInfo.DictType)) } } - return false + + return strings.Join(messages, "") } diff --git a/server/mcp/gva_review.go b/server/mcp/gva_review.go new file mode 100644 index 0000000000..ed4aa8e311 --- /dev/null +++ b/server/mcp/gva_review.go @@ -0,0 +1,170 @@ +package mcpTool + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/mark3labs/mcp-go/mcp" +) + +// GVAReviewer GVA代码审查工具 +type GVAReviewer struct{} + +// init 注册工具 +func init() { + RegisterTool(&GVAReviewer{}) +} + +// ReviewRequest 审查请求结构 +type ReviewRequest struct { + UserRequirement string `json:"userRequirement"` // 经过requirement_analyze后的用户需求 + GeneratedFiles []string `json:"generatedFiles"` // gva_execute创建的文件列表 +} + +// ReviewResponse 审查响应结构 +type ReviewResponse struct { + Success bool `json:"success"` // 是否审查成功 + Message string `json:"message"` // 审查结果消息 + AdjustmentPrompt string `json:"adjustmentPrompt"` // 调整代码的提示 + ReviewDetails string `json:"reviewDetails"` // 详细的审查结果 +} + +// New 创建GVA代码审查工具 +func (g *GVAReviewer) New() mcp.Tool { + return mcp.NewTool("gva_review", + mcp.WithDescription(`**GVA代码审查工具 - 在gva_execute调用后使用** + +**核心功能:** +- 接收经过requirement_analyze处理的用户需求和gva_execute生成的文件列表 +- 分析生成的代码是否满足用户的原始需求 +- 检查是否涉及到关联、交互等复杂功能 +- 如果代码不满足需求,提供调整建议和新的prompt + +**使用场景:** +- 在gva_execute成功执行后调用 +- 用于验证生成的代码是否完整满足用户需求 +- 检查模块间的关联关系是否正确实现 +- 发现缺失的交互功能或业务逻辑 + +**工作流程:** +1. 接收用户原始需求和生成的文件列表 +2. 分析需求中的关键功能点 +3. 检查生成的文件是否覆盖所有功能 +4. 识别缺失的关联关系、交互功能等 +5. 生成调整建议和新的开发prompt + +**输出内容:** +- 审查结果和是否需要调整 +- 详细的缺失功能分析 +- 针对性的代码调整建议 +- 可直接使用的开发prompt + +**重要提示:** +- 本工具专门用于代码质量审查,不执行实际的代码修改 +- 重点关注模块间关联、用户交互、业务流程完整性 +- 提供的调整建议应该具体可执行`), + mcp.WithString("userRequirement", + mcp.Description("经过requirement_analyze处理后的用户需求描述,包含详细的功能要求和字段信息"), + mcp.Required(), + ), + mcp.WithString("generatedFiles", + mcp.Description("gva_execute创建的文件列表,JSON字符串格式,包含所有生成的后端和前端文件路径"), + mcp.Required(), + ), + ) +} + +// Handle 处理审查请求 +func (g *GVAReviewer) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // 获取用户需求 + userRequirementData, ok := request.GetArguments()["userRequirement"] + if !ok { + return nil, errors.New("参数错误:userRequirement 必须提供") + } + + userRequirement, ok := userRequirementData.(string) + if !ok { + return nil, errors.New("参数错误:userRequirement 必须是字符串类型") + } + + // 获取生成的文件列表 + generatedFilesData, ok := request.GetArguments()["generatedFiles"] + if !ok { + return nil, errors.New("参数错误:generatedFiles 必须提供") + } + + generatedFilesStr, ok := generatedFilesData.(string) + if !ok { + return nil, errors.New("参数错误:generatedFiles 必须是JSON字符串") + } + + // 解析JSON字符串为字符串数组 + var generatedFiles []string + err := json.Unmarshal([]byte(generatedFilesStr), &generatedFiles) + if err != nil { + return nil, fmt.Errorf("解析generatedFiles失败: %v", err) + } + + if len(generatedFiles) == 0 { + return nil, errors.New("参数错误:generatedFiles 不能为空") + } + + // 直接生成调整提示,不进行复杂分析 + adjustmentPrompt := g.generateAdjustmentPrompt(userRequirement, generatedFiles) + + // 构建简化的审查详情 + reviewDetails := fmt.Sprintf("📋 **代码审查报告**\n\n **用户原始需求:**\n%s\n\n **已生成文件数量:** %d\n\n **建议进行代码优化和完善**", userRequirement, len(generatedFiles)) + + // 构建审查结果 + reviewResult := &ReviewResponse{ + Success: true, + Message: "代码审查完成", + AdjustmentPrompt: adjustmentPrompt, + ReviewDetails: reviewDetails, + } + + // 序列化响应 + responseJSON, err := json.MarshalIndent(reviewResult, "", " ") + if err != nil { + return nil, fmt.Errorf("序列化审查结果失败: %v", err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(fmt.Sprintf("代码审查结果:\n\n%s", string(responseJSON))), + }, + }, nil +} + +// generateAdjustmentPrompt 生成调整代码的提示 +func (g *GVAReviewer) generateAdjustmentPrompt(userRequirement string, generatedFiles []string) string { + var prompt strings.Builder + + prompt.WriteString("🔧 **代码调整指导 Prompt:**\n\n") + prompt.WriteString(fmt.Sprintf("**用户的原始需求为:** %s\n\n", userRequirement)) + prompt.WriteString("**经过GVA生成后的文件有如下内容:**\n") + for _, file := range generatedFiles { + prompt.WriteString(fmt.Sprintf("- %s\n", file)) + } + prompt.WriteString("\n") + + prompt.WriteString("**请帮我优化和完善代码,确保:**\n") + prompt.WriteString("1. 代码完全满足用户的原始需求\n") + prompt.WriteString("2. 完善模块间的关联关系,确保数据一致性\n") + prompt.WriteString("3. 实现所有必要的用户交互功能\n") + prompt.WriteString("4. 保持代码的完整性和可维护性\n") + prompt.WriteString("5. 遵循GVA框架的开发规范和最佳实践\n") + prompt.WriteString("6. 确保前后端功能完整对接\n") + prompt.WriteString("7. 添加必要的错误处理和数据验证\n\n") + prompt.WriteString("8. 如果需要vue路由跳转,请使用 menu_lister获取完整路由表,并且路由跳转使用 router.push({\"name\":从menu_lister中获取的name})\n\n") + prompt.WriteString("9. 如果当前所有的vue页面内容无法满足需求,则自行书写vue文件,并且调用 menu_creator创建菜单记录\n\n") + prompt.WriteString("10. 如果需要API调用,请使用 api_lister获取api表,根据需求调用对应接口\n\n") + prompt.WriteString("11. 如果当前所有API无法满足则自行书写接口,补全前后端代码,并使用 api_creator创建api记录\n\n") + + prompt.WriteString("**请基于用户需求和现有文件,提供完整的代码优化方案。**") + + return prompt.String() +} diff --git a/server/mcp/requirement_analyzer.go b/server/mcp/requirement_analyzer.go index 65d0085c04..7dc9bc2ef9 100644 --- a/server/mcp/requirement_analyzer.go +++ b/server/mcp/requirement_analyzer.go @@ -28,14 +28,14 @@ type RequirementAnalysisResponse struct { // New 返回工具注册信息 func (t *RequirementAnalyzer) New() mcp.Tool { return mcp.NewTool("requirement_analyzer", - mcp.WithDescription(`**🚀 需求分析工具 - 首选入口工具(最高优先级)** + mcp.WithDescription(`** 需求分析工具 - 首选入口工具(最高优先级)** -**⭐ 重要提示:这是所有MCP工具的首选入口,请优先使用!** +** 重要提示:这是所有MCP工具的首选入口,请优先使用!** -**🎯 核心职责:** +** 核心职责:** 将用户的自然语言需求转换为AI可理解的结构化提示词 -**📋 工作流程:** +** 工作流程:** 1. 接收用户自然语言需求描述 2. 生成专业的AI提示词,要求AI将需求梳理为清晰的步骤需求字段: - **1. 第一步功能需要的字段** @@ -43,7 +43,7 @@ func (t *RequirementAnalyzer) New() mcp.Tool { - **3. 第三步功能需要的字段** - **...** 3. 需要清楚描述出这些需求需要的字段有哪些,如果用户提供了字段内容或者sql文件,一定不要发散思维,一定使用用户提供的字段。 -4. 指导后续使用 gva_auto_generate 工具进行代码生成 +4. 指导后续使用 gva_analyze 工具进行代码生成 **✅ 适用场景:** - 用户有新的业务需求需要开发 @@ -51,13 +51,8 @@ func (t *RequirementAnalyzer) New() mcp.Tool { - 想要快速搭建业务系统 - 需求描述比较模糊,需要AI帮助梳理 -**❌ 不负责的事情:** -- 不生成具体的包名和模块名(交给 gva_auto_generate) -- 不进行代码生成(交给 gva_auto_generate) -- 不创建数据库表结构(交给 gva_auto_generate) - **🔄 推荐工作流:** -requirement_analyzer → gva_auto_generate → 其他辅助工具 +requirement_analyzer → gva_analyze → 其他辅助工具 `), mcp.WithString("userRequirement", @@ -105,12 +100,12 @@ func (t *RequirementAnalyzer) analyzeRequirement(userRequirement string) (*Requi // generateAIPrompt 生成AI提示词 - 要求AI梳理逻辑为1xxx2xxx格式 func (t *RequirementAnalyzer) generateAIPrompt(userRequirement string) string { - prompt := fmt.Sprintf(`# 🤖 AI需求逻辑梳理任务 + prompt := fmt.Sprintf(`# AI需求逻辑梳理任务 -## 📝 用户原始需求 +## 用户原始需求 %s -## 🎯 AI任务要求 +## AI任务要求 请将上述用户需求梳理成清晰的逻辑步骤,格式要求: - **1. 第一步功能需要的字段** @@ -118,14 +113,15 @@ func (t *RequirementAnalyzer) generateAIPrompt(userRequirement string) string { - **3. 第三步功能需要的字段** **...** -## 📋 梳理要求 +## 梳理要求 - 将需求拆解为具体的功能步骤 - 每个步骤用数字编号(1、2、3...) - 步骤描述要清晰、具体、可执行 - 按照业务逻辑顺序排列 - 考虑数据流和用户操作流程 +- 如果分析的字段涉及到关联,请明确指出关联关系为一对一还是一对多 -## 🔄 后续流程 +## 后续流程 梳理完成后,请使用 gva_analyzer 工具获取当前系统的模块信息,根据模块信息判断是否需要创建新的模块。 如果需要创建新的模块和结构体,需要使用 gva_execution 工具进行代码生成。 如果不需要创建新的模块和结构体,则返回当前的依赖路径,供非MCP的AI创建代码逻辑使用。 diff --git a/server/service/system/sys_dictionary.go b/server/service/system/sys_dictionary.go index d540a9602c..05ff67d46a 100644 --- a/server/service/system/sys_dictionary.go +++ b/server/service/system/sys_dictionary.go @@ -93,7 +93,7 @@ func (dictionaryService *DictionaryService) GetSysDictionary(Type string, Id uin flag = *status } err = global.GVA_DB.Where("(type = ? OR id = ?) and status = ?", Type, Id, flag).Preload("SysDictionaryDetails", func(db *gorm.DB) *gorm.DB { - return db.Where("status = ?", true).Order("sort") + return db.Where("status = ? and deleted_at is null", true).Order("sort") }).First(&sysDictionary).Error return } From 9712eeaa673a03084e1d8cf4dbac172a86957796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?piexlMax=28=E5=A5=87=E6=B7=BC?= Date: Wed, 10 Sep 2025 16:01:18 +0800 Subject: [PATCH 04/33] =?UTF-8?q?fix:=20=E8=B0=83=E6=95=B4mcp=E6=95=B4?= =?UTF-8?q?=E4=BD=93=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/mcp/gva_review.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/mcp/gva_review.go b/server/mcp/gva_review.go index ed4aa8e311..a32a54478c 100644 --- a/server/mcp/gva_review.go +++ b/server/mcp/gva_review.go @@ -163,7 +163,7 @@ func (g *GVAReviewer) generateAdjustmentPrompt(userRequirement string, generated prompt.WriteString("9. 如果当前所有的vue页面内容无法满足需求,则自行书写vue文件,并且调用 menu_creator创建菜单记录\n\n") prompt.WriteString("10. 如果需要API调用,请使用 api_lister获取api表,根据需求调用对应接口\n\n") prompt.WriteString("11. 如果当前所有API无法满足则自行书写接口,补全前后端代码,并使用 api_creator创建api记录\n\n") - + prompt.WriteString("12. 无论前后端都不要随意删除import的内容\n\n") prompt.WriteString("**请基于用户需求和现有文件,提供完整的代码优化方案。**") return prompt.String() From 8a9b088b0e1dda029381157946f2907903d37ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?piexlMax=28=E5=A5=87=E6=B7=BC?= Date: Wed, 10 Sep 2025 17:45:34 +0800 Subject: [PATCH 05/33] =?UTF-8?q?fix:=20=E8=B0=83=E6=95=B4mcp=E6=95=B4?= =?UTF-8?q?=E4=BD=93=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/mcp/gva_analyze.go | 50 +--------- server/mcp/gva_execute.go | 4 +- server/mcp/requirement_analyzer.go | 153 +++++++++++++++++++++-------- 3 files changed, 117 insertions(+), 90 deletions(-) diff --git a/server/mcp/gva_analyze.go b/server/mcp/gva_analyze.go index 8affe9ac9f..e48409bfe8 100644 --- a/server/mcp/gva_analyze.go +++ b/server/mcp/gva_analyze.go @@ -117,23 +117,19 @@ func (g *GVAAnalyzer) Handle(ctx context.Context, request mcp.CallToolRequest) ( // performAnalysis 执行分析逻辑 func (g *GVAAnalyzer) performAnalysis(ctx context.Context, req AnalyzeRequest) (*AnalyzeResponse, error) { - // 1. 检测插件意图 - suggestedType, isPlugin, confidence := g.detectPluginIntent(req.Requirement) - global.GVA_LOG.Info(fmt.Sprintf("插件意图检测结果: 类型=%s, 是否插件=%v, 置信度=%s", suggestedType, isPlugin, confidence)) - - // 2. 获取数据库中的包信息 + // 1. 获取数据库中的包信息 var packages []model.SysAutoCodePackage if err := global.GVA_DB.Find(&packages).Error; err != nil { return nil, fmt.Errorf("获取包信息失败: %v", err) } - // 3. 获取历史记录 + // 2. 获取历史记录 var histories []model.SysAutoCodeHistory if err := global.GVA_DB.Find(&histories).Error; err != nil { return nil, fmt.Errorf("获取历史记录失败: %v", err) } - // 4. 检查空包并进行清理 + // 3. 检查空包并进行清理 cleanupInfo := &CleanupInfo{ DeletedPackages: []string{}, DeletedModules: []string{}, @@ -225,7 +221,7 @@ func (g *GVAAnalyzer) performAnalysis(ctx context.Context, req AnalyzeRequest) ( // 8. 构建分析结果消息 var analysisMessage strings.Builder if len(cleanupInfo.DeletedPackages) > 0 || len(cleanupInfo.DeletedModules) > 0 { - analysisMessage.WriteString("🧹 **系统清理完成**\n\n") + analysisMessage.WriteString("**系统清理完成**\n\n") if len(cleanupInfo.DeletedPackages) > 0 { analysisMessage.WriteString(fmt.Sprintf("- 删除了 %d 个空包: %s\n", len(cleanupInfo.DeletedPackages), strings.Join(cleanupInfo.DeletedPackages, ", "))) } @@ -236,8 +232,7 @@ func (g *GVAAnalyzer) performAnalysis(ctx context.Context, req AnalyzeRequest) ( cleanupInfo.CleanupMessage = analysisMessage.String() } - analysisMessage.WriteString("📊 **分析结果**\n\n") - analysisMessage.WriteString(fmt.Sprintf("- **插件意图检测**: %s (置信度: %s)\n", suggestedType, confidence)) + analysisMessage.WriteString(" **分析结果**\n\n") analysisMessage.WriteString(fmt.Sprintf("- **现有包数量**: %d\n", len(validPackages))) analysisMessage.WriteString(fmt.Sprintf("- **预设计模块数量**: %d\n\n", len(filteredModules))) @@ -271,41 +266,6 @@ func (g *GVAAnalyzer) performAnalysis(ctx context.Context, req AnalyzeRequest) ( return response, nil } -// detectPluginIntent 检测插件意图 -func (g *GVAAnalyzer) detectPluginIntent(requirement string) (string, bool, string) { - requirement = strings.ToLower(requirement) - - // 插件关键词映射 - pluginKeywords := map[string]string{ - "插件": "plugin", - "plugin": "plugin", - "扩展": "plugin", - "extension": "plugin", - "addon": "plugin", - "模块": "package", - "module": "package", - "包": "package", - "package": "package", - "功能": "package", - "feature": "package", - } - - // 检查关键词 - for keyword, templateType := range pluginKeywords { - if strings.Contains(requirement, keyword) { - isPlugin := templateType == "plugin" - confidence := "高" - if strings.Contains(requirement, "可能") || strings.Contains(requirement, "也许") { - confidence = "中" - } - return templateType, isPlugin, confidence - } - } - - // 默认返回package类型 - return "package", false, "低" -} - // isPackageFolderEmpty 检查包文件夹是否为空 func (g *GVAAnalyzer) isPackageFolderEmpty(packageName, template string) (bool, error) { // 根据模板类型确定基础路径 diff --git a/server/mcp/gva_execute.go b/server/mcp/gva_execute.go index 2d41931234..118b28dcc1 100644 --- a/server/mcp/gva_execute.go +++ b/server/mcp/gva_execute.go @@ -88,14 +88,14 @@ func (g *GVAExecutor) New() mcp.Tool { 重要:ExecutionPlan结构体格式要求(支持批量创建): { "packageName": "包名(string)", - "packageType": "package或plugin(string),在用户提到使用插件时,必须为plugin,其余情况下如果是可以复用的业务就选择plugin,如果是特定业务流程则选用package。", + "packageType": "package或plugin(string),如果是可以复用的业务就选择plugin,如果是特定业务流程则选用package。", "needCreatedPackage": "是否需要创建包(bool)", "needCreatedModules": "是否需要创建模块(bool)", "needCreatedDictionaries": "是否需要创建字典(bool)", "packageInfo": { "desc": "描述(string)", "label": "展示名(string)", - "template": "package或plugin(string),在用户提到使用插件时,必须为plugin,其余情况下如果是可以复用的业务就选择plugin,如果是特定业务流程则选用package。", + "template": "package或plugin(string),如果是可以复用的业务就选择plugin,如果是特定业务流程则选用package。", "packageName": "包名(string)" }, "modulesInfo": [{ diff --git a/server/mcp/requirement_analyzer.go b/server/mcp/requirement_analyzer.go index 7dc9bc2ef9..765b7509a9 100644 --- a/server/mcp/requirement_analyzer.go +++ b/server/mcp/requirement_analyzer.go @@ -28,33 +28,36 @@ type RequirementAnalysisResponse struct { // New 返回工具注册信息 func (t *RequirementAnalyzer) New() mcp.Tool { return mcp.NewTool("requirement_analyzer", - mcp.WithDescription(`** 需求分析工具 - 首选入口工具(最高优先级)** + mcp.WithDescription(`** 智能需求分析与模块设计工具 - 首选入口工具(最高优先级)** ** 重要提示:这是所有MCP工具的首选入口,请优先使用!** -** 核心职责:** -将用户的自然语言需求转换为AI可理解的结构化提示词 - -** 工作流程:** -1. 接收用户自然语言需求描述 -2. 生成专业的AI提示词,要求AI将需求梳理为清晰的步骤需求字段: - - **1. 第一步功能需要的字段** - - **2. 第二步功能需要的字段** - - **3. 第三步功能需要的字段** - - **...** -3. 需要清楚描述出这些需求需要的字段有哪些,如果用户提供了字段内容或者sql文件,一定不要发散思维,一定使用用户提供的字段。 -4. 指导后续使用 gva_analyze 工具进行代码生成 - -**✅ 适用场景:** -- 用户有新的业务需求需要开发 -- 需要创建新的功能模块 -- 想要快速搭建业务系统 -- 需求描述比较模糊,需要AI帮助梳理 - -**🔄 推荐工作流:** -requirement_analyzer → gva_analyze → 其他辅助工具 - -`), +** 核心能力:** +作为资深系统架构师,智能分析用户需求并自动设计完整的模块架构 + +** 核心功能:** +1. **智能需求解构**:深度分析用户需求,识别核心业务实体、业务流程、数据关系 +2. **自动模块设计**:基于需求分析,智能确定需要多少个模块及各模块功能 +3. **字段智能推导**:为每个模块自动设计详细字段,包含数据类型、关联关系、字典需求 +4. **架构优化建议**:提供模块拆分、关联设计、扩展性等专业建议 + +** 输出内容:** +- 模块数量和架构设计 +- 每个模块的详细字段清单 +- 数据类型和关联关系设计 +- 字典需求和类型定义 +- 模块间关系图和扩展建议 + +** 适用场景:** +- 用户需求描述不完整,需要智能补全 +- 复杂业务系统的模块架构设计 +- 需要专业的数据库设计建议 +- 想要快速搭建生产级业务系统 + +** 推荐工作流:** + requirement_analyzer → gva_analyze → gva_execute → 其他辅助工具 + + `), mcp.WithString("userRequirement", mcp.Required(), mcp.Description("用户的需求描述,支持自然语言,如:'我要做一个猫舍管理系统,用来录入猫的信息,并且记录每只猫每天的活动信息'"), @@ -98,35 +101,99 @@ func (t *RequirementAnalyzer) analyzeRequirement(userRequirement string) (*Requi }, nil } -// generateAIPrompt 生成AI提示词 - 要求AI梳理逻辑为1xxx2xxx格式 +// generateAIPrompt 生成AI提示词 - 智能分析需求并确定模块结构 func (t *RequirementAnalyzer) generateAIPrompt(userRequirement string) string { - prompt := fmt.Sprintf(`# AI需求逻辑梳理任务 + prompt := fmt.Sprintf(`# 智能需求分析与模块设计任务 ## 用户原始需求 %s -## AI任务要求 -请将上述用户需求梳理成清晰的逻辑步骤,格式要求: +## 核心任务 +你需要作为一个资深的系统架构师,深度分析用户需求,智能设计出完整的模块架构。 + +## 分析步骤 + +### 第一步:需求解构分析 +请仔细分析用户需求,识别出: +1. **核心业务实体**(如:用户、商品、订单、疫苗、宠物等) +2. **业务流程**(如:注册、购买、记录、管理等) +3. **数据关系**(实体间的关联关系) +4. **功能模块**(需要哪些独立的管理模块) + +### 第二步:模块架构设计 +基于需求分析,设计出模块架构,格式如下: + +**模块1:[模块名称]** +- 功能描述:[该模块的核心功能] +- 主要字段:[列出关键字段,注明数据类型] +- 关联关系:[与其他模块的关系,明确一对一/一对多] +- 字典需求:[需要哪些字典类型] + +**模块2:[模块名称]** +- 功能描述:[该模块的核心功能] +- 主要字段:[列出关键字段,注明数据类型] +- 关联关系:[与其他模块的关系] +- 字典需求:[需要哪些字典类型] -- **1. 第一步功能需要的字段** -- **2. 第二步功能需要的字段** -- **3. 第三步功能需要的字段** **...** -## 梳理要求 -- 将需求拆解为具体的功能步骤 -- 每个步骤用数字编号(1、2、3...) -- 步骤描述要清晰、具体、可执行 -- 按照业务逻辑顺序排列 -- 考虑数据流和用户操作流程 -- 如果分析的字段涉及到关联,请明确指出关联关系为一对一还是一对多 +### 第三步:字段详细设计 +为每个模块详细设计字段: + +#### 模块1字段清单: +- 字段名1 (数据类型) - 字段描述 [是否必填] [关联信息/字典类型] +- 字段名2 (数据类型) - 字段描述 [是否必填] [关联信息/字典类型] +- ... + +#### 模块2字段清单: +- 字段名1 (数据类型) - 字段描述 [是否必填] [关联信息/字典类型] +- ... + +## 智能分析指导原则 + +### 模块拆分原则 +1. **单一职责**:每个模块只负责一个核心业务实体 +2. **数据完整性**:相关数据应该在同一模块中 +3. **业务独立性**:模块应该能够独立完成特定业务功能 +4. **扩展性考虑**:为未来功能扩展预留空间 + +### 字段设计原则 +1. **必要性**:只包含业务必需的字段 +2. **规范性**:遵循数据库设计规范 +3. **关联性**:正确识别实体间关系 +4. **字典化**:状态、类型等枚举值使用字典 + +### 关联关系识别 +- **一对一**:一个实体只能关联另一个实体的一个记录 +- **一对多**:一个实体可以关联另一个实体的多个记录 +- **多对多**:通过中间表实现复杂关联 + +## 特殊场景处理 + +### 复杂实体识别 +当用户提到某个概念时,要判断它是否需要独立模块: +- **字典处理**:简单的常见的状态、类型(如:开关、性别、完成状态等) +- **独立模块**:复杂实体(如:疫苗管理、宠物档案、注射记录) + +## 输出要求 + +### 必须包含的信息 +1. **模块数量**:明确需要几个模块 +2. **模块关系图**:用文字描述模块间关系 +3. **核心字段**:每个模块的关键字段(至少5-10个) +4. **数据类型**:string、int、bool、time.Time、float64等 +5. **关联设计**:明确哪些字段是关联字段 +6. **字典需求**:列出需要创建的字典类型 + +### 严格遵循用户输入 +- 如果用户提供了具体字段,**必须使用**用户提供的字段 +- 如果用户提供了SQL文件,**严格按照**SQL结构设计 +- **不要**随意发散,**不要**添加用户未提及的功能 +--- -## 后续流程 -梳理完成后,请使用 gva_analyzer 工具获取当前系统的模块信息,根据模块信息判断是否需要创建新的模块。 -如果需要创建新的模块和结构体,需要使用 gva_execution 工具进行代码生成。 -如果不需要创建新的模块和结构体,则返回当前的依赖路径,供非MCP的AI创建代码逻辑使用。 +**现在请开始深度分析用户需求:"%s"** -现在请开始梳理用户需求:"%s"`, userRequirement, userRequirement) +请按照上述框架进行系统性分析,确保输出的模块设计既满足当前需求,又具备良好的扩展性。`, userRequirement, userRequirement) return prompt } From 55afc0b2cdc492723cd0f957c0587488dd7f64e8 Mon Sep 17 00:00:00 2001 From: Azir-11 <2075125282@qq.com> Date: Sat, 20 Sep 2025 17:27:05 +0800 Subject: [PATCH 06/33] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0.gitignore?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=AF=B9=E6=9C=AC=E5=9C=B0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6=E7=9A=84=E5=BF=BD=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a25df7bb36..4707851efa 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ rm_file/ /server/server /server/latest_log /server/__debug_bin* +/server/*.local.yaml server/uploads/ *.iml From a4cccb88e887fe419bd9bc1794334c3c42b4205d Mon Sep 17 00:00:00 2001 From: Azir-11 <2075125282@qq.com> Date: Sat, 20 Sep 2025 21:48:46 +0800 Subject: [PATCH 07/33] =?UTF-8?q?feat(logo):=20=E6=96=B0=E5=A2=9ELogo?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=B9=B6=E5=9C=A8=E5=A4=9A=E4=B8=AA=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E4=B8=AD=E6=9B=BF=E6=8D=A2=E5=8E=9F=E6=9C=89logo?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/logo/index.vue | 82 +++++++++++++++++ web/src/core/config.js | 1 - web/src/view/layout/header/index.vue | 8 +- .../layout/setting/modules/general/index.vue | 91 +++++++------------ web/src/view/login/index.vue | 3 +- 5 files changed, 122 insertions(+), 63 deletions(-) create mode 100644 web/src/components/logo/index.vue diff --git a/web/src/components/logo/index.vue b/web/src/components/logo/index.vue new file mode 100644 index 0000000000..88c335b5a1 --- /dev/null +++ b/web/src/components/logo/index.vue @@ -0,0 +1,82 @@ + + + diff --git a/web/src/core/config.js b/web/src/core/config.js index 63a89edc66..606d351b6c 100644 --- a/web/src/core/config.js +++ b/web/src/core/config.js @@ -7,7 +7,6 @@ const greenText = (text) => `\x1b[32m${text}\x1b[0m` const config = { appName: 'Gin-Vue-Admin', - appLogo: 'logo.png', showViteLogo: true, logs: [] } diff --git a/web/src/view/layout/header/index.vue b/web/src/view/layout/header/index.vue index bf7b5be1c5..d709424ba9 100644 --- a/web/src/view/layout/header/index.vue +++ b/web/src/view/layout/header/index.vue @@ -13,11 +13,7 @@ :class="isMobile ? '' : 'min-w-48'" @click="router.push({ path: '/' })" > - +
-
+
-
+
🔄
@@ -59,19 +61,18 @@

将所有设置恢复为默认值

- + @click="handleResetConfig"> 重置配置
-
+
-
+
📤
@@ -79,20 +80,19 @@

导出当前配置为 JSON 文件

- + @click="handleExportConfig"> 导出配置
-
+
-
+
📥
@@ -100,18 +100,10 @@

从 JSON 文件导入配置

- - + + 导入配置 @@ -131,13 +123,9 @@
-
- Gin-Vue-Admin Logo +
+

Gin-Vue-Admin

@@ -145,21 +133,15 @@ 基于 Vue3 + Gin 的全栈开发基础平台,提供完整的后台管理解决方案

@@ -172,10 +154,11 @@ diff --git a/web/src/view/example/upload/upload.vue b/web/src/view/example/upload/upload.vue index eae676a0eb..fdc86825ab 100644 --- a/web/src/view/example/upload/upload.vue +++ b/web/src/view/example/upload/upload.vue @@ -1,26 +1,46 @@