2026-04-09 15:16:43 +08:00
|
|
|
using YD_Event.Application.Common.Dtos;
|
2025-12-17 11:12:13 +08:00
|
|
|
using YD_Event.Core.Options;
|
2025-12-17 10:32:52 +08:00
|
|
|
using Microsoft.AspNetCore.Hosting;
|
|
|
|
|
using Microsoft.Extensions.Options;
|
2026-03-20 11:59:26 +08:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using YD_Event.Core.Infrastructure.Aliyun.Oss;
|
2025-12-17 10:32:52 +08:00
|
|
|
|
2026-04-09 15:16:43 +08:00
|
|
|
namespace YD_Event.Application.Common;
|
2025-12-17 10:32:52 +08:00
|
|
|
|
2026-04-09 15:16:43 +08:00
|
|
|
[ApiDescriptionSettings("通用服务")]
|
2025-12-17 10:32:52 +08:00
|
|
|
public class FileService : IDynamicApiController, ITransient
|
|
|
|
|
{
|
|
|
|
|
private readonly IOptionsMonitor<OssConnectionOptions> _ossOptionsMonitor;
|
|
|
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
|
|
|
private readonly IWebHostEnvironment _webHostEnvironment;
|
|
|
|
|
private readonly IIdGenerator _idGenerator;
|
2026-03-20 11:59:26 +08:00
|
|
|
private readonly OssService _ossService;
|
2025-12-17 10:32:52 +08:00
|
|
|
|
2026-03-20 11:59:26 +08:00
|
|
|
public FileService(
|
2025-12-17 10:32:52 +08:00
|
|
|
IOptionsMonitor<OssConnectionOptions> ossOptionsMonitor,
|
|
|
|
|
IHttpContextAccessor httpContextAccessor,
|
|
|
|
|
IWebHostEnvironment webHostEnvironment,
|
2026-03-20 11:59:26 +08:00
|
|
|
IIdGenerator idGenerator,
|
|
|
|
|
OssService ossService)
|
2025-12-17 10:32:52 +08:00
|
|
|
{
|
|
|
|
|
_ossOptionsMonitor = ossOptionsMonitor;
|
|
|
|
|
_httpContextAccessor = httpContextAccessor;
|
|
|
|
|
_webHostEnvironment = webHostEnvironment;
|
|
|
|
|
_idGenerator = idGenerator;
|
2026-03-20 11:59:26 +08:00
|
|
|
_ossService = ossService;
|
2025-12-17 10:32:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 上传附件
|
|
|
|
|
/// </summary>
|
2026-03-20 11:59:26 +08:00
|
|
|
/// <param name="file">表单文件</param>
|
|
|
|
|
/// <returns>返回上传文件信息</returns>
|
2025-12-17 10:32:52 +08:00
|
|
|
[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("无效文件");
|
|
|
|
|
}
|
2026-03-20 11:59:26 +08:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2025-12-17 10:32:52 +08:00
|
|
|
if (!options.Enable)
|
|
|
|
|
{
|
2026-03-20 11:59:26 +08:00
|
|
|
// 本地存储
|
|
|
|
|
string basePath = Path.Combine(_webHostEnvironment.WebRootPath, options.Bucket ?? "uploads");
|
|
|
|
|
string fullPath = Path.Combine(basePath, fileName);
|
|
|
|
|
string dir = Path.GetDirectoryName(fullPath);
|
|
|
|
|
if (!Directory.Exists(dir))
|
2025-12-17 10:32:52 +08:00
|
|
|
{
|
2026-03-20 11:59:26 +08:00
|
|
|
Directory.CreateDirectory(dir);
|
2025-12-17 10:32:52 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 11:59:26 +08:00
|
|
|
using var fileStream = System.IO.File.Create(fullPath);
|
|
|
|
|
await stream.CopyToAsync(fileStream);
|
|
|
|
|
|
|
|
|
|
var request = _httpContextAccessor.HttpContext?.Request;
|
|
|
|
|
if (request != null)
|
2025-12-17 10:32:52 +08:00
|
|
|
{
|
2026-03-20 11:59:26 +08:00
|
|
|
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))
|
2025-12-17 10:32:52 +08:00
|
|
|
{
|
2026-03-20 11:59:26 +08:00
|
|
|
objectName = url.Substring(domainUrl.Length);
|
2025-12-17 10:32:52 +08:00
|
|
|
}
|
2026-03-20 11:59:26 +08:00
|
|
|
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)
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-12-17 10:32:52 +08:00
|
|
|
}
|
2026-03-20 11:59:26 +08:00
|
|
|
catch (Exception ex)
|
2025-12-17 10:32:52 +08:00
|
|
|
{
|
2026-03-20 11:59:26 +08:00
|
|
|
throw Oops.Bah($"下载失败: {ex.Message}");
|
2025-12-17 10:32:52 +08:00
|
|
|
}
|
2026-03-20 11:59:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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
|
2025-12-17 10:32:52 +08:00
|
|
|
{
|
2026-03-20 11:59:26 +08:00
|
|
|
if (!options.Enable)
|
2025-12-17 10:32:52 +08:00
|
|
|
{
|
2026-03-20 11:59:26 +08:00
|
|
|
// 本地删除
|
|
|
|
|
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);
|
|
|
|
|
}
|
2025-12-17 10:32:52 +08:00
|
|
|
}
|
2026-03-20 11:59:26 +08:00
|
|
|
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}");
|
|
|
|
|
}
|
2025-12-17 10:32:52 +08:00
|
|
|
}
|
2026-04-09 15:16:43 +08:00
|
|
|
}
|