- Remove duplicate UploadBaseURL constant (identical to APIBaseURL)
- Pass parentFileID and customDomain into service constructors instead of
reading from config.GlobalConfig at call time, eliminating hidden global
state dependencies in the service layer
- Replace []interface{} response building in HandleList with a typed
imageResponse struct for compile-time safety
- Extract inline CORS closure from main.go into handler.CORSMiddleware(),
consistent with how AuthMiddleware is organized
- Remove narrating comments throughout; keep only the non-obvious one
explaining why DoRawPUT omits auth headers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
114 lines
2.9 KiB
Go
114 lines
2.9 KiB
Go
package service
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"mime/multipart"
|
|
"strings"
|
|
"time"
|
|
|
|
"imagehost/internal/pan123"
|
|
)
|
|
|
|
type UploadService struct {
|
|
client *pan123.Client
|
|
parentFileID string
|
|
customDomain string
|
|
}
|
|
|
|
func NewUploadService(client *pan123.Client, parentFileID, customDomain string) *UploadService {
|
|
return &UploadService{
|
|
client: client,
|
|
parentFileID: parentFileID,
|
|
customDomain: strings.TrimRight(customDomain, "/"),
|
|
}
|
|
}
|
|
|
|
func (s *UploadService) UploadFile(fileHeader *multipart.FileHeader) (pan123.FileItem, error) {
|
|
file, err := fileHeader.Open()
|
|
if err != nil {
|
|
return pan123.FileItem{}, fmt.Errorf("打开分段文件失败: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
h := md5.New()
|
|
if _, err := io.Copy(h, file); err != nil {
|
|
return pan123.FileItem{}, fmt.Errorf("计算文件 MD5 失败: %w", err)
|
|
}
|
|
etag := fmt.Sprintf("%x", h.Sum(nil))
|
|
size := fileHeader.Size
|
|
|
|
file.Seek(0, io.SeekStart)
|
|
fileName := fileHeader.Filename
|
|
|
|
var fileID string
|
|
|
|
createResp, err := s.client.CreateFile(s.parentFileID, fileName, etag, size)
|
|
if err != nil {
|
|
return pan123.FileItem{}, fmt.Errorf("123pan 创建文件失败: %w", err)
|
|
}
|
|
|
|
if createResp.Reuse {
|
|
fileID = createResp.FileID
|
|
log.Printf("[上传] 文件 %s 命中秒传", fileName)
|
|
} else {
|
|
preuploadID := createResp.PreuploadID
|
|
|
|
urlResp, err := s.client.GetUploadURL(preuploadID, 1)
|
|
if err != nil {
|
|
return pan123.FileItem{}, fmt.Errorf("123pan 获取上传地址失败: %w", err)
|
|
}
|
|
|
|
log.Printf("[上传] 推送 %s 到 123pan (%d bytes)...", fileName, size)
|
|
if err = s.client.DoRawPUT(urlResp.PresignedURL, file, size); err != nil {
|
|
return pan123.FileItem{}, fmt.Errorf("123pan 数据上传失败: %w", err)
|
|
}
|
|
|
|
completeResp, err := s.client.UploadComplete(preuploadID)
|
|
if err != nil {
|
|
return pan123.FileItem{}, fmt.Errorf("123pan 上传完结通知失败: %w", err)
|
|
}
|
|
|
|
if completeResp.Completed {
|
|
fileID = completeResp.FileID
|
|
} else if completeResp.Async {
|
|
log.Printf("[上传] 123pan 异步处理 %s...", fileName)
|
|
for i := 0; i < 20; i++ {
|
|
time.Sleep(1 * time.Second)
|
|
asyncResp, err := s.client.CheckAsyncResult(preuploadID)
|
|
if err != nil {
|
|
log.Printf("[上传] 轮询出错 (继续重试): %v", err)
|
|
continue
|
|
}
|
|
if asyncResp.Completed {
|
|
fileID = asyncResp.FileID
|
|
break
|
|
}
|
|
}
|
|
if fileID == "" {
|
|
return pan123.FileItem{}, fmt.Errorf("123pan 异步处理超时")
|
|
}
|
|
} else {
|
|
return pan123.FileItem{}, fmt.Errorf("123pan 返回未知完结状态: %v", completeResp)
|
|
}
|
|
}
|
|
|
|
log.Printf("[上传] %s 完成, ID: %s", fileName, fileID)
|
|
|
|
downloadURL := ""
|
|
if s.customDomain != "" {
|
|
downloadURL = fmt.Sprintf("%s/%s", s.customDomain, fileName)
|
|
}
|
|
|
|
return pan123.FileItem{
|
|
FileID: fileID,
|
|
Filename: fileName,
|
|
Size: size,
|
|
Etag: etag,
|
|
DownloadURL: downloadURL,
|
|
UserSelfURL: downloadURL,
|
|
}, nil
|
|
}
|