395 lines
12 KiB
Go
395 lines
12 KiB
Go
package versionSer
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"stat/api/proto"
|
|
"stat/common/stdLog"
|
|
"stat/common/utils"
|
|
"stat/models/channelpackage"
|
|
"stat/models/downloadCpc"
|
|
downloadinfo "stat/models/downloadinfo"
|
|
"stat/models/versionMod"
|
|
"stat/service/cacheSer"
|
|
"stat/service/crossSer"
|
|
"stat/statg"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
)
|
|
|
|
type ChannelParams struct {
|
|
PC string `json:"i" bson:"i"` //推广码
|
|
DC string `json:"dc" bson:"dc"` //商区序列
|
|
CH string `json:"ch" bson:"ch"` //渠道
|
|
}
|
|
|
|
var (
|
|
appSvrUrlDict = make(map[int32]string)
|
|
appSvrIdxDict = make(map[int32]statg.ServerIndex)
|
|
once sync.Once
|
|
apkGenLock = utils.NewExpiredLock()
|
|
landPageVisit = utils.NewExpiredLock()
|
|
landPageClick = utils.NewExpiredLock()
|
|
landPageVisitIpTimeout = time.Hour // 每个IP,1个小时内无论访问多少次,都算一次
|
|
landPageClickIpTimeout = time.Hour // 每个IP,1个小时内无论访问多少次,都算一次
|
|
)
|
|
|
|
func Init() {
|
|
once.Do(func() {
|
|
for _, endpoint := range statg.GlobalEndpoints {
|
|
appSvrIdxDict[endpoint.AppId] = endpoint.OwnerIndex
|
|
if url, ok := statg.Conf.InnerServers[endpoint.OwnerIndex]; ok {
|
|
appSvrUrlDict[endpoint.AppId] = url
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// CrossButtonClick 跨服务器点击下载返回最新的下载链接
|
|
func CrossButtonClick(ctx context.Context, userAgent string, peerIP string, req *proto.PageButtonClickReq) (resp *proto.PageButtonClickResp, err error) {
|
|
req.Ip = peerIP
|
|
srvIdx := appSvrIdxDict[req.AppId]
|
|
if srvIdx == statg.ServerIndexNone {
|
|
return nil, fmt.Errorf("appid %d not found", req.AppId)
|
|
}
|
|
if srvIdx == statg.Conf.ServerIndex {
|
|
return PageButtonClick(ctx, peerIP, userAgent, req)
|
|
}
|
|
addr := appSvrUrlDict[req.AppId]
|
|
if addr == "" {
|
|
return nil, fmt.Errorf("appid %d server not found", req.AppId)
|
|
}
|
|
res := crossSer.CrossAppDownloadRes{}
|
|
err = crossSer.InnerRequest(addr+"/api/stat/inner/appDownload", req, &res, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if res.Data == nil {
|
|
return nil, fmt.Errorf("data not found")
|
|
}
|
|
return res.Data, nil
|
|
}
|
|
|
|
// PageButtonClick 落地页点击下载返回最新的下载链接
|
|
func PageButtonClick(ctx context.Context, userAgent string, peerIP string, req *proto.PageButtonClickReq) (resp *proto.PageButtonClickResp, err error) {
|
|
resp = &proto.PageButtonClickResp{}
|
|
|
|
var channelCode ChannelParams
|
|
// TODO 存储记录
|
|
_ = json.Unmarshal([]byte(req.Channel), &channelCode)
|
|
|
|
if req.Ip == "" {
|
|
req.Ip = peerIP
|
|
}
|
|
channelCode.DC = utils.RepairDC(channelCode.DC)
|
|
|
|
if req.Terminal == "ios" && req.PackageType == versionMod.Light {
|
|
var h5Req = &proto.H5ConfigReq{
|
|
AppId: req.AppId,
|
|
DC: channelCode.DC,
|
|
PC: channelCode.PC,
|
|
Domain: channelCode.PC,
|
|
}
|
|
resp.Terminal = req.Terminal
|
|
resp.PackageType = req.PackageType
|
|
resp.Status = true
|
|
resp.LightData, _ = GetH5Config(ctx, h5Req, userAgent, req.Ip)
|
|
return
|
|
}
|
|
|
|
downloadInfo := &downloadinfo.DownloadInfoModel{
|
|
AppId: req.AppId,
|
|
IP: req.Ip,
|
|
Domain: req.Domain,
|
|
PC: channelCode.PC,
|
|
DC: channelCode.DC,
|
|
CH: channelCode.CH,
|
|
SysType: strings.ToLower(strings.TrimSpace(req.Terminal)),
|
|
PackageType: req.PackageType,
|
|
PeerIP: peerIP,
|
|
UserAgent: userAgent,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
//和APP统一
|
|
if req.PackageType == versionMod.Shop {
|
|
downloadInfo.SysType = "h5_ios"
|
|
}
|
|
|
|
if landPageClick.TryLock(req.Ip, time.Now().Add(landPageClickIpTimeout)) {
|
|
cacheSer.Writers.LandPageClickWriter.Add(downloadInfo)
|
|
}
|
|
if downloadInfo.IP == "" {
|
|
downloadInfo.IP = peerIP
|
|
}
|
|
|
|
var data *versionMod.VersionModel
|
|
cacheKey := fmt.Sprintf("version_button#appId:%d#%s#%d", req.AppId, req.Terminal, req.PackageType)
|
|
if cache, exists := cacheSer.Cache.Get(cacheKey); exists {
|
|
if item, ok := cache.(*versionMod.VersionModel); ok {
|
|
data = item
|
|
}
|
|
}
|
|
if data == nil {
|
|
data, err = versionMod.ButtonClick(ctx, req.AppId, req.Terminal, req.PackageType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cacheSer.Cache.Set(cacheKey, data, 2*time.Minute)
|
|
}
|
|
|
|
resp.PackageType = data.PackageType
|
|
resp.Status = data.Status
|
|
resp.Terminal = data.Terminal
|
|
resp.VersionNum = data.VersionNum
|
|
resp.BackUrl = data.BackLink
|
|
resp.Link = append(resp.Link, data.Link...)
|
|
|
|
// IOS的TF包管理
|
|
if req.PackageType == versionMod.Tf {
|
|
// 1. FT包管理
|
|
|
|
for _, tfInfo := range data.TfInfo {
|
|
one := proto.TfInfo{
|
|
AppName: tfInfo.AppName,
|
|
AppIcon: tfInfo.AppIcon,
|
|
URL: tfInfo.URL,
|
|
}
|
|
resp.Tf = append(resp.Tf, one)
|
|
}
|
|
|
|
if len(resp.Tf) == 0 {
|
|
resp.Tf = make([]proto.TfInfo, 0)
|
|
for _, v := range data.Link {
|
|
tmp := proto.TfInfo{
|
|
URL: v,
|
|
}
|
|
if len(data.BackLink) > 0 {
|
|
tmp.BackUrl = data.BackLink
|
|
}
|
|
resp.Tf = append(resp.Tf, tmp)
|
|
}
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
if data.Terminal == "ios" {
|
|
// 配置多个随机返回一个
|
|
if len(data.Link) >= 1 {
|
|
resp.Link = make([]string, 0)
|
|
index := rand.Intn(len(data.Link))
|
|
resp.Link = append(resp.Link, data.Link[index])
|
|
}
|
|
return
|
|
}
|
|
|
|
// 新增批量打包逻辑
|
|
if req.Terminal == "android" && len(data.Md5) > 0 {
|
|
randTime := int64(60*60) + int64(rand.Intn(60*60))
|
|
|
|
requestPackageTime := time.Now().Add(-time.Second * time.Duration(randTime))
|
|
// 下载链接
|
|
projectString := ""
|
|
beginPath := ""
|
|
pathList := strings.Split(strings.Replace(data.Md5, ".apk", "", -1), "_")
|
|
if len(pathList) == 3 {
|
|
beginPath = strings.TrimSpace(pathList[len(pathList)-1])
|
|
} else {
|
|
return
|
|
}
|
|
|
|
endpoint := statg.GlobalAppIdEndpoints[req.AppId]
|
|
if endpoint != nil {
|
|
projectString = fmt.Sprintf("%s/out/%s/%s%s_", endpoint.NameEn, beginPath, endpoint.NameEn, requestPackageTime.Format("20060102")[2:])
|
|
}
|
|
if projectString != "" {
|
|
packageUrlString := statg.Conf.URL.PackageUrl
|
|
if req.AppId == 999 && len(statg.Conf.URL.BiliPackageUrl) > 0 {
|
|
packageUrlString = statg.Conf.URL.BiliPackageUrl
|
|
} else if req.AppId == 777 && len(statg.Conf.URL.HuanqiuPackageUrl) > 0 {
|
|
packageUrlString = statg.Conf.URL.HuanqiuPackageUrl
|
|
} else if req.AppId == 444 && len(statg.Conf.URL.ManhuaPackageUrl) > 0 {
|
|
packageUrlString = statg.Conf.URL.ManhuaPackageUrl
|
|
}
|
|
|
|
packageUrlArray := strings.Split(packageUrlString, ";")
|
|
|
|
if len(packageUrlArray) > 0 {
|
|
if len(channelCode.DC) > 0 { //&&
|
|
pack, serr := channelpackage.FindOneChannelPackage(ctx, bson.M{
|
|
channelpackage.FN.ChannelPackage.AppId: req.AppId,
|
|
channelpackage.FN.ChannelPackage.Channel: channelCode.DC,
|
|
channelpackage.FN.ChannelPackage.Version: data.VersionNum,
|
|
channelpackage.FN.ChannelPackage.FileName: data.Md5,
|
|
})
|
|
if serr == nil || serr.Code == stdLog.DataNotExist {
|
|
if pack != nil && pack.Status == channelpackage.ChanApkStatusSuccess {
|
|
pass, links := ApkCheckLinks(pack, packageUrlArray)
|
|
if pass {
|
|
resp.Link = links
|
|
_ = pack.IncDownCount(ctx)
|
|
return resp, nil
|
|
} else {
|
|
ApkReportErrorLinks(ctx, pack, links)
|
|
}
|
|
} else {
|
|
CheckChannelApkAutoGen(ctx, req.AppId, channelCode.DC, data, pack)
|
|
}
|
|
}
|
|
}
|
|
// 默认使用第一个
|
|
passedLinks := make([]string, 0)
|
|
for _, packageLink := range packageUrlArray {
|
|
link := strings.TrimSpace(packageLink + projectString + data.Md5)
|
|
checklink := link
|
|
cerr := CheckPackageLink(ctx, checklink)
|
|
if cerr != nil {
|
|
// log.Error("AppVersion", log.Any("link", link), log.E(cerr))
|
|
// if statg.Conf.RunMod == consts.RunModRelease && !slices.Contains([]int32{666, 998}, req.AppId) {
|
|
// appDict, _ := appInfoMod.GetAllAppIdNameMap(ctx)
|
|
// text := fmt.Sprintf("【%s】Android 下载链接异常:\n链接:%s\n错误:%s\n", appDict[req.AppId], link, cerr.Error())
|
|
// SendLinkErrorMessageToBot(text)
|
|
// }
|
|
} else {
|
|
passedLinks = append(passedLinks, link)
|
|
}
|
|
}
|
|
if len(passedLinks) > 0 {
|
|
resp.Link = passedLinks
|
|
}
|
|
} else {
|
|
link := strings.TrimSpace(packageUrlString + projectString + data.Md5)
|
|
resp.Link = []string{link}
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// PageButtonList 落地页获取下载按钮
|
|
func PageButtonList(ctx context.Context, isIos bool, peerIP string, req *proto.PageButtonListReq) (resp *proto.PageButtonListResp, err error) {
|
|
resp = &proto.PageButtonListResp{
|
|
IosTF: false,
|
|
}
|
|
var channelCode ChannelParams
|
|
// TODO 存储记录
|
|
_ = json.Unmarshal([]byte(req.Channel), &channelCode)
|
|
|
|
channelCode.DC = utils.RepairDC(channelCode.DC)
|
|
|
|
req.Ip = peerIP
|
|
|
|
if req.Ip != "" {
|
|
logInfo := &downloadCpc.DownloadCpc{
|
|
AppId: req.AppId,
|
|
IP: req.Ip,
|
|
PC: channelCode.PC,
|
|
DC: channelCode.DC,
|
|
CH: channelCode.CH,
|
|
PeerIP: peerIP,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
if landPageVisit.TryLock(req.Ip, time.Now().Add(landPageVisitIpTimeout)) {
|
|
cacheSer.Writers.LandPageVisitWriter.Add(logInfo)
|
|
}
|
|
}
|
|
cacheKey := fmt.Sprintf("version_button_list#appId:%d", req.AppId)
|
|
var buttons versionMod.VersionList
|
|
if cache, exists := cacheSer.Cache.Get(cacheKey); exists {
|
|
if list, ok := cache.(versionMod.VersionList); ok {
|
|
buttons = list
|
|
}
|
|
}
|
|
if buttons == nil {
|
|
buttons, err = versionMod.ButtonList(ctx, req.AppId)
|
|
if err != nil {
|
|
err = errors.New("服务器内部错误")
|
|
return
|
|
}
|
|
cacheSer.Cache.Set(cacheKey, buttons, 0)
|
|
}
|
|
|
|
if len(buttons) < 1 {
|
|
err = errors.New("请开启版本")
|
|
return
|
|
}
|
|
|
|
for _, value := range buttons {
|
|
if isIos && value.Terminal == "android" {
|
|
// 如果是ios设备请求,记录是android 跳过
|
|
continue
|
|
}
|
|
if value.PackageType == versionMod.Enterprise && value.Terminal == "ios" {
|
|
resp.IosDown = true
|
|
}
|
|
if value.PackageType == versionMod.Shop && value.Terminal == "ios" {
|
|
resp.IosStore = true
|
|
if len(value.Link) > 0 {
|
|
resp.IosLink = value.Link[0]
|
|
}
|
|
}
|
|
if value.PackageType == versionMod.Tf && value.Terminal == "ios" {
|
|
resp.IosTF = true
|
|
}
|
|
if value.PackageType == versionMod.Light && value.Terminal == "ios" {
|
|
resp.IosH5 = true
|
|
}
|
|
}
|
|
// android下载按钮
|
|
packageUrlString := statg.Conf.URL.PackageUrl
|
|
if req.AppId == 999 && len(statg.Conf.URL.BiliPackageUrl) > 0 {
|
|
packageUrlString = statg.Conf.URL.BiliPackageUrl
|
|
} else if req.AppId == 777 && len(statg.Conf.URL.HuanqiuPackageUrl) > 0 {
|
|
packageUrlString = statg.Conf.URL.HuanqiuPackageUrl
|
|
} else if req.AppId == 444 && len(statg.Conf.URL.ManhuaPackageUrl) > 0 {
|
|
packageUrlString = statg.Conf.URL.ManhuaPackageUrl
|
|
}
|
|
packageUrlArray := strings.Split(packageUrlString, ";")
|
|
resp.AndroidButton = len(packageUrlArray)
|
|
// 是否有渠道包
|
|
var version *versionMod.VersionModel
|
|
versionKey := fmt.Sprintf("version_button#appId:%d#%s#%d", req.AppId, "android", 0)
|
|
if cache, exists := cacheSer.Cache.Get(versionKey); exists {
|
|
if item, ok := cache.(*versionMod.VersionModel); ok {
|
|
version = item
|
|
}
|
|
}
|
|
if version == nil {
|
|
version, err = versionMod.ButtonClick(ctx, req.AppId, "android", 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cacheSer.Cache.Set(cacheKey, version, 2*time.Minute)
|
|
}
|
|
|
|
pack, serr := channelpackage.FindOneChannelPackage(ctx, bson.M{
|
|
channelpackage.FN.ChannelPackage.AppId: req.AppId,
|
|
channelpackage.FN.ChannelPackage.Channel: channelCode.DC,
|
|
channelpackage.FN.ChannelPackage.Version: version.VersionNum,
|
|
channelpackage.FN.ChannelPackage.FileName: version.Md5,
|
|
})
|
|
if serr != nil && serr.Code != stdLog.DataNotExist {
|
|
return nil, errors.New(serr.Msg)
|
|
}
|
|
if pack != nil && pack.Status == channelpackage.ChanApkStatusSuccess {
|
|
resp.HasChannelApk = true
|
|
}
|
|
|
|
// ----------------End---------------
|
|
|
|
resp.Contact = proto.ContactInfo{
|
|
Telegram: "https://t.me/madoutiktok",
|
|
Paoto: "https://ptcc.in/madouyingshi",
|
|
Madou: "http://madou101.com",
|
|
}
|
|
|
|
return
|
|
}
|