Merge pull request '把上传文件保存在数据库中' (#363) from koy07555/Admin.NET.Pro:上传文件保存到Db into v2
Reviewed-on: https://code.adminnet.top/Admin.NET/Admin.NET.Pro/pulls/363
This commit is contained in:
commit
e241ce5c3b
@ -1,11 +1,12 @@
|
||||
{
|
||||
{
|
||||
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
|
||||
|
||||
"Upload": {
|
||||
"Path": "upload/{yyyy}/{MM}/{dd}", // 文件上传目录
|
||||
"MaxSize": 51200, // 文件最大限制KB:1024*50
|
||||
"ContentType": [ "image/jpg", "image/png", "image/jpeg", "image/gif", "image/bmp", "text/plain", "text/xml", "application/pdf", "application/msword", "application/vnd.ms-excel", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "video/mp4", "application/wps-office.docx", "application/wps-office.xlsx", "application/wps-office.pptx", "application/vnd.android.package-archive", "application/octet-stream" ],
|
||||
"EnableMd5": false // 启用文件MDF5验证-防止重复上传
|
||||
"EnableMd5": false, // 启用文件MDF5验证-防止重复上传
|
||||
"EnableSaveFileToDb": false //把文件保存到数据库的表里,个别小的应用如果要集群部署时,这种方式比较方便。
|
||||
},
|
||||
"OSSProvider": {
|
||||
"Enabled": false,
|
||||
|
||||
27
Admin.NET/Admin.NET.Core/Entity/SysFileContent.cs
Normal file
27
Admin.NET/Admin.NET.Core/Entity/SysFileContent.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 系统文件内容表
|
||||
/// </summary>
|
||||
[SugarTable(null, "系统文件内容表")]
|
||||
[SysTable]
|
||||
public partial class SysFileContent : EntityBase
|
||||
{
|
||||
/// <summary>
|
||||
/// SysFile里的Id
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "SysFile里的Id")]
|
||||
public long SysFileId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件内容
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "文件内容", ColumnDataType = "blob,bytea,binary", IsNullable = false)]
|
||||
public byte[] Content { get; set; }
|
||||
}
|
||||
@ -32,7 +32,13 @@ public sealed class UploadOptions : IConfigurableOptions
|
||||
/// 启用文件MD5验证
|
||||
/// </summary>
|
||||
/// <remarks>防止重复上传</remarks>
|
||||
public bool EnableMd5 { get; set; }
|
||||
public bool EnableMd5 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 把文件保存到数据库的表里
|
||||
/// </summary>
|
||||
|
||||
public bool EnableSaveFileToDb { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -0,0 +1,93 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Furion.DependencyInjection;
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class DbFileProvider : ICustomFileProvider, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysFileContent> _sysFileContentRep;
|
||||
|
||||
public DbFileProvider(SqlSugarRepository<SysFileContent> sysFileContentRep)
|
||||
{
|
||||
_sysFileContentRep = sysFileContentRep;
|
||||
}
|
||||
|
||||
public async Task DeleteFileAsync(SysFile sysFile)
|
||||
{
|
||||
// 从数据库中删除文件内容
|
||||
await _sysFileContentRep.DeleteAsync(u => u.SysFileId == sysFile.Id);
|
||||
}
|
||||
|
||||
public async Task<string> DownloadFileBase64Async(SysFile sysFile)
|
||||
{// 考虑可能会曾经配置成保存到本地文件
|
||||
if (string.IsNullOrEmpty(sysFile.Provider) || sysFile.Provider == "Local")
|
||||
{
|
||||
var provider = App.GetService<DefaultFileProvider>();
|
||||
return await provider.DownloadFileBase64Async(sysFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从数据库获取文件内容
|
||||
var fileContent = await _sysFileContentRep.CopyNew().GetFirstAsync(u => u.SysFileId == sysFile.Id);
|
||||
if (fileContent == null || fileContent.Content == null)
|
||||
{
|
||||
Log.Error($"DbFileProvider.DownloadFileBase64:文件[{sysFile.Id},{sysFile.Url}]内容不存在");
|
||||
throw Oops.Oh($"文件[{sysFile.FilePath}]内容不存在");
|
||||
}
|
||||
return Convert.ToBase64String(fileContent.Content);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<FileStreamResult> GetFileStreamResultAsync(SysFile sysFile, string fileName)
|
||||
{
|
||||
// 考虑可能会曾经配置成保存到本地文件
|
||||
if (string.IsNullOrEmpty(sysFile.Provider) || sysFile.Provider == "Local")
|
||||
{
|
||||
var provider = App.GetService<DefaultFileProvider>();
|
||||
return await provider.GetFileStreamResultAsync(sysFile, fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从数据库获取文件内容
|
||||
var fileContent = await _sysFileContentRep.GetFirstAsync(u => u.SysFileId == sysFile.Id);
|
||||
if (fileContent == null || fileContent.Content == null)
|
||||
{
|
||||
Log.Error($"DbFileProvider.GetFileStreamResultAsync:文件[{sysFile.Id},{sysFile.Url}]内容不存在");
|
||||
throw Oops.Oh($"文件[{sysFile.FilePath}]内容不存在");
|
||||
}
|
||||
// 创建内存流
|
||||
var memoryStream = new MemoryStream(fileContent.Content);
|
||||
return new FileStreamResult(memoryStream, "application/octet-stream") { FileDownloadName = fileName + sysFile.Suffix };
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SysFile> UploadFileAsync(IFormFile file, SysFile newFile, string path, string finalName)
|
||||
{
|
||||
newFile.Provider = "Database"; // 数据库存储 Provider 显示为Database
|
||||
|
||||
// 读取文件内容到字节数组
|
||||
byte[] fileContent;
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
await file.CopyToAsync(memoryStream);
|
||||
fileContent = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
// 保存文件内容到数据库
|
||||
var sysFileContent = new SysFileContent
|
||||
{
|
||||
SysFileId = newFile.Id,
|
||||
Content = fileContent
|
||||
};
|
||||
await _sysFileContentRep.InsertAsync(sysFileContent);
|
||||
|
||||
// 设置文件URL
|
||||
newFile.Url = $"upload/downloadfile?fileMd5={newFile.FileMd5}&id={newFile.Id}&fileName=tmp{newFile.Suffix}";
|
||||
return newFile;
|
||||
}
|
||||
}
|
||||
@ -40,7 +40,7 @@ public class DefaultFileProvider : ICustomFileProvider, ITransient
|
||||
|
||||
public async Task<SysFile> UploadFileAsync(IFormFile file, SysFile newFile, string path, string finalName)
|
||||
{
|
||||
newFile.Provider = ""; // 本地存储 Provider 显示为空
|
||||
newFile.Provider = "Local";
|
||||
var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, path);
|
||||
if (!Directory.Exists(filePath))
|
||||
Directory.CreateDirectory(filePath);
|
||||
|
||||
@ -43,6 +43,10 @@ public class SysFileService : IDynamicApiController, ITransient
|
||||
{
|
||||
_customFileProvider = _namedServiceProvider.GetService<ITransient>(nameof(SSHFileProvider));
|
||||
}
|
||||
else if (App.Configuration["Upload:EnableSaveFileToDb"].ToBoolean())
|
||||
{
|
||||
_customFileProvider = _namedServiceProvider.GetService<ITransient>(nameof(DbFileProvider));
|
||||
}
|
||||
else
|
||||
{
|
||||
_customFileProvider = _namedServiceProvider.GetService<ITransient>(nameof(DefaultFileProvider));
|
||||
@ -126,6 +130,31 @@ public class SysFileService : IDynamicApiController, ITransient
|
||||
return await GetFileStreamResult(file, fileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在upload路径下载文件 🔖
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="fileMd5"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// 这个接口定义在 /upload/downloadfile与DbFileProvider返回的路径要对应得上,
|
||||
/// 之所以定义在这里,有利于使用反向代理指入我们的upload文件夹时不用修改文件的下载路径。
|
||||
/// 比如我们的前端就把他自己的upload转发到了我们后端的upload路径,所以这种情况下,我们用db来保存文件时,就不用修改下载文件的方式
|
||||
/// </remarks>
|
||||
[Route("/upload/downloadfile")]
|
||||
[AllowAnonymous]
|
||||
[HttpGet]
|
||||
[DisplayName("在upload路径")]
|
||||
public async Task<IActionResult> DownloadFile2([FromQuery]long id, [FromQuery] string fileMd5, [FromQuery] string fileName)
|
||||
{
|
||||
var file = await GetFile(id);
|
||||
if (file.FileMd5 != null && file.FileMd5 != fileMd5)
|
||||
throw Oops.Bah("文件校验信息不符");
|
||||
fileName = HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
|
||||
return await GetFileStreamResult(file, fileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件预览 🔖
|
||||
/// </summary>
|
||||
@ -248,12 +277,13 @@ public class SysFileService : IDynamicApiController, ITransient
|
||||
// 判断是否重复上传的文件
|
||||
var sizeKb = input.File.Length / 1024; // 大小KB
|
||||
var fileMd5 = string.Empty;
|
||||
// 不管要不要验证md5,也把Md6计算出来,方便统计重复的文件。
|
||||
await using (var fileStream = input.File.OpenReadStream())
|
||||
{
|
||||
fileMd5 = OssUtils.ComputeContentMd5(fileStream, fileStream.Length);
|
||||
}
|
||||
if (_uploadOptions.EnableMd5)
|
||||
{
|
||||
await using (var fileStream = input.File.OpenReadStream())
|
||||
{
|
||||
fileMd5 = OssUtils.ComputeContentMd5(fileStream, fileStream.Length);
|
||||
}
|
||||
// Mysql8 中如果使用了 utf8mb4_general_ci 之外的编码会出错,尽量避免在条件里使用.ToString()
|
||||
// 因为 Squsugar 并不是把变量转换为字符串来构造SQL语句,而是构造了CAST(123 AS CHAR)这样的语句,这样这个返回值是utf8mb4_general_ci,所以容易出错。
|
||||
var sysFile = await _sysFileRep.GetFirstAsync(u => u.FileMd5 == fileMd5 && u.SizeKb == sizeKb);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
@ -576,7 +576,8 @@ public class SysTenantService : IDynamicApiController, ITransient
|
||||
if (tenantId < 1) tenantId = long.Parse(App.User?.FindFirst(ClaimConst.TenantId)?.Value ?? "0");
|
||||
if (tenantId < 1) tenantId = SqlSugarConst.DefaultTenantId;
|
||||
var tenant = await _sysTenantRep.GetFirstAsync(u => u.Id == tenantId);
|
||||
if (tenant == null) return "";
|
||||
if (tenant == null)
|
||||
throw Oops.Bah($"租户信息不存在:{tenantId}");
|
||||
|
||||
// 若租户系统标题为空,则获取默认租户系统信息(兼容已有未配置的租户)
|
||||
if (string.IsNullOrWhiteSpace(tenant.Title))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user