测试修复文件上传 bug
This commit is contained in:
@@ -87,6 +87,7 @@ func (h *Handler) writeObject(c *gin.Context, publicOnly bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeFileError(c *gin.Context, err error) {
|
func writeFileError(c *gin.Context, err error) {
|
||||||
|
_ = c.Error(err)
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, ErrDependencyUnavailable):
|
case errors.Is(err, ErrDependencyUnavailable):
|
||||||
response.ServiceUnavailable(c, "文件存储未连接")
|
response.ServiceUnavailable(c, "文件存储未连接")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -56,7 +57,7 @@ func (s *Service) Upload(req uploadRequest) (*UploadDTO, error) {
|
|||||||
if err := s.storage.PutObject(req.Context, key, bytes.NewReader(data), int64(len(data)), contentType, map[string]string{
|
if err := s.storage.PutObject(req.Context, key, bytes.NewReader(data), int64(len(data)), contentType, map[string]string{
|
||||||
"original-filename": req.Header.Filename,
|
"original-filename": req.Header.Filename,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("%w: %v", ErrDependencyUnavailable, err)
|
||||||
}
|
}
|
||||||
var thumbnailURL string
|
var thumbnailURL string
|
||||||
var mediumURL string
|
var mediumURL string
|
||||||
|
|||||||
@@ -70,8 +70,18 @@ func (s *Storage) PutObject(ctx context.Context, key string, reader io.Reader, s
|
|||||||
}
|
}
|
||||||
_, err := s.client.PutObject(ctx, s.bucket, key, reader, size, minio.PutObjectOptions{
|
_, err := s.client.PutObject(ctx, s.bucket, key, reader, size, minio.PutObjectOptions{
|
||||||
ContentType: contentType,
|
ContentType: contentType,
|
||||||
UserMetadata: metadata,
|
UserMetadata: sanitizeObjectMetadata(metadata),
|
||||||
})
|
})
|
||||||
|
if isNoSuchBucketError(err) && rewindReader(reader) == nil {
|
||||||
|
s.markBucketNotReady()
|
||||||
|
if readyErr := s.ensureBucketReady(ctx); readyErr != nil {
|
||||||
|
return readyErr
|
||||||
|
}
|
||||||
|
_, err = s.client.PutObject(ctx, s.bucket, key, reader, size, minio.PutObjectOptions{
|
||||||
|
ContentType: contentType,
|
||||||
|
UserMetadata: sanitizeObjectMetadata(metadata),
|
||||||
|
})
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +130,48 @@ func (s *Storage) ensureBucketReady(ctx context.Context) error {
|
|||||||
return s.ensureBucket(ctx)
|
return s.ensureBucket(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Storage) markBucketNotReady() {
|
||||||
|
s.bucketMu.Lock()
|
||||||
|
defer s.bucketMu.Unlock()
|
||||||
|
s.bucketReady = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNoSuchBucketError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
errResp := minio.ToErrorResponse(err)
|
||||||
|
if errResp.Code == "NoSuchBucket" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return strings.Contains(strings.ToLower(err.Error()), "bucket does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
func rewindReader(reader io.Reader) error {
|
||||||
|
seeker, ok := reader.(io.Seeker)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("reader cannot rewind")
|
||||||
|
}
|
||||||
|
_, err := seeker.Seek(0, io.SeekStart)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeObjectMetadata(metadata map[string]string) map[string]string {
|
||||||
|
if len(metadata) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sanitized := make(map[string]string, len(metadata))
|
||||||
|
for key, value := range metadata {
|
||||||
|
key = strings.TrimSpace(key)
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// MinIO/S3 用户元数据会作为 HTTP header 发送,值统一转义为 ASCII,避免中文文件名导致上传失败。
|
||||||
|
sanitized[key] = url.QueryEscape(value)
|
||||||
|
}
|
||||||
|
return sanitized
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeEndpoint(raw string) (string, bool, error) {
|
func normalizeEndpoint(raw string) (string, bool, error) {
|
||||||
parsed, err := url.Parse(raw)
|
parsed, err := url.Parse(raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSanitizeObjectMetadataEscapesNonASCII(t *testing.T) {
|
||||||
|
got := sanitizeObjectMetadata(map[string]string{
|
||||||
|
"original-filename": "游戏ID截图 1.png",
|
||||||
|
})
|
||||||
|
|
||||||
|
if got["original-filename"] != "%E6%B8%B8%E6%88%8FID%E6%88%AA%E5%9B%BE+1.png" {
|
||||||
|
t.Fatalf("metadata filename = %q", got["original-filename"])
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user