IGeekFan.R2.NET
Cloudflare R2 对象存储 .NET 客户端,基于 AWS S3 SDK。
功能概览
| 功能 | 说明 |
|---|---|
| 文件上传 | 上传文件到 R2 存储 |
| 文件下载 | 按需下载文件 |
| 文件删除 | 删除指定文件 |
| S3 兼容 | 兼容 AWS S3 协议 |
安装
dotnet add package IGeekFan.R2.NET
快速开始
1. 配置
{
"CloudflareR2": {
"ApiBaseUri": "https://<account-id>.r2.cloudflarestorage.com",
"AccessKeyId": "<your-access-key-id>",
"Secret": "<your-secret-access-key>",
"Host": "https://your-domain.com",
"Bucket": "your-bucket"
}
}
2. 注册服务
using IGeekFan.R2.NET;
using IGeekFan.R2.NET.Configuration;
builder.Services.Configure<CloudflareR2Options>(
builder.Configuration.GetSection(CloudflareR2Options.SettingsName));
builder.Services.AddSingleton<ICloudflareR2ClientFactory, CloudflareR2ClientFactory>();
3. 上传文件
public class FileService
{
private readonly ICloudflareR2ClientFactory _factory;
public FileService(ICloudflareR2ClientFactory factory) => _factory = factory;
public async Task<string> UploadAsync(Stream file, string fileName)
{
ICloudflareR2Client client = _factory.GetClient("my-bucket");
string url = await client.UploadBlobAsync(file, fileName, CancellationToken.None);
return url;
}
public async Task<Stream> DownloadAsync(string fileName)
{
ICloudflareR2Client client = _factory.GetClient("my-bucket");
return await client.GetBlobAsync(fileName, CancellationToken.None);
}
public async Task DeleteAsync(string fileName)
{
ICloudflareR2Client client = _factory.GetClient("my-bucket");
await client.DeleteBlobAsync(fileName, CancellationToken.None);
}
}
API 方法
| 方法 | 说明 | 参数 |
|---|---|---|
UploadBlobAsync | 上传文件 | Stream, string fileName, CancellationToken |
GetBlobAsync | 下载文件 | string fileName, CancellationToken |
DeleteBlobAsync | 删除文件 | string fileName, CancellationToken |
ListBucketsAsync | 列出 Bucket | CancellationToken |
ListObjectsAsync | 列出对象 | string prefix, CancellationToken |
CopyObjectAsync | 复制对象 | string sourceKey, string destinationKey, CancellationToken |
使用示例
上传图片
public async Task<string> UploadImageAsync(IFormFile file)
{
using var stream = file.OpenReadStream();
var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
var url = await _factory.GetClient("images")
.UploadBlobAsync(stream, fileName, CancellationToken.None);
return url;
}
批量上传
public async Task<List<string>> UploadFilesAsync(List<IFormFile> files)
{
var urls = new List<string>();
var client = _factory.GetClient("uploads");
foreach (var file in files)
{
using var stream = file.OpenReadStream();
var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
var url = await client.UploadBlobAsync(stream, fileName, CancellationToken.None);
urls.Add(url);
}
return urls;
}
生成签名 URL
public string GetSignedUrl(string fileName, int expirationMinutes = 60)
{
var client = _factory.GetClient("my-bucket");
// 生成带签名的临时访问 URL
return client.GetSignedUrl(fileName, expirationMinutes);
}
列出文件
public async Task<List<string>> ListFilesAsync(string prefix = "")
{
var client = _factory.GetClient("my-bucket");
var objects = await client.ListObjectsAsync(prefix, CancellationToken.None);
return objects.Select(o => o.Key).ToList();
}
配置选项
{
"CloudflareR2": {
"ApiBaseUri": "https://<account-id>.r2.cloudflarestorage.com",
"AccessKeyId": "<your-access-key-id>",
"Secret": "<your-secret-access-key>",
"Host": "https://your-domain.com",
"Bucket": "your-bucket"
}
}
| 选项 | 说明 | 必填 |
|---|---|---|
ApiBaseUri | R2 API 地址 | 是 |
AccessKeyId | 访问密钥 ID | 是 |
Secret | 访问密钥 | 是 |
Host | 自定义域名 | 否 |
Bucket | 默认 Bucket | 是 |
获取 API Token
- 登录 Cloudflare Dashboard → R2 → Manage R2 API Tokens
- 创建 Token,选择 Admin Read & Write
- 记录 Access Key ID 和 Secret
与 FreeSql 集成
// 在实体中存储文件路径
public class Article : FullAuditEntity<Guid, Guid>
{
public string Title { get; set; } = default!;
public string CoverImage { get; set; } = default!; // R2 URL
}
// 上传并保存
public async Task UpdateCoverImageAsync(Guid articleId, IFormFile file)
{
var article = await _repository.GetByIdAsync(articleId);
using var stream = file.OpenReadStream();
var fileName = $"{articleId}/cover{Path.GetExtension(file.FileName)}";
article.CoverImage = await _r2Client.UploadBlobAsync(stream, fileName);
await _repository.UpdateAsync(article);
}
运维建议
| 建议 | 说明 |
|---|---|
| 配置 CDN | 使用 Cloudflare CDN 加速访问 |
| 设置缓存 | 静态资源设置长缓存 |
| 监控用量 | 监控存储用量和请求量 |
| 定期清理 | 清理无用文件 |
故障排查
| 问题 | 解决方案 |
|---|---|
| 上传失败 | 检查 AccessKeyId 和 Secret |
| 403 Forbidden | 检查 Token 权限 |
| 文件不存在 | 检查 Bucket 名称 |
| 超时 | 检查网络连接 |