MinIO的詳細介紹可以參考官網(https://min.io/product/overview)。
簡單來說它是一個實現了AWS S3標准的100%開源的,可商用的( Apache V2 license),高性能的分布式對象存儲管理系統。
AWS S3是什么(https://zhuanlan.zhihu.com/p/112057573):
- 提供了統一的接口 REST/SOAP 來統一訪問任何數據
- 對 S3 來說,存在里面的數據就是對象名(鍵),和數據(值)
- 不限量,單個文件最高可達 5TB
- 高速。每個 bucket 下每秒可達 3500 PUT/COPY/POST/DELETE 或 5500 GET/HEAD 請求
- 具備版本,權限控制能力
- 具備數據生命周期管理能力
這次主要介紹CentOS7如何安裝MinIO,以及C#如何使用MinIO的.NET SDK來上傳文件(顯示上傳進度)。
首先安裝好CentOS7 64位操作系統,然后在/usr/local下新建一個minio目錄,在minio目錄下新建三個子文件夾,分別是:
bin 用來保存minio的主程序
data 用來保存用戶上傳的文件
etc 用來保存minio的配置文件
然后新建MinIO運行所需的系統組和用戶,並給/usr/local/minio/分配組信息
groupadd -g 2020 minio useradd -r -M -u 2020 -g 2020 -c "Minio User" -s /sbin/nologin minio
chown -R minio:minio /usr/local/minio
建立了一個組ID是2020,名稱是minio的系統組,然后添加了一個不能登錄的名稱是minio,所屬組ID是2020並且個人ID也是2020的系統用戶。
MinIO默認提供的http訪問端口是9000,我們提前在防火牆里把9000端口打開
firewall-cmd --zone=public --add-port=9000/tcp --permanent firewall-cmd --reload
同時我們需要給MinIO掛載要給存儲空間比較大的磁盤用來存儲文件
執行lvdisplay找到比較大的分區,掛載給/usr/local/minio目錄
運行
mount /dev/centos/home /usr/local/minio
把這個200多G的邏輯卷掛載給minio的目錄。
同時為了重啟后自動掛載,我們需要執行以下代碼
vim /etc/fstab
把centos-home這句換成以下內容,保存退出,就可以讓系統重啟后自動掛載空間到/usr/local/minio目錄了
/dev/mapper/centos-home /usr/local/minio xfs defaults 0 0
然后我們運行lsblk就可以看到磁盤空間正確掛載在minio目錄下了
從官網(或者通過下載工具下載到本地后上傳)下載(https://min.io/download#/linux)到CentOS的/usr/local/minio/bin目錄下
其中第二句(chmod +x minio)等把minio文件移動到/usr/local/minio/bin后在執行
第三句先不要執行
下面我們建立MinIO的配置文件
vim /usr/local/minio/etc/minio.conf
寫入以下內容(--address后面的IP 是CentOS的IP地址)
MINIO_VOLUMES="/usr/local/minio/data" MINIO_OPTS="-C /usr/local/minio/etc --address 192.168.127.131:9000
建立MinIO的service文件,讓它隨系統啟動而啟動
vim /etc/systemd/system/minio.service
寫入以下內容
[Unit] Description=MinIO Documentation=https://docs.min.io Wants=network-online.target After=network-online.target AssertFileIsExecutable=/usr/local/minio/bin/minio [Service] # User and group User=minio Group=minio EnvironmentFile=/usr/local/minio/etc/minio.conf ExecStart=/usr/local/minio/bin/minio server $MINIO_OPTS $MINIO_VOLUMES # Let systemd restart this service always Restart=always # Specifies the maximum file descriptor number that can be opened by this process LimitNOFILE=65536 # Disable timeout logic and wait until process is stopped TimeoutStopSec=infinity SendSIGKILL=no [Install] WantedBy=multi-user.target
然后我們啟動MinIO
systemctl enable minio.service
systemctl start minio.service
systemctl status minio.service
啟動正常后,我們訪問http://192.168.127.131:9000就應該可以看到web管理端的登錄界面
我們可以通過下面的命令來獲取到默認的登錄名和密碼(minioadmin)
cat /usr/local/minio/data/.minio.sys/config/config.json
如果要自定義登錄名和密碼,可以在/usr/local/minio/etc/minio.conf中增加兩個配置內容
MINIO_ACCESS_KEY="登錄名"
MINIO_SECRET_KEY="登錄密碼"
至此,MinIO的安裝就介紹完畢,下面是本文重點,C#中使用WPF客戶端如何獲取文件上傳時的進度。
首先新建一個項目MinIO,項目類型是WPF,項目的.NET版本是4.6
引用官方的Minio .NET SDK以及其它一些需要的類庫
在項目的根目錄添加一個nlog.config並設置為較新時復制,內容如下(主要用來做一些日志記錄):
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd" autoReload="true" throwExceptions="false" internalLogLevel="Info"> <targets> <default-target-parameters xsi:type="File" createDirs="true" keepFileOpen="true" autoFlush="false" openFileFlushTimeout="10" openFileCacheTimeout="30" archiveAboveSize="10240" archiveNumbering="Sequence" concurrentWrites="true" encoding="UTF-8"/> <target xsi:type="File" name="InfoFile" fileName="${basedir}/InfoLogs/log.txt" archiveFileName="${basedir}/InfoLogs/log.{#}.txt"> <layout xsi:type="JsonLayout"> <attribute name="counter" layout="${counter}" /> <attribute name="time" layout="${longdate}" /> <attribute name="level" layout="${level:upperCase=true}"/> <attribute name="message" layout="${message:format=message}" encode="false" /> </layout> </target> <target xsi:type="File" name="ErrorFile" fileName="${basedir}/ErrorLogs/log.txt" archiveFileName="${basedir}/ErrorLogs/log.{#}.txt"> <layout xsi:type="JsonLayout"> <attribute name="time" layout="${longdate}" /> <attribute name="level" layout="${level:upperCase=true}"/> <attribute name="message" layout="${message}" encode="false" /> <attribute name="exception"> <layout xsi:type="JsonLayout"> <attribute name="callsite" layout="${callsite}" /> <attribute name="callsite-linenumber" layout="${callsite-linenumber} " /> </layout> </attribute> </layout> </target> </targets> <rules> <logger name="*" minlevel="Info" writeTo="InfoFile" /> <logger name="*" minlevel="Error" writeTo="ErrorFile" /> </rules> </nlog>
在使用過程中發現MinIO的.NET SDK中並沒有回顯上傳進度的地方(如果有請告知),經過一些摸索,在它暴露的日志記錄的地方獲取到當前上傳文件塊的編號,通過MVVM的Messenger傳遞給主程序,主程序接收后計算出當前進度並顯示出來。
我們在App.xaml中添加一個啟動事件App_OnStartup,在啟動的時候初始化一下Nlog,並定義一下MinIO上傳時遇到大文件分塊的默認值(5MB)
namespace MinIO { /// <summary> /// App.xaml 的交互邏輯 /// </summary> public partial class App : Application { public static NLog.Logger NewNLog; public static long MinimumPartSize = 5 * 1024L * 1024L;//單次上傳文件請求最大5MB private void App_OnStartup(object sender, StartupEventArgs e) { NewNLog = NLog.LogManager.GetLogger("MinIOLoger"); } } }
在MainWindow.xaml中我們添加一個按鈕用來上傳文件,一個TextBlock用來顯示上傳文件信息和進度
<Window x:Class="MinIO.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:MinIO" mc:Ignorable="d" DataContext="{Binding FileUploadViewModel,Source={StaticResource Locator}}" Title="MainWindow" Height="450" Width="800" Loaded="MainWindow_OnLoaded"> <Grid> <Button Content="上傳文件" HorizontalAlignment="Left" Margin="47,51,0,0" VerticalAlignment="Top" Width="75" Click="ButtonUpload_Click"/> <TextBlock HorizontalAlignment="Left" Margin="47,158,0,0" TextWrapping="Wrap" VerticalAlignment="Top"> <Run Text=" 文件名:"></Run> <Run Text="{Binding Path=FileName}"></Run> <Run Text=" "></Run> <Run Text="文件大小:"></Run> <Run Text="{Binding Path=FileSize}"></Run> <Run Text=" "></Run> <Run Text="上傳進度:"></Run> <Run Text="{Binding Path=UploadProcess}"></Run> </TextBlock> </Grid> </Window>
同時我們定義一個ViewModel來實現文件上傳信息變化時的通知(ViewModel目錄中新建一個FileUploadViewModel.cs)
using System.ComponentModel; namespace MinIO.ViewModel { public class FileUploadViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _filename { get; set; } /// <summary> /// 文件名 /// </summary> public string FileName { get => _filename; set { _filename = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FileName")); } } private long _fileSize { get; set; } /// <summary> /// 文件大小 /// </summary> public long FileSize { get => _fileSize; set { _fileSize = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FileSize")); } } private long _partNumber { get; set; } /// <summary> /// 當前上傳塊 /// </summary> public long PartNumber { get => _partNumber; set { _partNumber = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PartNumber")); } } private long _totalParts { get; set; } /// <summary> /// 文件全部塊數 /// </summary> public long TotalParts { get => _totalParts; set { _totalParts = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TotalParts")); } } private string _uploadProcess { get; set; } /// <summary> /// 上傳進度展示 /// </summary> public string UploadProcess { get => _uploadProcess; set { _uploadProcess = value; PropertyChanged?.Invoke(this,new PropertyChangedEventArgs("UploadProcess")); } } } }
同時為了能讓MinIO根據不同的文件類型展示不同的圖標,我們還需要建立一個ContentType對應關系類(ContentTypeHelper.cs)
using System.Collections.Generic; namespace MinIO.Helper { public static class ContentTypeHelper { private static readonly Dictionary<string,string> DictionaryContentType=new Dictionary<string, string> { {"default","application/octet-stream"}, {"bmp","application/x-bmp"}, {"doc","application/msword"}, {"docx","application/msword"}, {"exe","application/x-msdownload"}, {"gif","image/gif"}, {"html","text/html"}, {"jpg","image/jpeg"}, {"mp4","video/mpeg4"}, {"mpeg","video/mpg"}, {"mpg","video/mpg"}, {"ppt","application/x-ppt"}, {"pptx","application/x-ppt"}, {"png","image/png"}, {"rar","application/zip"}, {"txt","text/plain"}, {"xls","application/x-xls"}, {"xlsx","application/x-xls"}, {"zip","application/zip"}, }; /// <summary> /// 根據文件擴展名(不含.)返回ContentType /// </summary> /// <param name="fileExtension">文件擴展名(不包含.)</param> /// <returns></returns> public static string GetContentType(string fileExtension) { return DictionaryContentType.ContainsKey(fileExtension) ? DictionaryContentType[fileExtension] : DictionaryContentType["default"]; } } }
我們需要新建一個實現MinIO中IRequestLogger接口的類(LogHelper.cs),用來接收日志信息通過處理日志信息,來計算出當前上傳進度信息
using GalaSoft.MvvmLight.Messaging; using Minio; using Minio.DataModel.Tracing; using System.Net; namespace MinIO.Helper { public class LogHelper : IRequestLogger { public void LogRequest(RequestToLog requestToLog, ResponseToLog responseToLog, double durationMs) { if (responseToLog.statusCode == HttpStatusCode.OK) { foreach (var header in requestToLog.parameters) { if (!string.Equals(header.name, "partNumber")) continue; if(header.value==null) continue; int.TryParse(header.value.ToString(), out var partNumber);//minio遇到上傳文件大於5MB時,會進行分塊傳輸,這里就是當前塊的編號(遞增) Messenger.Default.Send(partNumber, "process");//發送給主界面計算上傳進度 break; } } } } }
在MainWindow初始化的時候初始化MinIO的配置信息,同時用MVVM的Messenger來接收當前上傳的塊編號,用來計算上傳進度。
using GalaSoft.MvvmLight.Messaging; using Microsoft.Win32; using Minio; using Minio.Exceptions; using MinIO.Helper; using MinIO.ViewModel; using System; using System.Diagnostics; using System.IO; using System.Threading.Tasks; using System.Windows; namespace MinIO { /// <summary> /// MainWindow.xaml 的交互邏輯 /// </summary> public partial class MainWindow : Window { private static string _endpoint = "192.168.127.131:9000";//minio服務器地址 private static string _accessKey = "minioadmin";//授權登錄賬號 private static string _secretKey = "minioadmin";//授權登錄密碼 private static MinioClient _minioClient; public MainWindow() { InitializeComponent(); } private void MainWindow_OnLoaded(object sender, RoutedEventArgs e) { _minioClient = new MinioClient(_endpoint, _accessKey, _secretKey); Messenger.Default.Register<int>(this, "process", obj => { try { Debug.WriteLine($"當前塊編號:{obj}"); if (obj == 0) { ViewModelLocator.Instance.FileUploadViewModel.UploadProcess = "0.00%"; return; } ViewModelLocator.Instance.FileUploadViewModel.PartNumber = obj; ViewModelLocator.Instance.FileUploadViewModel.UploadProcess = $"{(float)ViewModelLocator.Instance.FileUploadViewModel.PartNumber / ViewModelLocator.Instance.FileUploadViewModel.TotalParts:P2}";//計算文件上傳進度 } catch (Exception exception) { App.NewNLog.Error($"計算上傳進度時出錯:{exception}"); } }); } private void ButtonUpload_Click(object sender, RoutedEventArgs e) { var open = new OpenFileDialog { CheckFileExists = true, CheckPathExists = true, }; if (open.ShowDialog(this) == false) { return; } ViewModelLocator.Instance.FileUploadViewModel.FileName = open.SafeFileName; try { Dispatcher?.InvokeAsync(async () => { await Run(_minioClient, "test", open.FileName, ViewModelLocator.Instance.FileUploadViewModel.FileName); }); } catch (Exception ex) { Console.WriteLine(ex.Message); } } private static async Task Run(MinioClient minio, string userBucketName, string uploadFilePath, string saveFileName) { var bucketName = userBucketName; var location = "us-east-1"; var objectName = saveFileName; var filePath = uploadFilePath; var contentType = ContentTypeHelper.GetContentType(saveFileName.Substring(saveFileName.LastIndexOf('.') + 1)); var file = new FileInfo(uploadFilePath); try { var found = await minio.BucketExistsAsync(bucketName); if (!found) { await minio.MakeBucketAsync(bucketName, location); } _minioClient.SetTraceOn(new LogHelper());//我們在上傳開始的時候,打開日志,通過日志拋出的塊編號來計算出當前進度 ViewModelLocator.Instance.FileUploadViewModel.FileSize = file.Length; ViewModelLocator.Instance.FileUploadViewModel.TotalParts = file.Length / App.MinimumPartSize + 1;//計算出文件總塊數 await minio.PutObjectAsync(bucketName, objectName, filePath, contentType);//上傳文件 Debug.WriteLine("Successfully uploaded " + objectName); } catch (MinioException e) { App.NewNLog.Error($"File Upload Error: {e}"); Debug.WriteLine($"File Upload Error: {e.Message}"); } finally { _minioClient.SetTraceOff(); } } } }
具體效果如下:
至此,我們實現了CentOS7下安裝MinIO,並且在C#客戶端下展示文件上傳進度的功能。
PS:代碼還不完善,少了一個小於5M的時候,直接展示上傳成功的代碼。