feat: 文传易匿名临时文件传输站初始版本
- 后端 .NET 8 + FreeSql + 阿里云 OSS,支持共享/私密/标签三种文件模式 - 前端 Vue3 + Vite + TDesign,上传/取件/管理三页 - 取件码/二维码/海报合成(含保存二维码为图片) - deploy/ 部署文档与一键 FTP 发布脚本 - appsettings.json 含真实凭据,已 gitignore 不入库
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user