All Products
Search
Document Center

Object Storage Service:Unggah file langsung ke OSS dari klien

Last Updated:May 28, 2026

Unggah langsung oleh klien memungkinkan Anda mengunggah file dari klien secara langsung ke Object Storage Service (OSS), melewati server aplikasi untuk meningkatkan kecepatan unggah dan menghemat sumber daya server.

Mengapa menggunakan unggah langsung oleh klien

Dalam arsitektur klien-server yang umum, klien mengunggah file ke server aplikasi, yang kemudian meneruskannya ke OSS. Pendekatan ini mentransmisikan data melalui jaringan dua kali, menghabiskan bandwidth yang tidak perlu dan meningkatkan beban server. Dengan unggah langsung oleh klien, klien terhubung langsung ke OSS sehingga menghindari overhead tersebut.

Cara mengimplementasikan unggah langsung oleh klien

Unggah langsung oleh klien memerlukan penanganan dua tantangan utama:

Akses lintas-origin

Aplikasi web dan mini program tunduk pada pembatasan lintas-origin dari browser dan kontainer mini program yang mencegah koneksi langsung ke OSS. Konfigurasikan Pengaturan lintas-origin pada bucket Anda untuk mengizinkan domain tertentu.

Otorisasi aman

Mengunggah ke OSS memerlukan otentikasi signature dengan pasangan AccessKey milik pengguna Resource Access Management (RAM), tetapi menyimpan kredensial jangka panjang di klien menimbulkan risiko keamanan. Gunakan salah satu solusi berikut:

  • Hasilkan kredensial akses sementara STS di server

    Disarankan untuk sebagian besar skenario. Gunakan SDK STS di server Anda untuk mendapatkan kredensial akses sementara, lalu kirimkan ke klien untuk unggah langsung menggunakan SDK OSS. Mendukung unggah multipart dan unggah yang dapat dilanjutkan. Simpan cache kredensial dan refresh sebelum kedaluwarsa untuk menghindari throttling STS. Tambahkan kebijakan akses untuk membatasi lebih lanjut izin kredensial.

  • Hasilkan signature dan Post Policy yang diperlukan untuk PostObject di server

    Paling cocok untuk unggah formulir HTML dengan pembatasan file. Hasilkan signature Post dan Post Policy di server Anda; klien mengunggah langsung tanpa SDK OSS. Gunakan Post Policy untuk membatasi ukuran file, jenis, dan atribut lainnya. Tidak mendukung unggah multipart atau unggah yang dapat dilanjutkan. PostObject.

  • Hasilkan URL yang ditandatangani yang diperlukan untuk PutObject di server

    Paling cocok untuk unggah file tunggal yang sederhana. Hasilkan URL yang ditandatangani di server Anda menggunakan SDK OSS; klien mengunggah langsung melalui PutObject. Tidak cocok untuk unggah multipart atau unggah yang dapat dilanjutkan — menghasilkan URL per bagian meningkatkan round trip ke server, dan klien dapat mengubah konten atau urutan bagian, menyebabkan kesalahan penggabungan. Signature V1.

Hasilkan kredensial akses sementara STS di server

Alur unggah menggunakan kredensial akses sementara STS:

  1. Klien meminta kredensial akses sementara dari server aplikasi.

  2. Server aplikasi memanggil operasi AssumeRole menggunakan SDK STS untuk mendapatkan kredensial akses sementara.

  3. STS mengembalikan kredensial akses sementara ke server aplikasi.

  4. Server aplikasi mengembalikan kredensial akses sementara ke klien.

  5. Klien menggunakan SDK OSS dan kredensial akses sementara untuk mengunggah file ke OSS.

  6. OSS mengembalikan tanggapan sukses ke klien.

Potongan kode contoh

Potongan kode inti ditampilkan di bawah ini. Kode lengkap: sts.zip.

Kode contoh sisi server

Hasilkan kredensial akses sementara di server:

Catatan

Mendukung penerapan satu klik ke Function Compute (FC).oss-upload-sts-app

Python

# Ganti <YOUR_ROLE_ARN> dengan ARN dari peran RAM yang memiliki izin untuk mengunggah file ke bucket OSS tertentu.
role_arn_for_oss_upload = '<YOUR_ROLE_ARN>'
# Setel <YOUR_REGION_ID> ke wilayah layanan STS, seperti cn-hangzhou.
region_id = '<YOUR_REGION_ID>'

def get_sts_token():
    # Saat Anda menginisialisasi CredentialClient tanpa menentukan parameter apa pun, rantai kredensial default digunakan.
    # Saat menjalankan program secara lokal, Anda dapat menentukan pasangan AccessKey menggunakan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_ID dan ALIBABA_CLOUD_ACCESS_KEY_SECRET.
    # Saat menjalankan program di instance ECS, instance ECI, atau di Container Service, Anda dapat menentukan peran RAM instans terlampir menggunakan variabel lingkungan ALIBABA_CLOUD_ECS_METADATA. SDK secara otomatis mengambil kredensial sementara STS.
    config = Config(region_id=region_id, credential=CredentialClient())
    sts_client = Sts20150401Client(config=config)
    assume_role_request = sts_20150401_models.AssumeRoleRequest(
        role_arn=role_arn_for_oss_upload,
        # Setel <YOUR_ROLE_SESSION_NAME> ke nama sesi khusus, seperti oss-role-session.
        role_session_name='<YOUR_ROLE_SESSION_NAME>'
    )
    response = sts_client.assume_role(assume_role_request)
    token = json.dumps(response.body.credentials.to_map())
    return token

Java

// Setel <YOUR_ROLE_SESSION_NAME> ke nama sesi khusus, seperti my-website-server.
            .setRoleSessionName("<YOUR_ROLE_SESSION_NAME>")
            // Ganti <YOUR_ROLE_ARN> dengan ARN dari peran RAM yang memiliki izin untuk mengunggah file ke bucket OSS tertentu. Anda dapat memperoleh ARN peran dari detail peran RAM.
            .setRoleArn("<YOUR_ROLE_ARN>");

