v 1.1.4 新增文件上传中转 七牛云
This commit is contained in:
parent
c12e1b5d65
commit
7f269c8b04
@ -53,6 +53,8 @@ namespace LMS.DAO
|
|||||||
|
|
||||||
public DbSet<MJApiTasks> MJApiTasks { get; set; }
|
public DbSet<MJApiTasks> MJApiTasks { get; set; }
|
||||||
|
|
||||||
|
public DbSet<FileUploads> FileUploads { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
@ -112,6 +114,16 @@ namespace LMS.DAO
|
|||||||
.HasForeignKey(e => e.TokenId)
|
.HasForeignKey(e => e.TokenId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<FileUploads>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasIndex(e => e.UserId);
|
||||||
|
entity.HasIndex(e => e.FileKey);
|
||||||
|
entity.HasIndex(e => e.UploadTime);
|
||||||
|
|
||||||
|
entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETDATE()");
|
||||||
|
entity.Property(e => e.UploadTime).HasDefaultValueSql("GETDATE()");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
67
LMS.DAO/OptionDAO/OptionGlobalDAO.cs
Normal file
67
LMS.DAO/OptionDAO/OptionGlobalDAO.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using LMS.Repository.DB;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace LMS.DAO.OptionDAO
|
||||||
|
{
|
||||||
|
public class OptionGlobalDAO(ApplicationDbContext dbContext)
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContext _dbContext = dbContext;
|
||||||
|
/// <summary>
|
||||||
|
/// 根据配置键查找并返回指定类型的配置值
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">返回值类型</typeparam>
|
||||||
|
/// <param name="optionKey">配置键</param>
|
||||||
|
/// <returns>配置值,如果不存在或转换失败则返回默认值</returns>
|
||||||
|
public async Task<T?> FindAndReturnOption<T>(string optionKey)
|
||||||
|
{
|
||||||
|
// 参数验证
|
||||||
|
if (string.IsNullOrWhiteSpace(optionKey))
|
||||||
|
{
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = await _dbContext.Options
|
||||||
|
.Where(x => x.Key == optionKey)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (options == null) return default;
|
||||||
|
// 直接返回转换结果,GetValueObject内部应该处理null情况
|
||||||
|
return options.GetValueObject<T>() ?? default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查配置是否存在
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="optionKey">配置键</param>
|
||||||
|
/// <returns>是否存在</returns>
|
||||||
|
public async Task<bool> OptionExists(string optionKey)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(optionKey))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _dbContext.Options
|
||||||
|
.AnyAsync(x => x.Key == optionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取多个配置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="optionKeys">配置键列表</param>
|
||||||
|
/// <returns>配置字典</returns>
|
||||||
|
public async Task<Dictionary<string, Options>> GetMultipleOptions(params string[] optionKeys)
|
||||||
|
{
|
||||||
|
if (optionKeys == null || optionKeys.Length == 0)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, Options>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = await _dbContext.Options
|
||||||
|
.Where(x => optionKeys.Contains(x.Key))
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return options.ToDictionary(x => x.Key, x => x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
LMS.Repository/DB/FileUploads.cs
Normal file
47
LMS.Repository/DB/FileUploads.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace LMS.Repository.DB;
|
||||||
|
|
||||||
|
public class FileUploads
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(50)]
|
||||||
|
public long UserId { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(255)]
|
||||||
|
public string FileName { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(500)]
|
||||||
|
public string FileKey { get; set; }
|
||||||
|
|
||||||
|
public long FileSize { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(100)]
|
||||||
|
public string ContentType { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(100)]
|
||||||
|
public string Hash { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(1000)]
|
||||||
|
public string QiniuUrl { get; set; }
|
||||||
|
|
||||||
|
public DateTime UploadTime { get; set; }
|
||||||
|
|
||||||
|
[StringLength(20)]
|
||||||
|
public string Status { get; set; } = "active";
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除时间 ,默认为最大值,表示未删除
|
||||||
|
/// </summary>
|
||||||
|
public DateTime DeleteTime { get; set; } = DateTime.MaxValue;
|
||||||
|
}
|
||||||
39
LMS.Repository/DTO/FileUploadDto.cs
Normal file
39
LMS.Repository/DTO/FileUploadDto.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using LMS.Repository.DB;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace LMS.Repository.DTO
|
||||||
|
{
|
||||||
|
public class FileUploadDto
|
||||||
|
{
|
||||||
|
public class ByteUploadRequest
|
||||||
|
{
|
||||||
|
//public required string FileBytes { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 文件的base64
|
||||||
|
/// </summary>
|
||||||
|
public required string File { get; set; }
|
||||||
|
public required string FileName { get; set; }
|
||||||
|
public required string ContentType { get; set; }
|
||||||
|
public Dictionary<string, string> Metadata { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UploadResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public string FileKey { get; set; }
|
||||||
|
public string Hash { get; set; }
|
||||||
|
public long FileId { get; set; }
|
||||||
|
public long FileSize { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileListResponse
|
||||||
|
{
|
||||||
|
public List<FileUploads> Files { get; set; }
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
public int Page { get; set; }
|
||||||
|
public int PageSize { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
LMS.Repository/FileUpload/FileRequestReturn.cs
Normal file
42
LMS.Repository/FileUpload/FileRequestReturn.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
namespace LMS.Repository.FileUpload
|
||||||
|
{
|
||||||
|
public class FileRequestReturn
|
||||||
|
{
|
||||||
|
public class FileMachineRequestReturn
|
||||||
|
{
|
||||||
|
public string MachineId { get; set; }
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public long FileSize { get; set; }
|
||||||
|
public string ContentType { get; set; }
|
||||||
|
|
||||||
|
public string Hash { get; set; }
|
||||||
|
|
||||||
|
public string Url { get; set; }
|
||||||
|
|
||||||
|
public DateTime UploadTime { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 删除时间 ,默认为最大值,表示未不删除
|
||||||
|
/// </summary>
|
||||||
|
public DateTime DeleteTime { get; set; } = DateTime.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileUserRequestReturn
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public long FileSize { get; set; }
|
||||||
|
public string ContentType { get; set; }
|
||||||
|
public string Hash { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public DateTime UploadTime { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 删除时间 ,默认为最大值,表示未不删除
|
||||||
|
/// </summary>
|
||||||
|
public DateTime DeleteTime { get; set; } = DateTime.MaxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
LMS.Repository/FileUpload/QiniuSettings.cs
Normal file
19
LMS.Repository/FileUpload/QiniuSettings.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
namespace LMS.Repository.FileUpload;
|
||||||
|
public class QiniuSettings
|
||||||
|
{
|
||||||
|
public string AccessKey { get; set; }
|
||||||
|
public string SecretKey { get; set; }
|
||||||
|
public string BucketName { get; set; }
|
||||||
|
public string Domain { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除时间 天数 没有值 则不删除
|
||||||
|
/// </summary>
|
||||||
|
public int? DeleteDay { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileUploadSettings
|
||||||
|
{
|
||||||
|
public long MaxFileSize { get; set; } = 3 * 1024 * 1024; // 5MB
|
||||||
|
public List<string> AllowedContentTypes { get; set; } = new();
|
||||||
|
}
|
||||||
66
LMS.Tools/ImageTool/ImageTypeDetector.cs
Normal file
66
LMS.Tools/ImageTool/ImageTypeDetector.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
namespace LMS.Tools.ImageTool
|
||||||
|
{
|
||||||
|
public static class ImageTypeDetector
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, byte[]> ImageSignatures = new()
|
||||||
|
{
|
||||||
|
{ "image/jpeg", new byte[] { 0xFF, 0xD8, 0xFF } },
|
||||||
|
{ "image/png", new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } },
|
||||||
|
{ "image/gif", new byte[] { 0x47, 0x49, 0x46, 0x38 } }, // GIF8
|
||||||
|
{ "image/bmp", new byte[] { 0x42, 0x4D } }, // BM
|
||||||
|
{ "image/webp", new byte[] { 0x52, 0x49, 0x46, 0x46 } } // RIFF (需要额外检查)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查是否为支持的图片格式
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileBytes">文件字节数组</param>
|
||||||
|
/// <returns>是否为图片</returns>
|
||||||
|
public static bool IsValidImage(byte[] fileBytes)
|
||||||
|
{
|
||||||
|
if (fileBytes == null || fileBytes.Length < 8)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 检查 JPEG
|
||||||
|
if (StartsWithBytes(fileBytes, ImageSignatures["image/jpeg"]))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// 检查 PNG
|
||||||
|
if (StartsWithBytes(fileBytes, ImageSignatures["image/png"]))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// 检查 GIF
|
||||||
|
if (StartsWithBytes(fileBytes, ImageSignatures["image/gif"]))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// 检查 BMP
|
||||||
|
if (StartsWithBytes(fileBytes, ImageSignatures["image/bmp"]))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// 检查 WEBP (RIFF + WEBP标识)
|
||||||
|
if (StartsWithBytes(fileBytes, ImageSignatures["image/webp"]) &&
|
||||||
|
fileBytes.Length >= 12 &&
|
||||||
|
fileBytes[8] == 0x57 && fileBytes[9] == 0x45 &&
|
||||||
|
fileBytes[10] == 0x42 && fileBytes[11] == 0x50) // "WEBP"
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool StartsWithBytes(byte[] fileBytes, byte[] signature)
|
||||||
|
{
|
||||||
|
if (fileBytes.Length < signature.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < signature.Length; i++)
|
||||||
|
{
|
||||||
|
if (fileBytes[i] != signature[i])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,12 @@
|
|||||||
using LMS.DAO.MachineDAO;
|
using LMS.DAO.MachineDAO;
|
||||||
|
using LMS.DAO.OptionDAO;
|
||||||
using LMS.DAO.PermissionDAO;
|
using LMS.DAO.PermissionDAO;
|
||||||
using LMS.DAO.RoleDAO;
|
using LMS.DAO.RoleDAO;
|
||||||
using LMS.DAO.UserDAO;
|
using LMS.DAO.UserDAO;
|
||||||
using LMS.service.Configuration.InitConfiguration;
|
using LMS.service.Configuration.InitConfiguration;
|
||||||
using LMS.service.Extensions.Mail;
|
using LMS.service.Extensions.Mail;
|
||||||
using LMS.service.Service;
|
using LMS.service.Service;
|
||||||
|
using LMS.service.Service.FileUploadService;
|
||||||
using LMS.service.Service.MJPackage;
|
using LMS.service.Service.MJPackage;
|
||||||
using LMS.service.Service.Other;
|
using LMS.service.Service.Other;
|
||||||
using LMS.service.Service.PermissionService;
|
using LMS.service.Service.PermissionService;
|
||||||
@ -49,6 +51,7 @@ namespace Lai_server.Configuration
|
|||||||
services.AddScoped<MachineBasicDao>();
|
services.AddScoped<MachineBasicDao>();
|
||||||
services.AddScoped<PermissionBasicDao>();
|
services.AddScoped<PermissionBasicDao>();
|
||||||
services.AddScoped<PermissionTypeDao>();
|
services.AddScoped<PermissionTypeDao>();
|
||||||
|
services.AddScoped<OptionGlobalDAO>();
|
||||||
|
|
||||||
|
|
||||||
// 注入 Extensions
|
// 注入 Extensions
|
||||||
@ -63,6 +66,7 @@ namespace Lai_server.Configuration
|
|||||||
services.AddScoped<ITokenService, TokenService>();
|
services.AddScoped<ITokenService, TokenService>();
|
||||||
services.AddScoped<ITaskConcurrencyManager, TaskConcurrencyManager>();
|
services.AddScoped<ITaskConcurrencyManager, TaskConcurrencyManager>();
|
||||||
services.AddScoped<ITaskService, TaskService>();
|
services.AddScoped<ITaskService, TaskService>();
|
||||||
|
services.AddScoped<IQiniuUploadService, QiniuUploadService>();
|
||||||
|
|
||||||
|
|
||||||
// 注册后台服务
|
// 注册后台服务
|
||||||
|
|||||||
87
LMS.service/Controllers/FileUploadController.cs
Normal file
87
LMS.service/Controllers/FileUploadController.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using LMS.Common.Extensions;
|
||||||
|
using LMS.Repository.DTO;
|
||||||
|
using LMS.service.Service.FileUploadService;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using static LMS.Repository.DTO.FileUploadDto;
|
||||||
|
using static LMS.Repository.FileUpload.FileRequestReturn;
|
||||||
|
|
||||||
|
|
||||||
|
namespace LMS.service.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("lms/[controller]/[action]")]
|
||||||
|
public class FileUploadController(IQiniuUploadService qiniuUploadService) : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IQiniuUploadService _qiniuUploadService = qiniuUploadService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过字节数组上传文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">字节上传请求</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("{machineId}")]
|
||||||
|
public async Task<ActionResult<APIResponseModel<UploadResult>>> FileUpload(string machineId, [FromBody] ByteUploadRequest request)
|
||||||
|
{
|
||||||
|
return await _qiniuUploadService.UploadBase64Async(request, machineId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户文件列表,通过MachineId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="page">页码</param>
|
||||||
|
/// <param name="pageSize">每页数量</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("{machineId}")]
|
||||||
|
public async Task<ActionResult<APIResponseModel<CollectionResponse<FileMachineRequestReturn>>>> GetFilesByMachineId(string machineId, int page = 1, int pageSize = 10)
|
||||||
|
{
|
||||||
|
return await _qiniuUploadService.GetFilesByMachineId(machineId, page, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定ID的用户的文件列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <param name="page"></param>
|
||||||
|
/// <param name="pageSize"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("{userId}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<APIResponseModel<CollectionResponse<FileUserRequestReturn>>>> GetFilesByUser(long userId, [FromQuery] int page = 1, [FromQuery] int pageSize = 10)
|
||||||
|
{
|
||||||
|
long requestUserId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
|
||||||
|
return await _qiniuUploadService.GetFilesByUser(requestUserId, userId, page, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// 删除文件
|
||||||
|
///// </summary>
|
||||||
|
///// <param name="fileId">文件ID</param>
|
||||||
|
///// <returns></returns>
|
||||||
|
//[HttpDelete("files/{fileId}")]
|
||||||
|
//public async Task<IActionResult> DeleteFile(long fileId)
|
||||||
|
//{
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// var userId = GetCurrentUserId();
|
||||||
|
// var success = await _qiniuUploadService.DeleteFileAsync(fileId, userId);
|
||||||
|
|
||||||
|
// if (success)
|
||||||
|
// {
|
||||||
|
// return Ok(new { message = "删除成功" });
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// return NotFound(new { message = "文件不存在或无权限删除" });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// return BadRequest(new { message = $"删除失败: {ex.Message}" });
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
||||||
|
<PackageReference Include="Qiniu.Shared" Version="7.2.15" />
|
||||||
<PackageReference Include="Quartz" Version="3.14.0" />
|
<PackageReference Include="Quartz" Version="3.14.0" />
|
||||||
<PackageReference Include="Quartz.AspNetCore" Version="3.14.0" />
|
<PackageReference Include="Quartz.AspNetCore" Version="3.14.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
using AspNetCoreRateLimit;
|
using AspNetCoreRateLimit;
|
||||||
using Lai_server.Configuration;
|
using Lai_server.Configuration;
|
||||||
using LMS.DAO;
|
using LMS.DAO;
|
||||||
|
using LMS.Repository.FileUpload;
|
||||||
using LMS.Repository.Models.DB;
|
using LMS.Repository.Models.DB;
|
||||||
using LMS.service.Configuration;
|
using LMS.service.Configuration;
|
||||||
using LMS.service.Configuration.InitConfiguration;
|
using LMS.service.Configuration.InitConfiguration;
|
||||||
using LMS.service.Extensions.Middleware;
|
using LMS.service.Extensions.Middleware;
|
||||||
using LMS.Tools.MJPackage;
|
using LMS.Tools.MJPackage;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@ -52,6 +54,15 @@ builder.Services.AddMemoryCache();
|
|||||||
// 加载通用配置(从appsettings.json)
|
// 加载通用配置(从appsettings.json)
|
||||||
builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"));
|
builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"));
|
||||||
|
|
||||||
|
builder.Services.Configure<FileUploadSettings>(
|
||||||
|
builder.Configuration.GetSection("FileUploadSettings"));
|
||||||
|
|
||||||
|
// 配置模型验证
|
||||||
|
builder.Services.Configure<ApiBehaviorOptions>(options =>
|
||||||
|
{
|
||||||
|
options.SuppressModelStateInvalidFilter = false; // 确保模型验证生效
|
||||||
|
});
|
||||||
|
|
||||||
// 注入计数器和规则存储
|
// 注入计数器和规则存储
|
||||||
builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
|
builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
|
||||||
builder.Services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
|
builder.Services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
|
||||||
|
|||||||
19
LMS.service/Service/FileUploadService/IQiniuUploadService.cs
Normal file
19
LMS.service/Service/FileUploadService/IQiniuUploadService.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using LMS.Repository.DB;
|
||||||
|
using LMS.Repository.DTO;
|
||||||
|
using LMS.Repository.FileUpload;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static LMS.Repository.DTO.FileUploadDto;
|
||||||
|
using static LMS.Repository.FileUpload.FileRequestReturn;
|
||||||
|
|
||||||
|
namespace LMS.service.Service.FileUploadService
|
||||||
|
{
|
||||||
|
public interface IQiniuUploadService
|
||||||
|
{
|
||||||
|
Task<APIResponseModel<UploadResult>> UploadBase64Async(ByteUploadRequest request, string machineId);
|
||||||
|
//Task<List<FileUploads>> GetUserFilesAsync(string userId, int page = 1, int pageSize = 20);
|
||||||
|
//Task<int> GetUserFilesCountAsync(string userId);
|
||||||
|
//Task<bool> DeleteFileAsync(long fileId, string userId);
|
||||||
|
Task<ActionResult<APIResponseModel<CollectionResponse<FileMachineRequestReturn>>>> GetFilesByMachineId(string machineId, int page, int pageSize);
|
||||||
|
Task<ActionResult<APIResponseModel<CollectionResponse<FileUserRequestReturn>>>> GetFilesByUser(long requestUserId, long userId, int page, int pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
454
LMS.service/Service/FileUploadService/QiniuUploadService.cs
Normal file
454
LMS.service/Service/FileUploadService/QiniuUploadService.cs
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
using LMS.Common.Extensions;
|
||||||
|
using LMS.DAO;
|
||||||
|
using LMS.DAO.OptionDAO;
|
||||||
|
using LMS.DAO.UserDAO;
|
||||||
|
using LMS.Repository.DB;
|
||||||
|
using LMS.Repository.DTO;
|
||||||
|
using LMS.Repository.FileUpload;
|
||||||
|
using LMS.Tools.ImageTool;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Qiniu.Http;
|
||||||
|
using Qiniu.IO;
|
||||||
|
using Qiniu.IO.Model;
|
||||||
|
using Qiniu.RS;
|
||||||
|
using Qiniu.Util;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using static LMS.Common.Enums.ResponseCodeEnum;
|
||||||
|
using static LMS.Repository.DTO.FileUploadDto;
|
||||||
|
using static LMS.Repository.FileUpload.FileRequestReturn;
|
||||||
|
using Options = LMS.Repository.DB.Options;
|
||||||
|
|
||||||
|
namespace LMS.service.Service.FileUploadService
|
||||||
|
{
|
||||||
|
public class QiniuUploadService : IQiniuUploadService
|
||||||
|
{
|
||||||
|
private readonly FileUploadSettings _uploadSettings;
|
||||||
|
private readonly ApplicationDbContext _dbContext;
|
||||||
|
private readonly UploadManager _uploadManager;
|
||||||
|
private readonly ILogger<QiniuUploadService> _logger;
|
||||||
|
private readonly UserBasicDao _userBasicDao;
|
||||||
|
private readonly OptionGlobalDAO _optionGlobalDAO;
|
||||||
|
|
||||||
|
public QiniuUploadService(
|
||||||
|
IOptions<FileUploadSettings> uploadSettings,
|
||||||
|
ILogger<QiniuUploadService> logger,
|
||||||
|
UserBasicDao userBasicDao,
|
||||||
|
OptionGlobalDAO optionGlobalDAO,
|
||||||
|
ApplicationDbContext dbContext)
|
||||||
|
{
|
||||||
|
_uploadSettings = uploadSettings.Value;
|
||||||
|
_logger = logger;
|
||||||
|
_dbContext = dbContext;
|
||||||
|
_optionGlobalDAO = optionGlobalDAO;
|
||||||
|
_userBasicDao = userBasicDao; ;
|
||||||
|
_uploadManager = new UploadManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过字节数组上传文件到七牛云
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="machineId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<APIResponseModel<UploadResult>> UploadBase64Async(ByteUploadRequest request, string machineId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 将 文件的base64 字符串转换为字节数组
|
||||||
|
var fileBytes = ConvertBase64ToBytes(request.File);
|
||||||
|
if (fileBytes == null || fileBytes.Length == 0)
|
||||||
|
{
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的文件数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 验证数据
|
||||||
|
var validationResult = ValidateUploadRequest(request, fileBytes);
|
||||||
|
if (!validationResult.Success)
|
||||||
|
{
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, validationResult.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取用户
|
||||||
|
long? userId = await GetUserIdFromMachine(machineId);
|
||||||
|
if (userId == null)
|
||||||
|
{
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的机器ID或未找到关联用户");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 校验当前用户是不是超出了上传限制
|
||||||
|
var userFilesCount = await GetUserUploadToday(userId.Value);
|
||||||
|
if (userFilesCount >= 5)
|
||||||
|
{
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "今日上传文件数量已达上限,请明天再试");
|
||||||
|
}
|
||||||
|
|
||||||
|
QiniuSettings? qiniuSettings = await _optionGlobalDAO.FindAndReturnOption<QiniuSettings>("SYS_QiniuSetting");
|
||||||
|
if (qiniuSettings == null || string.IsNullOrEmpty(qiniuSettings.AccessKey) || string.IsNullOrEmpty(qiniuSettings.SecretKey) || string.IsNullOrEmpty(qiniuSettings.BucketName) || string.IsNullOrEmpty(qiniuSettings.Domain))
|
||||||
|
{
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.ParameterError, "配置不完整,请检查配置,请联系管理员");
|
||||||
|
}
|
||||||
|
|
||||||
|
Mac mac = new(qiniuSettings.AccessKey, qiniuSettings.SecretKey);
|
||||||
|
|
||||||
|
|
||||||
|
// 4. 生成文件key
|
||||||
|
var fileKey = GenerateFileKey(userId.Value, request.FileName);
|
||||||
|
|
||||||
|
|
||||||
|
// 5. 生成上传凭证
|
||||||
|
var putPolicy = new PutPolicy
|
||||||
|
{
|
||||||
|
Scope = qiniuSettings.BucketName
|
||||||
|
};
|
||||||
|
if (qiniuSettings.DeleteDay != null)
|
||||||
|
{
|
||||||
|
putPolicy.DeleteAfterDays = qiniuSettings.DeleteDay.Value; // 设置过期时间
|
||||||
|
}
|
||||||
|
putPolicy.SetExpires(3600);
|
||||||
|
var token = Auth.CreateUploadToken(mac, putPolicy.ToJsonString());
|
||||||
|
|
||||||
|
// 6. 计算文件哈希
|
||||||
|
var hash = ComputeSHA1Hash(fileBytes);
|
||||||
|
|
||||||
|
// 7. 上传到七牛云
|
||||||
|
HttpResult uploadResult;
|
||||||
|
using (var stream = new MemoryStream(fileBytes))
|
||||||
|
{
|
||||||
|
uploadResult = await _uploadManager.UploadStreamAsync(stream, fileKey, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 检查上传结果
|
||||||
|
if (uploadResult.Code != 200)
|
||||||
|
{
|
||||||
|
_logger.LogError($"文件上传失败, 上传用户ID: {userId}, 错误信息: {uploadResult.Text}");
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.SystemError, $"文件上传失败: {uploadResult.Text}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 构建访问URL
|
||||||
|
var qiniuUrl = BuildFileUrl(qiniuSettings.Domain, fileKey);
|
||||||
|
|
||||||
|
// 10. 保存到数据库
|
||||||
|
var fileUpload = new FileUploads
|
||||||
|
{
|
||||||
|
UserId = userId.Value,
|
||||||
|
FileName = request.FileName,
|
||||||
|
FileKey = fileKey,
|
||||||
|
FileSize = fileBytes.Length,
|
||||||
|
ContentType = request.ContentType ?? "application/octet-stream",
|
||||||
|
Hash = hash,
|
||||||
|
QiniuUrl = qiniuUrl,
|
||||||
|
UploadTime = DateTime.Now,
|
||||||
|
Status = "active",
|
||||||
|
CreatedAt = DateTime.Now,
|
||||||
|
DeleteTime = qiniuSettings.DeleteDay != null ? BeijingTimeExtension.GetBeijingTime().AddDays((double)qiniuSettings.DeleteDay) : DateTime.MaxValue // 默认未删除
|
||||||
|
};
|
||||||
|
|
||||||
|
_dbContext.FileUploads.Add(fileUpload);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
return APIResponseModel<UploadResult>.CreateSuccessResponseModel(new UploadResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "上传成功",
|
||||||
|
Url = qiniuUrl,
|
||||||
|
FileKey = fileKey,
|
||||||
|
Hash = hash,
|
||||||
|
FileId = fileUpload.Id,
|
||||||
|
FileSize = fileBytes.Length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 这里可以记录日志或处理异常
|
||||||
|
_logger.LogError(ex, $"文件上传失败, 上传机器码: {machineId}");
|
||||||
|
return APIResponseModel<UploadResult>.CreateErrorResponseModel(ResponseCode.SystemError, $"上传失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对应机器码上传的图片信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="machineId"></param>
|
||||||
|
/// <param name="page"></param>
|
||||||
|
/// <param name="pageSize"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<ActionResult<APIResponseModel<CollectionResponse<FileMachineRequestReturn>>>> GetFilesByMachineId(string machineId, int page, int pageSize)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. 判断机器码 是不是 存在 并且获取对应的ID
|
||||||
|
long? userId = await GetUserIdFromMachine(machineId);
|
||||||
|
if (userId == null)
|
||||||
|
{
|
||||||
|
return APIResponseModel<CollectionResponse<FileMachineRequestReturn>>.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的机器ID或未找到关联用户");
|
||||||
|
}
|
||||||
|
// 2. 获取用户的文件列表
|
||||||
|
var filesList = await GetUserFilesAsync(userId.Value, page, pageSize);
|
||||||
|
|
||||||
|
// 4. 构建返回结果
|
||||||
|
var fileList = filesList.fileList.Select(f => new FileMachineRequestReturn
|
||||||
|
{
|
||||||
|
MachineId = machineId,
|
||||||
|
FileName = f.FileName,
|
||||||
|
FileSize = f.FileSize,
|
||||||
|
ContentType = f.ContentType,
|
||||||
|
Hash = f.Hash,
|
||||||
|
Url = f.QiniuUrl,
|
||||||
|
UploadTime = f.UploadTime,
|
||||||
|
CreatedAt = f.CreatedAt,
|
||||||
|
DeleteTime = f.DeleteTime
|
||||||
|
}).ToList();
|
||||||
|
var response = new CollectionResponse<FileMachineRequestReturn>
|
||||||
|
{
|
||||||
|
Current = page,
|
||||||
|
Total = filesList.totlaCount,
|
||||||
|
Collection = fileList
|
||||||
|
};
|
||||||
|
return APIResponseModel<CollectionResponse<FileMachineRequestReturn>>.CreateSuccessResponseModel(response);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 这里可以记录日志或处理异常
|
||||||
|
_logger.LogError(ex, $"获取文件列表失败, 机器码: {machineId}");
|
||||||
|
return APIResponseModel<CollectionResponse<FileMachineRequestReturn>>.CreateErrorResponseModel(ResponseCode.SystemError, "获取文件列表失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定的用户的文件列表,如果是超级管理员则获取所有用户的文件列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requestUserId"></param>
|
||||||
|
/// <param name="page"></param>
|
||||||
|
/// <param name="pageSize"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<ActionResult<APIResponseModel<CollectionResponse<FileUserRequestReturn>>>> GetFilesByUser(long requestUserId, long userId, int page, int pageSize)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. 判断用户是不是超级管理员,不是超级管理员只能获取自己的
|
||||||
|
bool isSuperAdmin = await _userBasicDao.CheckUserIsSuperAdmin(requestUserId);
|
||||||
|
|
||||||
|
var fileMessage = (0, new List<FileUploads>());
|
||||||
|
if (isSuperAdmin)
|
||||||
|
{
|
||||||
|
// 超级管理员可以获取所有用户的文件
|
||||||
|
fileMessage = await GetAllUserFilesAsync(page, pageSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (requestUserId != userId)
|
||||||
|
{
|
||||||
|
return APIResponseModel<CollectionResponse<FileUserRequestReturn>>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
|
||||||
|
}
|
||||||
|
// 普通用户只能获取自己的文件
|
||||||
|
fileMessage = await GetUserFilesAsync(requestUserId, page, pageSize);
|
||||||
|
}
|
||||||
|
// 2. 构建返回结果
|
||||||
|
var fileList = fileMessage.Item2.Select(f => new FileUserRequestReturn
|
||||||
|
{
|
||||||
|
Id = f.Id,
|
||||||
|
UserId = requestUserId,
|
||||||
|
FileName = f.FileName,
|
||||||
|
FileSize = f.FileSize,
|
||||||
|
ContentType = f.ContentType,
|
||||||
|
Hash = f.Hash,
|
||||||
|
Url = f.QiniuUrl,
|
||||||
|
UploadTime = f.UploadTime,
|
||||||
|
CreatedAt = f.CreatedAt,
|
||||||
|
DeleteTime = f.DeleteTime
|
||||||
|
}).ToList();
|
||||||
|
var response = new CollectionResponse<FileUserRequestReturn>
|
||||||
|
{
|
||||||
|
Current = page,
|
||||||
|
Total = fileMessage.Item1,
|
||||||
|
Collection = fileList
|
||||||
|
};
|
||||||
|
return APIResponseModel<CollectionResponse<FileUserRequestReturn>>.CreateSuccessResponseModel(response);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"获取用户文件列表失败, 用户ID: {requestUserId}");
|
||||||
|
return APIResponseModel<CollectionResponse<FileUserRequestReturn>>.CreateErrorResponseModel(ResponseCode.SystemError, "获取用户文件列表失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(int totlaCount, List<FileUploads> fileList)> GetUserFilesAsync(long userId, int page = 1, int pageSize = 10)
|
||||||
|
{
|
||||||
|
// 获取用户的文件总数
|
||||||
|
int totalCount = await _dbContext.FileUploads
|
||||||
|
.Where(f => f.UserId == userId && f.Status == "active")
|
||||||
|
.CountAsync();
|
||||||
|
// 获取用户的文件列表
|
||||||
|
if (totalCount == 0)
|
||||||
|
{
|
||||||
|
return (0, new List<FileUploads>());
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileList = _dbContext.FileUploads
|
||||||
|
.Where(f => f.UserId == userId && f.Status == "active")
|
||||||
|
.OrderByDescending(f => f.UploadTime)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToListAsync();
|
||||||
|
return (totalCount, await fileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(int totleCount, List<FileUploads> fileList)> GetAllUserFilesAsync(int page = 1, int pageSize = 10)
|
||||||
|
{
|
||||||
|
// 获取所有用户的文件总数
|
||||||
|
int totalCount = await _dbContext.FileUploads
|
||||||
|
.Where(f => f.Status == "active")
|
||||||
|
.CountAsync();
|
||||||
|
if (totalCount == 0)
|
||||||
|
{
|
||||||
|
return (0, new List<FileUploads>());
|
||||||
|
}
|
||||||
|
// 获取所有用户的文件列表
|
||||||
|
List<FileUploads> fileUploads = await _dbContext.FileUploads
|
||||||
|
.Where(f => f.Status == "active")
|
||||||
|
.OrderByDescending(f => f.UploadTime)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToListAsync();
|
||||||
|
return (totalCount, fileUploads);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetUserFilesCountAsync(long userId)
|
||||||
|
{
|
||||||
|
return await _dbContext.FileUploads
|
||||||
|
.CountAsync(f => f.UserId == userId && f.Status == "active");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GetUserUploadToday(long userId)
|
||||||
|
{
|
||||||
|
return await _dbContext.FileUploads
|
||||||
|
.CountAsync(f => f.UserId == userId && f.CreatedAt.Date == BeijingTimeExtension.GetBeijingTime().Date);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[]? ConvertBase64ToBytes(string base64String)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(base64String))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 检查是否以 "data:" 开头并包含 base64 编码
|
||||||
|
if (base64String.StartsWith("data:"))
|
||||||
|
{
|
||||||
|
// 提取 base64 编码部分
|
||||||
|
var commaIndex = base64String.IndexOf(',');
|
||||||
|
if (commaIndex >= 0)
|
||||||
|
{
|
||||||
|
base64String = base64String.Substring(commaIndex + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 判断会不会出现异常
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 尝试将 base64 字符串转换为字节数组
|
||||||
|
return Convert.FromBase64String(base64String);
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{
|
||||||
|
// 如果格式不正确,返回 null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<long?> GetUserIdFromMachine(string machineId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(machineId))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Machine? machine = await _dbContext.Machine
|
||||||
|
.Where(m => m.MachineId == machineId && (m.DeactivationTime > BeijingTimeExtension.GetBeijingTime() || m.DeactivationTime == null))
|
||||||
|
.FirstOrDefaultAsync(); // 改回原来的 FirstOrDefaultAsync
|
||||||
|
|
||||||
|
if (machine != null)
|
||||||
|
{
|
||||||
|
return machine.UserID;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"未找到与机器ID {machineId} 关联的用户ID");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 私有方法
|
||||||
|
private UploadResult ValidateUploadRequest(ByteUploadRequest request, byte[] fileBytes)
|
||||||
|
{
|
||||||
|
if (fileBytes == null || fileBytes.Length == 0)
|
||||||
|
{
|
||||||
|
return new UploadResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "文件字节数据不能为空"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(request.FileName))
|
||||||
|
{
|
||||||
|
return new UploadResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "文件名不能为空"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileBytes.Length > _uploadSettings.MaxFileSize)
|
||||||
|
{
|
||||||
|
return new UploadResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"文件大小不能超过 {_uploadSettings.MaxFileSize / (1024 * 1024)}MB"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_uploadSettings.AllowedContentTypes.Count != 0 &&
|
||||||
|
!string.IsNullOrEmpty(request.ContentType) &&
|
||||||
|
!_uploadSettings.AllowedContentTypes.Contains(request.ContentType.ToLower()))
|
||||||
|
{
|
||||||
|
return new UploadResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"不支持的文件类型: {request.ContentType}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查实际的文件类型是否在允许的列表中
|
||||||
|
// 只检查是否为图片,不是图片就拒绝
|
||||||
|
if (!ImageTypeDetector.IsValidImage(fileBytes))
|
||||||
|
{
|
||||||
|
return new UploadResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "只支持图片格式文件 (JPEG, PNG, GIF, BMP, WEBP)"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UploadResult { Success = true };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateFileKey(long userId, string fileName)
|
||||||
|
{
|
||||||
|
var date = DateTime.Now.ToString("yyyyMMdd");
|
||||||
|
var guid = Guid.NewGuid().ToString("N");
|
||||||
|
var extension = Path.GetExtension(fileName);
|
||||||
|
return $"diantu/user/{userId}/{date}/{guid}{extension}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ComputeSHA1Hash(byte[] data)
|
||||||
|
{
|
||||||
|
var hash = SHA1.HashData(data);
|
||||||
|
return Convert.ToHexString(hash).ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildFileUrl(string domain, string fileKey)
|
||||||
|
{
|
||||||
|
return $"{domain}/{fileKey}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -68,6 +68,16 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Version": "1.1.3",
|
"FileUploadSettings": {
|
||||||
|
"MaxFileSize": 5242880,
|
||||||
|
"AllowedContentTypes": [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/jpg",
|
||||||
|
"image/png",
|
||||||
|
"image/gif",
|
||||||
|
"image/webp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Version": "1.1.4",
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,43 @@
|
|||||||
-- 文件上传记录表
|
/*
|
||||||
CREATE TABLE FileUploads (
|
Navicat Premium Dump SQL
|
||||||
Id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
UserId BIGINT NOT NULL, -- 用户ID
|
|
||||||
FileName VARCHAR(255) NOT NULL, -- 原始文件名
|
|
||||||
FileKey VARCHAR(500) NOT NULL, -- 七牛云存储key
|
|
||||||
FileSize BIGINT NOT NULL, -- 文件大小
|
|
||||||
ContentType VARCHAR(100) NOT NULL, -- 内容类型
|
|
||||||
Hash VARCHAR(100) NOT NULL, -- 文件哈希值
|
|
||||||
QiniuUrl VARCHAR(1000) NOT NULL, -- 七牛云访问URL
|
|
||||||
UploadTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
Status VARCHAR(20) NOT NULL DEFAULT 'active',
|
|
||||||
CreatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- 创建索引
|
Source Server : 亿速云(国内)
|
||||||
CREATE INDEX IX_FileUploads_UserId ON FileUploads(UserId);
|
Source Server Type : MySQL
|
||||||
CREATE INDEX IX_FileUploads_FileKey ON FileUploads(FileKey);
|
Source Server Version : 80018 (8.0.18)
|
||||||
CREATE INDEX IX_FileUploads_UploadTime ON FileUploads(UploadTime);
|
Source Host : yisurds-66dc0b453c05d4.rds.ysydb1.com:14080
|
||||||
|
Source Schema : LMS_TEST
|
||||||
|
|
||||||
|
Target Server Type : MySQL
|
||||||
|
Target Server Version : 80018 (8.0.18)
|
||||||
|
File Encoding : 65001
|
||||||
|
|
||||||
|
Date: 18/06/2025 16:31:33
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for FileUploads
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `FileUploads`;
|
||||||
|
CREATE TABLE `FileUploads` (
|
||||||
|
`Id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`UserId` bigint(20) NOT NULL,
|
||||||
|
`FileName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`FileKey` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`FileSize` bigint(20) NOT NULL,
|
||||||
|
`ContentType` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`Hash` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`QiniuUrl` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`UploadTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`Status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',
|
||||||
|
`CreatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`DeleteTime` datetime NOT NULL COMMENT '删除时间',
|
||||||
|
PRIMARY KEY (`Id`) USING BTREE,
|
||||||
|
INDEX `IX_FileUploads_UserId`(`UserId` ASC) USING BTREE,
|
||||||
|
INDEX `IX_FileUploads_FileKey`(`FileKey` ASC) USING BTREE,
|
||||||
|
INDEX `IX_FileUploads_UploadTime`(`UploadTime` ASC) USING BTREE
|
||||||
|
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user