using LiveForum.Code.Base; using LiveForum.IService.Others; using LiveForum.Model.Dto.Others; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; namespace LiveForum.Service.Others { public class FileUploadService //: IFileUploadService { private readonly IWebHostEnvironment _env; private readonly IHttpContextAccessor _httpContextAccessor; // 允许的文件扩展名 private readonly string[] _allowedImageExtensions = { ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp" }; private readonly string[] _allowedVideoExtensions = { ".mp4", ".avi", ".mov", ".wmv", ".flv", ".mkv" }; // 文件大小限制(单位:字节) private const long MaxImageSize = 10 * 1024 * 1024; // 10MB private const long MaxVideoSize = 100 * 1024 * 1024; // 100MB /// /// 构造函数 /// /// 环境变量 /// HTTP上下文访问器 public FileUploadService(IWebHostEnvironment env, IHttpContextAccessor httpContextAccessor) { _env = env; _httpContextAccessor = httpContextAccessor; } /// /// 上传文件 /// /// 请求参数 /// public async Task> UploadFile(UploadFileReq request) { try { // 验证文件 if (request.File == null || request.File.Length == 0) { return new BaseResponse(ResponseCode.Error, "文件不能为空"); } // 验证文件类型 var extension = Path.GetExtension(request.File.FileName)?.ToLower(); if (string.IsNullOrEmpty(extension)) { return new BaseResponse(ResponseCode.Error, "文件格式无效"); } // 判断是图片还是视频 bool isImage = _allowedImageExtensions.Contains(extension); bool isVideo = _allowedVideoExtensions.Contains(extension); if (!isImage && !isVideo) { return new BaseResponse(ResponseCode.Error, "不支持的文件格式"); } // 验证文件大小 if (isImage && request.File.Length > MaxImageSize) { return new BaseResponse(ResponseCode.Error, $"图片大小不能超过 {MaxImageSize / 1024 / 1024}MB"); } if (isVideo && request.File.Length > MaxVideoSize) { return new BaseResponse(ResponseCode.Error, $"视频大小不能超过 {MaxVideoSize / 1024 / 1024}MB"); } // 根据文件类型确定保存路径 string relativePath = GetUploadPath(request.Type, isImage); string wwwrootPath = Path.Combine(_env.ContentRootPath, "wwwroot"); string fullPath = Path.Combine(wwwrootPath, relativePath); // 确保目录存在 Directory.CreateDirectory(fullPath); // 生成唯一文件名 string fileName = $"{Guid.NewGuid()}{extension}"; string filePath = Path.Combine(fullPath, fileName); // 保存文件 using (var stream = new FileStream(filePath, FileMode.Create)) { await request.File.CopyToAsync(stream); } // 获取文件URL string fileUrl = GetFileUrl(relativePath, fileName); var response = new UploadFileRespDto { FileUrl = fileUrl, FileSize = request.File.Length, FileType = isImage ? "image" : "video" }; // 如果是图片,获取图片尺寸 if (isImage) { try { var (width, height) = GetImageDimensions(filePath); response.FileWidth = width; response.FileHeight = height; response.ThumbnailUrl = fileUrl; // 暂时使用原图作为缩略图 } catch { // 如果获取尺寸失败,不影响上传成功 } } else if (isVideo) { // 对于视频,需要额外处理 response.Duration = null; // 暂时不处理视频时长 } return new BaseResponse(response); } catch (Exception ex) { return new BaseResponse(ResponseCode.Error, $"文件上传失败:{ex.Message}"); } } /// /// 批量上传文件 /// /// 请求参数 /// public async Task> UploadFiles(UploadFilesReq request) { try { // 验证文件列表 if (request.Files == null || request.Files.Count == 0) { return new BaseResponse(ResponseCode.Error, "文件列表不能为空"); } // 限制最多上传9个文件 if (request.Files.Count > 9) { return new BaseResponse(ResponseCode.Error, "最多只能上传9个文件"); } var uploadedFiles = new List(); foreach (var file in request.Files) { // 为每个文件创建请求 var singleFileRequest = new UploadFileReq { File = file, Type = request.Type }; // 上传单个文件 var result = await UploadFile(singleFileRequest); if (result.Code == ResponseCode.Success && result.Data != null) { uploadedFiles.Add(result.Data); } else { // 如果某个文件上传失败,返回错误信息 return new BaseResponse(ResponseCode.Error, $"文件上传失败:{file.FileName}"); } } var response = new UploadFilesRespDto { Files = uploadedFiles }; return new BaseResponse(response); } catch (Exception ex) { return new BaseResponse(ResponseCode.Error, $"批量上传失败:{ex.Message}"); } } /// /// 根据类型获取上传路径 /// /// 文件类型:1-帖子图片,2-头像,3-认证视频,4-其他 /// 是否为图片 /// private string GetUploadPath(int type, bool isImage) { string folder = isImage ? "images" : "videos"; string subFolder = type switch { 1 => "posts", // 帖子图片 2 => "avatars", // 头像 3 => "certification", // 认证视频 4 => "other", // 其他 _ => "other" }; return $"uploads/{folder}/{subFolder}/{DateTime.Now:yyyy/MM}"; } /// /// 获取文件访问URL /// /// 相对路径 /// 文件名 /// private string GetFileUrl(string relativePath, string fileName) { var request = _httpContextAccessor.HttpContext?.Request; if (request == null) { return $"/{relativePath}/{fileName}"; } var scheme = request.Scheme; var host = request.Host; var pathBase = request.PathBase; return $"{scheme}://{host}{pathBase}/{relativePath}/{fileName}"; } /// /// 获取图片尺寸 /// /// 文件路径 /// 宽度和高度 private (int width, int height) GetImageDimensions(string filePath) { using var image = System.Drawing.Image.FromFile(filePath); return (image.Width, image.Height); } } }