Flutter中使用minio_new库

前言


在移动开发中,我们常常会遇到需要在App中处理文件上传和下载的需求。Minio是一个开源的对象存储服务,它兼容Amazon S3云存储服务接口,可以用于存储大规模非结构化的数据。

开始之前


在pubspec.yaml文件中添加minio_new库的依赖:

dependencies:
  minio_new: ^1.0.2

运行flutter pub get命令来获取依赖。可去pub上看 minio_new 最新版本。

初始化Minio客户端


需要先创建一个Minio客户端的实例。这个实例需要配置Minio服务器的连接信息,包括服务器的URL、端口号、访问密钥和密钥等。

var minio = Minio(
  endPoint: 'your-minio-server.com',
  port: 9000,
  useSSL: false,
  accessKey: 'your-access-key',
  secretKey: 'your-secret-key',
);

参数介绍:
useSSL:指定是否使用 SSL 连接。如果设置为 true,则使用 HTTPS 协议进行连接;如果设置为 false,则使用 HTTP 协议。
endPoint:指定 MinIO 服务器的终端节点(Endpoint)。这是 MinIO 服务器的主机名或 IP 地址。
port:指定连接 MinIO 服务器的端口号。
accessKey:指定用于身份验证的 MinIO 服务器的访问密钥。这是访问 MinIO 存储桶和对象所需的身份验证凭据之一,就是账号。
secretKey:指定用于身份验证的 MinIO 服务器的秘密密钥。与访问密钥一同用于身份验证,就是密码。

创建桶(Bucket)


在Minio中,桶(Bucket)是一种用于组织和存储对象的容器。类似于文件系统中的文件夹,桶在Minio中用于对对象进行逻辑分组和管理。每个桶都具有唯一的名称,并且可以在Minio服务器上创建多个桶。

桶的命名规则:只能包含小写字母、数字和连字符(-),并且长度必须在3到63个字符之间。桶的名称在Minio服务器上必须是唯一的。

 Future<void> createBucket(String bucketName) {
    minio.makeBucket(bucketName);

    //设置桶的公用权限,这样外界才能通过链接访问
    return minio.setBucketPolicy(bucketName, {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "PublicRead",
          //一个可选参数,表示这个策略的 ID,可以随意填写。
          "Effect": "Allow",
          //表示策略的效果,如果希望所有人都可以读取,那么这里就填写 'Allow'。
          "Principal": "*",
          //表示策略的主体,如果希望所有人都可以读取,那么这里就填写 '*'。
          "Action": ["s3:GetObject"],
          //一个数组,表示允许的操作,如果希望所有人都可以读取,那么就填写 ['s3:GetObject']。
          "Resource": ["arn:aws:s3:::$bucketName/*"]
          //一个数组,表示策略的资源,如果希望所有人都可以读取桶中的所有对象,那么就填写 ['arn:aws:s3:::your_bucket/*']。
        }
      ]
    });
  }

因为无论是上传还是下载文件都是基于桶进行操作的,所以初始化之后,在上传文件之前需要先创建桶,可以通过minio.bucketExists事先来判断桶是否存在。

如果不设置桶的权限的话,也就是不调用上面minio.setBucketPolicy方法,默认创建的桶是私有的,外界不能通过链接访问相关文件,出了调用minio.setBucketPolicy设置权限外,也可以在Minio后台设置桶的权限,如下图:
在这里插入图片描述

上传文件


 ///上传文件
  Future<String> uploadFile(String filename, String filePath) async {
    minio.fPutObject(bucketName, filename, filePath);

    //返回上传文件的完整访问路径
    return getUrl(filename);
  }

bucketName:要上传到哪个桶就写哪个桶名。

filename: 文件名,如:a.png。

filePath: 要上传文件的路径。

下载文件同理。

完整代码


minio.dart

import 'dart:async';
import 'dart:io';

import 'package:ecology/utils/log_util.dart';
import 'package:ecology/utils/toast.dart';
import 'package:minio_new/io.dart';
import 'package:minio_new/minio.dart';
import 'package:minio_new/models.dart';
import 'package:path/path.dart' show dirname;
import 'package:path_provider/path_provider.dart';

