全部產品
Search
文件中心

Object Storage Service:使用預簽名URL上傳檔案

更新時間:Dec 12, 2025

預設情況下,OSS儲存空間中檔案的讀寫權限是私人,僅檔案擁有者可以上傳。但檔案擁有者可以對指定的檔案產生具有臨時存取權限的預簽名URL,以允許他人使用該預簽名URL在有效期間內上傳檔案。該功能適用於授權夥伴上傳合約,使用者上傳頭像等情境。

注意事項

  • 本文以華東1(杭州)外網Endpoint為例。如果您希望通過與OSS同地區的其他阿里雲產品訪問OSS,請使用內網Endpoint。關於OSS支援的Region與Endpoint的對應關係,請參見地區和Endpoint

  • 預簽名URL無需許可權即可產生,但僅當您擁有oss:PutObject許可權時,第三方才能通過該預簽名URL成功上傳檔案。具體授權操作,請參見為RAM使用者授權自訂的權限原則

  • 預簽名URL上傳不支援上傳FormData格式,若需使用FormData上傳資料,建議使用OSS表單上傳

重要

根據策略調整,為提升OSS服務的合規性和安全性,自2025年3月20日起,新開通OSS服務的使用者在中國內地地區的Bucket將無法通過預設外網網域名稱調用資料操作類API(如上傳、下載檔案),需通過自訂網域名(CNAME)方式訪問OSS服務。使用HTTPS協議訪問(如控制台)時,還需為自訂網域名配置SSL認證

流程概覽

使用預簽名URL上傳檔案的過程如下:

