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;
}
}
}