阿里云OSS图片上传方法说明.md 13.1 KB

阿里云OSS图片上传方法说明

文档日期:2025年1月
文件位置netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs


一、核心上传方法

1.1 标准文件上传方法

方法名Uploader
位置FileService.cs 第60-95行
接口路径POST /api/File/Uploader/{type}

功能

  • 上传文件/图片到服务器或OSS
  • 支持多种存储类型(本地、MinIO、阿里云OSS、腾讯云COS)
  • annexpic 类型强制使用阿里云OSS存储

参数

  • type:文件类型(如:annexpicavatartemporary 等)
  • file:上传的文件(IFormFile

返回值

{
  "name": "原始文件名.jpg",
  "fileId": "20250123_123456789.jpg",
  "url": "https://oss.example.com/2025/01/23/20250123_123456789.jpg?签名参数"
}

关键代码

[HttpPost("Uploader/{type}")]
[AllowAnonymous]
public async Task<dynamic> Uploader(string type, IFormFile file)
{
    // 1. 验证文件类型
    var fileType = Path.GetExtension(file.FileName).Replace(".", "");
    if (!this.AllowFileType(fileType, type))
        throw NCCException.Oh(ErrorCode.D1800);

    // 2. 生成文件路径和文件名
    var _filePath = GetPathByType(type);
    var now = DateTime.Now;
    var _fileName = now.ToString("yyyyMMdd") + "_" + YitIdHelper.NextId().ToString() + Path.GetExtension(file.FileName);

    // 3. annexpic 类型强制使用阿里云OSS存储
    string forceStoreType = type == "annexpic" ? "aliyun-oss" : null;
    string uploadFilePath = _filePath;
    if (type == "annexpic")
    {
        // 按天生成文件夹:yyyy/MM/dd
        var dateFolder = now.ToString("yyyy/MM/dd");
        uploadFilePath = dateFolder;
    }

    // 4. 上传文件
    await UploadFileByType(file, uploadFilePath, _fileName, forceStoreType);

    // 5. 获取访问URL
    string fileUrl;
    if (type == "annexpic")
    {
        fileUrl = await GetOSSAccessUrl(uploadFilePath, _fileName);
    }
    else
    {
        fileUrl = string.Format("/api/File/Image/{0}/{1}", type, _fileName);
    }

    return new { name = file.FileName, fileId = _fileName, url = fileUrl };
}

1.2 Base64图片上传方法

方法名UploadBase64Image
位置FileService.cs 第696-775行
接口路径POST /api/File/UploadBase64Image

功能

  • 上传Base64格式的图片到阿里云OSS
  • 自动解析Base64数据并提取图片格式
  • 所有类型都上传到阿里云OSS存储

参数Base64ImageUploadInput):

{
  "base64Data": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
  "fileName": "图片名称(可选)",
  "imageType": "annexpic(可选,默认为temporary)"
}

返回值

{
  "name": "图片名称.jpg",
  "fileId": "20250123_123456789.jpg",
  "url": "https://oss.example.com/2025/01/23/20250123_123456789.jpg?签名参数",
  "fileSize": 12345,
  "imageFormat": "JPEG",
  "imageType": "annexpic"
}

关键代码

[HttpPost("UploadBase64Image")]
[AllowAnonymous]
public async Task<dynamic> UploadBase64Image([FromBody] Base64ImageUploadInput input)
{
    // 1. 解析Base64数据
    var imageData = ParseBase64Data(input.Base64Data, out string imageFormat);

    // 2. 验证图片格式
    if (!IsValidImageFormat(imageFormat))
        throw NCCException.Oh($"不支持的图片格式: {imageFormat}");

    // 3. 生成文件路径和文件名
    var imageType = string.IsNullOrEmpty(input.ImageType) ? "temporary" : input.ImageType;
    var now = DateTime.Now;

    string uploadFilePath;
    string fileName;

    if (imageType == "annexpic")
    {
        fileName = now.ToString("yyyyMMdd") + "_" + YitIdHelper.NextId().ToString() + "." + imageFormat;
        var dateFolder = now.ToString("yyyy/MM/dd");
        uploadFilePath = dateFolder;
    }
    else
    {
        fileName = GenerateImageFileName(input.FileName, imageFormat);
        var originalPath = GetPathByType(imageType).TrimEnd('/').TrimEnd('\\');
        var dateFolder = now.ToString("yyyy/MM/dd");
        uploadFilePath = $"{originalPath}/{dateFolder}";
    }

    // 4. 上传到OSS
    var bucketName = KeyVariable.BucketName;
    var ossPath = $"{uploadFilePath.TrimEnd('/').TrimEnd('\\')}/{fileName}";
    using (var stream = new MemoryStream(imageData))
    {
        await _oSSServiceFactory.Create("aliyun").PutObjectAsync(bucketName, ossPath, stream);
    }

    // 5. 获取OSS访问URL
    string accessUrl = await GetOSSAccessUrl(uploadFilePath, fileName);

    return new
    {
        name = originalFileName,
        fileId = fileName,
        url = accessUrl,
        fileSize = imageData.Length,
        imageFormat = imageFormat.ToUpper(),
        imageType = imageType,
    };
}

