小程序文件直传 腾讯云COS实践 采用uniapp + Node.js方案

1,006 阅读3分钟

本文正在参与技术专题征文Node.js进阶之路,点击查看详情

1、注册腾讯云并开通服务

腾讯云 console.cloud.tencent.com/cos

创建存储桶

将访问权限设置为:公有读私有写

创建完成后在桶管理> 安全管理 > 跨域访问CORS设置中添加规则

image.png

设置访问管理

访问管理 console.cloud.tencent.com/cam/capi

进入API密钥管理 新建密钥

image.png

2、完成STS服务获取临时密钥接口供前端访问 这里采用Node.JS后端实现(Egg.js)

搭建Egg.js项目

略 如果您不了解Egg.js 前往 www.eggjs.org/ 三分钟即可上手写WebAPI

获取临时密钥

获取临时密钥,可以通过提供的 COS STS SDK 方式获取,也可以直接请求 STS 云 API 的方式获取。

这里我们采用SDK方式来获取

官方示例 github.com/tencentyun/…

安装SDK npm i qcloud-cos-sts --save

在config文件中填写腾讯云配置

// app/config.default.js

// +++
// 腾讯云配置
config.qCloudConfig = {
    secretId: 'AKIDhDUVFiwtZ*******aXFgqjMjpfEXZKo', // 固定密钥
    secretKey: 'FDuJS********G2Zqrd9QSFm34qM', // 固定密钥
    proxy: '',
    host: 'cos.ap-shanghai.myqcloud.com', // 域名,非必须,默认为 sts.tencentcloudapi.com
    durationSeconds: 1800, // 密钥有效期
    // 放行判断相关参数
    bucket: 'note-125****098', // 换成你的 bucket
    region: 'ap-shanghai', // 换成 bucket 所在地区
    allowPrefix: '*', // 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径,例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用)
}
// +++

在controller中完成sts逻辑

// app/controller/v1/common.js
'use strict'

const Controller = require('egg').Controller
// 导入SDK
const STS = require('qcloud-cos-sts')

/**
 * @Controller 公共模块
 */

class CommonController extends Controller {
  /**
   * @summary 获取COS Token
   * @description 获取腾讯云存储临时访问token
   * @router get /api/v1/common/cosToken
   */
  async cosToken() {
    const { ctx } = this

    // 获取配置参数
    const { qCloudConfig } = ctx.app.config

    // 授权 getCredential
    const shortBucketName = qCloudConfig.bucket.substr(0, qCloudConfig.bucket.lastIndexOf('-'));
    const appId = qCloudConfig.bucket.substr(1 + qCloudConfig.bucket.lastIndexOf('-'));
    const policy = {
      version: '2.0',
      statement: [{
        action: [
          // 简单上传
          'name/cos:PutObject',
          'name/cos:PostObject',
          // 分片上传
          'name/cos:InitiateMultipartUpload',
          'name/cos:ListMultipartUploads',
          'name/cos:ListParts',
          'name/cos:UploadPart',
          'name/cos:CompleteMultipartUpload',
        ],
        effect: 'allow',
        principal: { qcs: ['*'] },
        resource: [
          'qcs::cos:' + qCloudConfig.region + ':uid/' + appId + ':prefix//' + appId + '/' + shortBucketName + '/' + qCloudConfig.allowPrefix,
        ],
      }],
    };

    try {
      const res = await STS.getCredential({
        secretId: qCloudConfig.secretId,
        secretKey: qCloudConfig.secretKey,
        proxy: qCloudConfig.proxy,
        durationSeconds: qCloudConfig.durationSeconds,
        region: qCloudConfig.region,
        policy,
      })
      console.log(res);
      ctx.helper.body.SUCCESS({ ctx, res })
    } catch (error) {
      console.error(error)
      return ctx.helper.body.INVALID_REQUEST({ ctx, code: 500, msg: 'COS授权失败' })
    }
  }
}

module.exports = CommonController

完成路由配置

// app/router.js

const { router, controller } = app
const { v1 } = controller

// +++
  router.get('/api/v1/common/cosToken', v1.common.cosToken)
// +++

访问接口 /api/v1/common/cosToken

image.png

3、小程序(uniapp)文件上传