Go

/**
 * Inisialisasi klien dengan pasangan AccessKey.
 * @param accessKeyId
 * @param accessKeySecret
 * @return Client
 * @throws Exception
 */
func CreateClient(accessKeyId *string, accessKeySecret *string) (*sts20150401.Client, error) {
    config := &openapi.Config{
    // Wajib diisi. ID AccessKey Anda.
    AccessKeyId: accessKeyId,
    // Wajib diisi. Rahasia AccessKey Anda.
    AccessKeySecret: accessKeySecret,
    }
    // Untuk informasi lebih lanjut tentang titik akhir, lihat https://api.aliyun.com/product/Sts.
    config.Endpoint = tea.String("sts.cn-hangzhou.aliyuncs.com")
    return sts20150401.NewClient(config)
}

func AssumeRole(client *sts20150401.Client) (*sts20150401.AssumeRoleResponse, error) {
    assumeRoleRequest := &sts20150401.AssumeRoleRequest{
    DurationSeconds: tea.Int64(3600),
    RoleArn:         tea.String("acs:ram::1379186349531844:role/admin-oss"),
    RoleSessionName: tea.String("peiyu-demo"),
    }
    return client.AssumeRoleWithOptions(assumeRoleRequest, &util.RuntimeOptions{})
}

PHP

<?php
require_once 'vendor/autoload.php';
use AlibabaCloud\Client\AlibabaCloud;
use AlibabaCloud\Client\Exception\ClientException;
use AlibabaCloud\Client\Exception\ServerException;
use AlibabaCloud\Sts\Sts;
// Inisialisasi klien Alibaba Cloud.
AlibabaCloud::accessKeyClient(getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'), getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'))
    ->regionId('cn-hangzhou')
    ->asDefaultClient();
// Buat permintaan STS.
$request = Sts::v20150401()->assumeRole();
// Mulai permintaan STS dan dapatkan hasilnya.
// Atur <YOUR_ROLE_SESSION_NAME> ke nama sesi kustom, seperti oss-role-session.
// Ganti <YOUR_ROLE_ARN> dengan ARN dari peran RAM yang memiliki izin untuk mengunggah file ke bucket OSS yang ditentukan.
$result = $request
    ->withRoleSessionName("<YOUR_ROLE_SESSION_NAME>")
    ->withDurationSeconds(3600)
    ->withRoleArn("<YOUR_ROLE_ARN>")
    ->request();
// Dapatkan informasi kredensial dari hasil permintaan STS.
$credentials = $result->get('Credentials');
// Buat data JSON untuk dikembalikan.
$response = [
    'AccessKeyId' => $credentials['AccessKeyId'],
    'AccessKeySecret' => $credentials['AccessKeySecret'],
    'SecurityToken' => $credentials['SecurityToken'],
];
// Atur header respons ke application/json.
header('Content-Type: application/json');
// Ubah hasil ke format JSON dan cetak.
echo json_encode(['Credentials' => $response]);
?>

