feat: tenant-scoped routing and portal navigation

This commit is contained in:
2026-01-08 21:30:46 +08:00
parent f3aa92078a
commit 3e095c57f3
52 changed files with 1111 additions and 670 deletions

View File

@@ -27,6 +27,7 @@ import (
"github.com/google/uuid"
"github.com/jackc/pgconn"
"go.ipao.vip/gen/types"
"gorm.io/gorm"
)
// @provider
@@ -49,7 +50,7 @@ func (s *common) Options(ctx context.Context) (*common_dto.OptionsResponse, erro
}, nil
}
func (s *common) CheckHash(ctx context.Context, userID int64, hash string) (*common_dto.UploadResult, error) {
func (s *common) CheckHash(ctx context.Context, tenantID, userID int64, hash string) (*common_dto.UploadResult, error) {
existing, err := models.MediaAssetQuery.WithContext(ctx).Where(models.MediaAssetQuery.Hash.Eq(hash)).First()
if err != nil {
return nil, nil // Not found, proceed to upload
@@ -58,18 +59,25 @@ func (s *common) CheckHash(ctx context.Context, userID int64, hash string) (*com
// Found existing file (Global deduplication hit)
// Check if user already has it (Logic deduplication hit)
myExisting, err := models.MediaAssetQuery.WithContext(ctx).
Where(models.MediaAssetQuery.Hash.Eq(hash), models.MediaAssetQuery.UserID.Eq(userID)).
First()
myQuery := models.MediaAssetQuery.WithContext(ctx).
Where(models.MediaAssetQuery.Hash.Eq(hash), models.MediaAssetQuery.UserID.Eq(userID))
if tenantID > 0 {
myQuery = myQuery.Where(models.MediaAssetQuery.TenantID.Eq(tenantID))
}
myExisting, err := myQuery.First()
if err == nil {
return s.composeUploadResult(myExisting), nil
}
// Create new record for this user reusing existing ObjectKey
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.UserID.Eq(userID)).First()
var tid int64 = 0
if err == nil {
tid = t.ID
// 优先使用路径租户,避免跨租户写入。
tenant, err := s.resolveTenant(ctx, tenantID, userID)
if err != nil {
return nil, err
}
var tid int64
if tenant != nil {
tid = tenant.ID
}
asset := &models.MediaAsset{
@@ -107,13 +115,47 @@ func (s *common) buildObjectKey(tenant *models.Tenant, hash, filename string) st
return path.Join("quyun", tenantUUID, hash+ext)
}
func (s *common) InitUpload(ctx context.Context, userID int64, form *common_dto.UploadInitForm) (*common_dto.UploadInitResponse, error) {
func (s *common) resolveTenant(ctx context.Context, tenantID, userID int64) (*models.Tenant, error) {
if tenantID > 0 {
tbl, q := models.TenantQuery.QueryContext(ctx)
tenant, err := q.Where(tbl.ID.Eq(tenantID)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound.WithMsg("租户不存在")
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return tenant, nil
}
if userID == 0 {
return nil, nil
}
tbl, q := models.TenantQuery.QueryContext(ctx)
tenant, err := q.Where(tbl.UserID.Eq(userID)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return tenant, nil
}
func (s *common) uploadTempDir(localPath string, tenantID int64, uploadID string) string {
tenantKey := "public"
if tenantID > 0 {
tenantKey = strconv.FormatInt(tenantID, 10)
}
return filepath.Join(localPath, "temp", tenantKey, uploadID)
}
func (s *common) InitUpload(ctx context.Context, tenantID, userID int64, form *common_dto.UploadInitForm) (*common_dto.UploadInitResponse, error) {
uploadID := uuid.NewString()
localPath := s.storage.Config.LocalPath
if localPath == "" {
localPath = "./storage"
}
tempDir := filepath.Join(localPath, "temp", uploadID)
tempDir := s.uploadTempDir(localPath, tenantID, uploadID)
if err := os.MkdirAll(tempDir, 0o755); err != nil {
return nil, errorx.ErrInternalError.WithCause(err)
}
@@ -134,12 +176,12 @@ func (s *common) InitUpload(ctx context.Context, userID int64, form *common_dto.
}, nil
}
func (s *common) UploadPart(ctx context.Context, userID int64, file *multipart.FileHeader, form *common_dto.UploadPartForm) error {
func (s *common) UploadPart(ctx context.Context, tenantID, userID int64, file *multipart.FileHeader, form *common_dto.UploadPartForm) error {
localPath := s.storage.Config.LocalPath
if localPath == "" {
localPath = "./storage"
}
partPath := filepath.Join(localPath, "temp", form.UploadID, strconv.Itoa(form.PartNumber))
partPath := filepath.Join(s.uploadTempDir(localPath, tenantID, form.UploadID), strconv.Itoa(form.PartNumber))
src, err := file.Open()
if err != nil {
@@ -159,12 +201,12 @@ func (s *common) UploadPart(ctx context.Context, userID int64, file *multipart.F
return nil
}
func (s *common) CompleteUpload(ctx context.Context, userID int64, form *common_dto.UploadCompleteForm) (*common_dto.UploadResult, error) {
func (s *common) CompleteUpload(ctx context.Context, tenantID, userID int64, form *common_dto.UploadCompleteForm) (*common_dto.UploadResult, error) {
localPath := s.storage.Config.LocalPath
if localPath == "" {
localPath = "./storage"
}
tempDir := filepath.Join(localPath, "temp", form.UploadID)
tempDir := s.uploadTempDir(localPath, tenantID, form.UploadID)
// Read Meta
var meta UploadMeta
@@ -220,21 +262,27 @@ func (s *common) CompleteUpload(ctx context.Context, userID int64, form *common_
dst.Close() // Ensure flush before potential removal
// Deduplication Logic (Similar to Upload)
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.UserID.Eq(userID)).First()
var tid int64 = 0
if err == nil {
tid = t.ID
tenant, err := s.resolveTenant(ctx, tenantID, userID)
if err != nil {
return nil, err
}
var tid int64
if tenant != nil {
tid = tenant.ID
}
objectKey := s.buildObjectKey(t, hash, meta.Filename)
objectKey := s.buildObjectKey(tenant, hash, meta.Filename)
existing, err := models.MediaAssetQuery.WithContext(ctx).Where(models.MediaAssetQuery.Hash.Eq(hash)).First()
var asset *models.MediaAsset
if err == nil {
os.Remove(mergedPath) // Delete duplicate
myExisting, err := models.MediaAssetQuery.WithContext(ctx).
Where(models.MediaAssetQuery.Hash.Eq(hash), models.MediaAssetQuery.UserID.Eq(userID)).
First()
myQuery := models.MediaAssetQuery.WithContext(ctx).
Where(models.MediaAssetQuery.Hash.Eq(hash), models.MediaAssetQuery.UserID.Eq(userID))
if tenantID > 0 {
myQuery = myQuery.Where(models.MediaAssetQuery.TenantID.Eq(tenantID))
}
myExisting, err := myQuery.First()
if err == nil {
os.RemoveAll(tempDir)
return s.composeUploadResult(myExisting), nil
@@ -282,10 +330,13 @@ func (s *common) CompleteUpload(ctx context.Context, userID int64, form *common_
return s.composeUploadResult(asset), nil
}
func (s *common) DeleteMediaAsset(ctx context.Context, userID, id int64) error {
asset, err := models.MediaAssetQuery.WithContext(ctx).
Where(models.MediaAssetQuery.ID.Eq(id), models.MediaAssetQuery.UserID.Eq(userID)).
First()
func (s *common) DeleteMediaAsset(ctx context.Context, tenantID, userID, id int64) error {
query := models.MediaAssetQuery.WithContext(ctx).
Where(models.MediaAssetQuery.ID.Eq(id), models.MediaAssetQuery.UserID.Eq(userID))
if tenantID > 0 {
query = query.Where(models.MediaAssetQuery.TenantID.Eq(tenantID))
}
asset, err := query.First()
if err != nil {
return errorx.ErrRecordNotFound
}
@@ -308,17 +359,18 @@ func (s *common) DeleteMediaAsset(ctx context.Context, userID, id int64) error {
return nil
}
func (s *common) AbortUpload(ctx context.Context, userID int64, uploadId string) error {
func (s *common) AbortUpload(ctx context.Context, tenantID, userID int64, uploadId string) error {
localPath := s.storage.Config.LocalPath
if localPath == "" {
localPath = "./storage"
}
tempDir := filepath.Join(localPath, "temp", uploadId)
tempDir := s.uploadTempDir(localPath, tenantID, uploadId)
return os.RemoveAll(tempDir)
}
func (s *common) Upload(
ctx context.Context,
tenantID int64,
userID int64,
file *multipart.FileHeader,
typeArg string,
@@ -357,13 +409,16 @@ func (s *common) Upload(
hash := hex.EncodeToString(hasher.Sum(nil))
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.UserID.Eq(userID)).First()
var tid int64 = 0
if err == nil {
tid = t.ID
tenant, err := s.resolveTenant(ctx, tenantID, userID)
if err != nil {
return nil, err
}
var tid int64
if tenant != nil {
tid = tenant.ID
}
objectKey := s.buildObjectKey(t, hash, file.Filename)
objectKey := s.buildObjectKey(tenant, hash, file.Filename)
var asset *models.MediaAsset
// Deduplication Check
@@ -374,9 +429,12 @@ func (s *common) Upload(
os.RemoveAll(tmpDir)
// Check if user already has it (Logic Deduplication)
myExisting, err := models.MediaAssetQuery.WithContext(ctx).
Where(models.MediaAssetQuery.Hash.Eq(hash), models.MediaAssetQuery.UserID.Eq(userID)).
First()
myQuery := models.MediaAssetQuery.WithContext(ctx).
Where(models.MediaAssetQuery.Hash.Eq(hash), models.MediaAssetQuery.UserID.Eq(userID))
if tenantID > 0 {
myQuery = myQuery.Where(models.MediaAssetQuery.TenantID.Eq(tenantID))
}
myExisting, err := myQuery.First()
if err == nil {
return s.composeUploadResult(myExisting), nil
}