Files
go-common/factory/factory.go

1198 lines
36 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 factory
import (
"context"
"fmt"
"io"
"net/http"
"time"
"git.toowon.com/jimmy/go-common/config"
"git.toowon.com/jimmy/go-common/email"
commonhttp "git.toowon.com/jimmy/go-common/http"
"git.toowon.com/jimmy/go-common/logger"
"git.toowon.com/jimmy/go-common/middleware"
"git.toowon.com/jimmy/go-common/migration"
"git.toowon.com/jimmy/go-common/sms"
"git.toowon.com/jimmy/go-common/storage"
"git.toowon.com/jimmy/go-common/tools"
"github.com/redis/go-redis/v9"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// ========== HTTP响应结构体暴露给外部项目使用 ==========
// Response 标准响应结构(暴露给外部项目使用)
// 外部项目可以直接使用 factory.Response 创建响应对象
//
// 示例:
//
// response := factory.Response{
// Code: 0,
// Message: "success",
// Data: data,
// }
type Response = commonhttp.Response
// PageResponse 分页响应结构(暴露给外部项目使用)
// 外部项目可以直接使用 factory.PageResponse 创建分页响应对象
type PageResponse = commonhttp.PageResponse
// PageData 分页数据(暴露给外部项目使用)
// 外部项目可以直接使用 factory.PageData 创建分页数据对象
//
// 示例:
//
// pageData := &factory.PageData{
// List: users,
// Total: 100,
// Page: 1,
// PageSize: 20,
// }
// fac.Success(w, pageData)
type PageData = commonhttp.PageData
// ========== HTTP请求结构体暴露给外部项目使用 ==========
// PaginationRequest 分页请求结构(暴露给外部项目使用)
// 外部项目可以直接使用 factory.PaginationRequest 创建分页请求对象
//
// 示例:
//
// type ListUserRequest struct {
// Keyword string `json:"keyword"`
// factory.PaginationRequest // 嵌入分页请求结构
// }
type PaginationRequest = commonhttp.PaginationRequest
// Factory 工厂类 - 黑盒模式设计
//
// 核心理念:
//
// 外部项目只需传递一个配置文件路径,即可直接使用所有功能,
// 无需关心内部实现细节。
//
// 推荐使用的黑盒方法:
// - GetMiddlewareChain():获取配置好的中间件链
// - LogInfo(), LogError():记录日志
// - RedisSet(), RedisGet()操作Redis
// - SendEmail(), SendSMS():发送邮件和短信
// - UploadFile(), GetFileURL():文件上传和访问
//
// 需要获取客户端对象的场景(高级功能):
// - GetDatabase()数据库操作GORM已经是很好的抽象
// - GetRedisClient()Redis高级操作Hash, List, Set, ZSet等
// - GetLogger()Logger高级功能Close等
//
// 使用示例:
//
// // 1. 创建工厂(传入配置文件路径)
// fac, _ := factory.NewFactoryFromFile("config.json")
//
// // 2. 直接使用黑盒方法(推荐)
// fac.LogInfo("用户登录成功")
// fac.RedisSet(ctx, "session:123", "data", time.Hour)
// fac.SendEmail([]string{"user@example.com"}, "主题", "内容")
// chain := fac.GetMiddlewareChain()
// chain.Append(yourAuthMiddleware) // 添加自定义中间件
//
// // 3. 获取客户端对象(仅在需要高级功能时)
// db, _ := fac.GetDatabase()
// db.Find(&users)
//
// Factory 工厂类,用于从配置创建各种客户端对象
type Factory struct {
cfg *config.Config
storage storage.Storage // 存储实例(延迟初始化)
logger *logger.Logger // 日志实例(延迟初始化)
email *email.Email // 邮件客户端(延迟初始化)
sms *sms.SMS // 短信客户端(延迟初始化)
db *gorm.DB // 数据库连接(延迟初始化)
redis *redis.Client // Redis客户端延迟初始化
}
// NewFactory 创建工厂实例
func NewFactory(cfg *config.Config) *Factory {
return &Factory{
cfg: cfg,
}
}
// 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) {
if f.email != nil {
return f.email, nil
}
f.email = email.NewEmail(f.cfg)
return f.email, nil
}
// SendEmail 发送邮件(黑盒模式,推荐使用)
// 自动使用配置文件中的SMTP配置发送邮件
// to: 收件人列表
// subject: 邮件主题
// body: 邮件正文(纯文本)
// htmlBody: HTML正文可选如果设置了会优先使用
func (f *Factory) SendEmail(to []string, subject, body string, htmlBody ...string) error {
e, err := f.getEmailClient()
if err != nil {
return err
}
return e.SendEmail(to, subject, body, htmlBody...)
}
// getSMSClient 获取短信客户端(内部方法,延迟初始化)
func (f *Factory) getSMSClient() (*sms.SMS, error) {
if f.sms != nil {
return f.sms, nil
}
f.sms = sms.NewSMS(f.cfg)
return f.sms, nil
}
// SendSMS 发送短信(黑盒模式,推荐使用)
// 自动使用配置文件中的阿里云短信配置发送短信
// phoneNumbers: 手机号列表
// templateParam: 模板参数map或JSON字符串
// templateCode: 模板代码(可选,如果为空使用配置中的模板代码)
func (f *Factory) SendSMS(phoneNumbers []string, templateParam interface{}, templateCode ...string) (*sms.SendResponse, error) {
s, err := f.getSMSClient()
if err != nil {
return nil, err
}
return s.SendSMS(phoneNumbers, templateParam, templateCode...)
}
// getLogger 获取日志记录器(内部方法,延迟初始化)
func (f *Factory) getLogger() (*logger.Logger, error) {
if f.logger != nil {
return f.logger, nil
}
var l *logger.Logger
var err error
if f.cfg.Logger == nil {
// 如果没有配置,使用默认配置创建
l, err = logger.NewLogger(nil)
} else {
l, err = logger.NewLogger(f.cfg.Logger)
}
if err != nil {
return nil, fmt.Errorf("failed to create logger: %w", err)
}
f.logger = l
return l, nil
}
// GetLogger 获取日志记录器对象(不推荐直接使用)
//
// ⚠️ 不推荐直接使用此方法,推荐使用黑盒方法:
// - LogDebug, LogInfo, LogWarn, LogError记录简单日志
// - LogDebugf, LogInfof, LogWarnf, LogErrorf记录带字段的日志
//
// 仅在以下高级场景时使用:
// - 需要调用 Close() 方法关闭logger
// - 需要使用logger的其他高级功能
//
// 示例(不推荐):
//
// logger, _ := factory.GetLogger()
// defer logger.Close()
//
// 示例(推荐):
//
// factory.LogInfo("用户登录成功")
// factory.LogErrorf(map[string]interface{}{"user_id": 123}, "登录失败")
func (f *Factory) GetLogger() (*logger.Logger, error) {
return f.getLogger()
}
// LogDebug 记录调试日志(黑盒模式,推荐使用)
// 自动使用配置文件中的logger配置
// message: 日志消息
// args: 格式化参数(可选)
func (f *Factory) LogDebug(message string, args ...interface{}) {
l, err := f.getLogger()
if err != nil {
// 如果日志初始化失败,使用标准输出
if len(args) > 0 {
fmt.Printf("[DEBUG] "+message+"\n", args...)
} else {
fmt.Printf("[DEBUG] %s\n", message)
}
return
}
if len(args) > 0 {
l.Debug(message, args...)
} else {
l.Debug(message)
}
}
// LogDebugf 记录调试日志(带字段,黑盒模式,推荐使用)
// 自动使用配置文件中的logger配置
// fields: 日志字段
// message: 日志消息
// args: 格式化参数(可选)
func (f *Factory) LogDebugf(fields map[string]interface{}, message string, args ...interface{}) {
l, err := f.getLogger()
if err != nil {
// 如果日志初始化失败,使用标准输出
if len(args) > 0 {
fmt.Printf("[DEBUG] "+message+"\n", args...)
} else {
fmt.Printf("[DEBUG] %s\n", message)
}
return
}
l.Debugf(fields, message, args...)
}
// LogInfo 记录信息日志(黑盒模式,推荐使用)
// 自动使用配置文件中的logger配置
// message: 日志消息
// args: 格式化参数(可选)
func (f *Factory) LogInfo(message string, args ...interface{}) {
l, err := f.getLogger()
if err != nil {
// 如果日志初始化失败,使用标准输出
if len(args) > 0 {
fmt.Printf("[INFO] "+message+"\n", args...)
} else {
fmt.Printf("[INFO] %s\n", message)
}
return
}
if len(args) > 0 {
l.Info(message, args...)
} else {
l.Info(message)
}
}
// LogInfof 记录信息日志(带字段,黑盒模式,推荐使用)
// 自动使用配置文件中的logger配置
// fields: 日志字段
// message: 日志消息
// args: 格式化参数(可选)
func (f *Factory) LogInfof(fields map[string]interface{}, message string, args ...interface{}) {
l, err := f.getLogger()
if err != nil {
// 如果日志初始化失败,使用标准输出
if len(args) > 0 {
fmt.Printf("[INFO] "+message+"\n", args...)
} else {
fmt.Printf("[INFO] %s\n", message)
}
return
}
l.Infof(fields, message, args...)
}
// LogWarn 记录警告日志(黑盒模式,推荐使用)
// 自动使用配置文件中的logger配置
// message: 日志消息
// args: 格式化参数(可选)
func (f *Factory) LogWarn(message string, args ...interface{}) {
l, err := f.getLogger()
if err != nil {
// 如果日志初始化失败,使用标准输出
if len(args) > 0 {
fmt.Printf("[WARN] "+message+"\n", args...)
} else {
fmt.Printf("[WARN] %s\n", message)
}
return
}
if len(args) > 0 {
l.Warn(message, args...)
} else {
l.Warn(message)
}
}
// LogWarnf 记录警告日志(带字段,黑盒模式,推荐使用)
// 自动使用配置文件中的logger配置
// fields: 日志字段
// message: 日志消息
// args: 格式化参数(可选)
func (f *Factory) LogWarnf(fields map[string]interface{}, message string, args ...interface{}) {
l, err := f.getLogger()
if err != nil {
// 如果日志初始化失败,使用标准输出
if len(args) > 0 {
fmt.Printf("[WARN] "+message+"\n", args...)
} else {
fmt.Printf("[WARN] %s\n", message)
}
return
}
l.Warnf(fields, message, args...)
}
// LogError 记录错误日志(黑盒模式,推荐使用)
// 自动使用配置文件中的logger配置
// message: 日志消息
// args: 格式化参数(可选)
func (f *Factory) LogError(message string, args ...interface{}) {
l, err := f.getLogger()
if err != nil {
// 如果日志初始化失败,使用标准输出
if len(args) > 0 {
fmt.Printf("[ERROR] "+message+"\n", args...)
} else {
fmt.Printf("[ERROR] %s\n", message)
}
return
}
if len(args) > 0 {
l.Error(message, args...)
} else {
l.Error(message)
}
}
// LogErrorf 记录错误日志(带字段,黑盒模式,推荐使用)
// 自动使用配置文件中的logger配置
// fields: 日志字段
// message: 日志消息
// args: 格式化参数(可选)
func (f *Factory) LogErrorf(fields map[string]interface{}, message string, args ...interface{}) {
l, err := f.getLogger()
if err != nil {
// 如果日志初始化失败,使用标准输出
if len(args) > 0 {
fmt.Printf("[ERROR] "+message+"\n", args...)
} else {
fmt.Printf("[ERROR] %s\n", message)
}
return
}
l.Errorf(fields, message, args...)
}
// getDatabase 获取数据库连接对象(内部方法,延迟初始化)
func (f *Factory) getDatabase() (*gorm.DB, error) {
if f.db != nil {
return f.db, nil
}
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)
}
f.db = db
return db, nil
}
// GetDatabase 获取数据库连接对象(推荐使用)
// 返回已初始化的GORM数据库对象可直接使用
//
// 数据库操作保持使用 GORM 对象,因为:
// - 数据库操作非常复杂多样(查询、插入、更新、删除、事务等)
// - GORM 已经提供了很好的抽象和 API
// - 无需在 factory 中重复封装所有数据库方法
//
// 示例:
//
// db, _ := factory.GetDatabase()
// db.Find(&users)
// db.Create(&user)
// db.Transaction(func(tx *gorm.DB) error { ... })
func (f *Factory) GetDatabase() (*gorm.DB, error) {
return f.getDatabase()
}
// getRedisClient 获取Redis客户端对象内部方法延迟初始化
func (f *Factory) getRedisClient() (*redis.Client, error) {
if f.redis != nil {
return f.redis, nil
}
if f.cfg.Redis == nil {
return nil, fmt.Errorf("redis config is nil")
}
// 获取Redis地址
addr := f.cfg.GetRedisAddr()
if addr == "" {
return nil, fmt.Errorf("redis address is empty")
}
// 设置默认值
redisConfig := f.cfg.Redis
if redisConfig.PoolSize == 0 {
redisConfig.PoolSize = 10 // 默认连接池大小
}
if redisConfig.MinIdleConns == 0 {
redisConfig.MinIdleConns = 5 // 默认最小空闲连接数
}
if redisConfig.DialTimeout == 0 {
redisConfig.DialTimeout = 5 // 默认连接超时5秒
}
if redisConfig.ReadTimeout == 0 {
redisConfig.ReadTimeout = 3 // 默认读取超时3秒
}
if redisConfig.WriteTimeout == 0 {
redisConfig.WriteTimeout = 3 // 默认写入超时3秒
}
// 创建Redis客户端
client := redis.NewClient(&redis.Options{
Addr: addr,
Password: redisConfig.Password,
DB: redisConfig.Database,
PoolSize: redisConfig.PoolSize,
MinIdleConns: redisConfig.MinIdleConns,
MaxRetries: redisConfig.MaxRetries,
DialTimeout: time.Duration(redisConfig.DialTimeout) * time.Second,
ReadTimeout: time.Duration(redisConfig.ReadTimeout) * time.Second,
WriteTimeout: time.Duration(redisConfig.WriteTimeout) * time.Second,
})
// 测试连接
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(redisConfig.DialTimeout)*time.Second)
defer cancel()
_, err := client.Ping(ctx).Result()
if err != nil {
client.Close() // 连接失败时关闭客户端
return nil, fmt.Errorf("failed to connect to redis: %w", err)
}
f.redis = client
return client, nil
}
// GetRedisClient 获取Redis客户端对象高级功能时使用
// 返回已初始化的Redis客户端对象
//
// 推荐使用黑盒方法:
// - RedisGet, RedisSet, RedisDelete, RedisExists常用操作
//
// 仅在需要使用高级功能时获取客户端:
// - Hash 操作HSet, HGet, HGetAll 等)
// - List 操作LPush, RPush, LRange 等)
// - Set 操作SAdd, SMembers 等)
// - ZSet 操作ZAdd, ZRange 等)
// - 其他高级功能
//
// 示例(常用操作,推荐):
//
// factory.RedisSet(ctx, "key", "value", time.Hour)
// value, _ := factory.RedisGet(ctx, "key")
//
// 示例(高级功能):
//
// client, _ := factory.GetRedisClient()
// client.HSet(ctx, "user:1", "name", "Alice")
// client.LPush(ctx, "queue", "task1")
func (f *Factory) GetRedisClient() (*redis.Client, error) {
return f.getRedisClient()
}
// RedisGet 获取Redis值黑盒模式推荐使用
// 自动使用配置文件中的Redis配置
// key: Redis键
func (f *Factory) RedisGet(ctx context.Context, key string) (string, error) {
client, err := f.getRedisClient()
if err != nil {
return "", err
}
result, err := client.Get(ctx, key).Result()
if err == redis.Nil {
return "", nil // key不存在返回空字符串
}
if err != nil {
return "", fmt.Errorf("failed to get redis key: %w", err)
}
return result, nil
}
// RedisSet 设置Redis值黑盒模式推荐使用
// 自动使用配置文件中的Redis配置
// key: Redis键
// value: Redis值
// expiration: 过期时间可选0表示不过期
func (f *Factory) RedisSet(ctx context.Context, key string, value interface{}, expiration ...time.Duration) error {
client, err := f.getRedisClient()
if err != nil {
return err
}
var exp time.Duration
if len(expiration) > 0 {
exp = expiration[0]
}
err = client.Set(ctx, key, value, exp).Err()
if err != nil {
return fmt.Errorf("failed to set redis key: %w", err)
}
return nil
}
// RedisDelete 删除Redis键黑盒模式推荐使用
// 自动使用配置文件中的Redis配置
// keys: Redis键列表
func (f *Factory) RedisDelete(ctx context.Context, keys ...string) error {
if len(keys) == 0 {
return nil
}
client, err := f.getRedisClient()
if err != nil {
return err
}
err = client.Del(ctx, keys...).Err()
if err != nil {
return fmt.Errorf("failed to delete redis keys: %w", err)
}
return nil
}
// RedisExists 检查Redis键是否存在黑盒模式推荐使用
// 自动使用配置文件中的Redis配置
// key: Redis键
func (f *Factory) RedisExists(ctx context.Context, key string) (bool, error) {
client, err := f.getRedisClient()
if err != nil {
return false, err
}
count, err := client.Exists(ctx, key).Result()
if err != nil {
return false, fmt.Errorf("failed to check redis key existence: %w", err)
}
return count > 0, nil
}
// GetConfig 获取配置对象
func (f *Factory) GetConfig() *config.Config {
return f.cfg
}
// getStorage 获取存储实例(内部方法,延迟初始化)
func (f *Factory) getStorage() (storage.Storage, error) {
if f.storage != nil {
return f.storage, nil
}
// 根据配置自动选择存储类型
// 优先级MinIO > OSS
var storageType storage.StorageType
if f.cfg.MinIO != nil {
storageType = storage.StorageTypeMinIO
} else if f.cfg.OSS != nil {
storageType = storage.StorageTypeOSS
} else {
return nil, fmt.Errorf("no storage config found (OSS or MinIO)")
}
// 创建存储实例
s, err := storage.NewStorage(storageType, f.cfg)
if err != nil {
return nil, fmt.Errorf("failed to create storage: %w", err)
}
f.storage = s
return s, nil
}
// UploadFile 上传文件(黑盒模式,推荐使用)
// 自动根据配置选择存储类型OSS 或 MinIO无需关心内部实现
// ctx: 上下文
// objectKey: 对象键(文件路径)
// reader: 文件内容
// contentType: 文件类型(可选)
// 返回文件访问URL和错误
func (f *Factory) UploadFile(ctx context.Context, objectKey string, reader io.Reader, contentType ...string) (string, error) {
s, err := f.getStorage()
if err != nil {
return "", err
}
// 上传文件
err = s.Upload(ctx, objectKey, reader, contentType...)
if err != nil {
return "", fmt.Errorf("failed to upload file: %w", err)
}
// 获取文件URL
url, err := s.GetURL(objectKey, 0)
if err != nil {
return "", fmt.Errorf("failed to get file URL: %w", err)
}
return url, nil
}
// GetFileURL 获取文件访问URL黑盒模式推荐使用
// 自动根据配置选择存储类型返回文件的访问URL
// objectKey: 对象键(文件路径)
// expires: 过期时间0表示永久有效
func (f *Factory) GetFileURL(objectKey string, expires int64) (string, error) {
s, err := f.getStorage()
if err != nil {
return "", err
}
return s.GetURL(objectKey, expires)
}
// GetMiddlewareChain 获取配置好的中间件链(黑盒模式)
// 自动包含Recovery、Logging、RateLimit如果配置了、CORS如果配置了、Timezone
// 返回已配置好的中间件链,可以通过 Append() 方法添加自定义中间件
//
// 示例1直接使用
//
// chain := factory.GetMiddlewareChain()
// http.Handle("/api/users", chain.ThenFunc(handleUsers))
//
// 示例2添加自定义中间件
//
// chain := factory.GetMiddlewareChain()
// chain.Append(yourCustomMiddleware1, yourCustomMiddleware2)
// http.Handle("/api/users", chain.ThenFunc(handleUsers))
func (f *Factory) GetMiddlewareChain() *middleware.Chain {
var middlewares []func(http.Handler) http.Handler
// 1. Recovery 中间件必需防止panic导致服务崩溃
l, _ := f.getLogger() // 获取logger如果失败会使用默认logger
middlewares = append(middlewares, middleware.Recovery(&middleware.RecoveryConfig{
Logger: l,
}))
// 2. Logging 中间件(必需,记录所有请求)
middlewares = append(middlewares, middleware.Logging(&middleware.LoggingConfig{
Logger: l,
}))
// 3. RateLimit 中间件(如果配置了限流)
if f.cfg != nil && f.cfg.RateLimit != nil {
if f.cfg.RateLimit.Enable {
// 从配置创建限流中间件
limiter := middleware.NewTokenBucketLimiter(
f.cfg.RateLimit.Rate,
time.Duration(f.cfg.RateLimit.Period)*time.Second,
)
var keyFunc func(r *http.Request) string
if f.cfg.RateLimit.ByIP {
keyFunc = func(r *http.Request) string {
return middleware.GetClientIP(r)
}
} else if f.cfg.RateLimit.ByUserID {
keyFunc = func(r *http.Request) string {
return r.Header.Get("X-User-ID")
}
}
middlewares = append(middlewares, middleware.RateLimit(&middleware.RateLimitConfig{
Limiter: limiter,
KeyFunc: keyFunc,
}))
}
}
// 4. CORS 中间件(如果配置了)
if f.cfg != nil && f.cfg.CORS != nil {
corsConfig := &middleware.CORSConfig{
AllowedOrigins: f.cfg.CORS.AllowedOrigins,
AllowedMethods: f.cfg.CORS.AllowedMethods,
AllowedHeaders: f.cfg.CORS.AllowedHeaders,
ExposedHeaders: f.cfg.CORS.ExposedHeaders,
AllowCredentials: f.cfg.CORS.AllowCredentials,
MaxAge: f.cfg.CORS.MaxAge,
}
middlewares = append(middlewares, middleware.CORS(corsConfig))
}
// 5. Timezone 中间件(必需,处理时区)
middlewares = append(middlewares, middleware.Timezone)
return middleware.NewChain(middlewares...)
}
// RunMigrations 执行数据库迁移(黑盒模式,推荐使用)
// 自动发现并执行指定目录下的所有迁移文件
// migrationsDir: 迁移文件目录(如 "migrations" 或 "scripts/sql"
//
// 支持的文件命名格式:
// - 数字前缀: 01_init_schema.sql
// - 时间戳: 20240101000001_create_users.sql
// - 带.up后缀: 20240101000001_create_users.up.sql
//
// 示例:
//
// fac, _ := factory.NewFactoryFromFile("config.json")
// err := fac.RunMigrations("migrations")
// if err != nil {
// log.Fatal(err)
// }
func (f *Factory) RunMigrations(migrationsDir string) error {
// 获取数据库连接
db, err := f.getDatabase()
if err != nil {
return fmt.Errorf("failed to get database: %w", err)
}
// 创建迁移器(传入数据库类型,性能更好)
dbType := "mysql" // 默认值
if f.cfg.Database != nil && f.cfg.Database.Type != "" {
dbType = f.cfg.Database.Type
}
migrator := migration.NewMigratorWithType(db, dbType)
// 自动发现并加载迁移文件
migrations, err := migration.LoadMigrationsFromFiles(migrationsDir, "*.sql")
if err != nil {
return fmt.Errorf("failed to load migrations: %w", err)
}
if len(migrations) == 0 {
f.LogInfo("在目录 '%s' 中没有找到迁移文件", migrationsDir)
return nil
}
migrator.AddMigrations(migrations...)
// 执行迁移
if err := migrator.Up(); err != nil {
return fmt.Errorf("failed to run migrations: %w", err)
}
f.LogInfo("迁移执行成功: %d 个迁移文件", len(migrations))
return nil
}
// GetMigrationStatus 获取迁移状态(黑盒模式,推荐使用)
// migrationsDir: 迁移文件目录
// 返回迁移状态列表,包含版本、描述、是否已应用等信息
//
// 示例:
//
// fac, _ := factory.NewFactoryFromFile("config.json")
// status, err := fac.GetMigrationStatus("migrations")
// if err != nil {
// log.Fatal(err)
// }
// for _, s := range status {
// fmt.Printf("Version: %s, Applied: %v\n", s.Version, s.Applied)
// }
func (f *Factory) GetMigrationStatus(migrationsDir string) ([]migration.MigrationStatus, error) {
// 获取数据库连接
db, err := f.getDatabase()
if err != nil {
return nil, fmt.Errorf("failed to get database: %w", err)
}
// 创建迁移器(传入数据库类型,性能更好)
dbType := "mysql" // 默认值
if f.cfg.Database != nil && f.cfg.Database.Type != "" {
dbType = f.cfg.Database.Type
}
migrator := migration.NewMigratorWithType(db, dbType)
// 加载迁移文件
migrations, err := migration.LoadMigrationsFromFiles(migrationsDir, "*.sql")
if err != nil {
return nil, fmt.Errorf("failed to load migrations: %w", err)
}
migrator.AddMigrations(migrations...)
// 获取状态
status, err := migrator.Status()
if err != nil {
return nil, fmt.Errorf("failed to get migration status: %w", err)
}
return status, nil
}
// ========== HTTP响应方法黑盒模式推荐使用 ==========
//
// 这些方法直接调用 http 包的公共方法,保持低耦合。
// 推荐直接使用 factory.Success() 等方法,而不是通过 handler。
// Success 成功响应(黑盒模式,推荐使用)
// w: ResponseWriter
// data: 响应数据可以为nil
// message: 响应消息(可选),如果为空则使用默认消息 "success"
//
// 示例:
//
// fac, _ := factory.NewFactoryFromFile("config.json")
// http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
// fac.Success(w, user) // 只有数据
// fac.Success(w, user, "查询成功") // 数据+消息
// })
func (f *Factory) Success(w http.ResponseWriter, data interface{}, message ...string) {
commonhttp.Success(w, data, message...)
}
// SuccessPage 分页成功响应(黑盒模式,推荐使用)
// w: ResponseWriter
// list: 数据列表
// total: 总记录数
// page: 当前页码
// pageSize: 每页大小
// message: 响应消息(可选),如果为空则使用默认消息 "success"
func (f *Factory) SuccessPage(w http.ResponseWriter, list interface{}, total int64, page, pageSize int, message ...string) {
commonhttp.SuccessPage(w, list, total, page, pageSize, message...)
}
// Error 错误响应(黑盒模式,推荐使用)
// w: ResponseWriter
// code: 业务错误码非0表示业务错误
// message: 错误消息
func (f *Factory) Error(w http.ResponseWriter, code int, message string) {
commonhttp.Error(w, code, message)
}
// SystemError 系统错误响应返回HTTP 500黑盒模式推荐使用
// w: ResponseWriter
// message: 错误消息
func (f *Factory) SystemError(w http.ResponseWriter, message string) {
commonhttp.SystemError(w, message)
}
// ========== HTTP请求解析方法黑盒模式推荐使用 ==========
//
// 这些方法直接调用 http 包的公共方法,保持低耦合。
// 推荐直接使用 factory.ParseJSON()、factory.GetQuery() 等方法。
// ParseJSON 解析JSON请求体黑盒模式推荐使用
// r: HTTP请求
// v: 目标结构体指针
//
// 示例:
//
// var req struct {
// Name string `json:"name"`
// }
// if err := fac.ParseJSON(r, &req); err != nil {
// fac.Error(w, 400, "请求参数解析失败")
// return
// }
func (f *Factory) ParseJSON(r *http.Request, v interface{}) error {
return commonhttp.ParseJSON(r, v)
}
// ParsePaginationRequest 从请求中解析分页参数(黑盒模式,推荐使用)
// r: HTTP请求
// 支持从查询参数和form表单中解析
// 优先级:查询参数 > form表单
//
// 示例:
//
// pagination := fac.ParsePaginationRequest(r)
// page := pagination.GetPage()
// pageSize := pagination.GetSize()
func (f *Factory) ParsePaginationRequest(r *http.Request) *PaginationRequest {
return commonhttp.ParsePaginationRequest(r)
}
// GetQuery 获取查询参数(字符串)(黑盒模式,推荐使用)
// r: HTTP请求
// key: 参数名
// defaultValue: 默认值
func (f *Factory) GetQuery(r *http.Request, key, defaultValue string) string {
return commonhttp.GetQuery(r, key, defaultValue)
}
// GetQueryInt 获取整数查询参数(黑盒模式,推荐使用)
// r: HTTP请求
// key: 参数名
// defaultValue: 默认值
func (f *Factory) GetQueryInt(r *http.Request, key string, defaultValue int) int {
return commonhttp.GetQueryInt(r, key, defaultValue)
}
// GetQueryInt64 获取int64查询参数黑盒模式推荐使用
// r: HTTP请求
// key: 参数名
// defaultValue: 默认值
func (f *Factory) GetQueryInt64(r *http.Request, key string, defaultValue int64) int64 {
return commonhttp.GetQueryInt64(r, key, defaultValue)
}
// GetQueryBool 获取布尔查询参数(黑盒模式,推荐使用)
// r: HTTP请求
// key: 参数名
// defaultValue: 默认值
func (f *Factory) GetQueryBool(r *http.Request, key string, defaultValue bool) bool {
return commonhttp.GetQueryBool(r, key, defaultValue)
}
// GetQueryFloat64 获取float64查询参数黑盒模式推荐使用
// r: HTTP请求
// key: 参数名
// defaultValue: 默认值
func (f *Factory) GetQueryFloat64(r *http.Request, key string, defaultValue float64) float64 {
return commonhttp.GetQueryFloat64(r, key, defaultValue)
}
// GetFormValue 获取表单值(字符串)(黑盒模式,推荐使用)
// r: HTTP请求
// key: 参数名
// defaultValue: 默认值
func (f *Factory) GetFormValue(r *http.Request, key, defaultValue string) string {
return commonhttp.GetFormValue(r, key, defaultValue)
}
// GetFormInt 获取表单整数(黑盒模式,推荐使用)
// r: HTTP请求
// key: 参数名
// defaultValue: 默认值
func (f *Factory) GetFormInt(r *http.Request, key string, defaultValue int) int {
return commonhttp.GetFormInt(r, key, defaultValue)
}
// GetFormInt64 获取表单int64黑盒模式推荐使用
// r: HTTP请求
// key: 参数名
// defaultValue: 默认值
func (f *Factory) GetFormInt64(r *http.Request, key string, defaultValue int64) int64 {
return commonhttp.GetFormInt64(r, key, defaultValue)
}
// GetFormBool 获取表单布尔值(黑盒模式,推荐使用)
// r: HTTP请求
// key: 参数名
// defaultValue: 默认值
func (f *Factory) GetFormBool(r *http.Request, key string, defaultValue bool) bool {
return commonhttp.GetFormBool(r, key, defaultValue)
}
// GetHeader 获取请求头(黑盒模式,推荐使用)
// r: HTTP请求
// key: 请求头名称
// defaultValue: 默认值
func (f *Factory) GetHeader(r *http.Request, key, defaultValue string) string {
return commonhttp.GetHeader(r, key, defaultValue)
}
// GetTimezone 从请求的context中获取时区黑盒模式推荐使用
// r: HTTP请求
// 如果使用了middleware.Timezone中间件可以从context中获取时区信息
// 如果未设置,返回默认时区 AsiaShanghai
func (f *Factory) GetTimezone(r *http.Request) string {
return commonhttp.GetTimezone(r)
}
// ========== Tools工具方法黑盒模式推荐使用 ==========
//
// 这些方法直接调用 tools 包的公共方法,保持低耦合。
// factory 只负责方法暴露,具体业务在 tools 包中实现。
// ========== Version 版本工具 ==========
// GetVersion 获取版本号(黑盒模式,推荐使用)
// 优先从环境变量 DOCKER_TAG 或 VERSION 中读取
// 如果没有设置环境变量,则使用默认版本号
func (f *Factory) GetVersion() string {
return tools.GetVersion()
}
// ========== Money 金额工具 ==========
// GetMoneyCalculator 获取金额计算器(黑盒模式,推荐使用)
// 返回金额计算器实例,可用于金额计算操作
func (f *Factory) GetMoneyCalculator() *tools.MoneyCalculator {
return tools.NewMoneyCalculator()
}
// YuanToCents 元转分(黑盒模式,推荐使用)
func (f *Factory) YuanToCents(yuan float64) int64 {
return tools.YuanToCents(yuan)
}
// CentsToYuan 分转元(黑盒模式,推荐使用)
func (f *Factory) CentsToYuan(cents int64) float64 {
return tools.CentsToYuan(cents)
}
// FormatYuan 格式化显示金额分转元保留2位小数黑盒模式推荐使用
func (f *Factory) FormatYuan(cents int64) string {
return tools.FormatYuan(cents)
}
// ========== DateTime 日期时间工具 ==========
// Now 获取当前时间(使用指定时区)(黑盒模式,推荐使用)
func (f *Factory) Now(timezone ...string) time.Time {
return tools.Now(timezone...)
}
// ParseDateTime 解析日期时间字符串2006-01-02 15:04:05黑盒模式推荐使用
func (f *Factory) ParseDateTime(value string, timezone ...string) (time.Time, error) {
return tools.ParseDateTime(value, timezone...)
}
// ParseDate 解析日期字符串2006-01-02黑盒模式推荐使用
func (f *Factory) ParseDate(value string, timezone ...string) (time.Time, error) {
return tools.ParseDate(value, timezone...)
}
// FormatDateTime 格式化日期时间2006-01-02 15:04:05黑盒模式推荐使用
func (f *Factory) FormatDateTime(t time.Time, timezone ...string) string {
return tools.FormatDateTime(t, timezone...)
}
// FormatDate 格式化日期2006-01-02黑盒模式推荐使用
func (f *Factory) FormatDate(t time.Time, timezone ...string) string {
return tools.FormatDate(t, timezone...)
}
// FormatTime 格式化时间15:04:05黑盒模式推荐使用
func (f *Factory) FormatTime(t time.Time, timezone ...string) string {
return tools.FormatTime(t, timezone...)
}
// ToUnix 转换为Unix时间戳黑盒模式推荐使用
func (f *Factory) ToUnix(t time.Time) int64 {
return tools.ToUnix(t)
}
// FromUnix 从Unix时间戳创建时间黑盒模式推荐使用
func (f *Factory) FromUnix(sec int64, timezone ...string) time.Time {
return tools.FromUnix(sec, timezone...)
}
// ToUnixMilli 转换为Unix毫秒时间戳黑盒模式推荐使用
func (f *Factory) ToUnixMilli(t time.Time) int64 {
return tools.ToUnixMilli(t)
}
// FromUnixMilli 从Unix毫秒时间戳创建时间黑盒模式推荐使用
func (f *Factory) FromUnixMilli(msec int64, timezone ...string) time.Time {
return tools.FromUnixMilli(msec, timezone...)
}
// AddDays 添加天数(黑盒模式,推荐使用)
func (f *Factory) AddDays(t time.Time, days int) time.Time {
return tools.AddDays(t, days)
}
// AddMonths 添加月数(黑盒模式,推荐使用)
func (f *Factory) AddMonths(t time.Time, months int) time.Time {
return tools.AddMonths(t, months)
}
// AddYears 添加年数(黑盒模式,推荐使用)
func (f *Factory) AddYears(t time.Time, years int) time.Time {
return tools.AddYears(t, years)
}
// StartOfDay 获取一天的开始时间00:00:00黑盒模式推荐使用
func (f *Factory) StartOfDay(t time.Time, timezone ...string) time.Time {
return tools.StartOfDay(t, timezone...)
}
// EndOfDay 获取一天的结束时间23:59:59.999999999)(黑盒模式,推荐使用)
func (f *Factory) EndOfDay(t time.Time, timezone ...string) time.Time {
return tools.EndOfDay(t, timezone...)
}
// StartOfMonth 获取月份的开始时间(黑盒模式,推荐使用)
func (f *Factory) StartOfMonth(t time.Time, timezone ...string) time.Time {
return tools.StartOfMonth(t, timezone...)
}
// EndOfMonth 获取月份的结束时间(黑盒模式,推荐使用)
func (f *Factory) EndOfMonth(t time.Time, timezone ...string) time.Time {
return tools.EndOfMonth(t, timezone...)
}
// StartOfYear 获取年份的开始时间(黑盒模式,推荐使用)
func (f *Factory) StartOfYear(t time.Time, timezone ...string) time.Time {
return tools.StartOfYear(t, timezone...)
}
// EndOfYear 获取年份的结束时间(黑盒模式,推荐使用)
func (f *Factory) EndOfYear(t time.Time, timezone ...string) time.Time {
return tools.EndOfYear(t, timezone...)
}
// DiffDays 计算两个时间之间的天数差(黑盒模式,推荐使用)
func (f *Factory) DiffDays(t1, t2 time.Time) int {
return tools.DiffDays(t1, t2)
}
// DiffHours 计算两个时间之间的小时差(黑盒模式,推荐使用)
func (f *Factory) DiffHours(t1, t2 time.Time) int64 {
return tools.DiffHours(t1, t2)
}
// DiffMinutes 计算两个时间之间的分钟差(黑盒模式,推荐使用)
func (f *Factory) DiffMinutes(t1, t2 time.Time) int64 {
return tools.DiffMinutes(t1, t2)
}
// DiffSeconds 计算两个时间之间的秒数差(黑盒模式,推荐使用)
func (f *Factory) DiffSeconds(t1, t2 time.Time) int64 {
return tools.DiffSeconds(t1, t2)
}