文件存储服务
HMX平台支持S3标准的对象存储服务(如RustFS、MinIO、阿里云OSS等),用于存储和管理业务文件。
配置
连接配置格式
http://{accessKey}:{secretKey}@{host}:{port}/{bucket}示例:
http://admin:admin@192.168.132.86:29000/mes配置存储方式
方式一:数据库存储(推荐)
通过系统参数表存储,支持动态修改:
csharp
// 从数据库读取配置
var settings = await SF.Proxy<ISystemKeyValueAppService>()
.GetSysKvListByGroup("A0000:GZMES_SETTINGS");
var rustfs = settings.FirstOrDefault(x => x.CCode == "RUSTFS");
if (rustfs != null && rustfs.CValue.IsNotNullOrEmpty())
{
_config = ParseRustFsUrl(rustfs.CValue);
}方式二:配置文件(不推荐)
⚠️ 安全警告:将AccessKey/SecretKey写在配置文件中存在安全风险,客户端程序可能被反编译导致密钥泄露。生产环境请使用数据库存储方式。
json
{
"S3": {
"ServiceUrl": "http://192.168.132.86:29000",
"AccessKey": "admin",
"SecretKey": "admin",
"Bucket": "mes"
}
}URL解析
csharp
private static S3Config ParseRustFsUrl(string url)
{
var uri = new Uri(url);
var accessKey = Uri.UnescapeDataString(uri.UserInfo.Split(':')[0]);
var secretKey = Uri.UnescapeDataString(uri.UserInfo.Split(':')[1]);
var serviceUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
var bucket = uri.AbsolutePath.TrimStart('/');
return new S3Config
{
ServiceUrl = serviceUrl,
AccessKey = accessKey,
SecretKey = secretKey,
LastBucket = bucket
};
}核心功能
S3Service 服务层
| 功能 | 方法 | 说明 |
|---|---|---|
| 连接测试 | TestConnectionAsync() | 测试S3服务是否可达 |
| Bucket管理 | ListBucketsAsync() | 列出所有Bucket |
CreateBucketAsync() | 创建Bucket | |
DeleteBucketAsync() | 删除Bucket | |
| 文件列表 | ListObjectsAsync() | 列出文件和目录(支持分页) |
| 文件上传 | UploadFileAsync() | 上传文件(自动分片) |
| 文件下载 | DownloadFileAsync() | 下载文件(带进度) |
| 文件删除 | DeleteObjectAsync() | 删除单个文件 |
DeleteObjectsAsync() | 批量删除(最多1000个) | |
| 文件重命名 | RenameObjectAsync() | 复制+删除实现 |
| 文件移动 | MoveObjectAsync() | 复制+删除实现 |
| 目录操作 | CreateDirectoryAsync() | 创建虚拟目录 |
DeleteDirectoryAsync() | 删除虚拟目录 | |
| 文件预览 | GetFileStreamAsync() | 获取文件流 |
GetObjectMetadataAsync() | 获取文件元数据 |
使用示例
1. 初始化连接
csharp
public class FileStorageService
{
private readonly S3Service _s3Service = new();
public void Initialize(string connectionUrl)
{
var config = ParseRustFsUrl(connectionUrl);
_s3Service.Configure(config.ServiceUrl, config.AccessKey, config.SecretKey);
}
}2. 上传文件
csharp
// 简单上传(小文件)
await _s3Service.UploadFileAsync(
bucketName: "mes",
key: "documents/2024/report.pdf",
localFilePath: @"C:\temp\report.pdf",
progress: new Progress<S3TransferProgressInfo>(p =>
{
Console.WriteLine($"上传进度: {p.PercentComplete}% ({p.SpeedDisplay})");
}));3. 分片上传(大文件)
csharp
// 大文件自动分片(>8MB自动启用)
// 分片大小: 8MB
// 支持断点续传
// 上传失败自动中止
await _s3Service.UploadFileAsync(
bucketName: "mes",
key: "videos/tutorial.mp4",
localFilePath: @"C:\videos\tutorial.mp4",
progress: progress);4. 下载文件
csharp
await _s3Service.DownloadFileAsync(
bucketName: "mes",
key: "documents/2024/report.pdf",
localFilePath: @"C:\downloads\report.pdf",
progress: new Progress<S3TransferProgressInfo>(p =>
{
UpdateProgressBar(p.PercentComplete);
}));5. 列出文件
csharp
var files = await _s3Service.ListObjectsAsync(
bucketName: "mes",
prefix: "documents/2024/");
foreach (var file in files)
{
if (file.IsDirectory)
Console.WriteLine($"📁 {file.DisplayName}/");
else
Console.WriteLine($"📄 {file.DisplayName} ({file.SizeDisplay})");
}6. 批量删除
csharp
var keysToDelete = new List<string>
{
"temp/file1.txt",
"temp/file2.txt",
"temp/file3.txt"
};
int deleted = await _s3Service.DeleteObjectsAsync("mes", keysToDelete);
Console.WriteLine($"已删除 {deleted} 个文件");7. 创建目录
csharp
// S3没有真正的目录,通过零字节对象模拟
await _s3Service.CreateDirectoryAsync("mes", "documents/2024/01/");大文件分片上传
分片策略
| 参数 | 值 | 说明 |
|---|---|---|
| 最小分片 | 5MB | S3协议要求 |
| 默认分片 | 8MB | 本项目默认值 |
| 预读缓冲 | 2个分片 | 提高上传效率 |
| 超时时间 | 10分钟 | 单次请求超时 |
| 重试次数 | 3次 | 自动重试 |
上传流程
1. 判断文件大小
│
├─ < 8MB ──► 简单上传 (PutObject)
│
└─ ≥ 8MB ──► 分片上传
│
├─ 1. InitiateMultipartUpload
│
├─ 2. 循环读取分片
│ └─ UploadPart (8MB/片)
│
├─ 3. CompleteMultipartUpload
│
└─ 失败时: AbortMultipartUpload代码实现
csharp
// 分片上传核心逻辑
private async Task MultipartUploadAsync(...)
{
// 1. 初始化分片上传
var initiateResponse = await _client.InitiateMultipartUploadAsync(...);
var uploadId = initiateResponse.UploadId;
try
{
var partETags = new List<PartETag>();
var partNumber = 1;
// 2. 循环上传分片
while (true)
{
var bytesRead = await ReadFullBufferAsync(fs, buffer, ct);
if (bytesRead == 0) break;
var partResponse = await _client.UploadPartAsync(new UploadPartRequest
{
UploadId = uploadId,
PartNumber = partNumber,
PartSize = bytesRead,
InputStream = partStream
});
partETags.Add(new PartETag { PartNumber = partNumber, ETag = partResponse.ETag });
partNumber++;
}
// 3. 完成上传
await _client.CompleteMultipartUploadAsync(new CompleteMultipartUploadRequest
{
UploadId = uploadId,
PartETags = partETags
});
}
catch (Exception)
{
// 4. 失败时中止
await _client.AbortMultipartUploadAsync(...);
throw;
}
}进度跟踪
进度模型
csharp
public class S3TransferProgressInfo
{
public long BytesTransferred { get; set; }
public long TotalBytes { get; set; }
public double SpeedBytesPerSecond { get; set; }
public TimeSpan Elapsed { get; set; }
public int PercentComplete => TotalBytes > 0
? (int)(100 * BytesTransferred / TotalBytes)
: 0;
public string SpeedDisplay => SpeedBytesPerSecond switch
{
> 1024 * 1024 => $"{SpeedBytesPerSecond / 1024 / 1024:F1} MB/s",
> 1024 => $"{SpeedBytesPerSecond / 1024:F1} KB/s",
_ => $"{SpeedBytesPerSecond:F0} B/s"
};
}使用进度回调
csharp
var progress = new Progress<S3TransferProgressInfo>(info =>
{
// 更新UI
progressBar.Value = info.PercentComplete;
lblSpeed.Text = info.SpeedDisplay;
lblEta.Text = CalculateEta(info);
});
await _s3Service.UploadFileAsync(bucket, key, filePath, progress, cts.Token);ContentType映射
| 扩展名 | ContentType |
|---|---|
.jpg, .jpeg | image/jpeg |
.png | image/png |
.pdf | application/pdf |
.doc, .docx | application/msword |
.xls, .xlsx | application/vnd.ms-excel |
.mp4 | video/mp4 |
.zip | application/zip |
| 其他 | application/octet-stream |
文件预览类型
csharp
public enum S3PreviewType
{
Image, // 图片预览
Pdf, // PDF预览
Office, // Office文档
Text, // 文本文件
Video, // 视频播放
Unsupported // 不支持预览
}最佳实践
1. Key命名规范
{业务类型}/{年月}/{文件名}
示例:
documents/2024/01/report.pdf
images/products/2024-01-15/logo.png
attachments/order/ORD20240101001/spec.pdf2. 错误处理
csharp
try
{
await _s3Service.UploadFileAsync(...);
}
catch (AmazonS3Exception ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
// Bucket不存在,创建后重试
await _s3Service.CreateBucketAsync(bucketName);
await _s3Service.UploadFileAsync(...);
}
catch (OperationCanceledException)
{
// 用户取消
ShowMessage("上传已取消");
}3. 取消上传
csharp
using var cts = new CancellationTokenSource();
// 绑定取消按钮
btnCancel.Click += (_, _) => cts.Cancel();
await _s3Service.UploadFileAsync(bucket, key, filePath, progress, cts.Token);4. 批量操作
csharp
// 批量删除(自动分批,每批最多1000个)
var keys = fileList.Select(f => f.Key).ToList();
int deleted = await _s3Service.DeleteObjectsAsync(bucketName, keys);常见问题
上传超时
原因:文件过大或网络不稳定
解决方案:
- 使用分片上传(已自动启用)
- 增加超时时间:
config.Timeout = TimeSpan.FromMinutes(20) - 检查网络连接
连接失败
排查步骤:
- 确认S3服务地址可访问
- 确认AccessKey/SecretKey正确
- 确认Bucket存在
- 检查防火墙设置
文件名乱码
原因:中文文件名编码问题
解决方案:使用URL编码
csharp
var encodedKey = Uri.EscapeDataString("中文文件名.pdf");