mi-assessment/server/MiAssessment/src/MiAssessment.Admin.Business/Services/Storage/LocalStorageProvider.cs
2026-02-03 14:25:01 +08:00

139 lines
4.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using MiAssessment.Admin.Business.Models.Upload;
using MiAssessment.Admin.Business.Services.Interfaces;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
namespace MiAssessment.Admin.Business.Services.Storage;
/// <summary>
/// 本地存储提供者
/// 将文件保存到 wwwroot/uploads/{yyyy}/{MM}/{dd}/ 目录
/// </summary>
public class LocalStorageProvider : IStorageProvider
{
private readonly IWebHostEnvironment _environment;
private readonly ILogger<LocalStorageProvider> _logger;
private const string UploadBasePath = "uploads";
public LocalStorageProvider(
IWebHostEnvironment environment,
ILogger<LocalStorageProvider> logger)
{
_environment = environment;
_logger = logger;
}
/// <inheritdoc />
public string StorageType => "1";
/// <inheritdoc />
public bool SupportsDirectUpload => false;
/// <inheritdoc />
public async Task<UploadResult> UploadAsync(Stream fileStream, string fileName, string contentType)
{
try
{
// 生成日期目录路径: uploads/2026/01/19/
var now = DateTime.Now;
var datePath = Path.Combine(
now.Year.ToString(),
now.Month.ToString("D2"),
now.Day.ToString("D2"));
// 生成唯一文件名
var uniqueFileName = GenerateUniqueFileName(fileName);
// 构建完整的物理路径
var relativePath = Path.Combine(UploadBasePath, datePath);
var physicalPath = Path.Combine(_environment.WebRootPath, relativePath);
// 确保目录存在
EnsureDirectoryExists(physicalPath);
// 完整文件路径
var fullFilePath = Path.Combine(physicalPath, uniqueFileName);
// 保存文件
await using var fileStreamOutput = new FileStream(fullFilePath, FileMode.Create, FileAccess.Write);
await fileStream.CopyToAsync(fileStreamOutput);
// 生成相对URL路径 (使用正斜杠)
var url = $"/{UploadBasePath}/{datePath.Replace(Path.DirectorySeparatorChar, '/')}/{uniqueFileName}";
_logger.LogInformation("本地存储上传成功: {FileName} -> {Url}", fileName, url);
return UploadResult.Ok(url);
}
catch (Exception ex)
{
_logger.LogError(ex, "本地存储上传失败: {FileName}", fileName);
return UploadResult.Fail($"保存文件失败: {ex.Message}");
}
}
/// <inheritdoc />
public Task<bool> DeleteAsync(string fileUrl)
{
try
{
if (string.IsNullOrWhiteSpace(fileUrl))
{
return Task.FromResult(false);
}
// 将URL转换为物理路径
// URL格式: /uploads/2026/01/19/xxx.jpg
var relativePath = fileUrl.TrimStart('/').Replace('/', Path.DirectorySeparatorChar);
var physicalPath = Path.Combine(_environment.WebRootPath, relativePath);
if (File.Exists(physicalPath))
{
File.Delete(physicalPath);
_logger.LogInformation("本地存储删除成功: {Url}", fileUrl);
return Task.FromResult(true);
}
_logger.LogWarning("本地存储删除失败,文件不存在: {Url}", fileUrl);
return Task.FromResult(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "本地存储删除失败: {Url}", fileUrl);
return Task.FromResult(false);
}
}
/// <inheritdoc />
public Task<PresignedUrlResponse?> GetPresignedUploadUrlAsync(string fileName, string contentType, int expiresInSeconds = 600)
{
// 本地存储不支持客户端直传返回null
_logger.LogDebug("本地存储不支持客户端直传");
return Task.FromResult<PresignedUrlResponse?>(null);
}
/// <summary>
/// 生成唯一文件名
/// 格式: {timestamp}_{guid}{extension}
/// </summary>
private static string GenerateUniqueFileName(string originalFileName)
{
var extension = Path.GetExtension(originalFileName).ToLowerInvariant();
var timestamp = DateTime.Now.ToString("yyyyMMddHHmmssfff");
var guid = Guid.NewGuid().ToString("N")[..8]; // 取GUID前8位
return $"{timestamp}_{guid}{extension}";
}
/// <summary>
/// 确保目录存在,不存在则创建
/// </summary>
private void EnsureDirectoryExists(string path)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
_logger.LogDebug("创建目录: {Path}", path);
}
}
}