perf(media): 生产媒体直传阿里云 OSS
生产同构验收确认 server_main_openapi 上传通道在媒体高并发下产生 381 个 502,并将已创建任务的 p95 拉高到 172 秒。\n\n为 request_asset 与 image_result 增加环境配置控制的阿里云 OSS 直传,保留普通上传原通道;使用 Kubernetes Secret 注入凭据,并对签名、重试、场景隔离和生产配置校验增加测试。\n\n验证:Go 全量测试、go vet、OpenAPI、迁移安全检查、Kustomize/Compose 渲染、gofmt 与 git diff --check 均通过;真实 OSS PUT/CDN 读取探针通过且对象已清理。
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/config"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
||||
)
|
||||
|
||||
func TestDirectOSSUploadSignsRequestAndReturnsPublicURL(t *testing.T) {
|
||||
payload := []byte("production-isomorphic-image")
|
||||
var calls atomic.Int64
|
||||
var uploadedPath string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
call := calls.Add(1)
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("method=%s", r.Method)
|
||||
}
|
||||
if r.Header.Get("Content-Type") != "image/png" || r.Header.Get("Date") == "" {
|
||||
t.Errorf("missing OSS upload headers")
|
||||
}
|
||||
stringToSign := "PUT\n\nimage/png\n" + r.Header.Get("Date") + "\n/media-bucket" + r.URL.EscapedPath()
|
||||
mac := hmac.New(sha1.New, []byte("access-secret"))
|
||||
_, _ = mac.Write([]byte(stringToSign))
|
||||
expectedAuthorization := "OSS access-key:" + base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
if r.Header.Get("Authorization") != expectedAuthorization {
|
||||
t.Errorf("invalid OSS request signature")
|
||||
}
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil || !bytes.Equal(body, payload) {
|
||||
t.Errorf("uploaded payload differs: err=%v", err)
|
||||
}
|
||||
uploadedPath = r.URL.EscapedPath()
|
||||
if call == 1 {
|
||||
http.Error(w, "retry", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
cfg := config.Config{
|
||||
MediaOSSDirectEnabled: true,
|
||||
MediaOSSEndpoint: server.URL,
|
||||
MediaOSSBucket: "media-bucket",
|
||||
MediaOSSAccessKeyID: "access-key",
|
||||
MediaOSSAccessKeySecret: "access-secret",
|
||||
MediaOSSPublicBaseURL: "https://cdn.example.com",
|
||||
MediaOSSObjectPrefix: "easyai-ai-gateway/production/media",
|
||||
}
|
||||
uploader := newDirectOSSUploader(cfg)
|
||||
uploader.now = func() time.Time {
|
||||
return time.Date(2026, time.July, 30, 12, 0, 0, 0, time.UTC)
|
||||
}
|
||||
service := &Service{cfg: cfg, directOSS: uploader}
|
||||
uploaded, err := service.UploadFile(context.Background(), FileUploadPayload{
|
||||
Bytes: payload,
|
||||
ContentType: "image/png",
|
||||
FileName: "input.png",
|
||||
Scene: store.FileStorageSceneRequestAsset,
|
||||
Source: "acceptance",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("direct OSS upload: %v", err)
|
||||
}
|
||||
if calls.Load() != 2 {
|
||||
t.Fatalf("upload calls=%d, want one retry", calls.Load())
|
||||
}
|
||||
if !strings.HasPrefix(uploadedPath, "/easyai-ai-gateway/production/media/request_asset/2026/07/30/input-") ||
|
||||
!strings.HasSuffix(uploadedPath, ".png") {
|
||||
t.Fatalf("unexpected object path=%q", uploadedPath)
|
||||
}
|
||||
if got := stringFromAny(uploaded["url"]); got != "https://cdn.example.com"+uploadedPath {
|
||||
t.Fatalf("public URL=%q", got)
|
||||
}
|
||||
channel, _ := uploaded["storageChannel"].(map[string]any)
|
||||
if stringFromAny(channel["provider"]) != "aliyun_oss_direct" {
|
||||
t.Fatalf("storage channel=%+v", channel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectOSSDoesNotReplaceGeneralUploadScene(t *testing.T) {
|
||||
var calls atomic.Int64
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
calls.Add(1)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
storageDir := t.TempDir()
|
||||
cfg := config.Config{
|
||||
LocalUploadedStorageDir: storageDir,
|
||||
MediaOSSDirectEnabled: true,
|
||||
MediaOSSEndpoint: server.URL,
|
||||
MediaOSSBucket: "media-bucket",
|
||||
MediaOSSAccessKeyID: "access-key",
|
||||
MediaOSSAccessKeySecret: "access-secret",
|
||||
MediaOSSPublicBaseURL: "https://cdn.example.com",
|
||||
MediaOSSObjectPrefix: "easyai-ai-gateway/production/media",
|
||||
}
|
||||
service := &Service{cfg: cfg, directOSS: newDirectOSSUploader(cfg)}
|
||||
uploaded, err := service.UploadFile(context.Background(), FileUploadPayload{
|
||||
Bytes: []byte("general-upload"),
|
||||
ContentType: "text/plain",
|
||||
FileName: "general.txt",
|
||||
Scene: store.FileStorageSceneUpload,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("general upload: %v", err)
|
||||
}
|
||||
if calls.Load() != 0 {
|
||||
t.Fatalf("direct OSS calls=%d for general upload scene", calls.Load())
|
||||
}
|
||||
channel, _ := uploaded["storageChannel"].(map[string]any)
|
||||
if stringFromAny(channel["provider"]) != "local_static" {
|
||||
t.Fatalf("general upload channel=%+v", channel)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user