Flutter中使用minio_new库

news2024/10/1 17:30:28

前言


在移动开发中,我们常常会遇到需要在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();
  }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最终Docker6:nacos集群部署

目录 mysql容器构建 1.进入soft 文件夹&#xff0c;创建mysql文件夹 2.进入conf文件夹 放入my.conf 配置文件 3.运行mysql容器 4.进入script文件夹 导入 sql文件 5.进入mysql 容器 并登录 6.创建nacos 数据库并使用&#xff0c;运行nacos.sql文件 7.授予用户所有权限 部…

loading stable diffusion model: FileNotFoundError解决方案

大家好&#xff0c;我是水滴~~ 本文主要介绍在安装 stable-diffusion-webui 时出现的 loading stable diffusion model: FileNotFoundError 问题的解决方案&#xff0c;希望能对你有所帮助。 文章目录 问题描述解决方案 问题描述 在安装 stable-diffusion-webui 过程中出现 l…

Linux环境下,针对QT软件工程搭建C++Test单元测试环境的操作指南

文章目录 前言一、安装QT二、安装CTest三、使用QT生成.bdf文件四、创建CTest工程注意事项 前言 CTest是Parasoft公司出品的一款可以针对C/C源代码进行静态分析、单元测试、集成测试的测试工具。本文主要讲解如何在Linux环境下&#xff0c;搭建QT插件版的CTest测试环境。 一、…

大数据开发之Hadoop(优化新特征)

第 1 章&#xff1a;HDFS-故障排除 注意&#xff1a;采用三台服务器即可&#xff0c;恢复到Yarn开始的服务器快照。 1.1 集群安全模块 1、安全模式&#xff1a;文件系统只接收读数据请求&#xff0c;而不接收删除、修改等变更请求 2、进入安全模式场景 1&#xff09;NameNod…

GPT应用开发:GPT插件开发指南

欢迎阅读本系列文章&#xff01;我将带你一起探索如何利用OpenAI API开发GPT应用。无论你是编程新手还是资深开发者&#xff0c;都能在这里获得灵感和收获。 本文&#xff0c;我们将继续展示聊天API中插件的使用方法&#xff0c;让你能够轻松驾驭这个强大的工具。 插件运行效…

记一次 .NET某道闸收费系统 内存溢出分析

一&#xff1a;背景 1. 讲故事 前些天有位朋友找到我&#xff0c;说他的程序几天内存就要爆一次&#xff0c;不知道咋回事&#xff0c;找不出原因&#xff0c;让我帮忙看一下&#xff0c;这种问题分析dump是最简单粗暴了&#xff0c;拿到dump后接下来就是一顿分析。 二&…

移动web开发流式布局

1.0 移动端基础 1.1 浏览器现状 PC端常见浏览器&#xff1a;360浏览器、谷歌浏览器、火狐浏览器、QQ浏览器、百度浏览器、搜狗浏览器、IE浏览器。 内核&#xff1a; 浏览器内核备注Safariwebkitwebkit内核是苹果公司开发的一款渲染引擎&#xff0c;目前已被很多手机厂商所采…

Java开发的审批流系统,前端使用vue,支持常态化工作审批流程

一、项目形式 springbootvueactiviti集成了activiti在线编辑器&#xff0c;快速开发平台&#xff0c;可插拔工作流服务。 二、项目介绍 本项目拥有用户管理&#xff0c;部门管理&#xff0c;代码生成&#xff0c;系统监管&#xff0c;报表&#xff0c;大屏展示&#xff0c;业…

文心一言使用分享

ChatGPT 和文心一言哪个更好用&#xff1f; 一个直接可以用&#xff0c;一个还需要借助一些工具&#xff0c;还有可能账号会消失…… 没有可比性。 通用大模型用于特定功能的时候需要一些引导技巧。 import math import time def calculate_coordinate(c, d, e, f, g, h,…

一套可以替代人工的Cnc机床自动上下料机器人

