forked from all_user/stat_center
338 lines
12 KiB
Go
338 lines
12 KiB
Go
package versionSer
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"math/rand"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"stat/common/crypt"
|
||
"stat/common/redis"
|
||
"stat/common/utils"
|
||
"stat/models/channelmod"
|
||
"stat/models/channelpackage"
|
||
"stat/models/versionMod"
|
||
"stat/statg"
|
||
"strings"
|
||
"time"
|
||
|
||
"go.mongodb.org/mongo-driver/bson"
|
||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||
"golang.org/x/exp/slices"
|
||
)
|
||
|
||
func GetChannelApkOutputPath(appId int32, channel string, version string, date time.Time) string {
|
||
endpoint := statg.GlobalAppIdEndpoints[appId]
|
||
if endpoint == nil {
|
||
return ""
|
||
}
|
||
if endpoint.NameEn == "" {
|
||
return ""
|
||
}
|
||
return fmt.Sprintf(`%s/package/%s_%s_%s-%s.apk`, endpoint.NameEn, endpoint.NameEn, date.Format("20060102150405"), version, channel)
|
||
}
|
||
|
||
func GetPackageBaseDomain(appId int32) []string {
|
||
packageUrlString := statg.Conf.URL.PackageUrl
|
||
if appId == 999 && len(statg.Conf.URL.BiliPackageUrl) > 0 {
|
||
packageUrlString = statg.Conf.URL.BiliPackageUrl
|
||
} else if appId == 777 && len(statg.Conf.URL.HuanqiuPackageUrl) > 0 {
|
||
packageUrlString = statg.Conf.URL.HuanqiuPackageUrl
|
||
} else if appId == 444 && len(statg.Conf.URL.ManhuaPackageUrl) > 0 {
|
||
packageUrlString = statg.Conf.URL.ManhuaPackageUrl
|
||
}
|
||
packageUrlArray := strings.Split(packageUrlString, ";")
|
||
return packageUrlArray
|
||
}
|
||
|
||
func ApkReportErrorLinks(ctx context.Context, pack *channelpackage.ChannelPackage, msg []string) {
|
||
if time.Since(pack.CheckLink) > channelpackage.ApkCheckLinkDuration {
|
||
filter := bson.M{
|
||
channelpackage.FN.ChannelPackage.Id: pack.Id,
|
||
channelpackage.FN.ChannelPackage.CheckLink: pack.CheckLink,
|
||
}
|
||
update := bson.M{
|
||
"$set": bson.M{
|
||
channelpackage.FN.ChannelPackage.CheckLink: time.Now(),
|
||
},
|
||
}
|
||
// 更新成功是生成apk的必要条件
|
||
if err := channelpackage.UpdateOneChannelPackage(ctx, filter, update); err != nil {
|
||
return
|
||
}
|
||
// 汇报TG
|
||
text := fmt.Sprintf("检测到渠道包链接无法下载:\n")
|
||
text += fmt.Sprintf("渠道:【%s】\n", pack.Channel)
|
||
text += fmt.Sprintf("应用:【%s,ID=%d】\n", statg.GlobalEndpoints.GetAppShortNameById(pack.AppId), pack.AppId)
|
||
text += fmt.Sprintf("版本:【%s】\n", pack.Version)
|
||
text += fmt.Sprintf("文件:【%s】\n", pack.FileName)
|
||
text += fmt.Sprintf("生成:【%s】\n", pack.GenTime.Format(time.DateTime))
|
||
text += fmt.Sprintf("错误: %s\n", msg)
|
||
text += fmt.Sprintf("提示: /channel_apk -channel=%s -app=%d", pack.Channel, pack.AppId)
|
||
statg.Bot.SendChannelApkMessage(text)
|
||
}
|
||
}
|
||
|
||
func ApkCheckLinks(pack *channelpackage.ChannelPackage, domains []string) (bool, []string) {
|
||
ctx := context.Background()
|
||
links := pack.GetAllLink(domains)
|
||
if len(links) <= 0 {
|
||
return false, links
|
||
}
|
||
var msg string
|
||
for i := 0; i < len(links); i++ {
|
||
link := links[i]
|
||
err := CheckPackageLink(ctx, link)
|
||
if err != nil {
|
||
msg += fmt.Sprintf("【链接:%s】错误:%s\n", link, err.Error())
|
||
}
|
||
}
|
||
return false, links
|
||
}
|
||
|
||
func TryMakeChannelApk(ctx context.Context, appId int32, channel string, version *versionMod.VersionModel, pack *channelpackage.ChannelPackage, reason string, forceMake bool, logBody io.Writer) (*channelpackage.ChannelPackage, error) {
|
||
// 检查版本有没有原始链接
|
||
if len(version.Link) <= 0 {
|
||
return pack, fmt.Errorf("version %d link is empty", version.Id)
|
||
}
|
||
// 先检查渠道是否存在
|
||
remoteFile := GetChannelApkOutputPath(appId, channel, version.VersionNum, time.Now())
|
||
chanInfo, err := channelmod.FindOneChannelInfo(ctx, bson.M{
|
||
channelmod.FN.ChannelInfo.Channel: channel,
|
||
})
|
||
if err != nil {
|
||
return pack, fmt.Errorf("渠道包生成失败,查询渠道【%s】出错: %v", channel, err)
|
||
}
|
||
if !chanInfo.Status {
|
||
return pack, fmt.Errorf("渠道包生成失败,渠道【%s】已被禁用,请启用该渠道后再次尝试", channel)
|
||
}
|
||
if !slices.Contains(chanInfo.AppIds, appId) {
|
||
return pack, fmt.Errorf("渠道包生成失败,应用【%s, ID=%d】未在渠道【%s】代理应用的列表中【%v】", statg.GlobalEndpoints.GetAppShortNameById(appId), appId, channel, chanInfo.AppIds)
|
||
}
|
||
// 开始生成
|
||
lockCode := rand.Int63()
|
||
packID := primitive.NewObjectID()
|
||
now := time.Now()
|
||
if pack == nil {
|
||
pack = &channelpackage.ChannelPackage{
|
||
Id: packID,
|
||
AppId: appId,
|
||
Channel: channel,
|
||
Version: version.VersionNum,
|
||
FileName: version.Md5,
|
||
UrlPath: remoteFile,
|
||
Status: channelpackage.ChanApkStatusMaking,
|
||
Desc: version.Info,
|
||
Reason: reason,
|
||
GenError: "第一次生成",
|
||
GenCount: 0,
|
||
LockCode: lockCode,
|
||
LockTime: now,
|
||
GenTime: now,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
}
|
||
// 插入成功是生成apk的必要条件
|
||
serr := channelpackage.InsertOneChannelPackage(ctx, pack)
|
||
if serr != nil {
|
||
return pack, fmt.Errorf("渠道包生成失败,插入渠道包记录失败: \n渠道:【%s】\n应用:【%s,ID=%d】\n错误: %s", channel, statg.GlobalEndpoints.GetAppShortNameById(appId), appId, serr.Error())
|
||
}
|
||
} else {
|
||
packID = pack.Id
|
||
if ok, left, tip := pack.CanMake(forceMake); !ok {
|
||
return pack, fmt.Errorf("渠道包生成失败【%s】,请在过【%v】后,再次尝试生成渠道【%s】的渠道包", tip, left, channel)
|
||
}
|
||
filter := bson.M{
|
||
channelpackage.FN.ChannelPackage.Id: packID,
|
||
channelpackage.FN.ChannelPackage.LockCode: pack.LockCode,
|
||
}
|
||
update := bson.M{
|
||
"$set": bson.M{
|
||
channelpackage.FN.ChannelPackage.LockCode: lockCode,
|
||
channelpackage.FN.ChannelPackage.LockTime: time.Now(),
|
||
channelpackage.FN.ChannelPackage.GenTime: time.Now(),
|
||
},
|
||
}
|
||
// 更新成功是生成apk的必要条件
|
||
if serr := channelpackage.UpdateOneChannelPackage(ctx, filter, update); serr != nil {
|
||
return pack, fmt.Errorf("update failed, code:%d, msg:%s", serr.Code, serr.Msg)
|
||
}
|
||
}
|
||
filter := bson.M{
|
||
channelpackage.FN.ChannelPackage.Id: packID,
|
||
channelpackage.FN.ChannelPackage.LockCode: lockCode,
|
||
}
|
||
err = makeChannelApk(version.Link[0], channel, remoteFile, logBody)
|
||
if err != nil {
|
||
// write error
|
||
update := bson.M{
|
||
"$inc": bson.M{
|
||
channelpackage.FN.ChannelPackage.GenCount: 1,
|
||
},
|
||
"$set": bson.M{
|
||
channelpackage.FN.ChannelPackage.LockTime: time.Time{}, // 重置锁
|
||
channelpackage.FN.ChannelPackage.GenError: err.Error(),
|
||
channelpackage.FN.ChannelPackage.Status: channelpackage.ChanApkStatusFailure,
|
||
},
|
||
}
|
||
_ = channelpackage.UpdateOneChannelPackage(ctx, filter, update)
|
||
return pack, err
|
||
}
|
||
update := bson.M{
|
||
"$set": bson.M{
|
||
channelpackage.FN.ChannelPackage.GenError: "success",
|
||
channelpackage.FN.ChannelPackage.UrlPath: remoteFile,
|
||
channelpackage.FN.ChannelPackage.FileName: version.Md5,
|
||
channelpackage.FN.ChannelPackage.Version: version.VersionNum,
|
||
channelpackage.FN.ChannelPackage.GenTime: time.Now(),
|
||
channelpackage.FN.ChannelPackage.Reason: reason,
|
||
channelpackage.FN.ChannelPackage.Desc: version.Info,
|
||
channelpackage.FN.ChannelPackage.LockTime: time.Time{}, // 重置锁
|
||
channelpackage.FN.ChannelPackage.Status: channelpackage.ChanApkStatusSuccess,
|
||
},
|
||
}
|
||
if serr := channelpackage.UpdateOneChannelPackage(ctx, filter, update); serr != nil {
|
||
return pack, fmt.Errorf("update failed,, code:%d, msg:%s", serr.Code, serr.Msg)
|
||
}
|
||
|
||
//if forceMake {
|
||
// // 自动开启渠道自动打包
|
||
// update := bson.M{
|
||
// "$set": bson.M{channelmod.FN.ChannelInfo.AutoGenApk: true},
|
||
// }
|
||
// _ = channelmod.UpdateOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channel}, update)
|
||
//}
|
||
|
||
pack2, serr := channelpackage.FindOneChannelPackage(ctx, filter)
|
||
if serr != nil {
|
||
return pack, fmt.Errorf("find failed, code:%d, msg:%s", serr.Code, serr.Msg)
|
||
}
|
||
return pack2, nil
|
||
}
|
||
|
||
func makeChannelApk(downUrl string, channel string, remoteFile string, logBody io.Writer) error {
|
||
var out []byte
|
||
var err error
|
||
// 生成临时apk文件名
|
||
step := 1
|
||
tempPath := os.TempDir()
|
||
srcName := utils.RandStringBytes(12)
|
||
dstName := utils.RandStringBytes(12)
|
||
tmpSrcApk := filepath.Join(tempPath, srcName+".apk")
|
||
tmpDstPath := filepath.Join(tempPath, dstName)
|
||
if logBody != nil {
|
||
logBody.Write([]byte(fmt.Sprintf("tmpSrcApk=%s\n", tmpSrcApk)))
|
||
logBody.Write([]byte(fmt.Sprintf("tmpDstPath=%s\n", tmpDstPath)))
|
||
}
|
||
// 下载apk原始文件
|
||
step = 2
|
||
cmd := exec.Command("/usr/bin/curl",
|
||
"-o",
|
||
tmpSrcApk,
|
||
downUrl)
|
||
out, err = cmd.Output()
|
||
if err != nil {
|
||
return fmt.Errorf("%w, step=%d", err, step)
|
||
}
|
||
if logBody != nil {
|
||
logBody.Write([]byte(fmt.Sprintf("step=%d, cmd=%s, out=%s\n", step, cmd.String(), string(out))))
|
||
}
|
||
defer func() {
|
||
_ = os.Remove(tmpSrcApk)
|
||
}()
|
||
step = 3
|
||
// 生成渠道包
|
||
cmd = exec.Command("/usr/bin/java", // 测试服 /tools/jdk/bin/java 正式服 /usr/bin/java
|
||
"-jar",
|
||
"/usr/lib/jar/packer-ng-2.0.1.jar",
|
||
"generate",
|
||
fmt.Sprintf("--channels=%s", channel),
|
||
fmt.Sprintf("--output=%s", tmpDstPath),
|
||
tmpSrcApk)
|
||
out, err = cmd.Output()
|
||
if err != nil {
|
||
return fmt.Errorf("%w, step=%d", err, step)
|
||
}
|
||
if logBody != nil {
|
||
logBody.Write([]byte(fmt.Sprintf("step=%d, cmd=%s, out=%s\n", step, cmd.String(), string(out))))
|
||
}
|
||
step = 4
|
||
// 上传到下载服务器
|
||
fileName := filepath.Join(tmpDstPath, fmt.Sprintf("%s-%s.apk", srcName, channel))
|
||
defer func() {
|
||
_ = os.Remove(fileName)
|
||
}()
|
||
cmd = exec.Command("/usr/bin/curl",
|
||
"-T",
|
||
fileName,
|
||
"-u",
|
||
"213apk:5JLm6dKS42Nsxmif",
|
||
fmt.Sprintf("ftp://216.83.58.213:21/%s", remoteFile))
|
||
out, err = cmd.Output()
|
||
if err != nil {
|
||
return fmt.Errorf("%w, step=%d", err, step)
|
||
}
|
||
if logBody != nil {
|
||
logBody.Write([]byte(fmt.Sprintf("step=%d, cmd=%s, out=%s\n", step, cmd.String(), string(out))))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func SendLinkErrorMessageToBot(text string) {
|
||
defer utils.RecoverError(nil)
|
||
key := crypt.StrToMd5(text)
|
||
hash := redis.NewHash(redis.StatRds.Client(), redis.GetDownloadCheckBotMsgHashKey())
|
||
_, _ = hash.Expire(time.Hour)
|
||
if exists, err := hash.Exists(key); err == nil && !exists {
|
||
_ = statg.Bot.SendMonitorMessage(text)
|
||
_, _ = hash.Set(key, text)
|
||
}
|
||
}
|
||
|
||
func CheckChannelApkAutoGen(ctx context.Context, appId int32, channel string, version *versionMod.VersionModel, pack *channelpackage.ChannelPackage) {
|
||
var err error
|
||
if apkGenLock.LockedCount() > channelpackage.ApkGenerateParallelMax {
|
||
return // 不能超出并行任务数
|
||
}
|
||
lockKey := fmt.Sprintf("id=%d#ch=%s", appId, channel)
|
||
expired := time.Now().Add(5 * time.Minute)
|
||
if locked := apkGenLock.TryLock(lockKey, expired); locked {
|
||
defer apkGenLock.UnLock(lockKey, expired)
|
||
bEnable := statg.Conf.ApkGenEnable
|
||
if !bEnable {
|
||
channelInfo, _ := channelmod.FindOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channel})
|
||
if channelInfo != nil && channelInfo.AutoGenApk {
|
||
bEnable = true
|
||
}
|
||
}
|
||
if bEnable {
|
||
canMake := pack == nil
|
||
if !canMake {
|
||
canMake, _, _ = pack.CanMake(false)
|
||
}
|
||
if canMake {
|
||
go func() {
|
||
defer utils.RecoverError(nil)
|
||
pack, err = TryMakeChannelApk(ctx, appId, channel, version, pack, "", false, nil)
|
||
if err == nil && pack != nil {
|
||
text := fmt.Sprintf("渠道包已全自动生成:\n")
|
||
text += fmt.Sprintf("渠道:【%s】\n", pack.Channel)
|
||
text += fmt.Sprintf("应用:【%s,ID=%d】\n", statg.GlobalEndpoints.GetAppShortNameById(pack.AppId), pack.AppId)
|
||
text += fmt.Sprintf("版本:【%s】\n", version.VersionNum)
|
||
text += fmt.Sprintf("文件:【%s】\n", version.Md5)
|
||
text += fmt.Sprintf("发布:【%s】\n", version.CreatedAt.Format(time.DateTime))
|
||
text += fmt.Sprintf("生成:【%s】\n", pack.GenTime.Format(time.DateTime))
|
||
baseUrls := GetPackageBaseDomain(pack.AppId)
|
||
for index, baseUrl := range baseUrls {
|
||
text += fmt.Sprintf("下载链接-%d: %s\n", index, pack.GetRandomLink([]string{baseUrl}))
|
||
}
|
||
statg.Bot.SendChannelApkMessage(text)
|
||
}
|
||
}()
|
||
}
|
||
}
|
||
}
|
||
}
|