89 lines
2.5 KiB
Go
89 lines
2.5 KiB
Go
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
|
|
}
|