向宁 11315fd00b feat: wire Watermill CQRS — EventBus in usecases, event handlers, router lifecycle
- Add EventPublisher interface in biz layer for domain event publishing
- Wire EventBusPublisher (Watermill EventBus adapter) into FileUsecase, FolderUsecase, ShareUsecase
- Publish events after UploadFile, DeleteFile, CreateFolder, DeleteFolder, CreateShare
- Implement CQRSHandler with logging event handlers for all 6 event types
- Register event handlers via CQRSBus.RegisterHandlers using Watermill EventProcessor
- Store subscriber and wmLogger in CQRSBus for EventProcessor wiring
- Expose SqlDB() on Data struct for Watermill SQL pub/sub
- Start Watermill router in goroutine alongside Kratos app with graceful close
- Use appContext wrapper struct to pass CQRSBus through Wire DI graph
2026-05-25 13:52:05 +08:00

127 lines
4.1 KiB
Go

package data
import (
"context"
"database/sql"
"time"
"rag/file-system/internal/conf"
"github.com/go-kratos/kratos/v2/log"
"github.com/google/wire"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// Data holds the GORM database connection and provides transaction support.
type Data struct {
db *gorm.DB
log *log.Helper
}
// contextTxKey is the context key for storing the current transaction.
type contextTxKey struct{}
// NewData creates a new Data instance with GORM connected to PostgreSQL.
func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) {
helper := log.NewHelper(logger)
db, err := gorm.Open(postgres.Open(c.Database.Source), &gorm.Config{})
if err != nil {
return nil, nil, err
}
sqlDB, err := db.DB()
if err != nil {
return nil, nil, err
}
sqlDB.SetMaxOpenConns(25)
sqlDB.SetMaxIdleConns(5)
if err := db.AutoMigrate(&FolderPO{}, &FileMetaPO{}, &ShareLinkPO{}); err != nil {
return nil, nil, err
}
helper.Info("connected to PostgreSQL via GORM")
cleanup := func() {
sqlDB.Close()
}
return &Data{db: db, log: helper}, cleanup, nil
}
// SqlDB returns the underlying *sql.DB for use by Watermill.
func (d *Data) SqlDB() (*sql.DB, error) {
return d.db.DB()
}
// DB returns the *gorm.DB for the given context. If a transaction is active
// in the context, it returns the transaction DB; otherwise the global DB.
func (d *Data) DB(ctx context.Context) *gorm.DB {
tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
if ok {
return tx
}
return d.db.WithContext(ctx)
}
// Transaction is the interface for executing operations within a database transaction.
type Transaction interface {
InTx(ctx context.Context, fn func(ctx context.Context) error) error
}
// InTx executes fn inside a database transaction.
func (d *Data) InTx(ctx context.Context, fn func(ctx context.Context) error) error {
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
ctx = context.WithValue(ctx, contextTxKey{}, tx)
return fn(ctx)
})
}
// ProviderSet is the Wire provider set for the data layer.
var ProviderSet = wire.NewSet(NewData, NewFileRepo, NewFolderRepo, NewFileMetaRepo, NewShareRepo)
// --- GORM Models (Persistence Objects) ---
// FolderPO maps to the "folders" table.
type FolderPO struct {
ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()"`
ParentID *string `gorm:"type:uuid;index:idx_folders_parent"`
Name string `gorm:"type:varchar(255);not null"`
OwnerID string `gorm:"type:varchar(36);not null;index:idx_folders_owner"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (FolderPO) TableName() string { return "folders" }
// FileMetaPO maps to the "files" table.
type FileMetaPO struct {
ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()"`
FolderID string `gorm:"type:uuid;index:idx_files_folder"`
Name string `gorm:"type:varchar(255);not null"`
S3Key string `gorm:"type:varchar(512);not null;index:idx_files_s3_key"`
S3Bucket string `gorm:"type:varchar(255);not null"`
Size int64 `gorm:"default:0"`
ContentType string `gorm:"type:varchar(255);default:'application/octet-stream'"`
OwnerID string `gorm:"type:varchar(36);not null;index:idx_files_owner"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (FileMetaPO) TableName() string { return "files" }
// ShareLinkPO maps to the "share_links" table.
type ShareLinkPO struct {
ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()"`
ResourceType string `gorm:"type:varchar(10);not null"`
ResourceID string `gorm:"type:uuid;not null"`
Token string `gorm:"type:varchar(32);not null;uniqueIndex:idx_share_token"`
Password *string `gorm:"type:varchar(255)"`
ExpiresAt *time.Time `gorm:"type:timestamptz"`
DownloadCount int `gorm:"default:0"`
MaxDownloads *int
CreatedBy string `gorm:"type:varchar(36);not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
}
func (ShareLinkPO) TableName() string { return "share_links" }