知晓程序注:很多小程序都为用户提供图片上传功能。这时候,使用一些「对象存储」云服务,也许是最快、最经济的选择。那么,小程序该如何使用这类服务呢?使用的时候,又应该注意哪些问题呢?知晓程序(微信号zxcx0101)今天分享的这篇文章,会以腾讯云的对象存储服务为例,教大家将小程序接入对象存储服务。

准备工作:要使用对象存储 API,需要先执行以下步骤:购买腾讯云对象存储(COS)服务;在腾讯云对象存储控制台里创建一个 Bucket;在控制台「个人 API 密钥」页面里,获取 AppID、SecretID、SecretKey 等内容;编写一个请求签名算法程序(或使用任何一种服务端 SDK);计算签名,调用 API 执行操作。所以,我们要做的准备工作有:进入腾讯云官网(www.qcloud.com),注册帐号;登录云对象存储服务(COS)控制台,开通 COS 服务,创建资源需要上传的 Bucket;在公众平台小程序后台中,配置相关域名信息(否则无法在小程序中发起对该域名的请求)。

这些配置过程这里就不做说明了,接下来主要介绍步骤 4 和 5。小程序上传图片到 COS 的流程图如下:在这个过程中我们需要实现的是,鉴权服务器返回签名的步骤,以及小程序处理图片的相关步骤。

使用对象存储服务 COS 时,可通过 RESTful API 对 COS 发起 HTTP 匿名请求或签名请求。对于签名请求,COS 服务器端将会进行对请求发起者的身份验证。HTTP 请求不携带任何身份标识和鉴权信息通过 RESTful API 进行 HTTP 请求操作。HTTP 请求时添加签名,COS服务器端收到消息后,进行身份验证,验证成功则可接受并执行请求否则将会返回错误信息并丢弃此请求。腾讯云 COS 对象存储基于密钥 HMAC(Hash Message Authentication Code)的自定义 HTTP 方案进行身份验证。在此例中,上传图片是一个签名请求需要进行签名验证。

以下是重构后的内容:

客户可以通过对 HTTP 请求进行签名,并将签名后的请求发送至腾讯云进行签名验证。具体流程如下图所示。我们可以使用 SDK 开发,只需大致了解这个流程即可。在 SDK 中已包含签名的实现,我们只需要调用 SDK 中的方法即可。

需要提供 SecretId 和 SecretKey。

2. 生成签名的接口

为了完成签名生成 API,我们需要先在文档中添加 API 的相关描述。以下是一个示例:

```yaml

/qc_cos/config: get: summary: 腾讯云配置 description: 腾讯云配置 tags: [Config] operationId: get_qc_cos_config parameters: - $ref: '#/parameters/AccessToken' - $ref: '#/parameters/qcos_path_in_query' responses: 200: schema: $ref: '#/definitions/QCOSConfig' default: description: Unexpected error schema: $ref: '#/definitions/Error' security: - OAuth2: [open]

```

这个接口要求登录才能调用。

使用以下命令生成签名:

```bash

swagger_py_codegen -s docs/v1.yml . -p apis -tlp sanic

```

腾讯云 COS v4 的 Python SDK 只支持 Python 2,而 sanic 需要 Python 3.5+。为了解决这个问题,我 fork 了一份添加了 Python 3 支持的版本,使用 Python 3 的开发者可以使用它。关注「知晓程序」微信公众号,回复「源码」,获取该框架下载地址。

以下是修改后的代码:

```python

from qcloud_cos import CosConfig

from qcloud_cos import CosS3Client

import sys

import logging

logging.basicConfig(level=logging.INFO, stream=sys.stdout)

# 请在本地填写你的 SecretId 和 SecretKey,然后替换为下面的内容

secret_id = 'your_secret_id'

secret_key = 'your_secret_key'

token = None

config = CosConfig(Region='region', SecretId=secret_id, SecretKey=secret_key, Token=token, appid='appid')

client = CosS3Client(config)

response = client.list_buckets()

print(str(response, encoding='utf-8'))

```

在小程序上传图片的代码如下:

1. 选择图片:

