#灵感/代码缓存 ## BuildApp.cs ``` cs using HTFW.BuildPackage; using UnityEditor; using System; using System.IO; using Cysharp.Threading.Tasks; using HTFW.Edit.BuildPackage; using xasset.editor; using HTFW.HotUpdate; using HTFW.Edit.HotUpdate; using Newtonsoft.Json; using System.Text; using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Net; using UnityEngine; public class BuildApp { #region 时间戳差值 /// /// 将秒数时间戳转换为多久之前。传入时间戳t(t= 当前时间戳() - 指定时间的时间戳 ) /// /// /// public static string GetTimeLongAgo(double t) { string str = ""; double num; if (t < 60) { str = string.Format("{0}秒", t); } else if (t >= 60 && t < 3600) { num = Math.Floor(t / 60); str = string.Format("{0}分钟", num); } else if (t >= 3600 && t < 86400) { num = Math.Floor(t / 3600); str = string.Format("{0}小时", num); } else if (t > 86400 && t < 2592000) { num = Math.Floor(t / 86400); str = string.Format("{0}天", num); } else if (t > 2592000 && t < 31104000) { num = Math.Floor(t / 2592000); str = string.Format("{0}月", num); } else { num = Math.Floor(t / 31104000); str = string.Format("{0}年", num); } return str; } #endregion #region 文件处理 public static void CopyDirectory(string sourceDir, string targetDir) { DirectoryInfo dir = new DirectoryInfo(sourceDir); DirectoryInfo[] dirs = dir.GetDirectories(); // If the source directory does not exist, throw an exception. if (!dir.Exists) { throw new DirectoryNotFoundException($"Source directory does not exist or could not be found: {sourceDir}"); } // If the destination directory does not exist, create it. if (!Directory.Exists(targetDir)) { Directory.CreateDirectory(targetDir); } // Get the files in the directory and copy them to the new location. FileInfo[] files = dir.GetFiles(); foreach (FileInfo file in files) { string tempPath = Path.Combine(targetDir, file.Name); file.CopyTo(tempPath, false); } // If copying subdirectories, copy them and their contents to the new location. foreach (DirectoryInfo subdir in dirs) { string tempPath = Path.Combine(targetDir, subdir.Name); CopyDirectory(subdir.FullName, tempPath); } } #endregion #region 网络处理 public static JObject PostJson(string url, Dictionary param) { string paramStr = JsonConvert.SerializeObject(param); byte[] data = Encoding.UTF8.GetBytes(paramStr); var request = (HttpWebRequest)WebRequest.Create(url); request.Method = "POST"; request.ContentType = "application/json"; Stream newStream = request.GetRequestStream(); newStream.Write(data, 0, data.Length); newStream.Close(); request.ServicePoint.Expect100Continue = false; request.ProtocolVersion = HttpVersion.Version11; var response = (HttpWebResponse)request.GetResponse(); var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); if (response.StatusCode == System.Net.HttpStatusCode.OK) { return JObject.Parse(responseString); } return null; } public static JObject GetJson(string url) { var request = (HttpWebRequest)WebRequest.Create(url); var response = (HttpWebResponse)request.GetResponse(); var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); if (response.StatusCode == System.Net.HttpStatusCode.OK) { return JObject.Parse(responseString); } return null; } /// /// 发送消息到飞书机器人 /// public static void PostLark() { // if (Directory.Exists(RemoteHotUpdateRootPath)) // { // Directory.Delete(RemoteHotUpdateRootPath, true); // } // CopyDirectory(SourceHotUpdateRootPath, RemoteHotUpdateRootPath); Dictionary dict = new Dictionary(); Dictionary content = new Dictionary(); string timeDesc = GetTimeLongAgo((DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000 - startTime); if (!isUpdate) { content.Add("text","本次打包完成,是整包\n" + $"本次打包时间:{timeDesc}\n" + $"对应的SVN分支是:{svn}\n" + $"安卓迭代的版本号是:{bundleVersionCode}\n" + $"本次打包对应分支:{Branch}\n" + $"本次打包版本描述:{Description}\n" + $"本次打包对应大版本:{Version}\n" + $"本次打包最小强更版本:{MinHotUpdateVersion}\n" + $"版本信息,是否上传到正式后台:{isUploadToReleaseServer}\n" + $"安装包下载路径:{HFSRemotePath}{PlayerPrefs.GetString(AppPackageNameKey)}"); } else { content.Add("text","本次打包完成,是热更包\n" + $"本次热更时间:{timeDesc}\n" + $"对应的SVN分支是:{svn}\n" + $"本次对应热更版本号:{HotUpdateVersion}\n" + $"安卓迭代的版本号是:{bundleVersionCode}\n" + $"本次打包对应分支:{Branch}\n" + $"本次打包版本描述:{Description}\n" + $"本次打包对应大版本:{Version}\n" + $"本次打包最小强更版本:{MinHotUpdateVersion}\n" + $"安装包下载路径:{HFSRemotePath}{PlayerPrefs.GetString(AppPackageNameKey)}"); } dict.Add("msg_type", "text"); dict.Add("content", content); string url = "https://open.larksuite.com/open-apis/bot/v2/hook/94281312-1071-4de8-a640-0878e8aab174"; PostJson(url, dict).ToString(); } #endregion private static string AppPackageNameKey = "AppPackageNameKey"; public static string svn = String.Empty; //svn分支地址 public static BuildPackageConfig.BuildTargetType buildTarget; //目标平台 public static int bundleVersionCode; //Android迭代版本号(每次打整包此处值++) public static BuildPackageConfig.AndroidBuildType androidBuildType; //打包类型 public static bool isUploadToReleaseServer; //是否版本信息上传到正式后台(false:内网,true:外网) public static string Branch = String.Empty; //版本分支,需要在 http://192.168.1.31:8888/platform_new/#/login?redirect=/index 确认,是否存在该分支 public static string Version = String.Empty; //打包 大版本 public static bool isUpdate = false; //是否热更 布尔 public static string _update = String.Empty; //是否热更 文本显示 public static string HotUpdateVersion = String.Empty; //热更版本号 public static string MinHotUpdateVersion = String.Empty; //最小热更版本 小于这个版本 会强制热更 public static string Description = String.Empty; //版本描述 public static string HFSRemotePath = String.Empty; //HFS IP服务器 public static string HFSLocalPath = String.Empty; public static BuildPackageConst.ResServerType assetsServerType; //上传资源服务器类型 public static string appName = String.Empty; //app名字 private static long startTime; // 开始打包时间(以秒为单位) #region 打整包 public static void BuildAPK() { startTime = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000; string[] args = Environment.GetCommandLineArgs(); foreach (var s in args) { if (s.Contains("--svn_")) { string _svn = s.Split('_')[1]; svn = _svn; } else if (s.Contains("--BuildTarget:")) { string _buildTarget = s.Split(':')[1]; if (_buildTarget == "Android") { buildTarget = BuildPackageConfig.BuildTargetType.Android; } else if (_buildTarget == "Windows") { buildTarget = BuildPackageConfig.BuildTargetType.Windows; } else if (_buildTarget == "Ios") { buildTarget = BuildPackageConfig.BuildTargetType.Ios; } } else if (s.Contains("--BundleVersionCode:")) { string _bundleVersionCode = s.Split(':')[1]; bundleVersionCode = int.Parse(_bundleVersionCode); } else if (s.Contains("--AndroidBuildType:")) { string _AndroidBuildType = s.Split(':')[1]; if (_AndroidBuildType == "开发包") { androidBuildType = BuildPackageConfig.AndroidBuildType.开发包; } else if (_AndroidBuildType == "谷歌包") { androidBuildType = BuildPackageConfig.AndroidBuildType.谷歌包; } } else if (s.Contains("--Branch:")) { string _Branch = s.Split(':')[1]; Branch = _Branch; } else if (s.Contains("--Version:")) { string _Version = s.Split(':')[1]; Version = _Version; } else if (s.Contains("--HotUpdate:")) { _update = s.Split(':')[1]; bool.TryParse(_update, out isUpdate); } else if (s.Contains("--IsUploadToReleaseServer:")) { string _IsUploadToReleaseServer = s.Split(':')[1]; bool.TryParse(_IsUploadToReleaseServer, out isUploadToReleaseServer); } else if (s.Contains("--HotUpdateVersion:")) { string _HotUpdateVersion = s.Split(':')[1]; HotUpdateVersion = _HotUpdateVersion; } else if (s.Contains("--MinHotUpdateVersion:")) { string _MinHotUpdateVersion = s.Split(':')[1]; MinHotUpdateVersion = _MinHotUpdateVersion; } else if (s.Contains("--Description:")) { string _Description = s.Split(':')[1]; Description = _Description; } else if (s.Contains("--HFSRemotePath_")) { string _HFSRemotePath = s.Split('_')[1]; HFSRemotePath = _HFSRemotePath; } else if (s.Contains("--HFSLocalPath.")) { string _HFSLocalPath = s.Split('.')[1]; HFSLocalPath = _HFSLocalPath; } else if (s.Contains("--AssetsServerType.")) { string _AssetsServerType = s.Split(':')[1]; if (_AssetsServerType == "测试资源服HFS") { assetsServerType = BuildPackageConst.ResServerType.测试资源服HFS; } else { assetsServerType = BuildPackageConst.ResServerType.正式资源服AmazonCDN; } } } BuildPackageConfig buildPackageConfig = AssetDatabase.LoadAssetAtPath("Assets/Scripts/BuildPackage/BuildPackageConfig.asset"); SetupGameMode(buildPackageConfig.CurrentBuildType == BuildPackageConfig.AndroidBuildType.开发包); HotUpdateUtil.ClearLastPackageVersionInfo(); buildPackageConfig.AssetsServerType = assetsServerType; if (buildPackageConfig.AssetsServerType == BuildPackageConst.ResServerType.测试资源服HFS) { buildPackageConfig.CDNCfg = BuildPackageEditorUtil.FindCDNConfig("HFSConfig"); buildPackageConfig.VersionInfo.RemoteAssetsHost = HFSRemotePath; buildPackageConfig.CDNCfg.RemoteAssetsHost = HFSRemotePath; } else { buildPackageConfig.CDNCfg = BuildPackageEditorUtil.FindCDNConfig("AmazonCDNConfig"); } buildPackageConfig.BuildTarget = buildTarget; buildPackageConfig.BundleVersionCode = bundleVersionCode; buildPackageConfig.CurrentBuildType = androidBuildType; buildPackageConfig.IsUploadToReleaseServer = isUploadToReleaseServer; buildPackageConfig.VersionInfo.Branch = Branch; buildPackageConfig.VersionInfo.Version = Version; buildPackageConfig.VersionInfo.HotUpdateVersion = HotUpdateVersion; buildPackageConfig.VersionInfo.MinHotUpdateVersion = MinHotUpdateVersion; buildPackageConfig.VersionInfo.Description = Description; bool hasExtension = buildPackageConfig.BuildTarget != BuildPackageConfig.BuildTargetType.Windows; if (!isUpdate) { appName = BuildPackageEditorUtil.GetPackageName(buildPackageConfig, hasExtension).Replace(" ",""); PlayerPrefs.SetString(AppPackageNameKey,appName); } EditorUtility.SetDirty(buildPackageConfig); AssetDatabase.SaveAssetIfDirty(buildPackageConfig); if (isUpdate) { BuildHotUpdate(); return; } //设置CustomSetting BuildPackageEditorUtil.SetupCustomSetting(buildPackageConfig); //设置ProjectSetting BuildPackageEditorUtil.SetupProjectSetting(buildPackageConfig); BuildPackageEditorUtil.UploadPackageVersionInfo(!buildPackageConfig.IsUploadToReleaseServer, buildPackageConfig.VersionInfo); var uploadCDN = CDNBase.GetCDN(buildPackageConfig.CDNCfg); //监听资源打包,资源有变更时,执行回调上传资源 Builder.PostprocessBuildBundles = changes => { //设置 DownloadURL,确保上传updateinfo.json前,下载地址已经设置正确 Builder.SetUpdateInfoDownloadURL(buildPackageConfig.VersionInfo.GetRemoteAssetsRootPath(true)); BuildHotUpdatePackageEditorUtil.UploadAA(true, changes, buildPackageConfig.VersionInfo, uploadCDN, isSuccess => { uploadCDN.Destroy(); }, false); }; //打整包AA资源 AssetDatabase.Refresh(); BuildPackageEditorUtil.BuildAA(buildPackageConfig.VersionInfo); AssetDatabase.Refresh(); //保存版本信息到本地 BuildPackageEditorUtil.SavePackageVersionInfo(buildPackageConfig.VersionInfo); AssetDatabase.Refresh(); if (buildPackageConfig.BuildTarget == BuildPackageConfig.BuildTargetType.Android) { EditorUserBuildSettings.androidCreateSymbolsZip = buildPackageConfig.CreateSymbolsZip; } else { EditorUserBuildSettings.androidCreateSymbolsZip = false; } //设置包体资源为完整资源 if (buildPackageConfig.CompleteResPkg) { BuildPackageEditorUtil.SetAssetsSplitModeToIncludeAll(); } //开始打整包 AssetDatabase.Refresh(); BuildPlayer(appName,buildPackageConfig.IsDevelopmentBuild); //打开打包文件夹 // EditorUtility.RevealInFinder(output); //还原游戏模式设置 SetupGameMode(true); //还原包体资源设置 if (buildPackageConfig.CompleteResPkg) { BuildPackageEditorUtil.ResetAssetsSplitMode(); } //刷新 AssetDatabase.Refresh(); PostLark(); } public static bool SetupGameMode(bool isDevelopMode) { string scriptPath = "Assets/Scripts/ConstDefine/GlobalConst.cs"; string developmentStr = "public static Eliminate.GlobalEnum.GameMode GameMode = Eliminate.GlobalEnum.GameMode.Development;"; string releaseStr = "public static Eliminate.GlobalEnum.GameMode GameMode = Eliminate.GlobalEnum.GameMode.Release;"; if (!File.Exists(scriptPath)) { return false; } string content = File.ReadAllText(scriptPath); if (isDevelopMode) { if (content.Contains(developmentStr)) return true; else if (content.Contains(releaseStr)) content = content.Replace(releaseStr, developmentStr); else { return false; } } else { if (content.Contains(developmentStr)) content = content.Replace(developmentStr, releaseStr); else if (content.Contains(releaseStr)) return true; else { return false; } } File.WriteAllText(scriptPath, content); return true; } /// /// 打包(运行文件) /// /// 打包配置文件 /// public static void BuildPlayer(string appName, bool isDevelopmentBuild) { string output = $"{HFSLocalPath}/{appName}"; // string output = BuildPackageEditorUtil.GetLocalPackageFullPath(cfg); if (File.Exists(output)) { File.Delete(output); } var buildOptions = isDevelopmentBuild ? BuildOptions.Development : BuildOptions.None; BuildPipeline.BuildPlayer(BuildPackageEditorUtil.FindEnableEditorScenes(), output, EditorUserBuildSettings.activeBuildTarget, buildOptions); } #endregion #region 打热更包 static bool isUploadAllResUpdate = true; static bool isFinishUploadRes = false; static bool isUploadResSuccess = false; public static void BuildHotUpdate() { string assetPath = "Assets/Scripts/HotUpdate/Edit/BuildHotUpdatePackageConfig.asset"; BuildHotUpdatePackageConfig buildHotUpdatePackageConfig = AssetDatabase.LoadAssetAtPath(assetPath); // Internal_BuildPackageHotUpdate(buildHotUpdatePackageConfig); buildHotUpdatePackageConfig.AssetsServerType = assetsServerType; if (buildHotUpdatePackageConfig.AssetsServerType == BuildPackageConst.ResServerType.测试资源服HFS) { buildHotUpdatePackageConfig.CDNCfg = BuildPackageEditorUtil.FindCDNConfig("HFSConfig"); buildHotUpdatePackageConfig.VersionInfo.RemoteAssetsHost = HFSRemotePath; buildHotUpdatePackageConfig.CDNCfg.RemoteAssetsHost = HFSRemotePath; } else { buildHotUpdatePackageConfig.CDNCfg = BuildPackageEditorUtil.FindCDNConfig("AmazonCDNConfig"); } //初始化VersionInfo buildHotUpdatePackageConfig.VersionInfo.InitCDNConfig(buildHotUpdatePackageConfig.CDNCfg.RemoteAssetsHost, buildHotUpdatePackageConfig.CDNCfg.RemoteHotUpdateRootPath); buildHotUpdatePackageConfig.IsUploadToReleaseServer = isUploadToReleaseServer; buildHotUpdatePackageConfig.VersionInfo.Branch = Branch; buildHotUpdatePackageConfig.VersionInfo.Version = Version; buildHotUpdatePackageConfig.VersionInfo.HotUpdateVersion = HotUpdateVersion; buildHotUpdatePackageConfig.VersionInfo.MinHotUpdateVersion = MinHotUpdateVersion; buildHotUpdatePackageConfig.VersionInfo.Description = Description; // buildHotUpdatePackageConfig.VersionInfo.HotUpdateVersion = (int.Parse(BuildPackageEditorUtil.LoadPackageVersionInfo().HotUpdateVersion) + 1).ToString(); //热更版本号 自增 EditorUtility.SetDirty(buildHotUpdatePackageConfig); AssetDatabase.SaveAssetIfDirty(buildHotUpdatePackageConfig); //保存版本信息到本地 BuildPackageEditorUtil.SavePackageVersionInfo(buildHotUpdatePackageConfig.VersionInfo); AssetDatabase.Refresh(); //-------------------- 版本信息检查 --------------------// if (buildHotUpdatePackageConfig.CDNCfg == null || !buildHotUpdatePackageConfig.CDNCfg.IsEnable || !buildHotUpdatePackageConfig.CDNCfg.IsValid()) { // Debug.LogError("需要有效 CDN 配置!"); return; } //是否使用正式后台 buildHotUpdatePackageConfig.VersionInfo.IsReleaseServer = buildHotUpdatePackageConfig.IsUploadToReleaseServer; //检测版本信息填写是否合法 // Debug.Log("开始检测版本信息填写是否合法..."); if (!CheckPackageVersionInfoValid(buildHotUpdatePackageConfig.VersionInfo)) { // Debug.LogError("版本信息填写不合法, 打包失败!"); return; } // Debug.Log("版本信息填写合法."); //-------------------- 打热更包 --------------------// // Debug.Log("开始检查打包资源前,本地资源状态跟远端是否一致..."); // if (!CheckLocalResStatusHotUpdate(buildHotUpdatePackageConfig)) // { // return; // } //-------------------- 监听资源打包--------------------// var uploadCDN = CDNBase.GetCDN(buildHotUpdatePackageConfig.CDNCfg); //监听资源打包,打包完成后执行后处理,上传热更AA资源到CDN Builder.PostprocessBuildBundles = changes => { //设置 DownloadURL,确保上传updateinfo.json前,下载地址已经设置正确 Builder.SetUpdateInfoDownloadURL(buildHotUpdatePackageConfig.VersionInfo.GetRemoteAssetsRootPath(true)); BuildHotUpdatePackageEditorUtil.UploadAA(true, changes, buildHotUpdatePackageConfig.VersionInfo, uploadCDN, isSuccess => { isFinishUploadRes = true; isUploadResSuccess = isSuccess; uploadCDN.Destroy(); }, false); }; //打热更AA资源 // Debug.Log("开始打热更AA资源..."); BuildHotUpdatePackageEditorUtil.BuildAA(buildHotUpdatePackageConfig.VersionInfo); // Debug.Log("热更AA资源打包完成!"); //等待资源上传逻辑完成 // while (!isFinishUploadRes) // { // UniTask.Delay(100); // 每隔一段时间进行检查,避免阻塞主线程 // } //由于内部使用了Unity的网络请求Api,所以必须回到unity主线程调用该异步函数 UploadPackageVersionInfoHotUpdate(isUploadResSuccess, buildHotUpdatePackageConfig.VersionInfo,buildHotUpdatePackageConfig).Forget(); } public static bool CheckPackageVersionInfoValid(PackageVersionInfo info) { if (info == null) { // Debug.LogError("版本信息不能为空!"); return false; } if (!info.CheckValid()) { // Debug.LogError("版本信息输入有误!"); return false; } return true; } /// /// 上传版本信息 /// /// /// static async UniTask UploadPackageVersionInfoHotUpdate(bool isUploadResSuccess, PackageVersionInfo info,BuildHotUpdatePackageConfig buildHotUpdatePackageConfig) { if (isUploadResSuccess) { //上传版本信息到后台 // Debug.Log("上传版本信息到后台..."); if (!await BuildPackageEditorUtil.UploadPackageVersionInfo(!buildHotUpdatePackageConfig.IsUploadToReleaseServer, info)){ } } else { // Debug.LogError($"上传资源失败!热更包打包失败."); } PostLark(); } #endregion } ```