From 7585b8e9da63408afa6158303f940287e068c24b Mon Sep 17 00:00:00 2001 From: Jimmy Xue Date: Sun, 30 Nov 2025 13:50:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AF=B9=E8=B1=A1=E7=9A=84?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 +++++---- docs/factory.md | 117 ++++++++++++++++++++++++++++++------ examples/factory_example.go | 101 ++++++++++++++++++------------- factory/factory.go | 72 +++++++++++++++++++++- go.mod | 9 ++- go.sum | 23 +++++++ 6 files changed, 279 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 5cad687..88c6c04 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ 提供阿里云短信发送功能,支持模板短信和批量发送,使用Go标准库实现。 ### 9. 工厂工具 (factory) -提供从配置直接创建已初始化客户端对象的功能,避免调用方重复实现创建逻辑。 +提供从配置文件直接创建已初始化客户端对象的功能,包括数据库、Redis、邮件、短信、日志等,避免调用方重复实现创建逻辑。 ### 10. 日志工具 (logger) 提供统一的日志记录功能,支持多种日志级别和输出方式,使用Go标准库实现。 @@ -185,27 +185,33 @@ smsClient.SendSimple( #### 使用工厂直接获取客户端(推荐) ```go -import ( - "git.toowon.com/jimmy/go-common/config" - "git.toowon.com/jimmy/go-common/factory" -) +import "git.toowon.com/jimmy/go-common/factory" -// 加载配置并创建工厂 -cfg, _ := config.LoadFromFile("./config.json") -fac := factory.NewFactory(cfg) +// 方式1:直接从配置文件创建工厂(最推荐) +fac, _ := factory.NewFactoryFromFile("./config.json") + +// 直接获取数据库对象(已初始化,可直接使用) +db, _ := fac.GetDatabase() +db.Find(&users) // 直接使用,无需再创建连接 + +// 获取Redis配置(用于创建Redis客户端) +redisConfig := fac.GetRedisConfig() +// 使用go-redis创建客户端: +// rdb := redis.NewClient(&redis.Options{ +// Addr: fmt.Sprintf("%s:%d", redisConfig.Host, redisConfig.Port), +// Password: redisConfig.Password, +// DB: redisConfig.Database, +// }) // 直接获取已初始化的客户端(无需重复实现创建逻辑) emailClient, _ := fac.GetEmailClient() smsClient, _ := fac.GetSMSClient() +logger, _ := fac.GetLogger() // 直接使用 emailClient.SendSimple(...) smsClient.SendSimple(...) - -// 获取日志记录器(已初始化,可直接使用) -logger, _ := fac.GetLogger() logger.Info("Application started") -logger.Error("Error occurred: %v", err) ``` 更多示例请查看 [examples](./examples/) 目录。 diff --git a/docs/factory.md b/docs/factory.md index 4e35230..f1b5180 100644 --- a/docs/factory.md +++ b/docs/factory.md @@ -12,7 +12,24 @@ ## 使用方法 -### 1. 创建工厂实例 +### 1. 从配置文件直接创建工厂(推荐) + +```go +import "git.toowon.com/jimmy/go-common/factory" + +// 直接传入配置文件路径,自动加载配置并创建工厂 +fac, err := factory.NewFactoryFromFile("./config.json") +if err != nil { + log.Fatal(err) +} + +// 直接获取已初始化的对象 +db, _ := fac.GetDatabase() // 直接获取数据库对象 +logger, _ := fac.GetLogger() // 直接获取日志对象 +emailClient, _ := fac.GetEmailClient() // 直接获取邮件客户端 +``` + +### 2. 从配置对象创建工厂 ```go import ( @@ -30,7 +47,38 @@ if err != nil { fac := factory.NewFactory(cfg) ``` -### 2. 获取邮件客户端(已初始化) +### 3. 获取数据库对象(已初始化,推荐) + +```go +// 直接获取已初始化的数据库对象(*gorm.DB) +db, err := fac.GetDatabase() +if err != nil { + log.Fatal(err) +} + +// 直接使用,无需再创建连接 +var users []User +db.Find(&users) +db.Create(&user) +``` + +### 4. 获取Redis配置 + +```go +// 获取Redis配置(用于创建Redis客户端) +// 注意:Go标准库没有Redis客户端,需要调用方使用第三方库创建 +redisConfig := fac.GetRedisConfig() +if redisConfig != nil { + // 使用go-redis创建客户端示例: + // rdb := redis.NewClient(&redis.Options{ + // Addr: fmt.Sprintf("%s:%d", redisConfig.Host, redisConfig.Port), + // Password: redisConfig.Password, + // DB: redisConfig.Database, + // }) +} +``` + +### 5. 获取邮件客户端(已初始化) ```go // 直接获取已初始化的邮件客户端 @@ -47,7 +95,7 @@ err = emailClient.SendSimple( ) ``` -### 3. 获取短信客户端(已初始化) +### 6. 获取短信客户端(已初始化) ```go // 直接获取已初始化的短信客户端 @@ -63,7 +111,7 @@ resp, err := smsClient.SendSimple( ) ``` -### 4. 获取日志记录器(已初始化) +### 7. 获取日志记录器(已初始化) ```go // 直接获取已初始化的日志记录器 @@ -77,7 +125,7 @@ logger.Info("Application started") logger.Error("Error occurred: %v", err) ``` -### 5. 完整示例 +### 8. 完整示例 ```go package main @@ -150,6 +198,17 @@ func main() { **返回:** 工厂实例 +### NewFactoryFromFile(filePath string) (*Factory, error) + +从配置文件直接创建工厂实例(便捷方法)。 + +**参数:** +- `filePath`: 配置文件路径 + +**返回:** 工厂实例和错误信息 + +**说明:** 这是推荐的使用方式,一步完成配置加载和工厂创建。 + ### (f *Factory) GetEmailClient() (*email.Email, error) 获取邮件客户端(已初始化)。 @@ -174,6 +233,27 @@ func main() { **说明:** 如果日志配置为nil,会使用默认配置创建。 +### (f *Factory) GetDatabase() (*gorm.DB, error) + +获取数据库连接对象(已初始化)。 + +**返回:** 已初始化的GORM数据库对象和错误信息 + +**说明:** +- 支持MySQL、PostgreSQL、SQLite +- 自动配置连接池参数 +- 数据库时间统一使用UTC时区 + +### (f *Factory) GetRedisConfig() *config.RedisConfig + +获取Redis配置(用于创建Redis客户端)。 + +**返回:** Redis配置对象(可能为nil) + +**说明:** +- Go标准库没有Redis客户端,需要调用方使用第三方库(如go-redis/redis)创建 +- 返回配置对象,调用方可以根据配置创建Redis客户端 + ### (f *Factory) GetConfig() *config.Config 获取配置对象。 @@ -187,30 +267,33 @@ func main() { ```go // 调用方需要自己实现创建逻辑 cfg, _ := config.LoadFromFile("./config.json") -emailConfig := cfg.GetEmail() -if emailConfig == nil { - log.Fatal("email config is nil") -} -emailClient, err := email.NewEmail(emailConfig) +dsn, _ := cfg.GetDatabaseDSN() +db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { log.Fatal(err) } +// 配置连接池 +sqlDB, _ := db.DB() +sqlDB.SetMaxOpenConns(100) // 才能使用 -emailClient.SendSimple(...) +db.Find(&users) ``` ### 使用工厂方式(直接获取已初始化对象) ```go -// 直接获取已初始化的对象,无需重复实现 +// 方式1:直接从配置文件创建(最推荐) +fac, _ := factory.NewFactoryFromFile("./config.json") +db, _ := fac.GetDatabase() // 直接获取已初始化的数据库对象 +// 直接使用 +db.Find(&users) + +// 方式2:从配置对象创建 cfg, _ := config.LoadFromFile("./config.json") fac := factory.NewFactory(cfg) -emailClient, err := fac.GetEmailClient() -if err != nil { - log.Fatal(err) -} +db, _ := fac.GetDatabase() // 直接获取已初始化的数据库对象 // 直接使用 -emailClient.SendSimple(...) +db.Find(&users) ``` ## 注意事项 diff --git a/examples/factory_example.go b/examples/factory_example.go index 5fc5c67..8b3a710 100644 --- a/examples/factory_example.go +++ b/examples/factory_example.go @@ -4,69 +4,86 @@ import ( "fmt" "log" - "git.toowon.com/jimmy/go-common/config" "git.toowon.com/jimmy/go-common/factory" ) func main() { - // 加载配置 - cfg, err := config.LoadFromFile("./config/example.json") + // 方式1:直接从配置文件创建工厂(推荐) + fac, err := factory.NewFactoryFromFile("./config/example.json") if err != nil { - log.Fatal("Failed to load config:", err) + log.Fatal("Failed to create factory:", err) } - // 创建工厂实例 - fac := factory.NewFactory(cfg) + // 直接获取数据库对象(已初始化,可直接使用) + db, err := fac.GetDatabase() + if err != nil { + log.Printf("Database not available: %v", err) + } else { + // 直接使用数据库 + var count int64 + if err := db.Table("users").Count(&count).Error; err != nil { + log.Printf("Failed to count users: %v", err) + } else { + fmt.Printf("User count: %d\n", count) + } + } - // 示例1:获取邮件客户端(已初始化,可直接使用) - fmt.Println("=== Example 1: Get Email Client ===") + // 获取Redis配置(用于创建Redis客户端) + redisConfig := fac.GetRedisConfig() + if redisConfig != nil { + fmt.Printf("Redis config: %s:%d\n", redisConfig.Host, redisConfig.Port) + // 使用go-redis创建客户端示例: + // import "github.com/redis/go-redis/v9" + // rdb := redis.NewClient(&redis.Options{ + // Addr: fmt.Sprintf("%s:%d", redisConfig.Host, redisConfig.Port), + // Password: redisConfig.Password, + // DB: redisConfig.Database, + // }) + } + + // 获取邮件客户端(已初始化,可直接使用) emailClient, err := fac.GetEmailClient() if err != nil { log.Printf("Email client not available: %v", err) } else { - fmt.Println("Email client created successfully") - // 直接使用,无需再创建 - _ = emailClient // 示例中不使用,实际使用时可以直接调用方法 - // err = emailClient.SendSimple( - // []string{"recipient@example.com"}, - // "测试邮件", - // "这是测试内容", - // ) - fmt.Println("Email client is ready to use") + // 直接使用 + err = emailClient.SendSimple( + []string{"recipient@example.com"}, + "测试邮件", + "这是测试内容", + ) + if err != nil { + log.Printf("Failed to send email: %v", err) + } else { + fmt.Println("Email sent successfully") + } } - // 示例2:获取短信客户端(已初始化,可直接使用) - fmt.Println("\n=== Example 2: Get SMS Client ===") + // 获取短信客户端(已初始化,可直接使用) smsClient, err := fac.GetSMSClient() if err != nil { log.Printf("SMS client not available: %v", err) } else { - fmt.Println("SMS client created successfully") - // 直接使用,无需再创建 - _ = smsClient // 示例中不使用,实际使用时可以直接调用方法 - // resp, err := smsClient.SendSimple( - // []string{"13800138000"}, - // map[string]string{"code": "123456"}, - // ) - fmt.Println("SMS client is ready to use") + // 直接使用 + resp, err := smsClient.SendSimple( + []string{"13800138000"}, + map[string]string{"code": "123456"}, + ) + if err != nil { + log.Printf("Failed to send SMS: %v", err) + } else { + fmt.Printf("SMS sent: %s\n", resp.RequestID) + } } - // 示例3:访问配置对象 - fmt.Println("\n=== Example 3: Access Config Object ===") - cfgObj := fac.GetConfig() - dsn, err := cfgObj.GetDatabaseDSN() + // 获取日志记录器(已初始化,可直接使用) + logger, err := fac.GetLogger() if err != nil { - log.Printf("Database DSN not available: %v", err) + log.Printf("Logger not available: %v", err) } else { - fmt.Printf("Database DSN: %s\n", dsn) + logger.Info("Application started") + logger.Debug("Debug message") + logger.Warn("Warning message") + logger.Error("Error message") } - - redisAddr := cfgObj.GetRedisAddr() - if redisAddr != "" { - fmt.Printf("Redis Address: %s\n", redisAddr) - } - - fmt.Println("\nNote: Factory provides initialized clients directly,") - fmt.Println("no need to implement creation logic in your code.") } - diff --git a/factory/factory.go b/factory/factory.go index 272fd9e..ef81cab 100644 --- a/factory/factory.go +++ b/factory/factory.go @@ -2,11 +2,16 @@ package factory import ( "fmt" + "time" "git.toowon.com/jimmy/go-common/config" "git.toowon.com/jimmy/go-common/email" "git.toowon.com/jimmy/go-common/logger" "git.toowon.com/jimmy/go-common/sms" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" ) // Factory 工厂类,用于从配置创建各种客户端对象 @@ -21,6 +26,16 @@ func NewFactory(cfg *config.Config) *Factory { } } +// NewFactoryFromFile 从配置文件创建工厂实例(便捷方法) +// filePath: 配置文件路径 +func NewFactoryFromFile(filePath string) (*Factory, error) { + cfg, err := config.LoadFromFile(filePath) + if err != nil { + return nil, fmt.Errorf("failed to load config: %w", err) + } + return NewFactory(cfg), nil +} + // GetEmailClient 获取邮件客户端(已初始化) // 返回已初始化的邮件客户端对象,可直接使用 func (f *Factory) GetEmailClient() (*email.Email, error) { @@ -49,8 +64,63 @@ func (f *Factory) GetLogger() (*logger.Logger, error) { return logger.NewLogger(f.cfg.Logger) } +// GetDatabase 获取数据库连接对象(已初始化) +// 返回已初始化的GORM数据库对象,可直接使用 +func (f *Factory) GetDatabase() (*gorm.DB, error) { + if f.cfg.Database == nil { + return nil, fmt.Errorf("database config is nil") + } + + // 获取DSN + dsn, err := f.cfg.GetDatabaseDSN() + if err != nil { + return nil, fmt.Errorf("failed to get DSN: %w", err) + } + + // 根据数据库类型创建连接 + var db *gorm.DB + switch f.cfg.Database.Type { + case "mysql": + db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) + case "postgres": + db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) + case "sqlite": + db, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{}) + default: + return nil, fmt.Errorf("unsupported database type: %s", f.cfg.Database.Type) + } + + if err != nil { + return nil, fmt.Errorf("failed to connect to database: %w", err) + } + + // 配置连接池 + sqlDB, err := db.DB() + if err != nil { + return nil, fmt.Errorf("failed to get sql.DB: %w", err) + } + + if f.cfg.Database.MaxOpenConns > 0 { + sqlDB.SetMaxOpenConns(f.cfg.Database.MaxOpenConns) + } + if f.cfg.Database.MaxIdleConns > 0 { + sqlDB.SetMaxIdleConns(f.cfg.Database.MaxIdleConns) + } + if f.cfg.Database.ConnMaxLifetime > 0 { + sqlDB.SetConnMaxLifetime(time.Duration(f.cfg.Database.ConnMaxLifetime) * time.Second) + } + + return db, nil +} + +// GetRedisConfig 获取Redis配置(用于创建Redis客户端) +// 返回Redis配置对象,调用方可以使用此配置创建Redis客户端 +// 注意:Go标准库没有Redis客户端,需要调用方使用第三方库(如go-redis/redis)创建 +func (f *Factory) GetRedisConfig() *config.RedisConfig { + return f.cfg.Redis +} + // GetConfig 获取配置对象 func (f *Factory) GetConfig() *config.Config { return f.cfg } - diff --git a/go.mod b/go.mod index 73efd25..d1c24c1 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,15 @@ require ( require ( github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/text v0.21.0 // indirect + gorm.io/driver/postgres v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 98c0e5b..60e1838 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,39 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=