Files
2026-10-26 16:28:14 +08:00

178 lines
5.1 KiB
Go

package srv_mediaresource
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"videoweb/pub/common/imgutil"
"videoweb/pub/common/log"
"videoweb/pub/common/netutil"
"videoweb/pub/models/cfg"
"videoweb/pub/models/playletmod"
"videoweb/pub/storage/proxy"
"videoweb/web/service/srv_search"
"go.mongodb.org/mongo-driver/bson"
)
// compressPlayletCoverOrEmpty 下载原封面并按 media Resize 规则处理。
// 原图未超过 60KB,或原图是 GIF 时,直接返回原封面地址;其它格式超过阈值则压缩为 JPEG 后上传。
// 失败返回空字符串,不阻断入库。
func compressPlayletCoverOrEmpty(cover string) string {
cover = strings.TrimSpace(cover)
if cover == "" {
return ""
}
pathStr, err := uploadCompressedPlayletCover(cover)
if err != nil {
log.Warn("压缩短剧封面失败", log.Any("cover", cover), log.E(err))
return ""
}
return pathStr
}
// FillPlayletCompressedCover 为指定短剧补写压缩封面,已有值则跳过。
func FillPlayletCompressedCover(ctx context.Context, playlet *playletmod.Playlet) bool {
if playlet == nil || playlet.Id <= 0 {
return false
}
if strings.TrimSpace(playlet.CompressedCover) != "" {
return false
}
cover := strings.TrimSpace(playlet.Cover)
if cover == "" {
return false
}
pathStr := compressPlayletCoverOrEmpty(cover)
if pathStr == "" {
return false
}
now := time.Now()
if err := playletmod.UpdateOnePlaylet(ctx,
bson.M{
playletmod.FN.Playlet.Id: playlet.Id,
"$or": []bson.M{
{playletmod.FN.Playlet.CompressedCover: bson.M{"$exists": false}},
{playletmod.FN.Playlet.CompressedCover: ""},
{playletmod.FN.Playlet.CompressedCover: nil},
},
},
bson.M{"$set": bson.M{
playletmod.FN.Playlet.CompressedCover: pathStr,
playletmod.FN.Playlet.UpdatedAt: now,
}},
); err != nil {
log.Warn("回写短剧压缩封面失败", log.Any("playletId", playlet.Id), log.E(err))
return false
}
playlet.CompressedCover = pathStr
latest, serr := playletmod.FindOnePlaylet(ctx, bson.M{playletmod.FN.Playlet.Id: playlet.Id})
if serr == nil && latest != nil && latest.Status == playletmod.StatusUp {
if syncErr := srv_search.SyncPlayletStatusToES(ctx, []int64{playlet.Id}, latest.Status); syncErr != nil {
log.Warn("压缩封面后同步ES失败", log.Any("playletId", playlet.Id), log.E(syncErr))
}
}
return true
}
func uploadCompressedPlayletCover(cover string) (string, error) {
downloadURL, err := resolvePlayletCoverURL(cover)
if err != nil {
return "", err
}
downloadURL, err = encodeCoverDownloadURL(downloadURL)
if err != nil {
return "", err
}
data, err := downloadPlayletCover(downloadURL)
if err != nil {
return "", err
}
data = imgutil.DecryptImage(data)
if !imgutil.NeedCompress(data) {
return cover, nil
}
fileName := imgutil.SafeCoverFileName(downloadURL)
compressed, outName, err := imgutil.CompressCover(data, fileName)
if err != nil {
return "", err
}
pathStr, err := proxy.UploadImg(outName, compressed, "")
if err != nil {
return "", fmt.Errorf("上传压缩封面失败: %w", err)
}
if strings.TrimSpace(pathStr) == "" {
return "", fmt.Errorf("上传压缩封面返回空路径")
}
return pathStr, nil
}
func resolvePlayletCoverURL(cover string) (string, error) {
if strings.HasPrefix(cover, "http://") || strings.HasPrefix(cover, "https://") {
return cover, nil
}
sysCfg, serr := cfg.FindOneSysCfg(context.Background(), bson.M{cfg.FN.SysCfg.Id: 1})
if serr != nil {
return "", fmt.Errorf("查询图片CDN失败: %s", serr.Error())
}
imageDomain := ""
if sysCfg != nil {
imageDomain = strings.TrimSpace(sysCfg.ImgWebCdn)
}
if imageDomain == "" {
return "", fmt.Errorf("图片CDN未配置,无法下载相对路径封面")
}
return netutil.MergeURL(imageDomain, cover), nil
}
// encodeCoverDownloadURL 把路径里的中文等非 ASCII 段转成百分号编码,避免下载失败或二次拼接异常。
func encodeCoverDownloadURL(raw string) (string, error) {
raw = strings.TrimSpace(raw)
u, err := url.Parse(raw)
if err != nil {
return "", fmt.Errorf("解析封面地址失败: %w", err)
}
if u.Scheme == "" || u.Host == "" {
return "", fmt.Errorf("封面地址无效")
}
escaped := u.EscapedPath()
if escaped != "" {
if dec, err := url.PathUnescape(escaped); err == nil {
u.Path = dec
u.RawPath = ""
}
}
return u.String(), nil
}
func downloadPlayletCover(rawURL string) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, rawURL, nil)
if err != nil {
return nil, fmt.Errorf("下载封面失败: %w", err)
}
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; xjsp-playlet-cover/1.0)")
req.Header.Set("Accept", "image/*")
cli := &http.Client{Timeout: 90 * time.Second}
resp, err := cli.Do(req)
if err != nil {
return nil, fmt.Errorf("下载封面失败: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("下载封面失败,状态码 %d", resp.StatusCode)
}
data, err := io.ReadAll(io.LimitReader(resp.Body, 20<<20))
if err != nil {
return nil, fmt.Errorf("下载封面失败: %w", err)
}
if len(data) == 0 {
return nil, fmt.Errorf("下载封面内容为空")
}
return data, nil
}