kra/cmd/kra-gen/generate.go

409 lines
15 KiB
Go

package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
var (
// Generate command flags
interactive bool
preview bool
dbName string
tableName string
packageName string
structName string
desc string
noWeb bool
noServer bool
onlyTemplate bool
isAdd bool
isTree bool
treeJson string
)
// generateCmd represents the generate command
var generateCmd = &cobra.Command{
Use: "generate",
Short: "生成代码",
Long: `根据配置生成 Kratos DDD 架构的后端代码和 React 前端代码。
支持三种方式:
1. 配置文件方式: 使用 -c 指定 YAML/JSON 配置文件
2. 命令行参数方式: 使用各种参数直接指定配置
3. 交互模式: 使用 -i 进入交互式配置
使用示例:
# 从配置文件生成
kra-gen generate -c autocode.yaml
# 使用命令行参数
kra-gen generate --db mysql --table users --package example --struct User
# 交互模式
kra-gen generate -i
# 预览模式(不写入文件)
kra-gen generate -c autocode.yaml -p
# 仅生成模板(不注入代码)
kra-gen generate -c autocode.yaml --only-template
# 树形结构数据
kra-gen generate -c autocode.yaml --tree --tree-json name`,
Run: runGenerate,
}
func init() {
// Generate command specific flags
generateCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "交互模式")
generateCmd.Flags().BoolVarP(&preview, "preview", "p", false, "预览模式(不写入文件)")
generateCmd.Flags().StringVar(&dbName, "db", "", "数据库连接名")
generateCmd.Flags().StringVar(&tableName, "table", "", "表名")
generateCmd.Flags().StringVar(&packageName, "package", "", "包名")
generateCmd.Flags().StringVar(&structName, "struct", "", "结构体名")
generateCmd.Flags().StringVar(&desc, "desc", "", "描述")
generateCmd.Flags().BoolVar(&noWeb, "no-web", false, "不生成前端代码")
generateCmd.Flags().BoolVar(&noServer, "no-server", false, "不生成后端代码")
generateCmd.Flags().BoolVar(&onlyTemplate, "only-template", false, "仅生成模板(不注入代码到现有文件)")
generateCmd.Flags().BoolVar(&isAdd, "add", false, "追加模式(不覆盖已有文件)")
generateCmd.Flags().BoolVar(&isTree, "tree", false, "树形结构数据")
generateCmd.Flags().StringVar(&treeJson, "tree-json", "", "树形结构展示字段")
}
// AutoCodeConfig represents the configuration for code generation
type AutoCodeConfig struct {
Package string `json:"package" yaml:"package"`
StructName string `json:"structName" yaml:"structName"`
TableName string `json:"tableName" yaml:"tableName"`
Description string `json:"description" yaml:"description"`
BusinessDB string `json:"businessDB" yaml:"businessDB"`
Abbreviation string `json:"abbreviation" yaml:"abbreviation"`
Options AutoCodeOptions `json:"options" yaml:"options"`
Fields []AutoCodeField `json:"fields" yaml:"fields"`
}
// AutoCodeOptions represents generation options
type AutoCodeOptions struct {
GvaModel bool `json:"gvaModel" yaml:"gvaModel"` // 使用 KRA_MODEL (包含 ID, CreatedAt, UpdatedAt, DeletedAt)
AutoMigrate bool `json:"autoMigrate" yaml:"autoMigrate"` // 自动迁移数据库表结构
AutoCreateResource bool `json:"autoCreateResource" yaml:"autoCreateResource"` // 自动创建资源标识
AutoCreateApiToSql bool `json:"autoCreateApiToSql" yaml:"autoCreateApiToSql"` // 自动创建 API 记录
AutoCreateMenuToSql bool `json:"autoCreateMenuToSql" yaml:"autoCreateMenuToSql"` // 自动创建菜单记录
AutoCreateBtnAuth bool `json:"autoCreateBtnAuth" yaml:"autoCreateBtnAuth"` // 自动创建按钮权限
OnlyTemplate bool `json:"onlyTemplate" yaml:"onlyTemplate"` // 仅生成模板(不注入代码)
IsTree bool `json:"isTree" yaml:"isTree"` // 树形结构
TreeJson string `json:"treeJson" yaml:"treeJson"` // 树形结构展示字段
IsAdd bool `json:"isAdd" yaml:"isAdd"` // 追加模式(不覆盖已有文件)
GenerateWeb bool `json:"generateWeb" yaml:"generateWeb"` // 生成前端代码
GenerateServer bool `json:"generateServer" yaml:"generateServer"` // 生成后端代码
}
// DataSource 数据源配置
type DataSource struct {
DBName string `json:"dbName" yaml:"dbName"`
Table string `json:"table" yaml:"table"`
Label string `json:"label" yaml:"label"`
Value string `json:"value" yaml:"value"`
Association int `json:"association" yaml:"association"` // 关联关系 1 一对一 2 一对多
HasDeletedAt bool `json:"hasDeletedAt" yaml:"hasDeletedAt"`
}
// AutoCodeField represents a field configuration
type AutoCodeField struct {
FieldName string `json:"fieldName" yaml:"fieldName"`
FieldDesc string `json:"fieldDesc" yaml:"fieldDesc"`
FieldType string `json:"fieldType" yaml:"fieldType"`
FieldJson string `json:"fieldJson" yaml:"fieldJson"`
ColumnName string `json:"columnName" yaml:"columnName"`
DataTypeLong string `json:"dataTypeLong" yaml:"dataTypeLong"`
Form bool `json:"form" yaml:"form"`
Table bool `json:"table" yaml:"table"`
Desc bool `json:"desc" yaml:"desc"` // 是否前端详情
Excel bool `json:"excel" yaml:"excel"` // 是否导入/导出
Require bool `json:"require" yaml:"require"`
DefaultValue string `json:"defaultValue" yaml:"defaultValue"` // 默认值
ErrorText string `json:"errorText" yaml:"errorText"` // 校验失败文字
Clearable bool `json:"clearable" yaml:"clearable"` // 是否可清空
Sort bool `json:"sort" yaml:"sort"` // 是否增加排序
FieldSearchType string `json:"fieldSearchType" yaml:"fieldSearchType"`
FieldSearchHide bool `json:"fieldSearchHide" yaml:"fieldSearchHide"` // 是否隐藏查询条件
DictType string `json:"dictType" yaml:"dictType"`
DataSource *DataSource `json:"dataSource" yaml:"dataSource"` // 数据源
FieldIndexType string `json:"fieldIndexType" yaml:"fieldIndexType"` // 索引类型
PrimaryKey bool `json:"primaryKey" yaml:"primaryKey"` // 是否主键
}
func runGenerate(cmd *cobra.Command, args []string) {
var config *AutoCodeConfig
var err error
// Determine configuration source
if configFile != "" {
config, err = loadConfigFile(configFile)
if err != nil {
fmt.Fprintf(os.Stderr, "加载配置文件失败: %v\n", err)
os.Exit(1)
}
} else if interactive {
config, err = runInteractiveMode()
if err != nil {
fmt.Fprintf(os.Stderr, "交互模式错误: %v\n", err)
os.Exit(1)
}
} else if hasRequiredFlags() {
config = buildConfigFromFlags()
} else {
fmt.Println("请指定配置文件 (-c) 或使用交互模式 (-i) 或提供必要参数")
fmt.Println("使用 'kra-gen generate --help' 查看帮助")
os.Exit(1)
}
// Apply command line overrides
applyFlagOverrides(config)
// Validate configuration
if err := validateConfig(config); err != nil {
fmt.Fprintf(os.Stderr, "配置验证失败: %v\n", err)
os.Exit(1)
}
// Execute generation
if preview {
if err := executePreview(config); err != nil {
fmt.Fprintf(os.Stderr, "预览失败: %v\n", err)
os.Exit(1)
}
} else {
if err := executeGenerate(config); err != nil {
fmt.Fprintf(os.Stderr, "生成失败: %v\n", err)
os.Exit(1)
}
}
}
// loadConfigFile loads configuration from a YAML or JSON file
func loadConfigFile(path string) (*AutoCodeConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("读取配置文件失败: %w", err)
}
config := &AutoCodeConfig{}
ext := strings.ToLower(filepath.Ext(path))
switch ext {
case ".yaml", ".yml":
if err := yaml.Unmarshal(data, config); err != nil {
return nil, fmt.Errorf("解析 YAML 配置失败: %w", err)
}
case ".json":
if err := json.Unmarshal(data, config); err != nil {
return nil, fmt.Errorf("解析 JSON 配置失败: %w", err)
}
default:
return nil, fmt.Errorf("不支持的配置文件格式: %s", ext)
}
return config, nil
}
// hasRequiredFlags checks if required command line flags are provided
func hasRequiredFlags() bool {
return tableName != "" && packageName != "" && structName != ""
}
// buildConfigFromFlags builds configuration from command line flags
func buildConfigFromFlags() *AutoCodeConfig {
abbreviation := strings.ToLower(structName)
if len(abbreviation) > 0 {
abbreviation = strings.ToLower(string(structName[0])) + structName[1:]
}
return &AutoCodeConfig{
Package: packageName,
StructName: structName,
TableName: tableName,
Description: desc,
BusinessDB: dbName,
Abbreviation: abbreviation,
Options: AutoCodeOptions{
GvaModel: true,
AutoMigrate: true,
AutoCreateResource: false,
AutoCreateApiToSql: true,
AutoCreateMenuToSql: true,
AutoCreateBtnAuth: true,
OnlyTemplate: false,
IsTree: false,
TreeJson: "",
IsAdd: false,
GenerateWeb: !noWeb,
GenerateServer: !noServer,
},
}
}
// applyFlagOverrides applies command line flag overrides to the configuration
func applyFlagOverrides(config *AutoCodeConfig) {
if noWeb {
config.Options.GenerateWeb = false
}
if noServer {
config.Options.GenerateServer = false
}
if onlyTemplate {
config.Options.OnlyTemplate = true
}
if isAdd {
config.Options.IsAdd = true
}
if isTree {
config.Options.IsTree = true
}
if treeJson != "" {
config.Options.TreeJson = treeJson
}
}
// validateConfig validates the configuration
func validateConfig(config *AutoCodeConfig) error {
if config.Package == "" {
return fmt.Errorf("包名不能为空")
}
if config.StructName == "" {
return fmt.Errorf("结构体名不能为空")
}
if config.TableName == "" {
return fmt.Errorf("表名不能为空")
}
return nil
}
// executePreview executes preview mode
func executePreview(config *AutoCodeConfig) error {
fmt.Println("========================================")
fmt.Println("代码预览模式")
fmt.Println("========================================")
fmt.Printf("包名: %s\n", config.Package)
fmt.Printf("结构体: %s\n", config.StructName)
fmt.Printf("表名: %s\n", config.TableName)
fmt.Printf("描述: %s\n", config.Description)
fmt.Printf("数据库: %s\n", config.BusinessDB)
fmt.Println("----------------------------------------")
// 显示生成选项
fmt.Println("生成选项:")
fmt.Printf(" 使用 KRA_MODEL: %s\n", boolToStr(config.Options.GvaModel))
fmt.Printf(" 自动迁移: %s\n", boolToStr(config.Options.AutoMigrate))
fmt.Printf(" 生成后端: %s\n", boolToStr(config.Options.GenerateServer))
fmt.Printf(" 生成前端: %s\n", boolToStr(config.Options.GenerateWeb))
fmt.Printf(" 仅生成模板: %s\n", boolToStr(config.Options.OnlyTemplate))
fmt.Printf(" 追加模式: %s\n", boolToStr(config.Options.IsAdd))
if !config.Options.OnlyTemplate {
fmt.Printf(" 创建 API 记录: %s\n", boolToStr(config.Options.AutoCreateApiToSql))
fmt.Printf(" 创建菜单记录: %s\n", boolToStr(config.Options.AutoCreateMenuToSql))
fmt.Printf(" 创建按钮权限: %s\n", boolToStr(config.Options.AutoCreateBtnAuth))
fmt.Printf(" 创建资源标识: %s\n", boolToStr(config.Options.AutoCreateResource))
}
if config.Options.IsTree {
fmt.Printf(" 树形结构: 是 (展示字段: %s)\n", config.Options.TreeJson)
}
fmt.Println("----------------------------------------")
fmt.Println("将生成以下文件:")
fmt.Println()
workDir := getWorkingDir()
if config.Options.GenerateServer {
fmt.Println("后端文件:")
fmt.Printf(" - %s/internal/biz/%s/%s.go\n", workDir, config.Package, strings.ToLower(config.StructName))
fmt.Printf(" - %s/internal/data/%s/%s.go\n", workDir, config.Package, strings.ToLower(config.StructName))
fmt.Printf(" - %s/internal/data/model/%s/%s.go\n", workDir, config.Package, strings.ToLower(config.StructName))
fmt.Printf(" - %s/internal/service/%s/%s.go\n", workDir, config.Package, strings.ToLower(config.StructName))
fmt.Printf(" - %s/internal/service/types/%s/request/%s.go\n", workDir, config.Package, strings.ToLower(config.StructName))
fmt.Printf(" - %s/internal/server/handler/%s/%s.go\n", workDir, config.Package, strings.ToLower(config.StructName))
fmt.Printf(" - %s/internal/server/router/%s/%s.go\n", workDir, config.Package, strings.ToLower(config.StructName))
}
if config.Options.GenerateWeb {
fmt.Println()
fmt.Println("前端文件:")
fmt.Printf(" - %s/web/src/pages/%s/%s/index.tsx\n", workDir, config.Package, strings.ToLower(config.StructName))
fmt.Printf(" - %s/web/src/pages/%s/%s/form.tsx\n", workDir, config.Package, strings.ToLower(config.StructName))
fmt.Printf(" - %s/web/src/pages/%s/%s/detail.tsx\n", workDir, config.Package, strings.ToLower(config.StructName))
fmt.Printf(" - %s/web/src/services/kratos/%s.ts\n", workDir, config.Package)
fmt.Printf(" - %s/web/src/types/%s/%s.d.ts\n", workDir, config.Package, strings.ToLower(config.StructName))
}
fmt.Println()
fmt.Println("字段配置:")
if len(config.Fields) == 0 {
fmt.Println(" (未配置字段,将从数据库表结构读取)")
} else {
for _, field := range config.Fields {
searchInfo := ""
if field.FieldSearchType != "" {
searchInfo = fmt.Sprintf(" [搜索:%s]", field.FieldSearchType)
}
dictInfo := ""
if field.DictType != "" {
dictInfo = fmt.Sprintf(" [字典:%s]", field.DictType)
}
fmt.Printf(" - %s (%s): %s%s%s\n", field.FieldName, field.FieldType, field.FieldDesc, searchInfo, dictInfo)
}
}
fmt.Println()
fmt.Println("========================================")
fmt.Println("预览完成,未写入任何文件")
fmt.Println("========================================")
return nil
}
// boolToStr 将布尔值转换为是/否字符串
func boolToStr(b bool) string {
if b {
return "是"
}
return "否"
}
// executeGenerate executes the actual code generation
func executeGenerate(config *AutoCodeConfig) error {
fmt.Println("========================================")
fmt.Println("开始生成代码...")
fmt.Println("========================================")
fmt.Printf("包名: %s\n", config.Package)
fmt.Printf("结构体: %s\n", config.StructName)
fmt.Printf("表名: %s\n", config.TableName)
fmt.Println("----------------------------------------")
// TODO: 实现实际的代码生成逻辑
// 这里需要调用 AutoCodeTemplateUsecase 的 Create 方法
// 由于 CLI 工具需要独立运行,需要初始化数据库连接和依赖
fmt.Println()
fmt.Println("注意: 完整的代码生成功能需要连接数据库。")
fmt.Println("请使用 Web 界面进行代码生成,或确保配置文件包含数据库连接信息。")
fmt.Println()
fmt.Println("========================================")
fmt.Println("代码生成完成!")
fmt.Println("========================================")
return nil
}
// runInteractiveMode runs the interactive configuration mode
func runInteractiveMode() (*AutoCodeConfig, error) {
// 使用新的交互模式实现,支持数据库/表选择
return runInteractiveModeWithDB()
}