使用預簽名URL上傳單個檔案

  1. Bucket擁有者產生PUT方法的預簽名URL

    說明

    通過SDK產生的預簽名URL,最大有效時間長度為7天。若使用STSToken來產生預簽名URL,則最大有效時間長度為43200秒(12小時)。

    Java

    更多SDK資訊,請參見Java使用預簽名URL上傳檔案

    import com.aliyun.oss.*;
    import com.aliyun.oss.common.auth.*;
    import com.aliyun.oss.common.comm.SignVersion;
    import com.aliyun.oss.model.GeneratePresignedUrlRequest;
    import java.net.URL;
    import java.util.*;
    import java.util.Date;
    
    public class GetSignUrl {
        public static void main(String[] args) throws Throwable {
            // 以華東1(杭州)的外網Endpoint為例,其它Region請按實際情況填寫。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 填寫Bucket名稱,例如examplebucket。
            String bucketName = "examplebucket";
            // 填寫Object完整路徑,例如exampleobject.txt。Object完整路徑中不能包含Bucket名稱。
            String objectName = "exampleobject.txt";
            // 填寫Bucket所在地區。以華東1(杭州)為例,Region填寫為cn-hangzhou。
            String region = "cn-hangzhou";
    
            // 建立OSSClient執行個體。
            // 當OSSClient執行個體不再使用時,調用shutdown方法以釋放資源。
            ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
            clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
            OSS ossClient = OSSClientBuilder.create()
                    .endpoint(endpoint)
                    .credentialsProvider(credentialsProvider)
                    .clientConfiguration(clientBuilderConfiguration)
                    .region(region)
                    .build();
    
            URL signedUrl = null;
            try {
                // 指定產生的預簽名URL到期時間,單位為毫秒。本樣本以設定到期時間為1小時為例。
                Date expiration = new Date(new Date().getTime() + 3600 * 1000L);
    
                // 產生預簽名URL。
                GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
                // 設定到期時間。
                request.setExpiration(expiration);
                // 通過HTTP PUT請求產生預簽名URL。
                signedUrl = ossClient.generatePresignedUrl(request);
                // 列印預簽名URL。
                System.out.println("signed url for putObject: " + signedUrl);
    
            } 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.");
                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());
            }
        }
    }       

    Go

    更多SDK資訊,請參見Go使用預簽名URL上傳檔案。

    package main
    
    import (
    	"context"
    	"flag"
    	"log"
    	"time"
    
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
    )
    
    // 定義全域變數
    var (
    	region     string // 儲存地區
    	bucketName string // 儲存空間名稱
    	objectName string // 對象名稱
    )
    
    // init函數用於初始化命令列參數
    func init() {
    	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
    	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
    	flag.StringVar(&objectName, "object", "", "The name of the object.")
    }
    
    func main() {
    	// 解析命令列參數
    	flag.Parse()
    
    	// 檢查bucket名稱是否為空白
    	if len(bucketName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, bucket name required")
    	}
    
    	// 檢查region是否為空白
    	if len(region) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, region required")
    	}
    
    	// 檢查object名稱是否為空白
    	if len(objectName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, object name required")
    	}
    
    	// 載入預設配置並設定憑證提供者和地區
    	cfg := oss.LoadDefaultConfig().
    		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
    		WithRegion(region)
    
    	// 建立OSS用戶端
    	client := oss.NewClient(cfg)
    
    	// 產生PutObject的預簽名URL
    	result, err := client.Presign(context.TODO(), &oss.PutObjectRequest{
    		Bucket: oss.Ptr(bucketName),
    		Key:    oss.Ptr(objectName),
    	},
    		oss.PresignExpires(10*time.Minute),
    	)
    	if err != nil {
    		log.Fatalf("failed to put object presign %v", err)
    	}
    
    	log.Printf("request method:%v\n", result.Method)
    	log.Printf("request expiration:%v\n", result.Expiration)
    	log.Printf("request url:%v\n", result.URL)
    	if len(result.SignedHeaders) > 0 {
    		//當返回結果包含簽名頭時,使用簽名URL發送Put請求時,需要設定相應的要求標頭
    		log.Printf("signed headers:\n")
    		for k, v := range result.SignedHeaders {
    			log.Printf("%v: %v\n", k, v)
    		}
    	}
    }
    

    Python

    更多SDK資訊,請參見Python使用預簽名URL上傳檔案

    import argparse
    import requests
    import alibabacloud_oss_v2 as oss
    
    from datetime import datetime, timedelta
    
    # 建立命令列參數解析器,並描述指令碼用途:產生對象預簽名PUT請求連結(Presign Put Object)
    parser = argparse.ArgumentParser(description="presign put object sample")
    
    # 定義命令列參數,包括必需的地區、儲存空間名稱、endpoint以及對象鍵名
    parser.add_argument('--region', help='The region in which the bucket is located.', required=True)
    parser.add_argument('--bucket', help='The name of the bucket.', required=True)
    parser.add_argument('--endpoint', help='The domain names that other services can use to access OSS')
    parser.add_argument('--key', help='The name of the object.', required=True)
    
    def main():
        # 解析命令列參數,擷取使用者輸入的值
        args = parser.parse_args()
    
        # 從環境變數中載入訪問憑證資訊,用於身分識別驗證
        credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    
        # 使用SDK預設配置建立設定物件,並設定認證提供者
        cfg = oss.config.load_default()
        cfg.credentials_provider = credentials_provider
    
        # 設定設定物件的地區屬性,根據使用者提供的命令列參數
        cfg.region = args.region
    
        # 如果提供了自訂endpoint,則更新設定物件中的endpoint屬性
        if args.endpoint is not None:
            cfg.endpoint = args.endpoint
    
        # 使用上述配置初始化OSS用戶端,準備與OSS互動
        client = oss.Client(cfg)
    
        # 發送請求以產生指定對象的預簽名PUT請求
        pre_result = client.presign(oss.PutObjectRequest(
            bucket=args.bucket,  # 儲存空間名
            key=args.key,  # 對象鍵名
        ),expires=timedelta(seconds=3600)) # 設定到期時間,單位為秒,此處設定為3600秒
    
        # 列印預簽章要求的方法、到期時間和URL,以便確認預簽名連結的有效性
        print(f'method: {pre_result.method},'
              f' expiration: {pre_result.expiration.strftime("%Y-%m-%dT%H:%M:%S.000Z")},'
              f' url: {pre_result.url}'
              )
    
        # 列印預簽章要求的已簽名頭資訊,這些資訊在發送實際請求時會被包含在HTTP頭部
        for key, value in pre_result.signed_headers.items():
            print(f'signed headers key: {key}, signed headers value: {value}')
    
    # 當此指令碼被直接執行時,調用main函數開始處理邏輯
    if __name__ == "__main__":
        main()  # 指令碼進入點,控製程序流程從這裡開始

    Node.js

    此處僅列舉普通情境,如何產生帶圖片處理參數的預簽名URL、如何產生帶versionID的預簽名URL,請參見Node.js使用預簽名URL上傳

    const OSS = require("ali-oss");
    
    // 定義一個產生簽名 URL 的函數
    async function generateSignatureUrl(fileName) {
      // 擷取預簽名URL
      const client = await new OSS({
          accessKeyId: 'yourAccessKeyId',
          accessKeySecret: 'yourAccessKeySecret',
          bucket: 'examplebucket',
          region: 'oss-cn-hangzhou',
          authorizationV4: true
      });
    
      return await client.signatureUrlV4('PUT', 3600, {
          headers: {} // 請根據實際發送的要求標頭設定此處的要求標頭
      }, fileName);
    }
    // 調用函數並傳入檔案名稱
    generateSignatureUrl('yourFileName').then(url => {
      console.log('Generated Signature URL:', url);
    }).catch(err => {
      console.error('Error generating signature URL:', err);
    });

    PHP

    此處僅列舉普通情境,如何產生帶versionID的預簽名URL,請參見PHP使用預簽名URL上傳

    <?php
    if (is_file(__DIR__ . '/../autoload.php')) {
        require_once __DIR__ . '/../autoload.php';
    }
    if (is_file(__DIR__ . '/../vendor/autoload.php')) {
        require_once __DIR__ . '/../vendor/autoload.php';
    }
    
    use OSS\OssClient;
    use OSS\Core\OssException;
    use OSS\Http\RequestCore;
    use OSS\Http\ResponseCore;
    use OSS\Credentials\EnvironmentVariableCredentialsProvider;
    
    // 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    $provider = new EnvironmentVariableCredentialsProvider();
    // yourEndpoint填寫Bucket所在地區對應的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。
    $endpoint = "yourEndpoint";
    // 填寫Bucket名稱。
    $bucket= "examplebucket";
    // 填寫不包含Bucket名稱在內的Object完整路徑。
    $object = "exampleobject.txt";
    // 指定預簽名URL的到期時間為600s(最長可達32400s)。
    $timeout = 600;
    try {
        $config = array(  
            "provider" => $provider,
            "endpoint" => $endpoint,
            'signatureVersion'=>OssClient::OSS_SIGNATURE_VERSION_V4,
            "region"=> "cn-hangzhou"
        );
        $ossClient = new OssClient($config);
        // 產生預簽名URL。
        $signedUrl = $ossClient->signUrl($bucket, $object, $timeout, "PUT");
        print_r($signedUrl);
    } catch (OssException $e) {
        printf(__FUNCTION__ . ": FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }           

    Android

    更多SDK資訊,請參見Android使用預簽名URL上傳檔案

    // 填寫Bucket名稱,例如examplebucket。
    String bucketName = "examplebucket";
    // 填寫不包含Bucket名稱在內源Object的完整路徑,例如exampleobject.txt。
    String objectKey = "exampleobject.txt";
    // 設定content-type。
    String contentType = "application/octet-stream";
    String url = null;
    try {
        // 產生用於上傳檔案的預簽名URL。
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectKey);
        // 設定預簽名URL的到期時間為30分鐘。
        request.setExpiration(30*60);
        request.setContentType(contentType);    
        request.setMethod(HttpMethod.PUT);
        url = oss.presignConstrainedObjectURL(request);
        Log.d("url", url);
    } catch (ClientException e) {
        e.printStackTrace();
    }

    C++

    更多SDK資訊,請參見C++使用預簽名URL上傳檔案

    #include <alibabacloud/oss/OssClient.h>
    using namespace AlibabaCloud::OSS;
    
    int main(void)
    {
        /* 初始化OSS帳號資訊。*/
                
        /* yourEndpoint填寫Bucket所在地區對應的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。*/
        std::string Endpoint = "yourEndpoint";
        /* yourRegion填寫Bucket所在地區對應的Region。以華東1(杭州)為例,Region填寫為cn-hangzhou。*/
        std::string Region = "yourRegion";
        /* 填寫Bucket名稱,例如examplebucket。*/
        std::string BucketName = "examplebucket";
        /* 填寫Object完整路徑,完整路徑中不能包含Bucket名稱,例如exampledir/exampleobject.txt。*/   
        std::string PutobjectUrlName = "exampledir/exampleobject.txt";
    
         /* 初始化網路等資源。*/
        InitializeSdk();
    
        ClientConfiguration conf;
        conf.signatureVersion = SignatureVersionType::V4;
        /* 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。*/
        auto credentialsProvider = std::make_shared<EnvironmentVariableCredentialsProvider>();
        OssClient client(Endpoint, credentialsProvider, conf);
        client.SetRegion(Region);
    
        /* 設定簽名有效時間長度,最大有效時間為32400秒。*/
        std::time_t t = std::time(nullptr) + 1200;
        /* 產生預簽名URL。*/
        auto genOutcome = client.GeneratePresignedUrl(BucketName, PutobjectUrlName, t, Http::Put);
        if (genOutcome.isSuccess()) {
            std::cout << "GeneratePresignedUrl success, Gen url:" << genOutcome.result().c_str() << std::endl;
        }
        else {
            /* 異常處理。*/
            std::cout << "GeneratePresignedUrl fail" <<
            ",code:" << genOutcome.error().Code() <<
            ",message:" << genOutcome.error().Message() <<
            ",requestId:" << genOutcome.error().RequestId() << std::endl;
            return -1;
        }
    
        /* 釋放網路等資源。*/
        ShutdownSdk();
        return 0;
    }

    iOS

    更多SDK資訊,請參見iOS使用預簽名URL上傳檔案

    // 填寫Bucket名稱。
    NSString *bucketName = @"examplebucket";
    // 填寫Object名稱。
    NSString *objectKey = @"exampleobject.txt";
    NSURL *file = [NSURL fileURLWithPath:@"<filePath>"];
    NSString *contentType = [OSSUtil detemineMimeTypeForFilePath:file.absoluteString uploadName:objectKey];
    __block NSString *urlString;
    // 產生用於上傳的預簽名URL,並指定預簽名URL到期時間為30分鐘。
    OSSTask *task = [client presignConstrainURLWithBucketName:bucketName
                                                withObjectKey:objectKey
                                                   httpMethod:@"PUT"
                                       withExpirationInterval:30 * 60
                                               withParameters:@{}
                                                  contentType:contentType
                                                   contentMd5:nil];
    [task continueWithBlock:^id _Nullable(OSSTask * _Nonnull task) {
        if (task.error) {
            NSLog(@"presign error: %@", task.error);
        } else {
            urlString = task.result;
            NSLog(@"url: %@", urlString);
        }
        return nil;
    }];

    .NET

    更多SDK資訊,請參見.NET使用預簽名URL上傳

    using Aliyun.OSS;
    using Aliyun.OSS.Common;
    // 填寫Bucket所在地區對應的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。
    var endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    var accessKeyId = Environment.GetEnvironmentVariable("OSS_ACCESS_KEY_ID");
    var accessKeySecret = Environment.GetEnvironmentVariable("OSS_ACCESS_KEY_SECRET");
    // 填寫Bucket名稱,例如examplebucket。
    var bucketName = "examplebucket";
    // 填寫Object完整路徑,完整路徑中不包含Bucket名稱,例如exampledir/exampleobject.txt。
    var objectName = "exampledir/exampleobject.txt";
    var objectContent = "More than just cloud.";
    // 填寫Bucket所在地區對應的Region。以華東1(杭州)為例,Region填寫為cn-hangzhou。
    const string region = "cn-hangzhou";
    
    // 建立ClientConfiguration執行個體,按照您的需要修改預設參數。
    var conf = new ClientConfiguration();
    
    // 設定v4簽名。
    conf.SignatureVersion = SignatureVersion.V4;
    
    // 建立OssClient執行個體。
    var client = new OssClient(endpoint, accessKeyId, accessKeySecret, conf);
    conf.SetRegion(region);
    try
    {
        // 產生預簽名URL。
        var generatePresignedUriRequest = new GeneratePresignedUriRequest(bucketName, objectName, SignHttpMethod.Put)
        {
            // 設定預簽名URL到期時間,預設值為3600秒。
            Expiration = DateTime.Now.AddHours(1),
        };
        var signedUrl = client.GeneratePresignedUri(generatePresignedUriRequest);
    }
    catch (OssException ex)
    {
        Console.WriteLine("Failed with error code: {0}; Error info: {1}. \nRequestID:{2}\tHostID:{3}",
            ex.ErrorCode, ex.Message, ex.RequestId, ex.HostId);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Failed with error info: {0}", ex.Message);
    }

    C

    更多SDK資訊,請參見C使用預簽名URL下載檔案

    #include "oss_api.h"
    #include "aos_http_io.h"
    /* yourEndpoint填寫Bucket所在地區對應的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。*/
    const char *endpoint = "yourEndpoint";
    
    /* 填寫Bucket名稱,例如examplebucket。*/
    const char *bucket_name = "examplebucket";
    /* 填寫Object完整路徑,完整路徑中不能包含Bucket名稱,例如exampledir/exampleobject.txt。*/
    const char *object_name = "exampledir/exampleobject.txt";
    /* 填寫本地檔案的完整路徑。*/
    const char *local_filename = "yourLocalFilename";
    void init_options(oss_request_options_t *options)
    {
        options->config = oss_config_create(options->pool);
        /* 用char*類型的字串初始化aos_string_t類型。*/
        aos_str_set(&options->config->endpoint, endpoint);
        /* 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。*/
        aos_str_set(&options->config->access_key_id, getenv("OSS_ACCESS_KEY_ID"));
        aos_str_set(&options->config->access_key_secret, getenv("OSS_ACCESS_KEY_SECRET"));
        /* 是否使用CNAME訪問OSS服務。0表示不使用。*/
        options->config->is_cname = 0;
        /* 設定網路相關參數,例如逾時時間等。*/
        options->ctl = aos_http_controller_create(options->pool, 0);
    }
    int main(int argc, char *argv[])
    {
        /* 在程式入口調用aos_http_io_initialize方法來初始化網路、記憶體等全域資源。*/
        if (aos_http_io_initialize(NULL, 0) != AOSE_OK) {
            exit(1);
        }
        /* 用於記憶體管理的記憶體池(pool),等價於apr_pool_t。其實現代碼在apr庫中。*/
        aos_pool_t *pool;
        /* 重新建立一個記憶體池,第二個參數是NULL,表示沒有繼承其它記憶體池。*/
        aos_pool_create(&pool, NULL);
        /* 建立並初始化options,該參數包括endpoint、access_key_id、acces_key_secret、is_cname、curl等全域配置資訊。*/
        oss_request_options_t *oss_client_options;
        /* 在記憶體池中分配記憶體給options。*/
        oss_client_options = oss_request_options_create(pool);
        /* 初始化Client的選項oss_client_options。*/
        init_options(oss_client_options);
        /* 初始化參數。*/
        aos_string_t bucket;
        aos_string_t object;
        aos_string_t file;    
        aos_http_request_t *req;
        apr_time_t now;
        char *url_str;
        aos_string_t url;
        int64_t expire_time; 
        int one_hour = 3600;
        aos_str_set(&bucket, bucket_name);
        aos_str_set(&object, object_name);
        aos_str_set(&file, local_filename);
        expire_time = now / 1000000 + one_hour;    
        req = aos_http_request_create(pool);
        req->method = HTTP_PUT;
        now = apr_time_now(); 
        /* 單位:微秒 */
        expire_time = now / 1000000 + one_hour;
        /* 產生預簽名URL。*/
        url_str = oss_gen_signed_url(oss_client_options, &bucket, &object, expire_time, req);
        aos_str_set(&url, url_str);
        printf("臨時上傳URL: %s\n", url_str);    
        /* 釋放記憶體池,相當於釋放了請求過程中各資源分派的記憶體。*/
        aos_pool_destroy(pool);
        /* 釋放之前分配的全域資源。*/
        aos_http_io_deinitialize();
        return 0;
    }
  1. 其他人使用PUT方法產生的預簽名URL上傳檔案。

    重要

    預簽名URL在有效期間內可多次訪問,但多次執行上傳操作,會有檔案覆蓋的風險。超期後,需執行步驟一重建預簽名URL以繼續訪問檔案。

    curl

    curl -X PUT -T /path/to/local/file "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"

    Java

    import org.apache.http.HttpEntity;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpPut;
    import org.apache.http.entity.FileEntity;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import java.io.*;
    import java.net.URL;
    import java.util.*;
    
    public class SignUrlUpload {
        public static void main(String[] args) throws Throwable {
            CloseableHttpClient httpClient = null;
            CloseableHttpResponse response = null;
    
            // 將<signedUrl>替換為授權URL。
            URL signedUrl = new URL("<signedUrl>");
    
            // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
            String pathName = "C:\\Users\\demo.txt";
    
            try {
                HttpPut put = new HttpPut(signedUrl.toString());
                System.out.println(put);
                HttpEntity entity = new FileEntity(new File(pathName));
                put.setEntity(entity);
                httpClient = HttpClients.createDefault();
                response = httpClient.execute(put);
    
                System.out.println("返回上傳狀態代碼:"+response.getStatusLine().getStatusCode());
                if(response.getStatusLine().getStatusCode() == 200){
                    System.out.println("使用網路程式庫上傳成功");
                }
                System.out.println(response.toString());
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                response.close();
                httpClient.close();
            }
        }
    }       

    Go

    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"os"
    )
    
    func uploadFile(signedUrl, filePath string) error {
    	// 開啟檔案
    	file, err := os.Open(filePath)
    	if err != nil {
    		return fmt.Errorf("無法開啟檔案: %w", err)
    	}
    	defer file.Close()
    
    	// 建立一個新的HTTP用戶端
    	client := &http.Client{}
    
    	// 建立一個PUT請求
    	req, err := http.NewRequest("PUT", signedUrl, file)
    	if err != nil {
    		return fmt.Errorf("建立請求失敗: %w", err)
    	}
    
    	// 發送請求
    	resp, err := client.Do(req)
    	if err != nil {
    		return fmt.Errorf("發送請求失敗: %w", err)
    	}
    	defer resp.Body.Close()
    
    	// 讀取響應
    	body, err := io.ReadAll(resp.Body)
    	if err != nil {
    		return fmt.Errorf("讀取響應失敗: %w", err)
    	}
    
    	fmt.Printf("返回上傳狀態代碼: %d\n", resp.StatusCode)
    	if resp.StatusCode == 200 {
    		fmt.Println("使用網路程式庫上傳成功")
    	}
    	fmt.Println(string(body))
    
    	return nil
    }
    
    func main() {
    	// 將<signedUrl>替換為授權URL。
    	signedUrl := "<signedUrl>"
    
    	// 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
    	filePath := "C:\\Users\\demo.txt"
    
    	err := uploadFile(signedUrl, filePath)
    	if err != nil {
    		fmt.Println("發生錯誤:", err)
    	}
    }
    

    python

    import requests
    
    def upload_file(signed_url, file_path):
        try:
            # 開啟檔案
            with open(file_path, 'rb') as file:
                # 發送PUT請求上傳檔案
                response = requests.put(signed_url, data=file)
         
            print(f"返回上傳狀態代碼:{response.status_code}")
            if response.status_code == 200:
                print("使用網路程式庫上傳成功")
            print(response.text)
     
        except Exception as e:
            print(f"發生錯誤:{e}")
    
    if __name__ == "__main__":
        # 將<signedUrl>替換為授權URL。
        signed_url = "<signedUrl>"
        
        # 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
        file_path = "C:\\Users\\demo.txt"
    
        upload_file(signed_url, file_path)
    

    Node.js

    const fs = require('fs');
    const axios = require('axios');
    
    async function uploadFile(signedUrl, filePath) {
        try {
            // 建立讀取流
            const fileStream = fs.createReadStream(filePath);
            
            // 發送PUT請求上傳檔案
            const response = await axios.put(signedUrl, fileStream, {
                headers: {
                    'Content-Type': 'application/octet-stream' // 根據實際情況調整Content-Type
                }
            });
    
            console.log(`返回上傳狀態代碼:${response.status}`);
            if (response.status === 200) {
                console.log('使用網路程式庫上傳成功');
            }
            console.log(response.data);
        } catch (error) {
            console.error(`發生錯誤:${error.message}`);
        }
    }
    
    // 主函數
    (async () => {
        // 將<signedUrl>替換為授權URL。
        const signedUrl = '<signedUrl>';
        
        // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
        const filePath = 'C:\\Users\\demo.txt';
    
        await uploadFile(signedUrl, filePath);
    })();

    browser.js

    重要

    如果您使用 Browser.js 上傳檔案時遇到 403 簽名不匹配錯誤,通常是因為瀏覽器會自動添加 Content-Type 要求標頭,而產生預簽名 URL 時未指定該要求標頭,導致簽名驗證失敗。為解決此問題,您需要在產生預簽名 URL 時指定 Content-Type 要求標頭。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>File Upload Example</title>
    </head>
    <body>
        <h1>File Upload Example</h1>
    
        <!-- 選擇檔案 -->
        <input type="file" id="fileInput" />
        <button id="uploadButton">Upload File</button>
    
        <script>
            // 請將此替換為步驟一產生的預簽名 URL。
            const signedUrl = "<signedUrl>"; 
    
    
            document.getElementById('uploadButton').addEventListener('click', async () => {
                const fileInput = document.getElementById('fileInput');
                const file = fileInput.files[0];
    
                if (!file) {
                    alert('Please select a file to upload.');
                    return;
                }
    
                try {
                    await upload(file, signedUrl);
                    alert('File uploaded successfully!');
                } catch (error) {
                    console.error('Error during upload:', error);
                    alert('Upload failed: ' + error.message);
                }
            });
    
            /**
             * 上傳檔案到 OSS
             * @param {File} file - 需要上傳的檔案
             * @param {string} presignedUrl - 預簽名 URL
             */
            const upload = async (file, presignedUrl) => {
                const response = await fetch(presignedUrl, {
                    method: 'PUT',
                    body: file,  // 直接上傳整個檔案
                });
    
                if (!response.ok) {
                    throw new Error(`Upload failed, status: ${response.status}`);
                }
    
                console.log('File uploaded successfully');
            };
        </script>
    </body>
    </html>

    C#

    using System.Net.Http.Headers;
    
    // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案
    var filePath = "C:\\Users\\demo.txt";
    // 將<signedUrl>替換為授權URL
    var presignedUrl = "<signedUrl>";
    
    // 建立HTTP用戶端並開啟本地檔案流
    using var httpClient = new HttpClient(); 
    using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
    using var content = new StreamContent(fileStream);
                
    // 建立PUT請求
    var request = new HttpRequestMessage(HttpMethod.Put, presignedUrl);
    request.Content = content;
    
    // 發送請求
    var response = await httpClient.SendAsync(request);
    
    // 處理響應
    if (response.IsSuccessStatusCode)
    {
        Console.WriteLine($"上傳成功!狀態代碼: {response.StatusCode}");
        Console.WriteLine("回應標頭部:");
        foreach (var header in response.Headers)
        {
            Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
        }
    }
    else
    {
        string responseContent = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"上傳失敗!狀態代碼: {response.StatusCode}");
        Console.WriteLine("響應內容: " + responseContent);
    }

    C++

    #include <iostream>
    #include <fstream>
    #include <curl/curl.h>
    
    void uploadFile(const std::string& signedUrl, const std::string& filePath) {
        CURL *curl;
        CURLcode res;
    
        curl_global_init(CURL_GLOBAL_DEFAULT);
        curl = curl_easy_init();
    
        if (curl) {
            // 設定URL
            curl_easy_setopt(curl, CURLOPT_URL, signedUrl.c_str());
    
            // 佈建要求方法為PUT
            curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
    
            // 開啟檔案
            FILE *file = fopen(filePath.c_str(), "rb");
            if (!file) {
                std::cerr << "無法開啟檔案: " << filePath << std::endl;
                return;
            }
    
            // 擷取檔案大小
            fseek(file, 0, SEEK_END);
            long fileSize = ftell(file);
            fseek(file, 0, SEEK_SET);
    
            // 設定檔案大小
            curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);
    
            // 設定輸入檔案控制代碼
            curl_easy_setopt(curl, CURLOPT_READDATA, file);
    
            // 執行請求
            res = curl_easy_perform(curl);
    
            if (res != CURLE_OK) {
                std::cerr << "curl_easy_perform() 失敗: " << curl_easy_strerror(res) << std::endl;
            } else {
                long httpCode = 0;
                curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
                std::cout << "返回上傳狀態代碼: " << httpCode << std::endl;
    
                if (httpCode == 200) {
                    std::cout << "使用網路程式庫上傳成功" << std::endl;
                }
            }
    
            // 關閉檔案
            fclose(file);
    
            // 清理
            curl_easy_cleanup(curl);
        }
    
        curl_global_cleanup();
    }
    
    int main() {
        // 將<signedUrl>替換為授權URL。
        std::string signedUrl = "<signedUrl>";
    
        // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
        std::string filePath = "C:\\Users\\demo.txt";
    
        uploadFile(signedUrl, filePath);
    
        return 0;
    }
    

    Android

    package com.example.signurlupload;
    
    import android.os.AsyncTask;
    import android.util.Log;
    
    import java.io.DataOutputStream;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    public class SignUrlUploadActivity {
    
        private static final String TAG = "SignUrlUploadActivity";
    
        public void uploadFile(String signedUrl, String filePath) {
            new UploadTask().execute(signedUrl, filePath);
        }
    
        private class UploadTask extends AsyncTask<String, Void, String> {
    
            @Override
            protected String doInBackground(String... params) {
                String signedUrl = params[0];
                String filePath = params[1];
    
                HttpURLConnection connection = null;
                DataOutputStream dos = null;
                FileInputStream fis = null;
    
                try {
                    URL url = new URL(signedUrl);
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("PUT");
                    connection.setDoOutput(true);
                    connection.setRequestProperty("Content-Type", "application/octet-stream");
    
                    fis = new FileInputStream(filePath);
                    dos = new DataOutputStream(connection.getOutputStream());
    
                    byte[] buffer = new byte[1024];
                    int length;
    
                    while ((length = fis.read(buffer)) != -1) {
                        dos.write(buffer, 0, length);
                    }
    
                    dos.flush();
                    dos.close();
                    fis.close();
    
                    int responseCode = connection.getResponseCode();
                    Log.d(TAG, "返回上傳狀態代碼: " + responseCode);
    
                    if (responseCode == 200) {
                        Log.d(TAG, "使用網路程式庫上傳成功");
                    }
    
                    return "上傳完成,狀態代碼: " + responseCode;
    
                } catch (IOException e) {
                    e.printStackTrace();
                    return "上傳失敗: " + e.getMessage();
                } finally {
                    if (connection != null) {
                        connection.disconnect();
                    }
                }
            }
    
            @Override
            protected void onPostExecute(String result) {
                Log.d(TAG, result);
            }
        }
    
        public static void main(String[] args) {
            SignUrlUploadActivity activity = new SignUrlUploadActivity();
            // 將<signedUrl>替換為授權URL。
            String signedUrl = "<signedUrl>";
            // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
            String filePath = "C:\\Users\\demo.txt";
            activity.uploadFile(signedUrl, filePath);
        }
    }
    

