tencent cloud

云点播

动态与公告
产品动态
公告
产品简介
产品概述
产品特性
产品功能
产品优势
应用场景
解决方案
专业版
云点播专业版简介
快速入门
控制台指南
开发指南
购买指南
计费概述
计费方式
购买指引
账单查询
续费说明
欠费说明
退费说明
快速入门
控制台指南
控制台介绍
服务概览
应用管理
媒体管理
资源包管理
License 管理
实时日志分析
实践教程
媒体上传
如何将点播的媒体文件进行智能降冷
媒体处理
分发播放
如何接收事件通知
如何进行源站迁移
直播录制
如何进行自定义源站回源
直播精彩剪辑固化至云点播 VOD 指引
如何使用 EdgeOne 分发云点播内容
开发指南
媒体上传
媒体加工处理
媒体 AI
事件通知
媒体分发播放
媒体加密与版权保护
播放频道
访问管理
下载媒体文件
应用体系
错误码
播放器 SDK 文档
概述
基本概念
产品功能
Demo 体验
免费测试
购买指南
SDK 下载
License 指引
播放器教程
含 UI 集成方案
无 UI 集成方案
高级功能
API 文档
第三方播放器插件
Player SDK Policy
服务端 API 文档
History
Introduction
API Category
Other APIs
Media Processing APIs
Task Management APIs
Media Upload APIs
Media Management APIs
Event Notification Relevant API
Media Categorization APIs
Domain Name Management APIs
Distribution APIs
AI-based Sample Management APIs
Region Management APIs
Data Statistics APIs
Carousel-Related APIs
Just In Time Transcode APIs
No longer recommended APIs
Making API Requests
AI-based image processing APIs
Parameter Template APIs
Task Flow APIs
Data Types
Error Codes
Video on Demand API 2024-07-18
常见问题
移动端播放问题
费用相关问题
视频上传问题
视频发布问题
视频播放问题
Web 端播放问题
全屏播放问题
数据统计问题
访问管理相关问题
媒资降冷问题
相关协议
Service Level Agreement
VOD 政策
隐私政策
数据处理和安全协议
联系我们
词汇表

服务端上传指引

PDF
聚焦模式
字号
最后更新时间: 2025-08-05 09:38:23
本文主要提供专业版应用的服务端上传指引。

适用场景

开发者通过 API 或 SDK 方式将存储在其后台服务器中的文件上传到专业版存储,本文将介绍如何使用 AWS S3 SDK 上传文件。

上传方式

服务端需通过专业版预置域名上传,支持方式如下:
上传方式
上传域名
上传凭证
支持操作
存储桶预置域名
[BucketId].vodpro.[存储地域].eovod.com
永久密钥(推荐)
上传至指定存储桶
临时凭证
上传至指定存储桶
应用预置域名
[SubAppId].vodpro-upload.com
临时凭证
上传至指定存储桶
就近上传至应用内某区域的存储桶

存储桶预置域名

专业版为每个存储桶预置了一个公网访问域名,可使用该域名上传文件至指定存储桶。

使用永久密钥通过 AWS S3 SDK 上传文件

下面介绍在常用的编程语言中,如何使用永久密钥上传文件至专业版应用的存储桶。
假设专业版应用 ID 为 1234567890 ,存储桶的存储地域为 ap-guangzhou,存储桶 ID 为 bucketid1
本示例使用的存储桶公网访问域名为:bucketid1.vodpro.ap-guangzhou.eovod.com

准备工作

1. 创建专业版应用和存储桶
云点播控制台 创建好专业版应用和存储桶,具体操作步骤参见 快速入门创建存储桶 文档。
2. 获取应用级别永久密钥对
从专业版应用的密钥管理中,获取密钥对 AccessKeyIdSecretAccessKey,具体操作步骤参见 密钥管理 文档。

上传示例

常用语言初始化 S3 客户端并上传文件代码实现如下所示:
GO
Java
C++
Python
// Package main
// 本示例基于 AWS SDK for Go v2 service/s3 v1.72.3 实现。
// AWS SDK for Go v2 service/s3 v1.73.0 及以后版本需禁用 S3 的默认完整性保护。
// 具体参考: https://github.com/aws/aws-sdk-go-v2/discussions/2960
package main

import (
"context"
"errors"
"fmt"
"log"
"net/url"
"os"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
smep "github.com/aws/smithy-go/endpoints"
"github.com/aws/smithy-go/logging"
)


// customEndpointResolverV2 自定义接入点
type customEndpointResolverV2 struct{}

// ResolveEndpoint 自定义接入点
func (r *customEndpointResolverV2) ResolveEndpoint(ctx context.Context,
params s3.EndpointParameters) (smep.Endpoint, error) {
if params.Bucket == nil || params.Region == nil {
return smep.Endpoint{}, errors.New("invalid endpoint param")
}
return smep.Endpoint{
URI: url.URL{
Scheme: "https",
Host: fmt.Sprintf("%s.vodpro.ap-guangzhou.eovod.com", *params.Bucket),
},
}, nil
}

func main() {
// new s3 client
s3cli := s3.New(s3.Options{
Credentials: credentials.NewStaticCredentialsProvider(
"AccessKeyId", // 填入密钥对中的 AccessKeyId
"SecretAccessKey", // 填入密钥对中的 SecretAccessKey
""), // 永久密钥无需填充 SessionToken
EndpointResolverV2: new(customEndpointResolverV2), // 自定义接入点
UsePathStyle: false, // 请求禁用 path style
Logger: logging.NewStandardLogger(os.Stdout), // 日志打印到标准输出流
ClientLogMode: aws.LogRequest | aws.LogResponse, // 打印请求头和响应头
Region: "auto", // 固定填写 auto
})

// 打开本地文件
file, err := os.Open("demo.mp4") // 本地文件路径
if err != nil {
log.Fatalf("failed to open file: %v", err)
return
}
defer file.Close()

// 上传文件到 S3
// 创建上传器
uploader := manager.NewUploader(s3cli, func(u *manager.Uploader) {
u.PartSize = 10 * 1024 * 1024 // 设置分片大小为 10MB
u.Concurrency = 5 // 设置并发上传的分片数
})
// 上传文件
result, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String("bucketid1"), // Bucket 设置为点播专业版应用中的存储桶 ID
Key: aws.String("upload/demo.mp4"), // 媒体文件在存储桶中的路径
Body: file,
})

if err != nil {
log.Fatalf("failed to upload file: %v", err)
return
}
log.Printf("etag: %s", *result.ETag) // 获取上传文件 etag
}O
// 本示例基于 AWS SDK for Java v2 service/s3 v2.31.35 实现。
// AWS SDK for Java 2.30.0 及以后版本需禁用 S3 的默认完整性保护。
// 具体参考: https://github.com/aws/aws-sdk-java-v2/issues/5801
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.UploadFileRequest;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;

import java.io.File;
import java.net.URI;
import java.util.concurrent.CompletableFuture;

public class Main {
public static void main(String[] args) {
S3AsyncClient s3AsyncClient = null;
S3TransferManager transferManager = null;

try {
// 专业版应用永久密钥 AccessKeyId
AwsBasicCredentials credentials = AwsBasicCredentials.create(
"AccessKeyId", // 专业版应用永久密钥 AccessKeyId
"SecretAccessKey" // 专业版应用永久密钥 SecretAccessKey
);
String region = "ap-guangzhou"; // 专业版应用存储桶地域
String bucketId = "bucketid1"; // 专业版应用存储桶ID
String filePath = "demo.mp4"; // 本地文件路径
String key = "upload/demo.mp4"; // 文件在存储桶中的路径
String endpointUrl = String.format("https://vodpro.%s.eovod.com", region); // 桶级别预置域名端点

// 使用自定义端点构建S3异步客户端
s3AsyncClient = S3AsyncClient.builder()
.credentialsProvider(StaticCredentialsProvider.create(credentials)) // 设置凭证提供者
.endpointOverride(URI.create(endpointUrl)) // 设置端点
.region(Region.of("auto")) // 固定填写 auto
.httpClient(NettyNioAsyncHttpClient.builder().build()) // 设置HTTP客户端
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(false) // 设置路径样式访问, 桶级别需明确禁用 PathStyle 模式
.build())
.requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED)
.responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED)
.build();

// 使用S3异步客户端创建S3TransferManager
transferManager = S3TransferManager.builder()
.s3Client(s3AsyncClient)
.build();

File fileToUpload = new File(filePath);

// 创建上传请求并添加传输监听器用于进度跟踪
UploadFileRequest uploadRequest = UploadFileRequest.builder()
.putObjectRequest(builder -> builder
.bucket(bucketId)
.key(key))
.addTransferListener(LoggingTransferListener.create())
.source(fileToUpload)
.build();

