一、实现效果
.NET Core WebApi基础入门项目源码下载
二、实现效果
2.1、文件分片下载思路
2.2、文件分片下载的后端实现?
①首先定义文件下载请求、分段下载的接口且编写
/***
* Title:".NET Core WebApi" 项目
* 主题:文件控制
* Description:
* 功能:
* 2-下载文件流程:
* ①接受前端上传文件的预览信息【文件大小、
* 分片数量、文件的md5值、文件扩展类型、文件名称】请求;
* 生成该请求的guid,存在缓存中,返回对应消息。
* ②根据获取到的文件分片数量循环请求文件分片数据进行下载
* 【也可单独请求下载某一文件片段流数据】
* Date:2021
* Version:0.1版本
* Author:Coffee
* Modify Recoder:
*/
using log4net;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Utils;
using WebApiUtils;
namespace WebApi_Learn.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class FileController : ControllerBase
{
#region 基础参数
//日志
private readonly ILog _log = LogManager.GetLogger(WebApi_Learn.Startup.repository.Name, typeof(FileController));
#endregion
#region 公有方法
/// <summary>
/// 请求下载文件
/// </summary>
/// <param name="fileInfo">文件参数信息[name]</param>
/// <returns></returns>
[HttpPost, Route("RequestDownload")]
public MessageEntity RequestDownloadFile([FromBody] Dictionary<string, object> fileInfo)
{
//string path = $".{GetFilePath(AppConfigHelper.GetUploadFileBasePath)}";
//DirectoryInfo root = new DirectoryInfo(path);
//FileInfo[] files = root.GetFiles();
//foreach (var item in files)
//{
// _log.Debug($"{path} 目录下的文件为:"+item.Name);
//}
return RequestDownloadFile(fileInfo,AppConfigHelper.GetUploadFileBasePath, _log);
}
/// <summary>
/// 分段下载文件
/// </summary>
/// <param name="fileInfo">请求参数信息[index,name]</param>
/// <returns></returns>
[HttpPost, Route("Download")]
public async Task<IActionResult> FileDownload([FromBody] Dictionary<string, object> fileInfo)
{
return await FileDownload(fileInfo,AppConfigHelper.GetUploadFileBasePath, _log);
}
#endregion
#region 私有方法
/// <summary>
/// 请求下载文件
/// </summary>
/// <param name="fileInfo">文件参数信息[name]</param>
/// <param name="fileBasePath">下载文件的基础路径</param>
/// <param name="log">日志</param>
/// <returns>返回消息实体</returns>
private MessageEntity RequestDownloadFile([FromBody] Dictionary<string, object> fileInfo, string fileBasePath, ILog log)
{
MessageEntity message = new MessageEntity();
string fileName = string.Empty;
string fileExt = string.Empty;
if (fileInfo.ContainsKey("name"))
{
fileName = fileInfo["name"].ToString();
}
if (fileInfo.ContainsKey("ext"))
{
fileExt = fileInfo["ext"].ToString();
}
if (string.IsNullOrEmpty(fileName))
{
message.Code = -1;
message.Msg = "文件名不能为空";
return message;
}
//获取对应目录下文件,如果有,获取文件开始准备分段下载
string filePath = GetFilePathAndName(fileBasePath, fileName);
filePath = $"{filePath}{fileExt}";
log.Debug($"下载文件的路径:{filePath}");
FileStream fs = null;
try
{
if (!System.IO.File.Exists(filePath))
{
//文件为空
message.Code = -1;
message.Msg = $"请求的:{filePath} 文件不存在!";
return message;
}
fs = new FileStream(filePath, FileMode.Open);
if (fs.Length <= 0)
{
//文件为空
message.Code = -1;
message.Msg = "文件尚未处理完";
return message;
}
int shardSize = 1 * 1024 * 1024;//一次1M
RequestFileUploadEntity request = new RequestFileUploadEntity();
request.fileext = fileExt;
request.size = fs.Length;
request.count = (int)(fs.Length / shardSize);
if ((fs.Length % shardSize) > 0)
{
request.count += 1;
}
request.filedata = GetCryptoString(fs);
message.Data = request;
}
catch (Exception ex)
{
log.Debug($"读取文件信息失败:{filePath},错误信息:{ex.Message}");
}
finally
{
if (fs != null)
{
fs.Close();
}
}
return message;
}
/// <summary>
/// 下载文件
/// </summary>
/// <param name="fileInfo">文件信息</param>
/// <param name="fileBasePath">下载文件的基础路径</param>
/// <param name="log">日志</param>
/// <returns></returns>
private async Task<IActionResult> FileDownload([FromBody] Dictionary<string, object> fileInfo, string fileBasePath, ILog log)
{
//开始根据片段来下载
int index = 0;
if (fileInfo.ContainsKey("index"))
{
int.TryParse(fileInfo["index"].ToString(), out index);
}
else
{
return Ok(new { code = -1, msg = "缺少参数" });
}
string fileName = string.Empty;
string fileExt = string.Empty;
if (fileInfo.ContainsKey("name"))
{
fileName = fileInfo["name"].ToString();
}
if (fileInfo.ContainsKey("ext"))
{
fileExt = fileInfo["ext"].ToString();
}
if (string.IsNullOrEmpty(fileName))
{
return Ok(new { code = -1, msg = "文件名不能为空" });
}
//获取对应目录下文件,如果有,获取文件开始准备分段下载
string filePath = GetFilePathAndName(fileBasePath, fileName);
filePath = $"{filePath}{fileExt}";
if (!System.IO.File.Exists(filePath))
{
return Ok(new { code = -1, msg = "文件尚未处理" });
}
using (var fs = new FileStream(filePath, FileMode.Open))
{
if (fs.Length <= 0)
{
return Ok(new { code = -1, msg = "文件尚未处理" });
}
int shardSize = 1 * 1024 * 1024;//一次1M
int count = (int)(fs.Length / shardSize);
if ((fs.Length % shardSize) > 0)
{
count += 1;
}
if (index > count - 1)
{
return Ok(new { code = -1, msg = "无效的下标" });
}
fs.Seek(index * shardSize, SeekOrigin.Begin);
if (index == count - 1)
{
//最后一片 = 总长 - (每次片段大小 * 已下载片段个数)
shardSize = (int)(fs.Length - (shardSize * index));
}
byte[] datas = new byte[shardSize];
await fs.ReadAsync(datas, 0, datas.Length);
//fs.Close();
return File(datas, "application/octet-stream");
}
}
/// <summary>
/// 获取上传文件的路径和名称
/// </summary>
/// <param name="fileBasePath">文件的基础路径</param>
/// <param name="fileName">文件名称</param>
/// <returns></returns>
private static string GetFilePathAndName(string fileBasePath, string fileName)
{
string path = $".{fileBasePath}{DateTime.Now.ToString("yyyy-MM-dd")}/{fileName}";
return path;
}
/// <summary>
/// 文件流加密
/// </summary>
/// <param name="fileStream">文件流</param>
/// <returns></returns>
private string GetCryptoString(Stream fileStream)
{
MD5 md5 = new MD5CryptoServiceProvider();
byte[] cryptBytes = md5.ComputeHash(fileStream);
return GetCryptoString(cryptBytes);
}
/// <summary>
/// 字节加密
/// </summary>
/// <param name="cryptBytes"></param>
/// <returns></returns>
private string GetCryptoString(byte[] cryptBytes)
{
//加密的二进制转为string类型返回
StringBuilder sb = new StringBuilder();
for (int i = 0; i < cryptBytes.Length; i++)
{
sb.Append(cryptBytes[i].ToString("x2"));
}
return sb.ToString();
}
#endregion
}//Class_end
}
②定义消息、请求上传文件实体
/***
* Title:".NET Core WebApi" 项目
* 主题:消息实体
* Description:
* 功能:XXX
* Date:2021
* Version:0.1版本
* Author:Coffee
* Modify Recoder:
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Utils
{
public class MessageEntity
{
private int _Code = 0;
private string _Msg = string.Empty;
private object _Data = new object();
/// <summary>
/// 状态标识
/// </summary>
public int Code { get => _Code; set => _Code = value; }
/// <summary>
/// 返回消息
/// </summary>
public string Msg { get => _Msg; set => _Msg = value; }
/// <summary>
/// 返回数据
/// </summary>
public object Data { get => _Data; set => _Data = value; }
}//Class_end
}
/***
* Title:".NET Core WebApi" 项目
* 主题:请求文件上传实体
* Description:
* 功能:XXX
* Date:2021
* Version:0.1版本
* Author:Coffee
* Modify Recoder:
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Utils
{
public class RequestFileUploadEntity
{
private long _size = 0;
private int _count = 0;
private string _filedata = string.Empty;
private string _fileext = string.Empty;
private string _factoryid = string.Empty;
private string _filename = string.Empty;
/// <summary>
/// 文件大小
/// </summary>
public long size { get => _size; set => _size = value; }
/// <summary>
/// 片段数量
/// </summary>
public int count { get => _count; set => _count = value; }
/// <summary>
/// 文件md5
/// </summary>
public string filedata { get => _filedata; set => _filedata = value; }
/// <summary>
/// 文件类型
/// </summary>
public string fileext { get => _fileext; set => _fileext = value; }
/// <summary>
/// 工厂id
/// </summary>
public string factoryid { get => _factoryid; set => _factoryid = value; }
/// <summary>
/// 文件名
/// </summary>
public string filename { get => _filename; set => _filename = value; }
}//Class_end
}
三、运行程序测试
3.1、跨域错误
运行后发现是跨域错误【跨域内容介绍请查看:(CORS) 启用跨域请求 ASP.NET Core | Microsoft Docs】
3.2、跨域设置
①在 【Startup--->ConfigureServices】方法下注册跨域策略
#region 跨域设置
//允许所有
services.AddCors(options =>
{
options.AddPolicy("AllowAll", p =>
{
p.SetIsOriginAllowed(hostName => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
只是示例,具体根据自身需要
//services.AddCors(options =>
//{
// options.AddPolicy("AllowSome", p =>
// {
// p.WithOrigins("https://www.baidu.com")
// .WithMethods("GET", "POST")
// .WithHeaders(HeaderNames.ContentType, "x-custom-header");
// });
//});
#endregion
②在【startup-->Configure】方法下使用注册好的的跨域策略
#region 使用跨域 (UseCors必须配置为在对 UseRouting 和 UseEndpoints的调用之间执行)
app.UseCors("AllowAll");
#endregion
?3.3、运行测试成功下载
四、上传文件使用的简单Html页面(之Layui)?
4.1、到Layui官网下载基础包
Layui - 经典开源模块化前端 UI 框架
?4.2、编写前端的文件分片上传界面和逻辑
①解压下载好的Layui框架包,然后修改test.html名为【uploadOrDownloadFile.html】
?②查看Layui上传文件示例编写分片上传文件逻辑
图片/文件上传模块文档 - Layui
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<link rel="stylesheet" href="layui/css/layui.css">
</head>
<body>
<div>
<button type="button" class="layui-btn" id="btnDownload">下载</button>
</div>
<script src="layui/layui.js"></script>
<script>
var form;
var $;
var succeed = 0, shardCount = 0;
var t1;
layui.use(['upload', 'form', 'element'],
function () {
form = layui.form;
$ = layui.$;
var upload = layui.upload;
var element = layui.element;
var host = "http://localhost:47918/api/File/";
$("#btnDownload").on("click", function (e) {
downloadRequest();
});
function downloadRequest() {
//请求文件
var data = {
name: 'Lexmark_XM1145_UsersGuide_sc',
ext: '.pdf',
};
//Ajax提交
$.ajax({
url: host + "RequestDownload",
type: "POST",
data: JSON.stringify(data),
type: "POST",
dataType: "json",
contentType: "application/json; charset=utf-8",
timeout: 36000,
success: function (res) {
if (res.code === -1) {
//layer.alert(decodeURIComponent(res.msg));
console.log(res);
return;
} else {
console.log(res.data);
//拿到文件开始分片下载
var count = res.data.count;
var filedata = res.data.fieldata;
var fileext = res.data.fileext;
var size = res.data.size;
maxCount = count - 1;
download();
window.clearInterval(t1);
}
}
});
}
var maxCount = 0;
var index = 0;
function download() {
var data = {
index: index,
name: 'Lexmark_XM1145_UsersGuide_sc',
ext: '.pdf',
};
//console.log(index);
//console.log(maxCount);
//Ajax提交
$.ajax({
url: host + "Download",
type: "POST",
data: JSON.stringify(data),
type: "POST",
dataType: "json",
contentType: "application/json; charset=utf-8",
timeout: 36000,
success: function (res) {
console.log(index + ":over");
index++;
if (index <= maxCount) {
download();
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log(textStatus);
console.log(index + ":errorover");
index++;
if (index <= maxCount) {
download();
} else {
console.log("下载完成");
}
}
});
}
});
</script>
</body>
</html>
?参考文章:net core WebApi——文件分片下载 - AprilBlank
|