253 lines
9.3 KiB
C#
253 lines
9.3 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|
||
|