// 开始上传
CompletableFuture<Void> future = transferManager.uploadFile(uploadRequest)
.completionFuture()
.thenAccept(response -> {
System.out.println("上传成功! ETag: " + response.response().eTag());
});

// 等待上传完成
future.join();

System.out.println("上传任务完成");
} catch (Exception e) {
System.err.println("上传失败: " + e.getMessage());
e.printStackTrace();
} finally {
// 资源释放
try {
if (transferManager != null) {
transferManager.close();
}
if (s3AsyncClient != null) {
s3AsyncClient.close();
}
System.out.println("资源已释放");
} catch (Exception e) {
System.err.println("关闭资源时发生错误: " + e.getMessage());
}
}
}
}
// 本示例基于 AWS SDK for C++ v1.11.560 实现。
// AWS SDK for C++ v1.11.486 及以后版本需禁用 S3 的默认完整性保护。
// 具体参考: https://github.com/aws/aws-sdk-cpp/issues/3253
#include <aws/core/Aws.h>
#include <aws/core/auth/AWSCredentials.h>
#include <aws/core/client/ClientConfiguration.h>
#include <aws/s3/S3Client.h>
#include <aws/transfer/TransferManager.h>
#include <iostream>

int main()
{
// AWS SDK 初始化
Aws::SDKOptions options;
options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Info;
Aws::InitAPI(options);

{
// 定义认证和端点信息
const Aws::String accessKeyId = "AccessKeyId"; // 填入点播专业版密钥对中的 AccessKeyId
const Aws::String secretAccessKey = "SecretAccessKey"; // 填入点播专业版密钥对中的 SecretAccessKey
const Aws::String region = "ap-guangzhou"; // 存储桶所在地域
const Aws::String bucketId = "bucketid1"; // 点播专业版应用中的存储桶 ID
const Aws::String keyName = "upload/demo.mp4"; // 文件在存储桶中的路径
const Aws::String filePath = "demo.mp4"; // 本地文件路径

// 配置客户端
Aws::Client::ClientConfiguration clientConfig;
clientConfig.region = "auto"; // 固定填写 auto
clientConfig.scheme = Aws::Http::Scheme::HTTPS; // 协议使用 HTTPS
clientConfig.enableEndpointDiscovery = false; // 禁用 endpoint discovery
clientConfig.verifySSL = true; // 启用 SSL 证书验证
clientConfig.endpointOverride = "vodpro." + region + ".eovod.com"; // 桶级别预置域名
// 禁用完整性校验
clientConfig.checksumConfig.requestChecksumCalculation = Aws::Client::RequestChecksumCalculation::WHEN_REQUIRED;
clientConfig.checksumConfig.responseChecksumValidation = Aws::Client::ResponseChecksumValidation::WHEN_REQUIRED;

// 设置认证信息
Aws::Auth::AWSCredentials credentials(accessKeyId, secretAccessKey); // 填入点播专业版永久密钥

// 创建 S3 客户端
auto s3Client = Aws::MakeShared<Aws::S3::S3Client>(
"S3Client",
credentials,
clientConfig,
Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, // 禁用 payload signing
true // 桶级别预置域名需明确使用 VirtualAddressing
);

// 创建线程池执行器
auto executor = Aws::MakeShared<Aws::Utils::Threading::PooledThreadExecutor>("executor", 10);

// 配置 TransferManager
Aws::Transfer::TransferManagerConfiguration transferConfig(executor.get());
transferConfig.s3Client = s3Client;

// 设置分片大小为 10MB
transferConfig.bufferSize = 10 * 1024 * 1024;

// 创建 TransferManager
auto transferManager = Aws::Transfer::TransferManager::Create(transferConfig);

std::cout << "Starting file upload: " << filePath << " to " << bucketId << "/" << keyName << std::endl;

// 执行上传
auto uploadHandle = transferManager->UploadFile(
filePath, // 本地文件路径
bucketId, // 存储桶ID
keyName, // 对象键(存储路径)
"application/octet-stream", // 内容类型
Aws::Map<Aws::String, Aws::String>() // 元数据
);

// 等待上传完成
uploadHandle->WaitUntilFinished();

// 检查上传状态
if (uploadHandle->GetStatus() == Aws::Transfer::TransferStatus::COMPLETED)
{
std::cout << "File upload completed successfully!" << std::endl;
}
else
{
auto lastError = uploadHandle->GetLastError();
std::cerr << "File upload failed: " << lastError.GetMessage() << std::endl;
}
}

// 清理SDK资源
Aws::ShutdownAPI(options);
return 0;
}
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
本示例基于 AWS SDK for Python v1.38.7 实现。
Boto3 的 AWS SDK for Python v1.36.0 及以后版本需禁用 S3 的默认完整性保护。
具体参考: https://github.com/boto/boto3/issues/4392
"""

import boto3
from botocore.config import Config
from botocore.exceptions import ClientError

# 常量定义
REGION = "ap-guangzhou" # 存储桶地域
BUCKET_ID = "bucketid1" # 填入点播专业版应用中的存储桶 ID
FILE_PATH = "demo.mp4" # 本地文件路径
OBJECT_KEY = "upload/demo.mp4" # 文件在存储桶中的路径

# 创建 S3 客户端
s3_client = boto3.client(
"s3",
aws_access_key_id="AccessKeyId", # 填入密钥对中的 AccessKeyId
aws_secret_access_key="SecretAccessKey", # 填入密钥对中的 SecretAccessKey
endpoint_url=f"https://vodpro.{REGION}.eovod.com", # 桶级别预置上传域名
region_name="auto", # 固定填写 auto
config=Config(
s3={"addressing_style": "virtual"}, # 使用 virtual hosted-style
request_checksum_calculation="when_required", # Boto3 的 AWS SDK for Python v1.36.0 及以后版本需禁用 S3 的默认完整性保护。
response_checksum_validation="when_required", # Boto3 的 AWS SDK for Python v1.36.0 及以后版本需禁用 S3 的默认完整性保护。
),
)

try:
# 上传文件
response = s3_client.upload_file(
Bucket=BUCKET_ID, # 填入点播专业版应用中的存储桶 ID
Key=OBJECT_KEY, # 文件在存储桶中的路径
Filename=FILE_PATH, # 本地文件路径
)
print(response)
except ClientError as e:
print(f"Error: {e}")
注意:
使用 AWS SDK 请确认数据完整性保护特性。
使用存储桶预置公网域名,路径必须为 VirtualStyle 格式。

使用临时凭证通过 AWS S3 SDK 上传文件

下面介绍在常用的编程语言中,如何使用临时凭证上传文件至专业版应用的存储桶。
假设专业版应用 ID 为 1234567890 ,存储桶的存储地域为 ap-guangzhou,存储桶 ID 为 bucketid1
本示例使用的存储桶访问域名为:bucketid1.vodpro.ap-guangzhou.eovod.com

上传步骤

临时凭证通常用于对密钥安全性要求较高场景。
服务端使用临时凭证上传文件至专业版存储
服务端使用临时凭证上传文件至专业版存储

1. 申请上传临时凭证:服务端调用云点播专业版服务的 创建应用存储临时访问凭证 接口。
2. 上传文件:服务端使用临时凭证上传文件至云点播专业版存储。

准备工作

1. 创建专业版应用和存储桶
云点播控制台 创建好专业版应用和存储桶,具体操作步骤参见 快速入门创建存储桶 文档。
2. 获取腾讯云账号永久密钥对
2.1 登录腾讯云控制台,选择访问管理 > 访问密钥 > API 密钥管理,进入“API 密钥管理” 页面。
2.2 获取云 API 密钥。如果您尚未创建密钥,则单击新建密钥即可创建一对 SecretIdSecretKey

上传文件

申请上传临时凭证
申请 bucketid1存储桶上传 upload/demo.mp4的临时凭证。
GO
Java
C++
Python
// Package main
package main

import (
"context"
"encoding/json"
"fmt"
"log"
"net/url"

"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
vod20240718 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod/v20240718"
)

const (
appId = 251000000 // 腾讯云账号 APPID
subAppId = 1234567890 // 云点播专业版应用 APPID
bucketId = "bucketid1" // 云点播专业版应用存储桶的 ID
fileKey = "upload/demo.mp4" // 上传到存储后的文件 KEY,包含完整路径和文件名,例如 upload/demo.mp4
)

// createStorageCredentialsPolicy 创建存储凭证策略
type createStorageCredentialsPolicy struct {
Statement []policyStatement `json:"statement"`
Version string `json:"version"`
}

// policyStatement 策略语句
type policyStatement struct {
Action []string `json:"action"`
Effect string `json:"effect"`
Resource []string `json:"resource"`
}

// cred 临时凭证
type cred struct {
AccessKeyId string
SecretAccessKey string
SessionToken string
}

// getCredential 获取临时凭证
func getCredential(context.Context) (*cred, error) {
// 1. 获取临时凭证
// 1.1 初始化调用腾讯云 API 对象
credential := common.NewCredential(
"SecretId", // 腾讯云账号 SecretId
"SecretKey", // 腾讯云账号 SecretKey
)
prof := profile.NewClientProfile()
vodClient, err := vod20240718.NewClient(credential, "ap-guangzhou", prof)
if err != nil {
log.Fatalf("create VOD client fail: %+v", err)
return nil, fmt.Errorf("create VOD client fail: %w", err)
}

// 1.2 构造申请上传临时凭证请求
policy := createStorageCredentialsPolicy{
Statement: []policyStatement{{
Action: []string{ // 当前仅支持如下 action,可以只申请其中一部分
"name/vod:PutObject",
"name/vod:ListParts",
"name/vod:PostObject",
"name/vod:CreateMultipartUpload",
"name/vod:UploadPart",
"name/vod:CompleteMultipartUpload",
"name/vod:AbortMultipartUpload",
"name/vod:ListMultipartUploads",
},
Effect: "allow",
Resource: []string{
fmt.Sprintf("qcs::vod:%s:uid/%d:prefix//%d/%s/%s",
"ap-guangzhou", // 存储桶所在地域
appId, // 腾讯云账号 APPID
subAppId, // 云点播专业版应用 APPID
bucketId, // 云点播专业版应用存储桶的 ID
fileKey, // 上传到存储后的文件 KEY,包含完整路径和文件名,例如 upload/demo.mp4
),
},
}},
Version: "2.0",
}
req := vod20240718.NewCreateStorageCredentialsRequest()
req.SubAppId = common.Uint64Ptr(subAppId)
policyStr, _ := json.Marshal(policy)
req.Policy = common.StringPtr(url.QueryEscape(string(policyStr)))

// 1.3 申请上传临时凭证
resp, err := vodClient.CreateStorageCredentials(req)
if err != nil {
log.Fatalf("create storage credentials fail: %+v", err)
return nil, fmt.Errorf("create storage credentials fail: %w", err)
}
log.Printf("create storage credentials success: %+v", resp)
creds := resp.Response.Credentials
return &cred{
AccessKeyId: *creds.AccessKeyId,
SecretAccessKey: *creds.SecretAccessKey,
SessionToken: *creds.SessionToken,
}, nil
}
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.vod.v20240718.VodClient;
import com.tencentcloudapi.vod.v20240718.models.CreateStorageCredentialsRequest;
import com.tencentcloudapi.vod.v20240718.models.CreateStorageCredentialsResponse;

import org.json.JSONArray;
import org.json.JSONObject;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
* 凭证辅助类,负责获取临时存储凭证
*/
public class CredentialHelper {
// 常量定义
private static final long APP_ID = 251000000; // 腾讯云账号 APPID
private static final long SUB_APP_ID = 1234567890; // 云点播专业版应用 APPID
private static final String BUCKET_ID = "bucketid1"; // 云点播专业版应用存储桶的 ID
private static final String FILE_KEY = "upload/demo.mp4"; // 上传到存储后的文件 KEY
private static final String REGION = "ap-guangzhou"; // 存储桶所在地域

/**
* 凭证对象,存储临时凭证信息
*/
public static class Cred {
private final String accessKeyId;
private final String secretAccessKey;
private final String sessionToken;

public Cred(String accessKeyId, String secretAccessKey, String sessionToken) {
this.accessKeyId = accessKeyId;
this.secretAccessKey = secretAccessKey;
this.sessionToken = sessionToken;
}

public String getAccessKeyId() {
return accessKeyId;
}

public String getSecretAccessKey() {
return secretAccessKey;
}

public String getSessionToken() {
return sessionToken;
}
}

/**
* 获取临时凭证
*
* @return 临时凭证对象
* @throws Exception 如果获取凭证失败
*/
public static Cred getCredential() throws Exception {
try {
// 1. 初始化腾讯云 API 客户端
Credential credential = new Credential("SecretId", "SecretKey"); // 腾讯云账号 SecretId 和 SecretKey
ClientProfile clientProfile = new ClientProfile(); // 客户端配置
VodClient vodClient = new VodClient(credential, "ap-guangzhou", clientProfile); // 创建 VodClient 对象

// 2. 构造并编码策略
String policyJson = createPolicyJson();
String encodedPolicy = URLEncoder.encode(policyJson, StandardCharsets.UTF_8.name());

// 3. 创建并发送请求
CreateStorageCredentialsRequest req = new CreateStorageCredentialsRequest();
req.setSubAppId(SUB_APP_ID); // 云点播专业版应用 APPID
req.setPolicy(encodedPolicy); // 策略

// 4. 获取响应并返回凭证
CreateStorageCredentialsResponse resp = vodClient.CreateStorageCredentials(req);

return new Cred(
resp.getCredentials().getAccessKeyId(),
resp.getCredentials().getSecretAccessKey(),
resp.getCredentials().getSessionToken());
} catch (TencentCloudSDKException e) {
System.err.println("获取存储凭证失败: " + e.getMessage());
throw new Exception("获取存储凭证失败", e);
}
}

/**
* 创建策略JSON字符串,使用 org.json 库
*
* @return 策略JSON字符串
*/
private static String createPolicyJson() {
// 构建资源路径
String resource = String.format(
"qcs::vod:%s:uid/%d:prefix//%d/%s/%s",
REGION,
APP_ID,
SUB_APP_ID,
BUCKET_ID,
FILE_KEY);

// 构建操作列表
String[] actions = {
"name/vod:PutObject",
"name/vod:ListParts",
"name/vod:PostObject",
"name/vod:CreateMultipartUpload",
"name/vod:UploadPart",
"name/vod:CompleteMultipartUpload",
"name/vod:AbortMultipartUpload",
"name/vod:ListMultipartUploads"
};

// 使用 JSONObject 构建 JSON
JSONObject policy = new JSONObject();
policy.put("version", "2.0");

JSONArray statements = new JSONArray();
JSONObject statement = new JSONObject();

JSONArray actionArray = new JSONArray();
for (String action : actions) {
actionArray.put(action);
}
statement.put("action", actionArray);

statement.put("effect", "allow");

JSONArray resources = new JSONArray();
resources.put(resource);
statement.put("resource", resources);

statements.put(statement);
policy.put("statement", statements);

return policy.toString();
}
}
#include <tencentcloud/core/TencentCloud.h>
#include <tencentcloud/core/profile/ClientProfile.h>
#include <tencentcloud/core/profile/HttpProfile.h>
#include <tencentcloud/core/Credential.h>
#include <tencentcloud/vod/v20240718/VodClient.h>
#include <tencentcloud/vod/v20240718/model/CreateStorageCredentialsRequest.h>
#include <tencentcloud/vod/v20240718/model/CreateStorageCredentialsResponse.h>
#include <string>
#include <sstream>
#include <iomanip>
#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

const uint64_t APP_ID = 251000000; // 腾讯云账号 APPID
const uint64_t SUB_APP_ID = 1234567890; // 云点播专业版应用 APPID
const std::string BUCKET_ID = "bucketid1"; // 云点播专业版应用存储桶的 ID
const std::string REGION = "ap-guangzhou"; // 云点播专业版应用存储桶地域
const std::string OBJECT_KEY = "upload/demo.mp4"; // 申请权限的存储文件 KEY

// 凭证结构体
struct Credential
{
std::string accessKeyId;
std::string secretAccessKey;
std::string sessionToken;
};

// URL 编码函数
std::string UrlEncode(const std::string &value)
{
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;

for (char c : value)
{
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
{
escaped << c;
}
else
{
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int((unsigned char)c);
escaped << std::nouppercase;
}
}

return escaped.str();
}

// 获取临时凭证
Credential GetCredential()
{
// 初始化腾讯云 SDK
TencentCloud::InitAPI();

// 创建 VOD 客户端
TencentCloud::Credential credential("SecretId", "SecretKey"); // 填入腾讯云账号 SecretId 和 SecretKey
TencentCloud::HttpProfile httpProfile;
TencentCloud::ClientProfile clientProfile;
clientProfile.SetHttpProfile(httpProfile);

TencentCloud::Vod::V20240718::VodClient client(credential, "ap-guangzhou", clientProfile);

// 构建策略
json policy_json = {
{"statement", {{{"action", {"name/vod:PutObject", "name/vod:ListParts", "name/vod:PostObject", "name/vod:CreateMultipartUpload", "name/vod:UploadPart", "name/vod:CompleteMultipartUpload", "name/vod:AbortMultipartUpload", "name/vod:ListMultipartUploads"}}, {"effect", "allow"}, {"resource", {"qcs::vod:" + REGION + ":uid/" + std::to_string(APP_ID) + ":prefix//" + std::to_string(SUB_APP_ID) + "/" + BUCKET_ID + "/" + OBJECT_KEY}}}}},
{"version", "2.0"}};
std::string policy = policy_json.dump();

// 创建请求对象
TencentCloud::Vod::V20240718::Model::CreateStorageCredentialsRequest req;
req.SetSubAppId(SUB_APP_ID);
req.SetPolicy(UrlEncode(policy));

// 发送请求
auto outcome = client.CreateStorageCredentials(req);
if (!outcome.IsSuccess())
{
std::cerr << "Failed to get storage credentials: " << outcome.GetError().GetErrorMessage() << std::endl;
TencentCloud::ShutdownAPI();
exit(1);
}

// 提取凭证
auto response = outcome.GetResult();
auto creds = response.GetCredentials();

Credential result;
result.accessKeyId = creds.GetAccessKeyId();
result.secretAccessKey = creds.GetSecretAccessKey();
result.sessionToken = creds.GetSessionToken();

// 清理腾讯云 SDK
TencentCloud::ShutdownAPI();

return result;
}
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import json
import urllib.parse
from typing import NamedTuple

from tencentcloud.common import credential
from tencentcloud.common.profile import client_profile
from tencentcloud.vod.v20240718 import vod_client, models

# 常量定义
APP_ID = 251000000 # 腾讯云账号 APPID
SUB_APP_ID = 1234567890 # 云点播专业版应用 APPID
BUCKET_ID = "bucketid1" # 云点播专业版应用存储桶的 ID
OBJECT_KEY = "upload/demo.mp4" # 上传到存储后的文件 KEY
REGION = "ap-guangzhou" # 地域


class Credential(NamedTuple):
"""临时凭证"""

access_key_id: str
secret_access_key: str
session_token: str


def get_credential() -> Credential:
"""获取临时凭证"""
# 1. 初始化调用腾讯云 API 对象
cred = credential.Credential(
"SecretId", # 腾讯云账号 SecretId
"SecretKey", # 腾讯云账号 SecretKey
)
prof = client_profile.ClientProfile()
vod_cli = vod_client.VodClient(cred, "ap-guangzhou", prof)

# 2. 构造申请上传临时凭证请求
policy = {
"statement": [
{
"action": [
"name/vod:PutObject",
"name/vod:ListParts",
"name/vod:PostObject",
"name/vod:CreateMultipartUpload",
"name/vod:UploadPart",
"name/vod:CompleteMultipartUpload",
"name/vod:AbortMultipartUpload",
"name/vod:ListMultipartUploads",
],
"effect": "allow",
"resource": [f"qcs::vod:{REGION}:uid/{APP_ID}:prefix//{SUB_APP_ID}/{BUCKET_ID}/{OBJECT_KEY}"],
}
],
"version": "2.0",
}

req = models.CreateStorageCredentialsRequest()
req.SubAppId = SUB_APP_ID
req.Policy = urllib.parse.quote(json.dumps(policy))

# 3. 申请上传临时凭证
resp = vod_cli.CreateStorageCredentials(req)
creds = resp.Credentials
return Credential(
access_key_id=creds.AccessKeyId,
secret_access_key=creds.SecretAccessKey,
session_token=creds.SessionToken,
)
文件上传
常用语言初始化 S3 客户端并上传文件至存储桶的代码实现如下所示:
GO
Java
C++
Python
// Package main
// 本示例基于 AWS SDK for Go v2 service/s3 v1.72.3 实现。
// AWS SDK for Go v2 service/s3 v1.73.0 及以后版本需禁用 S3 的默认完整性保护。
// 具体参考: https://github.com/aws/aws-sdk-go-v2/discussions/2960
package main

import (
"context"
"errors"
"fmt"
"log"
"net/url"
"os"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
smep "github.com/aws/smithy-go/endpoints"
"github.com/aws/smithy-go/logging"
)

type customEndpointResolverV2 struct {
}

// ResolveEndpoint 自定义接入点
func (r *customEndpointResolverV2) ResolveEndpoint(ctx context.Context,
params s3.EndpointParameters) (smep.Endpoint, error) {
if params.Bucket == nil || params.Region == nil {
return smep.Endpoint{}, errors.New("invalid endpoint param")
}
return smep.Endpoint{
URI: url.URL{
Scheme: "https",
Host: fmt.Sprintf(
"%s.vodpro.ap-guangzhou.eovod.com",
*params.Bucket, // 填入点播专业版应用中的存储桶 ID
),
},
}, nil
}

func main() {
// 1. 获取临时凭证
cred, err := getCredential(context.Background())
if err != nil {
log.Fatalf("get credential fail: %v", err)
return
}

// 2. 使用临时凭证上传文件
// 2.1 创建 s3 client
s3cli := s3.New(s3.Options{
Credentials: credentials.NewStaticCredentialsProvider(
cred.AccessKeyId, // 填入临时凭证中的 AccessKeyId
cred.SecretAccessKey, // 填入临时凭证中的 SecretAccessKey
cred.SessionToken, // 填入临时凭证中的 SessionToken
),
EndpointResolverV2: new(customEndpointResolverV2), // 自定义接入点
UsePathStyle: false, // 请求禁用 path style
Logger: logging.NewStandardLogger(os.Stdout), // 日志打印到标准输出流
ClientLogMode: aws.LogRequest | aws.LogResponse, // 打印请求头和响应头
Region: "auto", // 固定填写 auto
})

// 2.2 打开本地文件
file, err := os.Open("demo.mp4") // 本地文件路径
if err != nil {
log.Fatalf("failed to open file: %v", err)
return
}
defer file.Close()

// 2.3 上传文件到 S3
// 2.3.1 创建上传器
uploader := manager.NewUploader(s3cli, func(u *manager.Uploader) {
u.PartSize = 10 * 1024 * 1024 // 设置分片大小为 10MB
u.Concurrency = 5 // 设置并发上传的分片数
})
// 2.3.2 上传文件
result, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(bucketId), // Bucket 设置为点播专业版应用中的存储桶 ID
Key: aws.String(fileKey), // 媒体文件在存储桶中的路径
Body: file,
})

if err != nil {
log.Fatalf("failed to upload file: %v", err)
return
}
log.Printf("etag: %s", *result.ETag) // 获取上传文件 etag
}
// 本示例基于 AWS SDK for Java v2 service/s3 v2.31.35 实现。
// AWS SDK for Java 2.30.0 及以后版本需禁用 S3 的默认完整性保护。
// 具体参考: https://github.com/aws/aws-sdk-java-v2/issues/5801
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.UploadFileRequest;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;

import java.io.File;
import java.net.URI;
import java.util.concurrent.CompletableFuture;

public class Main {
public static void main(String[] args) {
S3AsyncClient s3AsyncClient = null;
S3TransferManager transferManager = null;

try {
// 1. 获取临时凭证
CredentialHelper.Cred cred = CredentialHelper.getCredential();
System.out.println("获取临时凭证成功, AccessKeyId: " + cred.getAccessKeyId());

// 2. 创建会话凭证(包含临时 token)
AwsSessionCredentials sessionCredentials = AwsSessionCredentials.create(
cred.getAccessKeyId(), // 临时凭证 AccessKeyId
cred.getSecretAccessKey(), // 临时凭证 SecretAccessKey
cred.getSessionToken() // 临时凭证 SessionToken
);

String region = "ap-guangzhou"; // 专业版应用存储桶地域
String bucketId = "bucketid1"; // 专业版应用存储桶ID
String filePath = "demo.mp4"; // 本地文件路径
String key = "upload/demo.mp4"; // 文件在存储桶中的路径
String endpointUrl = String.format("https://vodpro.%s.eovod.com", region); // 桶级别预置域名端点

// 3. 使用自定义端点构建S3异步客户端
s3AsyncClient = S3AsyncClient.builder()
.credentialsProvider(StaticCredentialsProvider.create(sessionCredentials)) // 设置凭证提供者
.endpointOverride(URI.create(endpointUrl)) // 设置端点
.region(Region.of("auto")) // 设置区域 auto 自动选择
.httpClient(NettyNioAsyncHttpClient.builder().build()) // 设置HTTP客户端
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(false) // 设置路径样式访问, 桶级别需明确禁用 PathStyle 模式
.build())
.requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED)
.responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED)
.build();

// 4. 使用S3异步客户端创建S3TransferManager
transferManager = S3TransferManager.builder()
.s3Client(s3AsyncClient)
.build();

// 5. 准备上传文件
File fileToUpload = new File(filePath);
if (!fileToUpload.exists()) {
throw new RuntimeException("文件不存在: " + filePath);
}

// 6. 创建上传请求并添加传输监听器用于进度跟踪
UploadFileRequest uploadRequest = UploadFileRequest.builder()
.putObjectRequest(builder -> builder
.bucket(bucketId)
.key(key))
.addTransferListener(LoggingTransferListener.create())
.source(fileToUpload)
.build();

// 7. 开始上传
System.out.println("开始上传文件...");
CompletableFuture<Void> future = transferManager.uploadFile(uploadRequest)
.completionFuture()
.thenAccept(response -> {
System.out.println("上传成功! ETag: " + response.response().eTag());
});

// 8. 等待上传完成
future.join();

System.out.println("上传任务完成");
} catch (Exception e) {
System.err.println("上传失败: " + e.getMessage());
e.printStackTrace();
} finally {
// 资源释放
try {
if (transferManager != null) {
transferManager.close();
}
if (s3AsyncClient != null) {
s3AsyncClient.close();
}
System.out.println("资源已释放");
} catch (Exception e) {
System.err.println("关闭资源时发生错误: " + e.getMessage());
}
}
}
}
// 本示例基于 AWS SDK for C++ v1.11.560 实现。
// AWS SDK for C++ v1.11.486 及以后版本需禁用 S3 的默认完整性保护。
// 具体参考: https://github.com/aws/aws-sdk-cpp/issues/3253
#include <aws/core/Aws.h>
#include <aws/core/auth/AWSCredentials.h>
#include <aws/core/client/ClientConfiguration.h>
#include <aws/s3/S3Client.h>
#include <aws/transfer/TransferManager.h>
#include <iostream>
#include <string>

// 引用 get_cred.cpp 中定义的凭证结构体
struct Credential
{
std::string accessKeyId;
std::string secretAccessKey;
std::string sessionToken;
};

// 引用 get_cred.cpp 中定义的函数
extern Credential GetCredential();

int main()
{
// AWS SDK 初始化
Aws::SDKOptions options;
options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Info;
Aws::InitAPI(options);

{
// 获取临时凭证
std::cout << "Getting temporary credentials..." << std::endl;
Credential cred = GetCredential();
std::cout << "Successfully obtained temporary credentials, access key Id: " << cred.accessKeyId << std::endl;

// 定义端点信息
const Aws::String region = "ap-guangzhou"; // 云点播专业版应用存储桶所在地域
const Aws::String bucketId = "bucketid1"; // 云点播专业版应用存储桶的 ID
const Aws::String keyName = "upload/demo.mp4"; // 文件在存储桶中的路径
const Aws::String filePath = "demo.mp4"; // 本地文件路径

// 配置客户端
Aws::Client::ClientConfiguration clientConfig;
clientConfig.region = "auto"; // 固定填写 auto
clientConfig.scheme = Aws::Http::Scheme::HTTPS; // 协议使用 HTTPS
clientConfig.enableEndpointDiscovery = false; // 禁用 endpoint discovery
clientConfig.verifySSL = true; // 启用 SSL 证书验证
clientConfig.endpointOverride = "vodpro." + region + ".eovod.com"; // 桶级别预置域名
// 禁用完整性校验
clientConfig.checksumConfig.requestChecksumCalculation = Aws::Client::RequestChecksumCalculation::WHEN_REQUIRED;
clientConfig.checksumConfig.responseChecksumValidation = Aws::Client::ResponseChecksumValidation::WHEN_REQUIRED;

// 设置认证信息(使用临时凭证)
Aws::Auth::AWSCredentials credentials(
cred.accessKeyId.c_str(),
cred.secretAccessKey.c_str(),
cred.sessionToken.c_str());

// 创建 S3 客户端
auto s3Client = Aws::MakeShared<Aws::S3::S3Client>(
"S3Client",
credentials,
clientConfig,
Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, // 禁用 payload signing
true // 桶级别预置域名需明确使用 VirtualAddressing
);

// 创建线程池执行器
auto executor = Aws::MakeShared<Aws::Utils::Threading::PooledThreadExecutor>("executor", 10);

// 配置 TransferManager
Aws::Transfer::TransferManagerConfiguration transferConfig(executor.get());
transferConfig.s3Client = s3Client;

// 设置分片大小为 10MB
transferConfig.bufferSize = 10 * 1024 * 1024;

// 创建 TransferManager
auto transferManager = Aws::Transfer::TransferManager::Create(transferConfig);

std::cout << "Starting file upload: " << filePath << " to " << bucketId << "/" << keyName << std::endl;

// 执行上传
auto uploadHandle = transferManager->UploadFile(
filePath, // 本地文件路径
bucketId, // 存储桶ID
keyName, // 对象键(存储路径)
"application/octet-stream", // 内容类型
Aws::Map<Aws::String, Aws::String>() // 元数据
);

// 等待上传完成
uploadHandle->WaitUntilFinished();

// 检查上传状态
if (uploadHandle->GetStatus() == Aws::Transfer::TransferStatus::COMPLETED)
{
std::cout << "File upload completed successfully!" << std::endl;
}
else
{
auto lastError = uploadHandle->GetLastError();
std::cerr << "File upload failed: " << lastError.GetMessage() << std::endl;
}
}

// 清理SDK资源
Aws::ShutdownAPI(options);
return 0;
}
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
本示例基于 AWS SDK for Python v1.38.7 实现。
Boto3 的 AWS SDK for Python v1.36.0 及以后版本需禁用 S3 的默认完整性保护。
具体参考: https://github.com/boto/boto3/issues/4392
"""