二、核心上传逻辑

2.1 UploadFileByType 方法

位置FileService.cs 第301-344行
功能:根据存储类型上传文件

关键代码

[NonAction]
public async Task UploadFileByType(IFormFile file, string filePath, string fileName, string forceStoreType = null)
{
    var bucketName = KeyVariable.BucketName;
    var fileStoreType = !string.IsNullOrEmpty(forceStoreType) ? forceStoreType : KeyVariable.FileStoreType;

    // OSS路径使用正斜杠,不使用Path.Combine
    var uploadPath = fileStoreType == "aliyun-oss" || fileStoreType == "tencent-cos" || fileStoreType == "minio"
        ? $"{filePath.TrimEnd('/').TrimEnd('\\')}/{fileName}"
        : Path.Combine(filePath, fileName);

    var stream = file.OpenReadStream();
    switch (fileStoreType)
    {
        case "minio":
            await _oSSServiceFactory.Create().PutObjectAsync(bucketName, uploadPath, stream);
            break;
        case "aliyun-oss":
            // ✅ 阿里云OSS上传
            await _oSSServiceFactory.Create("aliyun").PutObjectAsync(bucketName, uploadPath, stream);
            break;
        case "tencent-cos":
            await _oSSServiceFactory.Create("qcloud").PutObjectAsync(bucketName, uploadPath, stream);
            break;
        default:
            // 本地存储
            if (!Directory.Exists(filePath))
                Directory.CreateDirectory(filePath);
            using (var stream4 = File.Create(uploadPath))
            {
                await file.CopyToAsync(stream4);
            }
            break;
    }
}

关键点

  • ✅ 使用 _oSSServiceFactory.Create("aliyun") 创建阿里云OSS服务
  • ✅ 使用 PutObjectAsync(bucketName, uploadPath, stream) 上传文件
  • ✅ OSS路径使用正斜杠 /,不使用 Path.Combine

2.2 GetOSSAccessUrl 方法

位置FileService.cs 第391-476行
功能:获取阿里云OSS文件的访问URL(带签名的临时访问URL)

关键代码

[NonAction]
private async Task<string> GetOSSAccessUrl(string filePath, string fileName)
{
    var bucketName = KeyVariable.BucketName;
    var uploadPath = $"{filePath.TrimEnd('/').TrimEnd('\\')}/{fileName}";

    // 使用OSS服务生成带签名的临时访问URL(有效期24小时)
    var ossService = _oSSServiceFactory.Create("aliyun");
    var presignedUrl = await ossService.PresignedGetObjectAsync(bucketName, uploadPath, 86400);

    // 获取带签名的URL字符串
    string urlString = string.Empty;
    if (presignedUrl != null)
    {
        var urlType = presignedUrl.GetType();
        var absoluteUriProp = urlType.GetProperty("AbsoluteUri");
        if (absoluteUriProp != null)
        {
            urlString = absoluteUriProp.GetValue(presignedUrl)?.ToString() ?? string.Empty;
        }
        else
        {
            urlString = presignedUrl.ToString() ?? string.Empty;
        }
    }

    // 如果配置了自定义域名,替换为自定义域名
    var customDomain = _configuration["NCC_App:AliyunOSS:CustomDomain"]
        ?? _configuration["NCC_APP:AliyunOSS:CustomDomain"];

    if (!string.IsNullOrEmpty(customDomain))
    {
        // 替换域名逻辑...
    }

    return urlString;
}

关键点

  • ✅ 使用 PresignedGetObjectAsync 生成带签名的临时访问URL
  • ✅ 有效期:86400秒(24小时)
  • ✅ 支持自定义域名配置

三、OSS服务配置

3.1 服务注册

位置Startup.cs 第109-137行

配置代码

#region 阿里云OSS

var aliyunOSSEndpoint = App.Configuration["NCC_App:AliyunOSS:Endpoint"];
var aliyunOSSAccessKey = App.Configuration["NCC_App:AliyunOSS:AccessKeyId"];
var aliyunOSSSecretKey = App.Configuration["NCC_App:AliyunOSS:AccessKeySecret"];
var aliyunOSSRegion = App.Configuration["NCC_App:AliyunOSS:Region"];
var bucketName = App.Configuration["NCC_App:BucketName"];

