gva_auto_generate.go 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755
  1. package mcpTool
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "os"
  8. "path/filepath"
  9. "strings"
  10. "time"
  11. "unicode"
  12. "github.com/flipped-aurora/gin-vue-admin/server/global"
  13. common "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
  14. model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
  15. "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
  16. "github.com/flipped-aurora/gin-vue-admin/server/service"
  17. systemService "github.com/flipped-aurora/gin-vue-admin/server/service/system"
  18. "github.com/mark3labs/mcp-go/mcp"
  19. "gorm.io/gorm"
  20. )
  21. func init() {
  22. RegisterTool(&AutomationModuleAnalyzer{})
  23. }
  24. type AutomationModuleAnalyzer struct{}
  25. // ModuleInfo 模块信息
  26. type ModuleInfo struct {
  27. ID uint `json:"id"`
  28. PackageName string `json:"packageName"`
  29. Label string `json:"label"`
  30. Desc string `json:"desc"`
  31. Template string `json:"template"` // "plugin" 或 "package"
  32. Module string `json:"module"`
  33. }
  34. // HistoryInfo 历史记录信息
  35. type HistoryInfo struct {
  36. ID uint `json:"id"`
  37. StructName string `json:"structName"`
  38. TableName string `json:"tableName"`
  39. PackageName string `json:"packageName"`
  40. BusinessDB string `json:"businessDB"`
  41. Description string `json:"description"`
  42. Abbreviation string `json:"abbreviation"`
  43. CreatedAt string `json:"createdAt"`
  44. }
  45. // PredesignedModuleInfo 预设计模块信息
  46. type PredesignedModuleInfo struct {
  47. PackageName string `json:"packageName"`
  48. PackageType string `json:"packageType"` // "plugin" 或 "package"
  49. ModuleName string `json:"moduleName"`
  50. Path string `json:"path"`
  51. Modules []string `json:"modules"` // 包含的模块列表(如api、model、service等)
  52. Description string `json:"description"`
  53. StructName string `json:"structName,omitempty"` // 主要结构体名称
  54. }
  55. // AnalysisResponse 分析响应
  56. type AnalysisResponse struct {
  57. Packages []ModuleInfo `json:"packages"`
  58. History []HistoryInfo `json:"history"`
  59. PredesignedModules []PredesignedModuleInfo `json:"predesignedModules"`
  60. Message string `json:"message"`
  61. }
  62. // ExecutionPlan 执行计划 - 支持批量创建
  63. type ExecutionPlan struct {
  64. PackageName string `json:"packageName"`
  65. PackageType string `json:"packageType"` // "plugin" 或 "package"
  66. NeedCreatedPackage bool `json:"needCreatedPackage"`
  67. NeedCreatedModules bool `json:"needCreatedModules"`
  68. PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"`
  69. ModulesInfo []*request.AutoCode `json:"modulesInfo,omitempty"` // 改为数组支持多个模块
  70. Paths map[string]string `json:"paths,omitempty"`
  71. }
  72. // ExecutionResult 执行结果
  73. type ExecutionResult struct {
  74. Success bool `json:"success"`
  75. Message string `json:"message"`
  76. PackageID uint `json:"packageId,omitempty"`
  77. HistoryID uint `json:"historyId,omitempty"`
  78. Paths map[string]string `json:"paths,omitempty"`
  79. NextActions []string `json:"nextActions,omitempty"`
  80. }
  81. // ConfirmationRequest 确认请求结构
  82. type ConfirmationRequest struct {
  83. PackageName string `json:"packageName"`
  84. ModuleName string `json:"moduleName"`
  85. NeedCreatedPackage bool `json:"needCreatedPackage"`
  86. NeedCreatedModules bool `json:"needCreatedModules"`
  87. PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"`
  88. ModulesInfo *request.AutoCode `json:"modulesInfo,omitempty"`
  89. }
  90. // ConfirmationResponse 确认响应结构
  91. type ConfirmationResponse struct {
  92. Message string `json:"message"`
  93. PackageConfirm bool `json:"packageConfirm"`
  94. ModulesConfirm bool `json:"modulesConfirm"`
  95. CanProceed bool `json:"canProceed"`
  96. ConfirmationKey string `json:"confirmationKey"`
  97. }
  98. // New 返回工具注册信息
  99. func (t *AutomationModuleAnalyzer) New() mcp.Tool {
  100. return mcp.NewTool("gva_auto_generate",
  101. mcp.WithDescription(`**🔧 核心执行工具:接收requirement_analyzer分析结果,执行具体的模块创建操作**
  102. **工作流位置:**
  103. - **第二优先级**:在requirement_analyzer之后使用
  104. - **接收输入**:来自requirement_analyzer的1xxx2xxx格式分析结果
  105. - **执行操作**:根据分析结果创建完整模块、包、功能模块
  106. **批量创建功能:**
  107. - 支持在单个ExecutionPlan中创建多个模块
  108. - modulesInfo字段为数组,可包含多个模块配置
  109. - 一次性处理多个模块的创建和字典生成
  110. - 与requirement_analyzer配合实现完整工作流
  111. 分步骤分析自动化模块:1) 分析现有模块信息供AI选择 2) 请求用户确认 3) 根据确认结果执行创建操作
  112. **新功能:自动字典创建**
  113. - 当结构体字段使用了字典类型(dictType不为空)时,系统会自动检查字典是否存在
  114. - 如果字典不存在,会自动创建对应的字典及默认的字典详情项
  115. - 字典创建包括:字典主表记录和默认的选项值(选项1、选项2等)
  116. **推荐工作流:**
  117. 1. 用户提出需求 → requirement_analyzer(最高优先级)
  118. 2. AI分析需求为1xxx2xxx格式 → gva_auto_generate(执行创建)
  119. 3. 创建完成后,根据需要使用其他辅助工具
  120. **重要限制:**
  121. - 当needCreatedModules=true时,模块创建会自动生成API和菜单,因此不应再调用api_creator和menu_creator工具
  122. - 只有在单独创建API或菜单(不涉及模块创建)时才使用api_creator和menu_creator工具
  123. 重要:ExecutionPlan结构体格式要求(支持批量创建):
  124. {
  125. "packageName": "包名(string)",
  126. "packageType": "package或plugin(string)",
  127. "needCreatedPackage": "是否需要创建包(bool)",
  128. "needCreatedModules": "是否需要创建模块(bool)",
  129. "packageInfo": {
  130. "desc": "描述(string)",
  131. "label": "展示名(string)",
  132. "template": "package或plugin(string)",
  133. "packageName": "包名(string)"
  134. },
  135. "modulesInfo": [{
  136. "package": "包名(string)",
  137. "tableName": "数据库表名(string)",
  138. "businessDB": "业务数据库(string)",
  139. "structName": "结构体名(string)",
  140. "packageName": "文件名称(string)",
  141. "description": "中文描述(string)",
  142. "abbreviation": "简称(string)",
  143. "humpPackageName": "文件名称 一般是结构体名的小驼峰(string)",
  144. "gvaModel": "是否使用GVA模型(bool) 固定为true 后续不需要创建ID created_at deleted_at updated_at",
  145. "autoMigrate": "是否自动迁移(bool)",
  146. "autoCreateResource": "是否创建资源(bool)",
  147. "autoCreateApiToSql": "是否创建API(bool)",
  148. "autoCreateMenuToSql": "是否创建菜单(bool)",
  149. "autoCreateBtnAuth": "是否创建按钮权限(bool)",
  150. "onlyTemplate": "是否仅模板(bool)",
  151. "isTree": "是否树形结构(bool)",
  152. "treeJson": "树形JSON字段(string)",
  153. "isAdd": "是否新增(bool) 固定为false",
  154. "generateWeb": "是否生成前端(bool)",
  155. "generateServer": "是否生成后端(bool)",
  156. "fields": [{
  157. "fieldName": "字段名(string)必须大写开头",
  158. "fieldDesc": "字段描述(string)",
  159. "fieldType": "字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组)",
  160. "fieldJson": "JSON标签(string 必须是小驼峰命名,例:userName)",
  161. "dataTypeLong": "数据长度(string)",
  162. "comment": "注释(string)",
  163. "columnName": "数据库列名(string)",
  164. "fieldSearchType": "搜索类型:=/>/</>=/<=/NOT BETWEEN/LIKE/BETWEEN/IN/NOT IN等(string)",
  165. "fieldSearchHide": "是否隐藏搜索(bool)",
  166. "dictType": "字典类型(string)",
  167. "form": "表单显示(bool)",
  168. "table": "表格显示(bool)",
  169. "desc": "详情显示(bool)",
  170. "excel": "导入导出(bool)",
  171. "require": "是否必填(bool)",
  172. "defaultValue": "默认值(string),JSON类型(array,json,file,pictures)请保持为空他们不可以设置默认值",
  173. "errorText": "错误提示(string)",
  174. "clearable": "是否可清空(bool)",
  175. "sort": "是否排序(bool)",
  176. "primaryKey": "是否主键(bool)",
  177. "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此处填写为空",
  178. "checkDataSource": "是否检查数据源(bool) - 启用后会验证关联表的存在性",
  179. "fieldIndexType": "索引类型(string)"
  180. }]
  181. }, {
  182. "package": "包名(string)",
  183. "tableName": "第二个模块的表名(string)",
  184. "structName": "第二个模块的结构体名(string)",
  185. "description": "第二个模块的描述(string)",
  186. "...": "更多模块配置..."
  187. }]
  188. }
  189. 注意:
  190. 1. needCreatedPackage=true时packageInfo必需
  191. 2. needCreatedModules=true时modulesInfo必需
  192. 3. packageType只能是"package"或"plugin"
  193. 4. 字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组)
  194. 5. 搜索类型支持:=,!=,>,>=,<,<=,NOT BETWEEN/LIKE/BETWEEN/IN/NOT IN
  195. 6. gvaModel=true时自动包含ID,CreatedAt,UpdatedAt,DeletedAt字段
  196. 7. **重要**:当gvaModel=false时,必须有一个字段的primaryKey=true,否则会导致PrimaryField为nil错误
  197. 8. **重要**:当gvaModel=true时,系统会自动设置ID字段为主键,无需手动设置primaryKey=true
  198. 9. 智能字典创建功能:当字段使用字典类型(DictType)时,系统会:
  199. - 自动检查字典是否存在,如果不存在则创建字典
  200. - 根据字典类型和字段描述智能生成默认选项,支持状态、性别、类型、等级、优先级、审批、角色、布尔值、订单、颜色、尺寸等常见场景
  201. - 为无法识别的字典类型提供通用默认选项
  202. 10. **模块关联配置**:当需要配置模块间的关联关系时,使用dataSource字段:
  203. - **dbName**: 关联的数据库名称
  204. - **table**: 关联的表名
  205. - **label**: 用于显示的字段名(如name、title等)
  206. - **value**: 用于存储的值字段名(通常是id)
  207. - **association**: 关联关系类型(1=一对一关联,2=一对多关联)
  208. - **hasDeletedAt**: 关联表是否有软删除字段
  209. - **checkDataSource**: 设为true时会验证关联表的存在性
  210. - 示例:{"dbName":"gva","table":"sys_users","label":"username","value":"id","association":2,"hasDeletedAt":true}`),
  211. mcp.WithString("action",
  212. mcp.Required(),
  213. mcp.Description("执行操作:'analyze' 分析现有模块信息,'confirm' 请求用户确认创建,'execute' 执行创建操作(支持批量创建多个模块)"),
  214. ),
  215. mcp.WithString("requirement",
  216. mcp.Description("用户需求描述(action=analyze时必需)"),
  217. ),
  218. mcp.WithObject("executionPlan",
  219. mcp.Description("执行计划(action=confirm或execute时必需,必须严格按照上述格式提供完整的JSON对象)"),
  220. ),
  221. mcp.WithString("packageConfirm",
  222. mcp.Description("用户对创建包的确认(action=execute时,如果需要创建包则必需):'yes' 或 'no'"),
  223. ),
  224. mcp.WithString("modulesConfirm",
  225. mcp.Description("用户对创建模块的确认(action=execute时,如果需要创建模块则必需):'yes' 或 'no'"),
  226. ),
  227. )
  228. }
  229. // scanPredesignedModules 扫描预设计的模块
  230. func (t *AutomationModuleAnalyzer) scanPredesignedModules() ([]PredesignedModuleInfo, error) {
  231. var predesignedModules []PredesignedModuleInfo
  232. // 获取autocode配置路径
  233. if global.GVA_CONFIG.AutoCode.Root == "" {
  234. return predesignedModules, nil // 配置不存在时返回空列表,不报错
  235. }
  236. serverPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server)
  237. // 扫描plugin目录下的各个插件模块
  238. pluginPath := filepath.Join(serverPath, "plugin")
  239. if pluginModules, err := t.scanPluginModules(pluginPath); err == nil {
  240. predesignedModules = append(predesignedModules, pluginModules...)
  241. }
  242. // 扫描model目录下的各个包模块
  243. modelPath := filepath.Join(serverPath, "model")
  244. if packageModules, err := t.scanPackageModules(modelPath); err == nil {
  245. predesignedModules = append(predesignedModules, packageModules...)
  246. }
  247. return predesignedModules, nil
  248. }
  249. // scanPluginModules 扫描plugin目录下的各个插件模块
  250. func (t *AutomationModuleAnalyzer) scanPluginModules(pluginPath string) ([]PredesignedModuleInfo, error) {
  251. var modules []PredesignedModuleInfo
  252. if _, err := os.Stat(pluginPath); os.IsNotExist(err) {
  253. return modules, nil
  254. }
  255. entries, err := os.ReadDir(pluginPath)
  256. if err != nil {
  257. return modules, err
  258. }
  259. for _, entry := range entries {
  260. if !entry.IsDir() {
  261. continue
  262. }
  263. pluginName := entry.Name()
  264. pluginDir := filepath.Join(pluginPath, pluginName)
  265. // 扫描插件下的model目录,查找具体的模块文件
  266. modelDir := filepath.Join(pluginDir, "model")
  267. if _, err := os.Stat(modelDir); err == nil {
  268. if pluginModules, err := t.scanModuleFiles(modelDir, pluginName, "plugin"); err == nil {
  269. modules = append(modules, pluginModules...)
  270. }
  271. }
  272. }
  273. return modules, nil
  274. }
  275. // scanPackageModules 扫描model目录下的各个包模块
  276. func (t *AutomationModuleAnalyzer) scanPackageModules(modelPath string) ([]PredesignedModuleInfo, error) {
  277. var modules []PredesignedModuleInfo
  278. if _, err := os.Stat(modelPath); os.IsNotExist(err) {
  279. return modules, nil
  280. }
  281. entries, err := os.ReadDir(modelPath)
  282. if err != nil {
  283. return modules, err
  284. }
  285. for _, entry := range entries {
  286. if !entry.IsDir() {
  287. continue
  288. }
  289. packageName := entry.Name()
  290. // 跳过一些系统目录
  291. if packageName == "common" || packageName == "request" || packageName == "response" {
  292. continue
  293. }
  294. packageDir := filepath.Join(modelPath, packageName)
  295. // 扫描包目录下的模块文件
  296. if packageModules, err := t.scanModuleFiles(packageDir, packageName, "package"); err == nil {
  297. modules = append(modules, packageModules...)
  298. }
  299. }
  300. return modules, nil
  301. }
  302. // scanModuleFiles 扫描目录下的Go文件,识别具体的模块
  303. func (t *AutomationModuleAnalyzer) scanModuleFiles(dir, packageName, packageType string) ([]PredesignedModuleInfo, error) {
  304. var modules []PredesignedModuleInfo
  305. entries, err := os.ReadDir(dir)
  306. if err != nil {
  307. return modules, err
  308. }
  309. for _, entry := range entries {
  310. if entry.IsDir() {
  311. continue
  312. }
  313. fileName := entry.Name()
  314. if !strings.HasSuffix(fileName, ".go") {
  315. continue
  316. }
  317. // 跳过一些非模块文件
  318. if strings.HasSuffix(fileName, "_test.go") ||
  319. fileName == "enter.go" ||
  320. fileName == "request.go" ||
  321. fileName == "response.go" {
  322. continue
  323. }
  324. filePath := filepath.Join(dir, fileName)
  325. moduleName := strings.TrimSuffix(fileName, ".go")
  326. // 分析模块文件,提取结构体信息
  327. if moduleInfo, err := t.analyzeModuleFile(filePath, packageName, moduleName, packageType); err == nil {
  328. modules = append(modules, *moduleInfo)
  329. }
  330. }
  331. return modules, nil
  332. }
  333. // analyzeModuleFile 分析具体的模块文件
  334. func (t *AutomationModuleAnalyzer) analyzeModuleFile(filePath, packageName, moduleName, packageType string) (*PredesignedModuleInfo, error) {
  335. content, err := os.ReadFile(filePath)
  336. if err != nil {
  337. return nil, err
  338. }
  339. fileContent := string(content)
  340. // 提取结构体名称和描述
  341. structNames := t.extractStructNames(fileContent)
  342. description := t.extractModuleDescription(fileContent, moduleName)
  343. // 确定主要结构体名称
  344. mainStruct := moduleName
  345. if len(structNames) > 0 {
  346. // 优先选择与文件名相关的结构体
  347. for _, structName := range structNames {
  348. if strings.Contains(strings.ToLower(structName), strings.ToLower(moduleName)) {
  349. mainStruct = structName
  350. break
  351. }
  352. }
  353. if mainStruct == moduleName && len(structNames) > 0 {
  354. mainStruct = structNames[0] // 如果没有匹配的,使用第一个
  355. }
  356. }
  357. return &PredesignedModuleInfo{
  358. PackageName: packageName,
  359. PackageType: packageType,
  360. ModuleName: moduleName,
  361. Path: filePath,
  362. Modules: structNames,
  363. Description: description,
  364. StructName: mainStruct,
  365. }, nil
  366. }
  367. // extractStructNames 从文件内容中提取结构体名称
  368. func (t *AutomationModuleAnalyzer) extractStructNames(content string) []string {
  369. var structNames []string
  370. lines := strings.Split(content, "\n")
  371. for _, line := range lines {
  372. line = strings.TrimSpace(line)
  373. if strings.HasPrefix(line, "type ") && strings.Contains(line, " struct") {
  374. // 提取结构体名称
  375. parts := strings.Fields(line)
  376. if len(parts) >= 3 && parts[2] == "struct" {
  377. structNames = append(structNames, parts[1])
  378. }
  379. }
  380. }
  381. return structNames
  382. }
  383. // extractModuleDescription 从文件内容中提取模块描述
  384. func (t *AutomationModuleAnalyzer) extractModuleDescription(content, moduleName string) string {
  385. lines := strings.Split(content, "\n")
  386. // 查找package注释
  387. for i, line := range lines {
  388. line = strings.TrimSpace(line)
  389. if strings.HasPrefix(line, "package ") {
  390. // 向上查找注释
  391. for j := i - 1; j >= 0; j-- {
  392. commentLine := strings.TrimSpace(lines[j])
  393. if strings.HasPrefix(commentLine, "//") {
  394. comment := strings.TrimSpace(strings.TrimPrefix(commentLine, "//"))
  395. if comment != "" && len(comment) > 5 {
  396. return comment
  397. }
  398. } else if commentLine != "" {
  399. break
  400. }
  401. }
  402. break
  403. }
  404. }
  405. // 查找结构体注释
  406. for i, line := range lines {
  407. line = strings.TrimSpace(line)
  408. if strings.HasPrefix(line, "type ") && strings.Contains(line, " struct") {
  409. // 向上查找注释
  410. for j := i - 1; j >= 0; j-- {
  411. commentLine := strings.TrimSpace(lines[j])
  412. if strings.HasPrefix(commentLine, "//") {
  413. comment := strings.TrimSpace(strings.TrimPrefix(commentLine, "//"))
  414. if comment != "" && len(comment) > 5 {
  415. return comment
  416. }
  417. } else if commentLine != "" {
  418. break
  419. }
  420. }
  421. break
  422. }
  423. }
  424. return fmt.Sprintf("预设计的模块:%s", moduleName)
  425. }
  426. // Handle 处理工具调用
  427. func (t *AutomationModuleAnalyzer) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  428. action, ok := request.GetArguments()["action"].(string)
  429. if !ok || action == "" {
  430. return nil, errors.New("参数错误:action 必须是非空字符串")
  431. }
  432. switch action {
  433. case "analyze":
  434. return t.handleAnalyze(ctx, request)
  435. case "confirm":
  436. return t.handleConfirm(ctx, request)
  437. case "execute":
  438. return t.handleExecute(ctx, request)
  439. default:
  440. return nil, errors.New("无效的操作:action 必须是 'analyze'、'confirm' 或 'execute'")
  441. }
  442. }
  443. // handleAnalyze 处理分析请求
  444. func (t *AutomationModuleAnalyzer) handleAnalyze(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  445. requirement, ok := request.GetArguments()["requirement"].(string)
  446. if !ok || requirement == "" {
  447. return nil, errors.New("参数错误:requirement 必须是非空字符串")
  448. }
  449. // 检测用户是否想要创建插件
  450. suggestedType, isPlugin, confidence := t.detectPluginIntent(requirement)
  451. pluginDetectionMsg := ""
  452. if isPlugin {
  453. pluginDetectionMsg = fmt.Sprintf("\n\n🔍 **插件检测结果**:检测到用户想要创建插件(置信度:%s)\n⚠️ **重要提醒**:当用户提到插件时,packageType和template字段都必须设置为 \"plugin\",不能使用 \"package\"!", confidence)
  454. } else {
  455. pluginDetectionMsg = fmt.Sprintf("\n\n🔍 **类型检测结果**:建议使用 %s 类型", suggestedType)
  456. }
  457. // 从数据库获取所有自动化包信息
  458. var packages []model.SysAutoCodePackage
  459. if err := global.GVA_DB.Find(&packages).Error; err != nil {
  460. return nil, fmt.Errorf("获取包信息失败: %v", err)
  461. }
  462. // 从数据库获取所有历史记录
  463. var histories []model.SysAutoCodeHistory
  464. if err := global.GVA_DB.Find(&histories).Error; err != nil {
  465. return nil, fmt.Errorf("获取历史记录失败: %v", err)
  466. }
  467. // 转换包信息并检查空文件夹
  468. var moduleInfos []ModuleInfo
  469. var validPackages []model.SysAutoCodePackage
  470. var emptyPackageIDs []uint
  471. var emptyPackageNames []string
  472. for _, pkg := range packages {
  473. // 检查包对应的文件夹是否为空
  474. isEmpty, err := t.isPackageFolderEmpty(pkg.PackageName, pkg.Template)
  475. if err != nil {
  476. global.GVA_LOG.Warn(fmt.Sprintf("检查包 %s 文件夹失败: %v", pkg.PackageName, err))
  477. // 如果检查失败,仍然保留该包
  478. validPackages = append(validPackages, pkg)
  479. continue
  480. }
  481. if isEmpty {
  482. // 记录需要删除的包ID和包名
  483. emptyPackageIDs = append(emptyPackageIDs, pkg.ID)
  484. emptyPackageNames = append(emptyPackageNames, pkg.PackageName)
  485. global.GVA_LOG.Info(fmt.Sprintf("发现空包文件夹: %s,将删除数据库记录和文件夹", pkg.PackageName))
  486. // 删除空文件夹
  487. if err := t.removeEmptyPackageFolder(pkg.PackageName, pkg.Template); err != nil {
  488. global.GVA_LOG.Warn(fmt.Sprintf("删除空包文件夹 %s 失败: %v", pkg.PackageName, err))
  489. }
  490. } else {
  491. // 文件夹不为空,保留该包
  492. validPackages = append(validPackages, pkg)
  493. }
  494. }
  495. // 批量删除空包的数据库记录
  496. if len(emptyPackageIDs) > 0 {
  497. if err := global.GVA_DB.Where("id IN ?", emptyPackageIDs).Delete(&model.SysAutoCodePackage{}).Error; err != nil {
  498. global.GVA_LOG.Warn(fmt.Sprintf("删除空包数据库记录失败: %v", err))
  499. } else {
  500. global.GVA_LOG.Info(fmt.Sprintf("成功删除 %d 个空包的数据库记录", len(emptyPackageIDs)))
  501. }
  502. }
  503. // 转换有效的包信息
  504. for _, pkg := range validPackages {
  505. moduleInfos = append(moduleInfos, ModuleInfo{
  506. ID: pkg.ID,
  507. PackageName: pkg.PackageName,
  508. Label: pkg.Label,
  509. Desc: pkg.Desc,
  510. Template: pkg.Template,
  511. Module: pkg.Module,
  512. })
  513. }
  514. // 删除与空包相关的历史记录
  515. var emptyHistoryIDs []uint
  516. if len(emptyPackageNames) > 0 {
  517. for _, history := range histories {
  518. for _, emptyPackageName := range emptyPackageNames {
  519. if history.Package == emptyPackageName {
  520. emptyHistoryIDs = append(emptyHistoryIDs, history.ID)
  521. break
  522. }
  523. }
  524. }
  525. // 清理相关的API和菜单记录
  526. if len(emptyHistoryIDs) > 0 {
  527. if err := t.cleanupRelatedApiAndMenus(emptyHistoryIDs); err != nil {
  528. global.GVA_LOG.Warn(fmt.Sprintf("清理空包相关API和菜单失败: %v", err))
  529. }
  530. }
  531. // 批量删除相关历史记录
  532. if len(emptyHistoryIDs) > 0 {
  533. if err := global.GVA_DB.Where("id IN ?", emptyHistoryIDs).Delete(&model.SysAutoCodeHistory{}).Error; err != nil {
  534. global.GVA_LOG.Warn(fmt.Sprintf("删除空包相关历史记录失败: %v", err))
  535. } else {
  536. global.GVA_LOG.Info(fmt.Sprintf("成功删除 %d 个空包相关的历史记录", len(emptyHistoryIDs)))
  537. }
  538. }
  539. }
  540. // 创建有效包名的映射,用于快速查找
  541. validPackageNames := make(map[string]bool)
  542. for _, pkg := range validPackages {
  543. validPackageNames[pkg.PackageName] = true
  544. }
  545. // 收集需要删除的脏历史记录ID(包名不在有效包列表中的历史记录)
  546. var dirtyHistoryIDs []uint
  547. for _, history := range histories {
  548. if !validPackageNames[history.Package] {
  549. dirtyHistoryIDs = append(dirtyHistoryIDs, history.ID)
  550. }
  551. }
  552. // 删除脏历史记录
  553. if len(dirtyHistoryIDs) > 0 {
  554. // 清理相关的API和菜单记录
  555. if err := t.cleanupRelatedApiAndMenus(dirtyHistoryIDs); err != nil {
  556. global.GVA_LOG.Warn(fmt.Sprintf("清理脏历史记录相关API和菜单失败: %v", err))
  557. }
  558. if err := global.GVA_DB.Where("id IN ?", dirtyHistoryIDs).Delete(&model.SysAutoCodeHistory{}).Error; err != nil {
  559. global.GVA_LOG.Warn(fmt.Sprintf("删除脏历史记录失败: %v", err))
  560. } else {
  561. global.GVA_LOG.Info(fmt.Sprintf("成功删除 %d 个脏历史记录(包名不在有效包列表中)", len(dirtyHistoryIDs)))
  562. }
  563. }
  564. // 转换有效的历史记录(只保留包名存在于有效包列表中的历史记录)
  565. var historyInfos []HistoryInfo
  566. for _, history := range histories {
  567. // 只保留包名存在于有效包列表中的历史记录
  568. if validPackageNames[history.Package] {
  569. historyInfos = append(historyInfos, HistoryInfo{
  570. ID: history.ID,
  571. StructName: history.StructName,
  572. TableName: history.TableName(),
  573. PackageName: history.Package,
  574. BusinessDB: history.BusinessDB,
  575. Description: history.Description,
  576. Abbreviation: history.Abbreviation,
  577. CreatedAt: history.CreatedAt.Format("2006-01-02 15:04:05"),
  578. })
  579. }
  580. }
  581. // 扫描预设计的模块
  582. allPredesignedModules, err := t.scanPredesignedModules()
  583. if err != nil {
  584. global.GVA_LOG.Warn("扫描预设计模块失败" + err.Error())
  585. allPredesignedModules = []PredesignedModuleInfo{} // 确保不为nil
  586. }
  587. // 过滤掉与已删除包相关的预设计模块
  588. var predesignedModules []PredesignedModuleInfo
  589. for _, module := range allPredesignedModules {
  590. isDeleted := false
  591. for _, emptyPackageName := range emptyPackageNames {
  592. if module.PackageName == emptyPackageName {
  593. isDeleted = true
  594. break
  595. }
  596. }
  597. // 只保留未被删除包的预设计模块
  598. if !isDeleted {
  599. predesignedModules = append(predesignedModules, module)
  600. }
  601. }
  602. // 构建分析结果消息
  603. var message string
  604. var deletionDetails []string
  605. // 收集删除信息
  606. if len(emptyHistoryIDs) > 0 {
  607. deletionDetails = append(deletionDetails, fmt.Sprintf("%d个空包相关历史记录", len(emptyHistoryIDs)))
  608. }
  609. if len(dirtyHistoryIDs) > 0 {
  610. deletionDetails = append(deletionDetails, fmt.Sprintf("%d个脏历史记录", len(dirtyHistoryIDs)))
  611. }
  612. if len(allPredesignedModules) > len(predesignedModules) {
  613. deletionDetails = append(deletionDetails, fmt.Sprintf("%d个相关预设计模块", len(allPredesignedModules)-len(predesignedModules)))
  614. }
  615. if len(emptyPackageNames) > 0 || len(deletionDetails) > 0 {
  616. var cleanupInfo string
  617. if len(emptyPackageNames) > 0 {
  618. cleanupInfo = fmt.Sprintf("检测到存在 %s 包但内容为空,我已经删除这些包的文件夹(包括model、api、service、router目录)和数据库记录", strings.Join(emptyPackageNames, "、"))
  619. }
  620. deletionInfo := ""
  621. if len(deletionDetails) > 0 {
  622. if cleanupInfo != "" {
  623. deletionInfo = fmt.Sprintf(",同时删除了%s", strings.Join(deletionDetails, "、"))
  624. } else {
  625. deletionInfo = fmt.Sprintf("检测到脏数据,已删除%s", strings.Join(deletionDetails, "、"))
  626. }
  627. }
  628. if cleanupInfo != "" {
  629. message = fmt.Sprintf("分析完成:获取到 %d 个有效包、%d 个历史记录和 %d 个预设计模块。%s%s,如果需要使用这些包名,需要重新创建。请AI根据需求选择合适的包和模块", len(validPackages), len(historyInfos), len(predesignedModules), cleanupInfo, deletionInfo)
  630. } else {
  631. message = fmt.Sprintf("分析完成:获取到 %d 个有效包、%d 个历史记录和 %d 个预设计模块。%s。请AI根据需求选择合适的包和模块", len(validPackages), len(historyInfos), len(predesignedModules), deletionInfo)
  632. }
  633. } else {
  634. message = fmt.Sprintf("分析完成:获取到 %d 个有效包、%d 个历史记录和 %d 个预设计模块,请AI根据需求选择合适的包和模块", len(validPackages), len(historyInfos), len(predesignedModules))
  635. }
  636. // 构建分析结果
  637. analysisResult := AnalysisResponse{
  638. Packages: moduleInfos,
  639. History: historyInfos,
  640. PredesignedModules: predesignedModules,
  641. Message: message,
  642. }
  643. resultJSON, err := json.MarshalIndent(analysisResult, "", " ")
  644. if err != nil {
  645. return nil, fmt.Errorf("序列化结果失败: %v", err)
  646. }
  647. return &mcp.CallToolResult{
  648. Content: []mcp.Content{
  649. mcp.TextContent{
  650. Type: "text",
  651. Text: fmt.Sprintf(`分析结果:
  652. %s
  653. 请AI根据用户需求:%s%s
  654. %s
  655. 分析现有的包、历史记录和预设计模块,然后构建ExecutionPlan结构体调用execute操作。
  656. **预设计模块说明**:
  657. - 预设计模块是已经存在于autocode路径下的package或plugin
  658. - 这些模块包含了预先设计好的代码结构,可以直接使用或作为参考
  659. - 如果用户需求与某个预设计模块匹配,可以考虑直接使用该模块或基于它进行扩展
  660. **字典选项生成说明**:
  661. - 当字段需要使用字典类型时(dictType不为空),请使用 generate_dictionary_options 工具
  662. - 该工具允许AI根据字段描述智能生成合适的字典选项
  663. - 调用示例:
  664. {
  665. "dictType": "user_status",
  666. "fieldDesc": "用户状态",
  667. "options": [
  668. {"label": "正常", "value": "1", "sort": 1},
  669. {"label": "禁用", "value": "0", "sort": 2}
  670. ],
  671. "dictName": "用户状态字典",
  672. "description": "用于管理用户账户状态的字典"
  673. }
  674. - 请在创建模块之前先创建所需的字典选项
  675. 重要提醒:ExecutionPlan必须严格按照以下格式(支持批量创建多个模块):
  676. {
  677. "packageName": "包名",
  678. "packageType": "package或plugin", // 当用户提到插件时必须是"plugin"
  679. "needCreatedPackage": true/false,
  680. "needCreatedModules": true/false,
  681. "packageInfo": {
  682. "desc": "描述",
  683. "label": "展示名",
  684. "template": "package或plugin", // 必须与packageType保持一致!
  685. "packageName": "包名"
  686. },
  687. "modulesInfo": [{
  688. "package": "包名",
  689. "tableName": "数据库表名",
  690. "businessDB": "",
  691. "structName": "结构体名",
  692. "packageName": "文件名称小驼峰模式 一般是结构体名的小驼峰",
  693. "description": "中文描述",
  694. "abbreviation": "简称 package和结构体简称不可同名 小驼峰模式",
  695. "humpPackageName": "一般是结构体名的下划线分割的小驼峰 例如:sys_user",
  696. "gvaModel": true,
  697. "autoMigrate": true,
  698. "autoCreateResource": true/false 用户不特地强调开启资源标识则为false,
  699. "autoCreateApiToSql": true,
  700. "autoCreateMenuToSql": true,
  701. "autoCreateBtnAuth": false/true 用户不特地强调创建按钮权限则为false,
  702. "onlyTemplate": false,
  703. "isTree": false,
  704. "treeJson": "",
  705. "isAdd": false,
  706. "generateWeb": true,
  707. "generateServer": true,
  708. "fields": [{
  709. "fieldName": "字段名(必须大写开头)",
  710. "fieldDesc": "字段描述",
  711. "fieldType": "字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组)",
  712. "fieldJson": "json标签(string 必须是小驼峰命名,例:userName)",
  713. "dataTypeLong": "长度",
  714. "comment": "注释",
  715. "columnName": "数据库列名",
  716. "fieldSearchType": "=/!=/>/</>=/<=/LIKE等 可以为空",
  717. "fieldSearchHide": true/false,
  718. "dictType": "",
  719. "form": true/false 是否前端创建输入,
  720. "table": true/false 是否前端表格展示,
  721. "desc": true/false 是否前端详情展示,
  722. "excel": true/false 是否导出Excel,
  723. "require": true/false 是否必填,
  724. "defaultValue": "",
  725. "errorText": "错误提示",
  726. "clearable": true,
  727. "sort": false,
  728. "primaryKey": "当gvaModel=false时必须有一个字段设为true(bool)",
  729. "dataSource": null,
  730. "checkDataSource": false,
  731. "fieldIndexType": ""
  732. }]
  733. }, {
  734. "package": "包名",
  735. "tableName": "第二个模块的表名",
  736. "structName": "第二个模块的结构体名",
  737. "description": "第二个模块的描述",
  738. "...": "更多模块配置..."
  739. }]
  740. }
  741. **重要提醒**:ExecutionPlan必须严格按照以下格式和验证规则:
  742. **插件类型检测规则(最重要)**:
  743. 1. 当用户需求中包含"插件"、"plugin"等关键词时,packageType和template都必须设置为"plugin"
  744. 2. packageType和template字段必须保持一致,不能一个是"package"另一个是"plugin"
  745. 3. 如果检测到插件意图但设置错误,会导致创建失败
  746. **字段完整性要求**:
  747. 4. 所有字符串字段都不能为空(包括packageName、moduleName、structName、tableName、description等)
  748. 5. 所有布尔字段必须明确设置true或false,不能使用默认值
  749. **主键设置规则(关键)**:
  750. 6. 当gvaModel=false时:fields数组中必须有且仅有一个字段的primaryKey=true
  751. 7. 当gvaModel=true时:系统自动创建ID主键,fields中所有字段的primaryKey都应为false
  752. 8. 主键设置错误会导致模板执行时PrimaryField为nil的严重错误!
  753. **包和模块创建逻辑**:
  754. 9. 如果存在可用的package,needCreatedPackage应设为false
  755. 10. 如果存在可用的modules,needCreatedModules应设为false
  756. 11. 如果发现合适的预设计模块,可以考虑基于它进行扩展而不是从零创建
  757. **字典创建流程**:
  758. 12. 如果字段需要字典类型,请先使用 generate_dictionary_options 工具创建字典
  759. 13. 字典创建成功后,再执行模块创建操作
  760. `, string(resultJSON), requirement, pluginDetectionMsg,
  761. func() string {
  762. if len(emptyPackageNames) > 0 {
  763. return fmt.Sprintf("**重要提醒**:检测到 %s 包存在但内容为空,已自动删除相关文件夹和数据库记录。如果用户需求涉及这些包名,请设置 needCreatedPackage=true 重新创建。", strings.Join(emptyPackageNames, "、"))
  764. }
  765. return ""
  766. }()),
  767. },
  768. },
  769. }, nil
  770. }
  771. // handleConfirm 处理确认请求
  772. func (t *AutomationModuleAnalyzer) handleConfirm(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  773. executionPlanData, ok := request.GetArguments()["executionPlan"]
  774. if !ok {
  775. return nil, errors.New("参数错误:executionPlan 必须提供")
  776. }
  777. // 解析执行计划
  778. planJSON, err := json.Marshal(executionPlanData)
  779. if err != nil {
  780. return nil, fmt.Errorf("解析执行计划失败: %v", err)
  781. }
  782. var plan ExecutionPlan
  783. err = json.Unmarshal(planJSON, &plan)
  784. if err != nil {
  785. return nil, fmt.Errorf("解析执行计划失败: %v\n\n请确保ExecutionPlan格式正确,参考工具描述中的结构体格式要求", err)
  786. }
  787. // 验证执行计划的完整性
  788. if err := t.validateExecutionPlan(&plan); err != nil {
  789. return nil, fmt.Errorf("执行计划验证失败: %v", err)
  790. }
  791. // 构建确认响应
  792. var moduleNames []string
  793. for _, moduleInfo := range plan.ModulesInfo {
  794. moduleNames = append(moduleNames, moduleInfo.StructName)
  795. }
  796. moduleNamesStr := strings.Join(moduleNames, "_")
  797. confirmResponse := ConfirmationResponse{
  798. Message: "请确认以下创建计划:",
  799. PackageConfirm: plan.NeedCreatedPackage,
  800. ModulesConfirm: plan.NeedCreatedModules,
  801. CanProceed: true,
  802. ConfirmationKey: fmt.Sprintf("%s_%s_%d", plan.PackageName, moduleNamesStr, time.Now().Unix()),
  803. }
  804. // 构建详细的确认信息
  805. var confirmDetails strings.Builder
  806. confirmDetails.WriteString(fmt.Sprintf("包名: %s\n", plan.PackageName))
  807. confirmDetails.WriteString(fmt.Sprintf("包类型: %s\n", plan.PackageType))
  808. if plan.NeedCreatedPackage && plan.PackageInfo != nil {
  809. confirmDetails.WriteString("\n需要创建包:\n")
  810. confirmDetails.WriteString(fmt.Sprintf(" - 包名: %s\n", plan.PackageInfo.PackageName))
  811. confirmDetails.WriteString(fmt.Sprintf(" - 标签: %s\n", plan.PackageInfo.Label))
  812. confirmDetails.WriteString(fmt.Sprintf(" - 描述: %s\n", plan.PackageInfo.Desc))
  813. confirmDetails.WriteString(fmt.Sprintf(" - 模板: %s\n", plan.PackageInfo.Template))
  814. }
  815. if plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 {
  816. confirmDetails.WriteString(fmt.Sprintf("\n需要创建模块 (共%d个):\n", len(plan.ModulesInfo)))
  817. for i, moduleInfo := range plan.ModulesInfo {
  818. confirmDetails.WriteString(fmt.Sprintf("\n模块 %d:\n", i+1))
  819. confirmDetails.WriteString(fmt.Sprintf(" - 结构体名: %s\n", moduleInfo.StructName))
  820. confirmDetails.WriteString(fmt.Sprintf(" - 表名: %s\n", moduleInfo.TableName))
  821. confirmDetails.WriteString(fmt.Sprintf(" - 描述: %s\n", moduleInfo.Description))
  822. confirmDetails.WriteString(fmt.Sprintf(" - 字段数量: %d\n", len(moduleInfo.Fields)))
  823. confirmDetails.WriteString(" - 字段列表:\n")
  824. for _, field := range moduleInfo.Fields {
  825. confirmDetails.WriteString(fmt.Sprintf(" * %s (%s): %s\n", field.FieldName, field.FieldType, field.FieldDesc))
  826. }
  827. }
  828. }
  829. resultJSON, err := json.MarshalIndent(confirmResponse, "", " ")
  830. if err != nil {
  831. return nil, fmt.Errorf("序列化结果失败: %v", err)
  832. }
  833. return &mcp.CallToolResult{
  834. Content: []mcp.Content{
  835. mcp.TextContent{
  836. Type: "text",
  837. Text: fmt.Sprintf("确认信息:\n\n%s\n\n详细信息:\n%s\n\n请用户确认是否继续执行此计划。如果确认,请使用execute操作并提供相应的确认参数。", string(resultJSON), confirmDetails.String()),
  838. },
  839. },
  840. }, nil
  841. }
  842. // handleExecute 处理执行请求
  843. func (t *AutomationModuleAnalyzer) handleExecute(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  844. executionPlanData, ok := request.GetArguments()["executionPlan"]
  845. if !ok {
  846. return nil, errors.New("参数错误:executionPlan 必须提供")
  847. }
  848. // 解析执行计划
  849. planJSON, err := json.Marshal(executionPlanData)
  850. if err != nil {
  851. return nil, fmt.Errorf("解析执行计划失败: %v", err)
  852. }
  853. var plan ExecutionPlan
  854. err = json.Unmarshal(planJSON, &plan)
  855. if err != nil {
  856. return nil, fmt.Errorf("解析执行计划失败: %v\n\n请确保ExecutionPlan格式正确,参考工具描述中的结构体格式要求", err)
  857. }
  858. // 验证执行计划的完整性
  859. if err := t.validateExecutionPlan(&plan); err != nil {
  860. return nil, fmt.Errorf("执行计划验证失败: %v", err)
  861. }
  862. // 检查用户确认
  863. if plan.NeedCreatedPackage {
  864. packageConfirm, ok := request.GetArguments()["packageConfirm"].(string)
  865. if !ok || (packageConfirm != "yes" && packageConfirm != "no") {
  866. return nil, errors.New("参数错误:当需要创建包时,packageConfirm 必须是 'yes' 或 'no'")
  867. }
  868. if packageConfirm == "no" {
  869. return &mcp.CallToolResult{
  870. Content: []mcp.Content{
  871. mcp.TextContent{
  872. Type: "text",
  873. Text: "用户取消了包的创建操作",
  874. },
  875. },
  876. }, nil
  877. }
  878. }
  879. if plan.NeedCreatedModules {
  880. modulesConfirm, ok := request.GetArguments()["modulesConfirm"].(string)
  881. if !ok || (modulesConfirm != "yes" && modulesConfirm != "no") {
  882. return nil, errors.New("参数错误:当需要创建模块时,modulesConfirm 必须是 'yes' 或 'no'")
  883. }
  884. if modulesConfirm == "no" {
  885. return &mcp.CallToolResult{
  886. Content: []mcp.Content{
  887. mcp.TextContent{
  888. Type: "text",
  889. Text: "用户取消了模块的创建操作",
  890. },
  891. },
  892. }, nil
  893. }
  894. }
  895. // 执行创建操作
  896. result := t.executeCreation(ctx, &plan)
  897. resultJSON, err := json.MarshalIndent(result, "", " ")
  898. if err != nil {
  899. return nil, fmt.Errorf("序列化结果失败: %v", err)
  900. }
  901. // 添加权限分配提醒
  902. permissionReminder := "\n\n⚠️ 重要提醒:\n" +
  903. "模块创建完成后,请前往【系统管理】->【角色管理】中为相关角色分配新创建的API和菜单权限," +
  904. "以确保用户能够正常访问新功能。\n" +
  905. "具体步骤:\n" +
  906. "1. 进入角色管理页面\n" +
  907. "2. 选择需要授权的角色\n" +
  908. "3. 在API权限中勾选新创建的API接口\n" +
  909. "4. 在菜单权限中勾选新创建的菜单项\n" +
  910. "5. 保存权限配置"
  911. return &mcp.CallToolResult{
  912. Content: []mcp.Content{
  913. mcp.TextContent{
  914. Type: "text",
  915. Text: fmt.Sprintf("执行结果:\n\n%s%s", string(resultJSON), permissionReminder),
  916. },
  917. },
  918. }, nil
  919. }
  920. // isSystemFunction 判断是否为系统功能
  921. func (t *AutomationModuleAnalyzer) isSystemFunction(requirement string) bool {
  922. systemKeywords := []string{
  923. "用户", "权限", "角色", "菜单", "系统", "配置", "字典", "参数",
  924. "user", "authority", "role", "menu", "system", "config", "dictionary",
  925. "认证", "授权", "登录", "注册", "JWT", "casbin",
  926. }
  927. requirementLower := strings.ToLower(requirement)
  928. for _, keyword := range systemKeywords {
  929. if strings.Contains(requirementLower, keyword) {
  930. return true
  931. }
  932. }
  933. return false
  934. }
  935. // buildDirectoryStructure 构建目录结构信息
  936. func (t *AutomationModuleAnalyzer) buildDirectoryStructure(plan *ExecutionPlan) map[string]string {
  937. paths := make(map[string]string)
  938. // 获取配置信息
  939. autoCodeConfig := global.GVA_CONFIG.AutoCode
  940. // 构建基础路径
  941. rootPath := autoCodeConfig.Root
  942. serverPath := autoCodeConfig.Server
  943. webPath := autoCodeConfig.Web
  944. moduleName := autoCodeConfig.Module
  945. // 如果计划中有包名,使用计划中的包名,否则使用默认
  946. packageName := "example"
  947. if plan.PackageInfo != nil && plan.PackageInfo.PackageName != "" {
  948. packageName = plan.PackageInfo.PackageName
  949. }
  950. // 如果计划中有模块信息,获取第一个模块的结构名作为默认值
  951. structName := "ExampleStruct"
  952. if len(plan.ModulesInfo) > 0 && plan.ModulesInfo[0].StructName != "" {
  953. structName = plan.ModulesInfo[0].StructName
  954. }
  955. // 根据包类型构建不同的路径结构
  956. packageType := plan.PackageType
  957. if packageType == "" {
  958. packageType = "package" // 默认为package模式
  959. }
  960. // 构建服务端路径
  961. if serverPath != "" {
  962. serverBasePath := fmt.Sprintf("%s/%s", rootPath, serverPath)
  963. if packageType == "plugin" {
  964. // Plugin 模式:所有文件都在 /plugin/packageName/ 目录下
  965. pluginBasePath := fmt.Sprintf("%s/plugin/%s", serverBasePath, packageName)
  966. // API 路径
  967. paths["api"] = fmt.Sprintf("%s/api", pluginBasePath)
  968. // Service 路径
  969. paths["service"] = fmt.Sprintf("%s/service", pluginBasePath)
  970. // Model 路径
  971. paths["model"] = fmt.Sprintf("%s/model", pluginBasePath)
  972. // Router 路径
  973. paths["router"] = fmt.Sprintf("%s/router", pluginBasePath)
  974. // Request 路径
  975. paths["request"] = fmt.Sprintf("%s/model/request", pluginBasePath)
  976. // Response 路径
  977. paths["response"] = fmt.Sprintf("%s/model/response", pluginBasePath)
  978. // Plugin 特有文件
  979. paths["plugin_main"] = fmt.Sprintf("%s/main.go", pluginBasePath)
  980. paths["plugin_config"] = fmt.Sprintf("%s/plugin.go", pluginBasePath)
  981. paths["plugin_initialize"] = fmt.Sprintf("%s/initialize", pluginBasePath)
  982. } else {
  983. // Package 模式:传统的目录结构
  984. // API 路径
  985. paths["api"] = fmt.Sprintf("%s/api/v1/%s", serverBasePath, packageName)
  986. // Service 路径
  987. paths["service"] = fmt.Sprintf("%s/service/%s", serverBasePath, packageName)
  988. // Model 路径
  989. paths["model"] = fmt.Sprintf("%s/model/%s", serverBasePath, packageName)
  990. // Router 路径
  991. paths["router"] = fmt.Sprintf("%s/router/%s", serverBasePath, packageName)
  992. // Request 路径
  993. paths["request"] = fmt.Sprintf("%s/model/%s/request", serverBasePath, packageName)
  994. // Response 路径
  995. paths["response"] = fmt.Sprintf("%s/model/%s/response", serverBasePath, packageName)
  996. }
  997. }
  998. // 构建前端路径(两种模式相同)
  999. if webPath != "" {
  1000. webBasePath := fmt.Sprintf("%s/%s", rootPath, webPath)
  1001. // Vue 页面路径
  1002. paths["vue_page"] = fmt.Sprintf("%s/view/%s", webBasePath, packageName)
  1003. // API 路径
  1004. paths["vue_api"] = fmt.Sprintf("%s/api/%s", webBasePath, packageName)
  1005. }
  1006. // 添加模块信息
  1007. paths["module"] = moduleName
  1008. paths["package_name"] = packageName
  1009. paths["package_type"] = packageType
  1010. paths["struct_name"] = structName
  1011. paths["root_path"] = rootPath
  1012. paths["server_path"] = serverPath
  1013. paths["web_path"] = webPath
  1014. return paths
  1015. }
  1016. // validateExecutionPlan 验证执行计划的完整性
  1017. func (t *AutomationModuleAnalyzer) validateExecutionPlan(plan *ExecutionPlan) error {
  1018. // 验证基本字段
  1019. if plan.PackageName == "" {
  1020. return errors.New("packageName 不能为空")
  1021. }
  1022. if plan.PackageType != "package" && plan.PackageType != "plugin" {
  1023. return errors.New("packageType 必须是 'package' 或 'plugin'")
  1024. }
  1025. // 验证packageType和template字段的一致性
  1026. if plan.NeedCreatedPackage && plan.PackageInfo != nil {
  1027. if plan.PackageType != plan.PackageInfo.Template {
  1028. return errors.New("packageType 和 packageInfo.template 必须保持一致")
  1029. }
  1030. }
  1031. // 验证包信息
  1032. if plan.NeedCreatedPackage {
  1033. if plan.PackageInfo == nil {
  1034. return errors.New("当 needCreatedPackage=true 时,packageInfo 不能为空")
  1035. }
  1036. if plan.PackageInfo.PackageName == "" {
  1037. return errors.New("packageInfo.packageName 不能为空")
  1038. }
  1039. if plan.PackageInfo.Template != "package" && plan.PackageInfo.Template != "plugin" {
  1040. return errors.New("packageInfo.template 必须是 'package' 或 'plugin'")
  1041. }
  1042. if plan.PackageInfo.Label == "" {
  1043. return errors.New("packageInfo.label 不能为空")
  1044. }
  1045. if plan.PackageInfo.Desc == "" {
  1046. return errors.New("packageInfo.desc 不能为空")
  1047. }
  1048. }
  1049. // 验证模块信息(批量验证)
  1050. if plan.NeedCreatedModules {
  1051. if len(plan.ModulesInfo) == 0 {
  1052. return errors.New("当 needCreatedModules=true 时,modulesInfo 不能为空")
  1053. }
  1054. // 遍历验证每个模块
  1055. for moduleIndex, moduleInfo := range plan.ModulesInfo {
  1056. if moduleInfo.Package == "" {
  1057. return fmt.Errorf("模块 %d 的 package 不能为空", moduleIndex+1)
  1058. }
  1059. if moduleInfo.StructName == "" {
  1060. return fmt.Errorf("模块 %d 的 structName 不能为空", moduleIndex+1)
  1061. }
  1062. if moduleInfo.TableName == "" {
  1063. return fmt.Errorf("模块 %d 的 tableName 不能为空", moduleIndex+1)
  1064. }
  1065. if moduleInfo.Description == "" {
  1066. return fmt.Errorf("模块 %d 的 description 不能为空", moduleIndex+1)
  1067. }
  1068. if moduleInfo.Abbreviation == "" {
  1069. return fmt.Errorf("模块 %d 的 abbreviation 不能为空", moduleIndex+1)
  1070. }
  1071. if moduleInfo.PackageName == "" {
  1072. return fmt.Errorf("模块 %d 的 packageName 不能为空", moduleIndex+1)
  1073. }
  1074. if moduleInfo.HumpPackageName == "" {
  1075. return fmt.Errorf("模块 %d 的 humpPackageName 不能为空", moduleIndex+1)
  1076. }
  1077. // 验证字段信息
  1078. if len(moduleInfo.Fields) == 0 {
  1079. return fmt.Errorf("模块 %d 的 fields 不能为空,至少需要一个字段", moduleIndex+1)
  1080. }
  1081. for i, field := range moduleInfo.Fields {
  1082. if field.FieldName == "" {
  1083. return fmt.Errorf("模块 %d 字段 %d 的 fieldName 不能为空", moduleIndex+1, i+1)
  1084. }
  1085. if field.FieldDesc == "" {
  1086. return fmt.Errorf("模块 %d 字段 %d 的 fieldDesc 不能为空", moduleIndex+1, i+1)
  1087. }
  1088. if field.FieldType == "" {
  1089. return fmt.Errorf("模块 %d 字段 %d 的 fieldType 不能为空", moduleIndex+1, i+1)
  1090. }
  1091. if field.FieldJson == "" {
  1092. return fmt.Errorf("模块 %d 字段 %d 的 fieldJson 不能为空", moduleIndex+1, i+1)
  1093. }
  1094. if field.ColumnName == "" {
  1095. return fmt.Errorf("模块 %d 字段 %d 的 columnName 不能为空", moduleIndex+1, i+1)
  1096. }
  1097. // 确保字段名首字母大写
  1098. if len(field.FieldName) > 0 {
  1099. firstChar := string(field.FieldName[0])
  1100. if firstChar >= "a" && firstChar <= "z" {
  1101. moduleInfo.Fields[i].FieldName = strings.ToUpper(firstChar) + field.FieldName[1:]
  1102. }
  1103. }
  1104. // 确保FieldJson使用小驼峰命名
  1105. if len(field.FieldJson) > 0 {
  1106. // 处理下划线命名转小驼峰
  1107. if strings.Contains(field.FieldJson, "_") {
  1108. parts := strings.Split(field.FieldJson, "_")
  1109. camelCase := strings.ToLower(parts[0])
  1110. for j := 1; j < len(parts); j++ {
  1111. if len(parts[j]) > 0 {
  1112. camelCase += strings.ToUpper(string(parts[j][0])) + strings.ToLower(parts[j][1:])
  1113. }
  1114. }
  1115. moduleInfo.Fields[i].FieldJson = camelCase
  1116. } else {
  1117. // 处理首字母大写转小写
  1118. firstChar := string(field.FieldJson[0])
  1119. if firstChar >= "A" && firstChar <= "Z" {
  1120. moduleInfo.Fields[i].FieldJson = strings.ToLower(firstChar) + field.FieldJson[1:]
  1121. }
  1122. }
  1123. }
  1124. // 确保ColumnName使用下划线命名
  1125. if len(field.ColumnName) > 0 {
  1126. // 将驼峰命名转换为下划线命名
  1127. var result strings.Builder
  1128. for i, r := range field.ColumnName {
  1129. if i > 0 && r >= 'A' && r <= 'Z' {
  1130. result.WriteRune('_')
  1131. }
  1132. result.WriteRune(unicode.ToLower(r))
  1133. }
  1134. moduleInfo.Fields[i].ColumnName = result.String()
  1135. }
  1136. // 验证字段类型
  1137. validFieldTypes := []string{"string", "int", "int64", "float64", "bool", "time.Time", "enum", "picture", "video", "file", "pictures", "array", "richtext", "json"}
  1138. validType := false
  1139. for _, validFieldType := range validFieldTypes {
  1140. if field.FieldType == validFieldType {
  1141. validType = true
  1142. break
  1143. }
  1144. }
  1145. if !validType {
  1146. return fmt.Errorf("模块 %d 字段 %d 的 fieldType '%s' 不支持,支持的类型:%v", moduleIndex+1, i+1, field.FieldType, validFieldTypes)
  1147. }
  1148. // 验证搜索类型(如果设置了)
  1149. if field.FieldSearchType != "" {
  1150. validSearchTypes := []string{"=", "!=", ">", ">=", "<", "<=", "LIKE", "BETWEEN", "IN", "NOT IN"}
  1151. validSearchType := false
  1152. for _, validType := range validSearchTypes {
  1153. if field.FieldSearchType == validType {
  1154. validSearchType = true
  1155. break
  1156. }
  1157. }
  1158. if !validSearchType {
  1159. return fmt.Errorf("模块 %d 字段 %d 的 fieldSearchType '%s' 不支持,支持的类型:%v", moduleIndex+1, i+1, field.FieldSearchType, validSearchTypes)
  1160. }
  1161. }
  1162. }
  1163. // 验证主键设置
  1164. if !moduleInfo.GvaModel {
  1165. // 当不使用GVA模型时,必须有且仅有一个字段设置为主键
  1166. primaryKeyCount := 0
  1167. for _, field := range moduleInfo.Fields {
  1168. if field.PrimaryKey {
  1169. primaryKeyCount++
  1170. }
  1171. }
  1172. if primaryKeyCount == 0 {
  1173. return fmt.Errorf("模块 %d:当 gvaModel=false 时,必须有一个字段的 primaryKey=true", moduleIndex+1)
  1174. }
  1175. if primaryKeyCount > 1 {
  1176. return fmt.Errorf("模块 %d:当 gvaModel=false 时,只能有一个字段的 primaryKey=true", moduleIndex+1)
  1177. }
  1178. } else {
  1179. // 当使用GVA模型时,所有字段的primaryKey都应该为false
  1180. for i, field := range moduleInfo.Fields {
  1181. if field.PrimaryKey {
  1182. return fmt.Errorf("模块 %d:当 gvaModel=true 时,字段 %d 的 primaryKey 应该为 false,系统会自动创建ID主键", moduleIndex+1, i+1)
  1183. }
  1184. }
  1185. }
  1186. }
  1187. }
  1188. return nil
  1189. }
  1190. // executeCreation 执行创建操作
  1191. func (t *AutomationModuleAnalyzer) executeCreation(ctx context.Context, plan *ExecutionPlan) *ExecutionResult {
  1192. result := &ExecutionResult{
  1193. Success: false,
  1194. Paths: make(map[string]string),
  1195. }
  1196. // 无论如何都先构建目录结构信息,确保paths始终返回
  1197. result.Paths = t.buildDirectoryStructure(plan)
  1198. if !plan.NeedCreatedModules {
  1199. result.Success = true
  1200. result.Message += "已列出当前功能所涉及的目录结构信息; 请在paths中查看; 并且在对应指定文件中实现相关的业务逻辑; "
  1201. return result
  1202. }
  1203. // 创建包(如果需要)
  1204. if plan.NeedCreatedPackage && plan.PackageInfo != nil {
  1205. packageService := service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage
  1206. err := packageService.Create(ctx, plan.PackageInfo)
  1207. if err != nil {
  1208. result.Message = fmt.Sprintf("创建包失败: %v", err)
  1209. // 即使创建包失败,也要返回paths信息
  1210. return result
  1211. }
  1212. result.Message += "包创建成功; "
  1213. }
  1214. // 批量创建字典和模块(如果需要)
  1215. if plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 {
  1216. templateService := service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate
  1217. // 先批量创建所有模块需要的字典
  1218. dictResult := t.createRequiredDictionaries(ctx, plan.ModulesInfo)
  1219. result.Message += dictResult
  1220. // 遍历所有模块进行创建
  1221. for _, moduleInfo := range plan.ModulesInfo {
  1222. // 创建模块
  1223. err := moduleInfo.Pretreatment()
  1224. if err != nil {
  1225. result.Message += fmt.Sprintf("模块 %s 信息预处理失败: %v; ", moduleInfo.StructName, err)
  1226. continue // 继续处理下一个模块
  1227. }
  1228. err = templateService.Create(ctx, *moduleInfo)
  1229. if err != nil {
  1230. result.Message += fmt.Sprintf("创建模块 %s 失败: %v; ", moduleInfo.StructName, err)
  1231. continue // 继续处理下一个模块
  1232. }
  1233. result.Message += fmt.Sprintf("模块 %s 创建成功; ", moduleInfo.StructName)
  1234. }
  1235. result.Message += fmt.Sprintf("批量创建完成,共处理 %d 个模块; ", len(plan.ModulesInfo))
  1236. // 添加重要提醒:不要使用其他MCP工具
  1237. result.Message += "\n\n⚠️ 重要提醒:\n"
  1238. result.Message += "模块创建已完成,API和菜单已自动生成。请不要再调用以下MCP工具:\n"
  1239. result.Message += "- api_creator:API权限已在模块创建时自动生成\n"
  1240. result.Message += "- menu_creator:前端菜单已在模块创建时自动生成\n"
  1241. result.Message += "如需修改API或菜单,请直接在系统管理界面中进行配置。\n"
  1242. }
  1243. result.Message += "已构建目录结构信息; "
  1244. result.Success = true
  1245. if result.Message == "" {
  1246. result.Message = "执行计划完成"
  1247. }
  1248. return result
  1249. }
  1250. // createRequiredDictionaries 创建所需的字典(批量处理)
  1251. func (t *AutomationModuleAnalyzer) createRequiredDictionaries(ctx context.Context, modulesInfoList []*request.AutoCode) string {
  1252. var messages []string
  1253. dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService
  1254. createdDictTypes := make(map[string]bool) // 用于避免重复创建相同的字典
  1255. // 遍历所有模块
  1256. for moduleIndex, modulesInfo := range modulesInfoList {
  1257. messages = append(messages, fmt.Sprintf("处理模块 %d (%s) 的字典: ", moduleIndex+1, modulesInfo.StructName))
  1258. // 遍历当前模块的所有字段,查找使用字典的字段
  1259. moduleHasDictFields := false
  1260. for _, field := range modulesInfo.Fields {
  1261. if field.DictType != "" {
  1262. moduleHasDictFields = true
  1263. // 如果这个字典类型已经在之前的模块中创建过,跳过
  1264. if createdDictTypes[field.DictType] {
  1265. messages = append(messages, fmt.Sprintf("字典 %s 已在前面的模块中创建,跳过; ", field.DictType))
  1266. continue
  1267. }
  1268. // 检查字典是否存在
  1269. exists, err := t.checkDictionaryExists(field.DictType)
  1270. if err != nil {
  1271. messages = append(messages, fmt.Sprintf("检查字典 %s 时出错: %v; ", field.DictType, err))
  1272. continue
  1273. }
  1274. if !exists {
  1275. // 字典不存在,创建字典
  1276. dictionary := model.SysDictionary{
  1277. Name: t.generateDictionaryName(field.DictType, field.FieldDesc),
  1278. Type: field.DictType,
  1279. Status: &[]bool{true}[0], // 默认启用
  1280. Desc: fmt.Sprintf("自动生成的字典,用于模块 %s 字段: %s (%s)", modulesInfo.StructName, field.FieldName, field.FieldDesc),
  1281. }
  1282. err = dictionaryService.CreateSysDictionary(dictionary)
  1283. if err != nil {
  1284. messages = append(messages, fmt.Sprintf("创建字典 %s 失败: %v; ", field.DictType, err))
  1285. } else {
  1286. messages = append(messages, fmt.Sprintf("成功创建字典 %s (%s); ", field.DictType, dictionary.Name))
  1287. createdDictTypes[field.DictType] = true // 标记为已创建
  1288. // 创建默认的字典详情项
  1289. t.createDefaultDictionaryDetails(ctx, field.DictType, field.FieldDesc)
  1290. }
  1291. } else {
  1292. messages = append(messages, fmt.Sprintf("字典 %s 已存在,跳过创建; ", field.DictType))
  1293. createdDictTypes[field.DictType] = true // 标记为已存在
  1294. }
  1295. }
  1296. }
  1297. if !moduleHasDictFields {
  1298. messages = append(messages, "无需创建字典; ")
  1299. }
  1300. }
  1301. if len(messages) == 0 {
  1302. return "未发现需要创建的字典; "
  1303. }
  1304. return strings.Join(messages, "")
  1305. }
  1306. // checkDictionaryExists 检查字典是否存在
  1307. func (t *AutomationModuleAnalyzer) checkDictionaryExists(dictType string) (bool, error) {
  1308. var dictionary model.SysDictionary
  1309. err := global.GVA_DB.Where("type = ?", dictType).First(&dictionary).Error
  1310. if err != nil {
  1311. if errors.Is(err, gorm.ErrRecordNotFound) {
  1312. return false, nil // 字典不存在
  1313. }
  1314. return false, err // 其他错误
  1315. }
  1316. return true, nil // 字典存在
  1317. }
  1318. // generateDictionaryName 生成字典名称
  1319. func (t *AutomationModuleAnalyzer) generateDictionaryName(dictType, fieldDesc string) string {
  1320. if fieldDesc != "" {
  1321. return fmt.Sprintf("%s字典", fieldDesc)
  1322. }
  1323. return fmt.Sprintf("%s字典", dictType)
  1324. }
  1325. // createDefaultDictionaryDetails 创建默认的字典详情项
  1326. func (t *AutomationModuleAnalyzer) createDefaultDictionaryDetails(ctx context.Context, dictType, fieldDesc string) {
  1327. // 字典选项现在通过 generate_dictionary_options MCP工具由AI client传入
  1328. // 这里不再创建默认选项,只是保留方法以保持兼容性
  1329. global.GVA_LOG.Info(fmt.Sprintf("字典 %s 已创建,请使用 generate_dictionary_options 工具添加字典选项", dictType))
  1330. }
  1331. // DictionaryOption 字典选项结构
  1332. type DictionaryOption struct {
  1333. Label string `json:"label"`
  1334. Value string `json:"value"`
  1335. Sort int `json:"sort"`
  1336. }
  1337. // generateSmartDictionaryOptions 通过MCP调用让AI生成字典选项
  1338. func (t *AutomationModuleAnalyzer) generateSmartDictionaryOptions(dictType, fieldDesc string) []struct {
  1339. label string
  1340. value string
  1341. sort int
  1342. } {
  1343. // 返回空切片,不再使用预制选项
  1344. // 字典选项将通过新的MCP工具由AI client传入
  1345. return []struct {
  1346. label string
  1347. value string
  1348. sort int
  1349. }{}
  1350. }
  1351. // detectPluginIntent 检测用户需求中是否包含插件相关的关键词
  1352. func (t *AutomationModuleAnalyzer) detectPluginIntent(requirement string) (suggestedType string, isPlugin bool, confidence string) {
  1353. // 转换为小写进行匹配
  1354. requirementLower := strings.ToLower(requirement)
  1355. // 插件相关关键词
  1356. pluginKeywords := []string{
  1357. "插件", "plugin", "扩展", "extension", "addon", "模块插件",
  1358. "功能插件", "业务插件", "第三方插件", "自定义插件",
  1359. }
  1360. // 包相关关键词(用于排除误判)
  1361. packageKeywords := []string{
  1362. "包", "package", "模块包", "业务包", "功能包",
  1363. }
  1364. // 检测插件关键词
  1365. pluginMatches := 0
  1366. for _, keyword := range pluginKeywords {
  1367. if strings.Contains(requirementLower, keyword) {
  1368. pluginMatches++
  1369. }
  1370. }
  1371. // 检测包关键词
  1372. packageMatches := 0
  1373. for _, keyword := range packageKeywords {
  1374. if strings.Contains(requirementLower, keyword) {
  1375. packageMatches++
  1376. }
  1377. }
  1378. // 决策逻辑
  1379. if pluginMatches > 0 {
  1380. if packageMatches == 0 || pluginMatches > packageMatches {
  1381. return "plugin", true, "高"
  1382. } else {
  1383. return "plugin", true, "中"
  1384. }
  1385. }
  1386. // 默认返回package
  1387. return "package", false, "低"
  1388. }
  1389. // isPackageFolderEmpty 检查包对应的文件夹是否为空
  1390. func (t *AutomationModuleAnalyzer) isPackageFolderEmpty(packageName, template string) (bool, error) {
  1391. // 根据模板类型确定基础路径
  1392. var basePath string
  1393. if template == "plugin" {
  1394. basePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", packageName)
  1395. } else {
  1396. // package 类型
  1397. basePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "model", packageName)
  1398. }
  1399. // 检查文件夹是否存在
  1400. if _, err := os.Stat(basePath); os.IsNotExist(err) {
  1401. // 文件夹不存在,认为是空的
  1402. return true, nil
  1403. } else if err != nil {
  1404. return false, fmt.Errorf("检查文件夹状态失败: %v", err)
  1405. }
  1406. // 读取文件夹内容
  1407. entries, err := os.ReadDir(basePath)
  1408. if err != nil {
  1409. return false, fmt.Errorf("读取文件夹内容失败: %v", err)
  1410. }
  1411. // 检查目录下是否有 .go 文件
  1412. hasGoFiles := false
  1413. for _, entry := range entries {
  1414. name := entry.Name()
  1415. // 跳过隐藏文件、.DS_Store 等系统文件
  1416. if strings.HasPrefix(name, ".") {
  1417. continue
  1418. }
  1419. // 如果是目录,递归检查子目录中的 .go 文件
  1420. if entry.IsDir() {
  1421. subPath := filepath.Join(basePath, name)
  1422. subEntries, err := os.ReadDir(subPath)
  1423. if err != nil {
  1424. continue
  1425. }
  1426. for _, subEntry := range subEntries {
  1427. if !subEntry.IsDir() && strings.HasSuffix(subEntry.Name(), ".go") {
  1428. hasGoFiles = true
  1429. break
  1430. }
  1431. }
  1432. if hasGoFiles {
  1433. break
  1434. }
  1435. } else if strings.HasSuffix(name, ".go") {
  1436. // 如果是 .go 文件
  1437. hasGoFiles = true
  1438. break
  1439. }
  1440. }
  1441. // 如果没有 .go 文件,认为是空包
  1442. return !hasGoFiles, nil
  1443. }
  1444. // removeEmptyPackageFolder 删除空的包文件夹
  1445. func (t *AutomationModuleAnalyzer) removeEmptyPackageFolder(packageName, template string) error {
  1446. var errors []string
  1447. if template == "plugin" {
  1448. // plugin 类型只删除 plugin 目录下的文件夹
  1449. basePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", packageName)
  1450. if err := t.removeDirectoryIfExists(basePath); err != nil {
  1451. errors = append(errors, fmt.Sprintf("删除plugin文件夹失败: %v", err))
  1452. }
  1453. } else {
  1454. // package 类型需要删除多个目录下的相关文件
  1455. paths := []string{
  1456. filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "model", packageName),
  1457. filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", packageName),
  1458. filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", packageName),
  1459. filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", packageName),
  1460. }
  1461. for _, path := range paths {
  1462. if err := t.removeDirectoryIfExists(path); err != nil {
  1463. errors = append(errors, fmt.Sprintf("删除%s失败: %v", path, err))
  1464. }
  1465. }
  1466. }
  1467. if len(errors) > 0 {
  1468. return fmt.Errorf("删除过程中出现错误: %s", strings.Join(errors, "; "))
  1469. }
  1470. return nil
  1471. }
  1472. // removeDirectoryIfExists 删除目录(如果存在)
  1473. func (t *AutomationModuleAnalyzer) removeDirectoryIfExists(dirPath string) error {
  1474. // 检查文件夹是否存在
  1475. if _, err := os.Stat(dirPath); os.IsNotExist(err) {
  1476. // 文件夹不存在,无需删除
  1477. return nil
  1478. } else if err != nil {
  1479. return fmt.Errorf("检查文件夹状态失败: %v", err)
  1480. }
  1481. // 删除文件夹及其所有内容
  1482. if err := os.RemoveAll(dirPath); err != nil {
  1483. return fmt.Errorf("删除文件夹失败: %v", err)
  1484. }
  1485. global.GVA_LOG.Info(fmt.Sprintf("成功删除目录: %s", dirPath))
  1486. return nil
  1487. }
  1488. // cleanupRelatedApiAndMenus 清理与删除的模块相关的API和菜单记录
  1489. func (t *AutomationModuleAnalyzer) cleanupRelatedApiAndMenus(historyIDs []uint) error {
  1490. if len(historyIDs) == 0 {
  1491. return nil
  1492. }
  1493. // 获取要删除的历史记录信息
  1494. var histories []model.SysAutoCodeHistory
  1495. if err := global.GVA_DB.Where("id IN ?", historyIDs).Find(&histories).Error; err != nil {
  1496. return fmt.Errorf("获取历史记录失败: %v", err)
  1497. }
  1498. var deletedApiCount, deletedMenuCount int
  1499. for _, history := range histories {
  1500. // 删除相关的API记录(使用存储的API IDs)
  1501. if len(history.ApiIDs) > 0 {
  1502. ids := make([]int, 0, len(history.ApiIDs))
  1503. for _, id := range history.ApiIDs {
  1504. ids = append(ids, int(id))
  1505. }
  1506. idsReq := common.IdsReq{Ids: ids}
  1507. if err := systemService.ApiServiceApp.DeleteApisByIds(idsReq); err != nil {
  1508. global.GVA_LOG.Warn(fmt.Sprintf("删除API记录失败 (模块: %s): %v", history.StructName, err))
  1509. } else {
  1510. deletedApiCount += len(ids)
  1511. global.GVA_LOG.Info(fmt.Sprintf("成功删除API记录 (模块: %s, 数量: %d)", history.StructName, len(ids)))
  1512. }
  1513. }
  1514. // 删除相关的菜单记录(使用存储的菜单ID)
  1515. if history.MenuID != 0 {
  1516. if err := systemService.BaseMenuServiceApp.DeleteBaseMenu(int(history.MenuID)); err != nil {
  1517. global.GVA_LOG.Warn(fmt.Sprintf("删除菜单记录失败 (模块: %s, 菜单ID: %d): %v", history.StructName, history.MenuID, err))
  1518. } else {
  1519. deletedMenuCount++
  1520. global.GVA_LOG.Info(fmt.Sprintf("成功删除菜单记录 (模块: %s, 菜单ID: %d)", history.StructName, history.MenuID))
  1521. }
  1522. }
  1523. }
  1524. if deletedApiCount > 0 || deletedMenuCount > 0 {
  1525. global.GVA_LOG.Info(fmt.Sprintf("清理完成:删除了 %d 个API记录和 %d 个菜单记录", deletedApiCount, deletedMenuCount))
  1526. }
  1527. return nil
  1528. }