import boto3
from botocore.config import Config
from botocore.exceptions import ClientError

from get_cred import get_credential, REGION, BUCKET_ID, OBJECT_KEY

# 常量定义
FILE_PATH = "demo.mp4"

try:
# 1. 获取临时凭证
cred = get_credential()

# 2. 创建 S3 客户端
s3_client = boto3.client(
"s3",
aws_access_key_id=cred.access_key_id, # 临时凭证的 AccessKeyId
aws_secret_access_key=cred.secret_access_key, # 临时凭证的 SecretAccessKey
aws_session_token=cred.session_token, # 临时凭证的 SessionToken
endpoint_url=f"https://vodpro.{REGION}.eovod.com", # 桶级别预置上传域名
region_name="auto", # 固定填写 auto
config=Config(
s3={"addressing_style": "virtual"}, # 使用 virtual hosted-style
request_checksum_calculation="when_required", # Boto3 的 AWS SDK for Python v1.36.0 及以后版本需禁用 S3 的默认完整性保护。
response_checksum_validation="when_required", # Boto3 的 AWS SDK for Python v1.36.0 及以后版本需禁用 S3 的默认完整性保护。
),
)

# 3. 上传文件
response = s3_client.upload_file(
Bucket=BUCKET_ID, # 填入点播专业版应用中的存储桶 ID
Key=OBJECT_KEY, # 文件在存储桶中的路径
Filename=FILE_PATH, # 本地文件路径
)
print(response)
except ClientError as e:
print(f"Error: {e}")
注意:
使用 AWS SDK 请确认数据完整性保护特性。
使用存储桶预置公网域名,路径必须为 VirtualStyle 格式。