Node.js

   // Setel roleArn ke ARN peran yang diperoleh di Langkah 2, misalnya, acs:ram::175708322470****:role/ramtest.
   // Setel policy ke kebijakan akses khusus untuk membatasi lebih lanjut izin kredensial akses sementara STS. Jika Anda tidak menentukan kebijakan, kredensial akses sementara STS yang dikembalikan memiliki semua izin dari peran yang ditentukan secara default.
   // 3000 adalah waktu kedaluwarsa dalam detik.
   // Gunakan sessionName untuk menentukan nama sesi peran khusus guna membedakan token yang berbeda, misalnya, sessiontest.
   sts.assumeRole('<YOUR_ROLE_ARN>', ``, '3000', 'sessiontest').then((result) => {

Ruby

require 'sinatra'
require 'base64'
require 'open-uri'
require 'cgi'
require 'openssl'
require 'json'
require 'sinatra/reloader'
require 'sinatra/content_for'
require 'aliyunsdkcore'

# Atur jalur folder publik ke folder templat di direktori saat ini.
set :public_folder, File.dirname(__FILE__) + '/templates'

def get_sts_token_for_oss_upload()
  client = RPCClient.new(
    # Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_ID.
    access_key_id: ENV['ALIBABA_CLOUD_ACCESS_KEY_ID'],
    # Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_SECRET.
    access_key_secret: ENV['ALIBABA_CLOUD_ACCESS_KEY_SECRET'],
    endpoint: 'https://sts.cn-hangzhou.aliyuncs.com',
    api_version: '2015-04-01'
  )
  response = client.request(
    action: 'AssumeRole',
    params: {
      # Atur RoleArn ke Nama Sumber Daya Alibaba Cloud dari peran yang diperoleh di Langkah 2, misalnya, acs:ram::175708322470****:role/ramtest.
      "RoleArn": "acs:ram::175708322470****:role/ramtest",
      # 3600 adalah waktu kedaluwarsa dalam detik.
      "DurationSeconds": 3600,
      # Gunakan RoleSessionName untuk menentukan nama sesi peran kustom untuk membedakan token yang berbeda, misalnya, sessiontest.
      "RoleSessionName": "sessiontest"
    },
    opts: {
      method: 'POST',
      format_params: true
    }
  )
end

if ARGV.length == 1 
  $server_port = ARGV[0]
elsif ARGV.length == 2
  $server_ip = ARGV[0]
  $server_port = ARGV[1]
end

$server_ip = "0.0.0.0"
$server_port = 8000

puts "App server is running on: http://#{$server_ip}:#{$server_port}"

set :bind, $server_ip
set :port, $server_port

get '/get_sts_token_for_oss_upload' do
  token = get_sts_token_for_oss_upload()
  response = {
    "AccessKeyId" => token["Credentials"]["AccessKeyId"],
    "AccessKeySecret" => token["Credentials"]["AccessKeySecret"],
    "SecurityToken" => token["Credentials"]["SecurityToken"]
  }
  response.to_json
end

get '/*' do
  puts "********************* GET "
  send_file File.join(settings.public_folder, 'index.html')
end

C#

                var assumeRoleRequest = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest();
                // Setel <YOUR_ROLE_SESSION_NAME> ke nama sesi khusus, seperti oss-role-session.
                assumeRoleRequest.RoleSessionName = "<YOUR_ROLE_SESSION_NAME>";
                // Ganti <YOUR_ROLE_ARN> dengan ARN dari peran RAM yang memiliki izin untuk mengunggah file ke bucket OSS tertentu.
                assumeRoleRequest.RoleArn = "<YOUR_ROLE_ARN>";

Kode contoh sisi klien

Klien web mengunggah file menggunakan kredensial akses sementara:

let credentials = null;
const form = document.querySelector("form");
form.addEventListener("submit", async (event) => {
  event.preventDefault();
  // Untuk mengurangi panggilan ke layanan STS, ambil kembali kredensial hanya saat kredensial yang ada saat ini kedaluwarsa.
  if (isCredentialsExpired(credentials)) {
    const response = await fetch("/get_sts_token_for_oss_upload", {
      method: "GET",
    });
    if (!response.ok) {
      // Menangani kode status HTTP error.
      throw new Error(
        `Failed to obtain STS token: ${response.status} ${response.statusText}`
      );
    }
    credentials = await response.json();
  }
  const client = new OSS({
    // Atur <YOUR_BUCKET> ke nama bucket OSS Anda.
    bucket: "<YOUR_BUCKET>",
    // Atur <YOUR_REGION> ke wilayah tempat bucket OSS berada, misalnya, region: 'oss-cn-hangzhou'.
    region: "oss-<YOUR_REGION>",
    accessKeyId: credentials.AccessKeyId,
    accessKeySecret: credentials.AccessKeySecret,
    stsToken: credentials.SecurityToken,
  });

  const fileInput = document.querySelector("#file");
  const file = fileInput.files[0];
  const result = await client.put(file.name, file);
  console.log(result);
});

/**
 * Memeriksa apakah kredensial sementara telah kedaluwarsa.
 **/
function isCredentialsExpired(credentials) {
  if (!credentials) {
    return true;
  }
  const expireDate = new Date(credentials.Expiration);
  const now = new Date();
  // Jika periode validitas kurang dari satu menit, kredensial dianggap kedaluwarsa.
  return expireDate.getTime() - now.getTime() <= 60000;
}

Hasilkan signature dan Post Policy yang diperlukan untuk PostObject di server

Alur unggah menggunakan signature Post dan Post Policy:

  1. Klien meminta signature Post, Post Policy, dan informasi lainnya dari server aplikasi.

  2. Server aplikasi menghasilkan signature Post, Post Policy, dan informasi terkait lalu mengembalikannya ke klien.

  3. Klien memanggil PostObject dengan signature Post dan Post Policy untuk mengunggah file ke OSS melalui formulir HTML.

  4. OSS mengembalikan tanggapan sukses ke klien.

Potongan kode contoh

Potongan kode inti ditampilkan di bawah ini. Kode lengkap: postsignature.zip.

Kode contoh sisi server

Hasilkan signature Post dan Post Policy di server:

Catatan

Mendukung penerapan satu klik ke Function Compute (FC).oss-upload-post-signature-app

Python

import os
from hashlib import sha1 as sha
import json
import base64
import hmac
import datetime
import time

# Konfigurasikan variabel lingkungan OSS_ACCESS_KEY_ID.
access_key_id = os.environ.get('OSS_ACCESS_KEY_ID')
# Konfigurasikan variabel lingkungan OSS_ACCESS_KEY_SECRET.
access_key_secret = os.environ.get('OSS_ACCESS_KEY_SECRET')
# Ganti <YOUR_BUCKET> dengan nama bucket Anda.
bucket = '<YOUR_BUCKET>'
# Format host adalah bucketname.endpoint. Ganti <YOUR_BUCKET> dengan nama bucket Anda. Ganti <YOUR_ENDPOINT> dengan titik akhir OSS, seperti oss-cn-hangzhou.aliyuncs.com.
host = 'https://<YOUR_BUCKET>.<YOUR_ENDPOINT>'
# Tentukan awalan untuk file yang akan diunggah ke OSS.
upload_dir = 'user-dir-prefix/'
# Tentukan waktu kedaluwarsa dalam detik.
expire_time = 3600


def generate_expiration(seconds):
    """
    Buat waktu kedaluwarsa dengan menentukan periode validitas dalam detik.
    :param seconds: Periode validitas dalam detik.
    :return: String waktu ISO 8601, seperti "2014-12-01T12:00:00.000Z".
    """
    now = int(time.time())
    expiration_time = now + seconds
    gmt = datetime.datetime.utcfromtimestamp(expiration_time).isoformat()
    gmt += 'Z'
    return gmt


def generate_signature(access_key_secret, expiration, conditions, policy_extra_props=None):
    """
    Buat string tanda tangan.
    :param access_key_secret: Rahasia AccessKey dari akun yang memiliki izin untuk mengakses bucket tujuan.
    :param expiration: Waktu kedaluwarsa tanda tangan, dalam format ISO 8601 dan UTC. Contoh: "2014-12-01T12:00:00.000Z".
    :param conditions: Kondisi Kebijakan yang digunakan untuk membatasi nilai yang diizinkan saat mengunggah formulir.
    :param policy_extra_props: Parameter kebijakan tambahan. Jika parameter baru ditambahkan ke kebijakan, Anda dapat meneruskannya sebagai kamus.
    :return: signature, string tanda tangan.
    """
    policy_dict = {
        'expiration': expiration,
        'conditions': conditions
    }
    if policy_extra_props is not None:
        policy_dict.update(policy_extra_props)
    policy = json.dumps(policy_dict).strip()
    policy_encode = base64.b64encode(policy.encode())
    h = hmac.new(access_key_secret.encode(), policy_encode, sha)
    sign_result = base64.b64encode(h.digest()).strip()
    return sign_result.decode()

def generate_upload_params():
    policy = {
        # Periode validitas.
        "expiration": generate_expiration(expire_time),
        # Batasan.
        "conditions": [
            # Kode status yang akan dikembalikan setelah unggahan berhasil jika success_action_redirect tidak ditentukan. Nilai defaultnya adalah 204.
            ["eq", "$success_action_status", "200"],
            # Nilai bidang formulir harus dimulai dengan awalan yang ditentukan. Misalnya, untuk menentukan bahwa nilai kunci harus dimulai dengan user/user1, Anda dapat menulis ["starts-with", "$key", "user/user1"].
            ["starts-with", "$key", upload_dir],
            # Batasi ukuran minimum dan maksimum yang diizinkan dari objek yang diunggah dalam byte.
            ["content-length-range", 1, 1000000],
            # Batasi file yang diunggah ke jenis gambar yang ditentukan.
            ["in", "$content-type", ["image/jpg", "image/png"]]
        ]
    }
    signature = generate_signature(access_key_secret, policy.get('expiration'), policy.get('conditions'))
    response = {
        'policy': base64.b64encode(json.dumps(policy).encode('utf-8')).decode(),
        'ossAccessKeyId': access_key_id,
        'signature': signature,
        'host': host,
        'dir': upload_dir
        # Anda dapat menambahkan parameter lain di sini.
    }
    return json.dumps(response)

Java

package com.aliyun.sample;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.codehaus.jettison.json.JSONObject;
import java.util.Date;

@Controller
public class PostSignatureController {
    @Autowired
    private OSS ossClient;

    @Autowired
    private OssConfig ossConfig;

    @GetMapping("/get_post_signature_for_oss_upload")
    @ResponseBody
    public String generatePostSignature() {
        JSONObject response = new JSONObject();
        try {
            long expireEndTime = System.currentTimeMillis() + ossConfig.getExpireTime() * 1000;
            Date expiration = new Date(expireEndTime);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, ossConfig.getDir());
            String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);
            response.put("ossAccessKeyId", ossConfig.getAccessKeyId());
            response.put("policy", encodedPolicy);
            response.put("signature", postSignature);
            response.put("dir", ossConfig.getDir());
            response.put("host", ossConfig.getHost());
        } catch (OSSException oe) {
            System.out.println("Menangkap OSSException, yang berarti permintaan Anda berhasil sampai ke OSS, "
                    + "tetapi ditolak dengan tanggapan error karena alasan tertentu.");
            System.out.println("Kode Status HTTP: " + oe.getRawResponseError());
            System.out.println("Pesan Error: " + oe.getErrorMessage());
            System.out.println("Kode Error:       " + oe.getErrorCode());
            System.out.println("ID Permintaan:      " + oe.getRequestId());
            System.out.println("ID Host:           " + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Menangkap ClientException, yang berarti klien mengalami "
                    + "masalah internal yang serius saat mencoba berkomunikasi dengan OSS, "
                    + "seperti tidak dapat mengakses jaringan.");
            System.out.println("Pesan Error: " + ce.getMessage());
        } catch (Exception e) {
            System.out.println("Menangkap pengecualian tak terduga: " + e.getMessage());
        }
        return response.toString();
    }
}

