赛尔校园公共服务平台 Logo
平台使用
阿里云
百度云
移动云
智算服务
教育生态
登录 →
赛尔校园公共服务平台 Logo
平台使用 阿里云 百度云 移动云 智算服务 教育生态
登录
  1. 首页
  2. 阿里云
  3. 对象存储
  4. 开发参考
  5. SDK参考
  6. Go
  7. 2.0手册
  8. 对象文件
  9. 上传文件
  10. 使用预签名URL上传

使用预签名URL上传

  • 上传文件
  • 发布于 2025-04-21
  • 0 次阅读
文档编辑
文档编辑

默认情况下,OSS Bucket中的文件是私有的,仅文件拥有者可访问。本文介绍如何使用OSS Go SDK生成带有过期时间的PUT方法预签名URL,以允许他人临时上传文件。在有效期内可多次访问,超期后需重新生成。

注意事项

  • 本文示例代码以华东1(杭州)的地域IDcn-hangzhou为例,默认使用外网Endpoint,如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的Region与Endpoint的对应关系,请参见OSS地域和访问域名。

  • 本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见配置访问凭证。

  • 预签名URL无需权限即可生成,但仅当您拥有oss:PutObject权限时,第三方才能通过该预签名URL成功上传文件。具体授权操作,请参见为RAM用户授权自定义的权限策略。

  • 本文示例代码使用V4预签名URL,有效期最大为7天。更多信息,请参见签名版本4(推荐)。

使用过程

使用PUT方式的预签名URL上传文件的过程如下:

image

方法定义

您可以使用预签名接口生成预签名URL,授予对存储空间中对象的限时访问权限。在过期时间之前,您可以多次使用预签名URL。

预签名接口定义如下:

func (c *Client) Presign(ctx context.Context, request any, optFns ...func(*PresignOptions)) (result *PresignResult, err error)

请求参数列表

参数名

类型

说明

ctx

context.Context

请求的上下文

request

*PutObjectRequest

设置需要生成预签名URL的接口名

optFns

...func(*PresignOptions)

(可选)设置过期时间,如果不指定,默认有效期为15分钟

其中,PresignOptions选项列举如下:

选项值

类型

说明

Expires

time.Duration

从当前时间开始,多长时间过期。例如设置一个有效期为30分钟,30 * time.Minute

Expiration

time.Time

绝对过期时间

重要

在签名版本V4下,有效期最长为7天。同时设置Expiration和Expires时,优先取Expiration。

返回值列表

返回值名

类型

说明

result

*PresignResult

返回结果,包含预签名URL,HTTP方法,过期时间和参与签名的请求头

err

error

请求的状态,当请求失败时,err不为nil

其中,PresignResult返回值列举如下:

参数名

类型

说明

Method

string

HTTP方法,和接口对应,例如PutObject接口,返回PUT

URL

string

预签名URL

Expiration

time.Time

预签名URL的过期时间

SignedHeaders

map[string]string

被签名的请求头,例如PutObject接口,设置了Content-Type时,会返回 Content-Type的信息

示例代码

  1. 文件拥有者生成PUT方法的预签名URL。

    重要

    在生成PUT方法的预签名URL时,如果指定了请求头,确保在通过该预签名URL发起PUT请求时也包含相应的请求头,以免出现不一致,导致请求失败和签名错误。

    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)
    		}
    	}
    }
    
  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++

    #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。

    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)
    		}
    	}
    }
    
  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 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</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++

    #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,示例代码如下:

package main

import (
	"bytes"
	"context"
	"flag"
	"fmt"
	"log"
	"net/http"
	"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                     // 对象名称
	length     = int64(5000 * 1024)       // 文件总长度,单位为字节
	partSize   = int64(200 * 1024)        // 每个分片的大小,单位为字节
	partsNum   = int(length/partSize + 1) // 分片的数量
	data       = make([]byte, length)     // 模拟的数据,用于上传
)