使用預簽名URL分區上傳檔案

當用戶端無法整合OSS SDK,但需上傳大檔案時,可採用預簽名 URL 分區上傳方案。該方式由服務端初始化上傳任務,並為每個分區產生帶有許可權的預簽名 URL,供用戶端或第三方應用上傳。上傳完成後,服務端再發起合并,產生完整檔案。

重要

由於預簽名 URL 分區上傳相對複雜,用戶端必須按URL中的 partNumber 上傳對應的分區,缺失或錯位都會導致最終合并的檔案不正確。因此,若您的用戶端支援整合 OSS SDK,更推薦使用STS授權實現用戶端直傳,開發更簡單,上傳更穩定。

  1. 用戶端發起上傳請求

    用戶端向服務端發起上傳請求,傳入檔案名稱、檔案大小、期望的分區大小(不超過5GB,推薦5MB)。

    樣本如下,其中https://yourserver.com/init-upload是您服務端提供的初始化介面地址,請根據實際替換。

    curl -X POST https://yourserver.com/init-upload \
      -H "Content-Type: application/json" \
      -d '{
        "fileName": "exampleobject.jpg",
        "fileSize": 104857600,
        "partSize": 5242880
    }'
  2. 服務端初始化上傳任務並產生預簽名 URL

    1. 調用InitiateMultipartUpload初始化上傳任務,擷取UploadId

    2. 根據檔案大小和期望的分區大小,計算分區個數

    3. 為每個分區產生對應的預簽名URL

    4. 返回URL列表(JSON 格式)

    說明

    服務端應記錄UploadId,並確保它能與上傳的檔案建立唯一對應關係,以便後續完成分區校正與合并。您可結合業務需求,使用緩衝或資料庫等方式儲存UploadId。

    範例程式碼:初始化上傳請求並產生預簽名 URL

    Java

    import com.aliyun.oss.ClientBuilderConfiguration;
    import com.aliyun.oss.HttpMethod;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.common.auth.CredentialsProviderFactory;
    import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
    import com.aliyun.oss.common.comm.SignVersion;
    import com.aliyun.oss.model.GeneratePresignedUrlRequest;
    import com.aliyun.oss.model.InitiateMultipartUploadRequest;
    import com.aliyun.oss.model.InitiateMultipartUploadResult;
    
    import java.net.URL;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    public class InitAndGenerateURL {
        public static void main(String[] args) throws Throwable {
            // 類比用戶端傳來的參數(單位:位元組)
            long fileSize = 15 * 1024 * 1024L;   // 類比 15MB 檔案
            long partSize = 5 * 1024 * 1024L;    // 類比期望每個分區 5MB
    
            // 計算分區數量
            int totalParts = (int) ((fileSize + partSize - 1) / partSize);
    
            // 以華東1(杭州)的外網Endpoint為例
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            String region = "cn-hangzhou";
            String bucketName = "exampleBucket";
            String objectName = "exampleObject.jpeg";
            long expireTime = 3600 * 1000L; // URL 1小時到期
    
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            ClientBuilderConfiguration config = new ClientBuilderConfiguration();
            config.setSignatureVersion(SignVersion.V4);
    
            OSS ossClient = OSSClientBuilder.create()
                    .endpoint(endpoint)
                    .region(region)
                    .credentialsProvider(credentialsProvider)
                    .clientConfiguration(config)
                    .build();
    
            // 初始化上傳任務
            InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName, objectName);
            InitiateMultipartUploadResult initResult = ossClient.initiateMultipartUpload(initRequest);
            String uploadId = initResult.getUploadId();
            System.out.println("Upload ID: " + uploadId);
            System.out.println("Total parts: " + totalParts);
    
            // 為每個分區產生預簽名 URL
            for (int i = 1; i <= totalParts; i++) {
                Map<String, String> headers = new HashMap<>();
                String signedUrl = generatePresignedUrl(ossClient, bucketName, objectName, HttpMethod.PUT,
                        expireTime, i, uploadId, headers);
                System.out.println("Part " + i + " URL: " + signedUrl);
            }
    
            ossClient.shutdown();
        }
    
        public static String generatePresignedUrl(OSS ossClient, String bucketName, String objectName, HttpMethod method,
                                                  long expireTime, int partNum, String uploadId, Map<String, String> headers) {
            Date expiration = new Date(System.currentTimeMillis() + expireTime);
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, method);
            request.setExpiration(expiration);
            request.setHeaders(headers);
            request.addQueryParameter("partNumber", String.valueOf(partNum));
            request.addQueryParameter("uploadId", uploadId);
            URL url = ossClient.generatePresignedUrl(request);
            return url.toString();
        }
    }
    

    Go

    package main
    
    import (
    	"context"
    	"encoding/json"
    	"flag"
    	"fmt"
    	"log"
    	"time"
    
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
    )
    
    type SignedPart struct {
    	PartNumber int    `json:"part_number"`
    	URL        string `json:"url"`
    	Method     string `json:"method"`
    }
    
    type UploadInitResponse struct {
    	UploadId string       `json:"upload_id"`
    	Parts    []SignedPart `json:"parts"`
    }
    
    // 全域參數變數
    var (
    	region     string
    	bucketName string
    	objectName string
    )
    
    func init() {
    	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
    	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
    	flag.StringVar(&objectName, "object", "", "The name of the object.")
    }
    
    func main() {
    	flag.Parse()
    
    	// 假設檔案大小為15MB,分區大小為5MB
    	fileSize := int64(15 * 1024 * 1024)
    	partSize := int64(5 * 1024 * 1024)
    	totalParts := int((fileSize + partSize - 1) / partSize)
    
    	cfg := oss.LoadDefaultConfig().
    		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
    		WithRegion(region)
    
    	client := oss.NewClient(cfg)
    
    	// 初始化分區上傳
    	resp, err := client.InitiateMultipartUpload(context.TODO(), &oss.InitiateMultipartUploadRequest{
    		Bucket: oss.Ptr(bucketName),
    		Key:    oss.Ptr(objectName),
    	})
    	if err != nil {
    		log.Fatalf("Failed to initiate multipart upload: %v", err)
    	}
    	uploadId := resp.UploadId
    	fmt.Println("Upload ID:", *uploadId)
    	fmt.Println("Total parts:", totalParts)
    
    	// 產生每個分區的預簽名URL
    	expire := time.Hour // 預簽名 URL 的有效期間設定為 1 小時
    	var parts []SignedPart
    	for i := 1; i <= totalParts; i++ {
    		req := &oss.UploadPartRequest{
    			Bucket:     oss.Ptr(bucketName),
    			Key:        oss.Ptr(objectName),
    			PartNumber: int32(i),
    			UploadId:   uploadId,
    		}
    		presignResult, err := client.Presign(context.TODO(), req, oss.PresignExpiration(time.Now().Add(expire)))
    		if err != nil {
    			log.Fatalf("Failed to generate signed URL for part %d: %v", i, err)
    		}
    		parts = append(parts, SignedPart{
    			PartNumber: i,
    			URL:        presignResult.URL,
    			Method:     "PUT",
    		})
    	}
    
    	// 輸出 JSON
    	out := UploadInitResponse{
    		UploadId: *uploadId,
    		Parts:    parts,
    	}
    	outJSON, _ := json.MarshalIndent(out, "", "  ")
    	fmt.Println(string(outJSON))
    }
    

    Python

    import argparse
    import json
    import alibabacloud_oss_v2 as oss
    
    # 類比檔案參數
    FILE_SIZE = 15 * 1024 * 1024  # 類比上傳檔案大小(15MB)
    PART_SIZE = 5 * 1024 * 1024   # 每個分區大小:5MB
    EXPIRE_TIME = 3600            # 預簽名URL有效期間(單位:秒)
    
    def main():
        # 建立命令列參數解析器,並描述指令碼用途
        parser = argparse.ArgumentParser(description="presign multipart upload sample")
    
        # 添加命令列參數 --region,表示儲存空間所在的地區,必需參數
        parser.add_argument('--region', help='The region in which the bucket is located.', required=True)
        # 添加命令列參數 --bucket,表示要上傳對象的儲存空間名稱,必需參數
        parser.add_argument('--bucket', help='The name of the bucket.', required=True)
        # 添加命令列參數 --endpoint,表示其他服務可用來訪問OSS的網域名稱,非必需參數
        parser.add_argument('--endpoint', help='The domain names that other services can use to access OSS')
        # 添加命令列參數 --key,表示對象(檔案)在OSS中的鍵名,必需參數
        parser.add_argument('--key', help='The name of the object.', required=True)
    
        args = parser.parse_args()
    
        # 配置 OSS 用戶端
        credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
        cfg = oss.config.load_default()
        cfg.credentials_provider = credentials_provider
        cfg.region = args.region
        # 如果提供了自訂endpoint,則更新設定物件中的endpoint屬性
        if args.endpoint is not None:
            cfg.endpoint = args.endpoint
        
        # 初始化 OSS 用戶端
        client = oss.Client(cfg)
    
        # 發送初始化分區上傳請求,擷取 upload_id
        init_result = client.presign(oss.InitiateMultipartUploadRequest(
            bucket=args.bucket,
            key=args.key,
        ))
    
        # 使用預簽名 URL 發起初始化請求並擷取 upload_id
        import requests
        with requests.post(init_result.url, headers=init_result.signed_headers) as resp:
            obj = oss.InitiateMultipartUploadResult()
            oss.serde.deserialize_xml(xml_data=resp.content, obj=obj)
            upload_id = obj.upload_id
    
        # 根據檔案大小計算分區數量,並產生每個分區的預簽名URL
        total_parts = (FILE_SIZE + PART_SIZE - 1) // PART_SIZE
        parts = []
    
        for part_number in range(1, total_parts + 1):
            req = oss.UploadPartRequest(
                bucket=args.bucket,
                key=args.key,
                part_number=part_number,
                upload_id=upload_id,
                expiration_in_seconds=EXPIRE_TIME
            )
            presign_result = client.presign(req)
            parts.append({
                "part_number": part_number,
                "url": presign_result.url,
                "method": "PUT"
            })
    
        # 輸出 upload_id 和所有分區的預簽名URL(JSON格式)
        output = {
            "upload_id": upload_id,
            "parts": parts
        }
    
        print(json.dumps(output, indent=2))
    
    if __name__ == "__main__":
        main()
  3. 用戶端上傳分區資料

    用戶端使用服務端返回的預簽名 URL 列表,通過 HTTP PUT 上傳各個分區資料,方式與上傳單個檔案相同。全部分區上傳完成後,需通知服務端發起合并。

    說明

    支援並發上傳,但必須確保:每個 URL 上傳的內容是對應位置的分區。即URL 中的 partNumber=N,只能上傳檔案第 N 個分區,不能混用或跳過。

    樣本如下,上傳第 1 個分區,/path/to/local/file 是本地分區路徑。

    curl -X PUT -T /path/to/local/file "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.jpg?partNumber=1&uploadId=BE2D0BC931BE4DE1B23F339AABFA49EE&x-oss-credential=LTAI********************%2F20250520%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-date=20250520T082728Z&x-oss-expires=3600&x-oss-signature=81f3d2e5eaa67c432291577ed20af3b3f60df05ab3cddedcdce168ef707f7ad0&x-oss-signature-version=OSS4-HMAC-SHA256"
  4. (可選)服務端校正已上傳分區

    服務端收到“上傳完成”通知後,可選擇調用 ListParts 介面校正:

    • 分區數量是否完整

    • 每個分區大小是否符合預期

    說明

    該操作使用之前記錄的 UploadId。

    範例程式碼:服務端校正

    Java

    import com.aliyun.oss.ClientBuilderConfiguration;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.common.auth.CredentialsProviderFactory;
    import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
    import com.aliyun.oss.common.comm.SignVersion;
    import com.aliyun.oss.model.*;
    
    import java.util.ArrayList;
    import java.util.List;
    public class complete {
    
        public static void main(String[] args) throws Exception {
            // OSS服務的訪問網域名稱(根據實際Region填寫,這裡是華東1-杭州)
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            //替換成您的bucket名稱,此處以examplebucket為例
            String bucketName = "examplebucket";
            //替換成您的Object的完整路徑,完整路徑中不能包含Bucket名稱,例如exampledir/exampleobject.jpeg。
            String objectName = "exampleobject.jpeg";
            //分區上傳任務ID
            String uploadId = "4B78****************************";
            //替換為您的Region,此處以華東1-杭州為例
            String region = "cn-hangzhou";
    
            // 通過環境變數擷取憑證,構造 OSSClient
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            ClientBuilderConfiguration config = new ClientBuilderConfiguration();
            config.setSignatureVersion(SignVersion.V4);
    
            OSS ossClient = OSSClientBuilder.create()
                    .endpoint(endpoint)
                    .region(region)
                    .credentialsProvider(credentialsProvider)
                    .clientConfiguration(config)
                    .build();
    
            try {
                // 列舉當前上傳任務中已上傳的所有分區
                ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, objectName, uploadId);
                PartListing partListing = ossClient.listParts(listPartsRequest);
    
                // 收集分區的 ETag 資訊用於後續合并
                List<PartETag> partETags = new ArrayList<>();
                for (PartSummary part : partListing.getParts()) {
                    partETags.add(new PartETag(part.getPartNumber(), part.getETag()));
                }
    
                if (partETags.isEmpty()) {
                    System.out.println("沒有找到已上傳的分區,終止合并。");
                    return;
                }
    
                // 提交合并請求,完成分區上傳
                System.out.println("開始合并分區...");
                CompleteMultipartUploadRequest completeRequest =
                        new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
    
                CompleteMultipartUploadResult result = ossClient.completeMultipartUpload(completeRequest);
                System.out.println("合并分區成功!");
                System.out.println("ETag: " + result.getETag());
    
            } catch (Exception e) {
                System.err.println("合并分區失敗: " + e.getMessage());
                e.printStackTrace();
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
        }
    }
    

    Go

    package main
    
    import (
    	"context"
    	"flag"
    	"log"
    
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
    )
    
    var (
    	region     string
    	bucketName string
    	objectName string
    	uploadId   string
    )
    
    func init() {
    	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
    	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
    	flag.StringVar(&objectName, "object", "", "The name of the object.")
    	flag.StringVar(&uploadId, "uploadId", "", "The upload ID.")
    }
    
    func main() {
    	flag.Parse()
    
    	if region == "" || bucketName == "" || objectName == "" || uploadId == "" {
    		flag.PrintDefaults()
    		log.Fatal("Missing required parameters.")
    	}
    
    	cfg := oss.LoadDefaultConfig().
    		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
    		WithRegion(region)
    
    	client := oss.NewClient(cfg)
    
    	// 列舉已上傳分區,構建完整分區列表
    	partListResp, err := client.ListParts(context.TODO(), &oss.ListPartsRequest{
    		Bucket:   oss.Ptr(bucketName),
    		Key:      oss.Ptr(objectName),
    		UploadId: oss.Ptr(uploadId),
    	})
    	if err != nil {
    		log.Fatalf("failed to list parts: %v", err)
    	}
    
    	var parts []oss.UploadPart
    	for _, p := range partListResp.Parts {
    		parts = append(parts, oss.UploadPart{
    			PartNumber: p.PartNumber,
    			ETag:       p.ETag,
    		})
    	}
    
    	// 合并已上傳的分區
    	completeResult, err := client.CompleteMultipartUpload(context.TODO(), &oss.CompleteMultipartUploadRequest{
    		Bucket:   oss.Ptr(bucketName),
    		Key:      oss.Ptr(objectName),
    		UploadId: oss.Ptr(uploadId),
    		CompleteMultipartUpload: &oss.CompleteMultipartUpload{
    			Parts: parts,
    		},
    	})
    	if err != nil {
    		log.Fatalf("failed to complete multipart upload: %v", err)
    	}
    
    	// 列印合并結果
    	log.Println("Upload completed successfully.")
    	log.Printf("Bucket:   %s\n", oss.ToString(completeResult.Bucket))
    	log.Printf("Key:      %s\n", oss.ToString(completeResult.Key))
    	log.Printf("ETag:     %s\n", oss.ToString(completeResult.ETag))
    	log.Printf("Status:   %s\n", completeResult.Status)
    }
    

    Python

    # -*- coding: utf-8 -*-
    import argparse
    import alibabacloud_oss_v2 as oss
    
    def main():
        # 建立一個命令列參數解析器,並描述指令碼用途
        parser = argparse.ArgumentParser(description="presign multipart upload sample")
        # 添加命令列參數 --region,表示儲存空間所在的地區,必需參數
        parser.add_argument('--region', help='The region in which the bucket is located.', required=True)
        # 添加命令列參數 --bucket,表示要上傳對象的儲存空間名稱,必需參數
        parser.add_argument('--bucket', help='The name of the bucket.', required=True)
        # 添加命令列參數 --endpoint,表示其他服務可用來訪問OSS的網域名稱,非必需參數
        parser.add_argument('--endpoint', help='The domain names that other services can use to access OSS')
        # 添加命令列參數 --key,表示對象(檔案)在OSS中的鍵名,必需參數
        parser.add_argument('--key', help='The name of the object.', required=True)
        # 添加命令列參數 --upload_id,表示分區上傳任務的Upload ID,必需參數
        parser.add_argument('--upload_id', help='The upload ID to list parts for.',required=True)
    
        args = parser.parse_args()
    
        # 初始化用戶端配置和憑證
        credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
        cfg = oss.config.load_default()
        cfg.credentials_provider = credentials_provider
        cfg.region = args.region
        if args.endpoint:
            cfg.endpoint = args.endpoint
    
        client = oss.Client(cfg)
    
        try:
            # 擷取所有已上傳分區
            list_parts_result = client.list_parts(oss.ListPartsRequest(
                bucket=args.bucket,
                key=args.key,
                upload_id=args.upload_id
            ))
    
            # 構造分區列表
            upload_parts = [
                oss.UploadPart(part_number=part.part_number, etag=part.etag)
                for part in list_parts_result.parts
            ]
    
            if not upload_parts:
                print("未找到任何已上傳的分區,終止操作。")
                return
    
            # 構造並發送合并請求
            request = oss.CompleteMultipartUploadRequest(
                bucket=args.bucket,
                key=args.key,
                upload_id=args.upload_id,
                complete_multipart_upload=oss.CompleteMultipartUpload(parts=upload_parts)
            )
    
            result = client.complete_multipart_upload(request)
    
            print("合并成功!")
            print(f"ETag: {result.etag}")
            print(f"Bucket: {result.bucket}")
            print(f"Key: {result.key}")
    
        except Exception as e:
            print("合并分區失敗:", e)
    
    if __name__ == "__main__":
        main()
  5. 服務端合并分區並返回結果

    校正通過後,服務端調用CompleteMultipartUpload完成檔案合并,並將上傳結果返回給用戶端。

    範例程式碼:服務端合并分區

    Java

    import com.aliyun.oss.ClientBuilderConfiguration;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.common.auth.CredentialsProviderFactory;
    import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
    import com.aliyun.oss.common.comm.SignVersion;
    import com.aliyun.oss.model.*;
    
    import java.util.ArrayList;
    import java.util.List;
    public class complete {
    
        public static void main(String[] args) throws Exception {
            // OSS服務的訪問網域名稱(根據實際Region填寫,這裡是華東1-杭州)
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            //替換成您的bucket名稱,此處以examplebucket為例
            String bucketName = "examplebucket";
            //替換成您的Object的完整路徑,完整路徑中不能包含Bucket名稱,例如exampledir/exampleobject.jpeg。
            String objectName = "exampleobject.jpeg";
            //分區上傳任務ID
            String uploadId = "4B78****************************";
            //替換為您的Region,此處以華東1-杭州為例
            String region = "cn-hangzhou";
    
            // 通過環境變數擷取憑證,構造 OSSClient
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            ClientBuilderConfiguration config = new ClientBuilderConfiguration();
            config.setSignatureVersion(SignVersion.V4);
    
            OSS ossClient = OSSClientBuilder.create()
                    .endpoint(endpoint)
                    .region(region)
                    .credentialsProvider(credentialsProvider)
                    .clientConfiguration(config)
                    .build();
    
            try {
                // 列舉當前上傳任務中已上傳的所有分區
                ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, objectName, uploadId);
                PartListing partListing = ossClient.listParts(listPartsRequest);
    
                // 收集分區的 ETag 資訊用於後續合并
                List<PartETag> partETags = new ArrayList<>();
                for (PartSummary part : partListing.getParts()) {
                    partETags.add(new PartETag(part.getPartNumber(), part.getETag()));
                }
    
                if (partETags.isEmpty()) {
                    System.out.println("沒有找到已上傳的分區,終止合并。");
                    return;
                }
    
                // 提交合并請求,完成分區上傳
                System.out.println("開始合并分區...");
                CompleteMultipartUploadRequest completeRequest =
                        new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
    
                CompleteMultipartUploadResult result = ossClient.completeMultipartUpload(completeRequest);
                System.out.println("合并分區成功!");
                System.out.println("ETag: " + result.getETag());
    
            } catch (Exception e) {
                System.err.println("合并分區失敗: " + e.getMessage());
                e.printStackTrace();
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
        }
    }
    

    Go

    package main
    
    import (
    	"context"
    	"flag"
    	"log"
    
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
    )
    
    var (
    	region     string
    	bucketName string
    	objectName string
    	uploadId   string
    )
    
    func init() {
    	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
    	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
    	flag.StringVar(&objectName, "object", "", "The name of the object.")
    	flag.StringVar(&uploadId, "uploadId", "", "The upload ID.")
    }
    
    func main() {
    	flag.Parse()
    
    	if region == "" || bucketName == "" || objectName == "" || uploadId == "" {
    		flag.PrintDefaults()
    		log.Fatal("Missing required parameters.")
    	}
    
    	cfg := oss.LoadDefaultConfig().
    		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
    		WithRegion(region)
    
    	client := oss.NewClient(cfg)
    
    	// 列舉已上傳分區,構建完整分區列表
    	partListResp, err := client.ListParts(context.TODO(), &oss.ListPartsRequest{
    		Bucket:   oss.Ptr(bucketName),
    		Key:      oss.Ptr(objectName),
    		UploadId: oss.Ptr(uploadId),
    	})
    	if err != nil {
    		log.Fatalf("failed to list parts: %v", err)
    	}
    
    	var parts []oss.UploadPart
    	for _, p := range partListResp.Parts {
    		parts = append(parts, oss.UploadPart{
    			PartNumber: p.PartNumber,
    			ETag:       p.ETag,
    		})
    	}
    
    	// 合并已上傳的分區
    	completeResult, err := client.CompleteMultipartUpload(context.TODO(), &oss.CompleteMultipartUploadRequest{
    		Bucket:   oss.Ptr(bucketName),
    		Key:      oss.Ptr(objectName),
    		UploadId: oss.Ptr(uploadId),
    		CompleteMultipartUpload: &oss.CompleteMultipartUpload{
    			Parts: parts,
    		},
    	})
    	if err != nil {
    		log.Fatalf("failed to complete multipart upload: %v", err)
    	}
    
    	// 列印合并結果
    	log.Println("Upload completed successfully.")
    	log.Printf("Bucket:   %s\n", oss.ToString(completeResult.Bucket))
    	log.Printf("Key:      %s\n", oss.ToString(completeResult.Key))
    	log.Printf("ETag:     %s\n", oss.ToString(completeResult.ETag))
    	log.Printf("Status:   %s\n", completeResult.Status)
    }
    

    Python

    # -*- coding: utf-8 -*-
    import argparse
    import alibabacloud_oss_v2 as oss
    
    def main():
        # 建立一個命令列參數解析器,並描述指令碼用途
        parser = argparse.ArgumentParser(description="presign multipart upload sample")
        # 添加命令列參數 --region,表示儲存空間所在的地區,必需參數
        parser.add_argument('--region', help='The region in which the bucket is located.', required=True)
        # 添加命令列參數 --bucket,表示要上傳對象的儲存空間名稱,必需參數
        parser.add_argument('--bucket', help='The name of the bucket.', required=True)
        # 添加命令列參數 --endpoint,表示其他服務可用來訪問OSS的網域名稱,非必需參數
        parser.add_argument('--endpoint', help='The domain names that other services can use to access OSS')
        # 添加命令列參數 --key,表示對象(檔案)在OSS中的鍵名,必需參數
        parser.add_argument('--key', help='The name of the object.', required=True)
        # 添加命令列參數 --upload_id,表示分區上傳任務的Upload ID,必需參數
        parser.add_argument('--upload_id', help='The upload ID to list parts for.',required=True)
    
        args = parser.parse_args()
    
        # 初始化用戶端配置和憑證
        credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
        cfg = oss.config.load_default()
        cfg.credentials_provider = credentials_provider
        cfg.region = args.region
        if args.endpoint:
            cfg.endpoint = args.endpoint
    
        client = oss.Client(cfg)
    
        try:
            # 擷取所有已上傳分區
            list_parts_result = client.list_parts(oss.ListPartsRequest(
                bucket=args.bucket,
                key=args.key,
                upload_id=args.upload_id
            ))
    
            # 構造分區列表
            upload_parts = [
                oss.UploadPart(part_number=part.part_number, etag=part.etag)
                for part in list_parts_result.parts
            ]
    
            if not upload_parts:
                print("未找到任何已上傳的分區,終止操作。")
                return
    
            # 構造並發送合并請求
            request = oss.CompleteMultipartUploadRequest(
                bucket=args.bucket,
                key=args.key,
                upload_id=args.upload_id,
                complete_multipart_upload=oss.CompleteMultipartUpload(parts=upload_parts)
            )
    
            result = client.complete_multipart_upload(request)
    
            print("合并成功!")
            print(f"ETag: {result.etag}")
            print(f"Bucket: {result.bucket}")
            print(f"Key: {result.key}")
    
        except Exception as e:
            print("合并分區失敗:", e)
    
    if __name__ == "__main__":
        main()

