好記性台颠,不如爛筆頭,趁下班時間對今天研究的阿里云OSS存儲服務(wù)勒庄,通過前端直傳服務(wù)進行上傳的功能串前,做個小小的總結(jié),方便后期查看等锅铅。
一酪呻、背景
最近公司有新的需求,需要將文件上傳到阿里云OSS盐须,目前項目中的文件主要是存儲到自己的服務(wù)器玩荠,這樣很容易受服務(wù)器帶寬、硬件的影響贼邓,加上服務(wù)器配置不高阶冈,應(yīng)用服務(wù)很容易垮掉。之前也用過OSS塑径,但是上傳文件都是比較小的10MB以內(nèi)的文件女坑,采用的是生成數(shù)據(jù)流的方式。這種情況已經(jīng)無法滿足現(xiàn)在的應(yīng)用場景统舀,就又熟悉了一下SDK文檔匆骗,主要的實現(xiàn)方式有:
方案一:先將文件傳到自己的服務(wù)器后臺,然后由后臺存儲到OSS誉简,刪掉原來的文件碉就。這樣其實很多的缺點:
1、 上傳慢闷串。先上傳到應(yīng)用服務(wù)器瓮钥,再上傳到OSS,網(wǎng)絡(luò)傳送多了一倍烹吵。如果數(shù)據(jù)直傳到OSS碉熄,不走應(yīng)用服務(wù)器,速度將大大提升肋拔,而且OSS是采用BGP帶寬锈津,能保證各地各運營商的速度。
2凉蜂、 擴展性不好琼梆。如果后續(xù)用戶多了七咧,應(yīng)用服務(wù)器會成為瓶頸。
3叮叹、 費用高。由于OSS上傳流量是免費的爆存。如果數(shù)據(jù)直傳到OSS蛉顽,不走應(yīng)用服務(wù)器,那么將能省下幾臺應(yīng)用服務(wù)器先较。
方案二:服務(wù)端簽名携冤,前端直傳
方案三:STS臨時授權(quán)訪問OSS
https://help.aliyun.com/document_detail/32122.html
二、簽名方式與STS臨時授權(quán)
2.1服務(wù)端簽名后直傳
2.1.1背景
采用JavaScript客戶端直接簽名(參見JavaScript客戶端簽名直傳)時闲勺,AccessKey ID和AcessKey Secret會暴露在前端頁面曾棕,因此存在嚴(yán)重的安全隱患。因此菜循,OSS提供了服務(wù)端簽名后直傳的方案翘地。
2.1.2流程介紹
流程如下圖所示:
本示例中,Web端向服務(wù)端請求簽名癌幕,然后直接上傳衙耕,不會對服務(wù)端產(chǎn)生壓力,而且安全可靠勺远。但本示例中的服務(wù)端無法實時了解用戶上傳了多少文件橙喘,上傳了什么文件。如果想實時了解用戶上傳了什么文件胶逢,可以采用服務(wù)端簽名直傳并設(shè)置上傳回調(diào)厅瞎。
2.2STS臨時授權(quán)
OSS 可以通過阿里云 STS (Security Token Service) 進行臨時授權(quán)訪問。阿里云 STS 是為云計算用戶提供臨時訪問令牌的Web服務(wù)初坠。通過 STS和簸,您可以為第三方應(yīng)用或子用戶(即用戶身份由您自己管理的用戶)頒發(fā)一個自定義時效和權(quán)限的訪問憑證。
2.2.1使用場景
(1)對于您本地身份系統(tǒng)所管理的用戶某筐,比如您的 App 的用戶比搭、您的企業(yè)本地賬號、第三方 App 南誊,將這部分用戶稱為聯(lián)盟用戶身诺。這些聯(lián)盟用戶可能需要直接訪問 OSS 資源。此外抄囚,還可以是您創(chuàng)建的能訪問您的阿里云資源應(yīng)用程序的用戶霉赡。
(2)對于這部分聯(lián)盟用戶,通過阿里云 STS 服務(wù)為阿里云賬號(或 RAM 用戶)提供短期訪問權(quán)限管理幔托。您不需要透露云賬號(或 RAM 用戶)的長期密鑰(如登錄密碼穴亏、AccessKey)蜂挪,只需要生成一個短期訪問憑證給聯(lián)盟用戶使用即可。這個憑證的訪問權(quán)限及有效期限都可以由您自定義嗓化。您不需要關(guān)心權(quán)限撤銷問題棠涮,訪問憑證過期后會自動失效。
(3)通過 STS 生成的憑證包括安全令牌(SecurityToken)刺覆、臨時訪問密鑰(AccessKeyId, AccessKeySecret)严肪。使用AccessKey 方法與您在使用阿里云賬戶或 RAM 用戶 AccessKey 發(fā)送請求時的方法相同。需要注意的是在每個向 OSS 發(fā)送的請求中必須攜帶安全令牌谦屑。
2.2.2實現(xiàn)原理
以一個移動 App 舉例驳糯。假設(shè)您是一個移動 App 開發(fā)者,打算使用阿里云 OSS 服務(wù)來保存App 的終端用戶數(shù)據(jù)氢橙,并且要保證每個 App 用戶之間的數(shù)據(jù)隔離酝枢,防止一個 App 用戶獲取到其它 App 用戶的數(shù)據(jù)。你可以使用 STS 授權(quán)用戶直接訪問 OSS悍手。
2.2.3使用 STS 授權(quán)用戶直接訪問 OSS 的流程如下:
1帘睦、App 用戶登錄。App 用戶和云賬號無關(guān)坦康,它是 App 的終端用戶官脓,AppServer 支持 App 用戶登錄。對于每個有效的 App 用戶來說涝焙,需要 AppServer 能定義出每個 App 用戶的最小訪問權(quán)限卑笨。
2、AppServer 請求 STS 服務(wù)獲取一個安全令牌(SecurityToken)仑撞。在調(diào)用 STS 之前赤兴,AppServer 需要確定 App 用戶的最小訪問權(quán)限(用 Policy 語法描述)以及授權(quán)的過期時間。然后通過扮演角色(AssumeRole)來獲取一個代表角色身份的安全令牌隧哮。
3桶良、 STS 返回給 AppServer 一個有效的訪問憑證,包括一個安全令牌(SecurityToken)沮翔、臨時訪問密鑰(AccessKeyId, AccessKeySecret)以及過期時間陨帆。
4、AppServer 將訪問憑證返回給 ClientApp采蚀。ClientApp 可以緩存這個憑證疲牵。當(dāng)憑證失效時,ClientApp 需要向 AppServer 申請新的有效訪問憑證榆鼠。比如纲爸,訪問憑證有效期為1小時,那么 ClientApp 可以每 30 分鐘向 AppServer 請求更新訪問憑證妆够。
5识啦、ClientApp 使用本地緩存的訪問憑證去請求 Aliyun Service API负蚊。云服務(wù)會感知 STS 訪問憑證,并會依賴 STS 服務(wù)來驗證訪問憑證颓哮,正確響應(yīng)用戶請求家妆。
STS 安全令牌、角色管理和使用相關(guān)內(nèi)容詳情冕茅,請參考 RAM 角色管理揩徊。調(diào)用 STS 服務(wù)接口AssumeRole來獲取有效訪問憑證即可。
三嵌赠、知識圖譜
四、臨時授權(quán)與驗簽方法兩種方式的封裝
4.1添加實體對象類
/// <summary>
/// GetToken()函數(shù)返回的STS憑據(jù)數(shù)據(jù)模型
/// 參考:https://www.cnblogs.com/myhalo/p/6626530.html
/// https://help.aliyun.com/document_detail/100624.html?spm=a2c4g.11186623.2.5.600c6d13A0lSIR
/// </summary>
public class StsTokenModel
{
public int status { get; set; }
/// <summary>
///
/// </summary>
public string Region { get; set; }
/// <summary>
/// 訪問密鑰標(biāo)識
/// </summary>
public string AccessKeyId { get; set; }
/// <summary>
/// 訪問密鑰
/// </summary>
public string AccessKeySecret { get; set; }
/// <summary>
/// 安全令牌
/// </summary>
public string SecurityToken { get; set; }
/// <summary>
/// 失效時間
/// </summary>
public string Expiration { get; set; }
/// <summary>
/// Bucket名稱熄赡,即目錄名稱
/// </summary>
public string BucketName { get; set; }
}
4.2 封裝STS臨時授權(quán)訪問OSS工具類
/// <summary>
/// OSS工具類 2021-01-28
/// https://blog.csdn.net/weixin_39934264/article/details/102964004
/// OSS臨時驗簽:https://www.cnblogs.com/myhalo/p/6626530.html
/// </summary>
class OSS_Helper
{
const string accessKeyId = "你的";
const string accessKeySecret = "你的";
const string endpoint = "你的";
const string bucketName = "你的";
#region STS Token特有
private const string REGION_CN_HANGZHOU = "cn-hangzhou";
private const string STS_API_VERSION = "2021-01-28";
private const string RoleArn = "你的";//【RAM角色管理】——【單機角色】
private const int TokenExpireTime = 3600;//過期時間姜挺,單位為秒。 過期時間最小值為900秒彼硫,最大值為MaxSessionDuration設(shè)置的時間炊豪。默認(rèn)值為3600秒。
//這里是權(quán)限配置拧篮,請參考o(jì)ss的文檔【RAM 訪問控制】——【用戶】——【權(quán)限管理】
/*private const string PolicyFile = @"{
""Statement"": [
{
""Action"": [
""oss:PutObject""
],
""Effect"": ""Allow"",
""Resource"": [""acs:oss:*:*:bucketName/*"", ""acs:oss:*:*:bucketName""]
}
],
""Version"": ""1""
}";*/
private const string PolicyFile = @"{
""Statement"": [
{
""Action"": ""oss:*"",
""Effect"": ""Allow"",
""Resource"": ""*""
}
],
""Version"": ""1""
}";
#endregion
OssClient client = null;
public OSS_Helper()
{
// 由用戶指定的OSS訪問地址词渤、阿里云頒發(fā)的AccessKeyId/AccessKeySecret構(gòu)造一個新的OssClient實例。
client = new OssClient(endpoint, accessKeyId, accessKeySecret);
}
/*簡單上傳:文件最大不能超過5GB串绩。
追加上傳:文件最大不能超過5GB缺虐。
斷點續(xù)傳上傳:支持并發(fā)、斷點續(xù)傳礁凡、自定義分片大小高氮。大文件上傳推薦使用斷點續(xù)傳。最大不能超過48.8TB顷牌。
分片上傳:當(dāng)文件較大時剪芍,可以使用分片上傳,最大不能超過48.8TB窟蓝。*/
/// <summary>
/// 簡易文件上傳
/// </summary>
/// <param name="objectName">OSS文件路徑</param>
/// <param name="localFilename">本地文件路徑</param>
public void Simple_Up(string objectName, string localFilename)
{
//var objectName = "Project/222.jpg";
//var localFilename = @"C:\tiger.jpg";
// 創(chuàng)建OssClient實例罪裹。
try
{
// 上傳文件。
client.PutObject(bucketName, objectName, localFilename);
NLogger.Error("Put object succeeded");
}
catch (Exception ex)
{
NLogger.Error("Put object failed, {0}", ex.Message);
}
}
/// <summary>
/// 分片上傳
/// </summary>
/// <param name="objectName">OSS文件路徑</param>
/// <param name="localFilename">本地文件路徑</param>
public void Multipar_tUp(string objectName, string localFilename)
{
var uploadId = "";
try
{
// 定義上傳文件的名字和所屬存儲空間运挫。在InitiateMultipartUploadRequest中状共,可以設(shè)置ObjectMeta,但不必指定其中的ContentLength谁帕。
var request = new InitiateMultipartUploadRequest(bucketName, objectName);
var result = client.InitiateMultipartUpload(request);
uploadId = result.UploadId;
// 打印UploadId口芍。
NLogger.Error("Init multi part upload succeeded");
NLogger.Error("Upload Id:{0}", result.UploadId);
}
catch (Exception ex)
{
throw ex;
}
// 計算分片總數(shù)。
var partSize = 1024 * 1024;
var fi = new FileInfo(localFilename);
var fileSize = fi.Length;
var partCount = fileSize / partSize;
if (fileSize % partSize != 0)
{
partCount++;
}
// 開始分片上傳雇卷。partETags是保存partETag的列表鬓椭,OSS收到用戶提交的分片列表后颠猴,會逐一驗證每個分片數(shù)據(jù)的有效性。 當(dāng)所有的數(shù)據(jù)分片通過驗證后小染,OSS會將這些分片組合成一個完整的文件翘瓮。
var partETags = new List<PartETag>();
try
{
using (var fs = File.Open(localFilename, FileMode.Open))
{
for (var i = 0; i < partCount; i++)
{
var skipBytes = (long)partSize * i;
// 定位到本次上傳起始位置。
fs.Seek(skipBytes, 0);
// 計算本次上傳的片大小裤翩,最后一片為剩余的數(shù)據(jù)大小资盅。
var size = (partSize < fileSize - skipBytes) ? partSize : (fileSize - skipBytes);
var request = new UploadPartRequest(bucketName, objectName, uploadId)
{
InputStream = fs,
PartSize = size,
PartNumber = i + 1
};
// 調(diào)用UploadPart接口執(zhí)行上傳功能,返回結(jié)果中包含了這個數(shù)據(jù)片的ETag值踊赠。
var result = client.UploadPart(request);
partETags.Add(result.PartETag);
NLogger.Error("finish {0}/{1}", string.Format(partETags.Count.ToString(), partCount));
}
NLogger.Error("Put multi part upload succeeded");
}
}
catch (Exception ex)
{
throw ex;
}
// 列舉已上傳的分片呵扛。
try
{
var listPartsRequest = new ListPartsRequest(bucketName, objectName, uploadId);
var listPartsResult = client.ListParts(listPartsRequest);
Console.WriteLine("List parts succeeded");
// 遍歷所有分片。
var parts = listPartsResult.Parts;
foreach (var part in parts)
{
Console.WriteLine("partNumber: {0}, ETag: {1}, Size: {2}", part.PartNumber, part.ETag, part.Size);
}
}
catch (Exception ex)
{
throw ex;
}
// 完成分片上傳筐带。
try
{
var completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, objectName, uploadId);
foreach (var partETag in partETags)
{
completeMultipartUploadRequest.PartETags.Add(partETag);
}
var result = client.CompleteMultipartUpload(completeMultipartUploadRequest);
NLogger.Error("complete multi part succeeded");
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 斷點續(xù)傳
/// https://help.aliyun.com/document_detail/91101.html?spm=a2c4g.11186623.6.1372.10d13529n3VwoC
/// </summary>
/// <param name="objectName">OSS文件路徑</param>
/// <param name="localFilename">本地文件路徑</param>
/// <param name="checkpointDir">斷點續(xù)傳的中間狀態(tài)</param>
public void chkin_Up(string objectName, string localFilename, string checkpointDir)
{
try
{
// 通過UploadFileRequest設(shè)置多個參數(shù)今穿。
UploadObjectRequest request = new UploadObjectRequest(bucketName, objectName, localFilename)
{
// 指定上傳的分片大小。
PartSize = 1024 * 1024,
// 指定并發(fā)線程數(shù)伦籍。
ParallelThreadCount = 10,
// checkpointDir保存斷點續(xù)傳的中間狀態(tài)蓝晒,用于失敗后繼續(xù)上傳。如果checkpointDir為null帖鸦,斷點續(xù)傳功能不會生效芝薇,每次失敗后都會重新上傳。
CheckpointDir = checkpointDir,
};
// 斷點續(xù)傳上傳作儿。
client.ResumableUploadObject(request);
//NLogger.Error("Resumable upload object:{0} succeeded", objectName);
}
catch (OssException ex)
{
NLogger.Error("Failed with error code: {0}; Error info: {1}. \nRequestID:{2}\tHostID:{3}", string.Format(ex.ErrorCode, ex.Message, ex.RequestId, ex.HostId));
}
catch (Exception ex)
{
NLogger.Error("Failed with error info: {0}", ex.Message);
}
}
public void Stream_Down(string objectName, string downloadFilename)
{
// objectName 表示您在下載文件時需要指定的文件名稱洛二,如abc/efg/123.jpg。
//var objectName = "Project/cc.jpg";
//var downloadFilename = @"D:\GG.jpg";
// 創(chuàng)建OssClient實例攻锰。
//var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
try
{
// 下載文件到流灭红。OssObject 包含了文件的各種信息,如文件所在的存儲空間口注、文件名变擒、元信息以及一個輸入流。
var obj = client.GetObject(bucketName, objectName);
using (var requestStream = obj.Content)
{
byte[] buf = new byte[1024];
var fs = File.Open(downloadFilename, FileMode.OpenOrCreate);
var len = 0;
// 通過輸入流將文件的內(nèi)容讀取到文件或者內(nèi)存中寝志。
while ((len = requestStream.Read(buf, 0, 1024)) != 0)
{
fs.Write(buf, 0, len);
}
fs.Close();
}
NLogger.Error("Get object succeeded");
}
catch (Exception ex)
{
NLogger.Error("Get object failed. {0}", string.Format(ex.Message));
}
}
# region 上傳下載進度條
/// <summary>
/// 進度條:進度條用于指示上傳或下載的進度娇斑。
/// https://help.aliyun.com/document_detail/91759.html?spm=a2c4g.11186623.2.9.7c8e5d88iz39Cd
/// </summary>
public static void GetObjectProgress()
{
var endpoint = "<yourEndpoint>";
var accessKeyId = "<yourAccessKeyId>";
var accessKeySecret = "<yourAccessKeySecret>";
var bucketName = "<yourBucketName>";
var objectName = "<yourObjectName>";
// 創(chuàng)建OssClient實例。
var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
try
{
var getObjectRequest = new GetObjectRequest(bucketName, objectName);
getObjectRequest.StreamTransferProgress += streamProgressCallback;
// 下載文件材部。
var ossObject = client.GetObject(getObjectRequest);
using (var stream = ossObject.Content)
{
var buffer = new byte[1024 * 1024];
var bytesRead = 0;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
// 處理讀取的數(shù)據(jù)(此處代碼省略)毫缆。
}
}
Console.WriteLine("Get object:{0} succeeded", objectName);
}
catch (OssException ex)
{
NLogger.Error("Failed with error code: {0}; Error info: {1}. \nRequestID:{2}\tHostID:{3}",
string.Format(ex.ErrorCode, ex.Message, ex.RequestId, ex.HostId));
}
catch (Exception ex)
{
NLogger.Error("Failed with error info: {0}", ex.Message);
}
}
private static void streamProgressCallback(object sender, StreamTransferProgressArgs args)
{
NLogger.Error("ProgressCallback - Progress: {0}%, TotalBytes:{1}, TransferredBytes:{2} ",
string.Format((args.TransferredBytes * 100 / args.TotalBytes).ToString(), args.TotalBytes, args.TransferredBytes));
}
#endregion
/// <summary>
/// 刪除指定的單個文件
/// https://help.aliyun.com/document_detail/91924.html?spm=a2c4g.11186623.2.12.61937ff0mNZJra
/// </summary>
/// <param name="key">待刪除的文件名稱</param>
public void DeleteObject(string key)
{
try
{ //bucketName:文件所在存儲空間的名稱
client.DeleteObject(bucketName, key);
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 批量刪除文件
/// </summary>
public void DeleteBatchObject()
{
// 創(chuàng)建OssClient實例。
try
{
var keys = new List<string>();
var listResult = client.ListObjects(bucketName);
foreach (var summary in listResult.ObjectSummaries)
{
keys.Add(summary.Key);
}
// quietMode為true表示簡單模式乐导,為false表示詳細(xì)模式苦丁。默認(rèn)為詳細(xì)模式。
var quietMode = false;
// DeleteObjectsRequest的第三個參數(shù)指定返回模式物臂。
var request = new DeleteObjectsRequest(bucketName, keys, quietMode);
// 刪除多個文件旺拉。
var result = client.DeleteObjects(request);
if ((!quietMode) && (result.Keys != null))
{
foreach (var obj in result.Keys)
{
NLogger.Error("Delete successfully : {0} ", obj.Key);
}
}
NLogger.Error("Delete objects succeeded");
}
catch (Exception ex)
{
NLogger.Error("Delete objects failed. {0}", ex.Message);
}
}
#region STS Token驗簽 STS臨時授權(quán)訪問OSS
/// <summary>
/// https://help.aliyun.com/document_detail/28763.html?spm=a2c4g.11186623.2.13.3479606cQK2HOg
/// </summary>
/// <param name="accessKeyId"></param>
/// <param name="accessKeySecret"></param>
/// <param name="roleArn">指定角色的ARN产上。格式:acs:ram::$accountID:role/$roleName 。</param>
/// <param name="roleSessionName">用戶自定義參數(shù)蛾狗。此參數(shù)用來區(qū)分不同的令牌晋涣,可用于用戶級別的訪問審計。</param>
/// <param name="policy">權(quán)限策略沉桌。生成STS Token時可以指定一個額外的權(quán)限策略谢鹊,以進一步限制STS Token的權(quán)限。若不指定則返回的Token擁有指定角色的所有權(quán)限留凭。長度為1 ~1024個字符佃扼。</param>
/// <param name="protocolType"></param>
/// <param name="durationSeconds">過期時間,單位為秒蔼夜。過期時間最小值為900秒兼耀,最大值為MaxSessionDuration設(shè)置的時間。默認(rèn)值為3600秒挎扰。</param>
/// <returns></returns>
private AssumeRoleResponse assumeRole(String accessKeyId, String accessKeySecret, String roleArn,
String roleSessionName, String policy, ProtocolType protocolType, long durationSeconds)
{
try
{
// 創(chuàng)建一個 Aliyun Acs Client, 用于發(fā)起 OpenAPI 請求
IClientProfile profile = DefaultProfile.GetProfile(REGION_CN_HANGZHOU, accessKeyId, accessKeySecret);
DefaultAcsClient client = new DefaultAcsClient(profile);
// 創(chuàng)建一個 AssumeRoleRequest 并設(shè)置請求參數(shù)
AssumeRoleRequest request = new AssumeRoleRequest();
//request.Version = STS_API_VERSION;
request.Method = MethodType.POST;
//request.Protocol = protocolType;
request.RoleArn = roleArn;
request.RoleSessionName = roleSessionName;
request.Policy = policy;
request.DurationSeconds = durationSeconds;
// 發(fā)起請求,并得到response
AssumeRoleResponse response = client.GetAcsResponse(request);
return response;
}
catch (ClientException e)
{
throw e;
}
}
/// <summary>
/// 獲取token STS臨時授權(quán)訪問OSS 2021-01-28
/// </summary>
/// <returns></returns>
public StsTokenModel GetToken()
{
// 只有 RAM用戶(子賬號)才能調(diào)用 AssumeRole 接口
// 阿里云主賬號的AccessKeys不能用于發(fā)起AssumeRole請求
// 請首先在RAM控制臺創(chuàng)建一個RAM用戶巢音,并為這個用戶創(chuàng)建AccessKeys
// RoleArn 需要在 RAM 控制臺上獲取
// RoleSessionName 是臨時Token的會話名稱遵倦,自己指定用于標(biāo)識你的用戶,主要用于審計官撼,或者用于區(qū)分Token頒發(fā)給誰
// 但是注意RoleSessionName的長度和規(guī)則梧躺,不要有空格,只能有'-' '_' 字母和數(shù)字等字符
// 具體規(guī)則請參考API文檔中的格式要求
//string roleSessionName = "alice-001";
string roleSessionName = "HaiGongJiang-001";
// 必須為 HTTPS
try
{
AssumeRoleResponse stsResponse = assumeRole(accessKeyId, accessKeySecret, RoleArn, roleSessionName,
PolicyFile, ProtocolType.HTTPS, TokenExpireTime);
return new StsTokenModel()
{
status = 200,
Region = "oss-cn-beijing",//你的Region 注意 這個只要 空間名 不要 http:// 和 .aliyunoss.com !!,
AccessKeyId = stsResponse.Credentials.AccessKeyId,
AccessKeySecret = stsResponse.Credentials.AccessKeySecret,
Expiration = stsResponse.Credentials.Expiration,
SecurityToken = stsResponse.Credentials.SecurityToken,
BucketName = bucketName
};
}
catch (ClientException e)
{
return new StsTokenModel() { status = 500};
}
}
#endregion
#region 前端驗簽方法policy傲绣,未使用
public static string HmacSha1Sign(string text, string key)
{
Encoding encode = Encoding.UTF8;
byte[] byteData = encode.GetBytes(text);
byte[] byteKey = encode.GetBytes(key);
HMACSHA1 hmac = new HMACSHA1(byteKey);
CryptoStream cs = new CryptoStream(Stream.Null, hmac, CryptoStreamMode.Write);
cs.Write(byteData, 0, byteData.Length);
cs.Close();
return Convert.ToBase64String(hmac.Hash);
}
public int ConvertDateTimeInt(System.DateTime time)
{
System.DateTime startTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return (int)(time - startTime).TotalSeconds;
}
#endregion
}
4.3 Controller層調(diào)用掠哥,暴露給前端
4.3.1STS臨時授權(quán)
/// <summary>
/// 函數(shù)說明:獲取STS Token STS臨時授權(quán)訪問OSS 2021-01-28
/// 參考地址:https://help.aliyun.com/document_detail/100624.html?spm=a2c4g.11186623.2.5.600c6d13A0lSIR
/// </summary>
/// <returns>返回臨時token認(rèn)證信息https://help.aliyun.com/document_detail/28763.html?spm=a2c4g.11186623.2.13.3479606cQK2HOg</returns>
[HttpGet]
public Task<ResponseMessage> GetSTSsecurityToken()
{
var response = new MessageModel<StsTokenModel>();
try
{
IUser user = (IUser)this.Request.GetRouteData().Values["User"];
OSS_Helper oss = new OSS_Helper();
var stsmodel = oss.GetToken();
response.Data = stsmodel;
return Task.FromResult(response.SetError(GlobalErrorCode.OK));
}
catch (Exception ex)
{
NLogger.Exception(ex);
return Task.FromResult(response.SetError(GlobalErrorCode.SystemError));
}
}
4.3.2policy驗簽
/// <summary>
/// 前端驗簽方法policy:需要前端驗證,待測試秃诵,暫未使用該驗簽方式
/// https://help.aliyun.com/document_detail/100680.html?spm=a2c4g.11186623.2.10.3479606cQK2HOg#section-an0-sb1-5sh
/// </summary>
/// <returns>返回簽名認(rèn)證信息</returns>
[HttpGet]
public IHttpActionResult GetToken()
{
const string accessKeyId = "你的";
const string accessKeySecret = "你的";
const string endpoint = "oss-cn-beijing.aliyuncs.com";
const string bucketName = "你的";
const string hostURL = "http://" + bucketName + "." + endpoint;//
DateTime dt = DateTime.Now.AddMinutes(10).AddHours(-8);
string expire = dt.ToString("yyyy-MM-ddTHH:mm:ssZ");
string accessid = accessKeyId;
string accesskey = accessKeySecret;
string host = hostURL;
long contentLength = 10737418240;// 最大上傳文件大行蟆(10M)
long timestamp = (dt.ToUniversalTime().Ticks - 621355968000000000) / 10000000;
string dir = $"website/upload/img/{DateTime.Now.ToString("yyyyMMdd")}/";
string key = Guid.NewGuid().ToString("N");
string policy = "{\"expiration\":\"" + expire + "\",\"conditions\":[[\"content-length-range\",0," + contentLength + "],[\"starts-with\",\"$key\",\"" +
dir + "\"]]}";
string base64Policy = Convert.ToBase64String(System.Text.Encoding.Default.GetBytes(policy));
string signature = OSS_Helper.HmacSha1Sign(base64Policy, accesskey);
//文件名稱不需要后續(xù)名,這里取不到后綴名
var token = new
{
accessid = accessid,
host = host,
policy = base64Policy,
signature = signature,
expire = timestamp,
dir = dir,
filename = key
};
return Json(token);
}
4.4前端調(diào)用
前端調(diào)用菠净,請根據(jù)自身情況進行調(diào)用禁舷,下面是vue的示例
(1)安裝web前端接口框架:
npm install ali-oss
(2)封裝client.js工具類
//Client.js
const OSS = require('ali-oss');
export default function Client(data) {
//后臺返回數(shù)據(jù)
return new OSS({
region: data.Region,
bucket: data.BucketName,
accessKeyId: data.AccessKeyId,
accessKeySecret: data.AccessKeySecret,
securityToken: data.SecurityToken
})
}
(3)完整示例如下:
<template>
<div class="upload_temp">
<!-- 上傳文件 -->
<el-upload
class="upload-demo"
ref="upload"
drag
:before-upload="beforeUpload"
:on-success="handleSuccess"
:http-request="handleHttpRequest"
:headers="uploadHeaders"
:limit="files"
:disabled="disabled"
multiple
action
:file-list="fileList"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">
將文件拖到此處,或
<em>點擊上傳</em>
</div>
<div slot="tip" class="el-upload__tip">上傳文件大小不能超過 1G</div>
</el-upload>
<div @click="download_file()" style='margin-top:200px;'>下載</div>
</div>
</template>
<script>
import Client from "@/utils/client";
const OSS = require('ali-oss');
export default {
name: 'upload_temp',
data () {
return {
dataObj: {},
fileList: [],
files: 10,
uploadHeaders: {
authorization: "*"
},
disabled: false,
fileName: ""
}
},
methods:{
getAliToken() {
return new Promise((resolve, reject) => {
//請求后臺接口返回授權(quán)數(shù)據(jù)
this.$axios.get(this.$axios.defaults.baseURL + 'api/File/GetSTSsecurityToken')
.then((res) => {
console.log(JSON.parse(JSON.stringify(res.data)))
if(res.data.ErrorCode == 200){
this.dataObj = {
region: res.data.Data.Region,
bucket: res.data.Data.BucketName,
accessKeyId: res.data.Data.AccessKeyId,
accessKeySecret: res.data.Data.AccessKeySecret,
stsToken: res.data.Data.SecurityToken
};
resolve(true);
}
})
.catch(function (error) {
console.log(error);
reject(false);
});
// this.dataObj = {
// region: "oss-cn-beijing",
// bucket:"testhgj",
// accessKeyId:"LTAI4G9C5TAVuWL4TEgdvJUk",
// accessKeySecret: "7qIhJ5p4rWw1vkj6snFptyvHfyiF15",
// // security: response.data.security
// };
// resolve(true);
});
},
beforeUpload(file) {
return new Promise((resolve, reject) => {
this.getAliToken()
.then(response => {
if (response) {
resolve(response);
} else {
reject(response);
}
})
.catch(err => {
console.log(err);
reject(err);
});
});
},
async handleHttpRequest(option) {
console.log(option);
//上傳OSS
try {
let vm = this;
vm.disabled = true;
console.log(JSON.parse(JSON.stringify(this.dataObj)))
const client = new OSS(this.dataObj)
const file = option.file;
await client
.multipartUpload(option.file.name, file, {
progress: async function(p) {
let e = {};
e.percent = p * 100;
option.onProgress(e);
}
})
.then(({ res }) => {
console.log(res);
if (res.statusCode === 200) {
// option.onSuccess(ret)
console.log(res.requestUrls);
return res.requestUrls;
} else {
vm.disabled = false;
option.onError("上傳失敗");
}
})
.catch(error => {
vm.disabled = false;
option.onError("上傳失敗");
});
} catch (error) {
console.error(error);
this.disabled = false;
option.onError("上傳失敗");
}
},
handleSuccess(response, file, fileList) {
console.log(response);
console.log(file);
console.log(fileList);
},
// 下載阿里oss文件方法
download_file(){
var key = "Project/edraw-max_cn_setup_full5676.exe";
const client = Client(this.dataObj);
var url = client.signatureUrl(key, {
response: {
'content-disposition': 'attachment; filename="' + 'test_file.exe' + '"'
}
})
const a = document.createElement('a'); // 創(chuàng)建a標(biāo)簽
a.setAttribute('download', '');// download屬性
a.setAttribute('target', 'blank');// download屬性
a.setAttribute('href',url);// href鏈接
a.click();// 自執(zhí)行點擊事件
},
},
watch: {
url(val) {
if (val) {
this.urls.push(val);
}
}
},
mounted:function() {
}
}
</script>
<style scoped>
.upload_temp{width:80%;margin:auto;height: auto;background-color: #F4F4F4;padding: 23px 0 30px 0;}
/* 上傳 */
.upload_temp .avatar-uploader .el-upload {border: 1px dashed #d9d9d9;border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;}
.upload_temp .avatar-uploader .el-upload:hover { border-color: #409EFF;}
.upload_temp .avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 90px;height: 90px;line-height: 90px;text-align: center;border: 1px solid #ddd;}
</style>
五毅往、總結(jié)
我們在項目中最終采用的是STS臨時授權(quán)方案牵咙。
STS的優(yōu)勢如下:
- 您無需透露您的長期密鑰(AccessKey)給第三方應(yīng)用,只需生成一個訪問令牌并將令牌交給第三方應(yīng)用攀唯。您可以自定義這個令牌的訪問權(quán)限及有效期限洁桌。
- 您無需關(guān)心權(quán)限撤銷問題,訪問令牌過期后自動失效侯嘀。
關(guān)于STS的介紹請查閱阿里云官方文檔:
https://help.aliyun.com/document_detail/32093.html?spm=a2c4g.11186623.6.1409.13107d9ckOKzS6
六另凌、參考資料
在這邊不得不吐槽一下OSS的API谱轨,是真的很爛,基本找不到好的方法途茫,都是基于百度才做出來的碟嘴,當(dāng)然,我使用的方法估計還有一些坑囊卜,只是能實現(xiàn)了我的功能娜扇。
STS臨時授權(quán)訪問OSS
https://help.aliyun.com/document_detail/100624.html?spm=a2c4g.11186623.2.5.600c6d13A0lSIR
vue直傳OSS
https://blog.csdn.net/qq_33270001/article/details/88999189
el-upload組件結(jié)合上傳阿里云OSS實現(xiàn)更優(yōu)交互
https://blog.csdn.net/fifteen718/article/details/85259438
Web直傳OSS
https://blog.csdn.net/weixin_33907511/article/details/91479830
OSS文件上傳(頁面直傳)
https://blog.csdn.net/linlin_0904/article/details/84583676
請問STS和簽名帶Policy的差別
https://developer.aliyun.com/ask/205943?spm=a2c6h.13524658
STS臨時授權(quán)訪問OSS
https://www.cnblogs.com/ggband/p/10218851.html
vue+element+sts臨時授權(quán)上傳大文件到阿里云OSS時踩過的坑。
https://blog.csdn.net/aiguo94/article/details/111832776
Vue上傳阿里云OSS(STS方式)