专栏首页万物皆可Serverless【玩转腾讯云】万物皆可Serverless之在Flutter中写一个Dart原生腾讯云对象存储插件
原创

【玩转腾讯云】万物皆可Serverless之在Flutter中写一个Dart原生腾讯云对象存储插件

万物皆可Serverless系列文章

  1. 万物皆可Serverless之免费搭建自己的不限速大容量云盘(5TB)
  2. 万物皆可Serverless之使用云函数Timer触发器实现每天自动定时打卡
  3. 万物皆可Serverless之使用SCF+COS快速开发全栈应用
  4. 万物皆可Serverless之使用SCF+COS免费运营微信公众号
  5. 万物皆可Serverless之使用SCF快速部署验证码识别接口
  6. 万物皆可Serverless之Kaggle+SCF端到端验证码识别从训练到部署
  7. 万物皆可Serverless之借助微信公众号简单管理用户激活码
  8. 万物皆可Serverless之使用SCF+COS给未来写封信
  9. 万物皆可Serverless之在Flutter中快速接入腾讯云开发
  10. 万物皆可Serverless之在Flutter中写一个Dart原生腾讯云对象存储插件
  11. 万物皆可Serverless之我的Serverless之路

一、本文介绍

在上一篇文章中,我们尝试在Flutter中接入了腾讯云开发SDK

不过在有些应用场景下我们只需要用到腾讯云对象存储的能力,

比如将用户头像上传存储到自己的对象存储桶中,然后返回文件下载链接保存到本地数据库中,

这时候用云开发的话就有点高射炮打蚊子-->大材小用的感觉了。

所以这里我就带大家直接上手从头写一个Dart原生的腾讯云对象存储插件

废话少说,上图

直接在dart vm里调试

注意,

这里我是直接在windows本地的dart vm里运行的示例代码哈,

并不需要连接手机或者设备虚拟机去调试运行

因为这是Dart原生应用,放到哪里都可以运行的奥~

二、开始教程

第一步:创建Package

我们根据Flutter官方文档 https://flutter.dev/docs/development/packages-and-plugins/developing-packages

先创建一个名为 tencent_cloud_cos 的package

flutter create --template=package tencent_cloud_cos

创建成功

创建完之后,你的package目录应该是和上图一样的,下面我们就来编写插件

第二步:导入依赖

打开项目根目录下的pubspec.yaml配置文件,添加必要依赖

dependencies:
  flutter:
    sdk: flutter

  dio: ^3.0.9
  crypto: ^2.1.3

这里我们仅添加了dio和crypto两个dart原生依赖库,分别用来进行http请求和请求的加密签名工作

flutter pub get

当然,配置好依赖之后不要忘记下载安装一下依赖

第三步:编写插件

Life is short, show me the code.

打开lib/tencent_cloud_cos.dart文件,修改代码如下

// @author = WJG.
// @email = idootop@163.com
// @date = 2020-04-19

// @dart = 2.7

library tencent_cloud_cos;


import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:crypto/crypto.dart';

/// 腾讯云对象存储工具类
/// 使用腾讯云secret_id,secret_key和存储桶地址来初始化
///
///   ```dart
///    String secret_id='xxxxxxx';
///    String secret_key='xxxxxxx';
///    String bucket_host='https://xxxxxx.cos.xxxxx.myqcloud.com';
///    Cos cos = Cos(secret_id, secret_key, bucket_host);
///   ```
/// 上传/更新文件
///   ```dart
///    String imgUrl = await cos.upload('/example.jpg', File('example.jpg').readAsBytesSync());
///   ```
/// 下载文件
///   ```dart
///    bool success = await cos.download(imgUrl, 'download/example.jpg');
///   ```
/// 删除文件
///   ```dart
///    bool success = await cos.delete('/example.jpg');
///   ```
class Cos {
  Dio dio = Dio();
  String id;
  String key;
  String host;

  Cos(this.id, this.key, this.host);

