全部產品
Search
文件中心

Object Storage Service:使用預簽名URL上傳(Java SDK V1)

更新時間:Nov 26, 2025

預設情況下,OSS Bucket中的檔案是私人的,僅檔案擁有者擁有上傳許可權。您可以使用OSS Java SDK產生預簽名URL,以允許他人通過該URL上傳檔案。在產生預簽名URL時,可以自訂其到期時間以限制訪問持續時間長度。在預簽名URL有效期間內,該URL可被多次訪問。如果多次執行上傳操作,會有檔案覆蓋的風險。超出有效期間後,將無法進行上傳,此時需要重建預簽名URL。

注意事項

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

  • 本文以從環境變數讀取存取憑證為例。如何配置訪問憑證,請參見Java配置訪問憑證

  • 本文以OSS網域名稱建立OSSClient為例。如果您希望通過自訂網域名、STS等方式建立OSSClient,請參見常見情境配置樣本

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

  • 本文以V4預簽名URL為例,有效期間最大為7天。更多資訊,請參見簽名版本4(推薦)

使用過程

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

程式碼範例

  1. 檔案擁有者產生PUT方法的預簽名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());
            }
        }
    }       
  2. 其他人使用PUT方法的預簽名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指定要求標頭和自訂中繼資料上傳檔案

  1. 檔案擁有者產生指定要求標頭和自訂中繼資料的PUT方法的預簽名URL。

    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());
            }
        }
    }       
  2. 其他人使用PUT方法的預簽名URL上傳檔案。

    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();
                }
            }
        }
    }
    

使用簽名URL分區上傳檔案

使用預簽名URL分區上傳檔案需要配置分區大小並逐片產生預簽名URL,範例程式碼如下:

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.common.utils.CRC64;
import com.aliyun.oss.internal.Mimetypes;
import com.aliyun.oss.model.*;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.Date;

public class MultipartUrl {
    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";
        // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
        String pathName = "D:\\localpath\\examplefile.txt";
        // 指定產生的預簽名URL到期時間,單位為毫秒。本樣本以設定到期時間為1小時為例。
        long expireTime = 3600*1000L;
        // 填寫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();

