killapp/Assets/Scripts/Network/NetworkCtrl.cs
2026-04-20 08:31:41 +08:00

710 lines
23 KiB
C#
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.

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;
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 -
[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;
}
async void Start()
{
nowResourceUrl = resourceUrls[nowResourceIndex];
#if UNITY_EDITOR_WIN
platform = "Windows";
#elif UNITY_EDITOR_OSX
platform = "Mac";
#elif UNITY_ANDROID
platform = "Android";
#elif UNITY_IOS
platform = "iOS";
#endif
Debug.Log("Init");
await Init();
}
public async Task Init()
{
Debug.Log("初始化网络控制");
AndroidStatusBar.statusBarState = AndroidStatusBar.States.TranslucentOverContent;
// 初始化网络管理器
InitializeNetworkManagers();
// 检查并清理过期资源
CheckAndCleanOutdatedResources();
if (LoadRes.Instance.resPosition == LoadRes.ResPosition.)
{
await GetNowBundleList();
if (nowResVersion < new Version(resourceVersion))
{
Debug.Log("111");
await GetBundleList();
await UpdateResources();
}
}
await LoadRes.Instance.Init();
await DataManager.Instance.Init();
UI.UIManager.Instance.Init();
}
/// <summary>
/// 初始化网络管理器组件
/// </summary>
private void 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, Dictionary<string, string> formData, HttpRequestConfig config = null)
{
return await HttpRequestManager.Instance.PostForm<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>
/// 发送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 ()
public string[] resourceUrls;
public int nowResourceIndex;
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 void 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 + $"{platform}/{resourceVersion}/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)
{
bool success = await DownloadUpdateFiles(updateList, (downloaded, total) =>
{
float progress = (float)downloaded / total;
Debug.Log($"更新进度: {progress:P}");
});
if (success)
{
Debug.Log("资源更新完成");
}
else
{
ToastUI.ShowText("资源更新失败");
Debug.LogError("资源更新失败");
await UpdateResources();
}
}
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 + $"{platform}/{resourceVersion}/{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>
private 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
}
}