```javascript

wx.chooseImage({

count: 1, // 默认9

sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有

sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有

success: function (res) {

// 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片

var tempFilePaths = res.tempFilePaths;

wx.saveFile({

tempFilePath: tempFilePaths[0], // 要保存的文件路径

fileType: 'jpeg', // 要保存的文件类型,默认是jpg格式。注意:此后必须是jpg,否则会有问题,如黑框现象等。如果要保留原格式,不要修改此项。可参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#6.6%E5%A4%9A%E7%B1%BB%E5%8F%8D%E9%85%8D%E7%BD%AE%E6%A0%87%E5%8F%AF%E8%BF%9C%E4%B8%80%E4%B8%AA%26offset=%26limit=%26sort=create&fileId=0&fileName=xxx&content-type=image/jpeg', // 要保存的文件名,不填默认为 image/jpeg;base64 是二进制数据转成 base64 字符串的形式。

success: function (res) {

var data = res.data; // base64字符串转成Buffer对象的方法为wx.arrayBufferToBase64(res.data);

wx.uploadFile({

url: 'https://example.com/upload', // 微信小店、微信卡券等接口要求使用合法域名来请求接口资源。建议在公众平台后台配置合法域名后再进行开发。如果只是在小程序中开发,不需要做特殊处理。

fileName: 'filename', // 要上传文件的名字,不填会随机生成一串名字。注意:不能以中文命名!!!否则会报错!!!

filePath: data.buffer, // 在服务器端需要用到的数据缓冲区对象。相对于本机时间戳表示从何时开始计算的数据偏移量。一般不需要改动。如果要上传本地文件需要先把本地文件转成 ArrayBuffer 再传到服务器端。也可以设置 formData,将文件和其他参数一起提交给服务器。formData 参数中的键值对应与后端接收数据的键值对一一对应。

formData: {}, // 以 FormData 的形式发送叟表数据时使用。若为空对象则不发送任何字段。只支持二进制数据类型的参数。请尽量避免在此处传递文本字符串类型的参数。如非必要请不要添加任何字段。同时需要注意的是所有字段必须是小写字母开头且全部为小写字母或数字构成的字符串,即只能包含大小写字母、数字及短横线“-”。且只能由字母、数字及短横线“-”组成,不含其他字符。短横线不能连续出现两个以上。否则会导致请求失败或无法解析响应结果。请注意正确使用 formData 参数并确保服务器端能够解析该参数中的数据格式以正确处理请求体中的数据。如有疑问请参考 http://www.w3.org/TR/html5/forms.html#formdata-set-attribute-method

success: function (res) {

var data = res.data; // 从服务端获取到的返回数据,根据业务逻辑进行后续操作即可。具体返回值和使用方式见文档说明。如果上传过程中发生错误可以通过 res.errMsg 以及 res.errCode 属性获取相应的错误信息和错误码。详见 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#6.6%E5%A4%9A%E7%B1%BB%E5%8F%8D%E9%85%8D%E7%BD%AE&locale=zh_CN#wx1ec9d3b3a90c1c4f "success"事件描述

uploadToCos: function () { var that = this; // 选择上传的图片

wx.chooseImage({

sizeType: ['original', 'compressed'], // 图片类型

original: '原图', compressed: '压缩图',

success: function (res) { // 获取文件路径

var file = res.tempFiles[0]; console.log(file.size); // 获取文件名

var fileName = file.path.match(/(wxfile:\/\/)(.+)/)

fileName = fileName[2] // 获取到图片临时路径后,指定文件名

cosUpload(file.path, fileName, that); // 上传到cos

},

});

}

以下是重构后的代码:

```javascript

const config = require('../config.js'); // 先确定上传的 URL

const cosUrl = `https://${config.cos_region}.file.myqcloud.com/files/v2/${config.cos_appid}/${config.cos_bucket_name}${config.cos_dir_name}`; //填写自己的鉴权服务器地址

const cosSignatureUrl = config.host + '/v1/qc_cos/config?cos_path=' + config.cos_dir_name;

/**

* 上传方法

* filePath: 上传的文件路径

* fileName: 上传到cos后的文件名

* that: 小程序所在当前页面的对象

*/

function upload(filePath, fileName, that) {

const data = {}; // 鉴权获取签名

wx.request({ url: cosSignatureUrl, header: { Authorization: 'JWT' + ' ' + that.data.jwt.access_token }, success: (cosRes) => { // 获取签名

var signature = cosRes.data.sign; // 头部带上签名,上传文件至COS

var uploadTask = wx.uploadFile({

url: `${cosUrl}/${fileName}`, // 将url改为模板字符串格式,支持动态参数替换

filePath: filePath,

header: { 'Authorization': signature },

name: 'filecontent',

formData: { op: 'upload' },

success: (uploadRes) => { // 上传成功后的操作

var upload_res = JSON.parse(uploadRes.data);

var files = that.data.files;

files.push(upload_res.data.source_url);

that.setData({ upload_res: upload_res, files: files, test_image: upload_res.data.source_url });

},

fail: (e) => { console.log('e', e) },

onProgressUpdate: (res) => { // 将onProgressUpdate改为箭头函数形式,避免在回调函数中重复调用that.setData()方法

that.setData({ upload_progress: res.progress });

if (res.progress === 100) {

that.setData({ upload_progress: 0 });

}

}

}); // 上传进度条

} }); // return data; // 这行代码不需要了,因为upload函数没有明确的返回值。如果需要返回数据,可以在函数最后添加return语句。

```java

uploadTaskonProgressUpdate(new OnProgressListener() {

@Override

public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {

double progress = (100 * taskSnapshot.getBytesTransferred()) / taskSnapshot

.getTotalByteCount();

// updateUI(progress);

}

});

```