設定Header定義上傳策略

您可以在產生預簽名URL時,通過指定Header參數定義上傳策略。例如,您可以設定檔案儲存體類型x-oss-storage-class和檔案類型Content-Type(如下程式碼範例)。

重要

產生預簽名URL時指定了Header參數,實際使用預簽名URL上傳檔案時,也必須傳遞相同的Header。否則,OSS將因為簽名校正失敗返回403錯誤。

關於可設定的OSS系統Header,請參見PutObject。您還可以設定自訂Header來管理檔案,詳情請參見管理檔案中繼資料

  1. 檔案擁有者產生帶Header參數的預簽名URL。

    Java

    import com.aliyun.oss.*;
    import com.aliyun.oss.common.auth.*;
    import com.aliyun.oss.common.comm.SignVersion;
    import com.aliyun.oss.internal.OSSHeaders;
    import com.aliyun.oss.model.GeneratePresignedUrlRequest;
    import com.aliyun.oss.model.StorageClass;
    
    import java.net.URL;
    import java.util.*;
    import java.util.Date;
    
    public class GetSignUrl {
        public static void main(String[] args) throws Throwable {
            // 以華東1(杭州)的外網Endpoint為例,其它Region請按實際情況填寫。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 填寫Bucket名稱,例如examplebucket。
            String bucketName = "examplebucket";
            // 填寫Object完整路徑,例如exampleobject.txt。Object完整路徑中不能包含Bucket名稱。
            String objectName = "exampleobject.txt";
            // 填寫Bucket所在地區。以華東1(杭州)為例,Region填寫為cn-hangzhou。
            String region = "cn-hangzhou";
    
            // 建立OSSClient執行個體。
            // 當OSSClient執行個體不再使用時,調用shutdown方法以釋放資源。
            ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
            clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
            OSS ossClient = OSSClientBuilder.create()
                    .endpoint(endpoint)
                    .credentialsProvider(credentialsProvider)
                    .clientConfiguration(clientBuilderConfiguration)
                    .region(region)
                    .build();
    
            // 佈建要求頭。
            Map<String, String> headers = new HashMap<String, String>();
            // 指定StorageClass。
            headers.put(OSSHeaders.STORAGE_CLASS, StorageClass.Standard.toString());
            // 指定ContentType。
            headers.put(OSSHeaders.CONTENT_TYPE, "text/plain; charset=utf8");
    
            // 設定使用者自訂中繼資料。
            Map<String, String> userMetadata = new HashMap<String, String>();
            userMetadata.put("key1","value1");
            userMetadata.put("key2","value2");
    
            URL signedUrl = null;
            try {
                // 指定產生的預簽名URL到期時間,單位為毫秒。本樣本以設定到期時間為1小時為例。
                Date expiration = new Date(new Date().getTime() + 3600 * 1000L);
    
                // 產生預簽名URL。
                GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
                // 設定到期時間。
                request.setExpiration(expiration);
    
                // 將要求標頭加入到request中。
                request.setHeaders(headers);
                // 添加使用者自訂中繼資料。
                request.setUserMetadata(userMetadata);
    
                // 通過HTTP PUT請求產生預簽名URL。
                signedUrl = ossClient.generatePresignedUrl(request);
                // 列印預簽名URL。
                System.out.println("signed url for putObject: " + signedUrl);
    
            } 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.");
                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());
            }
        }
    }       

    Go

    package main
    
    import (
    	"context"
    	"flag"
    	"log"
    	"time"
    
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
    )
    
    // 定義全域變數
    var (
    	region     string // 儲存地區
    	bucketName string // 儲存空間名稱
    	objectName string // 對象名稱
    )
    
    // init函數用於初始化命令列參數
    func init() {
    	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
    	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
    	flag.StringVar(&objectName, "object", "", "The name of the object.")
    }
    
    func main() {
    	// 解析命令列參數
    	flag.Parse()
    
    	// 檢查bucket名稱是否為空白
    	if len(bucketName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, bucket name required")
    	}
    
    	// 檢查region是否為空白
    	if len(region) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, region required")
    	}
    
    	// 檢查object名稱是否為空白
    	if len(objectName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, object name required")
    	}
    
    	// 載入預設配置並設定憑證提供者和地區
    	cfg := oss.LoadDefaultConfig().
    		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
    		WithRegion(region)
    
    	// 建立OSS用戶端
    	client := oss.NewClient(cfg)
    
    	// 產生PutObject的預簽名URL
    	result, err := client.Presign(context.TODO(), &oss.PutObjectRequest{
    		Bucket:      oss.Ptr(bucketName),
    		Key:         oss.Ptr(objectName),
    		ContentType:  oss.Ptr("text/plain;charset=utf8"),                    // 請確保在服務端產生該簽名URL時設定的ContentType與在使用URL時設定的ContentType一致
    		StorageClass: oss.StorageClassStandard,                              // 請確保在服務端產生該簽名URL時設定的StorageClass與在使用URL時設定的StorageClass一致
    		Metadata:    map[string]string{"key1": "value1", "key2": "value2"}, // 請確保在服務端產生該簽名URL時設定的Metadata與在使用URL時設定的Metadata一致
    	},
    		oss.PresignExpires(10*time.Minute),
    	)
    	if err != nil {
    		log.Fatalf("failed to put object presign %v", err)
    	}
    
    	log.Printf("request method:%v\n", result.Method)
    	log.Printf("request expiration:%v\n", result.Expiration)
    	log.Printf("request url:%v\n", result.URL)
    	if len(result.SignedHeaders) > 0 {
    		//當返回結果包含簽名頭時,使用預簽名URL發送Put請求時,需要設定相應的要求標頭
    		log.Printf("signed headers:\n")
    		for k, v := range result.SignedHeaders {
    			log.Printf("%v: %v\n", k, v)
    		}
    	}
    }
    

    Python

    import argparse
    import requests
    import alibabacloud_oss_v2 as oss
    
    from datetime import datetime, timedelta
    
    # 建立命令列參數解析器,並描述指令碼用途:產生對象預簽名PUT請求連結(Presign Put Object)
    parser = argparse.ArgumentParser(description="presign put object sample")
    
    # 定義命令列參數,包括必需的地區、儲存空間名稱、endpoint以及對象鍵名
    parser.add_argument('--region', help='The region in which the bucket is located.', required=True)
    parser.add_argument('--bucket', help='The name of the bucket.', required=True)
    parser.add_argument('--endpoint', help='The domain names that other services can use to access OSS')
    parser.add_argument('--key', help='The name of the object.', required=True)
    
    def main():
        # 解析命令列參數,擷取使用者輸入的值
        args = parser.parse_args()
    
        # 從環境變數中載入訪問憑證資訊,用於身分識別驗證
        credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    
        # 使用SDK預設配置建立設定物件,並設定認證提供者
        cfg = oss.config.load_default()
        cfg.credentials_provider = credentials_provider
    
        # 設定設定物件的地區屬性,根據使用者提供的命令列參數
        cfg.region = args.region
    
        # 如果提供了自訂endpoint,則更新設定物件中的endpoint屬性
        if args.endpoint is not None:
            cfg.endpoint = args.endpoint
    
        # 使用上述配置初始化OSS用戶端,準備與OSS互動
        client = oss.Client(cfg)
    
        # 發送請求以產生指定對象的預簽名PUT請求
        pre_result = client.presign(oss.PutObjectRequest(
            bucket=args.bucket,  # 儲存空間名
            key=args.key,  # 對象鍵名
            content_type='text/plain;charset=utf8',  # 指定內容類型
            storage_class='Standard',  # 指定儲存類型
            metadata={
                'key1': 'value1',   # 指定中繼資料
                'key2': 'value2'    # 指定中繼資料
            }
        ),expires=timedelta(seconds=3600)) # 設定到期時間,單位為秒,此處設定為3600秒
    
    
        # 列印預簽章要求的方法、到期時間和URL,以便確認預簽名連結的有效性
        print(f'method: {pre_result.method},'
              f' expiration: {pre_result.expiration.strftime("%Y-%m-%dT%H:%M:%S.000Z")},'
              f' url: {pre_result.url}'
              )
    
        # 列印預簽章要求的已簽名頭資訊,這些資訊在發送實際請求時會被包含在HTTP頭部
        for key, value in pre_result.signed_headers.items():
            print(f'signed headers key: {key}, signed headers value: {value}')
    
    # 當此指令碼被直接執行時,調用main函數開始處理邏輯
    if __name__ == "__main__":
        main()  # 指令碼進入點,控製程序流程從這裡開始
  2. 其他人使用預簽名URL上傳檔案並傳遞相同Header。

    curl

    curl -X PUT \
         -H "Content-Type: text/plain;charset=utf8" \
         -H "x-oss-storage-class: Standard" \
         -H "x-oss-meta-key1: value1" \
         -H "x-oss-meta-key2: value2" \
         -T "C:\\Users\\demo.txt" \
         "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"

    Java

    import com.aliyun.oss.internal.OSSHeaders;
    import com.aliyun.oss.model.StorageClass;
    import org.apache.http.HttpEntity;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpPut;
    import org.apache.http.entity.FileEntity;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import java.io.*;
    import java.net.URL;
    import java.util.*;
    
    public class SignUrlUpload {
        public static void main(String[] args) throws Throwable {
            CloseableHttpClient httpClient = null;
            CloseableHttpResponse response = null;
    
            // 將<signedUrl>替換為授權URL。
            URL signedUrl = new URL("<signedUrl>");
    
            // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
            String pathName = "C:\\Users\\demo.txt";
    
            // 佈建要求頭,這裡的要求標頭資訊需要與產生URL時的資訊一致。
            Map<String, String> headers = new HashMap<String, String>();
            //指定Object的儲存類型。
            headers.put(OSSHeaders.STORAGE_CLASS, StorageClass.Standard.toString());
            //指定ContentType。
            headers.put(OSSHeaders.CONTENT_TYPE, "text/plain;charset=utf8");
    
            // 設定使用者自訂中繼資料,這裡的使用者自訂中繼資料需要與產生URL時的資訊一致。
            Map<String, String> userMetadata = new HashMap<String, String>();
            userMetadata.put("key1","value1");
            userMetadata.put("key2","value2");
    
            try {
                HttpPut put = new HttpPut(signedUrl.toString());
                System.out.println(put);
                HttpEntity entity = new FileEntity(new File(pathName));
                put.setEntity(entity);
                // 如果產生預簽名URL時設定了header參數,例如使用者中繼資料,儲存類型等,則調用預簽名URL上傳檔案時,也需要將這些參數發送至服務端。如果簽名和發送至服務端的不一致,會報簽名錯誤。
                for(Map.Entry header: headers.entrySet()){
                    put.addHeader(header.getKey().toString(),header.getValue().toString());
                }
                for(Map.Entry meta: userMetadata.entrySet()){
                    // 如果使用userMeta,sdk內部會為userMeta拼接"x-oss-meta-"首碼。當您使用其他方式產生預簽名URL進行上傳時,userMeta也需要拼接"x-oss-meta-"首碼。
                    put.addHeader("x-oss-meta-"+meta.getKey().toString(), meta.getValue().toString());
                }
    
                httpClient = HttpClients.createDefault();
    
                response = httpClient.execute(put);
    
                System.out.println("返回上傳狀態代碼:"+response.getStatusLine().getStatusCode());
                if(response.getStatusLine().getStatusCode() == 200){
                    System.out.println("使用網路程式庫上傳成功");
                }
                System.out.println(response.toString());
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                response.close();
                httpClient.close();
            }
        }
    }       

    Go

    package main
    
    import (
    	"bytes"
    	"fmt"
    	"io/ioutil"
    	"net/http"
    	"os"
    )
    
    func uploadFile(signedUrl string, filePath string, headers map[string]string, metadata map[string]string) error {
    	// 開啟檔案
    	file, err := os.Open(filePath)
    	if err != nil {
    		return err
    	}
    	defer file.Close()
    
    	// 讀取檔案內容
    	fileBytes, err := ioutil.ReadAll(file)
    	if err != nil {
    		return err
    	}
    
    	// 建立請求
    	req, err := http.NewRequest("PUT", signedUrl, bytes.NewBuffer(fileBytes))
    	if err != nil {
    		return err
    	}
    
    	// 佈建要求頭
    	for key, value := range headers {
    		req.Header.Set(key, value)
    	}
    
    	// 設定使用者自訂中繼資料
    	for key, value := range metadata {
    		req.Header.Set(fmt.Sprintf("x-oss-meta-%s", key), value)
    	}
    
    	// 發送請求
    	client := &http.Client{}
    	resp, err := client.Do(req)
    	if err != nil {
    		return err
    	}
    	defer resp.Body.Close()
    
    	// 處理響應
    	fmt.Printf("返回上傳狀態代碼:%d\n", resp.StatusCode)
    	if resp.StatusCode == 200 {
    		fmt.Println("使用網路程式庫上傳成功")
    	} else {
    		fmt.Println("上傳失敗")
    	}
    	body, _ := ioutil.ReadAll(resp.Body)
    	fmt.Println(string(body))
    
    	return nil
    }
    
    func main() {
    	// 將<signedUrl>替換為授權URL。
    	signedUrl := "<signedUrl>"
    
    	// 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
    	filePath := "C:\\Users\\demo.txt"
    
    	// 佈建要求頭,這裡的要求標頭資訊需要與產生URL時的資訊一致。
    	headers := map[string]string{
    		"Content-Type": "text/plain;charset=utf8",
    		"x-oss-storage-class": "Standard",
    	}
    
    	// 設定使用者自訂中繼資料,這裡的使用者自訂中繼資料需要與產生URL時的資訊一致。
    	metadata := map[string]string{
    		"key1": "value1",
    		"key2": "value2",
    	}
    
    	err := uploadFile(signedUrl, filePath, headers, metadata)
    	if err != nil {
    		fmt.Printf("發生錯誤:%v\n", err)
    	}
    }
    

    Python

    import requests
    from requests.auth import HTTPBasicAuth
    import os
    
    def upload_file(signed_url, file_path, headers=None, metadata=None):
        """
        使用預簽名的URL上傳檔案到OSS。
    
        :param signed_url: 預簽名的URL。
        :param file_path: 要上傳的檔案的完整路徑。
        :param headers: 可選,自訂HTTP頭部。
        :param metadata: 可選,自訂中繼資料。
        :return: None
        """
        if not headers:
            headers = {}
        if not metadata:
            metadata = {}
    
        # 更新headers,添加中繼資料首碼
        for key, value in metadata.items():
            headers[f'x-oss-meta-{key}'] = value
    
        try:
            with open(file_path, 'rb') as file:
                response = requests.put(signed_url, data=file, headers=headers)
                print(f"返回上傳狀態代碼:{response.status_code}")
                if response.status_code == 200:
                    print("使用網路程式庫上傳成功")
                else:
                    print("上傳失敗")
                print(response.text)
        except Exception as e:
            print(f"發生錯誤:{e}")
    
    if __name__ == "__main__":
        # 將<signedUrl>替換為授權URL。
        signed_url = "<signedUrl>"
       
        # 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從指令碼所在目錄中上傳檔案。
        file_path = "C:\\Users\\demo.txt"
    
        # 佈建要求頭,這裡的要求標頭資訊需要與產生URL時的資訊一致。
        headers = {
             "Content-Type": "text/plain;charset=utf8",
             "x-oss-storage-class": "Standard"
        }
    
        # 設定使用者自訂中繼資料,這裡的使用者自訂中繼資料需要與產生URL時的資訊一致。
        metadata = {
             "key1": "value1",
             "key2": "value2"
        }
    
        upload_file(signed_url, file_path, headers, metadata)
    

    Node.js

    const fs = require('fs');
    const axios = require('axios');
    
    async function uploadFile(signedUrl, filePath, headers = {}, metadata = {}) {
        try {
            // 更新headers,添加中繼資料首碼
            for (const [key, value] of Object.entries(metadata)) {
                headers[`x-oss-meta-${key}`] = value;
            }
    
            // 讀取檔案流
            const fileStream = fs.createReadStream(filePath);
    
            // 發送PUT請求
            const response = await axios.put(signedUrl, fileStream, {
                headers: headers
            });
    
            console.log(`返回上傳狀態代碼:${response.status}`);
            if (response.status === 200) {
                console.log("使用網路程式庫上傳成功");
            } else {
                console.log("上傳失敗");
            }
            console.log(response.data);
        } catch (error) {
            console.error(`發生錯誤:${error.message}`);
        }
    }
    
    // 主函數
    (async () => {
        // 將<signedUrl>替換為授權URL。
        const signedUrl = "<signedUrl>";
    
        // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從指令碼所在目錄中上傳檔案。
        const filePath = "C:\\Users\\demo.txt";
    
        // 佈建要求頭,這裡的要求標頭資訊需要與產生URL時的資訊一致。
        const headers = {
             "Content-Type": "text/plain;charset=utf8",
             "x-oss-storage-class": "Standard"
        };
    
        // 設定使用者自訂中繼資料,這裡的使用者自訂中繼資料需要與產生URL時的資訊一致。
        const metadata = {
             "key1": "value1",
             "key2": "value2"
        };
    
        await uploadFile(signedUrl, filePath, headers, metadata);
    })();
    

    Browser.js

    重要

    如果您使用 Browser.js 上傳檔案時遇到 403 簽名不匹配錯誤,通常是因為瀏覽器會自動添加 Content-Type 要求標頭,而產生預簽名 URL 時未指定該要求標頭,導致簽名驗證失敗。為解決此問題,您需要在產生預簽名 URL 時指定 Content-Type 要求標頭。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>File Upload Example</title>
    </head>
    <body>
        <h1>File Upload Example(Java SDK)</h1>
    
        <input type="file" id="fileInput" />
        <button id="uploadButton">Upload File</button>
    
        <script>
            // 請替換為實際的預簽名URL
            const signedUrl = "<signedUrl>"; 
    
            document.getElementById('uploadButton').addEventListener('click', async () => {
                const fileInput = document.getElementById('fileInput');
                const file = fileInput.files[0];
    
                if (file) {
                    try {
                        await upload(file, signedUrl);
                    } catch (error) {
                        console.error('Error during upload:', error);
                        alert('Upload failed: ' + error.message);
                    }
                } else {
                    alert('Please select a file to upload.');
                }
            });
    
            const upload = async (file, presignedUrl) => {
                const headers = {
                    "Content-Type": "text/plain;charset=utf8",
                    'x-oss-storage-class': 'Standard',
                    'x-oss-meta-key1': 'value1',
                    'x-oss-meta-key2': 'value2'
                };
    
                const response = await fetch(presignedUrl, {
                    method: 'PUT',
                    headers: headers,
                    body: file
                });
    
                if (!response.ok) {
                    throw new Error(`Upload failed, status: ${response.status}`);
                }
    
                alert('File uploaded successfully');
                console.log('File uploaded successfully');
            };
        </script>
    </body>
    </html>

    C#

    using System.Net.Http.Headers;
    
    // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案
    var filePath = "C:\\Users\\demo.txt";
    // 將<signedUrl>替換為授權URL
    var presignedUrl = "<signedUrl>";
    
    // 建立HTTP用戶端並開啟本地檔案流
    using var httpClient = new HttpClient(); 
    using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
    using var content = new StreamContent(fileStream);
                
    // 建立PUT請求
    var request = new HttpRequestMessage(HttpMethod.Put, presignedUrl);
    request.Content = content;
    
    // 如果產生預簽名URL時設定了header參數,例如使用者中繼資料,儲存類型等,則調用預簽名URL上傳檔案時,也需要將這些參數發送至服務端。如果簽名和發送至服務端的不一致,會報簽名錯誤。
    // 佈建要求頭,這裡的要求標頭資訊需要與產生URL時的資訊一致
    request.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain") { CharSet = "utf8" };  // 指定ContentType       
    // 設定使用者自訂中繼資料,這裡的使用者自訂中繼資料需要與產生URL時的資訊一致。
    request.Content.Headers.Add("x-oss-meta-key1", "value1");
    request.Content.Headers.Add("x-oss-meta-key2", "value2");
    // 指定Object的儲存類型
    request.Content.Headers.Add("x-oss-storage-class", "Standard");
    
    // 輸出要求標頭部
    Console.WriteLine("要求標頭部:");
    foreach (var header in request.Content.Headers)
    {
        Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
    }
    
    // 發送請求
    var response = await httpClient.SendAsync(request);
    
    // 處理響應
    if (response.IsSuccessStatusCode)
    {
        Console.WriteLine($"上傳成功!狀態代碼: {response.StatusCode}");
        Console.WriteLine("回應標頭部:");
        foreach (var header in response.Headers)
        {
            Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
        }
    }
    else
    {
        string responseContent = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"上傳失敗!狀態代碼: {response.StatusCode}");
        Console.WriteLine("響應內容: " + responseContent);
    }

    C++

    #include <iostream>
    #include <fstream>
    #include <curl/curl.h>
    #include <map>
    #include <string>
    
    // 回呼函數,用於處理HTTP響應
    size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* output) {
        size_t totalSize = size * nmemb;
        output->append((char*)contents, totalSize);
        return totalSize;
    }
    
    void uploadFile(const std::string& signedUrl, const std::string& filePath, const std::map<std::string, std::string>& headers, const std::map<std::string, std::string>& metadata) {
        CURL* curl;
        CURLcode res;
        std::string readBuffer;
    
        curl_global_init(CURL_GLOBAL_DEFAULT);
        curl = curl_easy_init();
    
        if (curl) {
            // 設定URL
            curl_easy_setopt(curl, CURLOPT_URL, signedUrl.c_str());
    
            // 佈建要求方法為PUT
            curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
    
            // 開啟檔案
            FILE* file = fopen(filePath.c_str(), "rb");
            if (!file) {
                std::cerr << "無法開啟檔案: " << filePath << std::endl;
                return;
            }
    
            // 設定檔案大小
            fseek(file, 0, SEEK_END);
            long fileSize = ftell(file);
            rewind(file);
    
            // 設定檔案讀取回調
            curl_easy_setopt(curl, CURLOPT_READDATA, file);
            curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);
    
            // 佈建要求頭
            struct curl_slist* chunk = nullptr;
            for (const auto& header : headers) {
                std::string headerStr = header.first + ": " + header.second;
                chunk = curl_slist_append(chunk, headerStr.c_str());
            }
            for (const auto& meta : metadata) {
                std::string metaStr = "x-oss-meta-" + meta.first + ": " + meta.second;
                chunk = curl_slist_append(chunk, metaStr.c_str());
            }
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
    
            // 設定響應處理回調
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
    
            // 執行請求
            res = curl_easy_perform(curl);
    
            // 檢查響應
            if (res != CURLE_OK) {
                std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
            } else {
                long responseCode;
                curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
                std::cout << "返回上傳狀態代碼: " << responseCode << std::endl;
                if (responseCode == 200) {
                    std::cout << "使用網路程式庫上傳成功" << std::endl;
                } else {
                    std::cout << "上傳失敗" << std::endl;
                }
                std::cout << readBuffer << std::endl;
            }
    
            // 清理
            fclose(file);
            curl_slist_free_all(chunk);
            curl_easy_cleanup(curl);
        }
    
        curl_global_cleanup();
    }
    
    int main() {
        // 將<signedUrl>替換為授權URL。
        std::string signedUrl = "<signedUrl>";
    
        // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
        std::string filePath = "C:\\Users\\demo.txt";
    
        // 佈建要求頭,這裡的要求標頭資訊需要與產生URL時的資訊一致。
        std::map<std::string, std::string> headers = {
             {"Content-Type", "text/plain;charset=utf8"},
             {"x-oss-storage-class", "Standard"}
        };
    
        // 設定使用者自訂中繼資料,這裡的使用者自訂中繼資料需要與產生URL時的資訊一致。
        std::map<std::string, std::string> metadata = {
             {"key1", "value1"},
             {"key2", "value2"}
        };
    
        uploadFile(signedUrl, filePath, headers, metadata);
    
        return 0;
    }
    

    Android

    import android.os.AsyncTask;
    import android.util.Log;
    
    import java.io.DataOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Map.Entry;
    
    public class SignUrlUploadActivity extends AppCompatActivity {
    
        private static final String TAG = "SignUrlUploadActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // 將<signedUrl>替換為授權URL。
            String signedUrl = "<signedUrl>";
    
            // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
            String pathName = "/storage/emulated/0/demo.txt";
    
            // 佈建要求頭,這裡的要求標頭資訊需要與產生URL時的資訊一致。
            Map<String, String> headers = new HashMap<>();
            headers.put("Content-Type", "text/plain;charset=utf8");
            headers.put("x-oss-storage-class", "Standard");
    
            // 設定使用者自訂中繼資料,這裡的使用者自訂中繼資料需要與產生URL時的資訊一致。
            Map<String, String> userMetadata = new HashMap<>();
            userMetadata.put("key1", "value1");
            userMetadata.put("key2", "value2");
    
            new UploadTask().execute(signedUrl, pathName, headers, userMetadata);
        }
    
        private class UploadTask extends AsyncTask<Object, Void, Integer> {
            @Override
            protected Integer doInBackground(Object... params) {
                String signedUrl = (String) params[0];
                String pathName = (String) params[1];
                Map<String, String> headers = (Map<String, String>) params[2];
                Map<String, String> userMetadata = (Map<String, String>) params[3];
    
                try {
                    URL url = new URL(signedUrl);
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("PUT");
                    connection.setDoOutput(true);
                    connection.setUseCaches(false);
    
                    // 佈建要求頭
                    for (Entry<String, String> header : headers.entrySet()) {
                        connection.setRequestProperty(header.getKey(), header.getValue());
                    }
    
                    // 設定使用者自訂中繼資料
                    for (Entry<String, String> meta : userMetadata.entrySet()) {
                        connection.setRequestProperty("x-oss-meta-" + meta.getKey(), meta.getValue());
                    }
    
                    // 讀取檔案
                    File file = new File(pathName);
                    FileInputStream fileInputStream = new FileInputStream(file);
                    DataOutputStream dos = new DataOutputStream(connection.getOutputStream());
    
                    byte[] buffer = new byte[1024];
                    int count;
                    while ((count = fileInputStream.read(buffer)) != -1) {
                        dos.write(buffer, 0, count);
                    }
    
                    fileInputStream.close();
                    dos.flush();
                    dos.close();
    
                    // 擷取響應
                    int responseCode = connection.getResponseCode();
                    Log.d(TAG, "返回上傳狀態代碼:" + responseCode);
                    if (responseCode == 200) {
                        Log.d(TAG, "使用網路程式庫上傳成功");
                    } else {
                        Log.d(TAG, "上傳失敗");
                    }
    
                    InputStream is = connection.getInputStream();
                    byte[] responseBuffer = new byte[1024];
                    StringBuilder responseStringBuilder = new StringBuilder();
                    while ((count = is.read(responseBuffer)) != -1) {
                        responseStringBuilder.append(new String(responseBuffer, 0, count));
                    }
                    Log.d(TAG, responseStringBuilder.toString());
    
                    return responseCode;
                } catch (IOException e) {
                    e.printStackTrace();
                    return -1;
                }
            }
    
            @Override
            protected void onPostExecute(Integer result) {
                super.onPostExecute(result);
                if (result == 200) {
                    Toast.makeText(SignUrlUploadActivity.this, "上傳成功", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(SignUrlUploadActivity.this, "上傳失敗", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }
    

設定上傳回調

上傳時,您可以添加回調參數,讓檔案上傳成功後自動通知應用伺服器。回調的詳細原理,請參見 Callback

  1. 檔案擁有者產生攜帶上傳回調參數的PUT方法的預簽名URL。

    Python

    import argparse
    import base64
    import requests
    import alibabacloud_oss_v2 as oss
    
    from datetime import datetime, timedelta
    
    # 建立命令列參數解析器,並描述指令碼用途:產生對象預簽名PUT請求連結(Presign Put Object)
    parser = argparse.ArgumentParser(description="presign put object sample")
    
    # 定義命令列參數,包括必需的地區、儲存空間名稱、endpoint以及對象鍵名
    parser.add_argument('--region', help='The region in which the bucket is located.', required=True)
    parser.add_argument('--bucket', help='The name of the bucket.', required=True)
    parser.add_argument('--endpoint', help='The domain names that other services can use to access OSS')
    parser.add_argument('--key', help='The name of the object.', required=True)
    
    def main():
        # 解析命令列參數,擷取使用者輸入的值
        args = parser.parse_args()
    
        # 從環境變數中載入訪問憑證資訊,用於身分識別驗證
        credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    
        # 使用SDK預設配置建立設定物件,並設定認證提供者
        cfg = oss.config.load_default()
        cfg.credentials_provider = credentials_provider
    
        # 設定設定物件的地區屬性,根據使用者提供的命令列參數
        cfg.region = args.region
    
        # 如果提供了自訂endpoint,則更新設定物件中的endpoint屬性
        if args.endpoint is not None:
            cfg.endpoint = args.endpoint
    
        # 使用上述配置初始化OSS用戶端,準備與OSS互動
        client = oss.Client(cfg)
    
        # 請填寫您的自訂回調地址
        call_back_url = "http://www.example.com/callback"
        # 構造回調參數(callback):指定回調地址和回調請求體,使用 Base 64 編碼
        callback=base64.b64encode(str('{\"callbackUrl\":\"' + call_back_url + '\",\"callbackBody\":\"bucket=${bucket}&object=${object}&my_var_1=${x:var1}&my_var_2=${x:var2}\"}').encode()).decode()
        # 構造自訂變數(callback-var),使用 Base 64 編碼
        callback_var=base64.b64encode('{\"x:var1\":\"value1\",\"x:var2\":\"value2\"}'.encode()).decode()
    
        # 發送請求以產生指定對象的預簽名PUT請求
        pre_result = client.presign(oss.PutObjectRequest(
            bucket=args.bucket,  # 儲存空間名
            key=args.key,  # 對象鍵名
            callback=callback,
            callback_var=callback_var,
        ),expires=timedelta(seconds=3600)) # 設定到期時間,單位為秒,此處設定為3600秒
    
        # 列印預簽章要求的方法、到期時間和URL,以便確認預簽名連結的有效性
        print(f'method: {pre_result.method},'
              f' expiration: {pre_result.expiration.strftime("%Y-%m-%dT%H:%M:%S.000Z")},'
              f' url: {pre_result.url}'
              )
    
        # 列印預簽章要求的已簽名頭資訊,這些資訊在發送實際請求時會被包含在HTTP頭部
        for key, value in pre_result.signed_headers.items():
            print(f'signed headers key: {key}, signed headers value: {value}')
    
    # 當此指令碼被直接執行時,調用main函數開始處理邏輯
    if __name__ == "__main__":
        main()  # 指令碼進入點,控製程序流程從這裡開始

    Go

    package main
    
    import (
    	"context"
    	"encoding/base64"
    	"encoding/json"
    	"flag"
    	"log"
    	"time"
    
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
    )
    
    // 定義全域變數
    var (
    	region     string // 儲存地區
    	bucketName string // 儲存空間名稱
    	objectName string // 對象名稱
    )
    
    // init函數用於初始化命令列參數
    func init() {
    	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
    	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
    	flag.StringVar(&objectName, "object", "", "The name of the object.")
    }
    
    func main() {
    	// 解析命令列參數
    	flag.Parse()
    
    	// 檢查bucket名稱是否為空白
    	if len(bucketName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, bucket name required")
    	}
    
    	// 檢查region是否為空白
    	if len(region) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, region required")
    	}
    
    	// 檢查object名稱是否為空白
    	if len(objectName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, object name required")
    	}
    
    	// 載入預設配置並設定憑證提供者和地區
    	cfg := oss.LoadDefaultConfig().
    		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
    		WithRegion(region)
    
    	// 建立OSS用戶端
    	client := oss.NewClient(cfg)
    
    	// 定義回調參數
    	callbackMap := map[string]string{
    		"callbackUrl":      "http://example.com:23450",                                                                   // 設定回調伺服器的URL,例如https://example.com:23450。
    		"callbackBody":     "bucket=${bucket}&object=${object}&size=${size}&my_var_1=${x:my_var1}&my_var_2=${x:my_var2}", // 設定回調請求體。
    		"callbackBodyType": "application/x-www-form-urlencoded",                                                          //設定回調請求體類型。
    	}
    
    	// 將回調參數轉換為JSON並進行Base64編碼,以便將其作為回調參數傳遞
    	callbackStr, err := json.Marshal(callbackMap)
    	if err != nil {
    		log.Fatalf("failed to marshal callback map: %v", err)
    	}
    	callbackBase64 := base64.StdEncoding.EncodeToString(callbackStr)
    
    	callbackVarMap := map[string]string{}
    	callbackVarMap["x:my_var1"] = "thi is var 1"
    	callbackVarMap["x:my_var2"] = "thi is var 2"
    	callbackVarStr, err := json.Marshal(callbackVarMap)
    	if err != nil {
    		log.Fatalf("failed to marshal callback var: %v", err)
    	}
    	callbackVarBase64 := base64.StdEncoding.EncodeToString(callbackVarStr)
    
    	// 產生PutObject的預簽名URL
    	result, err := client.Presign(context.TODO(), &oss.PutObjectRequest{
    		Bucket:      oss.Ptr(bucketName),
    		Key:         oss.Ptr(objectName),
    		Callback:    oss.Ptr(callbackBase64),    // 設定回調參數,此處為Base64編碼後的JSON字串
    		CallbackVar: oss.Ptr(callbackVarBase64), // 設定自訂回調參數,此處為Base64編碼後的JSON字串
    	},
    		oss.PresignExpires(10*time.Minute),
    	)
    	if err != nil {
    		log.Fatalf("failed to put object presign %v", err)
    	}
    
    	log.Printf("request method:%v\n", result.Method)
    	log.Printf("request expiration:%v\n", result.Expiration)
    	log.Printf("request url:%v\n", result.URL)
    	if len(result.SignedHeaders) > 0 {
    		//當返回結果包含簽名頭時,使用簽名URL發送Put請求時,需要設定相應的要求標頭
    		log.Printf("signed headers:\n")
    		for k, v := range result.SignedHeaders {
    			log.Printf("%v: %v\n", k, v)
    		}
    	}
    }
    

    Java

    import com.aliyun.oss.*;
    import com.aliyun.oss.common.auth.CredentialsProviderFactory;
    import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
    import com.aliyun.oss.common.comm.SignVersion;
    import com.aliyun.oss.internal.OSSHeaders;
    import com.aliyun.oss.model.GeneratePresignedUrlRequest;
    
    import java.net.URL;
    import java.text.SimpleDateFormat;
    import java.util.*;
    
    public class OssPresignExample {
        public static void main(String[] args) throws Throwable {
            // 以華東1(杭州)的外網Endpoint為例,其它Region請按實際情況填寫。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 填寫Bucket名稱,例如examplebucket。
            String bucketName = "examplebucket";
            // 填寫Object完整路徑,例如exampleobject.txt。Object完整路徑中不能包含Bucket名稱。
            String objectName = "exampleobject.txt";
            // 填寫Bucket所在地區。以華東1(杭州)為例,Region填寫為cn-hangzhou。
            String region = "cn-hangzhou";
    
            // 建立OSSClient執行個體。
            // 當OSSClient執行個體不再使用時,調用shutdown方法以釋放資源。
            ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
            clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
            OSS ossClient = OSSClientBuilder.create()
                    .endpoint(endpoint)
                    .credentialsProvider(credentialsProvider)
                    .clientConfiguration(clientBuilderConfiguration)
                    .region(region)
                    .build();
    
            URL signedUrl = null;
            try {
                // 構造回調參數
                String callbackUrl = "http://www.example.com/callback";
                String callbackBody = "{\"callbackUrl\":\"" + callbackUrl + "\",\"callbackBody\":\"bucket=${bucket}&object=${object}&my_var_1=${x:var1}&my_var_2=${x:var2}\"}";
                String callbackBase64 = Base64.getEncoder().encodeToString(callbackBody.getBytes());
    
                String callbackVarJson = "{\"x:var1\":\"value1\",\"x:var2\":\"value2\"}";
                String callbackVarBase64 = Base64.getEncoder().encodeToString(callbackVarJson.getBytes());
                // 佈建要求頭。
                Map<String, String> headers = new HashMap<String, String>();
                // 指定CALLBACK。
                headers.put(OSSHeaders.OSS_HEADER_CALLBACK, callbackBase64);
                // 指定CALLBACK-VAR。
                headers.put(OSSHeaders.OSS_HEADER_CALLBACK_VAR, callbackVarBase64);
    
                // 設定到期時間(3600秒後)
                Date expiration = new Date(new Date().getTime() + 3600 * 1000);
    
                // 格式化到期時間
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
                dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
                String expirationStr = dateFormat.format(expiration);
    
                // 構造請求
                GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName);
                request.setMethod(HttpMethod.PUT);
                request.setExpiration(expiration);
                // 將要求標頭加入到request中。
                request.setHeaders(headers);
    
                //列印callback參數和callback-var參數
                System.out.println("callback:"+callbackBase64);
                System.out.println("callback-var:"+callbackVarBase64);
    
                // 產生預簽名URL
                URL url = ossClient.generatePresignedUrl(request);
    
                // 輸出結果
                System.out.println("method: PUT,");
                System.out.println(" expiration: " + expirationStr + ",");
                System.out.println(" url: " + url);
    
            } 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.");
                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());
            }
        }
    }

    PHP

    <?php
    
    // 引入自動負載檔案,確保依賴庫能夠正確載入
    require_once __DIR__ . '/../vendor/autoload.php';
    
    use AlibabaCloud\Oss\V2 as Oss;
    
    // 定義命令列參數的描述資訊
    $optsdesc = [
        "region" => ['help' => 'The region in which the bucket is located.', 'required' => True], // Bucket所在的地區(必填)
        "endpoint" => ['help' => 'The domain names that other services can use to access OSS.', 'required' => False], // 訪問網域名稱(可選)
        "bucket" => ['help' => 'The name of the bucket', 'required' => True], // Bucket名稱(必填)
        "key" => ['help' => 'The name of the object', 'required' => True], // 對象名稱(必填)
    ];
    
    // 將參數描述轉換為getopt所需的長選項格式
    // 每個參數後面加上":"表示該參數需要值
    $longopts = \array_map(function ($key) {
        return "$key:";
    }, array_keys($optsdesc));
    
    // 解析命令列參數
    $options = getopt("", $longopts);
    
    // 驗證必填參數是否存在
    foreach ($optsdesc as $key => $value) {
        if ($value['required'] === True && empty($options[$key])) {
            $help = $value['help']; // 擷取參數的協助資訊
            echo "Error: the following arguments are required: --$key, $help" . PHP_EOL;
            exit(1); // 如果必填參數缺失,則退出程式
        }
    }
    
    // 從解析的參數中提取值
    $region = $options["region"]; // Bucket所在的地區
    $bucket = $options["bucket"]; // Bucket名稱
    $key = $options["key"];       // 對象名稱
    
    // 載入環境變數中的憑證資訊
    // 使用EnvironmentVariableCredentialsProvider從環境變數中讀取Access Key ID和Access Key Secret
    $credentialsProvider = new Oss\Credentials\EnvironmentVariableCredentialsProvider();
    
    // 使用SDK的預設配置
    $cfg = Oss\Config::loadDefault();
    $cfg->setCredentialsProvider($credentialsProvider); // 設定憑證提供者
    $cfg->setRegion($region); // 設定Bucket所在的地區
    if (isset($options["endpoint"])) {
        $cfg->setEndpoint($options["endpoint"]); // 如果提供了訪問網域名稱,則設定endpoint
    }
    
    // 建立OSS用戶端執行個體
    $client = new Oss\Client($cfg);
    
    // 添加x-oss-callback和x-oss-callback-var頭部資訊
    // 定義回調地址
    $call_back_url = "http://www.example.com/callback";
    
    // 構造回調參數(callback):指定回調地址和回調請求體,使用 Base 64 編碼
    // 使用預留位置 {var1} 和 {var2} 替代 ${x:var1} 和 ${x:var2}
    $callback_body_template = "bucket={bucket}&object={object}&my_var_1={var1}&my_var_2={var2}";
    $callback_body_replaced = str_replace(
        ['{bucket}', '{object}', '{var1}', '{var2}'],
        [$bucket, $key, 'value1', 'value2'],
        $callback_body_template
    );
    $callback = base64_encode(json_encode([
        "callbackUrl" => $call_back_url,
        "callbackBody" => $callback_body_replaced
    ]));
    
    // 構造自訂變數(callback-var),使用 Base 64 編碼
    $callback_var = base64_encode(json_encode([
        "x:var1" => "value1",
        "x:var2" => "value2"
    ]));
    
    // 建立PutObjectRequest對象,用於上傳對象。
    // 注意:此處添加了contentType、metadata和headers參數,用於簽名計算。
    $request = new Oss\Models\PutObjectRequest(
        bucket: $bucket,
        key: $key,
        callback:$callback,
        callbackVar:$callback_var,
    );
    
    // 調用presign方法產生預簽名URL
    $result = $client->presign($request);
    
    // 列印預簽名結果,輸出預簽名URL,使用者可以直接使用該URL進行上傳操作
    print(
        'put object presign result:' . var_export($result, true) . PHP_EOL .
        'put object url:' . $result->url . PHP_EOL
    );
    
  2. 其他人使用PUT方法的預簽名URL上傳檔案。

    curl

    curl -X PUT \
         -H "x-oss-callback: eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9" \
         -H "x-oss-callback-var: eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==" \
         -T "C:\\Users\\demo.txt" \
         "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"

    Python

    import requests
    
    def upload_file(signed_url, file_path, headers=None):
        """
        使用預簽名的URL上傳檔案到OSS。
    
        :param signed_url: 預簽名的URL。
        :param file_path: 要上傳的檔案的完整路徑。
        :param headers: 可選,自訂HTTP頭部。
        :return: None
        """
        if not headers:
            headers = {}
    
        try:
            with open(file_path, 'rb') as file:
                response = requests.put(signed_url, data=file, headers=headers)
                print(f"返回上傳狀態代碼:{response.status_code}")
                if response.status_code == 200:
                    print("使用網路程式庫上傳成功")
                else:
                    print("上傳失敗")
                print(response.text)
        except Exception as e:
            print(f"發生錯誤:{e}")
    
    if __name__ == "__main__":
        # 將<signedUrl>替換為授權URL。
        signed_url = "<signedUrl>"
    
        # 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從指令碼所在目錄中上傳檔案。
        file_path = "C:\\Users\\demo.txt"
    
        headers = {
            "x-oss-callback": "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9",
            "x-oss-callback-var": "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==",
        }
    
        upload_file(signed_url,  file_path, headers)

    Go

    package main
    
    import (
    	"bytes"
    	"fmt"
    	"io"
    
    	"net/http"
    	"os"
    )
    
    func uploadFile(signedUrl string, filePath string, headers map[string]string) error {
    	// 開啟檔案
    	file, err := os.Open(filePath)
    	if err != nil {
    		return err
    	}
    	defer file.Close()
    
    	// 讀取檔案內容
    	fileBytes, err := io.ReadAll(file)
    	if err != nil {
    		return err
    	}
    
    	// 建立請求
    	req, err := http.NewRequest("PUT", signedUrl, bytes.NewBuffer(fileBytes))
    	if err != nil {
    		return err
    	}
    
    	// 佈建要求頭
    	for key, value := range headers {
    		req.Header.Add(key, value)
    	}
    
    	// 發送請求
    	client := &http.Client{}
    	resp, err := client.Do(req)
    	if err != nil {
    		return err
    	}
    	defer resp.Body.Close()
    
    	// 處理響應
    	fmt.Printf("返回上傳狀態代碼:%d\n", resp.StatusCode)
    	if resp.StatusCode == 200 {
    		fmt.Println("使用網路程式庫上傳成功")
    	} else {
    		fmt.Println("上傳失敗")
    	}
    	body, _ := io.ReadAll(resp.Body)
    	fmt.Println(string(body))
    
    	return nil
    }
    
    func main() {
    	// 將<signedUrl>替換為授權URL。
    	signedUrl := "<signedUrl>"
    	// 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
    	filePath := "C:\\Users\\demo.txt"
    
    	// 佈建要求頭,這裡的要求標頭資訊需要與產生URL時的資訊一致。
    	headers := map[string]string{
    		"x-oss-callback":     "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9",
    		"x-oss-callback-var": "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==",
    	}
    
    	err := uploadFile(signedUrl, filePath, headers)
    	if err != nil {
    		fmt.Printf("發生錯誤:%v\n", err)
    	}
    }
    

    Java

    import org.apache.http.HttpEntity;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpPut;
    import org.apache.http.entity.FileEntity;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import java.io.*;
    import java.net.URL;
    import java.util.*;
    
    public class SignUrlUpload {
        public static void main(String[] args) throws Throwable {
            CloseableHttpClient httpClient = null;
            CloseableHttpResponse response = null;
    
            // 將<signedUrl>替換為授權URL。
            URL signedUrl = new URL("<signedUrl>");
    
            // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
            String pathName = "C:\\Users\\demo.txt";
    
            // 佈建要求頭,包括x-oss-callback和x-oss-callback-var。
            Map<String, String> headers = new HashMap<String, String>();
            headers.put("x-oss-callback", "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9");
            headers.put("x-oss-callback-var", "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==");
    
            try {
                HttpPut put = new HttpPut(signedUrl.toString());
                System.out.println(put);
                HttpEntity entity = new FileEntity(new File(pathName));
                put.setEntity(entity);
                // 如果產生預簽名URL時設定了header參數,則調用預簽名URL上傳檔案時,也需要將這些參數發送至服務端。如果簽名和發送至服務端的不一致,會報簽名錯誤。
                for(Map.Entry header: headers.entrySet()){
                    put.addHeader(header.getKey().toString(),header.getValue().toString());
                }
    
                httpClient = HttpClients.createDefault();
    
                response = httpClient.execute(put);
    
                System.out.println("返回上傳狀態代碼:"+response.getStatusLine().getStatusCode());
                if(response.getStatusLine().getStatusCode() == 200){
                    System.out.println("使用網路程式庫上傳成功");
                }
                System.out.println(response.toString());
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                response.close();
                httpClient.close();
            }
        }
    }       

    PHP

    <?php
    
    function uploadFile($signedUrl, $filePath, $headers = []) {
        // 檢查檔案是否存在
        if (!file_exists($filePath)) {
            echo "檔案不存在: $filePath\n";
            return;
        }
    
        // 初始化cURL會話
        $ch = curl_init();
    
        // 設定cURL選項
        curl_setopt($ch, CURLOPT_URL, $signedUrl);
        curl_setopt($ch, CURLOPT_PUT, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_INFILE, fopen($filePath, 'rb'));
        curl_setopt($ch, CURLOPT_INFILESIZE, filesize($filePath));
        curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(function($key, $value) {
            return "$key: $value";
        }, array_keys($headers), $headers));
    
        // 執行cURL請求
        $response = curl_exec($ch);
    
        // 擷取HTTP狀態代碼
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
        // 關閉cURL會話
        curl_close($ch);
    
        // 輸出結果
        echo "返回上傳狀態代碼:$httpCode\n";
        if ($httpCode == 200) {
            echo "使用網路程式庫上傳成功\n";
        } else {
            echo "上傳失敗\n";
        }
        echo $response . "\n";
    }
    
    // 將<signedUrl>替換為授權URL。
    $signedUrl = "<signedUrl>";
    
    // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從指令碼所在目錄中上傳檔案。
    $filePath = "C:\\Users\\demo.txt";
    
    $headers = [
        "x-oss-callback" => "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9",
        "x-oss-callback-var" => "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==",
    ];
    
    uploadFile($signedUrl, $filePath, $headers);
    
    ?>

