fix: optimize large file chunk upload stability
This commit is contained in:
@@ -3911,8 +3911,6 @@ namespace Lskj.PubBomImport
|
|||||||
string suffix = Path.GetExtension(File);
|
string suffix = Path.GetExtension(File);
|
||||||
string name = fileInfo.Name;
|
string name = fileInfo.Name;
|
||||||
DateTime creationTime = fileInfo.CreationTime;
|
DateTime creationTime = fileInfo.CreationTime;
|
||||||
byte[] fileBytes = ConvertFileToBytes(File);
|
|
||||||
string fileStream = Convert.ToBase64String(fileBytes);
|
|
||||||
string userId = ERPInfo.Instance.UserId;
|
string userId = ERPInfo.Instance.UserId;
|
||||||
DateTime uploadTime = DateTime.Now;
|
DateTime uploadTime = DateTime.Now;
|
||||||
string code = "";
|
string code = "";
|
||||||
@@ -3925,7 +3923,7 @@ namespace Lskj.PubBomImport
|
|||||||
//上传文件到服务器目录
|
//上传文件到服务器目录
|
||||||
//postUrl = "{0}Api/FileUploadApi.ashx?moduleId={1}&idValue={2}&speciesno={8}&folder={3}&totsize={4}&position={5}&filename={6}&method={7}&confirm=1";
|
//postUrl = "{0}Api/FileUploadApi.ashx?moduleId={1}&idValue={2}&speciesno={8}&folder={3}&totsize={4}&position={5}&filename={6}&method={7}&confirm=1";
|
||||||
|
|
||||||
postUrl = "{0}Api/FileUploadApi.ashx?moduleId={1}&idValue={2}&speciesno={8}&folder={3}&totsize={4}&position={5}&filename={6}&method={7}&comfirm={9}&encode={10}&gzip=true&uploadToken={11}&__tsp={12}";
|
postUrl = "{0}Api/FileUploadApi.ashx?moduleId={1}&idValue={2}&speciesno={8}&folder={3}&totsize={4}&position={5}&filename={6}&uname=false&method={7}&comfirm={9}&relPath=&encode={10}&gzip=true&uploadToken={11}&__tsp={12}";
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(idValue))
|
if (!string.IsNullOrEmpty(idValue))
|
||||||
{
|
{
|
||||||
@@ -3937,24 +3935,36 @@ namespace Lskj.PubBomImport
|
|||||||
loginPmsDic.Add("username", System.Web.HttpUtility.UrlEncode(ERPInfo.Instance.UserName));
|
loginPmsDic.Add("username", System.Web.HttpUtility.UrlEncode(ERPInfo.Instance.UserName));
|
||||||
loginPmsDic.Add("password", System.Web.HttpUtility.UrlEncode(ERPInfo.Instance.InPassWord));
|
loginPmsDic.Add("password", System.Web.HttpUtility.UrlEncode(ERPInfo.Instance.InPassWord));
|
||||||
HttpWebResponse loginResponse = HttpHelper.Post(loginUrl, "", loginPmsDic, null, out string loginResult);
|
HttpWebResponse loginResponse = HttpHelper.Post(loginUrl, "", loginPmsDic, null, out string loginResult);
|
||||||
if (loginResponse != null && !string.IsNullOrEmpty(loginResult))
|
try
|
||||||
{
|
{
|
||||||
JObject jObject = JsonConvert.DeserializeObject<JObject>(loginResult);
|
if (loginResponse != null && !string.IsNullOrEmpty(loginResult))
|
||||||
if (jObject.ContainsKey("success"))
|
|
||||||
{
|
{
|
||||||
if ((jObject["success"] + "").Equals("True"))
|
JObject jObject = JsonConvert.DeserializeObject<JObject>(loginResult);
|
||||||
|
if (jObject.ContainsKey("success"))
|
||||||
{
|
{
|
||||||
if (jObject.ContainsKey("token"))
|
if ((jObject["success"] + "").Equals("True"))
|
||||||
{
|
{
|
||||||
token = jObject["token"] + "";
|
if (jObject.ContainsKey("token"))
|
||||||
|
{
|
||||||
|
token = jObject["token"] + "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
loginResponse?.Close();
|
||||||
|
}
|
||||||
if (!string.IsNullOrEmpty(token))
|
if (!string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
int pageSize = 1 * 1024 * 1024;
|
|
||||||
long totalSize = fileInfo.Length;
|
long totalSize = fileInfo.Length;
|
||||||
|
// Web 前端上传器同样提示:经过防火墙的环境需要将分片
|
||||||
|
// 从 1MB 降到约 0.3MB。仅大文件启用安全分片,避免影响
|
||||||
|
// 现有小文件上传速度。
|
||||||
|
int pageSize = totalSize > 4L * 1024 * 1024
|
||||||
|
? 320 * 1024
|
||||||
|
: 1 * 1024 * 1024;
|
||||||
long position = 0;
|
long position = 0;
|
||||||
int confirm = 1;
|
int confirm = 1;
|
||||||
int encode = 4;
|
int encode = 4;
|
||||||
@@ -3963,7 +3973,7 @@ namespace Lskj.PubBomImport
|
|||||||
|
|
||||||
string uploadToken = Guid.NewGuid().ToString("N").Substring(0, 24);
|
string uploadToken = Guid.NewGuid().ToString("N").Substring(0, 24);
|
||||||
string uploadFileName = name;
|
string uploadFileName = name;
|
||||||
string uploadMethod = "DoWebUpload";
|
string uploadMethod = "DoWebUploadSafe";
|
||||||
|
|
||||||
Dictionary<string, string> headerDic = new Dictionary<string, string>();
|
Dictionary<string, string> headerDic = new Dictionary<string, string>();
|
||||||
headerDic.Add("Authorization", $"Bearer {token}");
|
headerDic.Add("Authorization", $"Bearer {token}");
|
||||||
@@ -3993,7 +4003,7 @@ namespace Lskj.PubBomImport
|
|||||||
string uploadUrl = string.Format(
|
string uploadUrl = string.Format(
|
||||||
postUrl,
|
postUrl,
|
||||||
SystemInfo.Instance.OAUrl,
|
SystemInfo.Instance.OAUrl,
|
||||||
"1000202",
|
"1000202-1",
|
||||||
System.Web.HttpUtility.UrlEncode(idValue),
|
System.Web.HttpUtility.UrlEncode(idValue),
|
||||||
"file",
|
"file",
|
||||||
totalSize,
|
totalSize,
|
||||||
@@ -4009,8 +4019,10 @@ namespace Lskj.PubBomImport
|
|||||||
|
|
||||||
string result = "";
|
string result = "";
|
||||||
HttpWebResponse webResponse = HttpHelper.PostFileChunk(uploadUrl, buffer, uploadFileName, headerDic, out result);
|
HttpWebResponse webResponse = HttpHelper.PostFileChunk(uploadUrl, buffer, uploadFileName, headerDic, out result);
|
||||||
|
bool hasResponse = webResponse != null;
|
||||||
|
webResponse?.Close();
|
||||||
|
|
||||||
if (webResponse != null && !string.IsNullOrEmpty(result))
|
if (hasResponse && !string.IsNullOrEmpty(result))
|
||||||
{
|
{
|
||||||
JObject jsonObject = (JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(result);
|
JObject jsonObject = (JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(result);
|
||||||
string isSuccess = jsonObject["success"] + "";
|
string isSuccess = jsonObject["success"] + "";
|
||||||
@@ -4079,7 +4091,9 @@ namespace Lskj.PubBomImport
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
returnMsg = $"上传请求失败,{result}";
|
long chunkIndex = position / pageSize + 1;
|
||||||
|
long chunkCount = (totalSize + pageSize - 1) / pageSize;
|
||||||
|
returnMsg = $"上传请求失败(分片{chunkIndex}/{chunkCount},位置{position},分片大小{pageSize / 1024}KB),{result}";
|
||||||
hasFail = true;
|
hasFail = true;
|
||||||
isUpload = false;
|
isUpload = false;
|
||||||
break;
|
break;
|
||||||
|
|||||||
+106
-54
@@ -14,6 +14,7 @@ namespace Lskj.Util
|
|||||||
{
|
{
|
||||||
private const int RequestTimeout = 120000;
|
private const int RequestTimeout = 120000;
|
||||||
private const int Tls12 = 3072;
|
private const int Tls12 = 3072;
|
||||||
|
private const int UploadChunkMaxAttempts = 3;
|
||||||
|
|
||||||
public enum Encode
|
public enum Encode
|
||||||
{
|
{
|
||||||
@@ -101,6 +102,28 @@ namespace Lskj.Util
|
|||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsTransientUploadException(WebException ex)
|
||||||
|
{
|
||||||
|
if (ex == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ex.Status)
|
||||||
|
{
|
||||||
|
case WebExceptionStatus.ConnectFailure:
|
||||||
|
case WebExceptionStatus.ConnectionClosed:
|
||||||
|
case WebExceptionStatus.KeepAliveFailure:
|
||||||
|
case WebExceptionStatus.PipelineFailure:
|
||||||
|
case WebExceptionStatus.ReceiveFailure:
|
||||||
|
case WebExceptionStatus.SendFailure:
|
||||||
|
case WebExceptionStatus.Timeout:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// post数据到指定的网址,获取cookie数据,和返回页
|
/// post数据到指定的网址,获取cookie数据,和返回页
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -270,70 +293,99 @@ namespace Lskj.Util
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static HttpWebResponse PostFileChunk(string url, byte[] fileBytes, string fileName, Dictionary<string, string> headerDic, out string result)
|
public static HttpWebResponse PostFileChunk(string url, byte[] fileBytes, string fileName, Dictionary<string, string> headerDic, out string result)
|
||||||
{
|
{
|
||||||
HttpWebResponse httpWebResponse = null;
|
result = "";
|
||||||
try
|
for (int attempt = 1; attempt <= UploadChunkMaxAttempts; attempt++)
|
||||||
{
|
{
|
||||||
string boundary = "----WebKitFormBoundary" + DateTime.Now.Ticks.ToString("x");
|
HttpWebRequest httpWebRequest = null;
|
||||||
|
HttpWebResponse httpWebResponse = null;
|
||||||
ConfigureTransport();
|
try
|
||||||
HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url);
|
|
||||||
ConfigureRequest(httpWebRequest);
|
|
||||||
httpWebRequest.Method = "POST";
|
|
||||||
httpWebRequest.Accept = Accept;
|
|
||||||
httpWebRequest.UserAgent = UserAgent;
|
|
||||||
httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary;
|
|
||||||
|
|
||||||
if (headerDic != null)
|
|
||||||
{
|
{
|
||||||
foreach (var item in headerDic)
|
string boundary = "----WebKitFormBoundary" + DateTime.Now.Ticks.ToString("x");
|
||||||
|
|
||||||
|
ConfigureTransport();
|
||||||
|
httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url);
|
||||||
|
ConfigureRequest(httpWebRequest);
|
||||||
|
// 客户网络中的代理/防火墙可能在复用连接时主动断开。
|
||||||
|
// 分片请求使用独立连接,避免 ERP 进程内其他 HTTP 请求
|
||||||
|
// 留下的连接状态影响当前上传。
|
||||||
|
httpWebRequest.KeepAlive = false;
|
||||||
|
httpWebRequest.ConnectionGroupName = "LserpUpload-"
|
||||||
|
+ Guid.NewGuid().ToString("N");
|
||||||
|
httpWebRequest.ProtocolVersion = HttpVersion.Version11;
|
||||||
|
httpWebRequest.Method = "POST";
|
||||||
|
httpWebRequest.Accept = Accept;
|
||||||
|
httpWebRequest.UserAgent = string.IsNullOrEmpty(UserAgent)
|
||||||
|
? "Lserp-Desktop/6.0"
|
||||||
|
: UserAgent;
|
||||||
|
httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary;
|
||||||
|
|
||||||
|
if (headerDic != null)
|
||||||
{
|
{
|
||||||
if (!item.Key.Equals("Content-Type"))
|
foreach (var item in headerDic)
|
||||||
{
|
{
|
||||||
httpWebRequest.Headers.Add(item.Key, item.Value);
|
if (!item.Key.Equals("Content-Type"))
|
||||||
|
{
|
||||||
|
httpWebRequest.Headers.Add(item.Key, item.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string header =
|
||||||
|
"--" + boundary + "\r\n" +
|
||||||
|
"Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n" +
|
||||||
|
"Content-Type: application/octet-stream\r\n\r\n";
|
||||||
|
|
||||||
|
string footer = "\r\n--" + boundary + "--\r\n";
|
||||||
|
byte[] headerBytes = Encoding.UTF8.GetBytes(header);
|
||||||
|
byte[] footerBytes = Encoding.UTF8.GetBytes(footer);
|
||||||
|
|
||||||
|
httpWebRequest.ContentLength = headerBytes.Length + fileBytes.Length + footerBytes.Length;
|
||||||
|
|
||||||
|
using (Stream stream = httpWebRequest.GetRequestStream())
|
||||||
|
{
|
||||||
|
stream.Write(headerBytes, 0, headerBytes.Length);
|
||||||
|
stream.Write(fileBytes, 0, fileBytes.Length);
|
||||||
|
stream.Write(footerBytes, 0, footerBytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
|
||||||
|
|
||||||
|
using (Stream responseStream = httpWebResponse.GetResponseStream())
|
||||||
|
using (StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8))
|
||||||
|
{
|
||||||
|
result = streamReader.ReadToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpWebResponse;
|
||||||
}
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex.ToString());
|
||||||
|
string exceptionMessage = BuildWebExceptionMessage(ex);
|
||||||
|
httpWebResponse?.Close();
|
||||||
|
ex.Response?.Close();
|
||||||
|
httpWebRequest?.Abort();
|
||||||
|
|
||||||
string header =
|
if (attempt < UploadChunkMaxAttempts && IsTransientUploadException(ex))
|
||||||
"--" + boundary + "\r\n" +
|
{
|
||||||
"Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n" +
|
System.Threading.Thread.Sleep(attempt * 1000);
|
||||||
"Content-Type: application/octet-stream\r\n\r\n";
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
string footer = "\r\n--" + boundary + "--\r\n";
|
result = string.Format("{0}; Attempts={1}", exceptionMessage, attempt);
|
||||||
|
return null;
|
||||||
byte[] headerBytes = Encoding.UTF8.GetBytes(header);
|
}
|
||||||
byte[] footerBytes = Encoding.UTF8.GetBytes(footer);
|
catch (Exception ex)
|
||||||
|
{
|
||||||
httpWebRequest.ContentLength = headerBytes.Length + fileBytes.Length + footerBytes.Length;
|
Console.WriteLine(ex.ToString());
|
||||||
|
httpWebResponse?.Close();
|
||||||
Stream stream = httpWebRequest.GetRequestStream();
|
httpWebRequest?.Abort();
|
||||||
stream.Write(headerBytes, 0, headerBytes.Length);
|
result = BuildExceptionMessage(ex);
|
||||||
stream.Write(fileBytes, 0, fileBytes.Length);
|
return null;
|
||||||
stream.Write(footerBytes, 0, footerBytes.Length);
|
}
|
||||||
stream.Close();
|
|
||||||
|
|
||||||
httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
|
|
||||||
|
|
||||||
Stream responseStream = httpWebResponse.GetResponseStream();
|
|
||||||
StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8);
|
|
||||||
result = streamReader.ReadToEnd();
|
|
||||||
streamReader.Close();
|
|
||||||
responseStream.Close();
|
|
||||||
|
|
||||||
return httpWebResponse;
|
|
||||||
}
|
|
||||||
catch (WebException ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex.ToString());
|
|
||||||
result = BuildWebExceptionMessage(ex);
|
|
||||||
return httpWebResponse;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex.ToString());
|
|
||||||
result = BuildExceptionMessage(ex);
|
|
||||||
return httpWebResponse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user