Files
blog-press/docs/Web-Backend/Others/RustFS.md
2025-12-12 10:41:15 +08:00

5.6 KiB
Raw Blame History

简介

RustFS 是一个基于 Rust 构建的高性能分布式对象存储系统。
  具体以下特点:

  • 高性能:基于 Rust 构建,确保极致的速度和资源效率。
  • 分布式架构:可扩展且容错的设计,适用于大规模部署。
  • S3 兼容性:与现有的 S3 兼容应用和工具无缝集成。
  • 数据湖支持:专为高吞吐量的大数据和 AI 工作负载优化。
  • 完全开源:采用 Apache 2.0 许可证,鼓励社区贡献和商业使用。
  • 简单易用:设计简洁,易于部署和管理。

S3 标准通常指 Amazon S3 的 API 接口规范,现已成为对象存储领域事实上的行业标准接口协议。它定义了对象存储的核心操作方式和数据模型。

  • 对象:基本存储单元,包含数据、元数据和唯一标识符
  • 桶:对象的逻辑容器,类似文件夹
  • 扁平结构无传统目录层级通过键名Key定位

安装部署

通过docker部署

services:
  rustfs:
    image: rustfs/rustfs:1.0.0-alpha.65
    container_name: rustfs
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - ./data:/data
      - ./logs:/logs
    environment:
      - TZ=Asia/Shanghai
      - RUSTFS_ACCESS_KEY=admin
      - RUSTFS_SECRET_KEY=19940822Cxx
      - RUSTFS_CONSOLE_ENABLE=true
    restart: unless-stopped

其中9000是API端口9001是控制台端口。
默认的登录账号密码为rustfsadmin / rustfsadmin。

API调用

登录控制台获取access_key和secret_access。

Python

  1. 安装AWS官方SDK依赖库
pip install boto3
  1. 配置与连接
import boto3
from botocore.client import Config

from config.setting import settings

url = 'ip:port'
access_key = ''
secret_access = ''

s3 = boto3.client('s3',
                  endpoint_url=url,
                  aws_access_key_id=access_key,
                  aws_secret_access_key=secret_access,
                  config=Config(signature_version='s3v4'),
                  region_name='cn-east-1'
                  )
  1. 上传文件
async def upload_file(md5: str, file: UploadFile = File(...)) -> str:
    file_ext = file.filename.split('.')[-1]
    unique_filename = f"{md5}.{file_ext}"

    file_content = await file.read()

    # 上传到S3
    s3.put_object(
        Bucket=BUCKET,
        Key=unique_filename,
        Body=file_content,
        ContentType=file.content_type
    )

    # 返回文件url
    return f"{BUCKET}/{unique_filename}"

Flutter

  1. 安装依赖包
dependencies:
  minio: ^3.5.8
  file_picker: ^10.3.3
  1. 工具类
import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:minio/io.dart';
import 'package:minio/minio.dart';

import 'file_utils.dart';

class MinIOHelper {
  static final MinIOHelper _instance = MinIOHelper._internal();

  factory MinIOHelper() => _instance;

  final String ip = 'ip';
  final int port = 9100;
  final String fileUrl = 'http://ip:port';
  final String accessKey = '';
  final String secretKey = '';

  MinIOHelper._internal() {
    _minio = Minio(
      endPoint: ip,
      port: port,
      accessKey: accessKey,
      secretKey: secretKey,
      useSSL: false,
    );
  }

  late Minio _minio;

  Future<String> uploadFile({
    required PlatformFile file,
    required String bucketName,
    Function(double)? onProgress,
  }) async {
    try {
      if (isImageFile(file.name)) {
        // 压缩图片
        final compressedFile = await compressImage(File(file.path!));

        String hashName = await generateMD5HashName(compressedFile.path);
        String fileName = '$hashName${getFileExtension(file.name)}';

        await _minio.fPutObject(bucketName, fileName, compressedFile.path);
        await compressedFile.delete();

        return fileName;
      } else {
        String hashName = await generateMD5HashName(file.path!);
        String fileName = '$hashName${getFileExtension(file.name)}';

        await _minio.fPutObject(bucketName, fileName, file.path!);

        return fileName;
      }
    } catch (e) {
      throw Exception('文件上传失败: $e');
    }
  }
}

  文件工具类:

import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';

import 'log_utils.dart';

String getFileExtension(String fileName) {
  if (fileName.contains('.')) {
    return '.${fileName.split('.').last.toLowerCase()}';
  }
  return '';
}

Future<String> generateMD5HashName(String filePath) async {
  final file = File(filePath);
  final bytes = await file.readAsBytes();
  final hash = md5.convert(bytes);
  return hash.toString();
}

// 图片压缩方法
Future<File> compressImage(File file) async {
  try {
    // 获取压缩后的文件路径
    final result = await FlutterImageCompress.compressAndGetFile(
      file.absolute.path,
      '${file.parent.path}/compressed_${DateTime.now().millisecondsSinceEpoch}.jpg',
      minWidth: 800,
      minHeight: 600,
      quality: 70,
      format: CompressFormat.jpeg,
    );

    if (result == null) {
      throw Exception('图片压缩失败');
    }

    return File(result.path);
  } catch (e) {
    logger.e('图片压缩失败,使用原文件: $e');
    return file;
  }
}

// 检查是否为图片文件
bool isImageFile(String fileName) {
  final imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
  final extension = fileName.toLowerCase().substring(fileName.lastIndexOf('.'));
  return imageExtensions.contains(extension);
}