瞭解更多

什麼是簽名URL

預簽名URL是一種安全連結,通過加密簽名和有效期間驗證,臨時授權訪問特定的OSS檔案。產生預簽名URL時,本地基於AK/SK金鑰組資源路徑、到期時間等參數進行加密計算,產生簽名參數並將其添加到URL中,形成完整的預簽名URL。其典型格式為:https://BucketName.Endpoint/Object?簽名參數

當第三方訪問該URL時,OSS會校正簽名參數,若參數被篡改或到期則拒絕訪問。

以下為預簽名URL樣本。

https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-process=image%2Fresize%2Cp_10&x-oss-date=20241115T095058Z&x-oss-expires=3600&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241115%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=6e7a********************************

通過這種方式,檔案擁有者可以安全地授權第三方訪問檔案,而無需暴露密鑰。

適用情境

  • 短期檔案分享權限設定:第三方申請上傳或下載指定檔案,後端便產生帶有效期間的預簽名URL並返回給前端。第三方可以在有效期間內使用該URL上傳或下載檔案,從而保障資料安全。

  • 靈活訪問:檔案擁有者可以通過郵件或聊天工具等方式分享預簽名URL,第三方擷取URL後在瀏覽器地址欄粘貼即可下載檔案。

常見問題

如何授權第三方對OSS資源進行更多操作,而不僅限於上傳?

除了預簽名URL,阿里雲還提供了更靈活的臨時授權方式——STS臨時訪問憑證。如果您希望第三方對OSS的操作不只局限於上傳,而是可以執行例如:列舉檔案、拷貝檔案等更多OSS資源的管理操作,建議您瞭解並使用STS臨時訪問憑證,詳情請參見使用STS臨時訪問憑證訪問OSS

是否可以僅允許指定網站訪問OSS資源,拒絕其他來源的請求?

是的,您可以通過配置Referer防盜鏈,設定白名單,限制只有通過特定網站(如您的網站)才能訪問OSS資源,從而防止其他惡意來源未通過您的網站而直接引用您的OSS檔案。詳細配置步驟,請參見配置Referer防盜鏈來阻止其他網站引用OSS檔案

報錯CORS錯誤

沒有配置Bucket跨域策略或跨域策略配置錯誤,請參考跨網域設定進行配置檢查。

報錯405 Method Not Allowed

要求方法不正確。使用簽名URL上傳檔案時,注意使用PUT請求非POST請求。