Go

package main

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

var (
	// Konfigurasikan variabel lingkungan OSS_ACCESS_KEY_ID.
	accessKeyId = os.Getenv("OSS_ACCESS_KEY_ID")
	// Konfigurasikan variabel lingkungan OSS_ACCESS_KEY_SECRET.
	accessKeySecret = os.Getenv("OSS_ACCESS_KEY_SECRET")
	// Format host adalah bucketname.endpoint. Ganti ${your-bucket} dengan nama bucket Anda. Ganti ${your-endpoint} dengan titik akhir OSS, seperti oss-cn-hangzhou.aliyuncs.com.
	host = "http://${your-bucket}.${your-endpoint}"
	// Tentukan awalan untuk file yang akan diunggah ke OSS.
	uploadDir = "user-dir-prefix/"
	// Tentukan waktu kedaluwarsa dalam detik.
	expireTime = int64(3600)
)

type ConfigStruct struct {
	Expiration string          `json:"expiration"`
	Conditions [][]interface{} `json:"conditions"`
}

type PolicyToken struct {
	AccessKeyId string `json:"ossAccessKeyId"`
	Host        string `json:"host"`
	Signature   string `json:"signature"`
	Policy      string `json:"policy"`
	Directory   string `json:"dir"`
}

func getGMTISO8601(expireEnd int64) string {
	return time.Unix(expireEnd, 0).UTC().Format("2006-01-02T15:04:05Z")
}

func getPolicyToken() string {
	now := time.Now().Unix()
	expireEnd := now + expireTime
	tokenExpire := getGMTISO8601(expireEnd)

	var config ConfigStruct
	config.Expiration = tokenExpire

	// Tambahkan pembatasan awalan file.
	config.Conditions = append(config.Conditions, []interface{}{"starts-with", "$key", uploadDir})

	// Tambahkan pembatasan ukuran file, misalnya, dari 1 KB hingga 10 MB.
	minSize := int64(1024)
	maxSize := int64(10 * 1024 * 1024)
	config.Conditions = append(config.Conditions, []interface{}{"content-length-range", minSize, maxSize})

	result, err := json.Marshal(config)
	if err != nil {
		fmt.Println("callback json err:", err)
		return ""
	}

	encodedResult := base64.StdEncoding.EncodeToString(result)
	h := hmac.New(sha1.New, []byte(accessKeySecret))
	io.WriteString(h, encodedResult)
	signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))

	policyToken := PolicyToken{
		AccessKeyId: accessKeyId,
		Host:        host,
		Signature:   signedStr,
		Policy:      encodedResult,
		Directory:   uploadDir,
	}

	response, err := json.Marshal(policyToken)
	if err != nil {
		fmt.Println("json err:", err)
		return ""
	}

	return string(response)
}

