package proxy import ( "bytes" "encoding/json" "fmt" "io" "mime" "mime/multipart" "net/http" "net/textproto" "path/filepath" "strconv" "strings" "time" "videoweb/pub/common/log" "videoweb/pub/pubdef" ) const mediaAutopublishImagePath = "/media/web/autopublish/image" type mediaImageUploadResp struct { Code int `json:"code"` Data struct { Id string `json:"id"` SourceNameFs string `json:"sourceNameFs"` } `json:"data"` Msg string `json:"msg"` } // UploadAutopublishImage 按 App /api/app/upload/image 同样方式上传到 media // POST {media}/media/web/autopublish/image,表单字段 file + appId,不加盖水印。 func UploadAutopublishImage(fileName string, data []byte) (string, error) { if len(data) == 0 { return "", fmt.Errorf("图片内容为空") } fileName = sanitizeAutopublishFileName(fileName) var buf bytes.Buffer mw := multipart.NewWriter(&buf) if err := mw.WriteField("appId", strconv.Itoa(pubdef.AppId)); err != nil { return "", err } if err := writeAutopublishImageFile(mw, fileName, data); err != nil { return "", err } if err := mw.Close(); err != nil { return "", err } base := strings.TrimRight(DomainAddressRepair("", BucketVideoAddressV2), "/") if base == "" || (!strings.HasPrefix(base, "http://") && !strings.HasPrefix(base, "https://")) { return "", fmt.Errorf("media地址未配置") } url := base + mediaAutopublishImagePath req, err := http.NewRequest(http.MethodPost, url, &buf) if err != nil { return "", err } req.Header.Set("Content-Type", mw.FormDataContentType()) cli := &http.Client{Timeout: 60 * time.Second} resp, err := cli.Do(req) if err != nil { log.Error("上传media图片失败", log.Any("url", url), log.E(err)) return "", err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", err } if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("media上传图片失败,状态码 %d,响应 %s", resp.StatusCode, trimRespBody(body)) } var result mediaImageUploadResp if err = json.Unmarshal(body, &result); err != nil { return "", fmt.Errorf("解析media上传图片响应失败: %w,响应 %s", err, trimRespBody(body)) } if result.Code != Success { msg := strings.TrimSpace(result.Msg) if msg == "" { msg = "unknown" } return "", fmt.Errorf("media上传图片失败: %s,响应 %s", msg, trimRespBody(body)) } pathStr := strings.TrimSpace(result.Data.SourceNameFs) if pathStr == "" { return "", fmt.Errorf("media上传图片返回空路径") } return pathStr, nil } func writeAutopublishImageFile(mw *multipart.Writer, fileName string, data []byte) error { h := make(textproto.MIMEHeader) contentType := mime.TypeByExtension(strings.ToLower(filepath.Ext(fileName))) if contentType == "" { contentType = "image/jpeg" } h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, fileName)) h.Set("Content-Type", contentType) part, err := mw.CreatePart(h) if err != nil { return err } _, err = part.Write(data) return err } func sanitizeAutopublishFileName(fileName string) string { fileName = filepath.Base(strings.TrimSpace(fileName)) fileName = strings.ReplaceAll(fileName, `"`, "") if fileName == "" || fileName == "." || !strings.Contains(fileName, ".") { return "playlet_cover.jpg" } for _, r := range fileName { if r > 127 || r < 32 || r == '/' || r == '\\' { return "playlet_cover.jpg" } } return fileName } func trimRespBody(body []byte) string { s := strings.TrimSpace(string(body)) if len(s) > 300 { return s[:300] } return s }