测试修复文件上传 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) {
|
||||
_ = c.Error(err)
|
||||
switch {
|
||||
case errors.Is(err, ErrDependencyUnavailable):
|
||||
response.ServiceUnavailable(c, "文件存储未连接")
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"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{
|
||||
"original-filename": req.Header.Filename,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%w: %v", ErrDependencyUnavailable, err)
|
||||
}
|
||||
var thumbnailURL 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{
|
||||
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
|
||||
}
|
||||
|
||||
@@ -120,6 +130,48 @@ func (s *Storage) ensureBucketReady(ctx context.Context) error {
|
||||
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) {
|
||||
parsed, err := url.Parse(raw)
|
||||
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