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; } }