1、阿里云目前未针对Flutter开发相关的集成功能,但是可以利用PostObject方式通过表单的形式来上传 ,PostObject方式上传图片官方文档:https://help.aliyun.com/document_detail/31988.html
注意:调用方式可以直接使用主用户的accesskeyId 、accessKeySecret ,不用单独写接口,但是存在安全问题,不推荐使用;推荐使用子用户STS方式,由后台接口提供临时用户的accesskeyId 、accessKeySecret 、securityToken(注意l临时用户上传是securityToken必传,传值时key为“x-oss-security-token”);参数中的signature是由accessKeySecret 经过一定的运算计算出来的;参数policy中的过期时间expiration可设置临时用户接口中返回的参数的有效期,建议在接口中计算好后直接返回该参数
2、相关工具类:
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'dart:math';
/*
* Oss工具类
* PostObject方式上传图片官方文档:https://help.aliyun.com/document_detail/31988.html
*/
class OssUtil {
// static String accesskeyId = '******';//Bucket 拥有者的accesskeyId 。
// static String accessKeySecret = '******';//Bucket 拥有者的accessKeySecret。
static String accesskeyId = '';//临时用户的AccessKeyId,通过后台接口动态获取
static String accessKeySecret = '';//临时用户的accessKeySecret,通过后台接口动态获取
static String stsToken="";//临时用户鉴权Token,临时用户认证时必传,通过后台接口动态获取
//验证文本域
static String _policyText =
'{"expiration": "2069-05-22T03:15:00.000Z","conditions": [["content-length-range", 0, 1048576000]]}';//UTC时间+8=北京时间
//进行utf8编码
static List<int> _policyText_utf8 = utf8.encode(_policyText);
//进行base64编码
static String policy= base64.encode(_policyText_utf8);
//再次进行utf8编码
static List<int> _policy_utf8 = utf8.encode(policy);
// 工厂模式
factory OssUtil() => _getInstance();
static OssUtil get instance => _getInstance();
static OssUtil _instance;
OssUtil._internal() {
}
static OssUtil _getInstance() {
if (_instance == null) {
_instance = new OssUtil._internal();
}
return _instance;
}
/*
*获取signature签名参数
*/
String getSignature(String _accessKeySecret){
//进行utf8 编码
List<int> _accessKeySecret_utf8 = utf8.encode(_accessKeySecret);
//通过hmac,使用sha1进行加密
List<int> signature_pre = new Hmac(sha1, _accessKeySecret_utf8).convert(_policy_utf8).bytes;
//最后一步,将上述所得进行base64 编码
String signature = base64.encode(signature_pre);
return signature;
}
/**
* 生成上传上传图片的名称 ,获得的格式:photo/20171027175940_oCiobK
* 可以定义上传的路径uploadPath(Oss中保存文件夹的名称)
* @param uploadPath 上传的路径 如:/photo
* @return photo/20171027175940_oCiobK
*/
String getImageUploadName(String uploadPath,String filePath) {
String imageMame = "";
var timestamp = new DateTime.now().millisecondsSinceEpoch;
imageMame =timestamp.toString()+"_"+getRandom(6);
if(uploadPath!=null&&uploadPath.isNotEmpty){
imageMame=uploadPath+"/"+imageMame;
}
String imageType=filePath?.substring(filePath?.lastIndexOf("."),filePath?.length);
return imageMame+imageType;
}
/*
* 生成固定长度的随机字符串
* */
String getRandom(int num) {
String alphabet = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM';
String left = '';
for (var i = 0; i < num; i++) {
// right = right + (min + (Random().nextInt(max - min))).toString();
left = left + alphabet[Random().nextInt(alphabet.length)];
}
return left;
}
/*
* 根据图片本地路径获取图片名称
* */
String getImageNameByPath(String filePath) {
return filePath?.substring(filePath?.lastIndexOf("/")+1,filePath?.length);
}
}
3、使用方式:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_picker/image_picker.dart';
import 'package:flutterdemo/page/util/CommonUtil.dart';
import 'package:flutterdemo/util/LogUtil.dart';
import 'package:flutterdemo/network/ApiService.dart';
import 'package:flutterdemo/model/OssTokenDataModel.dart';
import 'package:flutterdemo/util/oss/OssUtil.dart';
/*
*我的相册
*/
class MyPhotoPage extends StatefulWidget {
MyPhotoPage();
State<StatefulWidget> createState() => new _MyPhotoPageState();
}
class _MyPhotoPageState extends State<MyPhotoPage> {
String filePath;
_MyPhotoPageState();
@override
void initState() {
LogUtil.init(isDebug: true, tag: "****MyPhotoPage****");
}
@override
Widget build(BuildContext context) {
Widget _sectionAdd = Container(
child: IconButton(
iconSize: 60,
onPressed: () {
_selectImage();
},
icon: Icon(Icons.add)),
);
Widget _sectionImage = Container(
width: 1000,
height: 500,
child: Image.asset("$filePath"),
);
Widget _body = Container(
child: Column(
children: <Widget>[
filePath != null ? _sectionImage : Container(),
_sectionAdd,
],
),
);
return new MaterialApp(
theme: CommonUtil.getThemeData(),
home: new Scaffold(
appBar: new AppBar(
title: new Text('我的相册'),
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: onBack),
actions: <Widget>[new Container()],
),
body: _body,
),
);
}
/*
* 返回事件
*/
void onBack() {
Navigator.pop(context);
}
/*
* 读取本地图片路径
*/
Future _selectImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
LogUtil.i(image.path);
filePath = image.path ?? "";
_getOssToken();
});
}
}
/*
* 获取OssToken
*/
void _getOssToken() async {
await ApiService.getOssToken(context).then((data) {
LogUtil.i("----开始获取 getOssToken()----");
Map<String, dynamic> userMap = json.decode(data.toString());
OssTokenDataModel baseModel = OssTokenDataModel.fromMap(userMap);
if (baseModel != null && baseModel.data != null) {
//已经获取到OssToken
var _sign = baseModel.data;
LogUtil.i("getOssToken=" + baseModel.toString());
OssUtil.accesskeyId = _sign.accessKeyId;
OssUtil.accessKeySecret = _sign.accessKeySecret;
OssUtil.stsToken = _sign.securityToken;
} else {
Fluttertoast.showToast(msg: "Token获取异常");
}
}).then((data) {
LogUtil.i("----开始上传图片----");
_uploadImage();
});
}
void _uploadImage() async {
String uploadName = OssUtil.instance.getImageUploadName("photo", filePath);
await ApiService.uploadImage(context, uploadName, filePath).then((data) {
LogUtil.i("----上传图片完成----data:" + data?.toString());
if (data == null) {
Fluttertoast.showToast(msg: "图片上传成功");
}
}).then((data) {
//更新数据库中数据
});
}
}
4、接口方法文件:
import 'package:flutter/material.dart';
import 'package:flutterdemo/network/NetUtils.dart';
import 'package:flutterdemo/network/Api.dart';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutterdemo/util/oss/OssUtil.dart';
/*
* 接口请求方法
* 封装了传参方式及参数
*/
class ApiService {
/*
* 获取OSS Token
*/
static Future<dynamic> getOssToken(BuildContext context,
{cancelToken}) async {
return NetUtils.instance
.post(context, API.URL_TOKEN, data: null, cancelToken: cancelToken);
}
static Future<dynamic> uploadImage(
BuildContext context, String uploadName, String filePath,
{cancelToken}) async {
BaseOptions options = new BaseOptions();
options.responseType = ResponseType.plain; //必须,否则上传失败后aliyun返回的提示信息(非JSON格式)看不到
//创建一个formdata,作为dio的参数
File file = new File(filePath);
FormData data = new FormData.from({
'Filename': uploadName,//文件名,随意
'key': uploadName, //"可以填写文件夹名(对应于oss服务中的文件夹)/" + fileName
'policy': OssUtil.policy,
'OSSAccessKeyId':OssUtil.accesskeyId,//Bucket 拥有者的AccessKeyId。
'success_action_status': '200',//让服务端返回200,不然,默认会返回204
'signature': OssUtil.instance.getSignature(OssUtil.accessKeySecret),
'x-oss-security-token':OssUtil.stsToken,//临时用户授权时必须,需要携带后台返回的security-token
'file': new UploadFileInfo(file, OssUtil.instance.getImageNameByPath(filePath))//必须放在参数最后
});
return NetUtils.instance
.post(context, API.URL_UPLOAD_IMAGE_OSS, data: data, options: options);
}
}
5、接口地址文件:
import 'package:flutterdemo/network/UrlConstant.dart';
import 'package:flutterdemo/util/oss/OssConstant.dart';
/*
* 接口地址
* **/
class API {
//获取OSS Token
static final String URL_TOKEN= "****/getAliyunOssToken";
//获取OS上传图片服务器地址
static final String URL_UPLOAD_IMAGE_OSS= "http://bucketName.oss-cn-beijing.aliyuncs.com";
}
6、网络请求工具类封装:
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutterdemo/Config.dart';
import 'package:flutterdemo/network/UrlConstant.dart';
import 'package:flutterdemo/network/interceptor/TokenInterceptor.dart';
import 'package:flutterdemo/network/interceptor/ErrorInterceptor.dart';
import 'package:flutterdemo/network/interceptor/HeaderInterceptor.dart';
import 'package:flutterdemo/network/Code.dart';
import 'package:flutterdemo/page/util/NavigatorUtils.dart';
import 'package:flutterdemo/util/TextUtils.dart';
import 'package:flutterdemo/model/base/BaseModel.dart';
import 'package:common_utils/common_utils.dart';
/*
* Http请求配置工具类
*/
class NetUtils {
static BuildContext context = null;
BaseOptions _options;
Dio dio;
// 工厂模式
factory NetUtils() => _getInstance();
static NetUtils get instance => _getInstance();
static NetUtils _instance;
NetUtils._internal() {
//初始化
dio = getDio();
}
static NetUtils _getInstance() {
LogUtil.init(isDebug: false,tag: "****NetUtils****");
if (_instance == null) {
_instance = new NetUtils._internal();
}
return _instance;
}
/**
* 获取dio实例,不配置根url,完全使用传入的绝对路径url
*/
Dio getDio({String url, BaseOptions options}) {
if (options == null) {
if (TextUtils.isEmpty(url) ||
(!url.startsWith("http://") && url.startsWith("https://"))) {
_options = new BaseOptions(
baseUrl: UrlConstant.BASE_URL,
connectTimeout: 15000,
receiveTimeout: 15000,
contentType: ContentType.parse("application/x-www-form-urlencoded"),
);
} else {
_options = new BaseOptions(
connectTimeout: 15000,
receiveTimeout: 15000,
contentType: ContentType.parse("application/x-www-form-urlencoded"),
);
}
} else {
_options = options;
}
Dio _dio = new Dio(_options);
// _dio.interceptors.add(new TokenInterceptor());//待完善
// _dio.interceptors.add(new ErrorInterceptor(_dio));//待优化
_dio.interceptors.add(new HeaderInterceptor());
// _dio.interceptors.add(new LogInterceptor());
setProxy(_dio);
return _dio;
}
/**
* 设置代理
* */
void setProxy(Dio dio) {
//debug模式且为wifi网络时设置代理
if (Config.debug) {
//debug模式下设置代理
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
//设置代理
client.findProxy = (uri) {
return "PROXY " + UrlConstant.PROXY_URI;
};
};
}
}
post(BuildContext context, url, {data, BaseOptions options,cancelToken}) async {
LogUtil.v('启动post请求 url:$url ,body: $data');
Response response;
try {
if (url != null &&
(url.startsWith("http://") || url.startsWith("https://"))) {
dio = getDio(url: url,options: options);
}
response = await dio.post(url, data: data, cancelToken: cancelToken);
LogUtil.v('post请求成功 response.data:${response.toString()}');
} on DioError catch (e) {
if (CancelToken.isCancel(e)) {
LogUtil.v('post请求取消:' + e.message);
}
LogUtil.v('post请求发生错误:$e');
}
return response; //response.data.toString()这种方式不是标准json,不能使用
}
get(BuildContext context, url, {data,BaseOptions options,cancelToken}) async {
LogUtil.v('启动get请求 url:$url ,body: $data');
Response response;
try {
if (url != null &&
(url.startsWith("http://") || url.startsWith("https://"))) {
dio = getDio(url: url,options: options);
}
response =
await dio.get(url, queryParameters: data, cancelToken: cancelToken);
LogUtil.v('get请求成功 response.data:${response.toString()}');
} on DioError catch (e) {
if (CancelToken.isCancel(e)) {
LogUtil.v('get请求取消:' + e.message);
}
LogUtil.v('get请求发生错误:$e');
}
return response;
}
}