455 lines
13 KiB
Go
455 lines
13 KiB
Go
package configser
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"videoweb/app/api/proto"
|
|
"videoweb/app/service/translationser"
|
|
"videoweb/pub/common/log"
|
|
"videoweb/pub/common/netutil"
|
|
"videoweb/pub/common/srvdef"
|
|
"videoweb/pub/dataReport"
|
|
"videoweb/pub/models/cfg"
|
|
"videoweb/pub/models/hotnavmod"
|
|
"videoweb/pub/models/ipblackmod"
|
|
"videoweb/pub/models/richtextcontentmod"
|
|
"videoweb/pub/models/videocreatermod"
|
|
|
|
"github.com/robfig/cron/v3"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
)
|
|
|
|
var (
|
|
CacheMgr CacheInfo //
|
|
skdCron *cron.Cron
|
|
)
|
|
|
|
// richTextImgSrcRe 匹配富文本HTML里 img 标签的 src 图片地址,兼容单引号、双引号和JSON转义后的双引号
|
|
var richTextImgSrcRe = regexp.MustCompile(`(?i)(<img\b[^>]*?\s*)src\s*=\s*(\\?"([^"\\]*)\\?"|'([^']*)')`)
|
|
|
|
type CacheInfo struct {
|
|
Lock sync.RWMutex // 读写锁
|
|
Config proto.ConfigInfo // 系统配置缓存
|
|
Announcement proto.AnnouncementInfo
|
|
Advitise proto.AdvitiseData
|
|
IpBlackMaps map[string]struct{}
|
|
CreateInfo map[int64]*videocreatermod.VideoCreaterInfo // 作者信息
|
|
HotNavs []proto.HotNavItem // 热门导航
|
|
AppRichTexts []proto.AppRichTextItemResp // APP富文本内容列表
|
|
}
|
|
|
|
type appRichTextCacheItem struct {
|
|
Id int64 `bson:"_id,omitempty"`
|
|
Title string `bson:"title"`
|
|
Content string `bson:"content"`
|
|
Desc string `bson:"desc"`
|
|
}
|
|
|
|
func init() {
|
|
skdCron = cron.New(cron.WithSeconds()) // 秒级定时任务
|
|
CacheMgr.Config = proto.ConfigInfo{}
|
|
CacheMgr.CreateInfo = make(map[int64]*videocreatermod.VideoCreaterInfo)
|
|
CacheMgr.IpBlackMaps = make(map[string]struct{})
|
|
}
|
|
|
|
func Start() {
|
|
ctx := context.Background()
|
|
|
|
dataReport.StartSDKConfigCron()
|
|
UpdateConfigInfo(ctx)
|
|
//UpdateAdvitiseData(ctx)
|
|
UpdateAdvitiseDataFromJt()
|
|
UpdateAnnouncementData(ctx)
|
|
updateIpBlack()
|
|
updateHotNavCache(ctx)
|
|
updateAppRichTexts()
|
|
StartTopicCache()
|
|
go UpdateVideoRankCache()
|
|
go UpdatePlayletRankCache()
|
|
go UpdateAllFavoriteDataCount()
|
|
_, _ = skdCron.AddFunc("0 */5 * * * ?", func() {
|
|
UpdateConfigInfo(ctx)
|
|
//UpdateAdvitiseData(ctx)
|
|
UpdateAdvitiseDataFromJt()
|
|
UpdateAnnouncementData(ctx)
|
|
updateIpBlack()
|
|
updateHotNavCache(ctx)
|
|
updateAppRichTexts()
|
|
})
|
|
|
|
_, _ = skdCron.AddFunc("* */14 * * * ?", func() {
|
|
updateCreateCache(ctx)
|
|
})
|
|
// 每10分钟统计各标签关联数据量并刷新首页标签缓存
|
|
_, _ = skdCron.AddFunc("0 */10 * * * ?", func() {
|
|
UpdateAllTagDataCount()
|
|
})
|
|
// 每10分钟统计各内容收藏量(整点 10 分)
|
|
_, _ = skdCron.AddFunc("0 */10 * * * ?", func() {
|
|
UpdateAllFavoriteDataCount()
|
|
})
|
|
// 各内容榜单每 10 分钟刷新一次,错开整点避免任务扎堆
|
|
_, _ = skdCron.AddFunc("0 1/10 * * * ?", func() {
|
|
UpdateVideoRankCache()
|
|
})
|
|
_, _ = skdCron.AddFunc("0 5/10 * * * ?", func() {
|
|
UpdatePlayletRankCache()
|
|
})
|
|
skdCron.Start()
|
|
}
|
|
|
|
func UpdateConfigInfo(ctx context.Context) {
|
|
config := proto.ConfigInfo{}
|
|
|
|
filter := bson.M{cfg.FN.SysCfg.Id: 1}
|
|
sysCfg, _ := cfg.FindOneSysCfg(context.Background(), filter)
|
|
if sysCfg == nil {
|
|
return
|
|
}
|
|
|
|
config.NewUrlAddr = sysCfg.NewUrlAddr
|
|
config.NewUrlAddr1 = sysCfg.NewUrlAddr1
|
|
config.NewUrlAddr2 = sysCfg.NewUrlAddr2
|
|
config.ForeverUrlAddr = sysCfg.ForeverUrlAddr
|
|
config.GmailAddr = sysCfg.GmailAddr
|
|
config.AppAddr = sysCfg.AppAddr
|
|
config.WeiChart = sysCfg.WeiChart
|
|
config.QQ = sysCfg.QQ
|
|
config.TgGroup = sysCfg.TgGroup
|
|
config.PotaoGroup = sysCfg.PotaoGroup
|
|
config.GitAddr = sysCfg.GitAddr
|
|
config.TwitterAddr = sysCfg.TwitterAddr
|
|
config.Telegram = sysCfg.Telegram
|
|
config.TelegramAdd = sysCfg.TelegramAdd
|
|
config.TranslationAddr = sysCfg.TranslationAddr
|
|
config.ImgCdn = sysCfg.ImgAppCdn
|
|
config.TsCdn = sysCfg.MediaAppCdn
|
|
config.ImgAppUsCdn = sysCfg.ImgAppUsCdn
|
|
config.MediaAppUsCdn = sysCfg.MediaAppUsCdn
|
|
config.PdfAddr = sysCfg.PdfAddr
|
|
|
|
config.WebTitle = sysCfg.WebTitle
|
|
config.WebKeyworld = sysCfg.WebKeyworld
|
|
config.WebDescription = sysCfg.WebDescription
|
|
config.WebSiteName = sysCfg.WebSiteName
|
|
config.WebAddr = sysCfg.WebAddr
|
|
config.DHNewUrlAddr1 = sysCfg.DHNewUrlAddr1
|
|
config.DHNewUrlAddr2 = sysCfg.DHNewUrlAddr2
|
|
config.DHNewUrlAddr3 = sysCfg.DHNewUrlAddr3
|
|
config.UseIngAddress = sysCfg.UseAddress
|
|
config.FreeVPN = sysCfg.FreeVPN
|
|
config.DHKeyworld = sysCfg.DHKeyworld
|
|
config.DHTitle = sysCfg.DHTitle
|
|
config.DHDescription = sysCfg.DHDescription
|
|
config.DHSiteName = sysCfg.DHSiteName
|
|
config.ReportSDKConfig = dataReport.GetSDKDomainConfig()
|
|
config.ChannelCode = sysCfg.ChannelCode
|
|
config.EnableH265 = sysCfg.EnableH265
|
|
cfg.CacheAppKey(sysCfg.AppKey)
|
|
config.M3U8DayRateLimitMax = sysCfg.M3U8DayRateLimitMax
|
|
|
|
CacheMgr.Lock.Lock()
|
|
defer CacheMgr.Lock.Unlock()
|
|
CacheMgr.Config = config
|
|
|
|
}
|
|
|
|
func GetConfigInfo() proto.ConfigInfo {
|
|
CacheMgr.Lock.RLock()
|
|
defer CacheMgr.Lock.RUnlock()
|
|
|
|
return CacheMgr.Config
|
|
}
|
|
|
|
func GetAdvitise() proto.AdvitiseData {
|
|
CacheMgr.Lock.RLock()
|
|
defer CacheMgr.Lock.RUnlock()
|
|
|
|
return CacheMgr.Advitise
|
|
}
|
|
|
|
func GetAnnouncement() proto.AnnouncementInfo {
|
|
CacheMgr.Lock.RLock()
|
|
defer CacheMgr.Lock.RUnlock()
|
|
|
|
return CacheMgr.Announcement
|
|
}
|
|
|
|
func GetRandCDN() string {
|
|
config := GetConfigInfo()
|
|
return config.TsCdn
|
|
}
|
|
|
|
func updateHotNavCache(ctx context.Context) {
|
|
filter := bson.M{hotnavmod.FN.HotNav.Status: true}
|
|
opts := options.Find()
|
|
opts.SetSort(bson.D{
|
|
{Key: hotnavmod.FN.HotNav.Sort, Value: -1},
|
|
{Key: hotnavmod.FN.HotNav.CreatedAt, Value: -1},
|
|
})
|
|
opts.SetLimit(100)
|
|
list, err := hotnavmod.FindManyHotNav(ctx, filter, opts)
|
|
if err != nil {
|
|
log.Warn("updateHotNavCache.FindManyHotNav", log.E(err))
|
|
return
|
|
}
|
|
config := GetConfigInfo()
|
|
tempList := make([]proto.HotNavItem, len(list))
|
|
for i, item := range list {
|
|
tempList[i] = proto.HotNavItem{
|
|
Id: item.Id,
|
|
Title: item.Title,
|
|
Intro: item.Intro,
|
|
Url: item.Url,
|
|
Icon: netutil.MergeURL(config.ImgCdn, item.Icon),
|
|
}
|
|
}
|
|
CacheMgr.Lock.Lock()
|
|
defer CacheMgr.Lock.Unlock()
|
|
CacheMgr.HotNavs = tempList
|
|
}
|
|
|
|
func GetHotNavs() []proto.HotNavItem {
|
|
CacheMgr.Lock.RLock()
|
|
defer CacheMgr.Lock.RUnlock()
|
|
result := make([]proto.HotNavItem, len(CacheMgr.HotNavs))
|
|
copy(result, CacheMgr.HotNavs)
|
|
return result
|
|
}
|
|
|
|
// GetFooterInfo 查询APP底部信息
|
|
func GetFooterInfo() proto.FooterInfo {
|
|
config := GetConfigInfo()
|
|
return proto.FooterInfo{
|
|
Brand: buildFooterBrand(config),
|
|
NavList: buildFooterNavList(),
|
|
ContactList: buildFooterContactList(config),
|
|
}
|
|
}
|
|
|
|
// buildFooterBrand 构建APP底部品牌信息
|
|
func buildFooterBrand(config proto.ConfigInfo) proto.FooterBrand {
|
|
return proto.FooterBrand{
|
|
Title: config.WebSiteName,
|
|
Content: config.WebDescription,
|
|
}
|
|
}
|
|
|
|
// buildFooterNavList 构建APP底部导航列表
|
|
func buildFooterNavList() []proto.TranslationCategory {
|
|
bottom := translationser.GetTranslationBottomList()
|
|
return append([]proto.TranslationCategory{}, bottom.CategoryList...)
|
|
}
|
|
|
|
// buildFooterContactList 构建APP底部联系方式,固定返回原型要求的四项
|
|
func buildFooterContactList(config proto.ConfigInfo) []proto.FooterContact {
|
|
return []proto.FooterContact{
|
|
{Name: "E-mail", Value: config.GmailAddr},
|
|
{Name: "商务合作", Value: config.Telegram},
|
|
{Name: "官方群", Value: firstNotEmpty(config.TgGroup, config.TelegramAdd)},
|
|
{Name: "官方推特", Value: config.TwitterAddr},
|
|
}
|
|
}
|
|
|
|
// firstNotEmpty 返回第一个非空字符串
|
|
func firstNotEmpty(values ...string) string {
|
|
for _, value := range values {
|
|
value = strings.TrimSpace(value)
|
|
if value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetAppRichTextList 查询APP富文本内容列表
|
|
func GetAppRichTextList(rawIds string, rawTitles string) proto.AppRichTextListResp {
|
|
ids := parseInt64Set(rawIds)
|
|
titles := parseStringSet(rawTitles)
|
|
data := getCachedAppRichTexts()
|
|
if len(data) == 0 {
|
|
updateAppRichTexts()
|
|
data = getCachedAppRichTexts()
|
|
}
|
|
return proto.AppRichTextListResp{List: filterAppRichTexts(data, ids, titles)}
|
|
}
|
|
|
|
// getCachedAppRichTexts 读取APP富文本缓存副本
|
|
func getCachedAppRichTexts() []proto.AppRichTextItemResp {
|
|
CacheMgr.Lock.RLock()
|
|
defer CacheMgr.Lock.RUnlock()
|
|
return append([]proto.AppRichTextItemResp{}, CacheMgr.AppRichTexts...)
|
|
}
|
|
|
|
// filterAppRichTexts 按富文本ID或标题过滤缓存数据
|
|
func filterAppRichTexts(data []proto.AppRichTextItemResp, ids map[int64]struct{}, titles map[string]struct{}) []proto.AppRichTextItemResp {
|
|
list := make([]proto.AppRichTextItemResp, 0, len(data))
|
|
for _, item := range data {
|
|
if len(ids) > 0 {
|
|
if _, ok := ids[item.Id]; !ok {
|
|
continue
|
|
}
|
|
}
|
|
if len(titles) > 0 {
|
|
if _, ok := titles[item.Title]; !ok {
|
|
continue
|
|
}
|
|
}
|
|
list = append(list, item)
|
|
}
|
|
return list
|
|
}
|
|
|
|
// parseInt64Set 解析英文逗号分隔的ID集合
|
|
func parseInt64Set(raw string) map[int64]struct{} {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
resp := make(map[int64]struct{})
|
|
for _, item := range strings.Split(raw, ",") {
|
|
id, err := strconv.ParseInt(strings.TrimSpace(item), 10, 64)
|
|
if err != nil || id <= 0 {
|
|
continue
|
|
}
|
|
resp[id] = struct{}{}
|
|
}
|
|
return resp
|
|
}
|
|
|
|
// parseStringSet 解析英文逗号分隔的字符串集合
|
|
func parseStringSet(raw string) map[string]struct{} {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
resp := make(map[string]struct{})
|
|
for _, item := range strings.Split(raw, ",") {
|
|
item = strings.TrimSpace(item)
|
|
if item == "" {
|
|
continue
|
|
}
|
|
resp[item] = struct{}{}
|
|
}
|
|
return resp
|
|
}
|
|
|
|
// updateAppRichTexts 刷新APP富文本内容缓存
|
|
func updateAppRichTexts() {
|
|
resp, err := queryAppRichTexts(context.Background())
|
|
if err != nil {
|
|
log.Warn("查询APP富文本内容失败", log.E(err))
|
|
return
|
|
}
|
|
CacheMgr.Lock.Lock()
|
|
defer CacheMgr.Lock.Unlock()
|
|
CacheMgr.AppRichTexts = resp
|
|
}
|
|
|
|
// queryAppRichTexts 查询启用状态的APP富文本内容,统一供定时缓存和接口兜底加载使用
|
|
func queryAppRichTexts(ctx context.Context) ([]proto.AppRichTextItemResp, *srvdef.SrvErr) {
|
|
filter := bson.M{
|
|
richtextcontentmod.FN.RichTextContentModel.Status: true,
|
|
}
|
|
opts := options.Find().
|
|
SetProjection(bson.M{
|
|
richtextcontentmod.FN.RichTextContentModel.Id: 1,
|
|
richtextcontentmod.FN.RichTextContentModel.Title: 1,
|
|
richtextcontentmod.FN.RichTextContentModel.Content: 1,
|
|
richtextcontentmod.FN.RichTextContentModel.Desc: 1,
|
|
}).
|
|
SetSort(bson.D{
|
|
{Key: richtextcontentmod.FN.RichTextContentModel.Sort, Value: -1},
|
|
{Key: richtextcontentmod.FN.RichTextContentModel.UpdatedAt, Value: -1},
|
|
}).
|
|
SetLimit(100)
|
|
list := make([]appRichTextCacheItem, 0)
|
|
err := richtextcontentmod.FindManyRichTextContentModelModelWithBind(ctx, &list, filter, opts)
|
|
if err != nil {
|
|
log.Warn("查询APP富文本内容失败", log.Any("filter", filter), log.E(err))
|
|
return nil, err
|
|
}
|
|
imgCdn := GetConfigInfo().ImgCdn
|
|
resp := make([]proto.AppRichTextItemResp, 0, len(list))
|
|
for _, item := range list {
|
|
resp = append(resp, proto.AppRichTextItemResp{
|
|
Id: item.Id,
|
|
Title: item.Title,
|
|
Content: applyRichTextImgSrcCdn(imgCdn, item.Content),
|
|
Desc: item.Desc,
|
|
})
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// applyRichTextImgSrcCdn 给富文本 content 中 img 标签的 src 相对图片地址拼接图片CDN
|
|
func applyRichTextImgSrcCdn(imgCdn string, content string) string {
|
|
if imgCdn == "" || content == "" {
|
|
return content
|
|
}
|
|
return richTextImgSrcRe.ReplaceAllStringFunc(content, func(match string) string {
|
|
parts := richTextImgSrcRe.FindStringSubmatch(match)
|
|
if len(parts) < 5 {
|
|
return match
|
|
}
|
|
prefix := parts[1]
|
|
quote := `"`
|
|
src := parts[3]
|
|
if src == "" {
|
|
quote = `'`
|
|
src = parts[4]
|
|
} else if strings.HasPrefix(parts[2], `\"`) {
|
|
quote = `\"`
|
|
}
|
|
src = strings.TrimSpace(src)
|
|
if src == "" || isAbsoluteRichTextSrc(src) {
|
|
return match
|
|
}
|
|
return prefix + "src=" + quote + netutil.MergeURL(imgCdn, src) + quote
|
|
})
|
|
}
|
|
|
|
// isAbsoluteRichTextSrc 判断图片地址是否已经是完整地址或特殊协议,避免重复拼接CDN
|
|
func isAbsoluteRichTextSrc(src string) bool {
|
|
lowerSrc := strings.ToLower(src)
|
|
return strings.HasPrefix(lowerSrc, "http://") ||
|
|
strings.HasPrefix(lowerSrc, "https://") ||
|
|
strings.HasPrefix(lowerSrc, "data:") ||
|
|
strings.HasPrefix(lowerSrc, "blob:") ||
|
|
strings.HasPrefix(src, "//")
|
|
}
|
|
|
|
func updateIpBlack() {
|
|
ctx := context.Background()
|
|
list, err := ipblackmod.FindManyIPBlackModel(ctx, bson.M{})
|
|
if err != nil {
|
|
log.Error("updateIpBlack FindManyIPBlackModel error", log.E(err))
|
|
return
|
|
}
|
|
tempMaps := make(map[string]struct{})
|
|
for _, v := range list {
|
|
tempMaps[v.Ip] = struct{}{}
|
|
}
|
|
CacheMgr.Lock.Lock()
|
|
defer CacheMgr.Lock.Unlock()
|
|
CacheMgr.IpBlackMaps = tempMaps
|
|
}
|
|
|
|
func CheckIpBlack(ip string) bool {
|
|
CacheMgr.Lock.RLock()
|
|
defer CacheMgr.Lock.RUnlock()
|
|
if _, ok := CacheMgr.IpBlackMaps[ip]; ok {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func Stop() {
|
|
dataReport.StopSDKConfigCron()
|
|
skdCron.Stop()
|
|
}
|