Cnc机床自动上下料|整体解决方案 CNC机床自动上下料是指通过自动化设备和系统&#xff0c;实现CNC机床在加工过程中自动进行上下料操作。这种自动化系统通常包括自动送料机和卸料机&#xff0c;可以根据加工工件的尺寸和形状自动调整上下料的位置和角度&#xff0c;从而提高生产…

SpringCloud整合Zookeeper代替Eureka案例

文章目录 本期代码下载地址zookeeper简介zookeeper下载安装新建服务提供者测试 新建消费者测试 本期代码下载地址 地址:https://github.com/13thm/study_springcloud/tree/main/days4 zookeeper简介 zookeeper是一个分布式协调工具&#xff0c;可以实现注册中心功能 关闭Lin…

VMware Workstation Pro虚拟机搭建

下载链接&#xff1a;Download VMware Workstation Pro 点击上方下载&#xff0c;安装过程很简单&#xff0c;我再图片里面说明 等待安装中。。。。。是不是再考虑怎样激活&#xff0c;我都给你想好了&#xff0c;在下面这个链接&#xff0c;点赞收藏拿走不谢。 https://downl…

DBA技术栈MongoDB:简介

1.1 什么是MongoDB&#xff1f; MongoDB是一个可扩展、开源、表结构自由、用C语言编写且面向文档的数据库&#xff0c;旨在为Web应用程序提供高性能、高可用性且易扩展的数据存储解决方案。 MongoDB是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当…

ElasticSearch的常用增删改查DSL和代码

es增删改查常用语法 我们日常开发中&#xff0c;操作数据库写sql倒是不可能忘记&#xff0c;但是操作es的dsl语句有时候很容易忘记&#xff0c;特地记录一下方便查找。 DSL语句 1、创建索引 -- 创建索引 PUT /my_index {"mappings": {"properties": {&…

数据结构:链式栈

stack.h /* * 文件名称&#xff1a;stack.h * 创 建 者&#xff1a;cxy * 创建日期&#xff1a;2024年01月18日 * 描 述&#xff1a; */ #ifndef _STACK_H #define _STACK_H#include <stdio.h> #include <stdlib.h>typedef struct stack{int data…

查找局域网树莓派raspberry的mac地址和ip

依赖python库&#xff1a; pip install socket pip install scapy运行代码&#xff1a; import socket from scapy.layers.l2 import ARP, Ether, srpdef get_hostname(ip_address):try:return socket.gethostbyaddr(ip_address)[0]except socket.herror:# 未能解析主机名ret…

Leetcode2207. 字符串中最多数目的子字符串

Every day a Leetcode 题目来源&#xff1a;2207. 字符串中最多数目的子字符串 解法1&#xff1a;贪心 一次遍历 设 pattern 的第一个字符为 x&#xff0c;第二个字符为 y。 根据题意&#xff0c;x 插入的位置越靠左&#xff0c;答案的个数越多&#xff1b;y 插入的位置越…

大数据StarRocks(八):集群扩缩容

一、FE扩缩容 StarRocks FE 节点分为 Follower 节点和 Observer 节点。Follower 节点参与选举投票和写入&#xff0c;Observer 节点只用来同步日志&#xff0c;扩展读性能。 注意&#xff1a; 所有 FE 节点的 http_port 必须相同。Follower FE 节点&#xff08;包括 Leader …

大数据工作岗位分析

前言&#xff1a;随着大数据需求的增多&#xff0c;许多中小公司和团队也新增或扩展了大数据工作岗位&#xff1b;但是却对大数据要做什么和能做什么&#xff0c;没有深入的认识&#xff1b;往往是招了大数据岗位&#xff0c;搭建起基础能力后&#xff0c;就一直处于重复开发和…

K8S Informer机制原理解读 | 架构设计

在Kubernetes系统中&#xff0c;组件之间通过HTTP协议进行通信&#xff0c;在不依赖任何中间件的情况下需要保证消息的实时性、可靠性、顺序性等。那么Kubernetes是如何做到的呢&#xff1f;答案就是Informer机制。Kubernetes的其他组件都是通过client-go的Informer机制与Kuber…