参考腾讯云官方文档 cloud.tencent.com/document/pr…

image.png

吐槽官方示例代码 大量callback 回调地狱 /(ㄒoㄒ)/~~

打开uniapp项目

下载依赖

这里引用了 cos-auth.js,下载地址为 unpkg.com/cos-js-sdk-…

下载后放入 src/utils/cos-auth.js

上传代码示例

这里使用了uView 的 Upload 组件

代码实现

<!-- template部分 -->
<view class="file">
    <u-upload
      :fileList="fileList1"
      @afterRead="afterRead"
      @delete="deletePic"
      name="1"
      multiple
      :maxCount="9"
      :previewFullImage="true"
    ></u-upload>
</view>

script

import { fetchGetCosToken } from '@/api/note'
// 引入刚刚下载的文件
import CosAuth from '../../utils/cos-auth.min.js'

export default {
    data: () => ({
        // 文件列表
        fileList1: [],
        // token
        cosToken: {},
    }),
    onLoad(option) {
        // 获取临时token
        this.getCosToken()
    },
    methods: {
        // 获取腾讯云token
        async getCosToken() {
            const { data } = await fetchGetCosToken()
            this.cosToken = data
            console.log(data)
        },
        // 对更多字符编码的 url encode 格式
        camSafeUrlEncode(str) {
            return encodeURIComponent(str)
                .replace(/!/g, '%21')
                .replace(/'/g, '%27')
                .replace(/\(/g, '%28')
                .replace(/\)/g, '%29')
                .replace(/\*/g, '%2A')
        },
        // 计算签名
        getAuthorization() {
            return {
                XCosSecurityToken: this.cosToken.credentials.sessionToken,
                Authorization: CosAuth({
                    SecretId: this.cosToken.credentials.tmpSecretId,
                    SecretKey: this.cosToken.credentials.tmpSecretKey,
                    Method: 'POST',
                    Pathname: '/',
                }),
            }
        },
        // 删除图片
        deletePic(event) {
            this[`fileList${event.name}`].splice(event.index, 1)
        },
        // 新增图片
        async afterRead(event) {
            // 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
            let lists = [].concat(event.file)
            let fileListLen = this[`fileList${event.name}`].length
            lists.map((item) => {
                this[`fileList${event.name}`].push({
                    ...item,
                    status: 'uploading',
                    message: '上传中',
                })
            })
            for (let i = 0; i < lists.length; i++) {
                const result = await this.uploadFilePromise(lists[i].thumb)
                let item = this[`fileList${event.name}`][fileListLen]
                this[`fileList${event.name}`].splice(
                    fileListLen,
                    1,
                    Object.assign(item, {
                        status: 'success',
                        message: '',
                        url: result,
                    })
                )
                fileListLen++
            }
        },
        // 上传图片
        uploadFilePromise(filePath) {
            let Key = filePath.substr(filePath.lastIndexOf('/') + 1)
            let signPathname = '/'
            console.log(Key)
            // 获取签名的token
            const AuthData = this.getAuthorization()
            let prefix = 'https://note-12594*****.cos.ap-shanghai.myqcloud.com/'
            console.log(this.getAuthorization())
            return new Promise((resolve, reject) => {
                let a = uni.uploadFile({
                    url: prefix,
                    filePath: filePath,
                    name: 'file',
                    formData: {
                        key: Key,
                        success_action_status: 200,
                        Signature: AuthData.Authorization,
                        'x-cos-security-token': AuthData.XCosSecurityToken,
                        'Content-Type': '',
                    },
                    success: (res) => {
                        console.log(res)
                        const Encode = this.camSafeUrlEncode(Key)
                        let url = prefix + Encode.replace(/%2F/g, '/')
                        console.log(res.statusCode)
                        console.log(url)
                        if (/^2\d\d$/.test('' + res.statusCode)) {
                            console.log(url)
                        }
                        resolve(url)
                    },
                    fail: (res) => {
                        console.log(res)
                        reject(res)
                    },
                })
            })
        },
    }
}

上传文件测试

此时我们上传文件已经可以拿到上传成功后的URL了

image.png

接下来需要对uploadFilePromise方法进行封装以便多次复用。