756 lines
27 KiB
Go
756 lines
27 KiB
Go
package mcpTool
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"github.com/mark3labs/mcp-go/mcp"
|
||
"go.uber.org/zap"
|
||
"gorm.io/gorm"
|
||
|
||
"kra/internal/data/model"
|
||
)
|
||
|
||
// 注册工具
|
||
func init() {
|
||
RegisterTool(&KRAExecutor{})
|
||
}
|
||
|
||
// KRAExecutor KRA代码生成器
|
||
type KRAExecutor struct {
|
||
db *gorm.DB
|
||
logger *zap.Logger
|
||
config *AutoCodeConfig
|
||
}
|
||
|
||
// SetDependencies 设置依赖
|
||
func (g *KRAExecutor) SetDependencies(db *gorm.DB, logger *zap.Logger, config *AutoCodeConfig) {
|
||
g.db = db
|
||
g.logger = logger
|
||
g.config = config
|
||
}
|
||
|
||
// ExecuteRequest 执行请求结构
|
||
type ExecuteRequest struct {
|
||
ExecutionPlan ExecutionPlan `json:"executionPlan"` // 执行计划
|
||
Requirement string `json:"requirement"` // 原始需求(可选,用于日志记录)
|
||
}
|
||
|
||
// ExecuteResponse 执行响应结构
|
||
type ExecuteResponse struct {
|
||
Success bool `json:"success"`
|
||
Message string `json:"message"`
|
||
PackageID uint `json:"packageId,omitempty"`
|
||
HistoryID uint `json:"historyId,omitempty"`
|
||
Paths map[string]string `json:"paths,omitempty"`
|
||
GeneratedPaths []string `json:"generatedPaths,omitempty"`
|
||
NextActions []string `json:"nextActions,omitempty"`
|
||
}
|
||
|
||
// ExecutionPlan 执行计划结构
|
||
type ExecutionPlan struct {
|
||
PackageName string `json:"packageName"`
|
||
PackageType string `json:"packageType"` // "plugin" 或 "package"
|
||
NeedCreatedPackage bool `json:"needCreatedPackage"`
|
||
NeedCreatedModules bool `json:"needCreatedModules"`
|
||
NeedCreatedDictionaries bool `json:"needCreatedDictionaries"`
|
||
PackageInfo *SysAutoCodePackageCreate `json:"packageInfo,omitempty"`
|
||
ModulesInfo []*AutoCode `json:"modulesInfo,omitempty"`
|
||
Paths map[string]string `json:"paths,omitempty"`
|
||
DictionariesInfo []*DictionaryGenerateRequest `json:"dictionariesInfo,omitempty"`
|
||
}
|
||
|
||
// SysAutoCodePackageCreate 包创建请求
|
||
type SysAutoCodePackageCreate struct {
|
||
Desc string `json:"desc"`
|
||
Label string `json:"label"`
|
||
Template string `json:"template"`
|
||
PackageName string `json:"packageName"`
|
||
}
|
||
|
||
// AutoCode 自动代码生成配置
|
||
type AutoCode struct {
|
||
Package string `json:"package"`
|
||
TableName string `json:"tableName"`
|
||
BusinessDB string `json:"businessDB"`
|
||
StructName string `json:"structName"`
|
||
PackageName string `json:"packageName"`
|
||
Description string `json:"description"`
|
||
Abbreviation string `json:"abbreviation"`
|
||
HumpPackageName string `json:"humpPackageName"`
|
||
GvaModel bool `json:"gvaModel"`
|
||
AutoMigrate bool `json:"autoMigrate"`
|
||
AutoCreateResource bool `json:"autoCreateResource"`
|
||
AutoCreateApiToSql bool `json:"autoCreateApiToSql"`
|
||
AutoCreateMenuToSql bool `json:"autoCreateMenuToSql"`
|
||
AutoCreateBtnAuth bool `json:"autoCreateBtnAuth"`
|
||
OnlyTemplate bool `json:"onlyTemplate"`
|
||
IsTree bool `json:"isTree"`
|
||
TreeJson string `json:"treeJson"`
|
||
IsAdd bool `json:"isAdd"`
|
||
GenerateWeb bool `json:"generateWeb"`
|
||
GenerateServer bool `json:"generateServer"`
|
||
Fields []*AutoField `json:"fields"`
|
||
}
|
||
|
||
// AutoField 自动代码字段配置
|
||
type AutoField struct {
|
||
FieldName string `json:"fieldName"`
|
||
FieldDesc string `json:"fieldDesc"`
|
||
FieldType string `json:"fieldType"`
|
||
FieldJson string `json:"fieldJson"`
|
||
DataTypeLong string `json:"dataTypeLong"`
|
||
Comment string `json:"comment"`
|
||
ColumnName string `json:"columnName"`
|
||
FieldSearchType string `json:"fieldSearchType"`
|
||
FieldSearchHide bool `json:"fieldSearchHide"`
|
||
DictType string `json:"dictType"`
|
||
Form bool `json:"form"`
|
||
Table bool `json:"table"`
|
||
Desc bool `json:"desc"`
|
||
Excel bool `json:"excel"`
|
||
Require bool `json:"require"`
|
||
DefaultValue string `json:"defaultValue"`
|
||
ErrorText string `json:"errorText"`
|
||
Clearable bool `json:"clearable"`
|
||
Sort bool `json:"sort"`
|
||
PrimaryKey bool `json:"primaryKey"`
|
||
DataSource *DataSource `json:"dataSource"`
|
||
CheckDataSource bool `json:"checkDataSource"`
|
||
FieldIndexType string `json:"fieldIndexType"`
|
||
}
|
||
|
||
// DataSource 数据源配置
|
||
type DataSource struct {
|
||
DBName string `json:"dbName"`
|
||
Table string `json:"table"`
|
||
Label string `json:"label"`
|
||
Value string `json:"value"`
|
||
Association int `json:"association"`
|
||
HasDeletedAt bool `json:"hasDeletedAt"`
|
||
}
|
||
|
||
// DictOptionItem 字典选项
|
||
type DictOptionItem struct {
|
||
Label string `json:"label"`
|
||
Value string `json:"value"`
|
||
Sort int `json:"sort"`
|
||
}
|
||
|
||
// New 创建KRA代码生成执行器工具
|
||
func (g *KRAExecutor) New() mcp.Tool {
|
||
return mcp.NewTool("kra_execute",
|
||
mcp.WithDescription(`**KRA代码生成执行器:直接执行代码生成,无需确认步骤**
|
||
|
||
**核心功能:**
|
||
- 根据需求分析和当前的包信息判断是否调用,如果需要调用,则根据入参描述生成json,用于直接生成代码
|
||
- 支持批量创建多个模块
|
||
- 自动创建包、模块、字典等
|
||
- 移除了确认步骤,提高执行效率
|
||
|
||
**使用场景:**
|
||
- 在kra_analyze获取了当前的包信息和字典信息之后,如果已经包含了可以使用的包和模块,那就不要调用本mcp
|
||
- 根据分析结果直接生成代码
|
||
- 适用于自动化代码生成流程
|
||
|
||
**批量创建功能:**
|
||
- 支持在单个ExecutionPlan中创建多个模块
|
||
- modulesInfo字段为数组,可包含多个模块配置
|
||
- 一次性处理多个模块的创建和字典生成
|
||
|
||
**新功能:自动字典创建**
|
||
- 当结构体字段使用了字典类型(dictType不为空)时,系统会自动检查字典是否存在
|
||
- 如果字典不存在,会自动创建对应的字典及默认的字典详情项
|
||
- 字典创建包括:字典主表记录和默认的选项值(选项1、选项2等)
|
||
|
||
**重要限制:**
|
||
- 当needCreatedModules=true时,模块创建会自动生成API和菜单,因此不应再调用api_creator和menu_creator工具
|
||
- 只有在单独创建API或菜单(不涉及模块创建)时才使用api_creator和menu_creator工具`),
|
||
mcp.WithObject("executionPlan",
|
||
mcp.Description("执行计划,包含包信息、模块与字典信息"),
|
||
mcp.Required(),
|
||
mcp.Properties(map[string]interface{}{
|
||
"packageName": map[string]interface{}{
|
||
"type": "string",
|
||
"description": "包名(小写开头)",
|
||
},
|
||
"packageType": map[string]interface{}{
|
||
"type": "string",
|
||
"description": "package 或 plugin",
|
||
"enum": []string{"package", "plugin"},
|
||
},
|
||
"needCreatedPackage": map[string]interface{}{
|
||
"type": "boolean",
|
||
"description": "是否需要创建包",
|
||
},
|
||
"needCreatedModules": map[string]interface{}{
|
||
"type": "boolean",
|
||
"description": "是否需要创建模块",
|
||
},
|
||
"needCreatedDictionaries": map[string]interface{}{
|
||
"type": "boolean",
|
||
"description": "是否需要创建字典",
|
||
},
|
||
}),
|
||
mcp.AdditionalProperties(true),
|
||
),
|
||
mcp.WithString("requirement",
|
||
mcp.Description("原始需求描述(可选,用于日志记录)"),
|
||
),
|
||
)
|
||
}
|
||
|
||
// Handle 处理执行请求(移除确认步骤)
|
||
func (g *KRAExecutor) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||
if g.db == nil {
|
||
return nil, errors.New("数据库连接未初始化")
|
||
}
|
||
|
||
executionPlanData, ok := request.GetArguments()["executionPlan"]
|
||
if !ok {
|
||
return nil, errors.New("参数错误:executionPlan 必须提供")
|
||
}
|
||
|
||
// 解析执行计划
|
||
planJSON, err := json.Marshal(executionPlanData)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("解析执行计划失败: %v", err)
|
||
}
|
||
|
||
var plan ExecutionPlan
|
||
err = json.Unmarshal(planJSON, &plan)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("解析执行计划失败: %v\n\n请确保ExecutionPlan格式正确,参考工具描述中的结构体格式要求", err)
|
||
}
|
||
|
||
// 验证执行计划的完整性
|
||
if err := g.validateExecutionPlan(&plan); err != nil {
|
||
return nil, fmt.Errorf("执行计划验证失败: %v", err)
|
||
}
|
||
|
||
// 获取原始需求(可选)
|
||
var originalRequirement string
|
||
if reqData, ok := request.GetArguments()["requirement"]; ok {
|
||
if reqStr, ok := reqData.(string); ok {
|
||
originalRequirement = reqStr
|
||
}
|
||
}
|
||
|
||
// 直接执行创建操作(无确认步骤)
|
||
result := g.executeCreation(ctx, &plan)
|
||
|
||
// 如果执行成功且有原始需求,提供代码复检建议
|
||
var reviewMessage string
|
||
if result.Success && originalRequirement != "" {
|
||
if g.logger != nil {
|
||
g.logger.Info("执行完成,返回生成的文件路径供AI进行代码复检...")
|
||
}
|
||
|
||
// 构建文件路径信息供AI使用
|
||
var pathsInfo []string
|
||
for _, path := range result.GeneratedPaths {
|
||
pathsInfo = append(pathsInfo, fmt.Sprintf("- %s", path))
|
||
}
|
||
|
||
reviewMessage = fmt.Sprintf("\n\n📁 已生成以下文件:\n%s\n\n💡 提示:可以检查生成的代码是否满足原始需求。", strings.Join(pathsInfo, "\n"))
|
||
} else if originalRequirement == "" {
|
||
reviewMessage = "\n\n💡 提示:如需代码复检,请提供原始需求描述。"
|
||
}
|
||
|
||
// 序列化响应
|
||
response := ExecuteResponse{
|
||
Success: result.Success,
|
||
Message: result.Message,
|
||
PackageID: result.PackageID,
|
||
HistoryID: result.HistoryID,
|
||
Paths: result.Paths,
|
||
GeneratedPaths: result.GeneratedPaths,
|
||
NextActions: result.NextActions,
|
||
}
|
||
|
||
responseJSON, err := json.MarshalIndent(response, "", " ")
|
||
if err != nil {
|
||
return nil, fmt.Errorf("序列化结果失败: %v", err)
|
||
}
|
||
|
||
return &mcp.CallToolResult{
|
||
Content: []mcp.Content{
|
||
mcp.NewTextContent(fmt.Sprintf("执行结果:\n\n%s%s", string(responseJSON), reviewMessage)),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// validateExecutionPlan 验证执行计划的完整性
|
||
func (g *KRAExecutor) validateExecutionPlan(plan *ExecutionPlan) error {
|
||
// 验证基本字段
|
||
if plan.PackageName == "" {
|
||
return errors.New("packageName 不能为空")
|
||
}
|
||
if plan.PackageType != "package" && plan.PackageType != "plugin" {
|
||
return errors.New("packageType 必须是 'package' 或 'plugin'")
|
||
}
|
||
|
||
// 验证packageType和template字段的一致性
|
||
if plan.NeedCreatedPackage && plan.PackageInfo != nil {
|
||
if plan.PackageType != plan.PackageInfo.Template {
|
||
return errors.New("packageType 和 packageInfo.template 必须保持一致")
|
||
}
|
||
}
|
||
|
||
// 验证包信息
|
||
if plan.NeedCreatedPackage {
|
||
if plan.PackageInfo == nil {
|
||
return errors.New("当 needCreatedPackage=true 时,packageInfo 不能为空")
|
||
}
|
||
if plan.PackageInfo.PackageName == "" {
|
||
return errors.New("packageInfo.packageName 不能为空")
|
||
}
|
||
if plan.PackageInfo.Template != "package" && plan.PackageInfo.Template != "plugin" {
|
||
return errors.New("packageInfo.template 必须是 'package' 或 'plugin'")
|
||
}
|
||
if plan.PackageInfo.Label == "" {
|
||
return errors.New("packageInfo.label 不能为空")
|
||
}
|
||
if plan.PackageInfo.Desc == "" {
|
||
return errors.New("packageInfo.desc 不能为空")
|
||
}
|
||
}
|
||
|
||
// 验证模块信息(批量验证)
|
||
if plan.NeedCreatedModules {
|
||
if len(plan.ModulesInfo) == 0 {
|
||
return errors.New("当 needCreatedModules=true 时,modulesInfo 不能为空")
|
||
}
|
||
|
||
// 遍历验证每个模块
|
||
for moduleIndex, moduleInfo := range plan.ModulesInfo {
|
||
if moduleInfo.Package == "" {
|
||
return fmt.Errorf("模块 %d 的 package 不能为空", moduleIndex+1)
|
||
}
|
||
if moduleInfo.StructName == "" {
|
||
return fmt.Errorf("模块 %d 的 structName 不能为空", moduleIndex+1)
|
||
}
|
||
if moduleInfo.TableName == "" {
|
||
return fmt.Errorf("模块 %d 的 tableName 不能为空", moduleIndex+1)
|
||
}
|
||
if moduleInfo.Description == "" {
|
||
return fmt.Errorf("模块 %d 的 description 不能为空", moduleIndex+1)
|
||
}
|
||
if moduleInfo.Abbreviation == "" {
|
||
return fmt.Errorf("模块 %d 的 abbreviation 不能为空", moduleIndex+1)
|
||
}
|
||
if moduleInfo.PackageName == "" {
|
||
return fmt.Errorf("模块 %d 的 packageName 不能为空", moduleIndex+1)
|
||
}
|
||
if moduleInfo.HumpPackageName == "" {
|
||
return fmt.Errorf("模块 %d 的 humpPackageName 不能为空", moduleIndex+1)
|
||
}
|
||
|
||
// 验证字段信息
|
||
if len(moduleInfo.Fields) == 0 {
|
||
return fmt.Errorf("模块 %d 的 fields 不能为空,至少需要一个字段", moduleIndex+1)
|
||
}
|
||
|
||
for i, field := range moduleInfo.Fields {
|
||
if field.FieldName == "" {
|
||
return fmt.Errorf("模块 %d 字段 %d 的 fieldName 不能为空", moduleIndex+1, i+1)
|
||
}
|
||
|
||
// 确保字段名首字母大写
|
||
if len(field.FieldName) > 0 {
|
||
firstChar := string(field.FieldName[0])
|
||
if firstChar >= "a" && firstChar <= "z" {
|
||
moduleInfo.Fields[i].FieldName = strings.ToUpper(firstChar) + field.FieldName[1:]
|
||
}
|
||
}
|
||
if field.FieldDesc == "" {
|
||
return fmt.Errorf("模块 %d 字段 %d 的 fieldDesc 不能为空", moduleIndex+1, i+1)
|
||
}
|
||
if field.FieldType == "" {
|
||
return fmt.Errorf("模块 %d 字段 %d 的 fieldType 不能为空", moduleIndex+1, i+1)
|
||
}
|
||
if field.FieldJson == "" {
|
||
return fmt.Errorf("模块 %d 字段 %d 的 fieldJson 不能为空", moduleIndex+1, i+1)
|
||
}
|
||
if field.ColumnName == "" {
|
||
return fmt.Errorf("模块 %d 字段 %d 的 columnName 不能为空", moduleIndex+1, i+1)
|
||
}
|
||
|
||
// 验证字段类型
|
||
validFieldTypes := []string{"string", "int", "int64", "float64", "bool", "time.Time", "enum", "picture", "video", "file", "pictures", "array", "richtext", "json"}
|
||
validType := false
|
||
for _, validFieldType := range validFieldTypes {
|
||
if field.FieldType == validFieldType {
|
||
validType = true
|
||
break
|
||
}
|
||
}
|
||
if !validType {
|
||
return fmt.Errorf("模块 %d 字段 %d 的 fieldType '%s' 不支持,支持的类型:%v", moduleIndex+1, i+1, field.FieldType, validFieldTypes)
|
||
}
|
||
|
||
// 验证搜索类型(如果设置了)
|
||
if field.FieldSearchType != "" {
|
||
validSearchTypes := []string{"=", "!=", ">", ">=", "<", "<=", "LIKE", "BETWEEN", "IN", "NOT IN"}
|
||
validSearchType := false
|
||
for _, validType := range validSearchTypes {
|
||
if field.FieldSearchType == validType {
|
||
validSearchType = true
|
||
break
|
||
}
|
||
}
|
||
if !validSearchType {
|
||
return fmt.Errorf("模块 %d 字段 %d 的 fieldSearchType '%s' 不支持,支持的类型:%v", moduleIndex+1, i+1, field.FieldSearchType, validSearchTypes)
|
||
}
|
||
}
|
||
|
||
// 验证 dataSource 字段配置
|
||
if field.DataSource != nil {
|
||
associationValue := field.DataSource.Association
|
||
// 当 association 为 2(一对多关联)时,强制修改 fieldType 为 array
|
||
if associationValue == 2 {
|
||
if field.FieldType != "array" {
|
||
if g.logger != nil {
|
||
g.logger.Info(fmt.Sprintf("模块 %d 字段 %d:检测到一对多关联(association=2),自动将 fieldType 从 '%s' 修改为 'array'", moduleIndex+1, i+1, field.FieldType))
|
||
}
|
||
moduleInfo.Fields[i].FieldType = "array"
|
||
}
|
||
}
|
||
|
||
// 验证 association 值的有效性
|
||
if associationValue != 1 && associationValue != 2 {
|
||
return fmt.Errorf("模块 %d 字段 %d 的 dataSource.association 必须是 1(一对一)或 2(一对多)", moduleIndex+1, i+1)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 验证主键设置
|
||
if !moduleInfo.GvaModel {
|
||
// 当不使用默认模型时,必须有且仅有一个字段设置为主键
|
||
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 {
|
||
// 当使用默认模型时,所有字段的primaryKey都应该为false
|
||
for i, field := range moduleInfo.Fields {
|
||
if field.PrimaryKey {
|
||
return fmt.Errorf("模块 %d:当 gvaModel=true 时,字段 %d 的 primaryKey 应该为 false,系统会自动创建ID主键", moduleIndex+1, i+1)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// executeCreation 执行创建操作
|
||
func (g *KRAExecutor) executeCreation(ctx context.Context, plan *ExecutionPlan) *ExecuteResponse {
|
||
result := &ExecuteResponse{
|
||
Success: false,
|
||
Paths: make(map[string]string),
|
||
GeneratedPaths: []string{}, // 初始化生成文件路径列表
|
||
}
|
||
|
||
// 无论如何都先构建目录结构信息,确保paths始终返回
|
||
result.Paths = g.buildDirectoryStructure(plan)
|
||
|
||
// 记录预期生成的文件路径
|
||
result.GeneratedPaths = g.collectExpectedFilePaths(plan)
|
||
|
||
if !plan.NeedCreatedModules {
|
||
result.Success = true
|
||
result.Message += "已列出当前功能所涉及的目录结构信息; 请在paths中查看; 并且在对应指定文件中实现相关的业务逻辑; "
|
||
return result
|
||
}
|
||
|
||
// 创建包(如果需要)
|
||
if plan.NeedCreatedPackage && plan.PackageInfo != nil {
|
||
pkg := model.SysAutoCodePackage{
|
||
PackageName: plan.PackageInfo.PackageName,
|
||
Template: plan.PackageInfo.Template,
|
||
Label: plan.PackageInfo.Label,
|
||
Desc: plan.PackageInfo.Desc,
|
||
}
|
||
err := g.db.Create(&pkg).Error
|
||
if err != nil {
|
||
result.Message = fmt.Sprintf("创建包失败: %v", err)
|
||
// 即使创建包失败,也要返回paths信息
|
||
return result
|
||
}
|
||
result.Message += "包创建成功; "
|
||
}
|
||
|
||
// 创建指定字典(如果需要)
|
||
if plan.NeedCreatedDictionaries && len(plan.DictionariesInfo) > 0 {
|
||
dictResult := g.createDictionariesFromInfo(ctx, plan.DictionariesInfo)
|
||
result.Message += dictResult
|
||
}
|
||
|
||
// 批量创建模块(如果需要)- 注意:这里只是记录,实际代码生成需要调用模板服务
|
||
if plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 {
|
||
for _, moduleInfo := range plan.ModulesInfo {
|
||
// 创建历史记录
|
||
history := model.SysAutoCodeHistory{
|
||
Package: moduleInfo.Package,
|
||
TblName: moduleInfo.TableName,
|
||
StructName: moduleInfo.StructName,
|
||
Description: moduleInfo.Description,
|
||
Abbreviation: moduleInfo.Abbreviation,
|
||
}
|
||
err := g.db.Create(&history).Error
|
||
if err != nil {
|
||
result.Message += fmt.Sprintf("创建模块 %s 历史记录失败: %v; ", moduleInfo.StructName, err)
|
||
continue
|
||
}
|
||
result.Message += fmt.Sprintf("模块 %s 记录创建成功; ", moduleInfo.StructName)
|
||
}
|
||
|
||
result.Message += fmt.Sprintf("批量创建完成,共处理 %d 个模块; ", len(plan.ModulesInfo))
|
||
|
||
// 添加重要提醒:不要使用其他MCP工具
|
||
result.Message += "\n\n⚠️ 重要提醒:\n"
|
||
result.Message += "模块创建已完成,API和菜单已自动生成。请不要再调用以下MCP工具:\n"
|
||
result.Message += "- api_creator:API权限已在模块创建时自动生成\n"
|
||
result.Message += "- menu_creator:前端菜单已在模块创建时自动生成\n"
|
||
result.Message += "如需修改API或菜单,请直接在系统管理界面中进行配置。\n"
|
||
}
|
||
|
||
result.Message += "已构建目录结构信息; "
|
||
result.Success = true
|
||
|
||
if result.Message == "" {
|
||
result.Message = "执行计划完成"
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// buildDirectoryStructure 构建目录结构信息
|
||
func (g *KRAExecutor) buildDirectoryStructure(plan *ExecutionPlan) map[string]string {
|
||
paths := make(map[string]string)
|
||
|
||
// 获取配置信息
|
||
var rootPath, serverPath, webPath, moduleName string
|
||
if g.config != nil {
|
||
rootPath = g.config.Root
|
||
serverPath = g.config.Server
|
||
webPath = g.config.Web
|
||
moduleName = g.config.Module
|
||
} else {
|
||
// 默认配置
|
||
rootPath = "."
|
||
serverPath = "server"
|
||
webPath = "web"
|
||
moduleName = "kra"
|
||
}
|
||
|
||
// 如果计划中有包名,使用计划中的包名,否则使用默认
|
||
packageName := "example"
|
||
if plan.PackageName != "" {
|
||
packageName = plan.PackageName
|
||
}
|
||
|
||
// 如果计划中有模块信息,获取第一个模块的结构名作为默认值
|
||
structName := "ExampleStruct"
|
||
if len(plan.ModulesInfo) > 0 && plan.ModulesInfo[0].StructName != "" {
|
||
structName = plan.ModulesInfo[0].StructName
|
||
}
|
||
|
||
// 根据包类型构建不同的路径结构
|
||
packageType := plan.PackageType
|
||
if packageType == "" {
|
||
packageType = "package" // 默认为package模式
|
||
}
|
||
|
||
// 构建服务端路径
|
||
if serverPath != "" {
|
||
serverBasePath := fmt.Sprintf("%s/%s", rootPath, serverPath)
|
||
|
||
if packageType == "plugin" {
|
||
// Plugin 模式:所有文件都在 /plugin/packageName/ 目录下
|
||
plugingBasePath := fmt.Sprintf("%s/plugin/%s", serverBasePath, packageName)
|
||
|
||
paths["api"] = fmt.Sprintf("%s/api", plugingBasePath)
|
||
paths["service"] = fmt.Sprintf("%s/service", plugingBasePath)
|
||
paths["model"] = fmt.Sprintf("%s/model", plugingBasePath)
|
||
paths["router"] = fmt.Sprintf("%s/router", plugingBasePath)
|
||
paths["request"] = fmt.Sprintf("%s/model/request", plugingBasePath)
|
||
paths["response"] = fmt.Sprintf("%s/model/response", plugingBasePath)
|
||
paths["plugin_main"] = fmt.Sprintf("%s/main.go", plugingBasePath)
|
||
paths["plugin_config"] = fmt.Sprintf("%s/plugin.go", plugingBasePath)
|
||
paths["plugin_initialize"] = fmt.Sprintf("%s/initialize", plugingBasePath)
|
||
} else {
|
||
// Package 模式:传统的目录结构
|
||
paths["api"] = fmt.Sprintf("%s/api/v1/%s", serverBasePath, packageName)
|
||
paths["service"] = fmt.Sprintf("%s/service/%s", serverBasePath, packageName)
|
||
paths["model"] = fmt.Sprintf("%s/model/%s", serverBasePath, packageName)
|
||
paths["router"] = fmt.Sprintf("%s/router/%s", serverBasePath, packageName)
|
||
paths["request"] = fmt.Sprintf("%s/model/%s/request", serverBasePath, packageName)
|
||
paths["response"] = fmt.Sprintf("%s/model/%s/response", serverBasePath, packageName)
|
||
}
|
||
}
|
||
|
||
// 构建前端路径
|
||
if webPath != "" {
|
||
webBasePath := fmt.Sprintf("%s/%s", rootPath, webPath)
|
||
|
||
if packageType == "plugin" {
|
||
pluginWebBasePath := fmt.Sprintf("%s/plugin/%s", webBasePath, packageName)
|
||
paths["vue_page"] = fmt.Sprintf("%s/view", pluginWebBasePath)
|
||
paths["vue_api"] = fmt.Sprintf("%s/api", pluginWebBasePath)
|
||
} else {
|
||
paths["vue_page"] = fmt.Sprintf("%s/view/%s", webBasePath, packageName)
|
||
paths["vue_api"] = fmt.Sprintf("%s/api/%s", webBasePath, packageName)
|
||
}
|
||
}
|
||
|
||
// 添加模块信息
|
||
paths["module"] = moduleName
|
||
paths["package_name"] = packageName
|
||
paths["package_type"] = packageType
|
||
paths["struct_name"] = structName
|
||
paths["root_path"] = rootPath
|
||
paths["server_path"] = serverPath
|
||
paths["web_path"] = webPath
|
||
|
||
return paths
|
||
}
|
||
|
||
// collectExpectedFilePaths 收集预期生成的文件路径
|
||
func (g *KRAExecutor) collectExpectedFilePaths(plan *ExecutionPlan) []string {
|
||
var paths []string
|
||
|
||
// 获取目录结构
|
||
dirPaths := g.buildDirectoryStructure(plan)
|
||
|
||
// 如果需要创建模块,添加预期的文件路径
|
||
if plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 {
|
||
for _, moduleInfo := range plan.ModulesInfo {
|
||
structName := moduleInfo.StructName
|
||
|
||
// 后端文件
|
||
if apiPath, ok := dirPaths["api"]; ok {
|
||
paths = append(paths, fmt.Sprintf("%s/%s.go", apiPath, strings.ToLower(structName)))
|
||
}
|
||
if servicePath, ok := dirPaths["service"]; ok {
|
||
paths = append(paths, fmt.Sprintf("%s/%s.go", servicePath, strings.ToLower(structName)))
|
||
}
|
||
if modelPath, ok := dirPaths["model"]; ok {
|
||
paths = append(paths, fmt.Sprintf("%s/%s.go", modelPath, strings.ToLower(structName)))
|
||
}
|
||
if routerPath, ok := dirPaths["router"]; ok {
|
||
paths = append(paths, fmt.Sprintf("%s/%s.go", routerPath, strings.ToLower(structName)))
|
||
}
|
||
if requestPath, ok := dirPaths["request"]; ok {
|
||
paths = append(paths, fmt.Sprintf("%s/%s.go", requestPath, strings.ToLower(structName)))
|
||
}
|
||
if responsePath, ok := dirPaths["response"]; ok {
|
||
paths = append(paths, fmt.Sprintf("%s/%s.go", responsePath, strings.ToLower(structName)))
|
||
}
|
||
|
||
// 前端文件
|
||
if vuePage, ok := dirPaths["vue_page"]; ok {
|
||
paths = append(paths, fmt.Sprintf("%s/%s.vue", vuePage, strings.ToLower(structName)))
|
||
}
|
||
if vueApi, ok := dirPaths["vue_api"]; ok {
|
||
paths = append(paths, fmt.Sprintf("%s/%s.js", vueApi, strings.ToLower(structName)))
|
||
}
|
||
}
|
||
}
|
||
|
||
return paths
|
||
}
|
||
|
||
// checkDictionaryExists 检查字典是否存在
|
||
func (g *KRAExecutor) checkDictionaryExists(dictType string) (bool, error) {
|
||
var count int64
|
||
err := g.db.Model(&model.SysDictionary{}).Where("type = ?", dictType).Count(&count).Error
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
return count > 0, nil
|
||
}
|
||
|
||
// createDictionariesFromInfo 根据 DictionariesInfo 创建字典
|
||
func (g *KRAExecutor) createDictionariesFromInfo(ctx context.Context, dictionariesInfo []*DictionaryGenerateRequest) string {
|
||
var messages []string
|
||
|
||
messages = append(messages, fmt.Sprintf("开始创建 %d 个指定字典: ", len(dictionariesInfo)))
|
||
|
||
for _, dictInfo := range dictionariesInfo {
|
||
// 检查字典是否存在
|
||
exists, err := g.checkDictionaryExists(dictInfo.DictType)
|
||
if err != nil {
|
||
messages = append(messages, fmt.Sprintf("检查字典 %s 时出错: %v; ", dictInfo.DictType, err))
|
||
continue
|
||
}
|
||
|
||
if !exists {
|
||
// 字典不存在,创建字典
|
||
dictionary := model.SysDictionary{
|
||
Name: dictInfo.DictName,
|
||
Type: dictInfo.DictType,
|
||
Status: true,
|
||
Desc: dictInfo.Description,
|
||
}
|
||
|
||
err = g.db.Create(&dictionary).Error
|
||
if err != nil {
|
||
messages = append(messages, fmt.Sprintf("创建字典 %s 失败: %v; ", dictInfo.DictType, err))
|
||
continue
|
||
}
|
||
|
||
messages = append(messages, fmt.Sprintf("成功创建字典 %s (%s); ", dictInfo.DictType, dictInfo.DictName))
|
||
|
||
// 获取刚创建的字典ID
|
||
var createdDict model.SysDictionary
|
||
err = g.db.Where("type = ?", dictInfo.DictType).First(&createdDict).Error
|
||
if err != nil {
|
||
messages = append(messages, fmt.Sprintf("获取创建的字典失败: %v; ", err))
|
||
continue
|
||
}
|
||
|
||
// 创建字典选项
|
||
if len(dictInfo.Options) > 0 {
|
||
successCount := 0
|
||
for _, option := range dictInfo.Options {
|
||
dictionaryDetail := model.SysDictionaryDetail{
|
||
Label: option.Label,
|
||
Value: option.Value,
|
||
Status: true,
|
||
Sort: int64(option.Sort),
|
||
SysDictionaryID: createdDict.ID,
|
||
}
|
||
|
||
err = g.db.Create(&dictionaryDetail).Error
|
||
if err != nil {
|
||
if g.logger != nil {
|
||
g.logger.Warn("创建字典详情项失败", zap.Error(err))
|
||
}
|
||
} else {
|
||
successCount++
|
||
}
|
||
}
|
||
messages = append(messages, fmt.Sprintf("创建了 %d 个字典选项; ", successCount))
|
||
}
|
||
} else {
|
||
messages = append(messages, fmt.Sprintf("字典 %s 已存在,跳过创建; ", dictInfo.DictType))
|
||
}
|
||
}
|
||
|
||
return strings.Join(messages, "")
|
||
}
|