移动应用直传实践

最近更新时间:2023-05-31 10:12:41

我的收藏

简介

本文档介绍如何不依赖 SDK,用简单的代码,在APP端直传文件到对象存储(Cloud Object Storage,COS)的存储桶。
注意
本文档内容基于 XML 版本的 API

前提条件

1. 登录 COS 控制台 并创建存储桶,得到 Bucket(存储桶名称) 和 Region(地域名称),详情请参见 创建存储桶 文档。
2. 登录 访问管理控制台, 获取您的项目 SecretId 和 SecretKey。

实践步骤

整体步骤为:
1. 客户端调用服务端接口传入文件后缀,服务端根据后缀和时间戳等生成 cos key 以及直传的 url。
2. 服务端通过 sts sdk 获取临时密钥。
3. 服务端用获取到的临时密钥对直传 url 进行签名并返回 url、签名、token 等信息。
4. 客户端获取到步骤 3 中的信息后,直接发起 put 请求并携带签名、token 等 header 进行上传。
具体代码可参考:iOS 示例Android 示例

配置服务端实现签名

注意
正式部署时服务端请加一层您的网站本身的权限检验。
出于安全考虑,后端获取临时密钥后生成直传 url 并直接对其进行签名,可参考 Server 示例。 具体步骤为:
1. 通过sts sdk获取临时密钥。
2. 根据后缀名生成 cos key 以及直传 url相关。
3. 使用临时密钥对直传 url 进行签名并返回直传 url、签名、token 等信息
服务端配置步骤:
1. 配置好密钥、bucket以及region。
var config = {
// 获取腾讯云密钥,建议使用限定权限的子用户的密钥 https://console.cloud.tencent.com/cam/capi
secretId: process.env.COS_SECRET_ID,
secretKey: process.env.COS_SECRET_KEY,
// 密钥有效期
durationSeconds: 1800,
// 这里填写存储桶、地域,例如:test-1250000000、ap-guangzhou
bucket: process.env.PERSIST_BUCKET,
region: process.env.PERSIST_BUCKET_REGION,
// 限制的上传后缀
extWhiteList: ['jpg', 'jpeg', 'png', 'gif', 'bmp'],
};
2. 终端执行
npm install
3. 启动服务
node app.js
到这里服务端就启动成功了,可以开始客户端的流程。
如有其他语言或自行实现可以参考以下流程:
1. 向服务端获取临时密钥,服务端首先使用固定密钥 SecretId、SecretKey 向 STS 服务获取临时密钥,得到临时密钥 tmpSecretId、tmpSecretKey、sessionToken,详情请参考 临时密钥生成及使用指引cos-sts-sdk 文档。
2. 对直传 url 进行签名,生成 authorization。
3. 返回直传 url、authorization、sessionToken等信息,客户端上传文件时将得到的签名和 sessionToken,分别放到发请求时 header 的 authorization 和 x-cos-security-token 字段里。

客户端上传示例

iOS 客户端上传

1. 从服务端请求直传和签名信息。
这里的服务端地址为上一步启动的node服务。
// ext 为需要上传的文件后缀名,例如 jpg、png等
http://127.0.0.1:3000/sts-direct-sign?ext=jpg
iOS 请求代码
NSURL * stsURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:3000/sts-direct-sign?ext=%@",@"文件后缀"]];
NSMutableURLRequest * stsRequest = [NSMutableURLRequest requestWithURL:stsURL];
[stsRequest setHTTPMethod:@"GET"];
NSURLSession * session = [NSURLSession sharedSession];
NSURLSessionDataTask * task = [session dataTaskWithRequest:stsRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary * dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
NSDictionary * params = dic[@"data"] // 这里包含签名信息。
}];
[task resume];
2. 使用获取到的签名信息开始上传文件
// params 为上一步请求到的 dic[@"data"]
NSString * cosHost = [params objectForKey:@"cosHost"];
NSString * cosKey = [params objectForKey:@"cosKey"];
NSString * authorization = [params objectForKey:@"authorization"];
NSString * securityToken = [params objectForKey:@"securityToken"];

