Flutter学习之阿里云Oss图片上传功能开发

1 篇文章 0 订阅

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值