| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- using Microsoft.AspNetCore.Hosting;
- using Microsoft.AspNetCore.Http;
- using Microsoft.Extensions.Logging;
- using SharedKernel.Storage;
- namespace Infrastructure.Storage
- {
- public sealed class LocalFileStorage : IFileStorage
- {
- private readonly IWebHostEnvironment _env;
- private readonly ILogger<LocalFileStorage> _log;
- public LocalFileStorage(IWebHostEnvironment env, ILogger<LocalFileStorage> log)
- {
- _env = env;
- _log = log;
- }
- public async Task<FileUploadResult?> SaveFileAsync(IFormFile? file, FileStoragePath path, string[] allowedExtensions, CancellationToken ct = default)
- {
- if (file is null || file.Length <= 0)
- {
- return null;
- }
- var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
- if (!allowedExtensions.Contains(ext))
- {
- throw new ArgumentException("허용되지 않는 파일 확장자입니다.");
- }
- var rel = path.ToRelativePath();
- var dir = CombineUnderWebRoot(rel);
- Directory.CreateDirectory(dir); // 경로 생성(없으면)
- var fileName = $"{Guid.NewGuid():N}{ext}";
- var fullPath = Path.Combine(dir, fileName);
- await using var fs = new FileStream(fullPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
- await file.CopyToAsync(fs, ct);
- var url = "/" + $"{rel}/{fileName}".Replace("\\", "/");
- return new FileUploadResult(url, fileName, file.Length, ext);
- }
- public async Task<FileUploadResult?> SaveBytesAsync(ReadOnlyMemory<byte> bytes, string extension, FileStoragePath path, CancellationToken ct = default)
- {
- if (bytes.Length <= 0)
- {
- return null;
- }
- var ext = FileUtils.NormalizeExtension(extension);
- if (ext is not (".jpg" or ".png" or ".gif" or ".bmp" or ".webp"))
- {
- throw new ArgumentException("허용되지 않는 파일 확장자입니다.");
- }
- var rel = path.ToRelativePath();
- var dir = CombineUnderWebRoot(rel);
- Directory.CreateDirectory(dir); // 경로 생성(없으면)
- var fileName = $"{Guid.NewGuid():N}{ext}";
- var fullPath = Path.Combine(dir, fileName);
- await File.WriteAllBytesAsync(fullPath, bytes.ToArray(), ct);
- var url = "/" + $"{rel}/{fileName}".Replace("\\", "/");
- return new FileUploadResult(url, fileName, bytes.Length, ext);
- }
- public void DeleteByUrl(string? url)
- {
- if (string.IsNullOrWhiteSpace(url))
- {
- return;
- }
- if (!FileUtils.IsLocalPublicUrl(url))
- {
- _log.LogWarning("삭제가 불가한 주소입니다. {0}", url);
- return;
- }
- var fullPath = CombineUnderWebRoot(url.TrimStart('/'));
- if (!File.Exists(fullPath))
- {
- _log.LogWarning("파일을 찾을 수 없습니다. {0}", fullPath);
- return;
- }
- try
- {
- File.Delete(fullPath);
- _log.LogInformation("파일이 삭제됨 : {0}", fullPath);
- }
- catch (Exception e)
- {
- _log.LogError(e, "파일 삭제 중 오류가 발생했습니다. {0}", fullPath);
- }
- }
- private string CombineUnderWebRoot(string relative)
- {
- var webRoot = Path.GetFullPath(_env.WebRootPath);
- var fullPath = Path.GetFullPath(Path.Combine(_env.WebRootPath, relative));
- if (!fullPath.StartsWith(webRoot, StringComparison.OrdinalIgnoreCase))
- {
- throw new UnauthorizedAccessException("경로를 찾을 수 없습니다.");
- }
- return fullPath;
- }
- }
- }
|