万物皆可Serverless系列文章
- 万物皆可Serverless之免费搭建自己的不限速大容量云盘(5TB)
- 万物皆可Serverless之使用云函数Timer触发器实现每天自动定时打卡
- 万物皆可Serverless之使用SCF COS快速开发全栈应用
- 万物皆可Serverless之使用SCF COS免费运营微信公众号
- 万物皆可Serverless之使用SCF快速部署验证码识别接口
- 万物皆可Serverless之Kaggle SCF端到端验证码识别从训练到部署
- 万物皆可Serverless之借助微信公众号简单管理用户激活码
- 万物皆可Serverless之使用SCF COS给未来写封信
- 万物皆可Serverless之在Flutter中快速接入腾讯云开发
- 万物皆可Serverless之在Flutter中写一个Dart原生腾讯云对象存储插件
- 万物皆可Serverless之我的Serverless之路
一、本文介绍
在上一篇文章中,我们尝试在Flutter中接入了腾讯云开发SDK
不过在有些应用场景下我们只需要用到腾讯云对象存储的能力,
比如将用户头像上传存储到自己的对象存储桶中,然后返回文件下载链接保存到本地数据库中,
这时候用云开发的话就有点高射炮打蚊子-->大材小用的感觉了。
所以这里我就带大家直接上手从头写一个Dart原生的腾讯云对象存储插件
废话少说,上图
注意,
这里我是直接在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(无服务器)
以上,逃~