以下内容展示的是文件分片上传
1.接口项目:
1.1 控制器
[Route("api/[controller]/[action]")]
[ApiController]
public class UploadFilesController : ControllerBase
{
[HttpPost]
[DisableRequestSizeLimit]
public async Task<UploadResponse> Upload()
{
var response = new UploadResponse()
{
code = (int)HttpStatusCode.InternalServerError,
msg = ""
};
//判断请求是否为Multipart-FormData类型
if (!FormDataHelper.IsMultipartContentType(Request.ContentType))
{
response = new UploadResponse()
{
code = (int)HttpStatusCode.UnsupportedMediaType,
msg = "不支持的请求类型"
};
}
else
{
try
{
//配置:可用的磁盘
string saveDisk = AppSettingsUtils.UploadFilesConfigs.ContainsKey("SaveDisk") ? AppSettingsUtils.UploadFilesConfigs["SaveDisk"] : string.Empty;
//配置:基础地址,用于拼接返回的url
string IPHostBase = AppSettingsUtils.UploadFilesConfigs.ContainsKey("IPHostBase") ? AppSettingsUtils.UploadFilesConfigs["IPHostBase"] : string.Empty;
//配置:可用磁盘所需最小空间要求
string diskThreshold = AppSettingsUtils.UploadFilesConfigs.ContainsKey("DiskThreshold") ? AppSettingsUtils.UploadFilesConfigs["DiskThreshold"] : string.Empty;
//配置:基础文件路径
string dirBase = AppSettingsUtils.UploadFilesConfigs.ContainsKey("DirBase") ? AppSettingsUtils.UploadFilesConfigs["DirBase"] : string.Empty;
if (string.IsNullOrEmpty(saveDisk) || string.IsNullOrEmpty(diskThreshold) || string.IsNullOrEmpty(IPHostBase) || string.IsNullOrEmpty(dirBase))
{
response = new UploadResponse()
{
code = (int)HttpStatusCode.BadGateway,
msg = "接口未配置相关信息"
};
}
else
{
//磁盘空间检查
double diskLastLength = float.Parse(diskThreshold) * 1024 * 1024 * 1024;
string freeDisk = string.Empty;
DiskInfoDto diskObj = DiskUtils.DiskCurrentStatus();
diskObj.DiskList.ForEach(o =>
{
if (saveDisk.Contains(o.DiskName.Substring(0, 1)) && float.Parse(o.DiskTotalFreeSize) > diskLastLength)
{
freeDisk = o.DiskName;
}
});
if (string.IsNullOrEmpty(freeDisk))
{
response = new UploadResponse()
{
code = (int)HttpStatusCode.BadGateway,
msg = "服务器磁盘空间不足"
};
}
else
{
//设置文件目录与缓存目录
string dirPath = Path.Combine(freeDisk, dirBase);
if (!Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
string cacheDir = Path.Combine(dirPath, "FileCache");
if (!Directory.Exists(cacheDir))
{
Directory.CreateDirectory(cacheDir);
}
//获取文件参数
StringValues fileName = new StringValues();
Request.Form.TryGetValue("FileName", out fileName);
StringValues partNumber = new StringValues();
Request.Form.TryGetValue("PartNumber", out partNumber);
StringValues chunks = new StringValues();
Request.Form.TryGetValue("Chunks", out chunks);
// 文件分片名
var filePartName = fileName + ".partNumber-" + partNumber;
//开始读取请求里的文件并写到缓存目录
var file = Request.Form.Files.GetFile("file");
var path = Path.Combine(cacheDir, filePartName);
using (var stream = new FileStream(path, FileMode.Append))
{
file.CopyTo(stream);
}
//如果是最后分片,执行合并文件(可能涉及转码等)
if (partNumber == chunks)
{
await FormDataHelper.MergeChunkFile(fileName, dirPath);
response = new UploadResponse()
{
code = (int)HttpStatusCode.OK,
msg = "上传成功"
};
response.data = new UploadInner()
{
localPath = Path.Combine(dirPath, fileName),
url = IPHostBase + "/" + dirBase + "/" + fileName
};
}
else
{
response = new UploadResponse()
{
code = (int)HttpStatusCode.Accepted,
msg = "已接收分片文件" + partNumber
};
}
}
}
}
catch (Exception ex)
{
response = new UploadResponse()
{
code = (int)HttpStatusCode.InternalServerError,
msg = "内部错误:" + ex.Message
};
}
}
return response;
}
}
1.2 AppSettingsUtils.cs(如何加载appsettings.json参考我的另一篇博文)
public class AppSettingsUtils
{
private static IConfiguration configuration = new ConfigurationBuilder().SetBasePath(Environment.CurrentDirectory).AddJsonFile("appsettings.json").Build();
private static Dictionary<string, string> _UploadFilesConfigs = null;
public static Dictionary<string, string> UploadFilesConfigs
{
get
{
if (_UploadFilesConfigs == null)
{
_UploadFilesConfigs = new Dictionary<string, string>();
_UploadFilesConfigs.Add("IPHostBase", configuration.GetSection("UploadFilesConfigs:IPHostBase").Value);
_UploadFilesConfigs.Add("SaveDisk", configuration.GetSection("UploadFilesConfigs:SaveDisk").Value);
_UploadFilesConfigs.Add("DiskThreshold", configuration.GetSection("UploadFilesConfigs:DiskThreshold").Value);
_UploadFilesConfigs.Add("DirBase", configuration.GetSection("UploadFilesConfigs:DirBase").Value);
}
return _UploadFilesConfigs;
}
}
}
1.3 DiskUtils.cs
public class DiskUtils
{
/// <summary>
/// 获取硬盘信息
/// </summary>
/// <returns></returns>
private static List<DiskInfo> GetDiskInfo()
{
var list = new List<DiskInfo>();
//获取计算机上的所有逻辑驱动器信息
var allDrivers = DriveInfo.GetDrives();
foreach (var info in allDrivers)
{
if (!info.IsReady)//磁盘是否准备就绪
continue;
list.Add(new DiskInfo() { DiskName = info.Name, DiskType = info.DriveType.ToString(), DiskTotalSize = info.TotalSize.ToString(), DiskTotalFreeSize = info.TotalFreeSpace.ToString(), DiskFormat = info.DriveFormat });
}
return list;
}
public static DiskInfoDto DiskCurrentStatus()
{
var diskInfo = new DiskInfoDto();
var diskListInfo = GetDiskInfo();
diskInfo.DiskName = string.Join(",", diskListInfo.Select(d => d.DiskName.Replace(@"\", "")));
diskInfo.DiskTotalSize = Math.Round(diskListInfo.Sum(d => float.Parse(d.DiskTotalSize)) / 1024 / 1024 / 1024.0, 2);
diskInfo.DiskAvailableSize = Math.Round(diskListInfo.Sum(d => float.Parse(d.DiskTotalFreeSize)) / 1024 / 1024 / 1024.0, 2);
diskInfo.ResidualRate = Math.Round(diskInfo.DiskAvailableSize / diskInfo.DiskTotalSize, 2) * 100;
diskInfo.DiskList = diskListInfo;
return diskInfo;
}
}
public class DiskInfoDto
{
/// <summary>
/// 磁盘名称
/// </summary>
public string DiskName { get; set; }
/// <summary>
/// 磁盘大小
/// </summary>
public double DiskTotalSize { get; set; }
/// <summary>
/// 剩余空间大小
/// </summary>
public double DiskAvailableSize { get; set; }
/// <summary>
/// 空闲百分比
/// </summary>
public double ResidualRate { get; set; }
public List<DiskInfo> DiskList { get; set; }
}
public class DiskInfo
{
/// <summary>
/// 磁盘盘符名称
/// </summary>
public string DiskName { get; set; }
/// <summary>
/// 磁盘类型
/// </summary>
public string DiskType { get; set; }
/// <summary>
/// 磁盘文件系统格式
/// </summary>
public string DiskFormat { get; set; }
/// <summary>
/// 磁盘空间大小
/// </summary>
public string DiskTotalSize { get; set; }
/// <summary>
/// 可用空间大小
/// </summary>
public string DiskTotalFreeSize { get; set; }
}
1.4 FormDataHelper.cs
public class FormDataHelper
{
public static bool IsMultipartContentType(string contentType)
{
return
!string.IsNullOrEmpty(contentType) &&
contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static async Task MergeChunkFile(string fileName, string targetPath)
{
//文件上传目录名
var uploadDirectoryName = Path.Combine(targetPath, "FileCache");
//分片文件命名约定
var partToken = FileSort.PART_NUMBER;
//上传文件实际名称
var baseFileName = fileName;
//根据命名约定查询指定目录下符合条件的所有分片文件
var searchpattern = $"{Path.GetFileName(baseFileName)}{partToken}*";
//获取所有分片文件列表
var filesList = Directory.GetFiles(uploadDirectoryName, searchpattern);
if (!filesList.Any()) { return; }
var mergeFiles = new List<FileSort>();
foreach (string file in filesList)
{
var fileSize = new FileInfo(file).Length;
var sort = new FileSort
{
FileName = file
};
var fileIndex = file.Substring(file.IndexOf(partToken) + partToken.Length);
int.TryParse(fileIndex, out var number);
if (number <= 0) { continue; }
sort.PartNumber = number;
mergeFiles.Add(sort);
}
// 按照分片排序
var mergeOrders = mergeFiles.OrderBy(s => s.PartNumber).ToList();
// 合并文件
using var fileStream = new FileStream(Path.Combine(targetPath, baseFileName), FileMode.Create);
foreach (var fileSort in mergeOrders)
{
using FileStream fileChunk = new FileStream(fileSort.FileName, FileMode.Open);
await fileChunk.CopyToAsync(fileStream);
}
//删除分片文件
DeleteFile(mergeFiles);
}
private static void DeleteFile(List<FileSort> files)
{
foreach (var file in files)
{
try
{
System.IO.File.Delete(file.FileName);
}
catch { }
}
}
}
public class FileSort
{
public const string PART_NUMBER = ".partNumber-";
/// <summary>
/// 文件名
/// </summary>
public string FileName { get; set; }
/// <summary>
/// 文件分片号
/// </summary>
public int PartNumber { get; set; }
}
1.5 appsetting.json
{
"UploadFilesConfigs": {
"IPHostBase": "http://xx.xx.xx.xx:端口号",
"SaveDisk": "C|D|E|F",
"DiskThreshold": "20",//单位GB
"DirBase": "TestDir"
}
}
2.客户端调用:
2.1 方法
public static bool UploadFile(string url, string filePath, ref string networkPath, ref string localPath, ref string msg)
{
using var httpClient = new HttpClient();
using var fileStream = File.OpenRead(filePath);
// 文件名
string fileName = Path.GetFileName(filePath);
var fileChunks = new List<FileChunk>();
// 设置分片缓冲区大小KB
var maxFileSizeMB = 300;
var bufferChunkSize = maxFileSizeMB * 1024;
// 文件大小
var total = fileStream.Length;
int partNumber = 1;
long readed = 0;// 已读大小
// 文件分片
while (readed < total)
{
if (total - readed < bufferChunkSize)
{
int last = (int)(total - readed);
FileChunk chunk = new FileChunk()
{
FileName = fileName,
PartNumber = partNumber,
PartSize = last,
};
chunk.PartFileBytes = new byte[(int)(last)];
int readbytes = fileStream.Read(chunk.PartFileBytes, 0, last);
fileChunks.Add(chunk);
readed += readbytes;
}
else
{
FileChunk chunk = new FileChunk()
{
FileName = fileName,
PartNumber = partNumber,
PartSize = bufferChunkSize,
};
chunk.PartFileBytes = new byte[bufferChunkSize];
int readbytes = fileStream.Read(chunk.PartFileBytes, 0, bufferChunkSize);
fileChunks.Add(chunk);
readed += readbytes;
}
partNumber += 1;
}
string str = string.Empty;
for (int i = 0; i < fileChunks.Count; i++)
{
//参数
using var formData = new MultipartFormDataContent();
formData.Add(new StringContent(fileName), "FileName");
formData.Add(new StringContent(fileChunks[i].PartNumber.ToString()), "PartNumber");
formData.Add(new StringContent(fileChunks.Count.ToString()), "Chunks");
formData.Add(new ByteArrayContent(fileChunks[i].PartFileBytes), "file", fileChunks[i].FileName + ".partNumber-" + fileChunks[i].PartNumber);
var response = httpClient.PostAsync(url, formData).Result;
if (!response.IsSuccessStatusCode)
{
msg = "文件上传意外中断";
break;
}
else
{
var res = response.Content.ReadFromJsonAsync<Request.UploadResponse>().Result;
if (res != null && res.code == 200)
{
//所有分片传输完毕
networkPath = res.data.url;
localPath = res.data.localPath;
msg = res.msg;
return true;
}
else if (res != null && res.code == 202)
{
//分片传输成功,循环继续
}
else
{
msg = res != null ? res.msg : "文件上传失败";
return false;
}
}
}
return false;
}
2.2 FileChunk.cs
public class FileChunk
{
/// <summary>
/// 文件名
/// </summary>
public string FileName { get; set; }
/// <summary>
/// 当前分片
/// </summary>
public int PartNumber { get; set; }
/// <summary>
/// 缓冲区大小
/// </summary>
public int PartSize { get; set; }
/// <summary>
/// 分片文件
/// </summary>
public byte[] PartFileBytes { get; set; }
}
总结:复制粘贴