  /// 上传文件成功后返回文件下载链接
  ///
  /// `path` : 存储桶文件存放路径
  ///
  /// `bytes` : 待上传文件二进制数组
  ///
  /// `params` : 请求参数
  ///
  /// `headers` : 请求头部
  ///
  /// `progress` : 上传进度回调函数,示例
  ///   ```dart
  ///   progress(int count, int total) {
  ///     double progress = (count / total) * 100;
  ///     if (progress % 5 == 0) print('上传进度---> ${progress.round()}%');
  ///   }
  ///  ```
  Future<String> upload(String path, List<int> bytes,
      {Map<String, String> params,
      Map<String, String> headers,
      Function(int, int) progress}) async {
    String url = host + path;
    params = params ?? Map<String, String>();
    Options options = Options();
    options.headers = headers ?? Map<String, String>();
    options.headers['content-length'] =
        bytes.length.toString(); // 设置content-length,否则无法监听文件上传进度
    //对put上传请求签名
    options.headers['Authorization'] =
        sign('put', path, headers: options.headers, params: params);
    try {
      Response response = await dio.put(url,
          data: Stream.fromIterable(bytes.map((e) => [e])), //bytes转为Stream
          onSendProgress: progress ??
              (int count, int total) {
                double progress = (count / total) * 100;
                if (progress % 5 == 0) print('上传进度---> ${progress.round()}%');
              },
          queryParameters: params,
          options: options);
      return response.statusCode == 200 ? url : '';
    } on DioError catch (e) {
      print('Error:' + e.message);
      return '';
    }
  }

  /// 删除在线文件
  ///
  /// `path` : 存储桶文件存放路径
  ///
  /// `params` : 请求参数
  ///
  /// `headers` : 请求头部
  ///
  Future<bool> delete(String path,
      {Map<String, String> params, Map<String, String> headers}) async {
    String url = host + path;
    params = params ?? Map<String, String>();
    Options options = Options();
    options.headers = headers ?? Map<String, String>();
    //对请求签名
    options.headers['Authorization'] =
        sign('DELETE', path, headers: options.headers, params: params);
    try {
      Response response =
          await dio.delete(url, queryParameters: params, options: options);
      return response.statusCode == 204 ? true : false;
    } on DioError catch (e) {
      print('Error:' + e.message);
      return false;
    }
  }

  /// 下载文件
  ///
  /// `urlPath` : 存储桶文件存放路径
  ///
  /// `savePath` : 文件保存路径
  ///
  /// `progress` : 下载进度回调函数,示例
  ///   ```dart
  ///   progress(int count, int total) {
  ///     double progress = (count / total) * 100;
  ///     if (progress % 5 == 0) print('下载进度---> ${progress.round()}%');
  ///   }
  ///  ```
  Future<bool> download(String urlPath, String savePath,
      {Function(int, int) progress}) async {
    try {
      await dio.download(urlPath, savePath,
          options: Options(receiveTimeout: 0),
          onReceiveProgress: progress ??
              (int count, int total) {
                double progress = (count / total) * 100;
                if (progress % 5 == 0) print('下载进度---> ${progress.round()}%');
              });
      return true;
    } on DioError catch (e) {
      print('Error:' + e.message);
      return false;
    }
  }

  /// 对http请求进行签名,返回Authorization签名字符串
  ///
  /// `httpMethod` : 请求方法
  ///
  /// `httpUrl` : 请求地址
  ///
  /// `params` : 请求参数
  ///
  /// `headers` : 请求头部
  ///
  String sign(String httpMethod, String httpUrl,
      {Map<String, String> headers,
      Map<String, String> params,
      int expire = 10}) {
    headers = headers ?? Map();
    params = params ?? Map();
    headers = headers.map((key, value) => MapEntry(key.toLowerCase(), value));
    params = params.map((key, value) => MapEntry(key.toLowerCase(), value));
    List<String> headerKeys = headers.keys.toList();
    headerKeys.sort();
    String headerList = headerKeys.join(';');
    String httpHeaders = headerKeys
        .map((item) => '$item=${Uri.encodeFull(headers[item])}')
        .join('&');
    List<String> paramKeys = params.keys.toList();
    paramKeys.sort();
    String urlParamList = paramKeys.join(';');
    String httpParameters = paramKeys
        .map((item) => '$item=${Uri.encodeFull(params[item])}')
        .join('&');
    String httpString =
        '${httpMethod.toLowerCase()}\n$httpUrl\n$httpParameters\n$httpHeaders\n';
    String httpStringData = sha1.convert(utf8.encode(httpString)).toString();
    int timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
    String keyTime = '$timestamp;${timestamp + expire}';
    String signKey =
        Hmac(sha1, utf8.encode(key)).convert(utf8.encode(keyTime)).toString();
    String stringToSign = 'sha1\n$keyTime\n$httpStringData\n';
    String signature = Hmac(sha1, utf8.encode(signKey))
        .convert(utf8.encode(stringToSign))
        .toString();
    return 'q-sign-algorithm=sha1&q-ak=$id&q-sign-time=$keyTime&q-key-time=$keyTime&q-header-list=$headerList&q-url-param-list=$urlParamList&q-signature=$signature';
  }
}