// ignore: unused_import
import 'package:rxdart/rxdart.dart';

class Prefix {
  bool isPrefix;
  String key;
  String prefix; //使用前缀可以帮助你更好地组织和管理对象,避免冲突和重复,并方便批量操作,不使用传''

  Prefix({required this.key, required this.prefix, required this.isPrefix});
}

var _minio;

Future<Minio> _resetMinio() async {
  //固定配置-换成你实际的
  bool useSSl = false;
  String endPoint = 'red.xxx.com';
  int port = 9000;
  String accessKey = 'xxx';
  String secretKey = 'xxx';

  try {
    _minio = Minio(
      useSSL: useSSl,
      endPoint: endPoint,
      port: port,
      accessKey: accessKey,
      secretKey: secretKey,
      region: 'cn-north-1',
    );

  } catch (err) {
    XToast.show(err.toString());
    return Future.error(err);
  }
  return _minio;
}

class MinioController {
  late Minio minio;
  String bucketName;
  String prefix;

  static resetMinio() async {
    await _resetMinio();
  }



  /// maximum object size (5TB)
  final maxObjectSize = 5 * 1024 * 1024 * 1024 * 1024;

  ///传入唯一桶名,自动初始化桶
  MinioController({required this.bucketName,  this.prefix = ''}) {
    if (_minio is Minio) {
      minio = _minio;

      //初始化桶-由已有用户切换为新用户的情况下
      buckerExists(bucketName).then((exists) {
        if(!exists) {
          createBucket(bucketName);
        }
      });
    } else {
      _resetMinio().then((_) {
        minio = _;

        //初始化桶
        buckerExists(bucketName).then((exists) {
          if(!exists) {
            createBucket(bucketName);
          }
        });
      });
    }
  }

  ///用于列出存储桶中未完成的分块上传任务。这个函数允许你获取所有处于未完成状态的分块上传任务的信息,以便你可以对其进行管理或继续上传。
  Future<List<IncompleteUpload>> listIncompleteUploads(
      {String? bucketName}) async {
    final list =
        minio.listIncompleteUploads(bucketName ?? this.bucketName, '').toList();
    return list;
  }

  ///获取桶对象
  ///用于获取指定桶中的对象列表,并返回一个包含前缀列表和对象列表的Map
  Future<Map<dynamic, dynamic>> getBucketObjects(String prefix) async {
    //listObjectsV2:列出指定桶中的对象。它返回一个 Stream 对象,该对象会按需逐个返回对象信息。
    final objects =
        minio.listObjectsV2(bucketName, prefix: prefix, recursive: false);

    final map = {};

    await for (var obj in objects) {
      final prefixs = obj.prefixes.map((e) {
        final index = e.lastIndexOf('/') + 1;
        final prefix = e.substring(0, index);
        final key = e;
        return Prefix(key: key, prefix: prefix, isPrefix: true);
      }).toList();

      map['prefixes'] = prefixs;
      map['objests'] = obj.objects;
    }

    return map;
  }

  ///获取桶列表
  Future<List<Bucket>> getListBuckets() async {
    return minio.listBuckets();
  }

  ///桶是否存在
  Future<bool> buckerExists(String bucket) async {
    return minio.bucketExists(bucket);
  }

  ///下载文件
  Future<void> downloadFile(filename) async {
    final dir = await getExternalStorageDirectory();
    minio
        .fGetObject(
            bucketName, prefix + filename, '${dir?.path}/${prefix + filename}')
        .then((value) {});
  }

  ///上传文件
  Future<String> uploadFile(String filename, String filePath) async {
    minio.fPutObject(bucketName, filename, filePath);

    //返回上传文件的完整访问路径
    return getUrl(filename);
  }

  ///批量上传文件
  Future<void> uploadFiles(List<String> filepaths, String bucketName) async {
    for (String filepath in filepaths) {
      String filename = filepath.split('/').last;
      await minio.fPutObject(bucketName, filename, filepath,);
    }
  }