应用预置域名

专业版为每个应用预置了一个公网上传加速域名,可使用该域名上传至应用内所有存储桶。

使用临时凭证通过 AWS S3 SDK 上传文件

下面介绍在常用的编程语言中,如何进行适配以上传文件至专业版应用的存储桶。
假设专业版应用 ID 为 1234567890 ,存储桶的存储地域为 ap-guangzhou,存储桶 ID 为 bucketid1。示例使用的应用上传加速域名为:1234567890.vodpro-upload.com

准备工作

1. 创建专业版应用和存储桶
云点播控制台 创建好专业版应用和存储桶,具体操作步骤参见 快速入门创建存储桶 文档。
2. 获取腾讯云账号永久密钥对
2.1 登录腾讯云控制台,选择访问管理 > 访问密钥 > API 密钥管理,进入“API 密钥管理” 页面。
2.2 获取云 API 密钥。如果您尚未创建密钥,则单击新建密钥即可创建一对 SecretIdSecretKey

上传示例

申请上传临时凭证申请
GO
Java
C++
Python
// Package main
package main

import (
"context"
"encoding/json"
"fmt"
"log"
"net/url"

"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
vod20240718 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod/v20240718"
)

const (
appId = 251000000 // 腾讯云账号 APPID
subAppId = 1234567890 // 云点播专业版应用 APPID
fileKey = "upload/demo.mp4" // 申请权限的存储文件 KEY,包含完整路径和文件名,例如 upload/demo.mp4
bucketId = "auto" // 云点播专业版应用存储桶的 ID,auto 表示自动就近选择存储桶
region = "auto" // 云点播专业版应用存储桶地域,auto 表示自动就近选择地域
)

