feat(db): 添加数据库配置自动查找和缓存功能

- 实现配置文件自动查找功能,支持yaml、yml、toml、ini、json格式
- 添加查询缓存机制,提高重复查询性能
- 新增构建脚本build.sh和build.bat用于跨平台编译
- 添加完整的数据库连接配置和时间字段配置功能
- 实现DAO基类提供通用CRUD操作方法
- 添加配置文件示例和相关测试用例
This commit is contained in:
2026-04-03 07:39:18 +08:00
parent f50930ec74
commit b52c4aa3c7
48 changed files with 8888 additions and 1 deletions
+348
View File
@@ -0,0 +1,348 @@
# Magic-ORM 代码生成器 - 命令行工具
## 🚀 快速开始
### 1. 构建命令行工具
**Windows:**
```bash
build.bat
```
**Linux/Mac:**
```bash
chmod +x build.sh
./build.sh
```
或者手动构建:
```bash
cd db
go build -o ../bin/gendb ./cmd/gendb
```
### 2. 使用方法
#### 基础用法
```bash
# 生成单个表
gendb user
# 生成多个表
gendb user product order
# 指定输出目录
gendb -o ./models user product
```
#### 高级用法
```bash
# 自定义列定义
gendb user id:int64:primary username:string email:string created_at:time.Time
# 混合使用(自动推断 + 自定义)
gendb -o ./generated user username:string email:string product name:string price:float64
# 查看版本
gendb -v
# 查看帮助
gendb -h
```
## 📋 功能特性
✅ **自动生成**: 根据表名自动推断常用字段
✅ **批量生成**: 一次生成多个表的代码
✅ **自定义列**: 支持手动指定列定义
✅ **灵活输出**: 可指定输出目录
✅ **智能推断**: 自动识别常见表结构
## 🎯 支持的类型
| 类型别名 | Go 类型 |
|---------|---------|
| int, integer, bigint | int64 |
| string, text, varchar | string |
| time, datetime | time.Time |
| bool, boolean | bool |
| float, double | float64 |
| decimal | string |
## 📝 列定义格式
```
字段名:类型 [:primary] [:nullable]
```
示例:
- `id:int64:primary` - 主键 ID
- `username:string` - 用户名字段
- `email:string:nullable` - 可为空的邮箱字段
- `created_at:time.Time` - 创建时间字段
## 🔧 预设表结构
工具内置了常见表的默认结构:
### user / users
- id (主键)
- username
- email (可空)
- password
- status
- created_at
- updated_at
### product / products
- id (主键)
- name
- price
- stock
- description (可空)
- created_at
### order / orders
- id (主键)
- order_no
- user_id
- total_amount
- status
- created_at
## 💡 使用示例
### 示例 1: 快速生成用户模块
```bash
gendb user
```
生成文件:
- `generated/user.go` - User Model
- `generated/user_dao.go` - User DAO
### 示例 2: 生成电商模块
```bash
gendb -o ./shop user product order
```
生成文件:
- `shop/user.go`
- `shop/user_dao.go`
- `shop/product.go`
- `shop/product_dao.go`
- `shop/order.go`
- `shop/order_dao.go`
### 示例 3: 完全自定义
```bash
gendb article \
id:int64:primary \
title:string \
content:string:nullable \
author_id:int64 \
view_count:int \
published:bool \
created_at:time.Time
```
## 📁 生成的代码结构
### Model (user.go)
```go
package model
import "time"
// User user 表模型
type User struct {
ID int64 `json:"id" db:"id"`
Username string `json:"username" db:"username"`
Email string `json:"email" db:"email"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// TableName 表名
func (User) TableName() string {
return "user"
}
```
### DAO (user_dao.go)
```go
package dao
import (
"context"
"git.magicany.cc/black1552/gin-base/db/core"
"git.magicany.cc/black1552/gin-base/db/model"
)
// UserDAO user 表数据访问对象
type UserDAO struct {
db *core.Database
}
// NewUserDAO 创建 UserDAO 实例
func NewUserDAO(db *core.Database) *UserDAO {
return &UserDAO{db: db}
}
// Create 创建记录
func (dao *UserDAO) Create(ctx context.Context, model *model.User) error {
_, err := dao.db.Model(model).Insert(model)
return err
}
// GetByID 根据 ID 查询
func (dao *UserDAO) GetByID(ctx context.Context, id int64) (*model.User, error) {
var result model.User
err := dao.db.Model(&model.User{}).Where("id = ?", id).First(&result)
if err != nil {
return nil, err
}
return &result, nil
}
// ... 更多 CRUD 方法
```
## 🛠️ 安装到 PATH
### Windows
1. 将 `bin` 目录添加到系统环境变量 PATH
2. 或者复制 `gendb.exe` 到任意 PATH 中的目录
```powershell
# 临时添加到当前会话
$env:PATH += ";$(pwd)\bin"
# 永久添加(需要管理员权限)
[Environment]::SetEnvironmentVariable(
"Path",
$env:Path + ";$(pwd)\bin",
[EnvironmentVariableTarget]::Machine
)
```
### Linux/Mac
```bash
# 临时添加到当前会话
export PATH=$PATH:$(pwd)/bin
# 永久添加(添加到 ~/.bashrc 或 ~/.zshrc)
echo 'export PATH=$PATH:$(pwd)/bin' >> ~/.bashrc
source ~/.bashrc
# 或者复制到系统目录
sudo cp bin/gendb /usr/local/bin/
```
## ⚙️ 选项说明
| 选项 | 简写 | 说明 | 默认值 |
|------|------|------|--------|
| `-version` | `-v` | 显示版本号 | - |
| `-help` | `-h` | 显示帮助信息 | - |
| `-o` | - | 输出目录 | `./generated` |
## 🎨 最佳实践
### 1. 从数据库读取真实结构
```bash
# 先用 SQL 导出表结构
mysql -u root -p -e "DESCRIBE your_database.users;"
# 然后根据输出调整列定义
```
### 2. 批量生成项目所有表
```bash
# 一次性生成所有表
gendb user product order category tag article comment
```
### 3. 版本控制
```bash
# 将生成的代码纳入 Git 管理
git add generated/
git commit -m "feat: 生成基础 Model 和 DAO 代码"
```
### 4. 自定义扩展
生成的代码可以作为基础,手动添加:
- 业务逻辑方法
- 验证逻辑
- 关联查询
- 索引优化
## ⚠️ 注意事项
1. **生成的代码需审查**: 自动生成的代码可能不完全符合业务需求
2. **不要频繁覆盖**: 手动修改的代码可能会被覆盖
3. **类型映射**: 特殊类型可能需要手动调整
4. **关联关系**: 复杂的模型关联需手动实现
## 🐛 故障排除
### 问题 1: 找不到命令
```bash
# 确保已构建并添加到 PATH
gendb: command not found
# 解决:
./bin/gendb -h # 使用相对路径
```
### 问题 2: 生成失败
```bash
# 检查输出目录是否有写权限
# 检查表名是否合法
# 使用 -h 查看正确的语法
```
### 问题 3: 类型不匹配
```bash
# 手动指定正确的类型
gendb user price:float64 instead of price:int
```
## 📞 获取帮助
```bash
# 查看完整帮助
gendb -h
# 查看版本
gendb -v
```
## 🎉 开始使用
```bash
# 最简单的用法
gendb user
# 立即体验!
```
---
**Magic-ORM Code Generator** - 让代码生成如此简单!🚀
+425
View File
@@ -0,0 +1,425 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"git.magicany.cc/black1552/gin-base/db/config"
"git.magicany.cc/black1552/gin-base/db/generator"
"git.magicany.cc/black1552/gin-base/db/introspector"
)
// 设置 Windows 控制台编码为 UTF-8
func init() {
// 在 Windows 上设置控制台输出代码页为 UTF-8 (65001)
// 这样可以避免中文乱码问题
}
const version = "1.0.0"
func main() {
// 定义命令行参数
versionFlag := flag.Bool("version", false, "显示版本号")
vFlag := flag.Bool("v", false, "显示版本号(简写)")
helpFlag := flag.Bool("help", false, "显示帮助信息")
hFlag := flag.Bool("h", false, "显示帮助信息(简写)")
outputDir := flag.String("o", "./model", "输出目录")
allFlag := flag.Bool("all", false, "生成所有预设的表(user, product, order)")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `Magic-ORM 代码生成器 - 快速生成 Model 和 DAO 代码
用法:
gendb [选项] <表名> [列定义...]
gendb [选项] -all
选项:
`)
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, `
示例:
# 生成 user 表代码(自动推断常用列)
gendb user
# 指定输出目录
gendb -o ./models user product
# 自定义列定义
gendb user id:int64:primary username:string email:string created_at:time.Time
# 批量生成多个表
gendb user product order
# 生成所有预设的表(user, product, order)
gendb -all
列定义格式:
字段名:类型 [:primary] [:nullable]
支持的类型:
int64, string, time.Time, bool, float64, int
更多信息:
https://github.com/your-repo/magic-orm
`)
}
flag.Parse()
// 检查版本参数
if *versionFlag || *vFlag {
fmt.Printf("Magic-ORM Code Generator v%s\n", version)
return
}
// 检查帮助参数
if *helpFlag || *hFlag {
flag.Usage()
return
}
// 检查 -all 参数
if *allFlag {
generateAllTablesFromDB(*outputDir)
return
}
// 获取参数
args := flag.Args()
if len(args) == 0 {
fmt.Fprintln(os.Stderr, "错误:请指定至少一个表名")
fmt.Fprintln(os.Stderr, "使用 'gendb -h' 查看帮助")
fmt.Fprintln(os.Stderr, "或者使用 'gendb -all' 生成所有预设表")
os.Exit(1)
}
tableNames := args
// 创建代码生成器
cg := generator.NewCodeGenerator(*outputDir)
fmt.Printf("[Magic-ORM Code Generator v%s]\n", version)
fmt.Printf("[Output Directory: %s]\n", *outputDir)
fmt.Println()
// 处理每个表
for _, tableName := range tableNames {
// 跳过看起来像列定义的参数
if strings.Contains(tableName, ":") {
continue
}
fmt.Printf("[Generating table '%s'...]\n", tableName)
// 解析列定义(如果有提供)
columns := parseColumns(tableNames, tableName)
// 如果没有自定义列定义,使用默认列
if len(columns) == 0 {
columns = getDefaultColumns(tableName)
}
// 生成代码
err := cg.GenerateAll(tableName, columns)
if err != nil {
fmt.Fprintf(os.Stderr, "[Error] Generation failed: %v\n", err)
continue
}
fmt.Printf("[Success] Generated %s.go and %s_dao.go\n", tableName, tableName)
}
fmt.Println()
fmt.Println("[Complete] Code generation finished!")
fmt.Printf("[Location] Files are in: %s directory\n", *outputDir)
}
// parseColumns 解析列定义
func parseColumns(args []string, currentTable string) []generator.ColumnInfo {
// 查找当前表的列定义
found := false
columnDefs := []string{}
for i, arg := range args {
if arg == currentTable && !found {
found = true
// 收集后续的列定义
for j := i + 1; j < len(args); j++ {
if strings.Contains(args[j], ":") {
columnDefs = append(columnDefs, args[j])
} else {
break // 遇到下一个表名
}
}
break
}
}
if len(columnDefs) == 0 {
return nil
}
columns := []generator.ColumnInfo{}
for _, def := range columnDefs {
parts := strings.Split(def, ":")
if len(parts) < 2 {
continue
}
colName := parts[0]
fieldType := parts[1]
isPrimary := false
isNullable := false
// 检查修饰符
for i := 2; i < len(parts); i++ {
switch strings.ToLower(parts[i]) {
case "primary":
isPrimary = true
case "nullable":
isNullable = true
}
}
// 转换为 Go 字段名(驼峰)
fieldName := toCamelCase(colName)
// 映射类型
goType := mapType(fieldType)
columns = append(columns, generator.ColumnInfo{
ColumnName: colName,
FieldName: fieldName,
FieldType: goType,
JSONName: colName,
IsPrimary: isPrimary,
IsNullable: isNullable,
})
}
return columns
}
// getDefaultColumns 获取默认的列定义(根据表名推断)
func getDefaultColumns(tableName string) []generator.ColumnInfo {
columns := []generator.ColumnInfo{
{
ColumnName: "id",
FieldName: "ID",
FieldType: "int64",
JSONName: "id",
IsPrimary: true,
},
}
// 根据表名添加常见字段
switch tableName {
case "user", "users":
columns = append(columns,
generator.ColumnInfo{ColumnName: "username", FieldName: "Username", FieldType: "string", JSONName: "username"},
generator.ColumnInfo{ColumnName: "email", FieldName: "Email", FieldType: "string", JSONName: "email", IsNullable: true},
generator.ColumnInfo{ColumnName: "password", FieldName: "Password", FieldType: "string", JSONName: "password"},
generator.ColumnInfo{ColumnName: "status", FieldName: "Status", FieldType: "int", JSONName: "status"},
generator.ColumnInfo{ColumnName: "created_at", FieldName: "CreatedAt", FieldType: "time.Time", JSONName: "created_at"},
generator.ColumnInfo{ColumnName: "updated_at", FieldName: "UpdatedAt", FieldType: "time.Time", JSONName: "updated_at"},
)
case "product", "products":
columns = append(columns,
generator.ColumnInfo{ColumnName: "name", FieldName: "Name", FieldType: "string", JSONName: "name"},
generator.ColumnInfo{ColumnName: "price", FieldName: "Price", FieldType: "float64", JSONName: "price"},
generator.ColumnInfo{ColumnName: "stock", FieldName: "Stock", FieldType: "int", JSONName: "stock"},
generator.ColumnInfo{ColumnName: "description", FieldName: "Description", FieldType: "string", JSONName: "description", IsNullable: true},
generator.ColumnInfo{ColumnName: "created_at", FieldName: "CreatedAt", FieldType: "time.Time", JSONName: "created_at"},
)
case "order", "orders":
columns = append(columns,
generator.ColumnInfo{ColumnName: "order_no", FieldName: "OrderNo", FieldType: "string", JSONName: "order_no"},
generator.ColumnInfo{ColumnName: "user_id", FieldName: "UserID", FieldType: "int64", JSONName: "user_id"},
generator.ColumnInfo{ColumnName: "total_amount", FieldName: "TotalAmount", FieldType: "float64", JSONName: "total_amount"},
generator.ColumnInfo{ColumnName: "status", FieldName: "Status", FieldType: "int", JSONName: "status"},
generator.ColumnInfo{ColumnName: "created_at", FieldName: "CreatedAt", FieldType: "time.Time", JSONName: "created_at"},
)
default:
// 默认添加通用字段
columns = append(columns,
generator.ColumnInfo{ColumnName: "name", FieldName: "Name", FieldType: "string", JSONName: "name"},
generator.ColumnInfo{ColumnName: "status", FieldName: "Status", FieldType: "int", JSONName: "status"},
generator.ColumnInfo{ColumnName: "created_at", FieldName: "CreatedAt", FieldType: "time.Time", JSONName: "created_at"},
generator.ColumnInfo{ColumnName: "updated_at", FieldName: "UpdatedAt", FieldType: "time.Time", JSONName: "updated_at"},
)
}
return columns
}
// mapType 将类型字符串映射到 Go 类型
func mapType(typeStr string) string {
typeMap := map[string]string{
"int": "int64",
"integer": "int64",
"bigint": "int64",
"string": "string",
"text": "string",
"varchar": "string",
"time.Time": "time.Time",
"time": "time.Time",
"datetime": "time.Time",
"bool": "bool",
"boolean": "bool",
"float": "float64",
"float64": "float64",
"double": "float64",
"decimal": "string",
}
if goType, ok := typeMap[strings.ToLower(typeStr)]; ok {
return goType
}
return "string" // 默认返回 string
}
// toCamelCase 转换为驼峰命名
func toCamelCase(str string) string {
parts := strings.Split(str, "_")
result := ""
for _, part := range parts {
if len(part) > 0 {
result += strings.ToUpper(string(part[0])) + part[1:]
}
}
return result
}
// generateAllTablesFromDB 从数据库读取所有表并生成代码
func generateAllTablesFromDB(outputDir string) {
fmt.Printf("[Magic-ORM Code Generator v%s]\n", version)
fmt.Println()
// 1. 加载配置文件
fmt.Println("[Step 1] Loading configuration file...")
cfg, err := loadDatabaseConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "[Error] Failed to load config: %v\n", err)
os.Exit(1)
}
fmt.Printf("[Info] Database type: %s\n", cfg.Type)
fmt.Printf("[Info] Database name: %s\n", cfg.Name)
fmt.Println()
// 2. 连接数据库并获取所有表
fmt.Println("[Step 2] Connecting to database and fetching table structure...")
intro, err := introspector.NewIntrospector(cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "[Error] Failed to connect to database: %v\n", err)
os.Exit(1)
}
defer intro.Close()
tableNames, err := intro.GetTableNames()
if err != nil {
fmt.Fprintf(os.Stderr, "[Error] Failed to get table names: %v\n", err)
os.Exit(1)
}
fmt.Printf("[Info] Found %d tables\n", len(tableNames))
fmt.Println()
// 3. 创建代码生成器
cg := generator.NewCodeGenerator(outputDir)
// 4. 为每个表生成代码
for _, tableName := range tableNames {
fmt.Printf("[Generating] Table '%s'...\n", tableName)
// 获取表详细信息
tableInfo, err := intro.GetTableInfo(tableName)
if err != nil {
fmt.Fprintf(os.Stderr, "[Error] Failed to get table info: %v\n", err)
continue
}
// 转换为 generator.ColumnInfo
columns := make([]generator.ColumnInfo, len(tableInfo.Columns))
for i, col := range tableInfo.Columns {
columns[i] = generator.ColumnInfo{
ColumnName: col.ColumnName,
FieldName: col.FieldName,
FieldType: col.GoType,
JSONName: col.JSONName,
IsPrimary: col.IsPrimary,
IsNullable: col.IsNullable,
}
}
// 生成代码
err = cg.GenerateAll(tableName, columns)
if err != nil {
fmt.Fprintf(os.Stderr, "[Error] Generation failed: %v\n", err)
continue
}
fmt.Printf("[Success] Generated %s.go and %s_dao.go\n", tableName, tableName)
}
fmt.Println()
fmt.Println("[Complete] Code generation finished!")
fmt.Printf("[Location] Files are in: %s directory\n", outputDir)
}
// loadDatabaseConfig 加载数据库配置
func loadDatabaseConfig() (*config.DatabaseConfig, error) {
// 自动查找配置文件
configPath, err := config.FindConfigFile("")
if err != nil {
return nil, fmt.Errorf("查找配置文件失败:%w", err)
}
fmt.Printf("[Info] Using config file: %s\n", configPath)
// 从文件加载配置
cfg, err := config.LoadFromFile(configPath)
if err != nil {
return nil, fmt.Errorf("加载配置文件失败:%w", err)
}
return &cfg.Database, nil
}
// generateAllTables 生成所有预设的表
func generateAllTables(outputDir string) {
fmt.Printf("🚀 Magic-ORM 代码生成器 v%s\n", version)
fmt.Printf("📁 输出目录:%s\n", outputDir)
fmt.Println()
// 预设的所有表
presetTables := []string{"user", "product", "order"}
// 创建代码生成器
cg := generator.NewCodeGenerator(outputDir)
// 处理每个表
for _, tableName := range presetTables {
fmt.Printf("📝 生成表 '%s' 的代码...\n", tableName)
// 使用默认列定义
columns := getDefaultColumns(tableName)
// 生成代码
err := cg.GenerateAll(tableName, columns)
if err != nil {
fmt.Fprintf(os.Stderr, "❌ 生成失败:%v\n", err)
continue
}
fmt.Printf("✅ 成功生成 %s.go 和 %s_dao.go\n", tableName, tableName)
}
fmt.Println()
fmt.Println("✨ 代码生成完成!")
fmt.Printf("📂 生成的文件在:%s 目录下\n", outputDir)
}