前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【玩转腾讯云】万物皆可Serverless之在Flutter中写一个Dart原生腾讯云对象存储插件

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

原创
作者头像
乂乂又又
修改2020-04-22 18:13:07
3.3K5
修改2020-04-22 18:13:07
举报
文章被收录于专栏:万物皆可Serverless

万物皆可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.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

示例程序
示例程序

填上以下测试代码

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

以上,逃~

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 万物皆可Serverless系列文章
  • 一、本文介绍
  • 二、开始教程
    • 第一步:创建Package
      • 第二步:导入依赖
        • 第三步:编写插件
          • 第四步:代码示例
          • 三、文章最后
          相关产品与服务
          对象存储
          对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档