func handler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		http.ServeFile(w, r, "templates/index.html")
		return
	} else if r.URL.Path == "/get_post_signature_for_oss_upload" {
		policyToken := getPolicyToken()
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(policyToken))
		return
	}
	http.NotFound(w, r)
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}

PHP

<?php
function gmt_iso8601($time)
{
    return str_replace('+00:00', '.000Z', gmdate('c', $time));
}

// Dapatkan kredensial akses dari variabel lingkungan. Sebelum Anda menjalankan contoh kode ini, pastikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_ID dan ALIBABA_CLOUD_ACCESS_KEY_SECRET telah dikonfigurasi.
$accessKeyId = getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
$accessKeySecret = getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
// Format $host adalah '<YOUR-BUCKET>.<YOUR-ENDPOINT>'. Ganti variabel dengan informasi Anda yang sebenarnya.
$host = 'http://<YOUR-BUCKET>.<YOUR-ENDPOINT>';
// Awalan yang ditentukan oleh pengguna saat mengunggah file.
$dir = 'user-dir-prefix/';          

$now = time();
// Atur kebijakan agar kedaluwarsa dalam 10 detik. Setelah periode ini, kebijakan menjadi tidak valid.
$expire = 30;  
$end = $now + $expire;
$expiration = gmt_iso8601($end);

// Ukuran file maksimum. Anda dapat mengaturnya sendiri.
$condition = array(0 => 'content-length-range', 1 => 0, 2 => 1048576000);
$conditions[] = $condition;

// Data yang diunggah harus diawali dengan $dir, jika tidak, unggahan akan gagal. Langkah ini tidak wajib tetapi direkomendasikan untuk keamanan guna mencegah pengguna mengunggah ke direktori lain melalui kebijakan.
$start = array(0 => 'starts-with', 1 => '$key', 2 => $dir);
$conditions[] = $start;


$arr = array('expiration' => $expiration, 'conditions' => $conditions);
$policy = json_encode($arr);
$base64_policy = base64_encode($policy);
$string_to_sign = $base64_policy;
$signature = base64_encode(hash_hmac('sha1', $string_to_sign, $accessKeySecret, true));

$response = array();
$response['ossAccessKeyId'] = $accessKeyId;
$response['host'] = $host;
$response['policy'] = $base64_policy;
$response['signature'] = $signature;
$response['dir'] = $dir;  
echo json_encode($response);

Node.js

const express = require("express");
const { Buffer } = require("buffer");
const OSS = require("ali-oss");
const app = express();
const path = require("path");
const config = {
  // Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_ID.
  accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
  // Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_SECRET.
  accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
  // Ganti <YOUR-BUCKET> dengan nama bucket Anda.
  bucket: "<YOUR-BUCKET>",
  // Tentukan awalan untuk file yang akan diunggah ke OSS.
  dir: "prefix/",
};

app.use(express.static(path.join(__dirname, "templates")));

app.get("/get_post_signature_for_oss_upload", async (req, res) => {
  const client = new OSS(config);
  const date = new Date();
  // Atur periode validitas tanda tangan dalam detik.
  date.setSeconds(date.getSeconds() + 3600);
  const policy = {
    expiration: date.toISOString(),
    conditions: [
      // Atur batas ukuran untuk file yang diunggah.
      ["content-length-range", 0, 1048576000],
      // Batasi bucket tempat file dapat diunggah.
      { bucket: client.options.bucket },
    ],
  };
  const formData = await client.calculatePostSignature(policy);
  const host = `http://${config.bucket}.${
    (await client.getBucketLocation()).location
  }.aliyuncs.com`.toString();
  const params = {
    policy: formData.policy,
    signature: formData.Signature,
    ossAccessKeyId: formData.OSSAccessKeyId,
    host,
    dir: config.dir,
  };
  res.json(params);
});

app.get(/^(.+)*\.(html|js)$/i, async (req, res) => {
  res.sendFile(path.join(__dirname, "./templates", req.originalUrl));
});

app.listen(8000, () => {
  console.log("http://127.0.0.1:8000");
});

Ruby

require 'sinatra'
require 'base64'
require 'open-uri'
require 'cgi'
require 'openssl'
require 'json'
require 'sinatra/reloader'
require 'sinatra/content_for'

# Atur jalur folder publik ke folder templat di direktori saat ini.
set :public_folder, File.dirname(__FILE__) + '/templates'

# Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_ID.
$access_key_id = ENV['ALIBABA_CLOUD_ACCESS_ID']
# Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_SECRET.
$access_key_secret = ENV['ALIBABA_CLOUD_ACCESS_SECRET']

# Format $host adalah <bucketname>.<endpoint>. Ganti <bucketname> dan <endpoint> dengan informasi Anda yang sebenarnya.
$host = 'http://<bucketname>.<endpoint>';

# Awalan untuk file yang diunggah pengguna.
$upload_dir = 'user-dir-prefix/'
# Waktu kedaluwarsa dalam detik.
$expire_time = 30
$server_ip = "0.0.0.0"
$server_port = 8000

if ARGV.length == 1 
  $server_port = ARGV[0]
elsif ARGV.length == 2
  $server_ip = ARGV[0]
  $server_port = ARGV[1]
end

puts "App server is running on: http://#{$server_ip}:#{$server_port}"

