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 _logger; private readonly string _uploadPath; private readonly string _baseUrl; private readonly long _maxFileSize; private readonly List _allowedImageTypes; private readonly List _allowedVideoTypes; private readonly List _allowedDocumentTypes; private readonly List _allowedAudioTypes; public FileUploadService(IConfiguration configuration, ILogger 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 { "image/jpeg", "image/png", "image/gif", "image/webp" }; _allowedVideoTypes = new List { "video/mp4", "video/avi", "video/mov", "video/wmv" }; _allowedDocumentTypes = new List { "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }; _allowedAudioTypes = new List { "audio/mpeg", "audio/wav", "audio/ogg" }; // 确保上传目录存在 EnsureUploadDirectoryExists(); } public async Task> UploadFileAsync(IFormFile file, int userId, FileUploadRequestDto request) { try { if (file == null || file.Length == 0) { return ApiResponse.ErrorResult("请选择要上传的文件"); } // 检查文件大小 if (file.Length > _maxFileSize) { return ApiResponse.ErrorResult($"文件大小不能超过 {_maxFileSize / (1024 * 1024)}MB"); } // 检查文件类型 var contentType = file.ContentType.ToLower(); if (!IsAllowedFileType(contentType, request.Type)) { return ApiResponse.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.SuccessResult(response); } catch (Exception ex) { _logger.LogError(ex, "上传文件时发生错误"); return ApiResponse.ErrorResult("上传文件失败"); } } public async Task> DeleteFileAsync(string fileId, int userId) { try { // 在实际应用中,这里应该从数据库中查找文件信息 // 目前我们只是简单地根据文件ID删除文件 // 查找用户目录下的所有文件 var userDirectory = Path.Combine(_uploadPath, userId.ToString()); if (!Directory.Exists(userDirectory)) { return ApiResponse.ErrorResult("文件不存在"); } // 查找匹配的文件 var files = Directory.GetFiles(userDirectory, $"{fileId}*", SearchOption.AllDirectories); if (files.Length == 0) { return ApiResponse.ErrorResult("文件不存在"); } // 删除所有相关文件(包括缩略图) foreach (var file in files) { try { System.IO.File.Delete(file); } catch (Exception ex) { _logger.LogWarning(ex, $"删除文件 {file} 失败"); } } return ApiResponse.SuccessResult(true); } catch (Exception ex) { _logger.LogError(ex, "删除文件时发生错误"); return ApiResponse.ErrorResult("删除文件失败"); } } public async Task> GetFileAsync(string fileId, int userId) { try { // 在实际应用中,这里应该从数据库中查找文件信息 // 目前我们只是简单地根据文件ID返回文件信息 // 查找用户目录下的所有文件 var userDirectory = Path.Combine(_uploadPath, userId.ToString()); if (!Directory.Exists(userDirectory)) { return ApiResponse.ErrorResult("文件不存在"); } // 查找匹配的文件 var files = Directory.GetFiles(userDirectory, $"{fileId}*", SearchOption.AllDirectories); if (files.Length == 0) { return ApiResponse.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.SuccessResult(response); } catch (Exception ex) { _logger.LogError(ex, "获取文件信息时发生错误"); return ApiResponse.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 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; } } }