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);
}
}
}