// createStorageCredentialsPolicy 创建存储凭证策略
type createStorageCredentialsPolicy struct {
Statement []policyStatement `json:"statement"`
Version string `json:"version"`
}

// policyStatement 策略语句
type policyStatement struct {
Action []string `json:"action"`
Effect string `json:"effect"`
Resource []string `json:"resource"`
}

// cred 临时凭证
type cred struct {
AccessKeyId string
SecretAccessKey string
SessionToken string
}

// getCredential 获取临时凭证
func getCredential(context.Context) (*cred, error) {
// 1. 获取临时凭证
// 1.1 初始化调用腾讯云 API 对象
credential := common.NewCredential(
"SecretId", // 腾讯云账号 SecretId
"SecretKey", // 腾讯云账号 SecretKey
)
prof := profile.NewClientProfile()
vodClient, err := vod20240718.NewClient(credential, "ap-guangzhou", prof)
if err != nil {
log.Fatalf("create VOD client fail: %+v", err)
return nil, fmt.Errorf("create VOD client fail: %w", err)
}

// 1.2 构造申请上传临时凭证请求
policy := createStorageCredentialsPolicy{
Statement: []policyStatement{{
Action: []string{ // 当前仅支持如下 action,可以只申请其中一部分
"name/vod:PutObject",
"name/vod:ListParts",
"name/vod:PostObject",
"name/vod:InitiateMultipartUpload",
"name/vod:UploadPart",
"name/vod:CompleteMultipartUpload",
"name/vod:AbortMultipartUpload",
"name/vod:ListMultipartUploads",
},
Effect: "allow",
Resource: []string{
fmt.Sprintf("qcs::vod:%s:uid/%d:prefix//%d/%s/%s",
region, // 就近地域上传
appId, // 腾讯云账号 APPID
subAppId, // 云点播专业版应用 APPID
bucketId, // 就近地域的存储桶
fileKey, // 上传到存储后的文件 KEY,包含完整路径和文件名,例如 upload/demo.mp4
),
},
}},
Version: "2.0",
}
req := vod20240718.NewCreateStorageCredentialsRequest()
req.SubAppId = common.Uint64Ptr(subAppId)
policyStr, _ := json.Marshal(policy)
req.Policy = common.StringPtr(url.QueryEscape(string(policyStr)))

// 1.3 申请上传临时凭证
resp, err := vodClient.CreateStorageCredentials(req)
if err != nil {
log.Fatalf("create storage credentials fail: %+v", err)
return nil, fmt.Errorf("create storage credentials fail: %w", err)
}
log.Printf("create storage credentials success: %+v", resp)
creds := resp.Response.Credentials
return &cred{
AccessKeyId: *creds.AccessKeyId,
SecretAccessKey: *creds.SecretAccessKey,
SessionToken: *creds.SessionToken,
}, nil
}
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.vod.v20240718.VodClient;
import com.tencentcloudapi.vod.v20240718.models.CreateStorageCredentialsRequest;
import com.tencentcloudapi.vod.v20240718.models.CreateStorageCredentialsResponse;

