Files
emall-api/FutureMailAPI/Services/FileUploadService.cs
2025-10-16 09:56:36 +08:00

319 lines
13 KiB
C#
Raw Permalink 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 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;
}
}
}