本文详细介绍如何在 ASP.NET Core 8.0 项目中集成腾讯云对象存储(COS),实现文件上传、下载和删除功能。
一、前言
在现代 Web 应用中,文件存储是一个常见需求。相比传统的本地文件存储,云存储服务具有以下优势:
- 高可用性:多地域容灾备份
- 高扩展性:按需扩容,无需担心磁盘空间
- CDN 加速:全球加速访问
- 安全可靠:权限控制、防盗链、临时签名 URL
本文将以腾讯云 COS 为例,展示如何在 ASP.NET Core 项目中实现完整的文件上传下载功能。
二、技术栈
- 后端框架:ASP.NET Core 8.0
- 云存储:腾讯云 COS(Cloud Object Storage)
- SDK:Tencent.QCloud.Cos.Sdk v5.4.51
- 架构模式:DDD 分层架构
##三、项目结构
backend/
├── RequirementManagement.API/ # API 层
│ ├── Controllers/
│ │ ├── UploadController.cs # 文件上传控制器
│ │ └── RequirementsController.cs # 需求管理控制器
│ ├── Program.cs # 依赖注入配置
│ └── appsettings.json # COS 配置
├── RequirementManagement.Application/ # 应用层
│ └── Interfaces/
│ └── ICosFileService.cs # COS 服务接口
└── RequirementManagement.Infrastructure/ # 基础设施层
├── Configuration/
│ └── CosSettings.cs # COS 配置类
└── Services/
└── CosFileService.cs # COS 服务实现
四、实现步骤
4.1 安装 NuGet 包
在 RequirementManagement.Infrastructure 项目中安装腾讯云 COS SDK:
bash
dotnet add package Tencent.QCloud.Cos.Sdk --version 5.4.51
或在 .csproj 文件中添加:
xml
<PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.51" />
4.2 配置 COS 参数
在 appsettings.json 中添加 COS 配置:
json
{
"CosSettings": {
"SecretId": "your-secret-id",
"SecretKey": "your-secret-key",
"Region": "ap-guangzhou",
"Bucket": "your-bucket-name-appid",
"BasePath": "internal-platform/requirements/",
"UrlExpireSeconds": 600
}
}
配置说明:
SecretId和SecretKey:腾讯云 API 密钥(在腾讯云控制台获取)Region:存储桶所在地域(如 ap-guangzhou、ap-beijing)Bucket:存储桶名称(格式:bucket-name-appid)BasePath:文件存储的基础路径UrlExpireSeconds:临时签名 URL 的有效期(秒)
4.3 创建配置类
在 Infrastructure/Configuration/CosSettings.cs:
csharp
namespace RequirementManagement.Infrastructure.Configuration;
/// <summary>
/// 腾讯云 COS 配置
/// </summary>
public class CosSettings
{
/// <summary>
/// 腾讯云 SecretId
/// </summary>
public string SecretId { get; set; } = string.Empty;
/// <summary>
/// 腾讯云 SecretKey
/// </summary>
public string SecretKey { get; set; } = string.Empty;
/// <summary>
/// COS 地域
/// </summary>
public string Region { get; set; } = string.Empty;
/// <summary>
/// COS 存储桶名称
/// </summary>
public string Bucket { get; set; } = string.Empty;
/// <summary>
/// 文件存储基础路径
/// </summary>
public string BasePath { get; set; } = string.Empty;
/// <summary>
/// 临时 URL 有效期(秒)
/// </summary>
public int UrlExpireSeconds { get; set; } = 600;
}
4.4 定义服务接口
在 Application/Interfaces/ICosFileService.cs:
csharp
namespace RequirementManagement.Application.Interfaces;
/// <summary>
/// COS 文件存储服务接口
/// </summary>
public interface ICosFileService
{
/// <summary>
/// 上传文件到 COS
/// </summary>
/// <param name="stream">文件流</param>
/// <param name="fileName">文件名</param>
/// <param name="contentType">文件类型</param>
/// <returns>COS 对象路径</returns>
Task<string> UploadFileAsync(Stream stream, string fileName, string contentType);
/// <summary>
/// 获取文件临时访问 URL
/// </summary>
/// <param name="filePath">COS 对象路径</param>
/// <returns>临时访问 URL</returns>
Task<string> GetFileUrlAsync(string filePath);
/// <summary>
/// 删除文件
/// </summary>
/// <param name="filePath">COS 对象路径</param>
Task DeleteFileAsync(string filePath);
}
4.5 实现 COS 服务
在 Infrastructure/Services/CosFileService.cs:
csharp
using COSXML;
using COSXML.Auth;
using COSXML.Model.Object;
using COSXML.Model.Tag;
using Microsoft.Extensions.Options;
using RequirementManagement.Application.Interfaces;
using RequirementManagement.Infrastructure.Configuration;
namespace RequirementManagement.Infrastructure.Services;
/// <summary>
/// 腾讯云 COS 文件存储服务
/// </summary>
public class CosFileService : ICosFileService
{
private readonly CosSettings _cosSettings;
private readonly CosXml _cosClient;
public CosFileService(IOptions<CosSettings> cosSettings)
{
_cosSettings = cosSettings.Value;
// 初始化 COS 配置
var config = new CosXmlConfig.Builder()
.SetRegion(_cosSettings.Region)
.SetDebugLog(false)
.Build();
// 初始化密钥信息
var credential = new DefaultQCloudCredentialProvider(
_cosSettings.SecretId,
_cosSettings.SecretKey,
600);
// 初始化 COS 客户端
_cosClient = new CosXmlServer(config, credential);
}
/// <summary>
/// 上传文件到 COS
/// </summary>
public async Task<string> UploadFileAsync(Stream stream, string fileName, string contentType)
{
try
{
// 生成唯一文件名
var fileExtension = Path.GetExtension(fileName);
var uniqueFileName = $"{Guid.NewGuid()}{fileExtension}";
var objectKey = $"{_cosSettings.BasePath}{uniqueFileName}";
// 创建上传请求
var putObjectRequest = new PutObjectRequest(_cosSettings.Bucket, objectKey, stream);
// 执行上传
var result = _cosClient.PutObject(putObjectRequest);
if (result.IsSuccessful())
{
return objectKey;
}
throw new Exception($"上传文件到 COS 失败: {result.GetResultInfo()}");
}
catch (Exception ex)
{
throw new Exception($"上传文件到 COS 异常: {ex.Message}", ex);
}
}
/// <summary>
/// 获取文件临时访问 URL
/// </summary>
public Task<string> GetFileUrlAsync(string filePath)
{
try
{
// 使用 PreSignatureStruct 生成预签名 URL
var preSignatureStruct = new PreSignatureStruct
{
appid = _cosSettings.Bucket.Split('-').Last(),
region = _cosSettings.Region,
bucket = _cosSettings.Bucket,
key = filePath,
httpMethod = "GET",
isHttps = true,
signDurationSecond = _cosSettings.UrlExpireSeconds,
headers = null,
queryParameters = null
};
// 生成预签名 URL
var url = _cosClient.GenerateSignURL(preSignatureStruct);
return Task.FromResult(url);
}
catch (Exception ex)
{
throw new Exception($"获取文件 URL 异常: {ex.Message}", ex);
}
}
/// <summary>
/// 删除文件
/// </summary>
public async Task DeleteFileAsync(string filePath)
{
try
{
// 创建删除请求
var deleteObjectRequest = new DeleteObjectRequest(_cosSettings.Bucket, filePath);
// 执行删除
var result = _cosClient.DeleteObject(deleteObjectRequest);
if (!result.IsSuccessful())
{
throw new Exception($"删除文件失败: {result.GetResultInfo()}");
}
await Task.CompletedTask;
}
catch (Exception ex)
{
throw new Exception($"删除文件异常: {ex.Message}", ex);
}
}
}
4.6 注册服务
在 Program.cs 中注册服务:
csharp
// 配置 COS 设置
builder.Services.Configure<CosSettings>(
builder.Configuration.GetSection("CosSettings"));
// 注册 COS 文件服务
builder.Services.AddScoped<ICosFileService, CosFileService>();
4.7 创建上传控制器
在 API/Controllers/UploadController.cs:
csharp
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RequirementManagement.Application.DTOs;
using RequirementManagement.Application.Interfaces;
namespace RequirementManagement.API.Controllers;
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class UploadController : ControllerBase
{
private readonly ICosFileService _cosFileService;
private readonly ILogger<UploadController> _logger;
public UploadController(
ICosFileService cosFileService,
ILogger<UploadController> logger)
{
_cosFileService = cosFileService;
_logger = logger;
}
/// <summary>
/// 上传图片(用于富文本编辑器)
/// </summary>
[HttpPost("image")]
public async Task<ActionResult<ApiResponse<string>>> UploadImage(IFormFile file)
{
try
{
if (file == null || file.Length == 0)
{
return BadRequest(ApiResponse<string>.ErrorResponse("请选择要上传的文件"));
}
// 验证文件类型
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp" };
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(extension))
{
return BadRequest(ApiResponse<string>.ErrorResponse(
"只支持图片格式:jpg, jpeg, png, gif, bmp, webp"));
}
// 验证文件大小(最大 10MB)
if (file.Length > 10 * 1024 * 1024)
{
return BadRequest(ApiResponse<string>.ErrorResponse("图片大小不能超过 10MB"));
}
// 上传到 COS
string cosFilePath;
using (var stream = file.OpenReadStream())
{
cosFilePath = await _cosFileService.UploadFileAsync(
stream, file.FileName, file.ContentType);
}
// 获取临时访问 URL
var fileUrl = await _cosFileService.GetFileUrlAsync(cosFilePath);
_logger.LogInformation("图片上传到 COS 成功: {FileName}, COS Path: {CosPath}",
file.FileName, cosFilePath);
return Ok(ApiResponse<string>.SuccessResponse(fileUrl, "上传成功"));
}
catch (Exception ex)
{
_logger.LogError(ex, "上传图片到 COS 时发生错误");
return StatusCode(500, ApiResponse<string>.ErrorResponse("上传失败", 500));
}
}
}
五、使用示例
5.1 在业务控制器中使用
csharp
public class RequirementsController : ControllerBase
{
private readonly ICosFileService _cosFileService;
public RequirementsController(ICosFileService cosFileService)
{
_cosFileService = cosFileService;
}
/// <summary>
/// 上传需求附件
/// </summary>
[HttpPost("{id}/attachments")]
public async Task<IActionResult> UploadAttachment(long id, IFormFile file)
{
// 上传文件到 COS
string cosFilePath;
using (var stream = file.OpenReadStream())
{
cosFilePath = await _cosFileService.UploadFileAsync(
stream, file.FileName, file.ContentType);
}
// 获取临时访问 URL
var fileUrl = await _cosFileService.GetFileUrlAsync(cosFilePath);
// 保存附件信息到数据库
// ...
return Ok(new { url = fileUrl, path = cosFilePath });
}
/// <summary>
/// 删除需求附件
/// </summary>
[HttpDelete("attachments/{id}")]
public async Task<IActionResult> DeleteAttachment(long id)
{
// 从数据库获取附件信息
var attachment = await _attachmentRepository.GetByIdAsync(id);
// 从 COS 删除文件
await _cosFileService.DeleteFileAsync(attachment.FilePath);
// 从数据库删除记录
// ...
return Ok();
}
}
5.2 前端调用示例
typescript
// 上传图片
async function uploadImage(file: File): Promise<string> {
const formData = new FormData()
formData.append('file', file)
const response = await axios.post('/api/upload/image', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
return response.data.data // 返回图片 URL
}
// 使用示例
const file = event.target.files[0]
const imageUrl = await uploadImage(file)
console.log('图片 URL:', imageUrl)
六、核心特性
6.1 私有桶 + 临时签名 URL
本方案使用私有存储桶 配合临时签名 URL,具有以下优势:
- 安全性高:文件不公开访问,防止盗链
- 权限可控:通过签名 URL 控制访问权限
- 时效性:URL 有效期可配置(默认 10 分钟)
6.2 唯一文件名生成
使用 Guid.NewGuid() 生成唯一文件名,避免文件名冲突:
csharp
var uniqueFileName = $"{Guid.NewGuid()}{fileExtension}";
var objectKey = $"{_cosSettings.BasePath}{uniqueFileName}";
生成的文件路径示例:
internal-platform/requirements/a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpg
6.3 文件类型和大小验证
在上传前进行验证,提高安全性:
csharp
// 验证文件类型
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp" };
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(extension))
{
return BadRequest("不支持的文件类型");
}
// 验证文件大小(最大 10MB)
if (file.Length > 10 * 1024 * 1024)
{
return BadRequest("文件大小不能超过 10MB");
}
七、常见问题
7.1 依赖注入失败
错误信息:
Unable to resolve service for type 'ICosFileService' while attempting to activate 'RequirementsController'
解决方案:
-
确保在
Program.cs中注册了服务:csharpbuilder.Services.AddScoped<ICosFileService, CosFileService>(); -
确保安装了 NuGet 包:
bashdotnet add package Tencent.QCloud.Cos.Sdk --version 5.4.51 -
确保
CosFileService.cs没有被注释
7.2 上传失败
可能原因:
- SecretId 或 SecretKey 配置错误
- 存储桶名称格式不正确(应为
bucket-name-appid) - 地域配置错误
- 存储桶权限不足
排查步骤:
- 检查
appsettings.json配置是否正确 - 在腾讯云控制台验证密钥和存储桶信息
- 查看后端日志获取详细错误信息
7.3 临时 URL 过期
临时签名 URL 有有效期限制(默认 10 分钟),过期后需要重新获取:
csharp
// 重新获取临时 URL
var newUrl = await _cosFileService.GetFileUrlAsync(filePath);
八、性能优化建议
8.1 使用 CDN 加速
在腾讯云控制台为存储桶配置 CDN 加速域名,提升文件访问速度。
8.2 异步上传
对于大文件,使用异步上传避免阻塞:
csharp
// 使用 Task.Run 在后台线程上传
await Task.Run(async () =>
{
await _cosFileService.UploadFileAsync(stream, fileName, contentType);
});
8.3 分片上传
对于超大文件(>100MB),建议使用分片上传:
csharp
// 使用 TransferManager 进行分片上传
var transferManager = new TransferManager(_cosClient, new TransferConfig());
var uploadTask = new COSXMLUploadTask(bucket, objectKey);
await transferManager.UploadAsync(uploadTask);
九、安全建议
9.1 密钥安全
- ❌ 不要将密钥硬编码在代码中
- ❌ 不要将
appsettings.json提交到 Git 仓库 - ✅ 使用环境变量或密钥管理服务
- ✅ 定期轮换密钥
9.2 访问控制
- 使用私有存储桶,避免公开访问
- 通过临时签名 URL 控制访问权限
- 设置合理的 URL 有效期
9.3 文件验证
- 验证文件类型和大小
- 检查文件内容,防止恶意文件上传
- 使用病毒扫描服务
十、总结
本文详细介绍了如何在 ASP.NET Core 项目中集成腾讯云 COS,实现文件上传、下载和删除功能。主要内容包括:
- ✅ 安装和配置 COS SDK
- ✅ 实现 COS 服务接口和实现类
- ✅ 创建文件上传控制器
- ✅ 使用私有桶 + 临时签名 URL 保证安全性
- ✅ 文件类型和大小验证
- ✅ 常见问题排查和性能优化建议
通过本方案,你可以快速在项目中集成云存储功能,提升应用的可扩展性和可靠性。
参考资料
- 腾讯云 COS 官方文档
- COS .NET SDK 文档
-
ASP.NET Core 依赖注入\](