// Code scaffolded by goctl. Safe to edit. // goctl 1.9.2 package file import ( "context" "fmt" "net/http" "path/filepath" "strings" "time" "github.com/google/uuid" "github.com/youruser/base/internal/svc" "github.com/youruser/base/internal/types" "github.com/youruser/base/model" "github.com/zeromicro/go-zero/core/logx" ) type UploadFileLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } // 上传文件 func NewUploadFileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadFileLogic { return &UploadFileLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *UploadFileLogic) UploadFile(r *http.Request) (resp *types.FileInfo, err error) { // Get userId from context userId, _ := l.ctx.Value("userId").(int64) if userId == 0 { if num, ok := l.ctx.Value("userId").(interface{ Int64() (int64, error) }); ok { userId, _ = num.Int64() } } if userId == 0 { return nil, fmt.Errorf("unauthorized") } // Get file from multipart form formFile, header, err := r.FormFile("file") if err != nil { return nil, fmt.Errorf("failed to get upload file: %v", err) } defer formFile.Close() // Get optional form values category := r.FormValue("category") if category == "" { category = "default" } isPublicStr := r.FormValue("isPublic") isPublic := isPublicStr == "true" || isPublicStr == "1" // Get file metadata fileName := header.Filename fileSize := header.Size mimeType := header.Header.Get("Content-Type") if mimeType == "" { mimeType = "application/octet-stream" } // Generate storage key: {category}/{YYYY-MM}/{uuid}{ext} ext := filepath.Ext(fileName) now := time.Now() key := fmt.Sprintf("%s/%s/%s%s", category, now.Format("2006-01"), uuid.New().String(), ext) // Upload to storage if err := l.svcCtx.Storage.Upload(l.ctx, key, formFile, fileSize, mimeType); err != nil { return nil, fmt.Errorf("failed to upload file: %v", err) } // Insert file record fileRecord := &model.File{ Name: fileName, Key: key, Size: fileSize, MimeType: mimeType, Category: category, IsPublic: isPublic, UserId: userId, StorageType: l.svcCtx.Config.Storage.Type, Status: 1, } fileId, err := model.FileInsert(l.ctx, l.svcCtx.DB, fileRecord) if err != nil { // Cleanup uploaded file on DB failure if delErr := l.svcCtx.Storage.Delete(l.ctx, key); delErr != nil { l.Errorf("failed to cleanup file after DB error: %v", delErr) } return nil, fmt.Errorf("failed to save file record: %v", err) } // Build URL url, err := l.buildFileURL(fileId, key) if err != nil { return nil, fmt.Errorf("failed to get file URL: %v", err) } resp = &types.FileInfo{ Id: fileId, Name: fileName, Key: key, Size: fileSize, MimeType: mimeType, Category: category, IsPublic: isPublic, UserId: userId, StorageType: l.svcCtx.Config.Storage.Type, Url: url, CreatedAt: fileRecord.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: fileRecord.UpdatedAt.Format("2006-01-02 15:04:05"), } return resp, nil } // buildFileURL generates the appropriate URL for the file func (l *UploadFileLogic) buildFileURL(fileId int64, key string) (string, error) { rawURL, err := l.svcCtx.Storage.GetURL(l.ctx, key) if err != nil { return "", err } // For local storage, return API endpoint URL if strings.HasPrefix(rawURL, "local://") { return fmt.Sprintf("/api/v1/file/%d/url", fileId), nil } // For OSS/MinIO, return signed URL directly return rawURL, nil }