def hash_to_jason(source_hash)
  jason_string = source_hash.to_json;    

  jason_string.gsub!("\":[", "\": [")
  jason_string.gsub!("\",\"", "\", \"")
  jason_string.gsub!("],\"", "], \"")
  jason_string.gsub!("\":\"", "\": \"")

  jason_string
end


def get_token()
  expire_syncpoint = Time.now.to_i + $expire_time
  expire = Time.at(expire_syncpoint).utc.iso8601()
  response.headers['expire'] = expire
  policy_dict = {}
  condition_arrary = Array.new
  array_item = Array.new
  array_item.push('starts-with')
  array_item.push('$key')
  array_item.push($upload_dir)
  condition_arrary.push(array_item)
  policy_dict["conditions"] = condition_arrary
  policy_dict["expiration"] = expire
  policy = hash_to_jason(policy_dict)
  policy_encode = Base64.strict_encode64(policy).chomp;
  h = OpenSSL::HMAC.digest('sha1', $access_key_secret, policy_encode)
  hs = Digest::MD5.hexdigest(h)
  sign_result = Base64.strict_encode64(h).strip()
  token_dict = {}
  token_dict['ossAccessKeyId'] = $access_key_id
  token_dict['host'] = $host
  token_dict['policy'] = policy_encode
  token_dict['signature'] = sign_result 
  token_dict['expire'] = expire_syncpoint
  token_dict['dir'] = $upload_dir
  result = hash_to_jason(token_dict)
  result
end

set :bind, $server_ip
set :port, $server_port

get '/get_post_signature_for_oss_upload' do
  token = get_token()
  puts "Token: #{token}"
  token
end

get '/*' do
  puts "********************* GET "
  send_file File.join(settings.public_folder, 'index.html')
end

end

if ARGV.length == 1 
  $server_port = ARGV[0]
elsif ARGV.length == 2
  $server_ip = ARGV[0]
  $server_port = ARGV[1]
end

$server_ip = "0.0.0.0"
$server_port = 8000

puts "App server is running on: http://#{$server_ip}:#{$server_port}"

set :bind, $server_ip
set :port, $server_port

get '/get_sts_token_for_oss_upload' do
  token = get_sts_token_for_oss_upload()
  response = {
    "AccessKeyId" => token["Credentials"]["AccessKeyId"],
    "AccessKeySecret" => token["Credentials"]["AccessKeySecret"],
    "SecurityToken" => token["Credentials"]["SecurityToken"]
  }
  response.to_json
end

get '/*' do
  puts "********************* GET "
  send_file File.join(settings.public_folder, 'index.html')
end

C#

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;
using System.IO;
using System.Collections.Generic;
using System;
using System.Globalization;
using System.Text;
using System.Security.Cryptography;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace YourNamespace
{
    public class Program
    {
        private ILogger<Program> _logger;
        // Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_ID.
        public string AccessKeyId { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID");
        // Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_SECRET.
        public string AccessKeySecret { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
        // Format Host adalah bucketname.endpoint. Ganti <YOUR-BUCKET> dengan nama bucket Anda. Ganti <YOUR-ENDPOINT> dengan titik akhir OSS, seperti oss-cn-hangzhou.aliyuncs.com.
        public string Host { get; set; } = "<YOUR-BUCKET>.<YOUR-ENDPOINT>";
        // Tentukan awalan untuk file yang akan diunggah ke OSS.
        public string UploadDir { get; set; } = "user-dir-prefix/";
        // Tentukan waktu kedaluwarsa dalam detik.
        public int ExpireTime { get; set; } = 3600;
        public class PolicyConfig
        {
            public string expiration { get; set; }
            public List<List<object>> conditions { get; set; }
        }
        public class PolicyToken
        {
            public string Accessid { get; set; }
            public string Policy { get; set; }
            public string Signature { get; set; }
            public string Dir { get; set; }
            public string Host { get; set; }
            public string Expire { get; set; }
        }
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            var app = builder.Build();

            builder.Logging.AddConsole();
            var logger = builder.Services.BuildServiceProvider().GetRequiredService<ILogger<Program>>();

            app.UseStaticFiles(); 

            app.MapGet("/", async (context) =>
            {
                var filePath = Path.Combine(Directory.GetCurrentDirectory(), "templates/index.html");
                var htmlContent = await File.ReadAllTextAsync(filePath);
                await context.Response.WriteAsync(htmlContent);
                logger.LogInformation("GET request to root path");
            });

            app.MapGet("/get_post_signature_for_oss_upload", async (context) =>
            {
                var program = new Program(logger);
                var token = program.GetPolicyToken();

                logger.LogInformation($"Token: {token}");

                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(token);
            });

            app.Run();
        }

        public Program(ILogger<Program> logger)
        {
            _logger = logger;
        }

        private string ToUnixTime(DateTime dateTime)
        {
            return ((DateTimeOffset)dateTime).ToUnixTimeSeconds().ToString();
        }

        private string GetPolicyToken()
        {
            var expireDateTime = DateTime.Now.AddSeconds(ExpireTime);
            var config = new PolicyConfig
            {
                expiration = FormatIso8601Date(expireDateTime),
                conditions = new List<List<object>>()
            };
            config.conditions.Add(new List<object>
            {
                "content-length-range", 0, 1048576000
            });
            var policy = JsonConvert.SerializeObject(config);
            var policyBase64 = EncodeBase64("utf-8", policy);
            var signature = ComputeSignature(AccessKeySecret, policyBase64);
            var policyToken = new PolicyToken
            {
                Accessid = AccessKeyId,
                Host = Host,
                Policy = policyBase64,
                Signature = signature,
                Expire = ToUnixTime(expireDateTime),
                Dir = UploadDir
            };
            return JsonConvert.SerializeObject(policyToken);
        }

        private string FormatIso8601Date(DateTime dtime)
        {
            return dtime.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'",
                                    CultureInfo.CurrentCulture);
        }

        private string EncodeBase64(string codeType, string code)
        {
            string encode = "";
            byte[] bytes = Encoding.GetEncoding(codeType).GetBytes(code);
            try
            {
                encode = Convert.ToBase64String(bytes);
            }
            catch
            {
                encode = code;
            }
            return encode;
        }

        private string ComputeSignature(string key, string data)
        {
            using (var algorithm = new HMACSHA1(Encoding.UTF8.GetBytes(key)))
            {
                return Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(data)));
            }
        }
    }
}

