Files
emall-api/FutureMailAPI/Services/FileUploadService.cs

319 lines
13 KiB
C#
Raw Normal View History

2025-10-16 09:56:36 +08:00
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using FutureMailAPI.DTOs;
using System.Security.Cryptography;
using System.Text;
namespace FutureMailAPI.Services
{
public class FileUploadService : IFileUploadService
{
private readonly IConfiguration _configuration;
private readonly ILogger<FileUploadService> _logger;
private readonly string _uploadPath;
private readonly string _baseUrl;
private readonly long _maxFileSize;
private readonly List<string> _allowedImageTypes;
private readonly List<string> _allowedVideoTypes;
private readonly List<string> _allowedDocumentTypes;
private readonly List<string> _allowedAudioTypes;
public FileUploadService(IConfiguration configuration, ILogger<FileUploadService> logger)
{
_configuration = configuration;
_logger = logger;
// 从配置中获取上传路径和基础URL
_uploadPath = configuration["FileUpload:UploadPath"] ?? "uploads";
_baseUrl = configuration["FileUpload:BaseUrl"] ?? "http://localhost:5054/uploads";
_maxFileSize = long.Parse(configuration["FileUpload:MaxFileSize"] ?? "104857600"); // 默认100MB
// 允许的文件类型
_allowedImageTypes = new List<string> { "image/jpeg", "image/png", "image/gif", "image/webp" };
_allowedVideoTypes = new List<string> { "video/mp4", "video/avi", "video/mov", "video/wmv" };
_allowedDocumentTypes = new List<string> { "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" };
_allowedAudioTypes = new List<string> { "audio/mpeg", "audio/wav", "audio/ogg" };
// 确保上传目录存在
EnsureUploadDirectoryExists();
}
public async Task<ApiResponse<FileUploadResponseDto>> UploadFileAsync(IFormFile file, int userId, FileUploadRequestDto request)
{
try
{
if (file == null || file.Length == 0)
{
return ApiResponse<FileUploadResponseDto>.ErrorResult("请选择要上传的文件");
}
// 检查文件大小
if (file.Length > _maxFileSize)
{
return ApiResponse<FileUploadResponseDto>.ErrorResult($"文件大小不能超过 {_maxFileSize / (1024 * 1024)}MB");
}
// 检查文件类型
var contentType = file.ContentType.ToLower();
if (!IsAllowedFileType(contentType, request.Type))
{
return ApiResponse<FileUploadResponseDto>.ErrorResult("不支持的文件类型");
}
// 生成唯一文件名
var fileId = GenerateFileId();
var fileExtension = Path.GetExtension(file.FileName);
var fileName = $"{fileId}{fileExtension}";
// 创建用户目录
var userDirectory = Path.Combine(_uploadPath, userId.ToString());
var categoryDirectory = string.IsNullOrEmpty(request.Category)
? userDirectory
: Path.Combine(userDirectory, request.Category);
Directory.CreateDirectory(categoryDirectory);
// 保存文件
var filePath = Path.Combine(categoryDirectory, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// 生成缩略图(如果是图片)
string thumbnailUrl = string.Empty;
if (request.Type == AttachmentType.IMAGE)
{
thumbnailUrl = await GenerateThumbnailAsync(filePath, fileName, categoryDirectory);
}
// 构建文件URL
var relativePath = Path.GetRelativePath(_uploadPath, filePath).Replace("\\", "/");
var fileUrl = $"{_baseUrl}/{relativePath}";
// 构建缩略图URL
if (!string.IsNullOrEmpty(thumbnailUrl))
{
var relativeThumbnailPath = Path.GetRelativePath(_uploadPath, thumbnailUrl).Replace("\\", "/");
thumbnailUrl = $"{_baseUrl}/{relativeThumbnailPath}";
}
var response = new FileUploadResponseDto
{
FileId = fileId,
FileName = file.FileName,
FileUrl = fileUrl,
ThumbnailUrl = thumbnailUrl,
FileSize = file.Length,
ContentType = file.ContentType,
Type = request.Type,
UploadedAt = DateTime.UtcNow
};
return ApiResponse<FileUploadResponseDto>.SuccessResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "上传文件时发生错误");
return ApiResponse<FileUploadResponseDto>.ErrorResult("上传文件失败");
}
}
public async Task<ApiResponse<bool>> DeleteFileAsync(string fileId, int userId)
{
try
{
// 在实际应用中,这里应该从数据库中查找文件信息
// 目前我们只是简单地根据文件ID删除文件
// 查找用户目录下的所有文件
var userDirectory = Path.Combine(_uploadPath, userId.ToString());
if (!Directory.Exists(userDirectory))
{
return ApiResponse<bool>.ErrorResult("文件不存在");
}
// 查找匹配的文件
var files = Directory.GetFiles(userDirectory, $"{fileId}*", SearchOption.AllDirectories);
if (files.Length == 0)
{
return ApiResponse<bool>.ErrorResult("文件不存在");
}
// 删除所有相关文件(包括缩略图)
foreach (var file in files)
{
try
{
System.IO.File.Delete(file);
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"删除文件 {file} 失败");
}
}
return ApiResponse<bool>.SuccessResult(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除文件时发生错误");
return ApiResponse<bool>.ErrorResult("删除文件失败");
}
}
public async Task<ApiResponse<FileUploadResponseDto>> GetFileAsync(string fileId, int userId)
{
try
{
// 在实际应用中,这里应该从数据库中查找文件信息
// 目前我们只是简单地根据文件ID返回文件信息
// 查找用户目录下的所有文件
var userDirectory = Path.Combine(_uploadPath, userId.ToString());
if (!Directory.Exists(userDirectory))
{
return ApiResponse<FileUploadResponseDto>.ErrorResult("文件不存在");
}
// 查找匹配的文件
var files = Directory.GetFiles(userDirectory, $"{fileId}*", SearchOption.AllDirectories);
if (files.Length == 0)
{
return ApiResponse<FileUploadResponseDto>.ErrorResult("文件不存在");
}
// 获取主文件(排除缩略图)
var mainFile = files.FirstOrDefault(f => !f.Contains("_thumb."));
if (mainFile == null)
{
mainFile = files[0]; // 如果没有找到主文件,使用第一个文件
}
var fileInfo = new FileInfo(mainFile);
var relativePath = Path.GetRelativePath(_uploadPath, mainFile).Replace("\\", "/");
var fileUrl = $"{_baseUrl}/{relativePath}";
// 查找缩略图
var thumbnailFile = files.FirstOrDefault(f => f.Contains("_thumb."));
string thumbnailUrl = string.Empty;
if (thumbnailFile != null)
{
var relativeThumbnailPath = Path.GetRelativePath(_uploadPath, thumbnailFile).Replace("\\", "/");
thumbnailUrl = $"{_baseUrl}/{relativeThumbnailPath}";
}
// 确定文件类型
var contentType = GetContentType(fileInfo.Extension);
var attachmentType = GetAttachmentType(contentType);
var response = new FileUploadResponseDto
{
FileId = fileId,
FileName = fileInfo.Name,
FileUrl = fileUrl,
ThumbnailUrl = thumbnailUrl,
FileSize = fileInfo.Length,
ContentType = contentType,
Type = attachmentType,
UploadedAt = fileInfo.CreationTimeUtc
};
return ApiResponse<FileUploadResponseDto>.SuccessResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取文件信息时发生错误");
return ApiResponse<FileUploadResponseDto>.ErrorResult("获取文件信息失败");
}
}
private void EnsureUploadDirectoryExists()
{
if (!Directory.Exists(_uploadPath))
{
Directory.CreateDirectory(_uploadPath);
}
}
private bool IsAllowedFileType(string contentType, AttachmentType type)
{
return type switch
{
AttachmentType.IMAGE => _allowedImageTypes.Contains(contentType),
AttachmentType.VIDEO => _allowedVideoTypes.Contains(contentType),
AttachmentType.DOCUMENT => _allowedDocumentTypes.Contains(contentType),
AttachmentType.VOICE => _allowedAudioTypes.Contains(contentType),
_ => true // 其他类型暂时允许
};
}
private string GenerateFileId()
{
return Guid.NewGuid().ToString("N");
}
private async Task<string> GenerateThumbnailAsync(string filePath, string fileName, string directory)
{
try
{
// 在实际应用中这里应该使用图像处理库如ImageSharp生成缩略图
// 目前我们只是创建一个简单的缩略图文件名
var fileExtension = Path.GetExtension(fileName);
var thumbnailFileName = $"{Path.GetFileNameWithoutExtension(fileName)}_thumb{fileExtension}";
var thumbnailPath = Path.Combine(directory, thumbnailFileName);
// 这里应该添加实际的缩略图生成代码
// 暂时只是复制原文件作为缩略图
System.IO.File.Copy(filePath, thumbnailPath);
return thumbnailPath;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "生成缩略图失败");
return string.Empty;
}
}
private string GetContentType(string extension)
{
return extension.ToLower() switch
{
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".webp" => "image/webp",
".mp4" => "video/mp4",
".avi" => "video/avi",
".mov" => "video/mov",
".wmv" => "video/wmv",
".mp3" => "audio/mpeg",
".wav" => "audio/wav",
".ogg" => "audio/ogg",
".pdf" => "application/pdf",
".doc" => "application/msword",
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
_ => "application/octet-stream"
};
}
private AttachmentType GetAttachmentType(string contentType)
{
if (_allowedImageTypes.Contains(contentType))
return AttachmentType.IMAGE;
if (_allowedVideoTypes.Contains(contentType))
return AttachmentType.VIDEO;
if (_allowedAudioTypes.Contains(contentType))
return AttachmentType.VOICE;
if (_allowedDocumentTypes.Contains(contentType))
return AttachmentType.DOCUMENT;
return AttachmentType.OTHER;
}
}
}