2026-04-09 15:16:43 +08:00

221 lines
7.3 KiB
C#

using YD_Event.Application.Common.Dtos;
using YD_Event.Core.Options;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Mvc;
using YD_Event.Core.Infrastructure.Aliyun.Oss;
namespace YD_Event.Application.Common;
[ApiDescriptionSettings("通用服务")]
public class FileService : IDynamicApiController, ITransient
{
private readonly IOptionsMonitor<OssConnectionOptions> _ossOptionsMonitor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IWebHostEnvironment _webHostEnvironment;
private readonly IIdGenerator _idGenerator;
private readonly OssService _ossService;
public FileService(
IOptionsMonitor<OssConnectionOptions> ossOptionsMonitor,
IHttpContextAccessor httpContextAccessor,
IWebHostEnvironment webHostEnvironment,
IIdGenerator idGenerator,
OssService ossService)
{
_ossOptionsMonitor = ossOptionsMonitor;
_httpContextAccessor = httpContextAccessor;
_webHostEnvironment = webHostEnvironment;
_idGenerator = idGenerator;
_ossService = ossService;
}
/// <summary>
/// 上传附件
/// </summary>
/// <param name="file">表单文件</param>
/// <returns>返回上传文件信息</returns>
[NonUnify]
public async Task<List<UploadFileOutput>> Upload([Required] IFormFile file)
{
if (file is null or { Length: 0 })
{
throw Oops.Oh("请上传文件");
}
var options = _ossOptionsMonitor.CurrentValue;
var now = DateTime.Today;
string name = _idGenerator.Encode(_idGenerator.NewLong());
string extension = Path.GetExtension(file.FileName);
if (string.IsNullOrWhiteSpace(extension))
{
throw Oops.Bah("无效文件");
}
string filePath = $"{now.Year}/{now.Month:D2}/{now.Day:D2}/";
string fileName = $"{filePath}{name}{extension}";
using var stream = file.OpenReadStream();
string url = await UploadStreamInternal(fileName, stream, options);
return new List<UploadFileOutput>()
{
new()
{
Name = $"{name}{extension}",
Url = url
}
};
}
/// <summary>
/// 上传文件流 (供其他服务调用)
/// </summary>
/// <param name="fileName">文件名及路径</param>
/// <param name="stream">文件流</param>
/// <returns>返回完整 URL</returns>
[NonAction]
public async Task<string> UploadFileStreamAsync(string fileName, Stream stream)
{
var options = _ossOptionsMonitor.CurrentValue;
return await UploadStreamInternal(fileName, stream, options);
}
/// <summary>
/// 内部上传文件流
/// </summary>
private async Task<string> UploadStreamInternal(string fileName, Stream stream, OssConnectionOptions options)
{
if (!options.Enable)
{
// 本地存储
string basePath = Path.Combine(_webHostEnvironment.WebRootPath, options.Bucket ?? "uploads");
string fullPath = Path.Combine(basePath, fileName);
string dir = Path.GetDirectoryName(fullPath);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
using var fileStream = System.IO.File.Create(fullPath);
await stream.CopyToAsync(fileStream);
var request = _httpContextAccessor.HttpContext?.Request;
if (request != null)
{
return $"{request.Scheme}://{request.Host.Value}/{(options.Bucket ?? "uploads")}/{fileName}";
}
return $"/{(options.Bucket ?? "uploads")}/{fileName}";
}
else
{
// OSS 上传
return await _ossService.PutObjectAsync(fileName, stream);
}
}
/// <summary>
/// 下载文件
/// </summary>
/// <param name="url">文件 URL</param>
/// <returns>文件流结果</returns>
[HttpGet("download")]
public IActionResult Download([FromQuery] string url)
{
if (string.IsNullOrWhiteSpace(url)) throw Oops.Bah("参数错误");
var options = _ossOptionsMonitor.CurrentValue;
try
{
if (!options.Enable)
{
// 本地下载
var uri = new Uri(url);
string localPath = Path.Combine(_webHostEnvironment.WebRootPath, uri.AbsolutePath.TrimStart('/'));
if (!System.IO.File.Exists(localPath)) throw Oops.Bah("文件不存在");
var stream = System.IO.File.OpenRead(localPath);
return new FileStreamResult(stream, "application/octet-stream")
{
FileDownloadName = Path.GetFileName(localPath)
};
}
else
{
// OSS 下载
// 解析 objectName (移除 Domain 部分)
string domainUrl = options.Domain.TrimEnd('/') + "/";
string objectName = url;
if (url.StartsWith(domainUrl, StringComparison.OrdinalIgnoreCase))
{
objectName = url.Substring(domainUrl.Length);
}
else
{
var uri = new Uri(url);
objectName = uri.AbsolutePath.TrimStart('/');
}
var contentStream = _ossService.GetObject(objectName);
return new FileStreamResult(contentStream, "application/octet-stream")
{
FileDownloadName = Path.GetFileName(objectName)
};
}
}
catch (Exception ex)
{
throw Oops.Bah($"下载失败: {ex.Message}");
}
}
/// <summary>
/// 删除文件
/// </summary>
/// <param name="url">文件 URL</param>
[HttpDelete("delete")]
public IActionResult Delete([FromQuery] string url)
{
if (string.IsNullOrWhiteSpace(url)) throw Oops.Bah("参数错误");
var options = _ossOptionsMonitor.CurrentValue;
try
{
if (!options.Enable)
{
// 本地删除
var uri = new Uri(url);
string localPath = Path.Combine(_webHostEnvironment.WebRootPath, uri.AbsolutePath.TrimStart('/'));
if (System.IO.File.Exists(localPath))
{
System.IO.File.Delete(localPath);
}
}
else
{
// OSS 删除
string domainUrl = options.Domain.TrimEnd('/') + "/";
string objectName = url;
if (url.StartsWith(domainUrl, StringComparison.OrdinalIgnoreCase))
{
objectName = url.Substring(domainUrl.Length);
}
else
{
var uri = new Uri(url);
objectName = uri.AbsolutePath.TrimStart('/');
}
_ossService.DeleteObject(objectName);
}
return new JsonResult(new { success = true, msg = "删除成功" });
}
catch (Exception ex)
{
throw Oops.Bah($"删除失败: {ex.Message}");
}
}
}