using Aliyun.OSS; namespace WenChuanyi.Api.Services; /// OSS 存储服务:对象键构造、流式上传/下载/删除(后端中转,前端不接触凭据) public class OssStorageService { private readonly OssClient _client; private readonly string _bucket; private readonly string _baseKey; private readonly ILogger _logger; public OssStorageService(IConfiguration cfg, ILogger logger) { var endpoint = cfg["Oss:Endpoint"] ?? throw new InvalidOperationException("缺少配置 Oss:Endpoint"); var ak = cfg["Oss:AccessKeyId"] ?? throw new InvalidOperationException("缺少配置 Oss:AccessKeyId"); var sk = cfg["Oss:AccessKeySecret"] ?? throw new InvalidOperationException("缺少配置 Oss:AccessKeySecret"); _bucket = cfg["Oss:Bucket"] ?? throw new InvalidOperationException("缺少配置 Oss:Bucket"); _baseKey = cfg["Oss:BaseKey"] ?? "文传易2026"; _client = new OssClient(endpoint, ak, sk); _logger = logger; } /// 构造对象键:文传易2026/{yyyyMM}/{Guid}{ext}(按上传月份建子目录) public string BuildObjectKey(string originalName) { var month = DateTime.Now.ToString("yyyyMM"); var ext = Path.GetExtension(originalName).ToLowerInvariant(); return $"{_baseKey}/{month}/{Guid.NewGuid():N}{ext}"; } /// 流式上传(不落本地、不整体读内存)。ContentType 会去除参数部分(如 ";charset=utf-8"), /// 因为 OSS SDK 对带参数的 Content-Type 签名会与服务端校验不一致,导致 403 签名不匹配。 public async Task UploadAsync(string objectKey, Stream stream, string contentType) { var safeContentType = (contentType ?? "application/octet-stream").Split(';')[0].Trim(); if (string.IsNullOrEmpty(safeContentType)) safeContentType = "application/octet-stream"; var req = new PutObjectRequest(_bucket, objectKey, stream); req.Metadata = new ObjectMetadata { ContentType = safeContentType }; await Task.Run(() => _client.PutObject(req)).ConfigureAwait(false); } /// 获取对象(支持 Range;end 为 -1 表示到文件末尾) public OssObject GetObject(string objectKey, long? start = null, long? end = null) { var req = new GetObjectRequest(_bucket, objectKey); if (start.HasValue || end.HasValue) { req.SetRange(start ?? 0, end ?? -1); } return _client.GetObject(req); } /// 删除对象(失败不抛出,由调用方记录日志) public bool TryDeleteObject(string objectKey) { try { _client.DeleteObject(_bucket, objectKey); return true; } catch (Exception ex) { _logger.LogError(ex, "OSS 对象删除失败:{ObjectKey}", objectKey); return false; } } }