了解S3协议
MinIO 中提到的 S3 协议,指的是 Amazon S3(Simple Storage Service,简单存储服务)的 API(应用程序编程接口)标准。可以把这个协议理解为一套通用的“命令”或“语言”
S3的核心概念有两个:
- **存储桶 (Bucket):**就像是一个顶级的“存储空间”或“数据容器”。你可以为不同的项目或应用创建不同的存储桶,它们之间是相互隔离的。
- **对象 (Object):**指的是你存储在存储桶里的具体文件。每个对象都由一个唯一的键(Key,即文件名)来标识。
RustFS简介
它是基于Rust语言开发的高性能分布式对象存储软件,定位和minio高度相似。
根据官方同等硬件压测,RustFS 在小对象(4KB)场景下吞吐量约为 MinIO 的2.3 倍,大对象场景也高达1.8~2.2 倍
安装
参考:https://docs.rustfs.cn/installation/
运行成功后,访问 http://localhost:9001/ 进入管理后台。
- 用户名:rustfsadmin
- 密码:rustfsadmin
在管理后台可以:
- 创建和管理存储桶(Bucket)
- 查看文件列表
- 配置访问策略
- 生成 Access Key / Secret Key
需要自己本地创建存储桶并且添加访问密钥,然后修改 application.yml 中对应的配置。
springboot 集成
添加依赖
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> <version>2.44.10</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.belerweb</groupId> <artifactId>pinyin4j</artifactId> <version>2.5.1</version> <scope>compile</scope> </dependency>
|
添加yml配置
1 2 3 4 5 6 7 8
| app: storage: endpoint: http://localhost:9000 access-key: ${RUSTFS_ACCESS_KEY} secret-key: ${RUSTFS_SECRET_KEY} bucket: interview-guide region: us-east-1
|
添加配置类:
StorageConfigProperties
1 2 3 4 5 6 7 8 9 10 11
| @Data @Component @ConfigurationProperties(prefix = "app.storage") public class StorageConfigProperties {
private String endpoint; private String accessKey; private String secretKey; private String bucket; private String region = "us-east-1"; }
|
S3Config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Configuration @RequiredArgsConstructor public class S3Config {
private final StorageConfigProperties storageConfig;
@Bean public S3Client s3Client() { AwsBasicCredentials credentials = AwsBasicCredentials.create( storageConfig.getAccessKey(), storageConfig.getSecretKey() );
return S3Client.builder() .endpointOverride(URI.create(storageConfig.getEndpoint())) .region(Region.of(storageConfig.getRegion())) .credentialsProvider(StaticCredentialsProvider.create(credentials)) .forcePathStyle(true) .build(); } }
|
通用文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| private String uploadFile(MultipartFile file, String prefix) { String originalFilename = file.getOriginalFilename(); String fileKey = generateFileKey(originalFilename, prefix);
try { PutObjectRequest putRequest = PutObjectRequest.builder() .bucket(storageConfig.getBucket()) .key(fileKey) .contentType(file.getContentType()) .contentLength(file.getSize()) .build();
s3Client.putObject(putRequest, RequestBody.fromInputStream(file.getInputStream(), file.getSize())); log.info("文件上传成功: {} -> {}", originalFilename, fileKey); return fileKey; } catch (IOException e) { log.error("读取上传文件失败: {}", e.getMessage(), e); throw new BusinessException(ErrorCode.STORAGE_UPLOAD_FAILED, "文件读取失败"); } catch (S3Exception e) { log.error("上传文件到RustFS失败: {}", e.getMessage(), e); throw new BusinessException(ErrorCode.STORAGE_UPLOAD_FAILED, "文件存储失败: " + e.getMessage()); } }
private String generateFileKey(String originalFilename, String prefix) { LocalDateTime now = LocalDateTime.now(); String datePath = now.format(DATE_PATH_FORMAT); String uuid = UUID.randomUUID().toString().substring(0, 8); String safeName = sanitizeFilename(originalFilename); return String.format("%s/%s/%s_%s", prefix, datePath, uuid, safeName); }
private String sanitizeFilename(String filename) { if (filename == null || filename.isEmpty()) { return "unknown"; } return convertToPinyin(filename); }
|