package versionser import ( "91porn-server/app/appg" "91porn-server/common/httputil" "91porn-server/common/localcache" "91porn-server/common/log" "91porn-server/models/commod" "encoding/json" "fmt" "net/http" "time" "golang.org/x/sync/singleflight" ) const ( versionCacheTTL = time.Minute versionFailureCacheTTL = 10 * time.Second versionLastSuccessTTL = 24 * time.Hour ) var versionRequestGroup singleflight.Group type PacketType int32 const ( PacketTypeEnterprise PacketType = 1 // 企业签 PacketTypeShop PacketType = 2 // 商店包 PacketTypeTestFlight PacketType = 3 // TF包 ) type AppAllReq struct { AppId int32 `json:"appId" binding:"required"` // AppID SysType string `json:"sysType,omitempty"` // 终端类型 PktType PacketType `json:"pktType,omitempty"` // 包类型 CurVersion string `json:"curVersion,omitempty"` // 当前版本 CurPackageName string `json:"curPackageName,omitempty"` // 包名 } // 返回参数 type AppAllResp struct { VersionInfo VersionInfo `json:"versionInfo"` // 版本信息 AdvList []AdvertiseInfo `json:"advList"` // 广告信息 AnnouList []AnnouInfo `json:"annouList"` // 公告信息 IosUrl string `json:"iosLink"` // ios下载链接,网页版使用 AndroidUrl string `json:"andLink"` // 安卓下载链接,网页版使用 ShopIosLink string `json:"shopIosLink"` // ios商店包下载链接 } // 版本信息 type VersionInfo struct { HasNewVersion bool `json:"hasNewVersion,omitempty"` // 是否存在新版本 ServerVersion string `json:"serverVersion,omitempty"` // 服务器新版本 DownloadLink []string `json:"downloadLink,omitempty"` // 下载地址 Description string `json:"description,omitempty"` // 描述 IsForceUpdate bool `json:"isForceUpdate,omitempty"` // 是否强制升级 Size string `json:"size,omitempty"` // 大小 Md5 string `json:"md5,omitempty"` // md5 } // 广告信息 type AdvertiseInfo struct { Id int64 `json:"id"` // 广告id Title string `json:"title"` // 广告标题 CoverImg string `json:"coverImg"` // 封面 RandomImgs []string `json:"randomImgs"` // 随机封面 RandomNames []string `json:"randomNames"` // 随机名称 LocId int32 `json:"locId"` // 位置id JumpType int32 `json:"jumpType"` // 跳转方式 0:外部浏览器跳转 1:内部浏览器跳转 2:app内部跳转 Link string `json:"link"` // 链接地址 Sort int64 `json:"sort"` // 排序 Duration int32 `json:"duration"` // 广告持续时间(弃用) CoverImgSize string `json:"coverImgSize"` // 封面尺寸 WatchTime int `json:"watchTime"` // 视频广告时长 } // 公告信息 type AnnouInfo struct { //id ID string `json:"id"` //名字 Title string `json:"title"` //内容 Content string `json:"content"` //广告图 Cover string `json:"cover"` //链接地址 Href string `json:"href"` //类型 Type int64 `json:"type"` //时间 Time string `json:"time"` } // 广告、版本、公告,三合一接口 func AdvVersionAnnounThreeServer(curVersion, sysType string) (ver VersionInfo, adv []AdvertiseInfo, annou []AnnouInfo, iosUrl, androidUrl, shopIosLink string, err error) { //版本 key := "VerAnnInfo:" + sysType + ":" + curVersion if resp, ok := getVersionCache(key); ok { ver, annou, iosUrl, androidUrl, shopIosLink = unpackAppAllResp(resp) return } value, requestErr, _ := versionRequestGroup.Do(key, func() (interface{}, error) { // 同一版本的并发请求只允许一个访问产品中心;等待者进入后再检查一次缓存。 if cached, ok := getVersionCache(key); ok { return cached, nil } resp, fetchErr := fetchAppAllResp(curVersion, sysType) if fetchErr == nil { localcache.C.Set(key, resp, versionCacheTTL) localcache.C.Set(key+":lastSuccess", resp, versionLastSuccessTTL) return resp, nil } // 下游异常时短暂缓存最后一次成功结果(没有则为空结果),避免每个 Ping // 都同步等待同一个失败请求;10 秒后自动重试,恢复后能快速拿到新数据。 fallback, _ := getVersionCache(key + ":lastSuccess") localcache.C.Set(key, fallback, versionFailureCacheTTL) return fallback, fetchErr }) resp, ok := value.(AppAllResp) if !ok { return ver, adv, annou, iosUrl, androidUrl, shopIosLink, fmt.Errorf("invalid version cache type") } ver, annou, iosUrl, androidUrl, shopIosLink = unpackAppAllResp(resp) err = requestErr //log.Info("AdvVersionAnnounThreeServer response", log.Any("resp", resp)) return } func getVersionCache(key string) (AppAllResp, bool) { value, ok := localcache.C.Get(key) if !ok || value == nil { return AppAllResp{}, false } resp, ok := value.(AppAllResp) return resp, ok } func unpackAppAllResp(resp AppAllResp) (VersionInfo, []AnnouInfo, string, string, string) { return resp.VersionInfo, resp.AnnouList, resp.IosUrl, resp.AndroidUrl, resp.ShopIosLink } func fetchAppAllResp(curVersion, sysType string) (AppAllResp, error) { req := AppAllReq{ AppId: commod.KFK_APPID, PktType: PacketTypeEnterprise, SysType: sysType, CurVersion: curVersion, } bodyStr, err := json.Marshal(req) if err != nil { log.Info(fmt.Sprintf("AdvVersionAnnounThreeServer json.Marshal is fail error:%+v/data:%+v", err, req)) return AppAllResp{}, err } url := appg.Conf.URL.ProductUrl + "/api/stat/all/app" resp := AppAllResp{} code, err := httputil.DefaultClientPostJsonWithResp(&resp, url, nil, bodyStr) if err != nil { log.Error("AdvVersionAnnounThreeServer POSTWithJResp ", log.Any("url", url), log.E(err)) return AppAllResp{}, err } if code != http.StatusOK { log.Error("AdvVersionAnnounThreeServer response status ", log.Any("code", code)) return AppAllResp{}, fmt.Errorf("response status %d", code) } return resp, nil }