package oss import ( "context" "fmt" "io" "net/http" "strings" "time" "gitea.redpowerfuture.com/red-future/common/utils" "github.com/gogf/gf/v2/frame/g" ) // defaultDownloadTimeout 临时 URL 下载超时(默认 5m,与 model-gateway tempDownloadTimeout 一致)。 const defaultDownloadTimeout = 5 * time.Minute // TempURLToOSS 处理响应映射取值:模型返回的临时路径(http/https URL)会过期, // 需下载后转存 OSS,用 OSS 完整路径替换原值(从 model-gateway uploadTempURLToOSS 原样搬入)。 // - string 且以 http(s):// 开头 → 下载 → 转存 OSS → 返回 OSS 完整路径 // - []any → 逐元素处理,任一元素被替换则返回新切片 // - 其余类型 / 下载或上传失败 → 原样返回(失败仅记日志,不阻断任务) func TempURLToOSS(ctx context.Context, value any, opts ...Option) any { cfg := buildConfig(opts) do := func(rawURL string) (string, bool) { return uploadSingleURL(ctx, cfg, rawURL) } switch v := value.(type) { case string: if s, ok := do(v); ok { return s } case []any: out := make([]any, len(v)) changed := false for i, e := range v { if s, isStr := e.(string); isStr { if ns, ok := do(s); ok { out[i] = ns changed = true continue } } out[i] = e } if changed { return out } } return value } // uploadSingleURL 下载单个临时 URL 并转存 OSS;返回 OSS 完整路径 + 是否成功替换。 func uploadSingleURL(ctx context.Context, cfg *uploadConfig, rawURL string) (string, bool) { rawURL = strings.TrimSpace(rawURL) if !isHTTPURL(rawURL) { return rawURL, false } data, err := downloadTempURL(ctx, defaultDownloadTimeout, rawURL) if err != nil { g.Log().Errorf(ctx, "临时路径下载失败: url=%s err=%v", rawURL, err) return rawURL, false } ossRes, err := uploadFileBytesWithCfg(ctx, cfg, fmt.Sprintf("modelFile:%v%s", time.Now().UnixMilli(), extOfData(data)), data) if err != nil { g.Log().Errorf(ctx, "临时路径转存OSS失败: url=%s err=%v", rawURL, err) return rawURL, false } return ossRes.FileURL, true } func isHTTPURL(s string) bool { return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") } // extOfData 按下载内容嗅探文件后缀(不依赖 URL 路径,模型返回的临时路径可能无后缀)。 func extOfData(data []byte) string { _, ext := utils.DetectFileType(data) if ext == "" || ext == ".octet-stream" { return ".bin" } return ext } // downloadTempURL 带超时下载 URL 内容。 func downloadTempURL(ctx context.Context, timeout time.Duration, rawURL string) ([]byte, error) { if timeout <= 0 { timeout = defaultDownloadTimeout } client := &http.Client{Timeout: timeout} req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) if err != nil { return nil, err } resp, err := client.Do(req) if err != nil { return nil, err } defer func() { _ = resp.Body.Close() }() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("HTTP状态码异常: %d", resp.StatusCode) } return io.ReadAll(resp.Body) }