表單上傳
OSS表單上傳允許網(wǎng)頁應(yīng)用通過標(biāo)準(zhǔn)HTML表單直接將文件上傳至OSS。這種方式下,在前端頁面選擇文件后,瀏覽器發(fā)起POST請求直接將文件傳輸?shù)?span id="z68uejxpaoma" class="help-letter-space">OSS服務(wù)器,而無需經(jīng)過網(wǎng)站服務(wù)器中轉(zhuǎn),減輕了服務(wù)器的壓力,提高了文件上傳的效率和穩(wěn)定性。
使用限制
通過表單上傳的方式上傳的Object大小不能超過5 GB。
使用場景
表單上傳廣泛應(yīng)用于Web應(yīng)用程序,包括但不限于以下幾個方面:
用戶資料上傳:注冊賬號時上傳頭像、身份證照片或其他身份驗證材料。在個人中心修改信息時上傳新的頭像或背景圖。
文件分享與存儲:在網(wǎng)盤服務(wù)、協(xié)同辦公平臺等通過表單上傳各種格式的文件,如文檔、圖片、音頻、視頻等至云端進行存儲和共享。
內(nèi)容創(chuàng)作與發(fā)布:在博客、論壇、問答社區(qū)等平臺編寫文章并通過表單上傳圖片、附件等作為內(nèi)容補充。
電商管理:商家在電商平臺后臺上傳商品圖片、詳細描述文件、資質(zhì)證書等;買家在購買過程中上傳發(fā)票需求或其他證明材料。
在線教育平臺:學(xué)生在提交作業(yè)或項目時上傳文檔、PPT、視頻等作業(yè)文件;教師在課程建設(shè)時上傳教學(xué)資料、課件等。
求職招聘網(wǎng)站:求職者上傳簡歷、作品集等求職材料;企業(yè)發(fā)布職位時上傳公司LOGO、招聘信息文件等。
問卷調(diào)查與反饋:填寫在線調(diào)查問卷時上傳附加的證據(jù)文件或說明文檔。
軟件開發(fā)協(xié)作:在代碼托管平臺如GitHub、GitLab等上傳代碼文件或項目文檔。
方案概述
您可以在服務(wù)端生成PostObject所需的Post簽名、PostPolicy等信息,然后客戶端可以憑借這些信息,在一定的限制下不依賴OSS SDK直接上傳文件。您可以借助服務(wù)端生成的PostPolicy限制客戶端上傳的文件,例如限制文件大小、文件類型。此方案適用于通過HTML表單上傳的方式上傳文件。需要注意的是,此方案不支持基于分片上傳大文件、基于分片斷點續(xù)傳的場景。更多信息,請參見PostObject。
服務(wù)端通過Post簽名和Post Policy授權(quán)客戶端上傳文件到OSS的過程如下。
客戶端向業(yè)務(wù)服務(wù)器請求Post簽名和Post Policy等信息。
業(yè)務(wù)服務(wù)器生成并返回Post簽名和Post Policy等信息給客戶端。
客戶端使用Post簽名和Post Policy等信息調(diào)用PostObject通過HTML表單的方式上傳文件到OSS。
OSS返回成功響應(yīng)給客戶端。
操作步驟
服務(wù)端生成Post簽名和Post Policy等信息。
示例工程:postsignature.zip
示例代碼
服務(wù)端生成Post簽名和Post Policy等信息的示例代碼如下:
說明當(dāng)前代碼支持一鍵部署,您可以直接在函數(shù)計算FC中一鍵部署本代碼。oss-upload-post-signature-app
Python
import os from hashlib import sha1 as sha import json import base64 import hmac import datetime import time # 配置環(huán)境變量OSS_ACCESS_KEY_ID。 access_key_id = os.environ.get('OSS_ACCESS_KEY_ID') # 配置環(huán)境變量OSS_ACCESS_KEY_SECRET。 access_key_secret = os.environ.get('OSS_ACCESS_KEY_SECRET') # 將<YOUR_BUCKET>替換為Bucket名稱。 bucket = '<YOUR_BUCKET>' # host的格式為bucketname.endpoint。將<YOUR_BUCKET>替換為Bucket名稱。將<YOUR_ENDPOINT>替換為OSS Endpoint,例如oss-cn-hangzhou.aliyuncs.com。 host = 'https://<YOUR_BUCKET>.<YOUR_ENDPOINT>' # 指定上傳到OSS的文件前綴。 upload_dir = 'user-dir-prefix/' # 指定過期時間,單位為秒。 expire_time = 3600 def generate_expiration(seconds): """ 通過指定有效的時長(秒)生成過期時間。 :param seconds: 有效時長(秒)。 :return: ISO8601 時間字符串,如:"2014-12-01T12:00:00.000Z"。 """ now = int(time.time()) expiration_time = now + seconds gmt = datetime.datetime.utcfromtimestamp(expiration_time).isoformat() gmt += 'Z' return gmt def generate_signature(access_key_secret, expiration, conditions, policy_extra_props=None): """ 生成簽名字符串Signature。 :param access_key_secret: 有權(quán)限訪問目標(biāo)Bucket的AccessKeySecret。 :param expiration: 簽名過期時間,按照ISO8601標(biāo)準(zhǔn)表示,并需要使用UTC時間,格式為yyyy-MM-ddTHH:mm:ssZ。示例值:"2014-12-01T12:00:00.000Z"。 :param conditions: 策略條件,用于限制上傳表單時允許設(shè)置的值。 :param policy_extra_props: 額外的policy參數(shù),后續(xù)如果policy新增參數(shù)支持,可以在通過dict傳入額外的參數(shù)。 :return: signature,簽名字符串。 """ policy_dict = { 'expiration': expiration, 'conditions': conditions } if policy_extra_props is not None: policy_dict.update(policy_extra_props) policy = json.dumps(policy_dict).strip() policy_encode = base64.b64encode(policy.encode()) h = hmac.new(access_key_secret.encode(), policy_encode, sha) sign_result = base64.b64encode(h.digest()).strip() return sign_result.decode() def generate_upload_params(): policy = { # 有效期。 "expiration": generate_expiration(expire_time), # 約束條件。 "conditions": [ # 未指定success_action_redirect時,上傳成功后的返回狀態(tài)碼,默認為 204。 ["eq", "$success_action_status", "200"], # 表單域的值必須以指定前綴開始。例如指定key的值以user/user1開始,則可以寫為["starts-with", "$key", "user/user1"]。 ["starts-with", "$key", upload_dir], # 限制上傳Object的最小和最大允許大小,單位為字節(jié)。 ["content-length-range", 1, 1000000], # 限制上傳的文件為指定的圖片類型 ["in", "$content-type", ["image/jpg", "image/png"]] ] } signature = generate_signature(access_key_secret, policy.get('expiration'), policy.get('conditions')) response = { 'policy': base64.b64encode(json.dumps(policy).encode('utf-8')).decode(), 'ossAccessKeyId': access_key_id, 'signature': signature, 'host': host, 'dir': upload_dir # 可以在這里再自行追加其他參數(shù) } return json.dumps(response)
Java
import com.aliyun.help.demo.uploading_to_oss_directly_postsignature.config.OssConfig; import com.aliyun.oss.ClientException; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSException; import com.aliyun.oss.common.utils.BinaryUtil; import com.aliyun.oss.model.MatchMode; import com.aliyun.oss.model.PolicyConditions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.codehaus.jettison.json.JSONObject; import java.util.Date; import com.aliyun.oss.OSSClientBuilder; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Bean; import javax.annotation.PreDestroy; @Controller public class PostSignatureController { @Autowired private OSS ossClient; @Autowired private OssConfig ossConfig; @GetMapping("/get_post_signature_for_oss_upload") @ResponseBody public String generatePostSignature() { JSONObject response = new JSONObject(); try { long expireEndTime = System.currentTimeMillis() + ossConfig.getExpireTime() * 1000; Date expiration = new Date(expireEndTime); PolicyConditions policyConds = new PolicyConditions(); policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000); policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, ossConfig.getDir()); String postPolicy = ossClient.generatePostPolicy(expiration, policyConds); byte[] binaryData = postPolicy.getBytes("utf-8"); String encodedPolicy = BinaryUtil.toBase64String(binaryData); String postSignature = ossClient.calculatePostSignature(postPolicy); response.put("ossAccessKeyId", ossConfig.getAccessKeyId()); response.put("policy", encodedPolicy); response.put("signature", postSignature); response.put("dir", ossConfig.getDir()); response.put("host", ossConfig.getHost()); } catch ( OSSException oe) { System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason."); // 假設(shè)此方法存在 System.out.println("HTTP Status Code: " + oe.getRawResponseError()); System.out.println("Error Message: " + oe.getErrorMessage()); System.out.println("Error Code: " + oe.getErrorCode()); System.out.println("Request ID: " + oe.getRequestId()); System.out.println("Host ID: " + oe.getHostId()); } catch (ClientException ce) { System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network."); System.out.println("Error Message: " + ce.getMessage()); } finally { if (ossClient != null) { ossClient.shutdown(); } return response.toString(); } } } @Configuration public class OssConfig { /** * 將 <YOUR-ENDPOINT> 替換為 Endpoint,例如 oss-cn-hangzhou.aliyuncs.com */ private String endpoint = "<YOUR-ENDPOINT>"; /** * 將 <YOUR-BUCKET> 替換為 Bucket 名稱 */ private String bucket = "<YOUR-BUCKET>"; /** * 指定上傳到 OSS 的文件前綴 */ private String dir = "user-dir-prefix/"; /** * 指定過期時間,單位為秒 */ private long expireTime = 3600; /** * 構(gòu)造 host */ private String host = "http://" + bucket + "." + endpoint; /** * 通過環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_ID 設(shè)置 accessKeyId */ private String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"); /** * 通過環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_SECRET 設(shè)置 accessKeySecret */ private String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"); private OSS ossClient; @Bean public OSS getOssClient() { ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); return ossClient; } @Bean public String getHost() { return host; } @Bean public String getAccessKeyId() { return accessKeyId; } @Bean public long getExpireTime() { return expireTime; } @Bean public String getDir() { return dir; } @PreDestroy public void onDestroy() { ossClient.shutdown(); } }
Go
package main import ( "crypto/hmac" "crypto/sha1" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "os" "time" ) var ( // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。 accessKeyId = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID") // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_SECRET。 accessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET") // host的格式為bucketname.endpoint。將${your-bucket}替換為Bucket名稱。將${your-endpoint}替換為OSS Endpoint,例如oss-cn-hangzhou.aliyuncs.com。 host = "http://${your-bucket}.${your-endpoint}" // 指定上傳到OSS的文件前綴。 uploadDir = "user-dir-prefix/" // 指定過期時間,單位為秒。 expireTime = int64(3600) ) type ConfigStruct struct { Expiration string `json:"expiration"` Conditions [][]string `json:"conditions"` } type PolicyToken struct { AccessKeyId string `json:"ossAccessKeyId"` Host string `json:"host"` Signature string `json:"signature"` Policy string `json:"policy"` Directory string `json:"dir"` } func getGMTISO8601(expireEnd int64) string { return time.Unix(expireEnd, 0).UTC().Format("2006-01-02T15:04:05Z") } func getPolicyToken() string { now := time.Now().Unix() expireEnd := now + expireTime tokenExpire := getGMTISO8601(expireEnd) var config ConfigStruct config.Expiration = tokenExpire var condition []string condition = append(condition, "starts-with") condition = append(condition, "$key") condition = append(condition, uploadDir) config.Conditions = append(config.Conditions, condition) result, err := json.Marshal(config) if err != nil { fmt.Println("callback json err:", err) return "" } encodedResult := base64.StdEncoding.EncodeToString(result) h := hmac.New(sha1.New, []byte(accessKeySecret)) io.WriteString(h, encodedResult) signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil)) policyToken := PolicyToken{ AccessKeyId: accessKeyId, Host: host, Signature: signedStr, Policy: encodedResult, Directory: uploadDir, } response, err := json.Marshal(policyToken) if err != nil { fmt.Println("json err:", err) return "" } return string(response) } func handler(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { http.ServeFile(w, r, "templates/index.html") return } else if r.URL.Path == "/get_post_signature_for_oss_upload" { policyToken := getPolicyToken() w.Header().Set("Content-Type", "application/json") w.Write([]byte(policyToken)) return } http.NotFound(w, r) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) }
PHP
<?php function gmt_iso8601($time) { return str_replace('+00:00', '.000Z', gmdate('c', $time)); } // 從環(huán)境變量中獲取訪問憑證。運行本示例代碼之前,請確保已設(shè)置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET。 $accessKeyId = getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"); $accessKeySecret = getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"); // $host的格式為<YOUR-BUCKET>.<YOUR-ENDPOINT>',請?zhí)鎿Q為您的真實信息。 $host = 'http://<YOUR-BUCKET>.<YOUR-ENDPOINT>'; // 用戶上傳文件時指定的前綴。 $dir = 'user-dir-prefix/'; $now = time(); //設(shè)置該policy超時時間是10s. 即這個policy過了這個有效時間,將不能訪問。 $expire = 30; $end = $now + $expire; $expiration = gmt_iso8601($end); //最大文件大小.用戶可以自己設(shè)置。 $condition = array(0 => 'content-length-range', 1 => 0, 2 => 1048576000); $conditions[] = $condition; // 表示用戶上傳的數(shù)據(jù),必須是以$dir開始,不然上傳會失敗,這一步不是必須項,只是為了安全起見,防止用戶通過policy上傳到別人的目錄。 $start = array(0 => 'starts-with', 1 => '$key', 2 => $dir); $conditions[] = $start; $arr = array('expiration' => $expiration, 'conditions' => $conditions); $policy = json_encode($arr); $base64_policy = base64_encode($policy); $string_to_sign = $base64_policy; $signature = base64_encode(hash_hmac('sha1', $string_to_sign, $accessKeySecret, true)); $response = array(); $response['ossAccessKeyId'] = $accessKeyId; $response['host'] = $host; $response['policy'] = $base64_policy; $response['signature'] = $signature; $response['dir'] = $dir; echo json_encode($response);
Node.js
const express = require("express"); const { Buffer } = require("buffer"); const OSS = require("ali-oss"); const app = express(); const path = require("path"); const config = { // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。 accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID, // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_SECRET。 accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET, // 將<YOUR-BUCKET>替換為Bucket名稱。 bucket: "<YOUR-BUCKET>", // 指定上傳到OSS的文件前綴。 dir: "prefix/", }; app.use(express.static(path.join(__dirname, "templates"))); app.get("/get_post_signature_for_oss_upload", async (req, res) => { const client = new OSS(config); const date = new Date(); // 設(shè)置簽名的有效期,單位為秒。 date.setSeconds(date.getSeconds() + 3600); const policy = { expiration: date.toISOString(), conditions: [ // 設(shè)置上傳文件的大小限制。 ["content-length-range", 0, 1048576000], // 限制可上傳的Bucket。 { bucket: client.options.bucket }, ], }; const formData = await client.calculatePostSignature(policy); const host = `http://${config.bucket}.${ (await client.getBucketLocation()).location }.aliyuncs.com`.toString(); const params = { policy: formData.policy, signature: formData.Signature, ossAccessKeyId: formData.OSSAccessKeyId, host, dir: config.dir, }; res.json(params); }); app.get(/^(.+)*\.(html|js)$/i, async (req, res) => { res.sendFile(path.join(__dirname, "./templates", req.originalUrl)); }); app.listen(8000, () => { console.log("http://127.0.0.1:8000"); });
Ruby
require 'sinatra' require 'base64' require 'open-uri' require 'cgi' require 'openssl' require 'json' require 'sinatra/reloader' require 'sinatra/content_for' # 設(shè)置public文件夾路徑為當(dāng)前文件夾下的templates文件夾 set :public_folder, File.dirname(__FILE__) + '/templates' # 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。 $access_key_id = ENV['ALIBABA_CLOUD_ACCESS_ID'] # 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_SECRET。 $access_key_secret = ENV['ALIBABA_CLOUD_ACCESS_SECRET'] # $host的格式為<bucketname>.<endpoint>,請?zhí)鎿Q為您的真實信息。 $host = 'http://<bucketname>.<endpoint>'; # 用戶上傳文件時指定的前綴。 $upload_dir = 'user-dir-prefix/' # 過期時間,單位為秒。 $expire_time = 30 $server_ip = "0.0.0.0" $server_port = 8000 if ARGV.length == 1 $server_port = ARGV[0] elsif ARGV.length == 2 $server_ip = ARGV[0] $server_port = ARGV[1] end puts "App server is running on: http://#{$server_ip}:#{$server_port}" def hash_to_jason(source_hash) jason_string = source_hash.to_json; jason_string.gsub!("\":[", "\": [") jason_string.gsub!("\",\"", "\", \"") jason_string.gsub!("],\"", "], \"") jason_string.gsub!("\":\"", "\": \"") jason_string end def get_token() expire_syncpoint = Time.now.to_i + $expire_time expire = Time.at(expire_syncpoint).utc.iso8601() response.headers['expire'] = expire policy_dict = {} condition_arrary = Array.new array_item = Array.new array_item.push('starts-with') array_item.push('$key') array_item.push($upload_dir) condition_arrary.push(array_item) policy_dict["conditions"] = condition_arrary policy_dict["expiration"] = expire policy = hash_to_jason(policy_dict) policy_encode = Base64.strict_encode64(policy).chomp; h = OpenSSL::HMAC.digest('sha1', $access_key_secret, policy_encode) hs = Digest::MD5.hexdigest(h) sign_result = Base64.strict_encode64(h).strip() token_dict = {} token_dict['ossAccessKeyId'] = $access_key_id token_dict['host'] = $host token_dict['policy'] = policy_encode token_dict['signature'] = sign_result token_dict['expire'] = expire_syncpoint token_dict['dir'] = $upload_dir result = hash_to_jason(token_dict) result end set :bind, $server_ip set :port, $server_port get '/get_post_signature_for_oss_upload' do token = get_token() puts "Token: #{token}" token end get '/*' do puts "********************* GET " send_file File.join(settings.public_folder, 'index.html') end end if ARGV.length == 1 $server_port = ARGV[0] elsif ARGV.length == 2 $server_ip = ARGV[0] $server_port = ARGV[1] end $server_ip = "0.0.0.0" $server_port = 8000 puts "App server is running on: http://#{$server_ip}:#{$server_port}" set :bind, $server_ip set :port, $server_port get '/get_sts_token_for_oss_upload' do token = get_sts_token_for_oss_upload() response = { "AccessKeyId" => token["Credentials"]["AccessKeyId"], "AccessKeySecret" => token["Credentials"]["AccessKeySecret"], "SecurityToken" => token["Credentials"]["SecurityToken"] } response.to_json end get '/*' do puts "********************* GET " send_file File.join(settings.public_folder, 'index.html') end
C#
using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Http; using System.IO; using System.Collections.Generic; using System; using System.Globalization; using System.Text; using System.Security.Cryptography; using Newtonsoft.Json; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace YourNamespace { public class Program { private ILogger<Program> _logger; // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。 public string AccessKeyId { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID"); // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_SECRET。 public string AccessKeySecret { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET"); // host的格式為bucketname.endpoint。將<YOUR-BUCKET>替換為Bucket名稱。將<YOUR-ENDPOINT>替換為OSS Endpoint,例如oss-cn-hangzhou.aliyuncs.com。 public string Host { get; set; } = "<YOUR-BUCKET>.<YOUR-ENDPOINT>"; // 指定上傳到OSS的文件前綴。 public string UploadDir { get; set; } = "user-dir-prefix/"; // 指定過期時間,單位為秒。 public int ExpireTime { get; set; } = 3600; public class PolicyConfig { public string expiration { get; set; } public List<List<object>> conditions { get; set; } } public class PolicyToken { public string Accessid { get; set; } public string Policy { get; set; } public string Signature { get; set; } public string Dir { get; set; } public string Host { get; set; } public string Expire { get; set; } } public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); builder.Logging.AddConsole(); var logger = builder.Services.BuildServiceProvider().GetRequiredService<ILogger<Program>>(); app.UseStaticFiles(); app.MapGet("/", async (context) => { var filePath = Path.Combine(Directory.GetCurrentDirectory(), "templates/index.html"); var htmlContent = await File.ReadAllTextAsync(filePath); await context.Response.WriteAsync(htmlContent); logger.LogInformation("GET request to root path"); }); app.MapGet("/get_post_signature_for_oss_upload", async (context) => { var program = new Program(logger); var token = program.GetPolicyToken(); logger.LogInformation($"Token: {token}"); context.Response.ContentType = "application/json"; await context.Response.WriteAsync(token); }); app.Run(); } public Program(ILogger<Program> logger) { _logger = logger; } private string ToUnixTime(DateTime dateTime) { return ((DateTimeOffset)dateTime).ToUnixTimeSeconds().ToString(); } private string GetPolicyToken() { var expireDateTime = DateTime.Now.AddSeconds(ExpireTime); var config = new PolicyConfig { expiration = FormatIso8601Date(expireDateTime), conditions = new List<List<object>>() }; config.conditions.Add(new List<object> { "content-length-range", 0, 1048576000 }); var policy = JsonConvert.SerializeObject(config); var policyBase64 = EncodeBase64("utf-8", policy); var signature = ComputeSignature(AccessKeySecret, policyBase64); var policyToken = new PolicyToken { Accessid = AccessKeyId, Host = Host, Policy = policyBase64, Signature = signature, Expire = ToUnixTime(expireDateTime), Dir = UploadDir }; return JsonConvert.SerializeObject(policyToken); } private string FormatIso8601Date(DateTime dtime) { return dtime.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.CurrentCulture); } private string EncodeBase64(string codeType, string code) { string encode = ""; byte[] bytes = Encoding.GetEncoding(codeType).GetBytes(code); try { encode = Convert.ToBase64String(bytes); } catch { encode = code; } return encode; } private string ComputeSignature(string key, string data) { using (var algorithm = new HMACSHA1(Encoding.UTF8.GetBytes(key))) { return Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(data))); } } } }
客戶端使用Post簽名和Post Policy等信息調(diào)用PostObject通過HTML表單的方式上傳文件到OSS。
以js為例:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>上傳文件到OSS</title> </head> <body> <div class="container"> <form> <div class="mb-3"> <label for="file" class="form-label">選擇文件</label> <input type="file" class="form-control" id="file" name="file" required> </div> <button type="submit" class="btn btn-primary">上傳</button> </form> </div> <script type="text/javascript"> const form = document.querySelector('form'); const fileInput = document.querySelector('#file'); form.addEventListener('submit', (event) => { event.preventDefault(); let file = fileInput.files[0]; let filename = fileInput.files[0].name; fetch('/get_post_signature_for_oss_upload', { method: 'GET' }) .then(response => response.json()) .then(data => { const formData = new FormData(); formData.append('name',filename); formData.append('policy', data.policy); formData.append('OSSAccessKeyId', data.ossAccessKeyId); formData.append('success_action_status', '200'); formData.append('signature', data.signature); formData.append('key', data.dir + filename); // file必須為最后一個表單域,除file以外的其他表單域無順序要求。 formData.append('file', file); fetch(data.host, { method: 'POST', body: formData},).then((res) => { console.log(res); alert('文件已上傳'); }); }) .catch(error => { console.log('Error occurred while getting OSS upload parameters:', error); }); }); </script> </body> </html>
注意事項
數(shù)據(jù)安全
防止覆蓋同名Object
上傳時默認會覆蓋同名Object,您可以選擇以下任意方式防止Object被意外覆蓋。
開啟版本控制功能
開啟版本控制功能后,被覆蓋的Object會以歷史版本的形式保存下來。您可以隨時恢復(fù)歷史版本Object。更多信息,請參見版本控制介紹。
在上傳請求中攜帶禁止覆蓋同名Object的參數(shù)
在上傳請求的Header中攜帶參數(shù)x-oss-forbid-overwrite,并指定其值為true。當(dāng)您上傳的Object在OSS中存在同名Object時,該Object會上傳失敗,并返回
FileAlreadyExists
錯誤。當(dāng)不攜帶此參數(shù)或此參數(shù)的值為false時,同名Object會被覆蓋。
授權(quán)上傳
為了防止第三方用戶未經(jīng)授權(quán)在您的Bucket中上傳數(shù)據(jù),OSS提供了Bucket和Object級別的訪問權(quán)限控制。更多信息,請參見訪問控制。
使用簽名URL授權(quán)第三方用戶上傳指定文件。簽名URL允許第三方用戶在沒有安全憑證或者授權(quán)的情況下進行上傳操作。第三方用戶使用簽名URL上傳文件后,OSS將在指定的Bucket生成該文件。使用文件URL上傳文件。
降低PUT類請求費用
如果要上傳的文件數(shù)量較多,直接指定上傳的文件類型為深度冷歸檔類型會造成較高的PUT類請求費用。建議您先將文件的存儲類型指定為標(biāo)準(zhǔn)存儲進行上傳,然后通過生命周期規(guī)則將其轉(zhuǎn)儲為深度冷歸檔類型,從而降低PUT類請求費用。
避免影響OSS-HDFS服務(wù)
為避免影響OSS-HDFS服務(wù)的正常使用或者引發(fā)數(shù)據(jù)丟失的風(fēng)險,在開通了OSS-HDFS服務(wù)的Bucket中,禁止以非OSS-HDFS服務(wù)提供的方式在OSS-HDFS的數(shù)據(jù)存儲目錄.dlsdata/
中上傳Object。
上傳性能調(diào)優(yōu)
如果您在上傳大量Object時,在命名上使用了順序前綴(如時間戳或字母順序),可能會出現(xiàn)大量Object索引集中存儲于存儲空間中某個特定分區(qū)的情況,進而導(dǎo)致請求速率下降。建議您在上傳大量Object時,不要使用順序前綴的Object名稱,而是將順序前綴改為隨機性前綴。具體操作,請參見OSS性能與擴展性最佳實踐。