package appser import ( "bytes" "context" "encoding/json" "net/url" "strings" "sync" "videoweb/app/api/proto" "videoweb/app/service/configser" "videoweb/pub/common/http" "videoweb/pub/common/log" "videoweb/pub/common/netutil" "videoweb/pub/pubdef" "videoweb/pub/storage/proxy" "github.com/robfig/cron/v3" ) const ( appListAPIPath = "/media/web/appinfo/applist" ) var jsonHeader = map[string]string{"Content-Type": "application/json"} var ( appListCron *cron.Cron appListCache struct { lock sync.RWMutex appList []proto.AppCategoryInfo categoryMaps map[int64]proto.AppCategoryInfo } ) type remoteAppListResp struct { Code int `json:"code"` Data struct { List []proto.AppCategoryInfo `json:"list"` } `json:"data"` } // Start 启动定时刷新应用列表缓存(每10分钟一次) func Start() { appListCron = cron.New(cron.WithSeconds()) UpdateAppListCache(context.Background()) _, _ = appListCron.AddFunc("0 */10 * * * ?", func() { UpdateAppListCache(context.Background()) }) appListCron.Start() } // GetAppListCache 返回全部应用分类列表(带各分类下应用) func GetAppListCache() []proto.AppCategoryInfo { appListCache.lock.RLock() defer appListCache.lock.RUnlock() if len(appListCache.appList) == 0 { return make([]proto.AppCategoryInfo, 0) } result := make([]proto.AppCategoryInfo, len(appListCache.appList)) copy(result, appListCache.appList) return result } // GetCategoryCache 按分类ID返回单个分类(含应用列表);categoryId=0 返回第一个分类 func GetCategoryCache(categoryId int64) proto.AppCategoryInfo { appListCache.lock.RLock() defer appListCache.lock.RUnlock() if categoryId == 0 && len(appListCache.appList) > 0 { return appListCache.appList[0] } if v, ok := appListCache.categoryMaps[categoryId]; ok { return v } return proto.AppCategoryInfo{AppList: make([]proto.AppInfoData, 0)} } // UpdateAppListCache 从远端拉取应用列表并刷新内存缓存 func UpdateAppListCache(ctx context.Context) { domain := proxy.DomainAddressRepair("", proxy.BucketVideoAddressV2) apiURL := strings.TrimSuffix(domain, "/") + appListAPIPath reqBody, _ := json.Marshal(map[string]string{"dc": "dc"}) resp, err := http.Post(apiURL, jsonHeader, bytes.NewReader(reqBody)) if err != nil { log.Warn("UpdateAppListCache http.Post failed", log.Any("url", apiURL), log.E(err)) return } var remote remoteAppListResp if err = json.Unmarshal(resp.Data, &remote); err != nil { log.Warn("UpdateAppListCache unmarshal failed", log.Any("url", apiURL), log.E(err)) return } if remote.Code != 200 { log.Warn("UpdateAppListCache bad code", log.Any("url", apiURL), log.Any("code", remote.Code)) return } config := configser.GetConfigInfo() advitise := configser.GetAdvitise() channelCode := config.ChannelCode selfAppName := strings.TrimSpace(config.WebSiteName) categoryMaps := make(map[int64]proto.AppCategoryInfo) var appList []proto.AppCategoryInfo for _, category := range remote.Data.List { var apps []proto.AppInfoData for _, app := range category.AppList { if selfAppName != "" && strings.HasPrefix(app.Name, selfAppName) { continue } apps = append(apps, proto.AppInfoData{ Id: app.Id, CategoryId: app.CategoryId, Name: app.Name, Intro: app.Intro, Description: app.Description, Logo: netutil.MergeURL(config.ImgCdn, app.Logo), Url: appendChannelCode(app.Url, channelCode), Sort: app.Sort, }) } // 广告位 A(code=12) if category.Code == 12 { for _, ad := range advitise.AppInfoAList { apps = append(apps, proto.AppInfoData{ AdvitiseId: ad.Id, CategoryId: category.Id, Name: ad.Title, Logo: netutil.MergeURL(config.ImgCdn, ad.Images), Url: ad.Link, Location: ad.Location, }) } } // 广告位 B(code=13) if category.Code == 13 { for _, ad := range advitise.AppInfoBList { apps = append(apps, proto.AppInfoData{ AdvitiseId: ad.Id, CategoryId: category.Id, Name: ad.Title, Logo: netutil.MergeURL(config.ImgCdn, ad.Images), Url: ad.Link, Location: ad.Location, }) } } if len(apps) == 0 { continue } category.AppList = apps categoryMaps[category.Id] = proto.AppCategoryInfo{ Id: category.Id, Title: category.Title, Intro: category.Intro, Sort: category.Sort, Code: category.Code, } appList = append(appList, category) } appListCache.lock.Lock() appListCache.appList = appList appListCache.categoryMaps = categoryMaps appListCache.lock.Unlock() } // appendChannelCode 为应用 URL 拼接渠道码参数 dc=xxx;已含 dc 参数则不再拼接 func appendChannelCode(rawURL, channelCode string) string { if len(channelCode) == 0 { channelCode = pubdef.DefaultChannelCode } rawURL = strings.TrimSpace(rawURL) channelCode = strings.TrimSpace(channelCode) if rawURL == "" || channelCode == "" { return rawURL } parsed, err := url.Parse(rawURL) if err != nil { return rawURL } query := parsed.Query() if query.Has("dc") { return parsed.String() } query.Set("dc", channelCode) parsed.RawQuery = query.Encode() return parsed.String() }