Files
team-api/internal/logic/common/storage.go
T
2026-05-10 12:21:27 +08:00

150 lines
4.4 KiB
Go

package common
import (
"context"
"fmt"
"github.com/qianfree/team-api/internal/dao"
do "github.com/qianfree/team-api/internal/model/do"
"io"
"time"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
// StorageProvider defines the interface for file storage backends.
type StorageProvider interface {
// Upload uploads a file and returns the storage path.
Upload(ctx context.Context, reader io.Reader, key string, contentType string) (string, error)
// Download returns a reader for the file at the given path.
Download(ctx context.Context, key string) (io.ReadCloser, error)
// Delete deletes a file.
Delete(ctx context.Context, key string) error
// PresignedURL generates a temporary URL for downloading.
PresignedURL(ctx context.Context, key string, expires time.Duration) (string, error)
}
// FileService provides file storage operations with metadata tracking.
type FileService struct {
provider StorageProvider
providerName string
}
// NewFileService creates a new FileService with the given provider.
func NewFileService(provider StorageProvider) *FileService {
return &FileService{
provider: provider,
providerName: "unknown",
}
}
// FileUpload represents a file upload request.
type FileUpload struct {
Reader io.Reader
Filename string
ContentType string
Size int64
TenantID int64
UserID int64
}
// FileRecord represents a file metadata record.
type FileRecord struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
UserID int64 `json:"user_id"`
Filename string `json:"filename"`
OriginalName string `json:"original_name"`
MimeType string `json:"mime_type"`
Size int64 `json:"size"`
StorageProvider string `json:"storage_provider"`
StoragePath string `json:"storage_path"`
VirusScanStatus string `json:"virus_scan_status"`
Checksum string `json:"checksum"`
CreatedAt string `json:"created_at"`
}
// Upload uploads a file, stores it via the provider, and records metadata.
func (s *FileService) Upload(ctx context.Context, upload *FileUpload) (*FileRecord, error) {
// Generate storage key
key := fmt.Sprintf("%d/%d/%s", upload.TenantID, time.Now().Unix(), upload.Filename)
// Upload to storage provider
storagePath, err := s.provider.Upload(ctx, upload.Reader, key, upload.ContentType)
if err != nil {
return nil, gerror.Wrapf(err, "upload to storage")
}
// Record metadata
result, err := dao.FilFiles.Ctx(ctx).Data(do.FilFiles{
TenantId: upload.TenantID,
UserId: upload.UserID,
Filename: storagePath,
OriginalName: upload.Filename,
MimeType: upload.ContentType,
Size: upload.Size,
StorageProvider: s.providerName,
StoragePath: storagePath,
VirusScanStatus: "pending",
}).Insert()
if err != nil {
// Try to clean up the uploaded file
_ = s.provider.Delete(ctx, key)
return nil, gerror.Wrapf(err, "insert file record")
}
id, _ := result.LastInsertId()
return &FileRecord{
ID: id,
TenantID: upload.TenantID,
UserID: upload.UserID,
Filename: storagePath,
OriginalName: upload.Filename,
MimeType: upload.ContentType,
Size: upload.Size,
StorageProvider: s.providerName,
StoragePath: storagePath,
VirusScanStatus: "pending",
}, nil
}
// GetDownloadURL generates a presigned URL for downloading a file.
func (s *FileService) GetDownloadURL(ctx context.Context, fileID int64) (string, error) {
var record FileRecord
err := dao.FilFiles.Ctx(ctx).
Where("id", fileID).
Scan(&record)
if err != nil || record.ID == 0 {
return "", gerror.Newf("file not found: %d", fileID)
}
return s.provider.PresignedURL(ctx, record.StoragePath, 24*time.Hour)
}
// Delete deletes a file from storage and marks it as deleted.
func (s *FileService) Delete(ctx context.Context, fileID int64) error {
var record FileRecord
err := dao.FilFiles.Ctx(ctx).
Where("id", fileID).
Scan(&record)
if err != nil || record.ID == 0 {
return gerror.Newf("file not found: %d", fileID)
}
// Delete from storage
if err := s.provider.Delete(ctx, record.StoragePath); err != nil {
g.Log().Warningf(ctx, "delete file from storage: %v", err)
}
// Delete record
_, err = dao.FilFiles.Ctx(ctx).
Where("id", fileID).
Delete()
if err != nil {
return gerror.Wrapf(err, "delete file record")
}
return nil
}