import org.json.JSONArray;
import org.json.JSONObject;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
* 凭证辅助类,负责获取临时存储凭证
*/
public class CredentialHelper {
// 常量定义
private static final long APP_ID = 251000000; // 腾讯云账号 APPID
private static final long SUB_APP_ID = 1234567890; // 云点播专业版应用 APPID
private static final String BUCKET_ID = "auto"; // 云点播专业版应用存储桶的 ID,auto 表示自动就近选择存储桶
private static final String FILE_KEY = "upload/demo.mp4"; // 申请权限的存储文件 KEY,包含完整路径和文件名,例如 upload/demo.mp4
private static final String REGION = "auto"; // 云点播专业版应用存储桶地域,auto 表示自动就近选择地域

/**
* 凭证对象,存储临时凭证信息
*/
public static class Cred {
private final String accessKeyId;
private final String secretAccessKey;
private final String sessionToken;

public Cred(String accessKeyId, String secretAccessKey, String sessionToken) {
this.accessKeyId = accessKeyId;
this.secretAccessKey = secretAccessKey;
this.sessionToken = sessionToken;
}

public String getAccessKeyId() {
return accessKeyId;
}

public String getSecretAccessKey() {
return secretAccessKey;
}

public String getSessionToken() {
return sessionToken;
}
}

/**
* 获取临时凭证
*
* @return 临时凭证对象
* @throws Exception 如果获取凭证失败
*/
public static Cred getCredential() throws Exception {
try {
// 1. 初始化腾讯云 API 客户端
Credential credential = new Credential("SecretId", "SecretKey"); // 腾讯云账号 SecretId 和 SecretKey
ClientProfile clientProfile = new ClientProfile(); // 客户端配置
VodClient vodClient = new VodClient(credential, "ap-guangzhou", clientProfile); // 创建 VodClient 对象

// 2. 构造并编码策略
String policyJson = createPolicyJson();
String encodedPolicy = URLEncoder.encode(policyJson, StandardCharsets.UTF_8.name());

// 3. 创建并发送请求
CreateStorageCredentialsRequest req = new CreateStorageCredentialsRequest();
req.setSubAppId(SUB_APP_ID); // 云点播专业版应用 APPID
req.setPolicy(encodedPolicy); // 策略

// 4. 获取响应并返回凭证
CreateStorageCredentialsResponse resp = vodClient.CreateStorageCredentials(req);

return new Cred(
resp.getCredentials().getAccessKeyId(),
resp.getCredentials().getSecretAccessKey(),
resp.getCredentials().getSessionToken());
} catch (TencentCloudSDKException e) {
System.err.println("获取存储凭证失败: " + e.getMessage());
throw new Exception("获取存储凭证失败", e);
}
}

/**
* 创建策略JSON字符串,使用 org.json 库
*
* @return 策略JSON字符串
*/
private static String createPolicyJson() {
// 构建资源路径
String resource = String.format(
"qcs::vod:%s:uid/%d:prefix//%d/%s/%s",
REGION,
APP_ID,
SUB_APP_ID,
BUCKET_ID,
FILE_KEY);

// 构建操作列表
String[] actions = {
"name/vod:PutObject",
"name/vod:ListParts",
"name/vod:PostObject",
"name/vod:CreateMultipartUpload",
"name/vod:UploadPart",
"name/vod:CompleteMultipartUpload",
"name/vod:AbortMultipartUpload",
"name/vod:ListMultipartUploads"
};

// 使用 JSONObject 构建 JSON
JSONObject policy = new JSONObject();
policy.put("version", "2.0");

JSONArray statements = new JSONArray();
JSONObject statement = new JSONObject();

JSONArray actionArray = new JSONArray();
for (String action : actions) {
actionArray.put(action);
}
statement.put("action", actionArray);

statement.put("effect", "allow");

JSONArray resources = new JSONArray();
resources.put(resource);
statement.put("resource", resources);

statements.put(statement);
policy.put("statement", statements);

return policy.toString();
}
}
#include <tencentcloud/core/TencentCloud.h>
#include <tencentcloud/core/profile/ClientProfile.h>
#include <tencentcloud/core/profile/HttpProfile.h>
#include <tencentcloud/core/Credential.h>
#include <tencentcloud/vod/v20240718/VodClient.h>
#include <tencentcloud/vod/v20240718/model/CreateStorageCredentialsRequest.h>
#include <tencentcloud/vod/v20240718/model/CreateStorageCredentialsResponse.h>
#include <string>
#include <sstream>
#include <iomanip>
#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

// 常量定义
const uint64_t APP_ID = 251000000; // 腾讯云账号 APPID
const uint64_t SUB_APP_ID = 1234567890; // 云点播专业版应用 APPID
const std::string BUCKET_ID = "auto"; // 云点播专业版应用存储桶的 ID,auto 表示自动就近选择存储桶
const std::string REGION = "auto"; // 云点播专业版应用存储桶地域,auto 表示自动就近选择地域
const std::string OBJECT_KEY = "upload/demo.mp4"; // 申请权限的存储文件 KEY,包含完整路径和文件名,例如 upload/demo.mp4

// 凭证结构体
struct Credential
{
std::string accessKeyId;
std::string secretAccessKey;
std::string sessionToken;
};

// URL 编码函数
std::string UrlEncode(const std::string &value)
{
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;

for (char c : value)
{
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
{
escaped << c;
}
else
{
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int((unsigned char)c);
escaped << std::nouppercase;
}
}

return escaped.str();
}

