1091 lines
36 KiB
C#
1091 lines
36 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 请求头配置
|
||
/// </summary>
|
||
[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<HeaderConfig> globalHeaders = new List<HeaderConfig>
|
||
{
|
||
// 示例: 在这里添加你的全局请求头
|
||
// 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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化网络管理器组件
|
||
/// </summary>
|
||
private async Task InitializeNetworkManagers()
|
||
{
|
||
// 确保 NetworkStateManager 存在
|
||
if (NetworkStateManager.Instance == null)
|
||
{
|
||
var nsManagerObj = new GameObject("NetworkStateManager");
|
||
nsManagerObj.AddComponent<NetworkStateManager>();
|
||
}
|
||
|
||
// 确保 HttpRequestManager 存在并配置
|
||
if (HttpRequestManager.Instance == null)
|
||
{
|
||
var httpManagerObj = new GameObject("HttpRequestManager");
|
||
httpManagerObj.AddComponent<HttpRequestManager>();
|
||
}
|
||
|
||
// 配置 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<RequestQueueManager>();
|
||
queueManager.SetMaxConcurrentRequests(maxConcurrentRequests);
|
||
}
|
||
|
||
Debug.Log($"[NetworkCtrl] 网络管理器初始化完成 - 服务器: {serverBaseUrl}");
|
||
}
|
||
|
||
#region 便捷的网络请求API
|
||
|
||
/// <summary>
|
||
/// 发送GET请求
|
||
/// </summary>
|
||
public async Task<HttpResponse<T>> Get<T>(string url, Dictionary<string, string> queryParams = null, HttpRequestConfig config = null)
|
||
{
|
||
return await HttpRequestManager.Instance.Get<T>(url, queryParams, config);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送GET请求(返回字符串)
|
||
/// </summary>
|
||
public async Task<HttpResponse<string>> Get(string url, Dictionary<string, string> queryParams = null, HttpRequestConfig config = null)
|
||
{
|
||
return await HttpRequestManager.Instance.Get(url, queryParams, config);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送POST请求
|
||
/// </summary>
|
||
public async Task<HttpResponse<T>> Post<T>(string url, object data = null, HttpRequestConfig config = null)
|
||
{
|
||
return await HttpRequestManager.Instance.Post<T>(url, data, config);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送POST请求(表单数据)
|
||
/// </summary>
|
||
public async Task<HttpResponse<T>> PostForm<T>(string url, WWWForm formData, HttpRequestConfig config = null)
|
||
{
|
||
return await HttpRequestManager.Instance.PostForm<T>(url, formData, config);
|
||
}
|
||
public async Task<HttpResponse<T>> PostFormData<T>(string url, List<IMultipartFormSection> formData, HttpRequestConfig config = null)
|
||
{
|
||
return await HttpRequestManager.Instance.PostFormData<T>(url, formData, config);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送PUT请求
|
||
/// </summary>
|
||
public async Task<HttpResponse<T>> Put<T>(string url, object data = null, HttpRequestConfig config = null)
|
||
{
|
||
return await HttpRequestManager.Instance.Put<T>(url, data, config);
|
||
}
|
||
/// <summary>
|
||
/// 发送PUTform请求
|
||
/// </summary>
|
||
public async Task<HttpResponse<T>> PutForm<T>(string url, WWWForm formData, HttpRequestConfig config = null)
|
||
{
|
||
return await HttpRequestManager.Instance.PutForm<T>(url, formData, config);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送DELETE请求
|
||
/// </summary>
|
||
public async Task<HttpResponse<T>> Delete<T>(string url, object data = null, HttpRequestConfig config = null)
|
||
{
|
||
if (data != null)
|
||
{
|
||
return await HttpRequestManager.Instance.Delete<T>(url, data, config);
|
||
}
|
||
return await HttpRequestManager.Instance.Delete<T>(url, config);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 下载文件
|
||
/// </summary>
|
||
public async Task<HttpResponse<string>> DownloadFile(string url, string savePath, Action<DownloadProgress> onProgress = null, HttpRequestConfig config = null)
|
||
{
|
||
return await HttpRequestManager.Instance.DownloadFile(url, savePath, onProgress, config);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加请求到队列
|
||
/// </summary>
|
||
public string EnqueueRequest<T>(Func<Task<HttpResponse<T>>> requestFunc, RequestPriority priority = RequestPriority.Normal, Action<HttpResponse<T>> onComplete = null)
|
||
{
|
||
return RequestQueueManager.Instance.Enqueue(requestFunc, priority, onComplete);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消请求
|
||
/// </summary>
|
||
public bool CancelRequest(string requestId)
|
||
{
|
||
return RequestQueueManager.Instance.CancelRequest(requestId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消所有请求
|
||
/// </summary>
|
||
public void CancelAllRequests()
|
||
{
|
||
RequestQueueManager.Instance.CancelAllRequests();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查网络状态
|
||
/// </summary>
|
||
public bool IsNetworkConnected
|
||
{
|
||
get
|
||
{
|
||
return NetworkStateManager.Instance != null && NetworkStateManager.Instance.IsConnected;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前网络类型
|
||
/// </summary>
|
||
public NetworkConnectivityState CurrentNetworkState
|
||
{
|
||
get
|
||
{
|
||
return NetworkStateManager.Instance != null ? NetworkStateManager.Instance.CurrentState : NetworkConnectivityState.Unknown;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 全局配置
|
||
|
||
/// <summary>
|
||
/// 设置服务器基础URL
|
||
/// </summary>
|
||
public void SetServerUrl(string baseUrl)
|
||
{
|
||
BaseServerUrl = baseUrl;
|
||
if (HttpRequestManager.Instance != null)
|
||
{
|
||
HttpRequestManager.Instance.SetBaseUrl(baseUrl);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加全局请求头
|
||
/// </summary>
|
||
public void AddGlobalHeader(string key, string value)
|
||
{
|
||
HttpRequestManager.Instance?.AddGlobalHeader(key, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除全局请求头
|
||
/// </summary>
|
||
public void RemoveGlobalHeader(string key)
|
||
{
|
||
HttpRequestManager.Instance?.RemoveGlobalHeader(key);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置默认请求超时时间
|
||
/// </summary>
|
||
public void SetDefaultTimeout(int seconds)
|
||
{
|
||
var config = new HttpRequestConfig { Timeout = seconds };
|
||
HttpRequestManager.Instance?.SetDefaultConfig(config);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置默认最大重试次数
|
||
/// </summary>
|
||
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<AssetBundleInfoList>(File.ReadAllText(localListPath));
|
||
Debug.Log("===获取本地资源列表成功");
|
||
}
|
||
else
|
||
{
|
||
string listJson = await ReadTextFileFromStreamingAssets(streamingListPath);
|
||
if (!string.IsNullOrEmpty(listJson))
|
||
{
|
||
nowBundleList = JsonUtility.FromJson<AssetBundleInfoList>(listJson);
|
||
Debug.Log("===获取更包资源列表成功");
|
||
}
|
||
else
|
||
{
|
||
Debug.Log("===没有获取资源列表");
|
||
nowBundleList = null;
|
||
}
|
||
}
|
||
if (nowBundleList != null)
|
||
{
|
||
nowResVersion = new Version(nowBundleList.version);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查并清理过期的本地资源
|
||
/// </summary>
|
||
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<AssetBundleInfoList>(streamingListJson);
|
||
string localListPath = Path.Combine(localPath, "list.json");
|
||
|
||
if (!File.Exists(localListPath))
|
||
{
|
||
return;
|
||
}
|
||
|
||
string localListJson = File.ReadAllText(localListPath);
|
||
AssetBundleInfoList localBundleList = JsonUtility.FromJson<AssetBundleInfoList>(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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理本地的所有AssetBundle文件
|
||
/// </summary>
|
||
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<AssetBundleInfoList>(webRequest.downloadHandler.text);
|
||
newBundleList = list;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError(url + "获取资源列表失败" + webRequest.error);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从StreamingAssets目录读取文本文件(兼容各平台)
|
||
/// </summary>
|
||
private async Task<string> 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<AssetBundleInfo> 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("资源已是最新版本");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 对比当前资源列表和新资源列表,找出需要更新的文件
|
||
/// </summary>
|
||
public List<AssetBundleInfo> GetUpdateList()
|
||
{
|
||
List<AssetBundleInfo> updateList = new List<AssetBundleInfo>();
|
||
|
||
if (nowBundleList == null || nowBundleList.assetbundles == null)
|
||
{
|
||
return newBundleList.assetbundles ?? new List<AssetBundleInfo>();
|
||
}
|
||
|
||
Dictionary<string, AssetBundleInfo> nowBundleDict = new Dictionary<string, AssetBundleInfo>();
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算需要更新的总大小
|
||
/// </summary>
|
||
public long CalculateUpdateSize(List<AssetBundleInfo> updateList)
|
||
{
|
||
long totalSize = 0;
|
||
foreach (var bundle in updateList)
|
||
{
|
||
totalSize += bundle.size;
|
||
}
|
||
return totalSize;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 下载更新文件并实时返回进度
|
||
/// </summary>
|
||
public async Task<bool> DownloadUpdateFiles(List<AssetBundleInfo> updateList, Action<long, long> progressCallback)
|
||
{
|
||
long totalSize = CalculateUpdateSize(updateList);
|
||
long downloadedSize = 0;
|
||
|
||
if (nowBundleList == null || nowBundleList.assetbundles == null)
|
||
{
|
||
nowBundleList = new AssetBundleInfoList(new List<AssetBundleInfo>());
|
||
}
|
||
|
||
Dictionary<string, AssetBundleInfo> nowBundleDict = new Dictionary<string, AssetBundleInfo>();
|
||
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<AssetBundleInfo>(nowBundleDict.Values);
|
||
nowBundleList.version = resourceVersion;
|
||
SaveNewBundleList();
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 下载单个文件并校验MD5
|
||
/// </summary>
|
||
private async Task<bool> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算文件的MD5值
|
||
/// </summary>
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存新的资源列表到本地
|
||
/// </summary>
|
||
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 图片加载与缓存
|
||
|
||
/// <summary>
|
||
/// 图片缓存目录名
|
||
/// </summary>
|
||
private const string IMAGE_CACHE_DIR = "ImageCache";
|
||
|
||
/// <summary>
|
||
/// 获取图片缓存目录路径
|
||
/// </summary>
|
||
private string GetImageCacheDirectory()
|
||
{
|
||
string cacheDir = Path.Combine(localPath, IMAGE_CACHE_DIR);
|
||
if (!Directory.Exists(cacheDir))
|
||
{
|
||
Directory.CreateDirectory(cacheDir);
|
||
}
|
||
return cacheDir;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据URL生成缓存文件名(使用MD5哈希)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取图片缓存的完整路径
|
||
/// </summary>
|
||
private string GetImageCachePath(string url)
|
||
{
|
||
string cacheDir = GetImageCacheDirectory();
|
||
string fileName = GetCacheFileName(url);
|
||
return Path.Combine(cacheDir, fileName);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查图片是否已缓存
|
||
/// </summary>
|
||
public bool IsImageCached(string url)
|
||
{
|
||
string cachePath = GetImageCachePath(url);
|
||
return File.Exists(cachePath);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载网络图片(带缓存)
|
||
/// </summary>
|
||
/// <param name="url">图片URL</param>
|
||
/// <param name="onProgress">下载进度回调 (0-1)</param>
|
||
/// <returns>Texture2D对象,失败返回null</returns>
|
||
public async Task<Texture2D> LoadImageAsync(string url, Action<float> 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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从本地缓存加载图片
|
||
/// </summary>
|
||
private async Task<Texture2D> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 下载并缓存图片
|
||
/// </summary>
|
||
private async Task<Texture2D> DownloadAndCacheImageAsync(string url, string cachePath, Action<float> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载图片并应用到Image组件(带缓存)
|
||
/// </summary>
|
||
/// <param name="url">图片URL</param>
|
||
/// <param name="image">UI Image组件</param>
|
||
/// <param name="onProgress">下载进度回调</param>
|
||
public async Task LoadImageToUIImageAsync(string url, Image image, Action<float> 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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载图片并应用到RawImage组件(带缓存)
|
||
/// </summary>
|
||
public async Task LoadImageToUIRawImageAsync(string url, RawImage rawImage, Action<float> onProgress = null)
|
||
{
|
||
if (rawImage == null)
|
||
{
|
||
Debug.LogWarning("[NetworkCtrl] RawImage组件为空");
|
||
return;
|
||
}
|
||
|
||
Texture2D texture = await LoadImageAsync(url, onProgress);
|
||
|
||
if (texture != null)
|
||
{
|
||
rawImage.texture = texture;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载图片并应用到SpriteRenderer(带缓存)
|
||
/// </summary>
|
||
public async Task LoadImageToSpriteRendererAsync(string url, SpriteRenderer spriteRenderer, Action<float> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除指定URL的图片缓存
|
||
/// </summary>
|
||
public void ClearImageCache(string url)
|
||
{
|
||
string cachePath = GetImageCachePath(url);
|
||
if (File.Exists(cachePath))
|
||
{
|
||
File.Delete(cachePath);
|
||
Debug.Log($"[NetworkCtrl] 已清除图片缓存: {url}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除所有图片缓存
|
||
/// </summary>
|
||
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} 个文件");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取图片缓存总大小(字节)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取图片缓存文件数量
|
||
/// </summary>
|
||
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<BundleDataResponse>($"/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 设备控制接口数据模型
|
||
|
||
/// <summary>
|
||
/// 设备控制指令请求
|
||
/// </summary>
|
||
[System.Serializable]
|
||
public class DeviceControlRequest
|
||
{
|
||
public string device_sn;
|
||
public string command;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设备控制指令响应
|
||
/// </summary>
|
||
[System.Serializable]
|
||
public class DeviceControlResponse
|
||
{
|
||
public string code;
|
||
public string message;
|
||
}
|
||
|
||
#endregion
|
||
}
|