- 后端 .NET 8 + FreeSql + 阿里云 OSS,支持共享/私密/标签三种文件模式 - 前端 Vue3 + Vite + TDesign,上传/取件/管理三页 - 取件码/二维码/海报合成(含保存二维码为图片) - deploy/ 部署文档与一键 FTP 发布脚本 - appsettings.json 含真实凭据,已 gitignore 不入库
69 lines
3.0 KiB
C#
69 lines
3.0 KiB
C#
using Aliyun.OSS;
|
||
|
||
namespace WenChuanyi.Api.Services;
|
||
|
||
/// <summary>OSS 存储服务:对象键构造、流式上传/下载/删除(后端中转,前端不接触凭据)</summary>
|
||
public class OssStorageService
|
||
{
|
||
private readonly OssClient _client;
|
||
private readonly string _bucket;
|
||
private readonly string _baseKey;
|
||
private readonly ILogger<OssStorageService> _logger;
|
||
|
||
public OssStorageService(IConfiguration cfg, ILogger<OssStorageService> 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;
|
||
}
|
||
|
||
/// <summary>构造对象键:文传易2026/{yyyyMM}/{Guid}{ext}(按上传月份建子目录)</summary>
|
||
public string BuildObjectKey(string originalName)
|
||
{
|
||
var month = DateTime.Now.ToString("yyyyMM");
|
||
var ext = Path.GetExtension(originalName).ToLowerInvariant();
|
||
return $"{_baseKey}/{month}/{Guid.NewGuid():N}{ext}";
|
||
}
|
||
|
||
/// <summary>流式上传(不落本地、不整体读内存)。ContentType 会去除参数部分(如 ";charset=utf-8"),
|
||
/// 因为 OSS SDK 对带参数的 Content-Type 签名会与服务端校验不一致,导致 403 签名不匹配。</summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>获取对象(支持 Range;end 为 -1 表示到文件末尾)</summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>删除对象(失败不抛出,由调用方记录日志)</summary>
|
||
public bool TryDeleteObject(string objectKey)
|
||
{
|
||
try
|
||
{
|
||
_client.DeleteObject(_bucket, objectKey);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "OSS 对象删除失败:{ObjectKey}", objectKey);
|
||
return false;
|
||
}
|
||
}
|
||
}
|