package file import ( "bytes" "context" "io" "mime/multipart" "net/http" "time" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) type FileUploadLogic struct { logger.Logger ctx context.Context svcCtx *svc.ServiceContext } // Upload file to RustFS func NewFileUploadLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FileUploadLogic { return &FileUploadLogic{ Logger: logger.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *FileUploadLogic) FileUpload(req *types.FileUploadRequest, fileHeader *multipart.FileHeader, file multipart.File) (resp *types.FileUploadResponse, err error) { u, err := currentUserFromContext(l.ctx) if err != nil { return nil, err } if fileHeader == nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "file is required") } contentType, err := sniffContentType(fileHeader, file) if err != nil { return nil, err } if err := validateInitRequest(l.svcCtx, req.BizType, fileHeader.Filename, contentType, fileHeader.Size); err != nil { return nil, err } now := time.Now() fileID := buildFileID(u.Id, req.BizType, fileHeader.Filename) objectKey := buildObjectKey(l.svcCtx.Config.S3.Prefix, u.Id, req.BizType, fileID, fileHeader.Filename, now) putResult, err := l.svcCtx.S3Store.PutObject(l.ctx, objectKey, file, fileHeader.Size, contentType) if err != nil { l.Errorw("put object failed", logger.Field("error", err.Error()), logger.Field("user_id", u.Id), logger.Field("file_id", fileID)) return nil, err } return &types.FileUploadResponse{ FileId: fileID, FileName: fileHeader.Filename, ObjectKey: objectKey, Size: fileHeader.Size, ContentType: contentType, Etag: putResult.ETag, Status: fileUploadCompleteStatus, }, nil } func sniffContentType(fileHeader *multipart.FileHeader, file multipart.File) (string, error) { if headerType := fileHeader.Header.Get("Content-Type"); headerType != "" { return headerType, nil } if seeker, ok := file.(io.ReadSeeker); ok { buf := make([]byte, 512) n, readErr := seeker.Read(buf) if readErr != nil && readErr != io.EOF { return "", readErr } if _, err := seeker.Seek(0, io.SeekStart); err != nil { return "", err } return http.DetectContentType(bytes.TrimRight(buf[:n], "\x00")), nil } return "application/octet-stream", nil }