// 获取临时凭证
Credential GetCredential()
{
// 初始化腾讯云 SDK
TencentCloud::InitAPI();

// 创建 VOD 客户端
TencentCloud::Credential credential("SecretId", "SecretKey"); // 填入腾讯云账号 SecretId 和 SecretKey
TencentCloud::HttpProfile httpProfile;
TencentCloud::ClientProfile clientProfile;
clientProfile.SetHttpProfile(httpProfile);

TencentCloud::Vod::V20240718::VodClient client(credential, "ap-guangzhou", clientProfile);

// 构建策略
json policy_json = {
{"statement", {{{"action", {"name/vod:PutObject", "name/vod:ListParts", "name/vod:PostObject", "name/vod:CreateMultipartUpload", "name/vod:UploadPart", "name/vod:CompleteMultipartUpload", "name/vod:AbortMultipartUpload", "name/vod:ListMultipartUploads"}}, {"effect", "allow"}, {"resource", {"qcs::vod:" + REGION + ":uid/" + std::to_string(APP_ID) + ":prefix//" + std::to_string(SUB_APP_ID) + "/" + BUCKET_ID + "/" + OBJECT_KEY}}}}},
{"version", "2.0"}};
std::string policy = policy_json.dump();

// 创建请求对象
TencentCloud::Vod::V20240718::Model::CreateStorageCredentialsRequest req;
req.SetSubAppId(SUB_APP_ID);
req.SetPolicy(UrlEncode(policy));

// 发送请求
auto outcome = client.CreateStorageCredentials(req);
if (!outcome.IsSuccess())
{
std::cerr << "Failed to get storage credentials: " << outcome.GetError().GetErrorMessage() << std::endl;
TencentCloud::ShutdownAPI();
exit(1);
}

// 提取凭证
auto response = outcome.GetResult();
auto creds = response.GetCredentials();

Credential result;
result.accessKeyId = creds.GetAccessKeyId();
result.secretAccessKey = creds.GetSecretAccessKey();
result.sessionToken = creds.GetSessionToken();

// 清理腾讯云 SDK
TencentCloud::ShutdownAPI();

return result;
}
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import json
import urllib.parse
from typing import NamedTuple

from tencentcloud.common import credential
from tencentcloud.common.profile import client_profile
from tencentcloud.vod.v20240718 import vod_client, models

# 常量定义
APP_ID = 251000000 # 腾讯云账号 APPID
SUB_APP_ID = 1234567890 # 云点播专业版应用 APPID
BUCKET_ID = "auto" # 云点播专业版应用存储桶的 ID, auto 表示自动就近选择存储桶
OBJECT_KEY = "upload/demo.mp4" # 上传到存储后的文件 KEY
REGION = "auto" # 地域 auto 表示自动就近选择地域


class Credential(NamedTuple):
"""临时凭证"""

access_key_id: str
secret_access_key: str
session_token: str


def get_credential() -> Credential:
"""获取临时凭证"""
# 1. 初始化调用腾讯云 API 对象
cred = credential.Credential(
"SecretId", # 腾讯云账号 SecretId
"SecretKey", # 腾讯云账号 SecretKey
)
prof = client_profile.ClientProfile()
vod_cli = vod_client.VodClient(cred, "ap-guangzhou", prof)

# 2. 构造申请上传临时凭证请求
policy = {
"statement": [
{
"action": [
"name/vod:PutObject",
"name/vod:ListParts",
"name/vod:PostObject",
"name/vod:CreateMultipartUpload",
"name/vod:UploadPart",
"name/vod:CompleteMultipartUpload",
"name/vod:AbortMultipartUpload",
"name/vod:ListMultipartUploads",
],
"effect": "allow",
"resource": [f"qcs::vod:{REGION}:uid/{APP_ID}:prefix//{SUB_APP_ID}/{BUCKET_ID}/{OBJECT_KEY}"],
}
],
"version": "2.0",
}

req = models.CreateStorageCredentialsRequest()
req.SubAppId = SUB_APP_ID
req.Policy = urllib.parse.quote(json.dumps(policy))

# 3. 申请上传临时凭证
resp = vod_cli.CreateStorageCredentials(req)
creds = resp.Credentials
return Credential(
access_key_id=creds.AccessKeyId,
secret_access_key=creds.SecretAccessKey,
session_token=creds.SessionToken,
)
上传文件
常用语言初始化 S3 客户端并上传文件的代码实现如下所示:
GO
Java
C++
Python
// Package main
// 本示例基于 AWS SDK for Go v2 service/s3 v1.72.3 实现。
// AWS SDK for Go v2 service/s3 v1.73.0 及以后版本需禁用 S3 的默认完整性保护。
// 具体参考: https://github.com/aws/aws-sdk-go-v2/discussions/2960
package main

import (
"context"
"errors"
"fmt"
"log"
"net/url"
"os"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
smep "github.com/aws/smithy-go/endpoints"
"github.com/aws/smithy-go/logging"
)

type customEndpointResolverV2 struct {
}

// ResolveEndpoint 自定义接入点
func (r *customEndpointResolverV2) ResolveEndpoint(ctx context.Context,
params s3.EndpointParameters) (smep.Endpoint, error) {
if params.Bucket == nil || params.Region == nil {
return smep.Endpoint{}, errors.New("invalid endpoint param")
}
return smep.Endpoint{
URI: url.URL{
Scheme: "https",
Host: fmt.Sprintf(
"%d.vodpro-upload.com",
subAppId, // 子账号 Id
),
},
}, nil
}

func main() {
// 1. 获取临时凭证
cred, err := getCredential(context.Background())
if err != nil {
log.Fatalf("get credential fail: %v", err)
return
}

// 2. 使用临时凭证上传文件
// 2.1 创建 s3 client
s3cli := s3.New(s3.Options{
Credentials: credentials.NewStaticCredentialsProvider(
cred.AccessKeyId,
cred.SecretAccessKey,
cred.SessionToken,
),
EndpointResolverV2: new(customEndpointResolverV2), // 自定义接入点
UsePathStyle: true, // 应用级别域名需明确请求启用 path style 模式
Logger: logging.NewStandardLogger(os.Stdout), // 日志打印到标准输出流
ClientLogMode: aws.LogRequest | aws.LogResponse, // 打印请求头和响应头
Region: "auto", // 固定填写 "auto"
})

// 2.2 打开本地文件
file, err := os.Open("demo.mp4") // 本地文件路径
if err != nil {
log.Fatalf("failed to open file: %v", err)
return
}
defer file.Close()

// 2.3 上传文件到 S3
// 2.3.1 创建上传器
uploader := manager.NewUploader(s3cli, func(u *manager.Uploader) {
u.PartSize = 10 * 1024 * 1024 // 设置分片大小为 10MB
u.Concurrency = 5 // 设置并发上传的分片数
})
// 应用级别上传上传路径为 bucketId/fileKey
// S3 SDK Uploader 不支持 path style 模式,此处需拼接后作为上传 key
path, err := url.JoinPath(bucketId, fileKey)
if err != nil {
log.Fatalf("failed to join path: %v", err)
return
}
// 2.3.2 上传文件
result, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(bucketId), // Bucket 设置为点播专业版应用中的存储桶 ID
// 文件在存储桶中的路径
// 应用级别上传上传路径为 bucketId/fileKey
// S3 SDK Uploader 不支持 path style 模式,此处需拼接后作为上传 key
Key: aws.String(path),
Body: file,
})

if err != nil {
log.Fatalf("failed to upload file: %v", err)
return
}
log.Printf("etag: %s", *result.ETag) // 获取上传文件 etag
}
// 本示例基于 AWS SDK for Java v2 service/s3 v2.31.35 实现。
// AWS SDK for Java 2.30.0 及以后版本需禁用 S3 的默认完整性保护。
// 具体参考: https://github.com/aws/aws-sdk-java-v2/issues/5801
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.UploadFileRequest;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;

import java.io.File;
import java.net.URI;
import java.util.concurrent.CompletableFuture;

