live-forum/server/webapi/LiveForum/LiveForum.Service/Others/FileUploadService.cs
2026-03-24 11:27:37 +08:00

253 lines
9.3 KiB
C#
Raw 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 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
/// <summary>
/// 构造函数
/// </summary>
/// <param name="env">环境变量</param>
/// <param name="httpContextAccessor">HTTP上下文访问器</param>
public FileUploadService(IWebHostEnvironment env, IHttpContextAccessor httpContextAccessor)
{
_env = env;
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// 上传文件
/// </summary>
/// <param name="request">请求参数</param>
/// <returns></returns>
public async Task<BaseResponse<UploadFileRespDto>> UploadFile(UploadFileReq request)
{
try
{
// 验证文件
if (request.File == null || request.File.Length == 0)
{
return new BaseResponse<UploadFileRespDto>(ResponseCode.Error, "文件不能为空");
}
// 验证文件类型
var extension = Path.GetExtension(request.File.FileName)?.ToLower();
if (string.IsNullOrEmpty(extension))
{
return new BaseResponse<UploadFileRespDto>(ResponseCode.Error, "文件格式无效");
}
// 判断是图片还是视频
bool isImage = _allowedImageExtensions.Contains(extension);
bool isVideo = _allowedVideoExtensions.Contains(extension);
if (!isImage && !isVideo)
{
return new BaseResponse<UploadFileRespDto>(ResponseCode.Error, "不支持的文件格式");
}
// 验证文件大小
if (isImage && request.File.Length > MaxImageSize)
{
return new BaseResponse<UploadFileRespDto>(ResponseCode.Error, $"图片大小不能超过 {MaxImageSize / 1024 / 1024}MB");
}
if (isVideo && request.File.Length > MaxVideoSize)
{
return new BaseResponse<UploadFileRespDto>(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<UploadFileRespDto>(response);
}
catch (Exception ex)
{
return new BaseResponse<UploadFileRespDto>(ResponseCode.Error, $"文件上传失败:{ex.Message}");
}
}
/// <summary>
/// 批量上传文件
/// </summary>
/// <param name="request">请求参数</param>
/// <returns></returns>
public async Task<BaseResponse<UploadFilesRespDto>> UploadFiles(UploadFilesReq request)
{
try
{
// 验证文件列表
if (request.Files == null || request.Files.Count == 0)
{
return new BaseResponse<UploadFilesRespDto>(ResponseCode.Error, "文件列表不能为空");
}
// 限制最多上传9个文件
if (request.Files.Count > 9)
{
return new BaseResponse<UploadFilesRespDto>(ResponseCode.Error, "最多只能上传9个文件");
}
var uploadedFiles = new List<UploadFileRespDto>();
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<UploadFilesRespDto>(ResponseCode.Error, $"文件上传失败:{file.FileName}");
}
}
var response = new UploadFilesRespDto
{
Files = uploadedFiles
};
return new BaseResponse<UploadFilesRespDto>(response);
}
catch (Exception ex)
{
return new BaseResponse<UploadFilesRespDto>(ResponseCode.Error, $"批量上传失败:{ex.Message}");
}
}
/// <summary>
/// 根据类型获取上传路径
/// </summary>
/// <param name="type">文件类型1-帖子图片2-头像3-认证视频4-其他</param>
/// <param name="isImage">是否为图片</param>
/// <returns></returns>
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}";
}
/// <summary>
/// 获取文件访问URL
/// </summary>
/// <param name="relativePath">相对路径</param>
/// <param name="fileName">文件名</param>
/// <returns></returns>
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}";
}
/// <summary>
/// 获取图片尺寸
/// </summary>
/// <param name="filePath">文件路径</param>
/// <returns>宽度和高度</returns>
private (int width, int height) GetImageDimensions(string filePath)
{
using var image = System.Drawing.Image.FromFile(filePath);
return (image.Width, image.Height);
}
}
}