// 初始化命令行参数
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()

	// 检查必要参数是否已设置
	if len(bucketName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, bucket name required")
	}

	if len(region) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, region required")
	}

	if len(objectName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, object name required")
	}

	// 配置 OSS 客户端
	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion(region)

	// 创建 OSS 客户端
	client := oss.NewClient(cfg)

	// 初始化分片上传
	initResult, err := client.InitiateMultipartUpload(context.TODO(), &oss.InitiateMultipartUploadRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectName),
	})
	if err != nil {
		log.Fatalf("failed InitiateMultipartUpload %v", err)
	}

	// 遍历每个分片,生成签名 URL 并上传分片
	for i := 0; i < partsNum; i++ {
		start := int64(i) * partSize
		end := start + partSize
		if end > length {
			end = length
		}
		signedResult, err := client.Presign(context.TODO(), &oss.UploadPartRequest{
			Bucket:     oss.Ptr(bucketName),
			Key:        oss.Ptr(objectName),
			PartNumber: int32(i + 1),
			Body:       bytes.NewReader(data[start:end]),
			UploadId:   initResult.UploadId,
		}, oss.PresignExpiration(time.Now().Add(1*time.Hour))) // 生成签名 URL,有效期为1小时
		if err != nil {
			log.Fatalf("failed to generate presigned URL %v", err)
		}
		fmt.Printf("signed url:%#v\n", signedResult.URL) // 打印生成的签名URL

		// 创建HTTP请求并上传分片
		req, err := http.NewRequest(signedResult.Method, signedResult.URL, bytes.NewReader(data[start:end]))
		if err != nil {
			log.Fatalf("failed to create HTTP request %v", err)
		}

		c := &http.Client{} // 创建HTTP客户端
		_, err = c.Do(req)
		if err != nil {
			log.Fatalf("failed to upload part by signed URL %v", err)
		}
	}

	// 列举已上传的分片
	partsResult, err := client.ListParts(context.TODO(), &oss.ListPartsRequest{
		Bucket:   oss.Ptr(bucketName),
		Key:      oss.Ptr(objectName),
		UploadId: initResult.UploadId,
	})
	if err != nil {
		log.Fatalf("failed to list parts %v", err)
	}

	// 收集已上传的分片信息
	var parts []oss.UploadPart
	for _, p := range partsResult.Parts {
		parts = append(parts, oss.UploadPart{PartNumber: p.PartNumber, ETag: p.ETag})
	}

	// 完成分片上传
	result, err := client.CompleteMultipartUpload(context.TODO(), &oss.CompleteMultipartUploadRequest{
		Bucket:   oss.Ptr(bucketName),
		Key:      oss.Ptr(objectName),
		UploadId: initResult.UploadId,
		CompleteMultipartUpload: &oss.CompleteMultipartUpload{
			Parts: parts,
		},
	})
	if err != nil {
		log.Fatalf("failed to complete multipart upload %v", err)
	}

	// 打印完成分片上传的结果
	log.Printf("complete multipart upload result:%#v\n", result)
}

相关文档

  • 关于预签名URL的完整示例代码,请参见GitHub示例。

  • 关于预签名URL的API接口,请参见Presign。

相关文章

简单上传 2025-04-21 18:15

本文介绍如何通过简单上传方法将本地文件快速上传到OSS,此方法操作简便,适合快速将本地文件上传到云端存储。 注意事项 本文示例代码以华东1(杭州)的地域IDcn-hangzhou为例,默认使用外网En

追加上传 2025-04-21 18:15

追加上传是指在已上传的追加类型文件(Appendable Object)末尾直接追加内容。本文介绍如何使用OSS Go SDK进行追加上传。 注意事项 本文示例代码以华东1(杭州)的地域IDcn-hangzhou<

分片上传 2025-04-21 18:15

OSS提供的分片上传(Multipart Upload)功能,将要上传的较大文件(Object)分成多个分片(Part)来分别上传,上传完成后再调用CompleteMultipartUpload接口将这些Part组合成一个Object。 注意事项 本文示例代码以华东1(杭州)的地域ID

表单上传 2025-04-21 18:15

OSS表单上传允许网页应用通过标准HTML表单直接将文件上传至OSS。本文介绍如何使用Go SDK V2生成Post签名和Post Policy等信息,并调用HTTP Post方法上传文件到OSS。 注意事项 本文示例代码以华东1(杭州)的地域ID

使用预签名URL上传 2025-04-21 18:15

默认情况下,OSS Bucket中的文件是私有的,仅文件拥有者可访问。本文介绍如何使用OSS Go SDK生成带有过期时间的PUT方法预签名URL,以允许他人临时上传文件。在有效期内可多次访问,超期后需重新生成。 注意事项 本文示例代码以华东1(杭州)的地域ID

文件上传管理器 2025-04-21 18:15

本文针对文件的传输场景,介绍如何使用Go SDK V2新增的上传管理器Uploader模块进行文件上传。 注意事项 本文示例代码以华东1(杭州)的地域ID

目录
Copyright © 2025 your company All Rights Reserved. Powered by 赛尔网络.
京ICP备14022346号-15
gongan beian 京公网安备11010802041014号