package system import ( "context" "errors" "fmt" "go/ast" "go/format" "go/parser" "go/token" "io" "os" "path/filepath" "strings" "kra/internal/biz/system" "kra/internal/conf" astUtils "kra/pkg/utils/ast" "gorm.io/gorm" ) type SysAutoCodePackage struct { gorm.Model Desc string `json:"desc" gorm:"comment:描述"` Label string `json:"label" gorm:"comment:展示名"` Template string `json:"template" gorm:"comment:模版"` PackageName string `json:"packageName" gorm:"comment:包名"` Module string `json:"-" gorm:"comment:模块"` } func (SysAutoCodePackage) TableName() string { return "sys_auto_code_packages" } type autoCodePackageRepo struct { db *gorm.DB config *conf.AutoCodeConfig } func NewAutoCodePackageRepo(db *gorm.DB, config *conf.AutoCodeConfig) system.AutoCodePackageRepo { return &autoCodePackageRepo{db: db, config: config} } func (r *autoCodePackageRepo) Create(ctx context.Context, pkg *system.SysAutoCodePackage) error { entity := &SysAutoCodePackage{Model: gorm.Model{ID: pkg.ID}, Desc: pkg.Desc, Label: pkg.Label, Template: pkg.Template, PackageName: pkg.PackageName, Module: pkg.Module} if err := r.db.WithContext(ctx).Create(entity).Error; err != nil { return err } pkg.ID = entity.ID return nil } func (r *autoCodePackageRepo) Delete(ctx context.Context, id uint) error { return r.db.WithContext(ctx).Delete(&SysAutoCodePackage{}, id).Error } func (r *autoCodePackageRepo) DeleteByNames(ctx context.Context, names []string) error { if len(names) == 0 { return nil } return r.db.WithContext(ctx).Where("package_name IN ?", names).Delete(&SysAutoCodePackage{}).Error } func (r *autoCodePackageRepo) All(ctx context.Context) ([]system.SysAutoCodePackage, error) { var entities []SysAutoCodePackage if err := r.db.WithContext(ctx).Find(&entities).Error; err != nil { return nil, err } result := make([]system.SysAutoCodePackage, len(entities)) for i, m := range entities { result[i] = system.SysAutoCodePackage{ID: m.ID, Desc: m.Desc, Label: m.Label, Template: m.Template, PackageName: m.PackageName, Module: m.Module} } return result, nil } func (r *autoCodePackageRepo) First(ctx context.Context, packageName, template string) (*system.SysAutoCodePackage, error) { var entity SysAutoCodePackage err := r.db.WithContext(ctx).Where("package_name = ? AND template = ?", packageName, template).First(&entity).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } return nil, err } return &system.SysAutoCodePackage{ID: entity.ID, Desc: entity.Desc, Label: entity.Label, Template: entity.Template, PackageName: entity.PackageName, Module: entity.Module}, nil } func (r *autoCodePackageRepo) FindByPackageName(ctx context.Context, packageName string) (*system.SysAutoCodePackage, error) { var entity SysAutoCodePackage err := r.db.WithContext(ctx).Where("package_name = ?", packageName).First(&entity).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } return nil, err } return &system.SysAutoCodePackage{ID: entity.ID, Desc: entity.Desc, Label: entity.Label, Template: entity.Template, PackageName: entity.PackageName, Module: entity.Module}, nil } func (r *autoCodePackageRepo) BatchCreate(ctx context.Context, pkgs []system.SysAutoCodePackage) error { if len(pkgs) == 0 { return nil } list := make([]SysAutoCodePackage, len(pkgs)) for i, p := range pkgs { list[i] = SysAutoCodePackage{Model: gorm.Model{ID: p.ID}, Desc: p.Desc, Label: p.Label, Template: p.Template, PackageName: p.PackageName, Module: p.Module} } return r.db.WithContext(ctx).Create(&list).Error } func (r *autoCodePackageRepo) DeleteByIDs(ctx context.Context, ids []uint) error { if len(ids) == 0 { return nil } return r.db.WithContext(ctx).Delete(&SysAutoCodePackage{}, ids).Error } // Templates 获取模板文件映射 // 返回: dataList(模板内容), asts(AST注入), fileList(文件路径映射) func (r *autoCodePackageRepo) Templates(ctx context.Context, entity *system.SysAutoCodePackage, info interface{}, isPackage bool) (map[string]string, map[string]system.Ast, map[string]string, error) { dataList := make(map[string]string) fileList := make(map[string]string) asts := make(map[string]system.Ast) templatePath := r.config.Root if templatePath == "" { templatePath = "resource" } // 处理服务端模板 serverPath := filepath.Join(templatePath, entity.Template, "server") if _, err := os.Stat(serverPath); err == nil { if err := r.processKratosDDDServerDir(serverPath, entity, info, dataList, fileList, asts, isPackage); err != nil { return nil, nil, nil, fmt.Errorf("处理服务端模板失败: %w", err) } } // 处理前端模板 webPath := filepath.Join(templatePath, entity.Template, "web") if _, err := os.Stat(webPath); err == nil { if err := r.processKratosDDDWebDir(webPath, entity, info, dataList, fileList, isPackage); err != nil { return nil, nil, nil, fmt.Errorf("处理前端模板失败: %w", err) } } return dataList, asts, fileList, nil } // processKratosDDDServerDir 处理 Kratos DDD 服务端目录 func (r *autoCodePackageRepo) processKratosDDDServerDir(serverPath string, entity *system.SysAutoCodePackage, info interface{}, dataList, fileList map[string]string, asts map[string]system.Ast, isPackage bool) error { for _, mapping := range system.KratosDDDServerMappings { dirPath := filepath.Join(serverPath, mapping.TemplateDir) if _, err := os.Stat(dirPath); os.IsNotExist(err) { continue } switch mapping.TemplateDir { case "biz", "data", "handler", "api", "service": if err := r.processKratosDDDLayerDir(dirPath, mapping, entity, info, dataList, fileList, asts, isPackage); err != nil { return err } case "router": if err := r.processKratosDDDRouterDir(dirPath, mapping, entity, info, dataList, fileList, asts, isPackage); err != nil { return err } case "model", "model/request": if err := r.processKratosDDDModelDir(dirPath, mapping, entity, info, dataList, fileList, asts, isPackage); err != nil { return err } case "types": if err := r.processServerTypesDir(dirPath, mapping, entity, info, dataList, fileList, isPackage); err != nil { return err } } } return nil } // processKratosDDDLayerDir 处理 biz/data/handler/service 层目录 func (r *autoCodePackageRepo) processKratosDDDLayerDir(dirPath string, mapping system.TemplateDirMapping, entity *system.SysAutoCodePackage, info interface{}, dataList, fileList map[string]string, asts map[string]system.Ast, isPackage bool) error { return filepath.Walk(dirPath, func(path string, fileInfo os.FileInfo, err error) error { if err != nil || fileInfo.IsDir() { return err } if !strings.HasSuffix(fileInfo.Name(), ".tpl") { return nil } content, err := os.ReadFile(path) if err != nil { return fmt.Errorf("读取模板文件失败 %s: %w", path, err) } // 计算目标路径 targetDir := strings.ReplaceAll(mapping.TargetDir, "{package}", entity.PackageName) fileName := strings.TrimSuffix(fileInfo.Name(), ".tpl") + ".go" targetPath := filepath.Join(targetDir, fileName) dataList[path] = string(content) fileList[path] = targetPath // 添加 enter.go AST 注入 if !isPackage { r.addKratosDDDEnterAst(mapping.TemplateDir, entity, asts) // 添加 Wire Provider AST 注入 r.addKratosDDDWireProviderAst(mapping.TemplateDir, entity, info, asts) } return nil }) } // processKratosDDDRouterDir 处理 router 层目录 func (r *autoCodePackageRepo) processKratosDDDRouterDir(dirPath string, mapping system.TemplateDirMapping, entity *system.SysAutoCodePackage, info interface{}, dataList, fileList map[string]string, asts map[string]system.Ast, isPackage bool) error { return filepath.Walk(dirPath, func(path string, fileInfo os.FileInfo, err error) error { if err != nil || fileInfo.IsDir() { return err } if !strings.HasSuffix(fileInfo.Name(), ".tpl") { return nil } content, err := os.ReadFile(path) if err != nil { return fmt.Errorf("读取模板文件失败 %s: %w", path, err) } targetDir := strings.ReplaceAll(mapping.TargetDir, "{package}", entity.PackageName) fileName := strings.TrimSuffix(fileInfo.Name(), ".tpl") + ".go" targetPath := filepath.Join(targetDir, fileName) dataList[path] = string(content) fileList[path] = targetPath // 添加 router enter.go 和 init AST 注入 if !isPackage { r.addKratosDDDRouterEnterAst(entity, asts) r.addKratosDDDRouterInitAst(entity, asts) } return nil }) } // processKratosDDDModelDir 处理 model 层目录 func (r *autoCodePackageRepo) processKratosDDDModelDir(dirPath string, mapping system.TemplateDirMapping, entity *system.SysAutoCodePackage, info interface{}, dataList, fileList map[string]string, asts map[string]system.Ast, isPackage bool) error { return filepath.Walk(dirPath, func(path string, fileInfo os.FileInfo, err error) error { if err != nil || fileInfo.IsDir() { return err } if !strings.HasSuffix(fileInfo.Name(), ".tpl") { return nil } content, err := os.ReadFile(path) if err != nil { return fmt.Errorf("读取模板文件失败 %s: %w", path, err) } targetDir := strings.ReplaceAll(mapping.TargetDir, "{package}", entity.PackageName) fileName := strings.TrimSuffix(fileInfo.Name(), ".tpl") + ".go" targetPath := filepath.Join(targetDir, fileName) dataList[path] = string(content) fileList[path] = targetPath // 添加 GORM Gen AST 注入(自动更新 cmd/gen/main.go) if !isPackage { r.addKratosDDDGormGenAst(entity, info, asts) } return nil }) } // processServerTypesDir 处理 types 目录 func (r *autoCodePackageRepo) processServerTypesDir(dirPath string, mapping system.TemplateDirMapping, entity *system.SysAutoCodePackage, info interface{}, dataList, fileList map[string]string, isPackage bool) error { return filepath.Walk(dirPath, func(path string, fileInfo os.FileInfo, err error) error { if err != nil || fileInfo.IsDir() { return err } if !strings.HasSuffix(fileInfo.Name(), ".tpl") { return nil } content, err := os.ReadFile(path) if err != nil { return fmt.Errorf("读取模板文件失败 %s: %w", path, err) } targetDir := strings.ReplaceAll(mapping.TargetDir, "{package}", entity.PackageName) fileName := strings.TrimSuffix(fileInfo.Name(), ".tpl") + ".go" targetPath := filepath.Join(targetDir, fileName) dataList[path] = string(content) fileList[path] = targetPath return nil }) } // processKratosDDDWebDir 处理 Kratos DDD 前端目录 func (r *autoCodePackageRepo) processKratosDDDWebDir(webPath string, entity *system.SysAutoCodePackage, info interface{}, dataList, fileList map[string]string, isPackage bool) error { for _, mapping := range system.KratosDDDWebMappings { dirPath := filepath.Join(webPath, mapping.TemplateDir) if _, err := os.Stat(dirPath); os.IsNotExist(err) { continue } switch mapping.TemplateDir { case "pages", "view": if err := r.processWebPagesDir(dirPath, mapping, entity, info, dataList, fileList, isPackage); err != nil { return err } case "services", "api": if err := r.processWebServicesDir(dirPath, mapping, entity, info, dataList, fileList, isPackage); err != nil { return err } case "types": if err := r.processWebTypesDir(dirPath, mapping, entity, info, dataList, fileList, isPackage); err != nil { return err } } } return nil } // processWebPagesDir 处理前端页面目录 func (r *autoCodePackageRepo) processWebPagesDir(dirPath string, mapping system.TemplateDirMapping, entity *system.SysAutoCodePackage, info interface{}, dataList, fileList map[string]string, isPackage bool) error { return filepath.Walk(dirPath, func(path string, fileInfo os.FileInfo, err error) error { if err != nil || fileInfo.IsDir() { return err } if !strings.HasSuffix(fileInfo.Name(), ".tpl") { return nil } content, err := os.ReadFile(path) if err != nil { return fmt.Errorf("读取模板文件失败 %s: %w", path, err) } // 获取实体名称用于路径替换 entityName := entity.PackageName if autoInfo, ok := info.(*system.AutoCodeInfo); ok && autoInfo != nil { entityName = autoInfo.HumpPackageName } targetDir := strings.ReplaceAll(mapping.TargetDir, "{package}", entity.PackageName) targetDir = strings.ReplaceAll(targetDir, "{entity}", entityName) // 确定文件扩展名 baseName := strings.TrimSuffix(fileInfo.Name(), ".tpl") var fileName string if strings.HasSuffix(baseName, ".tsx") || strings.HasSuffix(baseName, ".vue") { fileName = baseName } else { fileName = baseName + ".tsx" } targetPath := filepath.Join(targetDir, fileName) dataList[path] = string(content) fileList[path] = targetPath return nil }) } // processWebServicesDir 处理前端服务目录 func (r *autoCodePackageRepo) processWebServicesDir(dirPath string, mapping system.TemplateDirMapping, entity *system.SysAutoCodePackage, info interface{}, dataList, fileList map[string]string, isPackage bool) error { return filepath.Walk(dirPath, func(path string, fileInfo os.FileInfo, err error) error { if err != nil || fileInfo.IsDir() { return err } if !strings.HasSuffix(fileInfo.Name(), ".tpl") { return nil } content, err := os.ReadFile(path) if err != nil { return fmt.Errorf("读取模板文件失败 %s: %w", path, err) } targetDir := mapping.TargetDir baseName := strings.TrimSuffix(fileInfo.Name(), ".tpl") var fileName string if strings.HasSuffix(baseName, ".ts") { fileName = baseName } else { fileName = baseName + ".ts" } targetPath := filepath.Join(targetDir, fileName) dataList[path] = string(content) fileList[path] = targetPath return nil }) } // processWebTypesDir 处理前端类型目录 func (r *autoCodePackageRepo) processWebTypesDir(dirPath string, mapping system.TemplateDirMapping, entity *system.SysAutoCodePackage, info interface{}, dataList, fileList map[string]string, isPackage bool) error { return filepath.Walk(dirPath, func(path string, fileInfo os.FileInfo, err error) error { if err != nil || fileInfo.IsDir() { return err } if !strings.HasSuffix(fileInfo.Name(), ".tpl") { return nil } content, err := os.ReadFile(path) if err != nil { return fmt.Errorf("读取模板文件失败 %s: %w", path, err) } targetDir := strings.ReplaceAll(mapping.TargetDir, "{package}", entity.PackageName) baseName := strings.TrimSuffix(fileInfo.Name(), ".tpl") var fileName string if strings.HasSuffix(baseName, ".ts") || strings.HasSuffix(baseName, ".d.ts") { fileName = baseName } else { fileName = baseName + ".ts" } targetPath := filepath.Join(targetDir, fileName) dataList[path] = string(content) fileList[path] = targetPath return nil }) } // addKratosDDDEnterAst 添加 enter.go AST 注入 func (r *autoCodePackageRepo) addKratosDDDEnterAst(layer string, entity *system.SysAutoCodePackage, asts map[string]system.Ast) { var enterPath string var astType string switch layer { case "biz": enterPath = "internal/biz/enter.go" astType = "PackageBizEnter" case "data": enterPath = "internal/data/enter.go" astType = "PackageDataEnter" case "handler", "api": enterPath = "internal/server/handler/enter.go" astType = "PackageApiEnter" case "service": enterPath = "internal/service/enter.go" astType = "PackageServiceEnter" default: return } key := fmt.Sprintf("%s=>%s", enterPath, astType) if _, exists := asts[key]; !exists { asts[key] = &KratosDDDEnterAst{ Layer: layer, PackageName: entity.PackageName, Type: "enter", Path: enterPath, } } } // addKratosDDDRouterEnterAst 添加 router enter.go AST 注入 func (r *autoCodePackageRepo) addKratosDDDRouterEnterAst(entity *system.SysAutoCodePackage, asts map[string]system.Ast) { enterPath := "internal/server/router/enter.go" astType := "PackageRouterEnter" key := fmt.Sprintf("%s=>%s", enterPath, astType) if _, exists := asts[key]; !exists { asts[key] = &KratosDDDRouterEnterAst{ PackageName: entity.PackageName, Path: enterPath, } } } // addKratosDDDRouterInitAst 添加 router 初始化 AST 注入 func (r *autoCodePackageRepo) addKratosDDDRouterInitAst(entity *system.SysAutoCodePackage, asts map[string]system.Ast) { initPath := "internal/server/http.go" astType := "PackageInitializeRouter" key := fmt.Sprintf("%s=>%s", initPath, astType) if _, exists := asts[key]; !exists { asts[key] = &KratosDDDRouterInitAst{ PackageName: entity.PackageName, Path: initPath, } } } // addKratosDDDGormMigrateAst 添加 GORM 迁移 AST 注入 func (r *autoCodePackageRepo) addKratosDDDGormMigrateAst(entity *system.SysAutoCodePackage, asts map[string]system.Ast) { migratePath := "internal/initialize/gorm.go" astType := "PackageInitializeGorm" key := fmt.Sprintf("%s=>%s", migratePath, astType) if _, exists := asts[key]; !exists { asts[key] = &KratosDDDGormMigrateAst{ PackageName: entity.PackageName, Path: migratePath, } } } // KratosDDDEnterAst enter.go AST 注入结构体 type KratosDDDEnterAst struct { Layer string PackageName string Type string Path string // 文件路径 } // Parse 解析文件 func (a *KratosDDDEnterAst) Parse(filename string, writer io.Writer) (*ast.File, error) { if filename == "" { filename = a.Path } if filename == "" { return nil, fmt.Errorf("文件路径为空") } fset := token.NewFileSet() file, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) if err != nil { return nil, fmt.Errorf("解析文件失败 %s: %w", filename, err) } return file, nil } // Rollback 回滚注入 func (a *KratosDDDEnterAst) Rollback(file *ast.File) error { // 移除导入 importPath := fmt.Sprintf("kra/internal/%s/%s", a.Layer, a.PackageName) astUtils.RemoveImport(file, importPath) // 移除结构体字段 structName := a.getStructName() fieldName := strings.Title(a.PackageName) astUtils.RemoveStructField(file, structName, fieldName) return nil } // Injection 注入代码 func (a *KratosDDDEnterAst) Injection(file *ast.File) error { // 添加导入 importPath := fmt.Sprintf("kra/internal/%s/%s", a.Layer, a.PackageName) astUtils.AddImport(file, importPath) // 添加结构体字段 structName := a.getStructName() fieldName := strings.Title(a.PackageName) fieldType := fmt.Sprintf("%s.Group", a.PackageName) astUtils.AddStructField(file, structName, fieldName, fieldType) return nil } // Format 格式化输出 func (a *KratosDDDEnterAst) Format(filename string, writer io.Writer, file *ast.File) error { if filename == "" { filename = a.Path } fset := token.NewFileSet() return format.Node(writer, fset, file) } // getStructName 获取结构体名称 func (a *KratosDDDEnterAst) getStructName() string { switch a.Layer { case "biz": return "BizGroup" case "data": return "DataGroup" case "handler", "api": return "HandlerGroup" case "service": return "ServiceGroup" default: return "Group" } } // KratosDDDRouterEnterAst router enter.go AST 注入结构体 type KratosDDDRouterEnterAst struct { PackageName string Path string // 文件路径 } // Parse 解析文件 func (a *KratosDDDRouterEnterAst) Parse(filename string, writer io.Writer) (*ast.File, error) { if filename == "" { filename = a.Path } if filename == "" { return nil, fmt.Errorf("文件路径为空") } fset := token.NewFileSet() file, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) if err != nil { return nil, fmt.Errorf("解析文件失败 %s: %w", filename, err) } return file, nil } // Rollback 回滚注入 func (a *KratosDDDRouterEnterAst) Rollback(file *ast.File) error { importPath := fmt.Sprintf("kra/internal/server/router/%s", a.PackageName) astUtils.RemoveImport(file, importPath) fieldName := strings.Title(a.PackageName) astUtils.RemoveStructField(file, "RouterGroup", fieldName) return nil } // Injection 注入代码 func (a *KratosDDDRouterEnterAst) Injection(file *ast.File) error { importPath := fmt.Sprintf("kra/internal/server/router/%s", a.PackageName) astUtils.AddImport(file, importPath) fieldName := strings.Title(a.PackageName) fieldType := fmt.Sprintf("%s.RouterGroup", a.PackageName) astUtils.AddStructField(file, "RouterGroup", fieldName, fieldType) return nil } // Format 格式化输出 func (a *KratosDDDRouterEnterAst) Format(filename string, writer io.Writer, file *ast.File) error { if filename == "" { filename = a.Path } fset := token.NewFileSet() return format.Node(writer, fset, file) } // KratosDDDRouterInitAst router 初始化 AST 注入结构体 type KratosDDDRouterInitAst struct { PackageName string Path string // 文件路径 } // Parse 解析文件 func (a *KratosDDDRouterInitAst) Parse(filename string, writer io.Writer) (*ast.File, error) { if filename == "" { filename = a.Path } if filename == "" { return nil, fmt.Errorf("文件路径为空") } fset := token.NewFileSet() file, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) if err != nil { return nil, fmt.Errorf("解析文件失败 %s: %w", filename, err) } return file, nil } // Rollback 回滚注入 func (a *KratosDDDRouterInitAst) Rollback(file *ast.File) error { // 移除路由初始化调用 funcName := fmt.Sprintf("Init%sRouter", strings.Title(a.PackageName)) astUtils.RemoveFuncCall(file, "initRouter", funcName) return nil } // Injection 注入代码 func (a *KratosDDDRouterInitAst) Injection(file *ast.File) error { // 添加路由初始化调用 funcName := fmt.Sprintf("Init%sRouter", strings.Title(a.PackageName)) callExpr := fmt.Sprintf("router.%s.%s(group)", strings.Title(a.PackageName), funcName) astUtils.AddFuncCall(file, "initRouter", callExpr) return nil } // Format 格式化输出 func (a *KratosDDDRouterInitAst) Format(filename string, writer io.Writer, file *ast.File) error { if filename == "" { filename = a.Path } fset := token.NewFileSet() return format.Node(writer, fset, file) } // KratosDDDGormMigrateAst GORM 迁移 AST 注入结构体 type KratosDDDGormMigrateAst struct { PackageName string Path string // 文件路径 } // Parse 解析文件 func (a *KratosDDDGormMigrateAst) Parse(filename string, writer io.Writer) (*ast.File, error) { if filename == "" { filename = a.Path } if filename == "" { return nil, fmt.Errorf("文件路径为空") } fset := token.NewFileSet() file, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) if err != nil { return nil, fmt.Errorf("解析文件失败 %s: %w", filename, err) } return file, nil } // Rollback 回滚注入 func (a *KratosDDDGormMigrateAst) Rollback(file *ast.File) error { // 移除模型导入 importPath := fmt.Sprintf("kra/internal/data/model/%s", a.PackageName) astUtils.RemoveImport(file, importPath) // 移除 AutoMigrate 调用中的模型 modelName := fmt.Sprintf("%s.%s", a.PackageName, strings.Title(a.PackageName)) astUtils.RemoveAutoMigrateModel(file, modelName) return nil } // Injection 注入代码 func (a *KratosDDDGormMigrateAst) Injection(file *ast.File) error { // 添加模型导入 importPath := fmt.Sprintf("kra/internal/data/model/%s", a.PackageName) astUtils.AddImport(file, importPath) // 添加 AutoMigrate 调用 modelName := fmt.Sprintf("&%s.%s{}", a.PackageName, strings.Title(a.PackageName)) astUtils.AddAutoMigrateModel(file, modelName) return nil } // Format 格式化输出 func (a *KratosDDDGormMigrateAst) Format(filename string, writer io.Writer, file *ast.File) error { if filename == "" { filename = a.Path } fset := token.NewFileSet() return format.Node(writer, fset, file) } // KratosDDDWireProviderAst Wire Provider AST 注入结构体 // 用于向各层的主 ProviderSet 中注入新包的 Provider type KratosDDDWireProviderAst struct { Layer string // 层名称: biz, data, service, handler, router PackageName string // 包名 StructName string // 结构体名 Module string // 模块名 (go.mod 中的 module) Path string // 目标文件路径 } // Parse 解析文件 func (a *KratosDDDWireProviderAst) Parse(filename string, writer io.Writer) (*ast.File, error) { if filename == "" { filename = a.Path } if filename == "" { return nil, fmt.Errorf("文件路径为空") } fset := token.NewFileSet() file, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) if err != nil { return nil, fmt.Errorf("解析文件失败 %s: %w", filename, err) } return file, nil } // Rollback 回滚注入 func (a *KratosDDDWireProviderAst) Rollback(file *ast.File) error { // 移除导入 importPath := a.getImportPath() astUtils.RemoveImport(file, importPath) // 移除 Provider providerName := a.getProviderName() return a.removeWireProvider(file, a.PackageName, providerName) } // Injection 注入代码 func (a *KratosDDDWireProviderAst) Injection(file *ast.File) error { // 添加导入 importPath := a.getImportPath() astUtils.AddImport(file, importPath) // 添加 Provider providerName := a.getProviderName() return a.addWireProvider(file, a.PackageName, providerName) } // Format 格式化输出 func (a *KratosDDDWireProviderAst) Format(filename string, writer io.Writer, file *ast.File) error { if filename == "" { filename = a.Path } fset := token.NewFileSet() return format.Node(writer, fset, file) } // getImportPath 获取导入路径 func (a *KratosDDDWireProviderAst) getImportPath() string { module := a.Module if module == "" { module = "kra" } switch a.Layer { case "biz": return fmt.Sprintf("%s/internal/biz/%s", module, a.PackageName) case "data": return fmt.Sprintf("%s/internal/data/%s", module, a.PackageName) case "service": return fmt.Sprintf("%s/internal/service/%s", module, a.PackageName) case "handler", "api": return fmt.Sprintf("%s/internal/server/handler/%s", module, a.PackageName) case "router": return fmt.Sprintf("%s/internal/server/router/%s", module, a.PackageName) default: return "" } } // getProviderName 获取 Provider 名称 func (a *KratosDDDWireProviderAst) getProviderName() string { structName := a.StructName if structName == "" { structName = strings.Title(a.PackageName) } switch a.Layer { case "biz": return fmt.Sprintf("New%sUsecase", structName) case "data": return fmt.Sprintf("New%sRepo", structName) case "service": return fmt.Sprintf("New%sService", structName) case "handler", "api": return fmt.Sprintf("New%sHandler", structName) case "router": return fmt.Sprintf("New%sRouter", structName) default: return "" } } // addWireProvider 向 wire.NewSet 中添加带包名的 Provider func (a *KratosDDDWireProviderAst) addWireProvider(file *ast.File, packageName, providerName string) error { var found bool ast.Inspect(file, func(node ast.Node) bool { genDecl, ok := node.(*ast.GenDecl) if !ok || genDecl.Tok != token.VAR { return true } for _, spec := range genDecl.Specs { valueSpec, ok := spec.(*ast.ValueSpec) if !ok { continue } for i, name := range valueSpec.Names { if name.Name != "ProviderSet" { continue } if i >= len(valueSpec.Values) { continue } callExpr, ok := valueSpec.Values[i].(*ast.CallExpr) if !ok { continue } selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) if !ok { continue } ident, ok := selExpr.X.(*ast.Ident) if !ok || ident.Name != "wire" || selExpr.Sel.Name != "NewSet" { continue } // 构建完整的 Provider 名称 fullProviderName := fmt.Sprintf("%s.%s", packageName, providerName) // 检查 Provider 是否已存在 for _, arg := range callExpr.Args { if selArg, ok := arg.(*ast.SelectorExpr); ok { if pkgIdent, ok := selArg.X.(*ast.Ident); ok { existingName := fmt.Sprintf("%s.%s", pkgIdent.Name, selArg.Sel.Name) if existingName == fullProviderName { found = true return false // 已存在,不需要添加 } } } } // 添加新的 Provider (package.ProviderName 格式) newArg := &ast.SelectorExpr{ X: &ast.Ident{Name: packageName}, Sel: &ast.Ident{Name: providerName}, } callExpr.Args = append(callExpr.Args, newArg) found = true return false } } return true }) if !found { return fmt.Errorf("未找到 ProviderSet 变量或 wire.NewSet 调用") } return nil } // removeWireProvider 从 wire.NewSet 中移除带包名的 Provider func (a *KratosDDDWireProviderAst) removeWireProvider(file *ast.File, packageName, providerName string) error { fullProviderName := fmt.Sprintf("%s.%s", packageName, providerName) ast.Inspect(file, func(node ast.Node) bool { genDecl, ok := node.(*ast.GenDecl) if !ok || genDecl.Tok != token.VAR { return true } for _, spec := range genDecl.Specs { valueSpec, ok := spec.(*ast.ValueSpec) if !ok { continue } for i, name := range valueSpec.Names { if name.Name != "ProviderSet" { continue } if i >= len(valueSpec.Values) { continue } callExpr, ok := valueSpec.Values[i].(*ast.CallExpr) if !ok { continue } selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) if !ok { continue } ident, ok := selExpr.X.(*ast.Ident) if !ok || ident.Name != "wire" || selExpr.Sel.Name != "NewSet" { continue } // 移除指定的 Provider newArgs := make([]ast.Expr, 0, len(callExpr.Args)) for _, arg := range callExpr.Args { if selArg, ok := arg.(*ast.SelectorExpr); ok { if pkgIdent, ok := selArg.X.(*ast.Ident); ok { existingName := fmt.Sprintf("%s.%s", pkgIdent.Name, selArg.Sel.Name) if existingName == fullProviderName { continue // 跳过要移除的 Provider } } } newArgs = append(newArgs, arg) } callExpr.Args = newArgs return false } } return true }) return nil } // addKratosDDDWireProviderAst 添加 Wire Provider AST 注入 func (r *autoCodePackageRepo) addKratosDDDWireProviderAst(layer string, entity *system.SysAutoCodePackage, info interface{}, asts map[string]system.Ast) { var mainFilePath string var astType string switch layer { case "biz": mainFilePath = "internal/biz/biz.go" astType = "PackageBizWireProvider" case "data": mainFilePath = "internal/data/data.go" astType = "PackageDataWireProvider" case "service": mainFilePath = "internal/service/service.go" astType = "PackageServiceWireProvider" case "handler", "api": // Handler 层不需要注入到主文件,因为它使用 enter.go 的 Group 结构 return case "router": // Router 层不需要注入到主文件,因为它使用 enter.go 的 RouterGroup 结构 return default: return } // 获取结构体名 structName := "" if autoInfo, ok := info.(*system.AutoCodeInfo); ok && autoInfo != nil { structName = autoInfo.StructName } if structName == "" { structName = strings.Title(entity.PackageName) } key := fmt.Sprintf("%s=>%s", mainFilePath, astType) if _, exists := asts[key]; !exists { asts[key] = &KratosDDDWireProviderAst{ Layer: layer, PackageName: entity.PackageName, StructName: structName, Module: entity.Module, Path: mainFilePath, } } } // GetLayerMainFilePath 获取层的主文件路径 func GetLayerMainFilePath(layer string) string { switch layer { case "biz": return "internal/biz/biz.go" case "data": return "internal/data/data.go" case "service": return "internal/service/service.go" case "handler", "api": return "internal/server/handler/enter.go" case "router": return "internal/server/router/enter.go" default: return "" } } // KratosDDDGormGenAst GORM Gen AST 注入结构体 // 用于自动更新 cmd/gen/main.go 文件,添加新模型到 GORM Gen 配置 type KratosDDDGormGenAst struct { TableName string // 数据库表名 StructName string // 结构体名 PackageName string // 包名 Path string // 目标文件路径 (cmd/gen/main.go) } // Parse 解析文件 func (a *KratosDDDGormGenAst) Parse(filename string, writer io.Writer) (*ast.File, error) { if filename == "" { filename = a.Path } if filename == "" { filename = "cmd/gen/main.go" } fset := token.NewFileSet() file, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) if err != nil { return nil, fmt.Errorf("解析文件失败 %s: %w", filename, err) } return file, nil } // Rollback 回滚注入 func (a *KratosDDDGormGenAst) Rollback(file *ast.File) error { // 从 g.ApplyBasic 调用中移除模型 astUtils.RemoveGormGenModel(file, a.TableName) return nil } // Injection 注入代码 func (a *KratosDDDGormGenAst) Injection(file *ast.File) error { // 检查模型是否已存在 if astUtils.CheckGormGenModelExists(file, a.TableName) { return nil // 已存在,不需要添加 } // 向 g.ApplyBasic 调用中添加新模型 astUtils.AddGormGenModel(file, a.TableName) return nil } // Format 格式化输出 func (a *KratosDDDGormGenAst) Format(filename string, writer io.Writer, file *ast.File) error { if filename == "" { filename = a.Path } fset := token.NewFileSet() return format.Node(writer, fset, file) } // addKratosDDDGormGenAst 添加 GORM Gen AST 注入 func (r *autoCodePackageRepo) addKratosDDDGormGenAst(entity *system.SysAutoCodePackage, info interface{}, asts map[string]system.Ast) { genPath := "cmd/gen/main.go" astType := "PackageGormGen" key := fmt.Sprintf("%s=>%s", genPath, astType) if _, exists := asts[key]; exists { return } // 获取表名和结构体名 tableName := "" structName := "" if autoInfo, ok := info.(*system.AutoCodeInfo); ok && autoInfo != nil { tableName = autoInfo.TableName structName = autoInfo.StructName } if tableName == "" { tableName = entity.PackageName } if structName == "" { structName = strings.Title(entity.PackageName) } asts[key] = &KratosDDDGormGenAst{ TableName: tableName, StructName: structName, PackageName: entity.PackageName, Path: genPath, } }