public class Main {
public static void main(String[] args) {
S3AsyncClient s3AsyncClient = null;
S3TransferManager transferManager = null;

try {
// 1. 获取临时凭证
CredentialHelper.Cred cred = CredentialHelper.getCredential();
System.out.println("获取临时凭证成功, AccessKeyId: " + cred.getAccessKeyId());

// 2. 创建会话凭证(包含临时 token)
AwsSessionCredentials sessionCredentials = AwsSessionCredentials.create(
cred.getAccessKeyId(), // 临时凭证 AccessKeyId
cred.getSecretAccessKey(), // 临时凭证 SecretAccessKey
cred.getSessionToken() // 临时凭证 SessionToken
);

long subAppId = 1234567890; // 云点播专业版应用 APPID
String bucketId = "auto"; // 云点播专业版应用存储桶的 ID,auto 表示自动就近选择存储桶
String filePath = "demo.mp4"; // 本地文件路径
String key = "upload/demo.mp4"; // 申请权限的存储文件 KEY,包含完整路径和文件名,例如 upload/demo.mp4
String endpointUrl = String.format("https://%d.vodpro-upload.com", subAppId); // 应用级别的预置域名端点

// 3. 使用自定义端点构建S3异步客户端
s3AsyncClient = S3AsyncClient.builder()
.credentialsProvider(StaticCredentialsProvider.create(sessionCredentials)) // 设置凭证提供者
.endpointOverride(URI.create(endpointUrl)) // 设置端点
.region(Region.of("auto")) // 固定填写 auto
.httpClient(NettyNioAsyncHttpClient.builder().build()) // 设置HTTP客户端
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(true) // 设置路径样式访问, 应用级别需明确使用 PathStyle 模式
.build())
.requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED)
.responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED)
.build();

// 4. 使用S3异步客户端创建S3TransferManager
transferManager = S3TransferManager.builder()
.s3Client(s3AsyncClient)
.build();

// 5. 准备上传文件
File fileToUpload = new File(filePath);
if (!fileToUpload.exists()) {
throw new RuntimeException("文件不存在: " + filePath);
}

// 6. 创建上传请求并添加传输监听器用于进度跟踪
UploadFileRequest uploadRequest = UploadFileRequest.builder()
.putObjectRequest(builder -> builder
.bucket(bucketId)
.key(key))
.addTransferListener(LoggingTransferListener.create())
.source(fileToUpload)
.build();

// 7. 开始上传
System.out.println("开始上传文件...");
CompletableFuture<Void> future = transferManager.uploadFile(uploadRequest)
.completionFuture()
.thenAccept(response -> {
System.out.println("上传成功! ETag: " + response.response().eTag());
});

// 8. 等待上传完成
future.join();

System.out.println("上传任务完成");
} catch (Exception e) {
System.err.println("上传失败: " + e.getMessage());
e.printStackTrace();
} finally {
// 资源释放
try {
if (transferManager != null) {
transferManager.close();
}
if (s3AsyncClient != null) {
s3AsyncClient.close();
}
System.out.println("资源已释放");
} catch (Exception e) {
System.err.println("关闭资源时发生错误: " + e.getMessage());
}
}
}
}
// 本示例基于 AWS SDK for C++ v1.11.560 实现。
// AWS SDK for C++ v1.11.486 及以后版本需禁用 S3 的默认完整性保护。
// 具体参考: https://github.com/aws/aws-sdk-cpp/issues/3253
#include <aws/core/Aws.h>
#include <aws/core/auth/AWSCredentials.h>
#include <aws/core/client/ClientConfiguration.h>
#include <aws/s3/S3Client.h>
#include <aws/transfer/TransferManager.h>
#include <iostream>
#include <string>

// 引用 get_cred.cpp 中定义的凭证结构体
struct Credential
{
std::string accessKeyId;
std::string secretAccessKey;
std::string sessionToken;
};

// 引用 get_cred.cpp 中定义的函数
extern Credential GetCredential();

int main()
{
// AWS SDK 初始化
Aws::SDKOptions options;
options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Info;
Aws::InitAPI(options);

{
// 获取临时凭证
std::cout << "Getting temporary credentials..." << std::endl;
Credential cred = GetCredential();
std::cout << "Successfully obtained temporary credentials, access key Id: " << cred.accessKeyId << std::endl;
// 定义常量
const uint64_t subAppId = 1234567890; // 云点播专业版应用 SubAppId
const Aws::String bucketId = "auto"; // 云点播专业版应用存储桶的 ID,auto 表示自动就近选择存储桶
const Aws::String keyName = "upload/demo.mp4"; // 文件在存储桶中的路径,包含完整路径和文件名,例如 upload/demo.mp4
const Aws::String filePath = "demo.mp4"; // 本地文件路径

// 配置客户端
Aws::Client::ClientConfiguration clientConfig;
clientConfig.region = "auto"; // 固定填写 auto
clientConfig.scheme = Aws::Http::Scheme::HTTPS; // 协议使用 HTTPS
clientConfig.enableEndpointDiscovery = false; // 禁用 endpoint discovery
clientConfig.verifySSL = true; // 启用 SSL 证书验证
clientConfig.endpointOverride = std::to_string(subAppId) + ".vodpro-upload.com"; // 应用级别预置域名
// 禁用完整性校验
clientConfig.checksumConfig.requestChecksumCalculation = Aws::Client::RequestChecksumCalculation::WHEN_REQUIRED;
clientConfig.checksumConfig.responseChecksumValidation = Aws::Client::ResponseChecksumValidation::WHEN_REQUIRED;

// 设置认证信息(使用临时凭证)
Aws::Auth::AWSCredentials credentials(
cred.accessKeyId.c_str(),
cred.secretAccessKey.c_str(),
cred.sessionToken.c_str());

// 创建 S3 客户端
auto s3Client = Aws::MakeShared<Aws::S3::S3Client>(
"S3Client",
credentials,
clientConfig,
Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, // 禁用 payload signing
false // 应用级别预置域名需明确使用 PathStyle 格式
);

// 创建线程池执行器
auto executor = Aws::MakeShared<Aws::Utils::Threading::PooledThreadExecutor>("executor", 10);

// 配置 TransferManager
Aws::Transfer::TransferManagerConfiguration transferConfig(executor.get());
transferConfig.s3Client = s3Client;

// 设置分片大小为 10MB
transferConfig.bufferSize = 10 * 1024 * 1024;

// 创建 TransferManager
auto transferManager = Aws::Transfer::TransferManager::Create(transferConfig);

std::cout << "Starting file upload: " << filePath << " to " << bucketId << "/" << keyName << std::endl;

// 执行上传
auto uploadHandle = transferManager->UploadFile(
filePath, // 本地文件路径
bucketId, // 存储桶ID
keyName, // 对象键(存储路径)
"application/octet-stream", // 内容类型
Aws::Map<Aws::String, Aws::String>() // 元数据
);

// 等待上传完成
uploadHandle->WaitUntilFinished();

// 检查上传状态
if (uploadHandle->GetStatus() == Aws::Transfer::TransferStatus::COMPLETED)
{
std::cout << "File upload completed successfully!" << std::endl;
}
else
{
auto lastError = uploadHandle->GetLastError();
std::cerr << "File upload failed: " << lastError.GetMessage() << std::endl;
}
}

// 清理SDK资源
Aws::ShutdownAPI(options);
return 0;
}
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
本示例基于 AWS SDK for Python v1.38.7 实现。
Boto3 的 AWS SDK for Python v1.36.0 及以后版本需禁用 S3 的默认完整性保护。
具体参考: https://github.com/boto/boto3/issues/4392
"""

import boto3
from botocore.config import Config
from botocore.exceptions import ClientError

from get_cred import get_credential, SUB_APP_ID, BUCKET_ID, OBJECT_KEY

# 常量定义
FILE_PATH = "demo.mp4"


try:
# 1. 获取临时凭证
cred = get_credential()

# 2. 创建 S3 客户端
s3_client = boto3.client(
"s3",
aws_access_key_id=cred.access_key_id, # 临时凭证的 AccessKeyId
aws_secret_access_key=cred.secret_access_key, # 临时凭证的 SecretAccessKey
aws_session_token=cred.session_token, # 临时凭证的 SessionToken
endpoint_url=f"https://{SUB_APP_ID}.vodpro-upload.com", # 应用级别预置上传域名
region_name="auto", # 固定填写 auto
config=Config(
s3={"addressing_style": "path"}, # 使用 path-style
request_checksum_calculation="when_required", # Boto3 的 AWS SDK for Python v1.36.0 及以后版本需禁用 S3 的默认完整性保护。
response_checksum_validation="when_required", # Boto3 的 AWS SDK for Python v1.36.0 及以后版本需禁用 S3 的默认完整性保护。
),
)

# 3. 上传文件
response = s3_client.upload_file(
Bucket=BUCKET_ID, # 填入点播专业版应用中的存储桶 ID
Key=OBJECT_KEY, # 文件在存储桶中的路径
Filename=FILE_PATH, # 本地文件路径
)
print(response)
except ClientError as e:
print(f"Error: {e}")
注意:
使用 AWS SDK 请确认数据完整性保护特性。
使用应用级别上传加速域名,必须使用 PathStyle 格式。

帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