Files
stat_center/service/versionSer/helper.go
T
2026-05-20 16:13:58 +08:00

338 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}()
}
}
}
}