ASP.NET Core 集成腾讯云 COS 实现文件上传下载完整指南

本文详细介绍如何在 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
  }
}

配置说明

  • SecretIdSecretKey:腾讯云 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'

解决方案

  1. 确保在 Program.cs 中注册了服务:

    csharp 复制代码
    builder.Services.AddScoped<ICosFileService, CosFileService>();
  2. 确保安装了 NuGet 包:

    bash 复制代码
    dotnet add package Tencent.QCloud.Cos.Sdk --version 5.4.51
  3. 确保 CosFileService.cs 没有被注释

7.2 上传失败

可能原因

  • SecretId 或 SecretKey 配置错误
  • 存储桶名称格式不正确(应为 bucket-name-appid
  • 地域配置错误
  • 存储桶权限不足

排查步骤

  1. 检查 appsettings.json 配置是否正确
  2. 在腾讯云控制台验证密钥和存储桶信息
  3. 查看后端日志获取详细错误信息

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,实现文件上传、下载和删除功能。主要内容包括:

  1. ✅ 安装和配置 COS SDK
  2. ✅ 实现 COS 服务接口和实现类
  3. ✅ 创建文件上传控制器
  4. ✅ 使用私有桶 + 临时签名 URL 保证安全性
  5. ✅ 文件类型和大小验证
  6. ✅ 常见问题排查和性能优化建议

通过本方案,你可以快速在项目中集成云存储功能,提升应用的可扩展性和可靠性。

参考资料

相关推荐
fangcaojushi1 小时前
cos文件存储
云计算·腾讯云
testpassportcn3 小时前
AWS DVA-C02 考試完整介紹 |AWS Certified Developer Associate 最新考試內容
云计算·aws
sun03223 小时前
【AWS】【secret】Java从AWS Secrets Manager获取指定secret中特定key的值
云计算·aws
予枫的编程笔记3 小时前
【Docker基础篇】从0到1写Dockerfile:FROM/COPY/CMD/ENTRYPOINT指令详解+Hello World实战
人工智能·docker·云计算·dockerfile·容器技术·docker入门·docker实战
henry1010104 小时前
Debian/Ubuntu EC2实例上一键部署WireGuard
ubuntu·云计算·debian·aws
人间打气筒(Ada)4 小时前
k8s:认证、授权、准入控制
云原生·容器·kubernetes·云计算·k8s认证·k8s授权·k8s准入控制
主机哥哥12 小时前
还不会部署OpenClaw?阿里云推出五种OpenClaw快速部署方案
阿里云·云计算
Re.不晚16 小时前
可视化大数据——淘宝母婴购物数据【含详细代码】
大数据·阿里云·云计算
zhougl99619 小时前
云计算超详细介绍
云计算