Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
132 lines
3.5 KiB
Go
132 lines
3.5 KiB
Go
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
|
|
}
|