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/common/playletcache" "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 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 }