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

2020-04-22 18:13:07 浏览数 (2)

万物皆可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里调试直接在dart vm里调试

注意,

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

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

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

二、开始教程

第一步:创建Package

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

先创建一个名为 tencent_cloud_cos 的package

代码语言:javascript复制
flutter create --template=package tencent_cloud_cos

创建成功创建成功

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

第二步:导入依赖

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

代码语言:javascript复制
dependencies:
  flutter:
    sdk: flutter

  dio: ^3.0.9
  crypto: ^2.1.3

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

代码语言:javascript复制
flutter pub get

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

第三步:编写插件

Life is short, show me the code.

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

代码语言: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.myzijiebao.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$httpUrln$httpParametersn$httpHeadersn';
    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 = 'sha1n$keyTimen$httpStringDatan';
    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

示例程序示例程序

填上以下测试代码

代码语言:javascript复制
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.myzijiebao.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(无服务器)

以上,逃~

0 人点赞