Kode contoh sisi klien

Klien web mengunggah file menggunakan signature Post dan Post Policy:

const form = document.querySelector("form");
const fileInput = document.querySelector("#file");
form.addEventListener("submit", (event) => {
  event.preventDefault();
  const file = fileInput.files[0];
  if (!fileInput.files[0]) {
    alert('Silakan pilih file untuk diunggah.');
    return;
  }
  const filename = fileInput.files[0].name;
  fetch("/get_post_signature_for_oss_upload", { method: "GET" })
    .then((response) => {
      if (!response.ok) {
        throw new Error("Gagal mendapatkan tanda tangan.");
      }
      return response.json();
    })
    .then((data) => {
      const formData = new FormData();
      formData.append("name", filename);
      formData.append("policy", data.policy);
      formData.append("OSSAccessKeyId", data.ossAccessKeyId);
      formData.append("success_action_status", "200");
      formData.append("signature", data.signature);
      formData.append("key", data.dir + filename);
      formData.append("file", file);

      return fetch(data.host, { method: "POST", body: formData });
    })
    .then((response) => {
      if (response.ok) {
        console.log("Unggahan berhasil");
        alert("File berhasil diunggah.");
      } else {
        console.log("Unggahan gagal", response);
        alert("Unggahan gagal. Silakan coba lagi nanti.");
      }
    })
    .catch((error) => {
      console.error("Terjadi kesalahan:", error);
    });
});

Hasilkan URL yang ditandatangani yang diperlukan untuk PutObject di server

Alur unggah menggunakan URL yang ditandatangani:

  1. Klien meminta URL yang ditandatangani dari server aplikasi.

  2. Server aplikasi menghasilkan URL yang ditandatangani PUT menggunakan SDK OSS dan mengembalikannya ke klien.

  3. Klien memanggil PutObject dengan URL yang ditandatangani untuk mengunggah file ke OSS.

  4. OSS mengembalikan tanggapan sukses ke klien.

Potongan kode contoh

Potongan kode inti ditampilkan di bawah ini. Kode lengkap: presignedurl.zip.

Kode contoh sisi server

Hasilkan URL yang ditandatangani di server:

Catatan

Mendukung penerapan satu klik ke Function Compute (FC).oss-upload-presigned-url-app

Python

import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider

# Dapatkan kredensial akses dari variabel lingkungan. Sebelum Anda menjalankan contoh kode ini, pastikan variabel lingkungan OSS_ACCESS_KEY_ID dan OSS_ACCESS_KEY_SECRET telah dikonfigurasi.
auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
# Ganti <YOUR_ENDPOINT> dengan titik akhir bucket Anda.
# Misalnya, jika bucket Anda berada di wilayah China (Hangzhou),
# atur titik akhir ke https://oss-cn-hangzhou.aliyuncs.com.
# Ganti <YOUR_BUCKET> dengan nama bucket Anda.
bucket = oss2.Bucket(auth, '<YOUR_ENDPOINT>', '<YOUR_BUCKET>')
# Tentukan waktu kedaluwarsa dalam detik.
expire_time = 3600
# Tentukan jalur lengkap objek, misalnya, exampledir/exampleobject.png. Jalur lengkap tidak boleh berisi nama bucket.
object_name = 'exampledir/exampleobject.png'

def generate_presigned_url():
    # Tentukan header.
    headers = dict()
    # Tentukan Content-Type.
    headers['Content-Type'] = 'image/png'
    # Tentukan kelas penyimpanan.
    # headers["x-oss-storage-class"] = "Standard"
    # Secara default, saat membuat URL yang ditandatangani, OSS akan meng-escape garis miring (/) di jalur lengkap objek. Hal ini membuat URL yang ditandatangani yang dihasilkan tidak dapat digunakan.
    # Atur slash_safe ke True. OSS tidak akan meng-escape garis miring (/) di jalur lengkap objek. URL yang ditandatangani yang dihasilkan kemudian dapat langsung digunakan.
    url = bucket.sign_url('PUT', object_name, expire_time, slash_safe=True, headers=headers)
    return url

Java

    /**
     * Ganti <your-endpoint> dengan titik akhir OSS, misalnya, oss-cn-hangzhou.aliyuncs.com.
     */
    private static final String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";

    /**
     * Ganti <your-bucket> dengan nama bucket Anda.
     * Tentukan awalan untuk file yang diunggah ke OSS.
     * Ganti <your-object> dengan path lengkap objek, misalnya, exampleobject.txt. Path lengkap tidak boleh berisi nama bucket.
     * Tentukan waktu kedaluwarsa dalam milidetik.
     */
    private static final String BUCKET_NAME = "<your-bucket>";
    private static final String OBJECT_NAME = "<your-object>";
    private static final long EXPIRE_TIME = 3600 * 1000L;

Go

	// Setel yourEndpoint ke titik akhir bucket Anda. Misalnya, jika bucket Anda berada di wilayah Tiongkok (Hangzhou), setel titik akhir ke https://oss-cn-hangzhou.aliyuncs.com. Untuk wilayah lain, setel titik akhir sesuai.
	endpoint := "https://oss-cn-beijing.aliyuncs.com"
	// Tentukan nama bucket, misalnya, examplebucket.
	bucketName := "examplebucket"
	// Tentukan path lengkap file, misalnya, exampledir/exampleobject.txt. Path lengkap tidak boleh berisi nama bucket.
	objectName := "exampledir/exampleobject.txt"

PHP

