向宁 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

121 lines
4.7 KiB
Go

package biz
import (
"context"
"io"
"time"
"rag/file-system/internal/data"
"rag/file-system/internal/watermark"
"github.com/go-kratos/kratos/v2/log"
)
// FileRepo defines the interface for S3 storage operations.
type FileRepo interface {
UploadFile(ctx context.Context, bucket, key string, fileData io.Reader) error
DownloadFile(ctx context.Context, bucket, key string) (io.ReadCloser, error)
ListBuckets(ctx context.Context) ([]string, error)
CreateBucket(ctx context.Context, name string) error
DeleteBucket(ctx context.Context, name string) error
ListObjectsV2(ctx context.Context, bucket, prefix string, maxKeys int32, token *string) (*data.ListFilesResult, error)
GeneratePresignedURL(ctx context.Context, bucket, key string, expiry time.Duration) (string, error)
GetFileContent(ctx context.Context, bucket, key string) (string, error)
DeleteFile(ctx context.Context, bucket, key string) error
CreateMultipartUpload(ctx context.Context, bucket, key string) (string, error)
UploadPart(ctx context.Context, bucket, key, uploadID string, partNumber int32, fileData io.Reader) (string, error)
CompleteMultipartUpload(ctx context.Context, bucket, key, uploadID string, parts []data.Part) (string, error)
AbortMultipartUpload(ctx context.Context, bucket, key, uploadID string) error
}
// FileUsecase wraps FileRepo and provides file-level business operations.
type FileUsecase struct {
repo FileRepo
eventPub EventPublisher
log *log.Helper
}
// NewFileUsecase creates a new FileUsecase.
func NewFileUsecase(repo FileRepo, eventPub EventPublisher, logger log.Logger) *FileUsecase {
return &FileUsecase{
repo: repo,
eventPub: eventPub,
log: log.NewHelper(logger),
}
}
// UploadFile uploads data to the specified bucket and key.
func (uc *FileUsecase) UploadFile(ctx context.Context, bucket, key string, fileData io.Reader) error {
if err := uc.repo.UploadFile(ctx, bucket, key, fileData); err != nil {
return err
}
// Publish domain event on success.
if err := uc.eventPub.Publish(ctx, &watermark.FileUploadedEvent{
BucketName: bucket,
ObjectKey: key,
}); err != nil {
uc.log.Errorf("failed to publish FileUploadedEvent: %v", err)
}
return nil
}
// DownloadFile downloads an object from S3.
func (uc *FileUsecase) DownloadFile(ctx context.Context, bucket, key string) (io.ReadCloser, error) {
return uc.repo.DownloadFile(ctx, bucket, key)
}
// GetFileContent retrieves text content for preview.
func (uc *FileUsecase) GetFileContent(ctx context.Context, bucket, key string) (string, error) {
return uc.repo.GetFileContent(ctx, bucket, key)
}
// DeleteFile removes a file from S3.
func (uc *FileUsecase) DeleteFile(ctx context.Context, bucket, key string) error {
if err := uc.repo.DeleteFile(ctx, bucket, key); err != nil {
return err
}
// Publish domain event on success.
if err := uc.eventPub.Publish(ctx, &watermark.FileDeletedEvent{
BucketName: bucket,
ObjectKey: key,
}); err != nil {
uc.log.Errorf("failed to publish FileDeletedEvent: %v", err)
}
return nil
}
// ListObjectsV2 lists files with pagination support.
func (uc *FileUsecase) ListObjectsV2(ctx context.Context, bucket, prefix string, maxKeys int32, token *string) (*data.ListFilesResult, error) {
return uc.repo.ListObjectsV2(ctx, bucket, prefix, maxKeys, token)
}
// GeneratePresignedURL generates a presigned URL with custom expiry.
func (uc *FileUsecase) GeneratePresignedURL(ctx context.Context, bucket, key string, expiry time.Duration) (string, error) {
return uc.repo.GeneratePresignedURL(ctx, bucket, key, expiry)
}
// GetPreviewURL generates a presigned URL with 24h expiry for file preview.
func (uc *FileUsecase) GetPreviewURL(ctx context.Context, bucket, key string) (string, error) {
return uc.repo.GeneratePresignedURL(ctx, bucket, key, 24*time.Hour)
}
// CreateMultipartUpload initializes a multipart upload session.
func (uc *FileUsecase) CreateMultipartUpload(ctx context.Context, bucket, key string) (string, error) {
return uc.repo.CreateMultipartUpload(ctx, bucket, key)
}
// UploadPart uploads a single part of a multipart upload.
func (uc *FileUsecase) UploadPart(ctx context.Context, bucket, key, uploadID string, partNumber int32, fileData io.Reader) (string, error) {
return uc.repo.UploadPart(ctx, bucket, key, uploadID, partNumber, fileData)
}
// CompleteMultipartUpload assembles all parts to complete the upload.
func (uc *FileUsecase) CompleteMultipartUpload(ctx context.Context, bucket, key, uploadID string, parts []data.Part) (string, error) {
return uc.repo.CompleteMultipartUpload(ctx, bucket, key, uploadID, parts)
}
// AbortMultipartUpload cancels an in-progress multipart upload.
func (uc *FileUsecase) AbortMultipartUpload(ctx context.Context, bucket, key, uploadID string) error {
return uc.repo.AbortMultipartUpload(ctx, bucket, key, uploadID)
}