if (!string.IsNullOrEmpty(aliyunOSSEndpoint) && !string.IsNullOrEmpty(aliyunOSSAccessKey) && !string.IsNullOrEmpty(aliyunOSSSecretKey))
{
    services.AddOSSService("aliyun", option =>
    {
        option.Provider = OSSProvider.Aliyun;
        option.Endpoint = aliyunOSSEndpoint;  // 格式:oss-{region}.aliyuncs.com
        option.AccessKey = aliyunOSSAccessKey;
        option.SecretKey = aliyunOSSSecretKey;
        option.IsEnableHttps = true;
        option.IsEnableCache = true;
        if (!string.IsNullOrEmpty(aliyunOSSRegion))
        {
            option.Region = aliyunOSSRegion;  // 如:cn-chengdu
        }
    });
}

#endregion

3.2 配置文件

位置appsettings.json

配置项

{
  "NCC_App": {
    "AliyunOSS": {
      "Endpoint": "oss-cn-chengdu.aliyuncs.com",
      "AccessKeyId": "your-access-key-id",
      "AccessKeySecret": "your-access-key-secret",
      "Region": "cn-chengdu",
      "CustomDomain": "https://cdn.example.com"  // 可选,自定义域名
    },
    "BucketName": "your-bucket-name",
    "FileStoreType": "aliyun-oss"  // 默认存储类型
  }
}

四、使用示例

4.1 标准文件上传

前端调用

// 使用 FormData
const formData = new FormData();
formData.append('file', file);

const response = await fetch('/api/File/Uploader/annexpic', {
  method: 'POST',
  body: formData
});

const result = await response.json();
// result: { name: "原始文件名.jpg", fileId: "20250123_123456789.jpg", url: "https://..." }

curl 示例

curl -X POST "http://localhost:2011/api/File/Uploader/annexpic" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "file=@/path/to/image.jpg"

4.2 Base64图片上传

前端调用

const base64Data = "data:image/jpeg;base64,/9j/4AAQSkZJRg...";

const response = await fetch('/api/File/UploadBase64Image', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    base64Data: base64Data,
    fileName: "图片名称",
    imageType: "annexpic"
  })
});

const result = await response.json();
// result: { name: "图片名称.jpg", fileId: "20250123_123456789.jpg", url: "https://...", fileSize: 12345, imageFormat: "JPEG", imageType: "annexpic" }

curl 示例

curl -X POST "http://localhost:2011/api/File/UploadBase64Image" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "base64Data": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
    "fileName": "图片名称",
    "imageType": "annexpic"
  }'

五、文件路径规则

5.1 annexpic 类型

  • 文件夹结构yyyy/MM/dd(如:2025/01/23
  • 文件名格式yyyyMMdd_{ID}.{ext}(如:20250123_123456789.jpg
  • 完整路径2025/01/23/20250123_123456789.jpg
  • 存储类型:强制使用阿里云OSS

5.2 其他类型

  • 文件夹结构{原始路径}/yyyy/MM/dd
  • 文件名格式:根据类型生成
  • 存储类型:根据配置决定(KeyVariable.FileStoreType

六、依赖服务

6.1 IOSSServiceFactory

接口IOSSServiceFactory
实现OnceMi.AspNetCore.OSS

使用方式

// 创建阿里云OSS服务
var ossService = _oSSServiceFactory.Create("aliyun");

// 上传文件
await ossService.PutObjectAsync(bucketName, uploadPath, stream);

// 生成预签名URL
var presignedUrl = await ossService.PresignedGetObjectAsync(bucketName, uploadPath, 86400);

七、注意事项

7.1 路径格式

  • ✅ OSS路径使用正斜杠 /,不使用 Path.Combine
  • ✅ 路径格式:{filePath}/{fileName}

7.2 文件命名

  • ✅ 文件名格式:yyyyMMdd_{ID}.{ext}
  • ✅ 使用 YitIdHelper.NextId() 生成唯一ID

7.3 访问URL

  • ✅ 返回带签名的临时访问URL(有效期24小时)
  • ✅ 支持自定义域名配置
  • ✅ 如果生成失败,返回相对路径作为降级方案

7.4 错误处理

  • ✅ 上传失败时抛出异常,包含详细错误信息
  • ✅ URL生成失败时返回相对路径

八、相关文件

  • 主服务文件netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs
  • 服务注册netcore/src/Application/NCC.API.Core/Startup.cs
  • 配置文件netcore/src/Application/NCC.API/appsettings.json
  • 依赖库OnceMi.AspNetCore.OSS

文档完成时间:2025年1月
文档状态:✅ 已完成