来源:hgdjnet-server 9bfbafe10d417deea959bd53117bc88a10e60a83 项目身份 HKDJ / JHA-216,独立官网仓库,Jenkins 守卫键 hkdjnet 测试目录 /server/hkdj_net,App/Web API 30331/30332 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
229 lines
7.0 KiB
Go
229 lines
7.0 KiB
Go
package srv_mediaresource
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"hkdj-web-server/pub/common/imgutil"
|
|
"hkdj-web-server/pub/common/log"
|
|
"hkdj-web-server/pub/common/netutil"
|
|
"hkdj-web-server/pub/common/playletcache"
|
|
"hkdj-web-server/pub/models/cfg"
|
|
"hkdj-web-server/pub/models/playletmod"
|
|
"hkdj-web-server/pub/storage/proxy"
|
|
"hkdj-web-server/web/service/srv_search"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"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
|
|
playletcache.ClearHomeCache()
|
|
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
|
|
}
|
|
|
|
// FillPlayletCompressedHorizontalCover 为指定短剧补写压缩横版封面,已有值则跳过。
|
|
func FillPlayletCompressedHorizontalCover(ctx context.Context, playlet *playletmod.Playlet) bool {
|
|
if playlet == nil || playlet.Id <= 0 {
|
|
return false
|
|
}
|
|
if strings.TrimSpace(playlet.CompressedHorizontalCover) != "" {
|
|
return false
|
|
}
|
|
cover := strings.TrimSpace(playlet.HorizontalCover)
|
|
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.CompressedHorizontalCover: bson.M{"$exists": false}},
|
|
{playletmod.FN.Playlet.CompressedHorizontalCover: ""},
|
|
{playletmod.FN.Playlet.CompressedHorizontalCover: nil},
|
|
},
|
|
},
|
|
bson.M{"$set": bson.M{
|
|
playletmod.FN.Playlet.CompressedHorizontalCover: pathStr,
|
|
playletmod.FN.Playlet.UpdatedAt: now,
|
|
}},
|
|
); err != nil {
|
|
log.Warn("回写短剧压缩横版封面失败", log.Any("playletId", playlet.Id), log.E(err))
|
|
return false
|
|
}
|
|
playlet.CompressedHorizontalCover = pathStr
|
|
playletcache.ClearHomeCache()
|
|
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.UploadAutopublishImage(outName, compressed)
|
|
if err != nil {
|
|
log.Warn("media上传压缩封面失败,回退图床", log.Any("cover", cover), log.E(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; cgdj-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
|
|
}
|