NSURL * stsURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@/%@",cosHost,cosKey]];
NSMutableURLRequest * stsRequest = [NSMutableURLRequest requestWithURL:stsURL];
[stsRequest setHTTPMethod:@"PUT"];
[stsRequest setAllHTTPHeaderFields:@{
@"Content-Type":@"application/octet-stream",
@"Content-Length":@(data.length).stringValue,// data 为待上传的文件NSData数据
@"Authorization":authorization,
@"x-cos-security-token":securityToken,
@"Host":cosHost
}];

NSURLSession * session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];

NSURLSessionDataTask * task = [session uploadTaskWithRequest:stsRequest fromData:self.body completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(error){
NSLog(@"上传失败");
}else{
NSLog(@"上传成功");
}
}];
[task resume];

Android 客户端上传

1. 从服务端请求直传和签名信息。
/**
* 获取直传的url和签名等
*
* @param ext 文件后缀 直传后端会根据后缀生成cos key
* @return 直传url和签名等
*/
private JSONObject getStsDirectSign(String ext) {
// 获取上传路径和签名
HttpURLConnection getConnection = null;
try {
//直传签名业务服务端url(正式环境 请替换成正式的直传签名业务url)
URL url = new URL("http://127.0.0.1:3000/sts-direct-sign?ext=" + ext);
getConnection = (HttpURLConnection) url.openConnection();
getConnection.setRequestMethod("GET");

int responseCode = getConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(getConnection.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
reader.close();
JSONObject jsonObject;
try {
jsonObject = new JSONObject(stringBuilder.toString());
if(jsonObject.has("code") && jsonObject.optInt("code") == 0){
return jsonObject.optJSONObject("data");
} else {
Log.e(TAG, String.format("getStsDirectSign error code: %d, error message: %s", jsonObject.optInt("code"), jsonObject.optString("message")));
}
} catch (JSONException e) {
e.printStackTrace();
}
} else {
Log.e(TAG, "getStsDirectSign HTTP error code: " + responseCode);
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "getStsDirectSign Error sending GET request: " + e.getMessage());
} finally {
if (getConnection != null) {
getConnection.disconnect();
}
}
return null;
}
2. 使用获取到的直传和签名信息开始上传文件
/**
* 上传文件
* @param filePath 本地文件路径
* @param listener 进度回调
*/
private void uploadFile(String filePath, final ProgressListener listener) {
File file = new File(filePath);
// 获取直传签名等数据
JSONObject directTransferData = getStsDirectSign(FilePathHelper.getFileExtension(file));
if (directTransferData == null) {
toastMessage("getStsDirectSign fail");
return;
}
String cosHost = directTransferData.optString("cosHost");
String cosKey = directTransferData.optString("cosKey");
String authorization = directTransferData.optString("authorization");
String securityToken = directTransferData.optString("securityToken");

//生成上传的url
URL url;
try {
url = new URL(String.format("https://%s/%s", cosHost, cosKey));
} catch (MalformedURLException e) {
e.printStackTrace();
return;
}

HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("PUT");
conn.setDoOutput(true);

// 设置请求头
conn.setRequestProperty("Content-Type", "application/octet-stream");
conn.setRequestProperty("Content-Length", String.valueOf(file.length()));
conn.setRequestProperty("Authorization", authorization);
conn.setRequestProperty("x-cos-security-token", securityToken);
conn.setRequestProperty("Host", cosHost);

// 获取输出流
OutputStream outputStream = conn.getOutputStream();

// 读取文件并上传
FileInputStream inputStream = new FileInputStream(file);
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
long totalBytesRead = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (listener != null) {
listener.onProgress(totalBytesRead, file.length());
}
}
// 关闭流
inputStream.close();
outputStream.flush();
outputStream.close();

// 获取响应码
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 上传成功
} else {
Log.e(TAG, "uploadFile HTTP error code: " + responseCode);
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "uploadFile Error sending PUT request: " + e.getMessage());
} finally {
if (conn != null) {
conn.disconnect();
}
}
}

相关文档

若您有更丰富的接口调用需求,请参考以下 客户端 SDK 文档: