kra/internal/data/system/auto_code_package.go

1124 lines
33 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
}
}