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) } }() } } } }