using UnityEngine; using System.Collections; using System.Collections.Generic; using System; using System.Text; using BestHTTP; using Newtonsoft.Json; using System.IO; using Cysharp.Threading.Tasks; using System.Security.Cryptography; using System.Linq; using AX.MessageSystem; /// /// 服务器配置文件对应类型 /// public class ServerConfig { public string Ip; public string Port; } /// /// 刷新令牌信息。 /// public class RefreshTokenInfo { /// /// JWT令牌。 /// public string Token { get; set; } /// /// 刷新令牌。 /// public string RefreshToken { get; set; } } public class HTTPRequestManager : MonoBehaviour { public static HTTPRequestManager Instance; private string baseHttpUrl = ""; public IdentityInfo identityInfo = new IdentityInfo(); //记录登录成功后的身份信息 public string userName;//记录登录成功后的用户名 private const string refreshTokenApi = "/api/Account/RefreshToken"; #region 文件下载、上传成员变量 private const int fragmentSize = 5 * 1024 * 1024;//分块大小 private const string newMultipartUploadApi = "/api/NewMultipartUpload/firetraining"; private const string completeMultipartUploadApi = "/api/CompleteMultipartUpload/firetraining"; private const string multipartUploadInfosKey = "MultipartUploadInfos"; #endregion private void Awake() { DontDestroyOnLoad(this); Instance = this; //Json序列化全局默认设置 JsonConvert.DefaultSettings = () => new JsonSerializerSettings() { Formatting = Formatting.Indented }; } void Start() { if (string.IsNullOrEmpty(baseHttpUrl)) { string filepath = Application.streamingAssetsPath + @"/ServerConfig.json"; LoadFileHelper.Instance.LoadFileStringAsync(filepath, (s) => { ServerConfig config = JsonConvert.DeserializeObject(s); baseHttpUrl = "http://" + config.Ip + ":" + config.Port; MessageDispatcher.SendMessage("ServerConfigLoaded"); }); } } /// /// 获取json数据 /// /// 返回数据类型 /// API URL /// 请求成功回调 /// 请求错误回调 public void GetJson(string apiUrl, Action success, Action error = null) { if (string.IsNullOrEmpty(apiUrl)) { throw new ArgumentNullException($"API URL:{apiUrl}无效!"); } HTTPRequest request = new HTTPRequest(new Uri(baseHttpUrl + apiUrl), HTTPMethods.Get, (req, resp) => { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { T result = JsonConvert.DeserializeObject(resp.DataAsText); success?.Invoke(result); } else { Debug.LogWarning(string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); error?.Invoke(resp.StatusCode, JsonConvert.DeserializeObject(resp.DataAsText).detail); } break; case HTTPRequestStates.Error: Debug.LogError("Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception")); error?.Invoke((int)HTTPRequestStates.Error, "Request Finished with Error!"); break; case HTTPRequestStates.Aborted: Debug.LogWarning("Request Aborted!"); error?.Invoke((int)HTTPRequestStates.Aborted, "Request Aborted!"); break; case HTTPRequestStates.ConnectionTimedOut: Debug.LogError("Connection Timed Out!"); error?.Invoke((int)HTTPRequestStates.ConnectionTimedOut, "Connection Timed Out!"); break; case HTTPRequestStates.TimedOut: Debug.LogError("Processing the request Timed Out!"); error?.Invoke((int)HTTPRequestStates.TimedOut, "Processing the request Timed Out!"); break; } }); request.SetHeader("Authorization", "Bearer " + identityInfo.token); request.Send(); } /// /// 提交json数据 /// /// 提交数据类型 /// 返回数据类型 /// API URL /// 提交数据 /// 请求成功回调 /// 请求错误回调 public void PostJson(string apiUrl, TIn data, Action success, Action error = null) { if (string.IsNullOrEmpty(apiUrl)) { throw new ArgumentNullException($"API URL:{apiUrl}无效!"); } HTTPRequest request = new HTTPRequest(new Uri(baseHttpUrl + apiUrl), HTTPMethods.Post, (req, resp) => { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { TOut result = JsonConvert.DeserializeObject(resp.DataAsText); success?.Invoke(result); } else { Debug.LogWarning(string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); error?.Invoke(resp.StatusCode, JsonConvert.DeserializeObject(resp.DataAsText).detail); } break; case HTTPRequestStates.Error: Debug.LogError("Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception")); error?.Invoke((int)HTTPRequestStates.Error, "Request Finished with Error!"); break; case HTTPRequestStates.Aborted: Debug.LogWarning("Request Aborted!"); error?.Invoke((int)HTTPRequestStates.Aborted, "Request Aborted!"); break; case HTTPRequestStates.ConnectionTimedOut: Debug.LogError("Connection Timed Out!"); error?.Invoke((int)HTTPRequestStates.ConnectionTimedOut, "Connection Timed Out!"); break; case HTTPRequestStates.TimedOut: Debug.LogError("Processing the request Timed Out!"); error?.Invoke((int)HTTPRequestStates.TimedOut, "Processing the request Timed Out!"); break; } }); request.SetHeader("Authorization", "Bearer " + identityInfo.token); request.SetHeader("Content-Type", "application/json; charset=UTF-8"); string json = JsonConvert.SerializeObject(data); request.RawData = Encoding.UTF8.GetBytes(json); request.Send(); } /// /// 提交json数据 /// /// 提交数据类型 /// API URL /// 提交数据 /// 请求成功回调 /// 请求错误回调 public void PostJson(string apiUrl, TIn data, Action success, Action error = null) { if (string.IsNullOrEmpty(apiUrl)) { throw new ArgumentNullException($"API URL:{apiUrl}无效!"); } HTTPRequest request = new HTTPRequest(new Uri(baseHttpUrl + apiUrl), HTTPMethods.Post, (req, resp) => { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { success?.Invoke(); } else { Debug.LogWarning(string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); error?.Invoke(resp.StatusCode, JsonConvert.DeserializeObject(resp.DataAsText).detail); } break; case HTTPRequestStates.Error: Debug.LogError("Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception")); error?.Invoke((int)HTTPRequestStates.Error, "Request Finished with Error!"); break; case HTTPRequestStates.Aborted: Debug.LogWarning("Request Aborted!"); error?.Invoke((int)HTTPRequestStates.Aborted, "Request Aborted!"); break; case HTTPRequestStates.ConnectionTimedOut: Debug.LogError("Connection Timed Out!"); error?.Invoke((int)HTTPRequestStates.ConnectionTimedOut, "Connection Timed Out!"); break; case HTTPRequestStates.TimedOut: Debug.LogError("Processing the request Timed Out!"); error?.Invoke((int)HTTPRequestStates.TimedOut, "Processing the request Timed Out!"); break; } }); request.SetHeader("Authorization", "Bearer " + identityInfo.token); request.SetHeader("Content-Type", "application/json; charset=UTF-8"); string json = JsonConvert.SerializeObject(data); request.RawData = Encoding.UTF8.GetBytes(json); request.Send(); } /// /// 提交json数据 /// /// 返回数据类型 /// API URL /// 请求成功回调 /// 请求错误回调 public void PostJson(string apiUrl, Action success, Action error = null) { if (string.IsNullOrEmpty(apiUrl)) { throw new ArgumentNullException($"API URL:{apiUrl}无效!"); } HTTPRequest request = new HTTPRequest(new Uri(baseHttpUrl + apiUrl), HTTPMethods.Post, (req, resp) => { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { TOut result = JsonConvert.DeserializeObject(resp.DataAsText); success?.Invoke(result); } else { Debug.LogWarning(string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); error?.Invoke(resp.StatusCode, JsonConvert.DeserializeObject(resp.DataAsText).detail); } break; case HTTPRequestStates.Error: Debug.LogError("Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception")); error?.Invoke((int)HTTPRequestStates.Error, "Request Finished with Error!"); break; case HTTPRequestStates.Aborted: Debug.LogWarning("Request Aborted!"); error?.Invoke((int)HTTPRequestStates.Aborted, "Request Aborted!"); break; case HTTPRequestStates.ConnectionTimedOut: Debug.LogError("Connection Timed Out!"); error?.Invoke((int)HTTPRequestStates.ConnectionTimedOut, "Connection Timed Out!"); break; case HTTPRequestStates.TimedOut: Debug.LogError("Processing the request Timed Out!"); error?.Invoke((int)HTTPRequestStates.TimedOut, "Processing the request Timed Out!"); break; } }); request.SetHeader("Authorization", "Bearer " + identityInfo.token); request.Send(); } /// /// 提交json数据 /// /// API URL /// 请求成功回调 /// 请求错误回调 public void PostJson(string apiUrl, Action success, Action error = null) { if (string.IsNullOrEmpty(apiUrl)) { throw new ArgumentNullException($"API URL:{apiUrl}无效!"); } HTTPRequest request = new HTTPRequest(new Uri(baseHttpUrl + apiUrl), HTTPMethods.Post, (req, resp) => { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { success?.Invoke(); } else { Debug.LogWarning(string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); error?.Invoke(resp.StatusCode, JsonConvert.DeserializeObject(resp.DataAsText).detail); } break; case HTTPRequestStates.Error: Debug.LogError("Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception")); error?.Invoke((int)HTTPRequestStates.Error, "Request Finished with Error!"); break; case HTTPRequestStates.Aborted: Debug.LogWarning("Request Aborted!"); error?.Invoke((int)HTTPRequestStates.Aborted, "Request Aborted!"); break; case HTTPRequestStates.ConnectionTimedOut: Debug.LogError("Connection Timed Out!"); error?.Invoke((int)HTTPRequestStates.ConnectionTimedOut, "Connection Timed Out!"); break; case HTTPRequestStates.TimedOut: Debug.LogError("Processing the request Timed Out!"); error?.Invoke((int)HTTPRequestStates.TimedOut, "Processing the request Timed Out!"); break; } }); request.SetHeader("Authorization", "Bearer " + identityInfo.token); request.Send(); } /// /// 修改json数据 /// /// 修改数据类型 /// 返回数据类型 /// API URL /// 修改数据 /// 请求成功回调 /// 请求错误回调 public void PatchJson(string apiUrl, TIn data, Action success, Action error = null) { if (string.IsNullOrEmpty(apiUrl)) { throw new ArgumentNullException($"API URL:{apiUrl}无效!"); } HTTPRequest request = new HTTPRequest(new Uri(baseHttpUrl + apiUrl), HTTPMethods.Patch, (req, resp) => { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { TOut result = JsonConvert.DeserializeObject(resp.DataAsText); success?.Invoke(result); } else { Debug.LogWarning(string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); error?.Invoke(resp.StatusCode, JsonConvert.DeserializeObject(resp.DataAsText).detail); } break; case HTTPRequestStates.Error: Debug.LogError("Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception")); error?.Invoke((int)HTTPRequestStates.Error, "Request Finished with Error!"); break; case HTTPRequestStates.Aborted: Debug.LogWarning("Request Aborted!"); error?.Invoke((int)HTTPRequestStates.Aborted, "Request Aborted!"); break; case HTTPRequestStates.ConnectionTimedOut: Debug.LogError("Connection Timed Out!"); error?.Invoke((int)HTTPRequestStates.ConnectionTimedOut, "Connection Timed Out!"); break; case HTTPRequestStates.TimedOut: Debug.LogError("Processing the request Timed Out!"); error?.Invoke((int)HTTPRequestStates.TimedOut, "Processing the request Timed Out!"); break; } }); request.SetHeader("Authorization", "Bearer " + identityInfo.token); request.SetHeader("Content-Type", "application/json; charset=UTF-8"); string json = JsonConvert.SerializeObject(data); request.RawData = Encoding.UTF8.GetBytes(json); request.Send(); } /// /// 修改json数据 /// /// 修改数据类型 /// API URL /// 修改数据 /// 请求成功回调 /// 请求错误回调 public void PatchJson(string apiUrl, TIn data, Action success, Action error = null) { if (string.IsNullOrEmpty(apiUrl)) { throw new ArgumentNullException($"API URL:{apiUrl}无效!"); } HTTPRequest request = new HTTPRequest(new Uri(baseHttpUrl + apiUrl), HTTPMethods.Patch, (req, resp) => { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { success?.Invoke(); } else { Debug.LogWarning(string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); error?.Invoke(resp.StatusCode, JsonConvert.DeserializeObject(resp.DataAsText).detail); } break; case HTTPRequestStates.Error: Debug.LogError("Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception")); error?.Invoke((int)HTTPRequestStates.Error, "Request Finished with Error!"); break; case HTTPRequestStates.Aborted: Debug.LogWarning("Request Aborted!"); error?.Invoke((int)HTTPRequestStates.Aborted, "Request Aborted!"); break; case HTTPRequestStates.ConnectionTimedOut: Debug.LogError("Connection Timed Out!"); error?.Invoke((int)HTTPRequestStates.ConnectionTimedOut, "Connection Timed Out!"); break; case HTTPRequestStates.TimedOut: Debug.LogError("Processing the request Timed Out!"); error?.Invoke((int)HTTPRequestStates.TimedOut, "Processing the request Timed Out!"); break; } }); request.SetHeader("Authorization", "Bearer " + identityInfo.token); request.SetHeader("Content-Type", "application/json; charset=UTF-8"); string json = JsonConvert.SerializeObject(data); request.RawData = Encoding.UTF8.GetBytes(json); request.Send(); } /// /// 删除json数据 /// /// API URL /// 请求成功回调 /// 请求错误回调 public void DeleteJson(string apiUrl, Action success, Action error = null) { if (string.IsNullOrEmpty(apiUrl)) { throw new ArgumentNullException($"API URL:{apiUrl}无效!"); } HTTPRequest request = new HTTPRequest(new Uri(baseHttpUrl + apiUrl), HTTPMethods.Delete, (req, resp) => { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { success?.Invoke(); } else { Debug.LogWarning(string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); error?.Invoke(resp.StatusCode, JsonConvert.DeserializeObject(resp.DataAsText).detail); } break; case HTTPRequestStates.Error: Debug.LogError("Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception")); error?.Invoke((int)HTTPRequestStates.Error, "Request Finished with Error!"); break; case HTTPRequestStates.Aborted: Debug.LogWarning("Request Aborted!"); error?.Invoke((int)HTTPRequestStates.Aborted, "Request Aborted!"); break; case HTTPRequestStates.ConnectionTimedOut: Debug.LogError("Connection Timed Out!"); error?.Invoke((int)HTTPRequestStates.ConnectionTimedOut, "Connection Timed Out!"); break; case HTTPRequestStates.TimedOut: Debug.LogError("Processing the request Timed Out!"); error?.Invoke((int)HTTPRequestStates.TimedOut, "Processing the request Timed Out!"); break; } }); request.SetHeader("Authorization", "Bearer " + identityInfo.token); request.Send(); } /// /// 删除json数据 /// /// 提交数据类型 /// API URL /// 提交数据 /// 请求成功回调 /// 请求错误回调 public void DeleteJson(string apiUrl, TIn data, Action success, Action error = null) { if (string.IsNullOrEmpty(apiUrl)) { throw new ArgumentNullException($"API URL:{apiUrl}无效!"); } HTTPRequest request = new HTTPRequest(new Uri(baseHttpUrl + apiUrl), HTTPMethods.Delete, (req, resp) => { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { success?.Invoke(); } else { Debug.LogWarning(string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); error?.Invoke(resp.StatusCode, JsonConvert.DeserializeObject(resp.DataAsText).detail); } break; case HTTPRequestStates.Error: Debug.LogError("Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception")); error?.Invoke((int)HTTPRequestStates.Error, "Request Finished with Error!"); break; case HTTPRequestStates.Aborted: Debug.LogWarning("Request Aborted!"); error?.Invoke((int)HTTPRequestStates.Aborted, "Request Aborted!"); break; case HTTPRequestStates.ConnectionTimedOut: Debug.LogError("Connection Timed Out!"); error?.Invoke((int)HTTPRequestStates.ConnectionTimedOut, "Connection Timed Out!"); break; case HTTPRequestStates.TimedOut: Debug.LogError("Processing the request Timed Out!"); error?.Invoke((int)HTTPRequestStates.TimedOut, "Processing the request Timed Out!"); break; } }); request.SetHeader("Authorization", "Bearer " + identityInfo.token); request.SetHeader("Content-Type", "application/json; charset=UTF-8"); string json = JsonConvert.SerializeObject(data); request.RawData = Encoding.UTF8.GetBytes(json); request.Send(); } /// /// 文件下载:分块下载,支持下载的断点续传 /// /// API URL /// 是否缓存到本地 /// 服务器文件是否变动 /// 下载完成回调 /// 下载进度回调 /// 下载错误回调 /// 本地缓存路径:包含文件名、扩展名的相对路径 /// 内存中缓存文件字节流的数组 /// /// 传出HTTPRequest实例的回调:方便上层缓存请求来中止请求(request.Abort()) public void FileDownload(string apiUrl, bool isCache, bool isFileChange, Action complete, Action progress = null, Action error = null, string localPath = null, List fileBytes = null, Action reqCallBack = null) { if (string.IsNullOrEmpty(apiUrl)) throw new ArgumentNullException($"API URL:{apiUrl}无效!"); if (isCache) { if (string.IsNullOrEmpty(localPath)) { throw new ArgumentNullException($"本地缓存路径:{localPath}无效!"); } else { try { string fileName = Path.GetFileName(localPath); if (string.IsNullOrEmpty(fileName)) { if (fileName == null) throw new ArgumentException($"本地缓存路径:{localPath}无效!"); if (fileName == "") throw new ArgumentException($"本地缓存路径:{localPath}无效!"); } } catch (Exception e) { throw new ArgumentException("本地缓存路径无效!", "localPath", e); } } } else { if (fileBytes == null) { throw new ArgumentNullException("不缓存本地时,必须传入fileBytes参数!"); } } string tempFilePath = ""; if (isCache) tempFilePath = $"{localPath}.temp"; long downloadFileTotalLength = -1; long downloadStartedAt = 0; long downloadedLength = 0; HTTPRequest downloadRequest = new HTTPRequest(new Uri(baseHttpUrl + apiUrl), HTTPMethods.Get, (req, resp) => { var fs = req.Tag as FileStream; switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { if ((downloadStartedAt + downloadedLength) >= downloadFileTotalLength) { if (fs != null) { fs.Close(); fs.Dispose(); } if (isCache) { if (File.Exists(localPath)) File.Delete(localPath); File.Move(tempFilePath, localPath); } complete?.Invoke(); } else { req.SetRangeHeader(downloadStartedAt + downloadedLength, downloadStartedAt + downloadedLength + fragmentSize); req.Send(); } } else { Debug.LogWarning(string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); error?.Invoke(resp.StatusCode, string.Format("Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); if (fs != null) { fs.Close(); fs.Dispose(); } downloadRequest = null; } break; case HTTPRequestStates.Error: Debug.LogError("Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception")); error?.Invoke((int)HTTPRequestStates.Error, "Request Finished with Error!"); if (fs != null) { fs.Close(); fs.Dispose(); } downloadRequest = null; break; case HTTPRequestStates.Aborted: Debug.LogWarning("Request Aborted!"); if (fs != null) { fs.Close(); fs.Dispose(); } downloadRequest = null; break; case HTTPRequestStates.ConnectionTimedOut: Debug.LogError("Connection Timed Out!"); error?.Invoke((int)HTTPRequestStates.ConnectionTimedOut, "Connection Timed Out!"); if (fs != null) { fs.Close(); fs.Dispose(); } downloadRequest = null; break; case HTTPRequestStates.TimedOut: Debug.LogError("Processing the request Timed Out!"); error?.Invoke((int)HTTPRequestStates.TimedOut, "Processing the request Timed Out!"); if (fs != null) { fs.Close(); fs.Dispose(); } downloadRequest = null; break; default: break; } downloadRequest = null; }); reqCallBack?.Invoke(downloadRequest); downloadRequest.OnHeadersReceived += (req, resp, headers) => { var range = resp.GetRange(); if (range != null) downloadFileTotalLength = range.ContentLength; if (!isCache && fileBytes.Count == 0) { fileBytes.Capacity = (int)downloadFileTotalLength; } }; if (isCache) { if (!isFileChange) { if (File.Exists(tempFilePath)) { FileInfo fileInfo = new FileInfo(tempFilePath); downloadStartedAt = fileInfo.Length; fileInfo = null; } else {//下载了部分需要继续断点下载的情况下,下载了部分的本地临时文件被误删,则需要从新下载整个文件 downloadStartedAt = 0; } } else { downloadStartedAt = 0; if (File.Exists(tempFilePath)) { File.Delete(tempFilePath); } } } else { downloadStartedAt = fileBytes.Count; } downloadRequest.OnDownloadProgress += (req, downloaded, downloadLength) => { double downloadPercent = ((downloadStartedAt + downloadedLength + downloaded) / (double)downloadFileTotalLength); progress?.Invoke((float)downloadPercent); }; downloadRequest.OnStreamingData += (req, resp, dataFragment, dataFragmentLength) => { if (isCache) { if (resp.IsSuccess) { string directory = Path.GetDirectoryName(tempFilePath); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); var fs = req.Tag as FileStream; if (fs == null) req.Tag = fs = new FileStream(tempFilePath, FileMode.Append, FileAccess.Write, FileShare.Read); fs.Write(dataFragment, 0, dataFragmentLength); downloadedLength += dataFragmentLength; } } else { if (resp.IsSuccess) { fileBytes.AddRange(dataFragment.Skip(0).Take(dataFragmentLength).ToArray()); downloadedLength += dataFragmentLength; } } return true; }; downloadRequest.SetHeader("Authorization", "Bearer " + identityInfo.token); downloadRequest.SetRangeHeader(downloadStartedAt, downloadStartedAt + fragmentSize); downloadRequest.DisableCache = true; downloadRequest.StreamFragmentSize = fragmentSize; downloadRequest.Send(); } /// /// 文件上传:分块上传,支持上传的断点续传 /// /// API URL /// 服务端桶下的路径 /// 上传文件在本地的路径 /// 上传完成回调 /// 上传进度回调 /// 上传错误回调 /// 传出HTTPRequest实例的回调:方便上层缓存请求来中止请求(request.Abort()) public async UniTaskVoid FileUploadAsync(string apiUrl, string objectName, string filePath, Action complete, Action progress = null, Action error = null, Action reqCallBack = null) { if (string.IsNullOrEmpty(apiUrl)) throw new ArgumentNullException($"API URL:{apiUrl}无效!"); if (string.IsNullOrEmpty(objectName)) { throw new ArgumentNullException($"文件路径:{objectName}无效!"); } else { try { string fileName = Path.GetFileName(objectName); if (string.IsNullOrEmpty(fileName)) { if (fileName == null) throw new ArgumentException($"本地缓存路径:{objectName}无效!"); if (fileName == "") throw new ArgumentException($"本地缓存路径:{objectName}无效!"); } } catch (Exception e) { throw new ArgumentException("本地缓存路径无效!", "objectName", e); } } if (string.IsNullOrEmpty(filePath)) { throw new ArgumentNullException($"文件路径:{filePath}无效!"); } else { try { string fileName = Path.GetFileName(filePath); if (string.IsNullOrEmpty(fileName)) { if (fileName == null) throw new ArgumentException($"本地缓存路径:{filePath}无效!"); if (fileName == "") throw new ArgumentException($"本地缓存路径:{filePath}无效!"); } } catch (Exception e) { throw new ArgumentException("本地缓存路径无效!", "filePath", e); } } string md5; using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { md5 = HashAlgorithm.ComputeHash(fs); } string uploadFileName = Path.GetFileName(objectName); var savedMultipartUploadInfos = GetMultipartUploadInfos(objectName); NewMultipartUploadResult newMultipartUploadResult = new NewMultipartUploadResult(); MultipartUploadInfos multipartUploadInfos = new MultipartUploadInfos(); if (objectName == savedMultipartUploadInfos.newMultipartUploadResult.objectName && md5 == savedMultipartUploadInfos.md5) { newMultipartUploadResult = savedMultipartUploadInfos.newMultipartUploadResult; } else { string directoryName = Path.GetDirectoryName(objectName); string newMultipartUploadUrl = $"{newMultipartUploadApi}/{directoryName}?keepOriginalName={true}&fileName={uploadFileName}"; //初始化分块上传事件 newMultipartUploadResult = await NewMultipartUpload(newMultipartUploadUrl, error); multipartUploadInfos.newMultipartUploadResult = newMultipartUploadResult; multipartUploadInfos.md5 = md5; SaveMultipartUploadInfos(objectName, $"{JsonConvert.SerializeObject(multipartUploadInfos)}"); savedMultipartUploadInfos = GetMultipartUploadInfos(objectName); } //分块上传文件处理 using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { string multipartUploadUrl; List partInfos = new List(); double uploadPercent; //上传文件总长度 long uploadFileTotalSize = fileStream.Length; //上传文件剩余总长度 long uploadFileRemainTotalSize = fileStream.Length; //分块总数 int fragmentTotalCount = (int)Math.Ceiling((double)uploadFileTotalSize / fragmentSize); //当前上传分块数 int currentUploadFragmentCount = 0; //上传开始位置 long uploadStartAt = 0; //缓存分块读取的字节流数组 byte[] bytes = new byte[fragmentSize]; //实际读取长度 int bytesLength = 0; long uploadProcessedBytes = savedMultipartUploadInfos.uploadProcessedBytes; if (uploadProcessedBytes > 0) { uploadFileRemainTotalSize = uploadFileTotalSize - uploadProcessedBytes; currentUploadFragmentCount = (int)Math.Ceiling((double)uploadProcessedBytes / fragmentSize); uploadStartAt = uploadProcessedBytes; fileStream.Position = uploadStartAt; partInfos = savedMultipartUploadInfos.partInfos; } else DeleteKeys(objectName); while (currentUploadFragmentCount < fragmentTotalCount) { if (uploadFileRemainTotalSize < fragmentSize) { bytes = new byte[uploadFileRemainTotalSize]; bytesLength = fileStream.Read(bytes, 0, Convert.ToInt32(uploadFileRemainTotalSize)); } else { bytes = new byte[fragmentSize]; bytesLength = fileStream.Read(bytes, 0, fragmentSize); } multipartUploadUrl = $"{apiUrl}?uploadId={newMultipartUploadResult.uploadId}&partNumber={currentUploadFragmentCount + 1}"; //上传文件分块数据 PartInfo partInfo = await MultipartUpload(multipartUploadUrl, bytes, uploadFileName, error, reqCallBack); partInfos.Add(partInfo); uploadFileRemainTotalSize -= bytesLength; currentUploadFragmentCount++; uploadStartAt += bytesLength; fileStream.Position = uploadStartAt; multipartUploadInfos.uploadProcessedBytes = uploadStartAt; multipartUploadInfos.partInfos = partInfos; SaveMultipartUploadInfos(objectName, $"{JsonConvert.SerializeObject(multipartUploadInfos)}"); uploadPercent = uploadStartAt / (double)fileStream.Length; progress?.Invoke((float)uploadPercent); } //分块上传完成处理 if (currentUploadFragmentCount == fragmentTotalCount) { string completeMultipartUploadUrl = $"{completeMultipartUploadApi}/{objectName}?uploadId={newMultipartUploadResult.uploadId}"; await CompleteMultipartUpload(completeMultipartUploadUrl, partInfos, complete, error); DeleteKeys(objectName); } } } /// /// 初始化分块上传事件 /// /// /// /// private async UniTask NewMultipartUpload(string url, Action error) { HTTPRequest req = new HTTPRequest(new Uri(baseHttpUrl + url), HTTPMethods.Post); req.SetHeader("Authorization", "Bearer " + identityInfo.token); try { var json = await req.GetAsStringAsync(); NewMultipartUploadResult newMultipartUploadResult = JsonConvert.DeserializeObject(json); return newMultipartUploadResult; } catch (AsyncHTTPException ex) { if (ex.StatusCode != 0) { error?.Invoke(ex.StatusCode, string.Format("Status Code: {0}-{1} Message: {2}", ex.StatusCode, ex.Message, ex.Content)); } else { error?.Invoke((int)req.State, ex.Message); } throw ex; } } /// /// 上传文件分块数据 /// /// /// /// /// /// private async UniTask MultipartUpload(string url, byte[] bytes, string fileName, Action error, Action reqCallBack = null) { HTTPRequest req = new HTTPRequest(new Uri(baseHttpUrl + url), HTTPMethods.Post); req.SetHeader("Authorization", "Bearer " + identityInfo.token); req.AddBinaryData("file", bytes, fileName, "application/octet-stream"); reqCallBack?.Invoke(req); try { var json = await req.GetAsStringAsync(); PartInfo partInfo = JsonConvert.DeserializeObject(json); return partInfo; } catch (AsyncHTTPException ex) { if (ex.StatusCode != 0) { error?.Invoke(ex.StatusCode, string.Format("Status Code: {0}-{1} Message: {2}", ex.StatusCode, ex.Message, ex.Content)); } else { error?.Invoke((int)req.State, ex.Message); } throw ex; } } /// /// 完成分块上传事件 /// /// /// /// /// /// private async UniTask CompleteMultipartUpload(string url, List partInfos, Action complete, Action error) { HTTPRequest req = new HTTPRequest(new Uri(baseHttpUrl + url), HTTPMethods.Post); req.SetHeader("Authorization", "Bearer " + identityInfo.token); req.SetHeader("Content-Type", "application/json; charset=UTF-8"); List partNumberETags = new List(); foreach (var partInfo in partInfos) { partNumberETags.Add(new PartNumberETag() { partNumber = partInfo.partNumber, eTag = partInfo.eTag }); } string jsonData = JsonConvert.SerializeObject(partNumberETags); req.RawData = Encoding.UTF8.GetBytes(jsonData); try { await req.GetAsStringAsync(); complete?.Invoke(); } catch (AsyncHTTPException ex) { if (ex.StatusCode != 0) { error?.Invoke(ex.StatusCode, string.Format("Status Code: {0}-{1} Message: {2}", ex.StatusCode, ex.Message, ex.Content)); } else { error?.Invoke((int)req.State, ex.Message); } throw ex; } } /// /// 保存文件分块上传信息 /// /// /// private void SaveMultipartUploadInfos(string objectName, string str) { PlayerPrefs.SetString(objectName + multipartUploadInfosKey, str); } /// /// 获取保存的文件分块上传信息 /// /// /// private MultipartUploadInfos GetMultipartUploadInfos(string objectName) { string str = PlayerPrefs.GetString(objectName + multipartUploadInfosKey, ""); if (!string.IsNullOrEmpty(str)) { var multipartUploadInfos = JsonConvert.DeserializeObject(str); return multipartUploadInfos; } else { return new MultipartUploadInfos(); } } /// /// 删除保存的文件分块上传信息 /// /// private void DeleteKeys(string objectName) { PlayerPrefs.DeleteKey(objectName + multipartUploadInfosKey); PlayerPrefs.Save(); } /// /// 文件夹复制 /// /// /// /// public void FileDirectoryCopy(string apiUrl, Action complete, Action error = null) { if (string.IsNullOrEmpty(apiUrl)) throw new ArgumentNullException($"API URL:{apiUrl}无效!"); HTTPRequest directoryCopyRequest = new HTTPRequest(new Uri(baseHttpUrl + apiUrl), HTTPMethods.Put, (req, resp) => { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { ListObjectsResult result = JsonConvert.DeserializeObject(resp.DataAsText); complete?.Invoke(result); } else { Debug.LogWarning(string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}", resp.StatusCode, resp.Message, resp.DataAsText)); error?.Invoke(resp.StatusCode, JsonConvert.DeserializeObject(resp.DataAsText).detail); } break; case HTTPRequestStates.Error: Debug.LogError("Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception")); error?.Invoke((int)HTTPRequestStates.Error, "Request Finished with Error!"); break; case HTTPRequestStates.Aborted: Debug.LogWarning("Request Aborted!"); break; case HTTPRequestStates.ConnectionTimedOut: Debug.LogError("Connection Timed Out!"); error?.Invoke((int)HTTPRequestStates.ConnectionTimedOut, "Connection Timed Out!"); break; case HTTPRequestStates.TimedOut: Debug.LogError("Processing the request Timed Out!"); error?.Invoke((int)HTTPRequestStates.TimedOut, "Processing the request Timed Out!"); break; } }); directoryCopyRequest.SetHeader("Authorization", "Bearer " + identityInfo.token); directoryCopyRequest.Send(); } public string GetBaseHttpUrl() { return baseHttpUrl; } #region 刷新令牌 /// /// 启动刷新令牌计时器 /// public void StartTimer() { RefreshTokenInfo refreshTokenInfo = new RefreshTokenInfo(); refreshTokenInfo.Token = identityInfo.token; refreshTokenInfo.RefreshToken = identityInfo.refreshToken; StartCoroutine(Timer(refreshTokenInfo)); } /// /// 计时器:根据登录成功后,服务端返回的身份信息中的token过期时间(分钟),定时提前1分钟来刷新token /// /// 刷新令牌信息 /// private IEnumerator Timer(RefreshTokenInfo refreshTokenInfo) { while (true) { yield return new WaitForSeconds((float)(identityInfo.expires - 1) * 60); PostJson( refreshTokenApi, refreshTokenInfo, RefreshTokenSuccessed, RefreshTokenError ); } } private void RefreshTokenSuccessed(IdentityInfo identityInfo) { this.identityInfo = identityInfo; //如果处于管理员界面处理页面token刷新 //AdminManager.Instance.RefreshWebToke(); } private void RefreshTokenError(int responseCode, string errorMsg) { Debug.LogError($"刷新身份错误:{responseCode},{errorMsg}"); } #endregion }