// Setel yourEndpoint ke titik akhir wilayah tempat bucket Anda berada. Misalnya, jika bucket Anda berada di wilayah Tiongkok (Hangzhou), setel titik akhir ke https://oss-cn-hangzhou.aliyuncs.com.
$endpoint = "<YOUR-ENDPOINT>";
// Tentukan nama bucket.
$bucket= "<YOUR-BUCKET>";
// Tentukan path lengkap objek, tidak termasuk nama bucket.
$object = "test.png";
// Setel periode validitas URL yang ditandatangani menjadi 3600 detik.
$timeout = 3600;

Node.js

const express = require("express");
const { Buffer } = require("buffer");
const OSS = require("ali-oss");
const app = express();
const path = require("path");
const fs = require("fs");
const axios = require("axios");

const config = {
  // Atur <YOURREGION> ke wilayah tempat bucket Anda berada. Misalnya, jika bucket Anda berada di wilayah China (Hangzhou), atur wilayah ke oss-cn-hangzhou.
  region: '<YOURREGION>',
  // Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_ID.
  accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
  // Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_SECRET.
  accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
  // Ganti <YOUR-BUCKET> dengan nama bucket Anda.
  bucket: "<YOUR-BUCKET>",
}
const object = "examplefile.png";

app.use(express.static(path.join(__dirname, "templates")));

app.get("/get_presigned_url_for_oss_upload", async (req, res) => {
    const client = new OSS(config);
    const url = client.signatureUrl(object, {
        method: "PUT",
        "Content-Type": "application/x-www-form-urlencoded",
    });
    res.send(url); 
    console.log(url);
  });

app.listen(8000, () => {
  console.log("http://127.0.0.1:8000");
});

Ruby

require 'sinatra'
require 'base64'
require 'open-uri'
require 'cgi'
require 'openssl'
require 'json'
require 'sinatra/reloader'
require 'sinatra/content_for'
require 'aliyun/oss'
include Aliyun::OSS

# Atur jalur folder publik ke folder templat di direktori saat ini.
set :public_folder, File.dirname(__FILE__) + '/templates'

# Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_ID.
$access_key_id = ENV['ALIBABA_CLOUD_ACCESS_KEY_ID']
# Konfigurasikan variabel lingkungan ALIBABA_CLOUD_ACCESS_KEY_SECRET.
$access_key_secret = ENV['ALIBABA_CLOUD_ACCESS_KEY_SECRET']

# Tentukan jalur lengkap objek, misalnya, exampledir/exampleobject.png. Jalur lengkap tidak boleh berisi nama bucket.
object_key = 'exampledir/exampleobject.png'

def get_presigned_url(client, object_key)
  # Ganti <YOUR-BUCKET> dengan nama bucket Anda.
  bucket = client.get_bucket('<YOUR-BUCKET>')
  # Buat URL yang ditandatangani dan tentukan bahwa URL tersebut berlaku selama 1 jam (3600 detik).
  bucket.object_url(object_key, 3600)
end

client = Aliyun::OSS::Client.new(
  # Ganti <YOUR-ENDPOINT> dengan titik akhir wilayah tempat bucket Anda berada. Misalnya, jika bucket Anda berada di wilayah Tiongkok (Hangzhou), atur titik akhir ke https://oss-cn-hangzhou.aliyuncs.com.
  endpoint: '<YOUR-ENDPOINT>',
  # Dapatkan kredensial akses dari variabel lingkungan. Sebelum Anda menjalankan kode sampel ini, pastikan variabel lingkungan OSS_ACCESS_KEY_ID dan OSS_ACCESS_KEY_SECRET telah dikonfigurasi.
  access_key_id: $access_key_id,
  access_key_secret: $access_key_secret
)


if ARGV.length == 1 
  $server_port = ARGV[0]
elsif ARGV.length == 2
  $server_ip = ARGV[0]
  $server_port = ARGV[1]
end

$server_ip = "0.0.0.0"
$server_port = 8000

puts "App server is running on: http://#{$server_ip}:#{$server_port}"

set :bind, $server_ip
set :port, $server_port

get '/get_presigned_url_for_oss_upload' do
  url = get_presigned_url(client, object_key.to_s)
  puts "Token: #{url}"
  url
end

get '/*' do
  puts "********************* GET "
  send_file File.join(settings.public_folder, 'index.html')
end

C#

        // Ganti <YOUR-ENDPOINT> dengan titik akhir wilayah tempat bucket Anda berada. Misalnya, jika bucket Anda berada di wilayah Tiongkok (Hangzhou), setel titik akhir ke https://oss-cn-hangzhou.aliyuncs.com.
        private string EndPoint { get; set; } = "<YOUR-ENDPOINT>";
        // Ganti <YOUR-BUCKET> dengan nama bucket Anda.
        private string BucketName { get; set; } = "<YOUR-BUCKET>";
        private string ObjectName { get; set; } = "exampledir/exampleobject2.png";

Kode contoh sisi klien

Klien web mengunggah file menggunakan URL yang ditandatangani:

const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
  event.preventDefault();
  const fileInput = document.querySelector("#file");
  const file = fileInput.files[0];
  fetch("/get_presigned_url_for_oss_upload", { method: "GET" })
    .then((response) => {
      if (!response.ok) {
        throw new Error("Gagal mendapatkan URL yang telah ditandatangani.");
      }
      return response.text();
    })
    .then((url) => {
      fetch(url, {
        method: "PUT",
        headers: new Headers({
          "Content-Type": "image/png",
        }),
        body: file,
      }).then((response) => {
        if (!response.ok) {
          throw new Error("Gagal mengunggah file ke OSS.");
        }
        console.log(response);
        alert("File berhasil diunggah.");
      });
    })
    .catch((error) => {
      console.error("Terjadi kesalahan:", error);
      alert(error.message);
    });
});

Panduan praktik

Panduan praktik unggah berdasarkan jenis klien: