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
{
///
/// 请求头配置
///
[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 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;
}
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();
}
///
/// 初始化网络管理器组件
///
private void 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, Dictionary formData, HttpRequestConfig config = null)
{
return await HttpRequestManager.Instance.PostForm(url, formData, config);
}
///
/// 发送PUT请求
///
public async Task> Put(string url, object data = null, HttpRequestConfig config = null)
{
return await HttpRequestManager.Instance.Put(url, data, 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 资源更新 (保留原有功能)
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(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 void 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 + $"{resourceVersion}/{platform}/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)
{
bool success = await DownloadUpdateFiles(updateList, (downloaded, total) =>
{
float progress = (float)downloaded / total;
Debug.Log($"更新进度: {progress:P}");
});
if (success)
{
Debug.Log("资源更新完成");
}
else
{
ToastUI.Show("100039");
Debug.LogError("资源更新失败");
await UpdateResources();
}
}
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 + $"{resourceVersion}/{platform}/{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值
///
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();
}
}
}
///
/// 保存新的资源列表到本地
///
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
}
}