using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; using Kill.Network; using Kill.Managers; using Kill.Core; using Kill.UI.Components; using UnityEngine.UI; namespace Kill.Network { /// /// 请求头配置 /// [System.Serializable] public class HeaderConfig { public string key; public string value; } public class NetworkCtrl : MonoBehaviour { public static NetworkCtrl Instance; public string localPath; public string streamingPath; #region 网络配置区域 - 直接修改以下配置 public string[] serverBaseUrls; [Header("服务器配置")] [Tooltip("服务器基础URL,例如: https://api.example.com")] public string serverBaseUrl = "https://api.example.com"; [Header("请求配置")] [Tooltip("默认请求超时时间(秒)")] public int defaultTimeout = 30; [Tooltip("默认最大重试次数")] public int defaultMaxRetries = 3; [Tooltip("是否使用指数退避重试")] public bool useExponentialBackoff = true; [Header("全局请求头")] [Tooltip("添加全局请求头,如Authorization等")] public List globalHeaders = new List { // 示例: 在这里添加你的全局请求头 // new HeaderConfig { key = "Authorization", value = "Bearer your_token" }, // new HeaderConfig { key = "X-App-Version", value = "1.0.0" }, new HeaderConfig { key = "Content-Type", value = "application/json" }, }; [Header("队列配置")] [Tooltip("最大并发请求数")] public int maxConcurrentRequests = 5; #endregion public string BaseServerUrl { get; set; } = ""; void Awake() { Instance = this; localPath = Application.persistentDataPath; streamingPath = Application.streamingAssetsPath; // 设置目标帧率为 60 FPS Application.targetFrameRate = 120; // 禁用垂直同步,允许更高帧率 QualitySettings.vSyncCount = 0; #if UNITY_ANDROID AndroidStatusBar.statusBarState = AndroidStatusBar.States.TranslucentOverContent; #endif } async void Start() { #if UNITY_IOS platform = "IOS"; #else platform = "ANDROID"; #endif Debug.Log("Init"); await Init(); } public async Task Init() { Debug.Log("初始化网络控制"); // 初始化网络管理器 await InitializeNetworkManagers(); await NetworkStateManager.Instance.Init(); await GetNewBundle(); // 检查并清理过期资源 await CheckAndCleanOutdatedResources(); if (LoadRes.Instance.resPosition == LoadRes.ResPosition.资源包) { await GetNowBundleList(); if (nowResVersion < new Version(resourceVersion)) { LoadingUI.Show(); await GetBundleList(); LoadingUI.Hide(); await UpdateResources(); } } await LoadRes.Instance.Init(); await DataManager.Instance.Init(); await UI.UIManager.Instance.Init(); } /// /// 初始化网络管理器组件 /// private async Task InitializeNetworkManagers() { // 确保 NetworkStateManager 存在 if (NetworkStateManager.Instance == null) { var nsManagerObj = new GameObject("NetworkStateManager"); nsManagerObj.AddComponent(); } // 确保 HttpRequestManager 存在并配置 if (HttpRequestManager.Instance == null) { var httpManagerObj = new GameObject("HttpRequestManager"); httpManagerObj.AddComponent(); } // 配置 HttpRequestManager var httpManager = HttpRequestManager.Instance; if (httpManager != null) { // 设置基础URL if (!string.IsNullOrEmpty(serverBaseUrl)) { httpManager.SetBaseUrl(serverBaseUrl); BaseServerUrl = serverBaseUrl; } // 设置默认配置 var config = new HttpRequestConfig { Timeout = defaultTimeout, MaxRetries = defaultMaxRetries, UseExponentialBackoff = useExponentialBackoff }; httpManager.SetDefaultConfig(config); // 添加全局请求头 foreach (var header in globalHeaders) { if (!string.IsNullOrEmpty(header.key)) { httpManager.AddGlobalHeader(header.key, header.value); } } } // 确保 RequestQueueManager 存在并配置 if (RequestQueueManager.Instance == null) { var queueManagerObj = new GameObject("RequestQueueManager"); var queueManager = queueManagerObj.AddComponent(); queueManager.SetMaxConcurrentRequests(maxConcurrentRequests); } Debug.Log($"[NetworkCtrl] 网络管理器初始化完成 - 服务器: {serverBaseUrl}"); } #region 便捷的网络请求API /// /// 发送GET请求 /// public async Task> Get(string url, Dictionary queryParams = null, HttpRequestConfig config = null) { return await HttpRequestManager.Instance.Get(url, queryParams, config); } /// /// 发送GET请求(返回字符串) /// public async Task> Get(string url, Dictionary queryParams = null, HttpRequestConfig config = null) { return await HttpRequestManager.Instance.Get(url, queryParams, config); } /// /// 发送POST请求 /// public async Task> Post(string url, object data = null, HttpRequestConfig config = null) { return await HttpRequestManager.Instance.Post(url, data, config); } /// /// 发送POST请求(表单数据) /// public async Task> PostForm(string url, WWWForm formData, HttpRequestConfig config = null) { return await HttpRequestManager.Instance.PostForm(url, formData, config); } public async Task> PostFormData(string url, List formData, HttpRequestConfig config = null) { return await HttpRequestManager.Instance.PostFormData(url, formData, config); } /// /// 发送PUT请求 /// public async Task> Put(string url, object data = null, HttpRequestConfig config = null) { return await HttpRequestManager.Instance.Put(url, data, config); } /// /// 发送PUTform请求 /// public async Task> PutForm(string url, WWWForm formData, HttpRequestConfig config = null) { return await HttpRequestManager.Instance.PutForm(url, formData, config); } /// /// 发送DELETE请求 /// public async Task> Delete(string url, object data = null, HttpRequestConfig config = null) { if (data != null) { return await HttpRequestManager.Instance.Delete(url, data, config); } return await HttpRequestManager.Instance.Delete(url, config); } /// /// 下载文件 /// public async Task> DownloadFile(string url, string savePath, Action onProgress = null, HttpRequestConfig config = null) { return await HttpRequestManager.Instance.DownloadFile(url, savePath, onProgress, config); } /// /// 添加请求到队列 /// public string EnqueueRequest(Func>> requestFunc, RequestPriority priority = RequestPriority.Normal, Action> onComplete = null) { return RequestQueueManager.Instance.Enqueue(requestFunc, priority, onComplete); } /// /// 取消请求 /// public bool CancelRequest(string requestId) { return RequestQueueManager.Instance.CancelRequest(requestId); } /// /// 取消所有请求 /// public void CancelAllRequests() { RequestQueueManager.Instance.CancelAllRequests(); } /// /// 检查网络状态 /// public bool IsNetworkConnected { get { return NetworkStateManager.Instance != null && NetworkStateManager.Instance.IsConnected; } } /// /// 获取当前网络类型 /// public NetworkConnectivityState CurrentNetworkState { get { return NetworkStateManager.Instance != null ? NetworkStateManager.Instance.CurrentState : NetworkConnectivityState.Unknown; } } #endregion #region 全局配置 /// /// 设置服务器基础URL /// public void SetServerUrl(string baseUrl) { BaseServerUrl = baseUrl; if (HttpRequestManager.Instance != null) { HttpRequestManager.Instance.SetBaseUrl(baseUrl); } } /// /// 添加全局请求头 /// public void AddGlobalHeader(string key, string value) { HttpRequestManager.Instance?.AddGlobalHeader(key, value); } /// /// 移除全局请求头 /// public void RemoveGlobalHeader(string key) { HttpRequestManager.Instance?.RemoveGlobalHeader(key); } /// /// 设置默认请求超时时间 /// public void SetDefaultTimeout(int seconds) { var config = new HttpRequestConfig { Timeout = seconds }; HttpRequestManager.Instance?.SetDefaultConfig(config); } /// /// 设置默认最大重试次数 /// public void SetDefaultMaxRetries(int retries) { var config = new HttpRequestConfig { MaxRetries = retries }; HttpRequestManager.Instance?.SetDefaultConfig(config); } #endregion #region 资源更新 (保留原有功能) string nowResourceUrl; public string resourceVersion; string platform; Version nowResVersion = new Version("0.0.0"); public AssetBundleInfoList nowBundleList; public AssetBundleInfoList newBundleList; public async Task GetNowBundleList() { string localListPath = localPath + "/list.json"; string streamingListPath = streamingPath + "/list.json"; if (File.Exists(localListPath)) { nowBundleList = JsonUtility.FromJson(File.ReadAllText(localListPath)); Debug.Log("===获取本地资源列表成功"); } else { string listJson = await ReadTextFileFromStreamingAssets(streamingListPath); if (!string.IsNullOrEmpty(listJson)) { nowBundleList = JsonUtility.FromJson(listJson); Debug.Log("===获取更包资源列表成功"); } else { Debug.Log("===没有获取资源列表"); nowBundleList = null; } } if (nowBundleList != null) { nowResVersion = new Version(nowBundleList.version); } } /// /// 检查并清理过期的本地资源 /// public async Task CheckAndCleanOutdatedResources() { try { string streamingListPath = Path.Combine(streamingPath, "list.json"); string streamingListJson = await ReadTextFileFromStreamingAssets(streamingListPath); if (string.IsNullOrEmpty(streamingListJson)) { return; } AssetBundleInfoList streamingBundleList = JsonUtility.FromJson(streamingListJson); string localListPath = Path.Combine(localPath, "list.json"); if (!File.Exists(localListPath)) { return; } string localListJson = File.ReadAllText(localListPath); AssetBundleInfoList localBundleList = JsonUtility.FromJson(localListJson); if (new Version(streamingBundleList.version) >= new Version(localBundleList.version)) { Debug.Log($"跟包资源版本: {localBundleList.version} -> {streamingBundleList.version},正在清理本地过期资源..."); CleanLocalAssetBundleFiles(); } else { Debug.Log($"本地资源版本({localBundleList.version})更新,无需清理"); } } catch (Exception ex) { Debug.LogError($"检查资源版本时出错: {ex.Message}"); } } /// /// 清理本地的所有AssetBundle文件 /// private void CleanLocalAssetBundleFiles() { try { string[] localAbFiles = Directory.GetFiles(localPath, "*.ab", SearchOption.AllDirectories); foreach (string abFile in localAbFiles) { try { File.Delete(abFile); Debug.Log($"已删除过期资源文件: {abFile}"); } catch (Exception ex) { Debug.LogError($"删除文件失败 {abFile}: {ex.Message}"); } } File.Delete(localPath + "/list.json"); Debug.Log("本地过期资源清理完成"); } catch (Exception ex) { Debug.LogError($"清理本地资源时出错: {ex.Message}"); } } public async Task GetBundleList() { string url = nowResourceUrl + "/list.json"; using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) { var operation = webRequest.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); } if (webRequest.result == UnityWebRequest.Result.Success) { AssetBundleInfoList list = JsonUtility.FromJson(webRequest.downloadHandler.text); newBundleList = list; } else { Debug.LogError(url + "获取资源列表失败" + webRequest.error); } } } /// /// 从StreamingAssets目录读取文本文件(兼容各平台) /// private async Task ReadTextFileFromStreamingAssets(string filePath) { string result = null; #if UNITY_ANDROID && !UNITY_EDITOR UnityWebRequest www = UnityWebRequest.Get(filePath); var operation = www.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); } if (www.result == UnityWebRequest.Result.Success) { result = www.downloadHandler.text; } else { Debug.LogError($"读取StreamingAssets文件失败: {www.error}"); } www.Dispose(); #else if (File.Exists(filePath)) { result = File.ReadAllText(filePath); } #endif return result; } public async Task UpdateResources() { List updateList = GetUpdateList(); if (updateList.Count > 0) { UpdateLoadingUI.Instance.Show(); bool success = await DownloadUpdateFiles(updateList, (downloaded, total) => { float progress = (float)downloaded / total; UpdateLoadingUI.Instance.SetPrecent(progress); Debug.Log($"更新进度: {progress:P}"); }); UpdateLoadingUI.Instance.Hide(); if (success) { Debug.Log("资源更新完成"); } else { ToastUI.ShowText("resource error"); Debug.LogError("资源更新失败"); } } else { Debug.Log("资源已是最新版本"); } } /// /// 对比当前资源列表和新资源列表,找出需要更新的文件 /// public List GetUpdateList() { List updateList = new List(); if (nowBundleList == null || nowBundleList.assetbundles == null) { return newBundleList.assetbundles ?? new List(); } Dictionary nowBundleDict = new Dictionary(); foreach (var bundle in nowBundleList.assetbundles) { nowBundleDict[bundle.name] = bundle; } foreach (var newBundle in newBundleList.assetbundles) { if (!nowBundleDict.ContainsKey(newBundle.name) || nowBundleDict[newBundle.name].md5 != newBundle.md5) { updateList.Add(newBundle); } } return updateList; } /// /// 计算需要更新的总大小 /// public long CalculateUpdateSize(List updateList) { long totalSize = 0; foreach (var bundle in updateList) { totalSize += bundle.size; } return totalSize; } /// /// 下载更新文件并实时返回进度 /// public async Task DownloadUpdateFiles(List updateList, Action progressCallback) { long totalSize = CalculateUpdateSize(updateList); long downloadedSize = 0; if (nowBundleList == null || nowBundleList.assetbundles == null) { nowBundleList = new AssetBundleInfoList(new List()); } Dictionary nowBundleDict = new Dictionary(); foreach (var bundle in nowBundleList.assetbundles) { nowBundleDict[bundle.name] = bundle; } foreach (var bundle in updateList) { string url = nowResourceUrl + $"/{bundle.name}"; string savePath = Path.Combine(localPath, bundle.name); string directory = Path.GetDirectoryName(savePath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } bool downloadSuccess = await DownloadSingleFile(url, savePath, bundle.md5); if (!downloadSuccess) { Debug.LogError($"下载文件失败: {bundle.name}"); return false; } nowBundleDict[bundle.name] = bundle; downloadedSize += bundle.size; progressCallback?.Invoke(downloadedSize, totalSize); await Task.Yield(); } nowBundleList.assetbundles = new List(nowBundleDict.Values); nowBundleList.version = resourceVersion; SaveNewBundleList(); return true; } /// /// 下载单个文件并校验MD5 /// private async Task DownloadSingleFile(string url, string savePath, string expectedMd5) { int maxRetries = 5; for (int i = 0; i <= maxRetries; i++) { using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) { var operation = webRequest.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); } if (webRequest.result == UnityWebRequest.Result.Success) { File.WriteAllBytes(savePath, webRequest.downloadHandler.data); string actualMd5 = CalculateMD5(savePath); if (actualMd5.ToLower() == expectedMd5.ToLower()) { return true; } else { Debug.LogError($"MD5校验失败 {url}: 期望 {expectedMd5}, 实际 {actualMd5}"); if (File.Exists(savePath)) { File.Delete(savePath); } } } else { Debug.LogError($"下载失败 {url}: {webRequest.error} (尝试 {i + 1}/{maxRetries + 1})"); } if (i < maxRetries) { await Task.Delay(1000 * (i + 1)); } } } return false; } /// /// 计算文件的MD5值 /// public string CalculateMD5(string filePath) { using (var md5 = System.Security.Cryptography.MD5.Create()) { using (var stream = File.OpenRead(filePath)) { byte[] hash = md5.ComputeHash(stream); return BitConverter.ToString(hash).Replace("-", "").ToLower(); } } } /// /// 保存新的资源列表到本地 /// public void SaveNewBundleList() { if (newBundleList != null) { string json = JsonUtility.ToJson(newBundleList, true); string savePath = Path.Combine(localPath, "list.json"); File.WriteAllText(savePath, json); Debug.Log("新的资源列表已保存到本地"); } } #endregion #region 图片加载与缓存 /// /// 图片缓存目录名 /// private const string IMAGE_CACHE_DIR = "ImageCache"; /// /// 获取图片缓存目录路径 /// private string GetImageCacheDirectory() { string cacheDir = Path.Combine(localPath, IMAGE_CACHE_DIR); if (!Directory.Exists(cacheDir)) { Directory.CreateDirectory(cacheDir); } return cacheDir; } /// /// 根据URL生成缓存文件名(使用MD5哈希) /// private string GetCacheFileName(string url) { using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create()) { byte[] hashBytes = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(url)); string hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); // 尝试从URL获取扩展名,如果没有则使用.jpg string extension = ".jpg"; try { string urlPath = new Uri(url).AbsolutePath; string urlExt = Path.GetExtension(urlPath); if (!string.IsNullOrEmpty(urlExt)) { extension = urlExt; } } catch { } return hashString + extension; } } /// /// 获取图片缓存的完整路径 /// private string GetImageCachePath(string url) { string cacheDir = GetImageCacheDirectory(); string fileName = GetCacheFileName(url); return Path.Combine(cacheDir, fileName); } /// /// 检查图片是否已缓存 /// public bool IsImageCached(string url) { string cachePath = GetImageCachePath(url); return File.Exists(cachePath); } /// /// 加载网络图片(带缓存) /// /// 图片URL /// 下载进度回调 (0-1) /// Texture2D对象,失败返回null public async Task LoadImageAsync(string url, Action onProgress = null) { if (string.IsNullOrEmpty(url)) { Debug.LogWarning("[NetworkCtrl] 图片URL为空"); return null; } // 首先检查本地缓存 string cachePath = GetImageCachePath(url); if (File.Exists(cachePath)) { Debug.Log($"[NetworkCtrl] 从缓存加载图片: {url}"); return await LoadImageFromCacheAsync(cachePath); } // 从网络下载 Debug.Log($"[NetworkCtrl] 从网络下载图片: {url}"); return await DownloadAndCacheImageAsync(url, cachePath, onProgress); } /// /// 从本地缓存加载图片 /// private async Task LoadImageFromCacheAsync(string cachePath) { try { byte[] imageData = await File.ReadAllBytesAsync(cachePath); Texture2D texture = new Texture2D(2, 2, TextureFormat.RGBA32, false); if (texture.LoadImage(imageData)) { return texture; } else { Debug.LogError($"[NetworkCtrl] 缓存图片加载失败: {cachePath}"); // 删除损坏的缓存文件 File.Delete(cachePath); return null; } } catch (Exception ex) { Debug.LogError($"[NetworkCtrl] 读取缓存图片失败: {ex.Message}"); return null; } } /// /// 下载并缓存图片 /// private async Task DownloadAndCacheImageAsync(string url, string cachePath, Action onProgress) { try { using (UnityWebRequest request = UnityWebRequest.Get(url)) { request.downloadHandler = new DownloadHandlerBuffer(); var operation = request.SendWebRequest(); // 报告进度 while (!operation.isDone) { onProgress?.Invoke(request.downloadProgress); await Task.Yield(); } onProgress?.Invoke(1f); if (request.result == UnityWebRequest.Result.Success) { byte[] imageData = request.downloadHandler.data; // 保存到缓存 try { await File.WriteAllBytesAsync(cachePath, imageData); Debug.Log($"[NetworkCtrl] 图片已缓存: {cachePath}"); } catch (Exception ex) { Debug.LogWarning($"[NetworkCtrl] 图片缓存保存失败: {ex.Message}"); } // 创建Texture2D Texture2D texture = new Texture2D(2, 2, TextureFormat.RGBA32, false); if (texture.LoadImage(imageData)) { return texture; } else { Debug.LogError($"[NetworkCtrl] 图片数据解析失败: {url}"); Destroy(texture); return null; } } else { Debug.LogError($"[NetworkCtrl] 图片下载失败: {url}, 错误: {request.error}"); return null; } } } catch (Exception ex) { Debug.LogError($"[NetworkCtrl] 下载图片异常: {ex.Message}"); return null; } } /// /// 加载图片并应用到Image组件(带缓存) /// /// 图片URL /// UI Image组件 /// 下载进度回调 public async Task LoadImageToUIImageAsync(string url, Image image, Action onProgress = null) { if (image == null) { Debug.LogWarning("[NetworkCtrl] Image组件为空"); return; } Texture2D texture = await LoadImageAsync(url, onProgress); if (texture != null) { Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 0.5f); image.sprite = sprite; } image.gameObject.SetActive(true); } /// /// 加载图片并应用到RawImage组件(带缓存) /// public async Task LoadImageToUIRawImageAsync(string url, RawImage rawImage, Action onProgress = null) { if (rawImage == null) { Debug.LogWarning("[NetworkCtrl] RawImage组件为空"); return; } Texture2D texture = await LoadImageAsync(url, onProgress); if (texture != null) { rawImage.texture = texture; } } /// /// 加载图片并应用到SpriteRenderer(带缓存) /// public async Task LoadImageToSpriteRendererAsync(string url, SpriteRenderer spriteRenderer, Action onProgress = null) { if (spriteRenderer == null) { Debug.LogWarning("[NetworkCtrl] SpriteRenderer组件为空"); return; } Texture2D texture = await LoadImageAsync(url, onProgress); if (texture != null) { Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 0.5f); spriteRenderer.sprite = sprite; } } /// /// 清除指定URL的图片缓存 /// public void ClearImageCache(string url) { string cachePath = GetImageCachePath(url); if (File.Exists(cachePath)) { File.Delete(cachePath); Debug.Log($"[NetworkCtrl] 已清除图片缓存: {url}"); } } /// /// 清除所有图片缓存 /// public void ClearAllImageCache() { string cacheDir = GetImageCacheDirectory(); if (Directory.Exists(cacheDir)) { string[] files = Directory.GetFiles(cacheDir); foreach (string file in files) { try { File.Delete(file); } catch (Exception ex) { Debug.LogWarning($"[NetworkCtrl] 删除缓存文件失败: {ex.Message}"); } } Debug.Log($"[NetworkCtrl] 已清除所有图片缓存,共 {files.Length} 个文件"); } } /// /// 获取图片缓存总大小(字节) /// public long GetImageCacheSize() { string cacheDir = GetImageCacheDirectory(); if (!Directory.Exists(cacheDir)) return 0; long totalSize = 0; string[] files = Directory.GetFiles(cacheDir); foreach (string file in files) { try { FileInfo fileInfo = new FileInfo(file); totalSize += fileInfo.Length; } catch { } } return totalSize; } /// /// 获取图片缓存文件数量 /// public int GetImageCacheCount() { string cacheDir = GetImageCacheDirectory(); if (!Directory.Exists(cacheDir)) return 0; return Directory.GetFiles(cacheDir).Length; } public async Task GetNewBundle() { LoadingUI.Show(); var response = await Instance.Get($"/api/v1/app/version/bundle?version={Application.version}&platform={platform}"); ResponseCodeHandler.HandleResponse(response, onSuccess: (data) => { resourceVersion=data.data.bundle_version; nowResourceUrl=data.data.bundle_url; } ); LoadingUI.Hide(); } #endregion #region 设备控制指令接口 #endregion } #region 设备控制接口数据模型 /// /// 设备控制指令请求 /// [System.Serializable] public class DeviceControlRequest { public string device_sn; public string command; } /// /// 设备控制指令响应 /// [System.Serializable] public class DeviceControlResponse { public string code; public string message; } #endregion }