这里我就不再详细解释了,代码里都写得很清楚

请求签名

请求签名过程可参考腾讯云官方文档,地址 https://cloud.tencent.com/document/product/436/7778

第四步:代码示例

在项目根目录创建一个bin目录,然后在里面新建一个main.dart

示例程序

填上以下测试代码

import 'dart:io';
import '../lib/tencent_cloud_cos.dart';

main() async {
  String secret_id = 'xxxxxxxxxx'; //你的腾讯云secret_id
  String secret_key = 'xxxxxxxxxxxxxxxxx'; //你的腾讯云secret_key
  String bucket_host = 'https://xxxxxx-6666666.cos.ap-chongqing.myqcloud.com'; //你的对象存储桶访问域名
  Cos cos = Cos(secret_id, secret_key, bucket_host);
  String imgUrl = await cos.upload('/example.jpg', File('example.jpg').readAsBytesSync());
  await cos.download(imgUrl, 'example2.jpg');
  await cos.delete('/example.jpg');
}

然后按F5调试运行一下吧,没啥意外你就可以看到文章一开始那张图了

测试成功

三、文章最后

哈?这也算Serverless?

你可能会疑问,这不是介绍腾讯云对象存储吗,和serverless有啥关系~

哈哈,我只能说cos也是serverless的一种表现形式,

只要是不需要自己购买服务器运行的服务,大体都可以称之为serverless(无服务器)

以上,逃~

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【玩转腾讯云】万物皆可Serverless之使用SCF+COS免费运营微信公众号

    在上一篇《万物皆可Serverless之使用SCF+COS快速开发全栈应用》教程中,

    乂乂又又
  • 【玩转腾讯云】万物皆可Serverless之借助微信公众号简单管理用户激活码

    就可以添加并回复一个指定有效期的会员激活码,实现了在微信公众号简单管理用户激活码的需求

    乂乂又又
  • 【玩转腾讯云】万物皆可Serverless之使用SCF+COS快速开发全栈应用

    直到后来我接触到腾讯云无服务器云函数,让前端可以快速获得后端的能力同时,一并解决了前端数据请求跨域的问题。

    乂乂又又
  • JMail接收发送邮件使用参考

    用户2135432
  • Android版-支付宝APP支付

    补充(20170513) 支付宝APP支付可以使用沙箱环境测试。如需开启测试模式只需要在OnCreate中添加如下代码。沙箱环境测试APP支付中请使用沙箱版钱...

    Javen
  • orika实现自定义转换

    orika是我比较喜欢的一个属性复制框架,性能高,也用得比较爽。 但在实际项目中,复制时,不一定成功。 如现在我有一个项目,属性名称一样,类型不一样,就需要...

    星痕
  • Android 8.0(Android O) AccountManager 行为变更

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/...

    用户2965908
  • java中两个map比较

    ydymz
  • 神器 | 教你使用 JAVA 开发 类似【百度翻译】功能

    码神联盟
  • bootstrap fileinput 使用记录

    多出个上传按钮,图片也会多几个按钮,我选择了删除和放大图片的按钮,还可以显示图片单独上传按钮,这里我把它去掉了,统一在下方点击上传时,全部上传。==这里重点说下...

    老梁

扫码关注云+社区

领取腾讯云代金券