fix: optimize large file chunk upload stability

This commit is contained in:
2026-08-06 18:02:11 +08:00
parent 031fc34f12
commit 219caba818
2 changed files with 134 additions and 68 deletions
+22 -8
View File
@@ -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,6 +3935,8 @@ 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);
try
{
if (loginResponse != null && !string.IsNullOrEmpty(loginResult)) if (loginResponse != null && !string.IsNullOrEmpty(loginResult))
{ {
JObject jObject = JsonConvert.DeserializeObject<JObject>(loginResult); JObject jObject = JsonConvert.DeserializeObject<JObject>(loginResult);
@@ -3951,10 +3951,20 @@ namespace Lskj.PubBomImport
} }
} }
} }
}
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;
+64 -12
View File
@@ -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,17 +293,30 @@ 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)
{ {
result = "";
for (int attempt = 1; attempt <= UploadChunkMaxAttempts; attempt++)
{
HttpWebRequest httpWebRequest = null;
HttpWebResponse httpWebResponse = null; HttpWebResponse httpWebResponse = null;
try try
{ {
string boundary = "----WebKitFormBoundary" + DateTime.Now.Ticks.ToString("x"); string boundary = "----WebKitFormBoundary" + DateTime.Now.Ticks.ToString("x");
ConfigureTransport(); ConfigureTransport();
HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url); httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url);
ConfigureRequest(httpWebRequest); ConfigureRequest(httpWebRequest);
// 客户网络中的代理/防火墙可能在复用连接时主动断开。
// 分片请求使用独立连接,避免 ERP 进程内其他 HTTP 请求
// 留下的连接状态影响当前上传。
httpWebRequest.KeepAlive = false;
httpWebRequest.ConnectionGroupName = "LserpUpload-"
+ Guid.NewGuid().ToString("N");
httpWebRequest.ProtocolVersion = HttpVersion.Version11;
httpWebRequest.Method = "POST"; httpWebRequest.Method = "POST";
httpWebRequest.Accept = Accept; httpWebRequest.Accept = Accept;
httpWebRequest.UserAgent = UserAgent; httpWebRequest.UserAgent = string.IsNullOrEmpty(UserAgent)
? "Lserp-Desktop/6.0"
: UserAgent;
httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary; httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary;
if (headerDic != null) if (headerDic != null)
@@ -300,42 +336,58 @@ namespace Lskj.Util
"Content-Type: application/octet-stream\r\n\r\n"; "Content-Type: application/octet-stream\r\n\r\n";
string footer = "\r\n--" + boundary + "--\r\n"; string footer = "\r\n--" + boundary + "--\r\n";
byte[] headerBytes = Encoding.UTF8.GetBytes(header); byte[] headerBytes = Encoding.UTF8.GetBytes(header);
byte[] footerBytes = Encoding.UTF8.GetBytes(footer); byte[] footerBytes = Encoding.UTF8.GetBytes(footer);
httpWebRequest.ContentLength = headerBytes.Length + fileBytes.Length + footerBytes.Length; httpWebRequest.ContentLength = headerBytes.Length + fileBytes.Length + footerBytes.Length;
Stream stream = httpWebRequest.GetRequestStream(); using (Stream stream = httpWebRequest.GetRequestStream())
{
stream.Write(headerBytes, 0, headerBytes.Length); stream.Write(headerBytes, 0, headerBytes.Length);
stream.Write(fileBytes, 0, fileBytes.Length); stream.Write(fileBytes, 0, fileBytes.Length);
stream.Write(footerBytes, 0, footerBytes.Length); stream.Write(footerBytes, 0, footerBytes.Length);
stream.Close(); }
httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse(); httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
Stream responseStream = httpWebResponse.GetResponseStream(); using (Stream responseStream = httpWebResponse.GetResponseStream())
StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8); using (StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8))
{
result = streamReader.ReadToEnd(); result = streamReader.ReadToEnd();
streamReader.Close(); }
responseStream.Close();
return httpWebResponse; return httpWebResponse;
} }
catch (WebException ex) catch (WebException ex)
{ {
Console.WriteLine(ex.ToString()); Console.WriteLine(ex.ToString());
result = BuildWebExceptionMessage(ex); string exceptionMessage = BuildWebExceptionMessage(ex);
return httpWebResponse; httpWebResponse?.Close();
ex.Response?.Close();
httpWebRequest?.Abort();
if (attempt < UploadChunkMaxAttempts && IsTransientUploadException(ex))
{
System.Threading.Thread.Sleep(attempt * 1000);
continue;
}
result = string.Format("{0}; Attempts={1}", exceptionMessage, attempt);
return null;
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex.ToString()); Console.WriteLine(ex.ToString());
httpWebResponse?.Close();
httpWebRequest?.Abort();
result = BuildExceptionMessage(ex); result = BuildExceptionMessage(ex);
return httpWebResponse; return null;
} }
} }
return null;
}
/// <summary> /// <summary>
/// url转换 /// url转换
/// </summary> /// </summary>