Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
197 lines
5.2 KiB
Go
197 lines
5.2 KiB
Go
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.AList {
|
||
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.BList {
|
||
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()
|
||
}
|