  String getUrl(String filename) {
    return 'http://${minio.endPoint}:${minio.port}/$bucketName/$filename';
  }

  ///用于生成一个预签名的 URL,该 URL 允许在一定时间内以有限的权限直接访问 MinIO 存储桶中的对象
  Future<String> presignedGetObject(String filename, {int? expires}) {
    return minio.presignedGetObject(bucketName, filename, expires: expires);
  }

  ///获取一个文件一天的访问链接
  Future<String> getPreviewUrl(String filename) {
    return presignedGetObject(filename, expires: 60 * 60 * 24);
  }

  /// 可多删除和单删除
  Future<void> removeFiles(List<String> filenames) {
    return minio.removeObjects(bucketName, filenames);
  }

  ///创建桶
  Future<void> createBucket(String bucketName) {
    minio.makeBucket(bucketName);

    //设置桶的公用权限,这样外界才能通过链接访问
    return minio.setBucketPolicy(bucketName, {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "PublicRead",
          //一个可选参数,表示这个策略的 ID,可以随意填写。
          "Effect": "Allow",
          //表示策略的效果,如果希望所有人都可以读取,那么这里就填写 'Allow'。
          "Principal": "*",
          //表示策略的主体,如果希望所有人都可以读取,那么这里就填写 '*'。
          "Action": ["s3:GetObject"],
          //一个数组,表示允许的操作,如果希望所有人都可以读取,那么就填写 ['s3:GetObject']。
          "Resource": ["arn:aws:s3:::$bucketName/*"]
          //一个数组,表示策略的资源,如果希望所有人都可以读取桶中的所有对象,那么就填写 ['arn:aws:s3:::your_bucket/*']。
        }
      ]
    });
  }

  ///移除桶
  Future<void> removeBucket(String bucketName) {
    return minio.removeBucket(bucketName);
  }

  ///用于获取 MinIO 存储桶中对象的部分内容,即获取对象的部分数据。这个函数可以用于实现断点续传、分片下载或其他需要获取对象部分内容的场景。
  Future<dynamic> getPartialObject(
      String bucketName, String filename, String filePath,
      {required void Function(int downloadSize, int? fileSize) onListen,
      required void Function(int downloadSize, int? fileSize) onCompleted,
      required void Function(StreamSubscription<List<int>> subscription)
          onStart}) async {
    final stat = await this.minio.statObject(bucketName, filename);

    final dir = dirname(filePath);
    await Directory(dir).create(recursive: true);

    final partFileName = '$filePath.${stat.etag}.part.minio';
    final partFile = File(partFileName);
    IOSink partFileStream;
    var offset = 0;

    final rename = () => partFile.rename(filePath);

    if (await partFile.exists()) {
      final localStat = await partFile.stat();
      if (stat.size == localStat.size) return rename();
      offset = localStat.size;
      partFileStream = partFile.openWrite(mode: FileMode.append);
    } else {
      partFileStream = partFile.openWrite(mode: FileMode.write);
    }

    final dataStream =
        (await minio.getPartialObject(bucketName, filename, offset))
            .asBroadcastStream(onListen: (sub) {
      if (onStart != null) {
        onStart(sub);
      }
    });

    Future.delayed(Duration.zero).then((_) {
      final listen = dataStream.listen((data) {
        if (onListen != null) {
          onListen(partFile.statSync().size, stat.size);
        }
      });
      listen.onDone(() {
        if (onListen != null) {
          onListen(partFile.statSync().size, stat.size);
        }
        listen.cancel();
      });
    });

    await dataStream.pipe(partFileStream);

    if (onCompleted != null) {
      onCompleted(partFile.statSync().size, stat.size);
    }

    final localStat = await partFile.stat();
    if (localStat.size != stat.size) {
      throw MinioError('Size mismatch between downloaded file and the object');
    }
    return rename();
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/332350.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023企业怎样抵御经济的不确定性?

在日益不确定的经济环境&#xff0c;继续业务照旧可能会超过陷入逆风的风险。要加入真正有复原力的企业行列&#xff0c;实现整个经济周期的增长&#xff0c;2023是时候发力业务创新&#xff1a;优化选择新的创新组合&#xff0c;洞察并发现新的创新机会&#xff0c;并发展新的…

Android Launcher3各启动场景源码分析

文章目录 一、概述二、开机启动Launcher2.1、开机启动Launcher流程图2.2、开机启动流程源码分析 三、短压Home键启动Launcher3.1、短压Home键启动Launcher流程图3.2、短压Home键启动Launcher源码分析 四、Launcher异常崩溃后的自启动4.1、Launcher异常崩溃后的自启动流程图4.2、…

C语言总结十三:程序环境和预处理详细总结

了解程序的运行环境可以让我们更加清楚的程序的底层运行的每一个步骤和过程&#xff0c;做到心中有数&#xff0c;预处理阶段是在预编译阶段完成&#xff0c;掌握常用的预处理命令语法&#xff0c;可以让我们正确的使用预处理命令&#xff0c;从而提高代码的开发能力和阅读别人…

在线扒站网PHP源码-在线扒站工具网站源码

源码介绍 这是一款在线的网站模板下载程序&#xff0c;也就是我们常说的扒站工具&#xff0c;利用它我们可以很轻松的将别人的网站模板样式下载下来&#xff0c;这样就可以大大提高我们编写前端的速度了&#xff01;注&#xff1a;扒取的任何站点不得用于商业、违法用途&#…

如何在CentOS 7 中基于OpenSSL 3.0 搭建Python 3.0 环境

1、OpenSSL 1.1 原因 [rootlocalhost ~]# openssl version OpenSSL 1.0.2k-fips 26 Jan 2017 [rootlocalhost ~]#通过执行openssl version可知Linux系统已经安装了OpenSSL&#xff0c;但该版本较低&#xff1b;Python 3 要求 OpenSSL版本不能低于1.1.1&#xff0c;否则安装P…

【02】mapbox js api加载arcgis切片服务

需求&#xff1a; 第三方的mapbox js api加载arcgis切片服务&#xff0c;同时叠加在mapbox自带底图上 效果图&#xff1a; 形如这种地址去加载&#xff1a; http://zjq2022.gis.com:8080/demo/loadmapbox.html arcgis切片服务参考链接思路&#xff1a;【01】mapbox js api加…

【Copula】最可能场景详解

基于Copula联合分布的最可能场景详解 最可能场景&#xff08;The most-likely scenario&#xff09;实例探讨参考 最可能场景&#xff08;The most-likely scenario&#xff09; 相应英文介绍原理介绍如下&#xff1a;&#xff08;出自论文J2020-Drought hazard transferabilit…

RTC讲解

RTC&#xff08;Real Time Clock&#xff09;实时时钟 RTC实时时钟本质上是一个独立的定时器。RTC模块拥有一组连续计数的32位无符号计数器&#xff0c;在相应软件配置下&#xff0c;可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。 RTC模块和时钟配…

基于XG24-EK2703A的BLE HID蓝牙键盘+鼠标复合设备功能开发(BLE+HID+FreeRTOS+Gecko SDK)

目录 项目介绍硬件介绍项目设计开发环境及工程参考总体流程图硬件基本配置应用初始化按键中断回调定时器回调按键响应任务蓝牙事件回调BLE HIDReport Map及报文键盘设备鼠标设备复合设备 发送字符串上/下滚动 功能展示项目总结 &#x1f449; 【Funpack3-1】基于XG24-EK2703A的…

网络端口映射和端口转发的区别和联系

目 录 一、端口映射技术 1.1 原理 1.2 应用场景 1、远程访问 2、游戏主机 3、文件共享 4、监控视频共享 二、端口转发技术 2.1 原理 2.2 应用场景 1、网络负载均衡 2、网络安全 3、网络代理 三、端口映射和转发的实现方法 3.1 路由器配置 3.2 网络防火墙 …

C Primer Plus 第6版 编程练习 chapter 16

文章目录 1. 第1题1.1 题目描述1.2 编程源码1.3 结果显示 2. 第2题2.1 题目描述2.2 编程源码2.3 结果显示 3. 第3题3.1 题目描述3.2 编程源码3.3 结果显示 4. 第4题4.1 题目描述4.2 编程源码4.3 结果显示 5. 第5题5.1 题目描述5.2 编程源码5.3 结果显示 6. 第6题6.1 题目描述6.…

linux|操作系统|centos7物理机安装网卡驱动8188gu(内核升级,firmware固件,USB设备管理,module管理)

前言&#xff1a; 目前服务器领域centos7基本是主流的操作系统&#xff0c;而linux相对于Windows来说&#xff0c;软硬件方面的支持是差很多的&#xff0c;在硬件方面来说&#xff0c;以一个免驱的网卡为例&#xff0c;window xp可能不会自动识别到&#xff0c;但Windows10基本…

问题:Feem无法发送信息OR无法连接(手机端无法发给电脑端)

目录 前言 问题分析 资源、链接 其他问题 前言 需要在小米手机、华为平板、Dell电脑之间传输文件&#xff0c;试过安装破解的华为电脑管家、小米的MIUI文件传输等&#xff0c;均无果。&#xff08;小米“远程管理”ftp传输倒是可以&#xff0c;但速度太慢了&#xff0c;且…

【Ant Design of Vue】Modal.confirm无法关闭的bug

一、问题 在使用 Ant Design Vue 的 Modal.confirm 确认框时&#xff0c;出现了点击取消和确定后 Modal.confirm 确认框无法关闭的问题 二、代码 代码完全是 copy 的官网的代码&#xff0c;但是 copy 到本地后就会出现上述问题 <template><a-button click"sho…

鸿蒙开发(五)鸿蒙UI开发概览

从用户角度来讲&#xff0c;一个软件拥有好看的UI&#xff0c;那是锦上添花的事情。再精确的算法&#xff0c;再厉害的策略&#xff0c;最终都得通过UI展现给用户并且跟用户交互。那么&#xff0c;本篇一起学习下鸿蒙开发UI基础知识&#xff0c;认识下各种基本控件以及使用方式…

UE5 C++ 学习笔记 UBT UHT 和 一些头文件

总结一些似懂非懂的知识点&#xff0c;从头慢慢梳理。 任何一个项目都有创建这些三个.cs。 这个是蓝图转C 这个是本身就是C项目,应该就是多了一个GameModeBase类 Build.cs包含了每个模块的信息&#xff0c;表明了这个项目用到了哪一些模块。该文件里的using UnrealBuilTool 是…

为数字取证和 OSINT 调查定制用户体验

Tsurugi Linux 是一个高度定制的开源发行版&#xff0c;专注于支持 DFIR 调查。 该项目主要侧重于实时取证分析、事后分析和数字证据获取。用户还可以执行恶意软件分析、OSINT(开源情报)和计算机视觉活动。 我们精心打造了用户友好的体验&#xff0c;按照逻辑取证分析顺序组织…

【JS逆向学习】36kr登陆逆向案例(webpack)

在开始讲解实际案例之前&#xff0c;大家先了解下webpack的相关知识 WebPack打包 webpack是一个基于模块化的打包&#xff08;构建&#xff09;工具, 它把一切都视作模块 webpack数组形式&#xff0c;通过下标取值 !function(e) {var t {};// 加载器 所有的模块都是从这个…

Ps:使用钢笔工具快速精准抠图的技巧

众所周知&#xff0c;钢笔工具是 Photoshop 中最精准的、适用于硬边缘&#xff08;清晰轮廓&#xff09;对象的抠图工具。但是&#xff0c;如果从头开始一个锚点一个锚点的勾勒&#xff0c;既费时又费眼。 我们可以先用选区工具或选区命令做一个基础选区&#xff0c;然后将选区…

IPv6自动隧道---6to4中继

6to4中继 普通IPv6网络需要与6to4网络通过IPv4网络互通,这可以通过6to4中继路由器方式实现。所谓6to4中继,就是通过6to4隧道转发的IPv6报文的目的地址不是6to4地址,但转发的下一跳是6to4地址,该下一跳为路由器我们称之为6to4中继。隧道的IPv4目的地址依然从下一跳的6to4地…