主要参考以下文档与代码

后端 STS 服务

接口提供临时密钥和 token,服务端代码如下:

package main

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/joho/godotenv"
	sts "github.com/tencentyun/qcloud-cos-sts-sdk/go"
)

const DefaultCredExpireTime = 600 // seconds

var (
	appId  string
	bucket string
	region string
	stsClient *sts.Client
	credOpts  *sts.CredentialOptions
	stsExpireTime int
)

type Credentials struct {
	TmpSecretId  string `json:"tmpSecretId"`
	TmpSecretKey string `json:"tmpSecretKey"`
	SessionToken string `json:"sessionToken"`
}

type CredentialsResp struct {
	Credentials Credentials `json:"credentials"`
	StartTime   int64       `json:"startTime"`
	ExpiredTime int64       `json:"expiredTime"`
}

func init() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}

	appId = os.Getenv("COS_APPID")
	bucket = os.Getenv("COS_BUCKET")
	region = os.Getenv("COS_REGION")
	secretId := os.Getenv("COS_SECRET_ID")
	secretKey := os.Getenv("COS_SECRET_KEY")
	stsExpireTime, err = strconv.Atoi(os.Getenv("STS_EXPIRE_TIME"))
	if err != nil {
		log.Printf("read enviroment `STS_EXPIRE_TIME` error: %v\n", err)
		log.Printf("set `STS_EXPIRE_TIME` to default %v", DefaultCredExpireTime)
		stsExpireTime = DefaultCredExpireTime
	}

	stsClient = sts.NewClient(
		secretId,
		secretKey,
		nil,
	)

	credOpts = &sts.CredentialOptions{
		DurationSeconds: int64(stsExpireTime),
		Region:          region,
		Policy: &sts.CredentialPolicy{
			Statement: []sts.CredentialPolicyStatement{
				{
					Action: []string{
						"name/cos:PostObject",
						"name/cos:PutObject",
						"name/cos:InitiateMultipartUpload",
						"name/cos:ListMultipartUploads",
						"name/cos:ListParts",
						"name/cos:UploadPart",
						"name/cos:CompleteMultipartUpload",
					},
					Effect: "allow",
					Resource: []string{
						"qcs::cos:ap-guangzhou:uid/" + appId + ":" + bucket + "/*",
					},
					Condition: map[string]map[string]interface{}{},
				},
			},
		},
	}
}

func getCredentialsHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
	w.Header().Set("Access-Control-Allow-Headers", "*")

	res, err := stsClient.GetCredential(credOpts)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	resp := CredentialsResp{
		Credentials: Credentials{
			TmpSecretId:  res.Credentials.TmpSecretID,
			TmpSecretKey: res.Credentials.TmpSecretKey,
			SessionToken: res.Credentials.SessionToken,
		},
		StartTime:   time.Now().Unix(),
		ExpiredTime: time.Now().Add(time.Duration(stsExpireTime) * time.Second).Unix(),
	}
	err = json.NewEncoder(w).Encode(resp)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

}

func main() {
	log.Println("RunStsServer starts...")
	http.HandleFunc("/credentials", getCredentialsHandler)
	http.ListenAndServe(":8080", nil)
}

通过调用接口就可以获得临时密钥

curl "http://127.0.0.1:8080/credentials"

因为要在 Web 调用,因此需要设置允许跨域请求

w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "*")

Web 直传 demo

<template>
  <div>
    <input type="file" @change="uploadImage" ref="imageInput" />
    <button @click="submitImage">Upload Image</button>
  </div>
</template>
  
<script>
import COS from 'cos-js-sdk-v5';

export default {
  data() {
    return {
      imageFile: null,
      imageUrl: null,
      cos: null,
    };
  },
  methods: {
    uploadImage(event) {
      this.imageFile = event.target.files && event.target.files[0];
    },
    submitImage() {
      if (!this.imageFile) {
        alert("Please choose an image to upload.");
        return;
      }
      this.initCOS();
      this.uploadToCOS();
    },
    initCOS() {
      this.cos = new COS({
        getAuthorization: function (options, callback) {
          const url = 'http://${host}:8080/credentials'
          const xhr = new XMLHttpRequest()
          let data = null
          let credentials = null
          xhr.open('GET', url, true)
          xhr.onload = function (e) {
            try {
              data = JSON.parse(e.target.responseText);
              credentials = data.credentials;
            } catch (e) {
              console.log(e)
            }
            if (!data || !credentials) {
              return console.error('credentials invalid:\n' + JSON.stringify(data, null, 2))
            }
            callback({
              TmpSecretId: credentials.tmpSecretId,
              TmpSecretKey: credentials.tmpSecretKey,
              SecurityToken: credentials.sessionToken,
              // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
              StartTime: data.startTime, // 时间戳,单位秒,如:1580000000
              ExpiredTime: data.expiredTime, // 时间戳,单位秒,如:1580000000
            })
          }
          xhr.send();
        }
      })
    },
    uploadToCOS() {
      const bucket = ${bucket};
      const region = ${region};
      const key = `images/${Date.now()}_${this.imageFile.name}`;

      this.cos.uploadFile(
        {
          Bucket: bucket,
          Region: region,
          Key: key,
          StorageClass: "STANDARD",
          Body: this.imageFile,
        },
        (err) => {
          if (err) {
            console.error("Error uploading image:", err);
            alert("Error uploading image.");
          } else {
            this.imageUrl = `https://${bucket}.cos.${region}.myqcloud.com/${key}`;
            console.log("Image uploaded successfully. URL:", this.imageUrl);
            alert("Image uploaded successfully.");
            this.$refs.imageInput.value = null;
            this.imageFile = null;
          }
        }
      );
    },
  },
};
</script>

点击对应按钮即可上传图片至 COS