package mcpTool import ( "context" "encoding/json" "errors" "fmt" "os" "path/filepath" "strings" "time" "unicode" "git.echol.cn/loser/lckt/global" common "git.echol.cn/loser/lckt/model/common/request" model "git.echol.cn/loser/lckt/model/system" "git.echol.cn/loser/lckt/model/system/request" "git.echol.cn/loser/lckt/service" systemService "git.echol.cn/loser/lckt/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"` 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"` } // 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 必须是小驼峰命名,例:userName)", "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),JSON类型(array,json,file,pictures)请保持为空他们不可以设置默认值", "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' 执行创建操作(支持批量创建多个模块)"), ), 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'"), ), ) } // 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) default: return nil, errors.New("无效的操作:action 必须是 'analyze'、'confirm' 或 'execute'") } } // 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标签(string 必须是小驼峰命名,例:userName)", "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 } // 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) 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", string(resultJSON), 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 } // 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 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) } // 确保字段名首字母大写 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 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), } // 无论如何都先构建目录结构信息,确保paths始终返回 result.Paths = t.buildDirectoryStructure(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 errors []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)) } } 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 { errors = append(errors, fmt.Sprintf("删除%s失败: %v", path, err)) } } } if len(errors) > 0 { return fmt.Errorf("删除过程中出现错误: %s", strings.Join(errors, "; ")) } 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 }