有時候,為了追求便利性,我們可能會讓前端直接將檔案上傳到阿里雲OSS,然後將URL提交給ASP.NET。然而,這種做法意味著前端需要擁有OSS的訪問金鑰,而將金鑰存放在前端,無疑增加了被破解的風險。因此,最安全的做法仍然是由伺服器端負責上傳檔案到OSS。
接下來,我將演示如何實現分塊上傳到伺服器的過程,而且在這個過程中,伺服器並不儲存任何分塊,而是直接將分塊上傳到OSS。
伺服器端
asp.net 引用 nuget 包:
JMS.FileUploader.AspNetCore
Aliyun.OSS.SDK.NetCore
實現一個oss的 IUploadFilter , 把接收到的分塊資料,實時傳到oss
[UploadFilterDescription("Aliyun")]
public class AliyunUploadFilter : IUploadFilter
{
const string BucketName = "<your-bucket-name>";
const string OssEndpoint = "<your-oss-endpoint>";
const string AccessKeyId = "<your-accessKeyId>";
const string AccessKeySecret = "<your-accessKeySecret>";
string _uploadId;
string _ossUploadId;
string _objectKey;
OssClient _ossClient;
public async Task OnUploadBeginAsync(HttpContext context, string uploadId, string fileName, long fileSize, int fileItemIndex)
{
_uploadId = uploadId;
_objectKey = $"file{fileItemIndex}.zip";
_ossClient = new OssClient(OssEndpoint , AccessKeyId , AccessKeySecret );
var ret = _ossClient.InitiateMultipartUpload(new InitiateMultipartUploadRequest(BucketName, _objectKey));
if (ret.HttpStatusCode != System.Net.HttpStatusCode.OK)
throw new Exception(ret.HttpStatusCode.ToString());
_ossUploadId = ret.UploadId;
}
public async Task OnReceivedAsync(HttpContext context, Stream inputStream, long position, int size)
{
var data = new byte[size];
await inputStream.ReadAtLeastAsync(data, size);
using var ms = new MemoryStream(data);
var num = (int)(position / 102400) + 1;
var ret = _ossClient.UploadPart(new UploadPartRequest(BucketName, _objectKey, _ossUploadId) {
InputStream = ms,
PartSize = size,
PartNumber = num
});
if (ret.HttpStatusCode != System.Net.HttpStatusCode.OK)
throw new Exception(ret.HttpStatusCode.ToString());
}
public async Task<string> OnUploadCompletedAsync(HttpContext context)
{
for (int i = 0; i < 3; i++) // 如果發生錯誤,最多嘗試3次
{
try
{
// 列出所有分塊。
var listPartsRequest = new ListPartsRequest(BucketName, _objectKey, _ossUploadId);
var partList = _ossClient.ListParts(listPartsRequest);
// 建立CompleteMultipartUploadRequest物件。
var completeRequest = new CompleteMultipartUploadRequest(BucketName, _objectKey, _ossUploadId);
// 設定分塊列表。
foreach (var part in partList.Parts)
{
completeRequest.PartETags.Add(new PartETag(part.PartNumber, part.ETag));
}
// 完成上傳。
var ret = _ossClient.CompleteMultipartUpload(completeRequest);
if (ret.HttpStatusCode != System.Net.HttpStatusCode.OK)
throw new Exception(ret.HttpStatusCode.ToString());
//設定訪問許可權
_ossClient.SetObjectAcl(BucketName, _objectKey, CannedAccessControlList.PublicRead);
//返回下載的url路徑
return ret.Location;
}
catch (Exception)
{
if (i == 2)
{
throw;
}
else
{
Thread.Sleep(3000);
}
}
}
return null;
}
public void OnUploadError()
{
}
}
然後註冊這個 filter :
services.AddFileUploadFilter<AliyunUploadFilter>();
啟用上傳元件:
app.UseJmsFileUploader();
controller裡面寫一個最終的業務處理函式
[ApiController]
[Route("[controller]/[action]")]
public class MainController : ControllerBase
{
[HttpPost]
public string Test([FromBody] object body)
{
var customHeader = Request.Headers["Custom-Header"];
//臨時檔案路徑
var filepath = Request.Headers["FilePath"];
//檔名
var filename = Request.Headers["Name"];
return filepath + "\r\n" + filename + "\r\n" + customHeader;
}
}
前端
前端 import 模組:jms-uploader
async function uploadToAliyun() {
//自定義請求頭
var headers = function () {
return { "Custom-Header": "test" };
};
//提交的body
var dataBody = {
name: "abc"
};
var uploader = new JmsUploader("http://localhost:5200/Main/Test", [document.querySelector("#file1").files, document.querySelector("#file2").files], headers, dataBody);
uploader.setPartSize(1024*300);//設定分塊大小300K
uploader.setUploadFilter("Aliyun");//設定伺服器使用哪個upload filter
uploader.onUploading = function (percent, uploadedSize, totalSize) {
document.querySelector("#info").innerHTML = percent + "% " + uploadedSize + "," + totalSize;
};
try {
var ret = await uploader.upload();
alert(ret);
} catch (e) {
alert("錯誤:" + JSON.stringify(e));
}
}
html
<body>
<input id="file1" multiple type="file" />
<input id="file2" multiple type="file" />
<button onclick="uploadToAliyun()">
upload to aliyun oss
</button>
<div id="info"></div>
</body>