        // 建立InitiateMultipartUploadRequest對象。
        InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName, objectName);
        ObjectMetadata metadata = new ObjectMetadata();
        // 根據檔案自動化佈建ContentType。如果不設定,ContentType預設值為application/octet-stream
        if (metadata.getContentType() == null) {
            metadata.setContentType(Mimetypes.getInstance().getMimetype(new File(pathName), objectName));
        }

        initRequest.setObjectMetadata(metadata);
        // 初始化分區。
        InitiateMultipartUploadResult upResult = ossClient.initiateMultipartUpload(initRequest);
        // 返回uploadId。uploadId是分區上傳事件的唯一標識。您可以根據該uploadId發起相關的操作,例如取消分區上傳、查詢分區上傳等。
        String uploadId = upResult.getUploadId();

        // partETags是PartETag的集合。PartETag由分區的ETag和分區號組成。
        List<PartETag> partETags =  new ArrayList<PartETag>();
        // 每個分區的大小,用於計算檔案有多少個分區。單位為位元組。
        long partSize = 1 * 100 * 1024L;   //100kb。

        // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
        File sampleFile = new File(pathName);
        long fileLength = sampleFile.length();
        // 如果希望設定為1個分區,可以將分區大小設定為檔案大小。
        // long fileLength = sampleFile.length();
        int partCount = (int) (fileLength / partSize);
        if (fileLength % partSize != 0) {
            partCount++;
        }

        // 設定預簽名URL的要求標頭。
        Map<String, String> headers = new HashMap<String, String>();
        // 指定ContentType。
        // headers.put(OSSHeaders.CONTENT_TYPE, "text/plain");


        // 遍曆分區擷取分區簽名,並上傳分區。
        // 您還可以一次返回所有分區的預簽名URL,然後依次上傳。此處以返回單個預簽名URL,並通過預簽名URL上傳單個分區為例。
        for (int i = 0; i < partCount; i++) {
            long startPos = i * partSize;
            long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;

            /*// 設定md5校正,只支援對單個分區進行md5校正
            FileInputStream inStream = new FileInputStream(pathName);
            // 跳過已經上傳的分區。
            inStream.skip(startPos);
            BoundedInputStream entity = new BoundedInputStream(inStream, partSize);
            String md5 = BinaryUtil.toBase64String(DigestUtils.md5(entity));
            headers.put("Content-MD5", md5);*/

            String signUrl = getSignUrl(ossClient, bucketName, objectName, HttpMethod.PUT, expireTime, i + 1, uploadId, headers);

            // 通過預簽名URL上傳檔案,以HttpClients為例說明。
            putObjectWithHttp(signUrl, pathName, startPos, curPartSize, headers);
        }

        // 假設合并分區時,與上傳分區不在同一個系統。此時,您需要先列舉分區,然後再合并分區。
        // 列舉已上傳的分區。
        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, objectName, uploadId);
        PartListing partListing = ossClient.listParts(listPartsRequest);

        // 遍曆分區,並填充partETags。
        for (PartSummary part : partListing.getParts()) {
            PartETag partETag = new PartETag(part.getPartNumber(), part.getETag());
            partETags.add(partETag);
        }

        // 合并分區。
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
        // String md5 = BinaryUtil.toBase64String(BinaryUtil.calculateMd5("aaa".getBytes()));
        // 設定禁止覆蓋同名檔案。
        // completeMultipartUploadRequest.addHeader("x-oss-forbid-overwrite", "true");

        // 完成分區上傳。
        CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
        System.out.println("合并分區成功,上傳分區完成。");


        // 校正整體上傳檔案是否完整
        CRC64 crc = new CRC64();
        InputStream inStream = new FileInputStream(pathName);
        byte[] bytes = new byte[1024];
        int cnt;
        while ((cnt = inStream.read(bytes)) != -1) {
            crc.update(bytes, 0, cnt);
        }

        if(crc.getValue() == completeMultipartUploadResult.getServerCRC()){
            System.out.println("上傳檔案完整");
        } else {
            System.out.println("上傳檔案不完整,請做異常處理");
        }
    }

    public static void putObjectWithHttp(String signedUrl, String pathName, long startPos, long partSize, Map<String, String> headers) throws IOException {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        try {
            HttpPut put = new HttpPut(signedUrl);

            FileInputStream inStream = new FileInputStream(pathName);
            // 跳過已經上傳的分區。
            inStream.skip(startPos);
            InputStreamEntity entity = new InputStreamEntity(inStream, partSize);
            BufferedHttpEntity byteArrayEntity = new BufferedHttpEntity(entity);
            put.setEntity(byteArrayEntity);

            // 如果產生預簽名URL時設定了header參數,例如使用者中繼資料,儲存類型等,則呼叫簽章URL上傳檔案時,也需要將這些參數發送至服務端。如果簽名和發送至服務端的不一致,會報簽名錯誤。
            for(Map.Entry header: headers.entrySet()){
                put.addHeader(header.getKey().toString(),header.getValue().toString());
            }

            // 加入重試,設定為重試3次。這裡僅為舉例,業務代碼根據需要自行設定重試
            httpClient = HttpClients.custom().setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)).build();

            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 {
            if(response != null){
                response.close();
            }
            if(httpClient != null){
                httpClient.close();
            }
        }
    }

    public static String getSignUrl(OSS ossClient, String bucketName, String objectName, HttpMethod method, long expireTime, int partNum, String uploadId, Map<String, String> headers){
        // 指定產生的預簽名URL到期時間,單位為毫秒。
        Date expiration = new Date(new Date().getTime() + expireTime);

        // 產生預簽名URL。
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, method);
        // 設定到期時間。
        request.setExpiration(expiration);

        // 將要求標頭加入到request中。
        request.setHeaders(headers);

        request.addQueryParameter("partNumber", String.valueOf(partNum));

        request.addQueryParameter("uploadId", uploadId);


        // 通過HTTP Method請求產生預簽名URL。
        URL signedUrl = ossClient.generatePresignedUrl(request);
        // 列印預簽名URL。
        System.out.println("signed url: " + signedUrl);
        return signedUrl.toString();
    }
}

使用預簽名URL上傳檔案並設定上傳回調參數

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

    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());
            }
        }
    }
  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只要是在有效期間裡(取Token的有效期間和預簽名有效期間最小值),都可以使用。

如果我在產生URL時未佈建要求頭和自訂中繼資料,在使用URL上傳時還需要配置嗎?

不需要配置。

要求標頭和自訂中繼資料為非必要參數,如果不佈建要求頭和自訂中繼資料可以將樣本中相關代碼去掉。

相關文檔