# 开发 \| 手把手，教你为小程序添加「上传图片」功能

- 作者：知晓程序
- 发布时间：2017-10-11 20:00
- 原文：https://www.ifanr.com/minapp/921200

![](https://s3.ifanr.com/wp-content/uploads/2017/07/minapp-dev-final-feature.jpg)

**知晓程序注：**

很多小程序都为用户提供图片上传功能。这时候，使用一些「对象存储」云服务，也许是最快、最经济的选择。

那么，小程序该如何使用这类服务呢？使用的时候，又应该注意哪些问题呢？

知晓程序（微信号 zxcx0101）今天分享的这篇文章，会以腾讯云的对象存储服务为例，教大家将小程序接入对象存储服务。

---

文 \| goodspeed

### 准备工作

上传图片，主要是将图片上传到腾讯云对象存储（COS）。

要使用对象存储 API，需要先执行以下步骤：

- 购买腾讯云对象存储（COS）服务。
- 在腾讯云 对象存储控制台 里创建一个 Bucket。
- 在控制台「个人 API 密钥」页面里，获取 AppID、SecretID、SecretKey 等内容
- 编写一个请求签名算法程序（或使用任何一种服务端 SDK）
- 计算签名，调用 API 执行操作

所以，我们要做的准备工作有：

- 进入腾讯云官网（www.qcloud.com），注册帐号。
- 登录云对象存储服务（COS）控制台，开通 COS 服务，创建资源需要上传的 Bucket。
- 在公众平台小程序后台中，配置相关域名信息（否则无法在小程序中发起对该域名的请求）。

![](https://s3.ifanr.com/wp-content/uploads/2017/10/txy-1.jpg)

这些配置过程这里就不做说明了，接下来主要介绍步骤 4 和 5。

小程序上传图片到 COS 的流程图，如下：

![](https://s3.ifanr.com/wp-content/uploads/2017/10/txy-2.jpeg)

在这个过程中我们需要实现的是，鉴权服务器返回签名的步骤，以及小程序处理图片的相关步骤。

### COS 鉴权服务

使用对象存储服务 COS 时，**可通过 RESTful API，对 COS 发起 HTTP 匿名请求或签名请求。**

对于签名请求，COS 服务器端将会进行对请求发起者的身份验证。

- 匿名请求：**HTTP 请求不携带任何身份标识和鉴权信息**，通过 RESTful API 进行 HTTP 请求操作。
- 签名请求：HTTP 请求时添加签名，**COS服务器端收到消息后，进行身份验证，验证成功则可接受并执行请求**，否则将会返回错误信息并丢弃此请求。

腾讯云 COS 对象存储，基于密钥 HMAC（Hash Message Authentication Code）的自定义 HTTP 方案，进行身份验证。

**在此例中，上传图片是一个签名请求**，需要进行签名验证。

#### 1. 签名流程

客户通过对 HTTP 请求进行签名，并将签名后的请求发送至腾讯云进行签名验证，具体流程如下图所示。

![](https://s3.ifanr.com/wp-content/uploads/2017/10/txy-3.jpg)

我们使用 SDK 开发，只需要大致了解这个流程就行。在 SDK 中已包含签名的实现，我们只需要调用 SDK 中的方法即可。

通过签名流程我们可以知道，签名需要 `SecretId` 和 `SecretKey`，**这两个信息不适合存放在客户端中**，这也是我们单独部署一个鉴权服务器的主要原因。

#### 2. 生成签名的接口

在之前的文章中，我介绍过，我在服务端使用 `sanic` 框架和 `swagger_py_codegen` 生成 Rest API。

为了完成签名生成 API，我们需要先在文档中添加 API 的相关描述。

```
/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]
```

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

文档定义完成之后，调用 `swagger_py_codegen <span class="hljs-_">-s</span> docs/v1.yml . -p apis -tlp sanic` 生成代码模板。API 代码实现如下：

```
from qcloud_cos.cos_auth import Auth
from qcloud_cos.cos_auth import Auth
async def get(self, request):
    auth = Auth(appid=Config.QCOS_APPID,
                secret_id=Config.QCOS_SECRET_ID,
                secret_key=Config.QCOS_SECRET_KEY)
    expired = time() + 3600 # 签名有效时间 3600 秒
    # 上传到 cos bucket 的目录
    dir_name = request.raw_args.get('cos_path', '/xrzeti')
    # 生成签名
    sign = auth.sign_more(Config.QCOS_BUCKET_NAME,
                          cos_path=dir_name,
                          expired=expired)
    return {"sign": sign}, 200
```

由于**腾讯云 COS v4 的 Python SDK 只支持 Python 2，而 sanic 需要 Python 3.5+**，所以，这里我 fork 出来一份添加了 Python 3 的支持，使用 Python 3 的开发者，可以使用它。

关注「知晓程序」微信公众号，回复「源码」，获取该框架下载地址。

### 在小程序上传图片

#### 1. 选择图片

`wx.chooseImage(OBJECT)` 从本地相册选择图片或使用相机拍照。

调用这个方法，**小程序会把选择的图片放到临时路径，在小程序本次启动期间可以正常使用**。

如需持久保存，需再调用 `wx.saveFile`，在小程序下次启动时才能访问。

在小程序中，**我们只能上传临时路径的文件**。

核心代码如下：

```
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]

      // 获取到图片临时路径后，指定文件名 上传到cos
      upload(file.path, fileName, that);
    }
  })
}
```

这里图片选择成功后，我们取原图上传到 COS。

#### 2. 上传图片

COS 上传图片的 URL，由 `cos_region`，`appid`，`bucket_name` 和 `cos_dir_name` 等参数拼接而成。

把以下字段配置成自己的 COS 相关信息（详情可看 API 文档）：

```
cosUrl = "https://" + REGION + ".file.myqcloud.com/files/v2/" + APPID + "/" + BUCKET_NAME + DIR_NAME;
```

- `REGION`：COS 上传的地区
- `APPID`：账号的appid
- `BUCKET_NAME`：COS Bucket的名字
- `DIR_NAME`：上传的文件目录

```
var config = require('../config.js');
// 先确定上传的 URL
var cosUrl = "https://" + config.cos_region + ".file.myqcloud.com/files/v2/" + config.cos_appid + "/" + config.cos_bucket_name + config.cos_dir_name;

//填写自己的鉴权服务器地址
var cosSignatureUrl = config.host + '/v1/qc_cos/config?cos_path=' + config.cos_dir_name;

/**
 * 上传方法
 * filePath: 上传的文件路径
 * fileName： 上传到cos后的文件名
 * that: 小程序所在当前页面的 object
 */
function upload(filePath, fileName, that) {
    var data;

    // 鉴权获取签名
    wx.request({
        url: cosSignatureUrl,
        header: {
            Authorization: 'JWT' + ' ' + that.data.jwt.access_token
        },
        success: function (cosRes) {
            // 获取签名
            var signature = cosRes.data.sign;

            // 头部带上签名，上传文件至COS
            var uploadTask = wx.uploadFile({
                url: cosUrl + '/' + fileName,
                filePath: filePath,
                header: {
                    'Authorization': signature
                },
                name: 'filecontent',
                formData: {
                    op: 'upload'
                },
                success: function (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: function (e) {
                    console.log('e', e)
                }
            });
            // 上传进度条
            uploadTask.onProgressUpdate((res) => {
                that.setData({
                    upload_progress: res.progress
                })
                if (res.progress === 100){
                    that.setData({
                        upload_progress: 0
                    })
                }
            })
        }
    })
    return data
}
```

**小程序提供了 `uploadTask.onProgressUpdate()` 来获取图片的上传进度**，所以在这里，我将图片的上传进度显示了出来。

最后，感谢女朋友支持。

原文地址：https://juejin.im/post/59a23fff518825244d204d9e

**关注「知晓程序」公众号 👇**

- 在微信后台回复「**开发**」，获取知晓程序开发全套经验。
- 在微信后台回复「**666**」，获取小程序开发 demo。

![](https://s3.ifanr.com/wp-content/uploads/2017/09/minapp-qrcode.gif)
