移动应用直传实践

2026-01-28   访问量:1007


简介

本文档介绍如何不依赖 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 文档:

iOS SDK

Android SDK


热门文章
更多>