1124 lines
33 KiB
Go
1124 lines
33 KiB
Go
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,
|
||
}
|
||
}
|