fix: scope creator contents and bind uploads
This commit is contained in:
@@ -11,6 +11,8 @@ type ContentListFilter struct {
|
|||||||
Genre *string `query:"genre"`
|
Genre *string `query:"genre"`
|
||||||
// TenantID 租户ID筛选(内容所属店铺)。
|
// TenantID 租户ID筛选(内容所属店铺)。
|
||||||
TenantID *int64 `query:"tenant_id"`
|
TenantID *int64 `query:"tenant_id"`
|
||||||
|
// AuthorID 作者用户ID筛选(用于筛选创作者作品)。
|
||||||
|
AuthorID *int64 `query:"author_id"`
|
||||||
// Sort 排序规则(latest/hot/price_asc)。
|
// Sort 排序规则(latest/hot/price_asc)。
|
||||||
Sort *string `query:"sort"`
|
Sort *string `query:"sort"`
|
||||||
// IsPinned 置顶内容筛选(true 仅返回置顶)。
|
// IsPinned 置顶内容筛选(true 仅返回置顶)。
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type Tenant struct{}
|
|||||||
// @Tags TenantPublic
|
// @Tags TenantPublic
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path int64 true "Tenant ID"
|
// @Param id path int64 true "Creator User ID"
|
||||||
// @Param page query int false "Page"
|
// @Param page query int false "Page"
|
||||||
// @Param limit query int false "Limit"
|
// @Param limit query int false "Limit"
|
||||||
// @Success 200 {object} requests.Pager
|
// @Success 200 {object} requests.Pager
|
||||||
@@ -29,13 +29,13 @@ type Tenant struct{}
|
|||||||
// @Bind filter query
|
// @Bind filter query
|
||||||
func (t *Tenant) ListContents(ctx fiber.Ctx, id int64, filter *dto.ContentListFilter) (*requests.Pager, error) {
|
func (t *Tenant) ListContents(ctx fiber.Ctx, id int64, filter *dto.ContentListFilter) (*requests.Pager, error) {
|
||||||
tenantID := getTenantID(ctx)
|
tenantID := getTenantID(ctx)
|
||||||
if tenantID > 0 && id != tenantID {
|
|
||||||
return nil, errorx.ErrForbidden.WithMsg("租户不匹配")
|
|
||||||
}
|
|
||||||
if filter == nil {
|
if filter == nil {
|
||||||
filter = &dto.ContentListFilter{}
|
filter = &dto.ContentListFilter{}
|
||||||
}
|
}
|
||||||
|
if tenantID > 0 {
|
||||||
filter.TenantID = &tenantID
|
filter.TenantID = &tenantID
|
||||||
|
}
|
||||||
|
filter.AuthorID = &id
|
||||||
return services.Content.List(ctx, tenantID, filter)
|
return services.Content.List(ctx, tenantID, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,9 +104,16 @@ func (s *common) CheckHash(ctx context.Context, tenantID, userID int64, hash str
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UploadMeta struct {
|
type UploadMeta struct {
|
||||||
Filename string
|
// Filename 原始文件名,用于生成对象路径。
|
||||||
Type string
|
Filename string `json:"filename"`
|
||||||
MimeType string
|
// Type 业务媒体类型(video/audio/image 等)。
|
||||||
|
Type string `json:"type"`
|
||||||
|
// MimeType 上传文件的 MIME 类型。
|
||||||
|
MimeType string `json:"mime_type"`
|
||||||
|
// TenantID 上传所属租户ID,用于归属校验。
|
||||||
|
TenantID int64 `json:"tenant_id"`
|
||||||
|
// UserID 上传发起用户ID,用于归属校验。
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *common) buildObjectKey(tenant *models.Tenant, hash, filename string) string {
|
func (s *common) buildObjectKey(tenant *models.Tenant, hash, filename string) string {
|
||||||
@@ -119,6 +126,31 @@ func (s *common) buildObjectKey(tenant *models.Tenant, hash, filename string) st
|
|||||||
return path.Join("quyun", tenantUUID, hash+ext)
|
return path.Join("quyun", tenantUUID, hash+ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *common) loadUploadMeta(tempDir string) (*UploadMeta, error) {
|
||||||
|
metaFile, err := os.Open(filepath.Join(tempDir, "meta.json"))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, errorx.ErrRecordNotFound.WithCause(err).WithMsg("上传会话不存在")
|
||||||
|
}
|
||||||
|
return nil, errorx.ErrInternalError.WithCause(err)
|
||||||
|
}
|
||||||
|
defer metaFile.Close()
|
||||||
|
|
||||||
|
var meta UploadMeta
|
||||||
|
if err := json.NewDecoder(metaFile).Decode(&meta); err != nil {
|
||||||
|
return nil, errorx.ErrDataCorrupted.WithCause(err).WithMsg("上传会话元信息损坏")
|
||||||
|
}
|
||||||
|
return &meta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *common) verifyUploadOwner(meta *UploadMeta, tenantID, userID int64) error {
|
||||||
|
// 校验上传会话归属,避免同租户猜测 upload_id 进行越权操作。
|
||||||
|
if meta.TenantID != tenantID || meta.UserID != userID {
|
||||||
|
return errorx.ErrForbidden.WithMsg("无权访问该上传会话")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *common) resolveTenant(ctx context.Context, tenantID, userID int64) (*models.Tenant, error) {
|
func (s *common) resolveTenant(ctx context.Context, tenantID, userID int64) (*models.Tenant, error) {
|
||||||
if tenantID > 0 {
|
if tenantID > 0 {
|
||||||
tbl, q := models.TenantQuery.QueryContext(ctx)
|
tbl, q := models.TenantQuery.QueryContext(ctx)
|
||||||
@@ -169,6 +201,8 @@ func (s *common) InitUpload(ctx context.Context, tenantID, userID int64, form *c
|
|||||||
Filename: form.Filename,
|
Filename: form.Filename,
|
||||||
Type: form.Type, // Ensure form has Type
|
Type: form.Type, // Ensure form has Type
|
||||||
MimeType: form.MimeType,
|
MimeType: form.MimeType,
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: userID,
|
||||||
}
|
}
|
||||||
metaFile, _ := os.Create(filepath.Join(tempDir, "meta.json"))
|
metaFile, _ := os.Create(filepath.Join(tempDir, "meta.json"))
|
||||||
json.NewEncoder(metaFile).Encode(meta)
|
json.NewEncoder(metaFile).Encode(meta)
|
||||||
@@ -185,7 +219,15 @@ func (s *common) UploadPart(ctx context.Context, tenantID, userID int64, file *m
|
|||||||
if localPath == "" {
|
if localPath == "" {
|
||||||
localPath = "./storage"
|
localPath = "./storage"
|
||||||
}
|
}
|
||||||
partPath := filepath.Join(s.uploadTempDir(localPath, tenantID, form.UploadID), strconv.Itoa(form.PartNumber))
|
tempDir := s.uploadTempDir(localPath, tenantID, form.UploadID)
|
||||||
|
meta, err := s.loadUploadMeta(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.verifyUploadOwner(meta, tenantID, userID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
partPath := filepath.Join(tempDir, strconv.Itoa(form.PartNumber))
|
||||||
|
|
||||||
src, err := file.Open()
|
src, err := file.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -212,14 +254,14 @@ func (s *common) CompleteUpload(ctx context.Context, tenantID, userID int64, for
|
|||||||
}
|
}
|
||||||
tempDir := s.uploadTempDir(localPath, tenantID, form.UploadID)
|
tempDir := s.uploadTempDir(localPath, tenantID, form.UploadID)
|
||||||
|
|
||||||
// Read Meta
|
// 校验上传会话归属,避免同租户猜测 upload_id 进行越权操作。
|
||||||
var meta UploadMeta
|
meta, err := s.loadUploadMeta(tempDir)
|
||||||
metaFile, err := os.Open(filepath.Join(tempDir, "meta.json"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrRecordNotFound.WithMsg("Upload session expired or invalid")
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.verifyUploadOwner(meta, tenantID, userID); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
json.NewDecoder(metaFile).Decode(&meta)
|
|
||||||
metaFile.Close()
|
|
||||||
|
|
||||||
// List parts
|
// List parts
|
||||||
entries, err := os.ReadDir(tempDir)
|
entries, err := os.ReadDir(tempDir)
|
||||||
@@ -373,6 +415,13 @@ func (s *common) AbortUpload(ctx context.Context, tenantID, userID int64, upload
|
|||||||
localPath = "./storage"
|
localPath = "./storage"
|
||||||
}
|
}
|
||||||
tempDir := s.uploadTempDir(localPath, tenantID, uploadId)
|
tempDir := s.uploadTempDir(localPath, tenantID, uploadId)
|
||||||
|
meta, err := s.loadUploadMeta(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.verifyUploadOwner(meta, tenantID, userID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return os.RemoveAll(tempDir)
|
return os.RemoveAll(tempDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ func (s *content) List(ctx context.Context, tenantID int64, filter *content_dto.
|
|||||||
if tenantID > 0 {
|
if tenantID > 0 {
|
||||||
q = q.Where(tbl.TenantID.Eq(tenantID))
|
q = q.Where(tbl.TenantID.Eq(tenantID))
|
||||||
}
|
}
|
||||||
|
if filter.AuthorID != nil && *filter.AuthorID > 0 {
|
||||||
|
q = q.Where(tbl.UserID.Eq(*filter.AuthorID))
|
||||||
|
}
|
||||||
if filter.Genre != nil && *filter.Genre != "" {
|
if filter.Genre != nil && *filter.Genre != "" {
|
||||||
q = q.Where(tbl.Genre.Eq(*filter.Genre))
|
q = q.Where(tbl.Genre.Eq(*filter.Genre))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user