😎优化文件存储数据库相关代码

This commit is contained in:
zuohuaijun 2025-06-20 13:54:56 +08:00
parent e241ce5c3b
commit 7ae8e5e00f
8 changed files with 143 additions and 64 deletions

View File

@ -6,7 +6,7 @@
"MaxSize": 51200, // KB1024*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-
"EnableSaveFileToDb": false //便
"EnableSaveDb": false //
},
"OSSProvider": {
"Enabled": false,

View File

@ -27,10 +27,10 @@
<PackageReference Include="AspectCore.Extensions.Reflection" Version="2.4.0" />
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.1" Aliases="BouncyCastleV2" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.0.6" />
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.7.88" />
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.7.88" />
<PackageReference Include="Furion.Pure" Version="4.9.7.88" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.0.7" />
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.7.89" />
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.7.89" />
<PackageReference Include="Furion.Pure" Version="4.9.7.89" />
<PackageReference Include="Hardware.Info" Version="101.0.1.1" />
<PackageReference Include="Hashids.net" Version="1.7.0" />
<PackageReference Include="IPTools.China" Version="1.6.0" />

View File

@ -35,10 +35,9 @@ public sealed class UploadOptions : IConfigurableOptions
public bool EnableMd5 { get; set; }
/// <summary>
/// 把文件保存到数据库的表里
/// 启用文件存储到数据库
/// </summary>
public bool EnableSaveFileToDb { get; set; }
public bool EnableSaveDb { get; set; }
}
/// <summary>

View File

@ -1,11 +1,9 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Furion.DependencyInjection;
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
public class DbFileProvider : ICustomFileProvider, ITransient
@ -24,7 +22,8 @@ public class DbFileProvider : ICustomFileProvider, ITransient
}
public async Task<string> DownloadFileBase64Async(SysFile sysFile)
{// 考虑可能会曾经配置成保存到本地文件
{
// 若先前配置成保存到本地文件
if (string.IsNullOrEmpty(sysFile.Provider) || sysFile.Provider == "Local")
{
var provider = App.GetService<DefaultFileProvider>();
@ -45,7 +44,7 @@ public class DbFileProvider : ICustomFileProvider, ITransient
public async Task<FileStreamResult> GetFileStreamResultAsync(SysFile sysFile, string fileName)
{
// 考虑可能会曾经配置成保存到本地文件
// 若先前配置成保存到本地文件
if (string.IsNullOrEmpty(sysFile.Provider) || sysFile.Provider == "Local")
{
var provider = App.GetService<DefaultFileProvider>();
@ -68,9 +67,9 @@ public class DbFileProvider : ICustomFileProvider, ITransient
public async Task<SysFile> UploadFileAsync(IFormFile file, SysFile newFile, string path, string finalName)
{
newFile.Provider = "Database"; // 数据库存储 Provider 显示为Database
// 读取文件内容到字节数组
newFile.Provider = "Database"; // 数据库存储 Provider 显示为Database
// 读取文件内容到字节数组
byte[] fileContent;
using (var memoryStream = new MemoryStream())
{

View File

@ -131,27 +131,19 @@ public class SysFileService : IDynamicApiController, ITransient
}
/// <summary>
/// 在upload路径下载文件 🔖
/// 根据文件Id和MD5下载与db存储模式路径对应 🔖
/// </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)
[HttpGet("/upload/downloadfile")]
[DisplayName("根据文件Id和MD5下载")]
public async Task<IActionResult> DownloadFile2([FromQuery] long id, [FromQuery] string fileMd5)
{
var file = await GetFile(id);
if (file.FileMd5 != null && file.FileMd5 != fileMd5)
throw Oops.Bah("文件校验信息不符");
fileName = HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
if (file.FileMd5 != null && file.FileMd5 != fileMd5) throw Oops.Oh("文件校验信息不符");
var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8"));
return await GetFileStreamResult(file, fileName);
}

View File

@ -72,7 +72,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("获取租户分页列表")]
[DisplayName("获取租户分页列表")]
public async Task<SqlSugarPagedList<TenantOutput>> Page(PageTenantInput input)
{
return await _sysTenantRep.AsQueryable()
@ -115,7 +115,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// 获取库隔离的租户列表
/// </summary>
/// <returns></returns>
[NonAction]
[NonAction]
public async Task<List<SysTenant>> GetTenantDbList()
{
return await _sysTenantRep.GetListAsync(u => u.TenantType == TenantTypeEnum.Db && u.Status == StatusEnum.Enable);
@ -128,7 +128,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// <returns></returns>
[UnitOfWork]
[ApiDescriptionSettings(Name = "Add"), HttpPost]
[DisplayName("增加租户")]
[DisplayName("增加租户")]
public async Task AddTenant(AddTenantInput input)
{
var isExist = await _sysOrgRep.IsAnyAsync(u => u.Name == input.Name);
@ -171,7 +171,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("设置租户状态")]
[DisplayName("设置租户状态")]
public async Task<int> SetStatus(TenantInput input)
{
var tenant = await _sysTenantRep.GetByIdAsync(input.Id);
@ -277,7 +277,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
[DisplayName("删除租户")]
[DisplayName("删除租户")]
public async Task DeleteTenant(DeleteTenantInput input)
{
// 禁止删除默认租户
@ -321,7 +321,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Update"), HttpPost]
[DisplayName("更新租户")]
[DisplayName("更新租户")]
public async Task UpdateTenant(UpdateTenantInput input)
{
var isExist = await _sysOrgRep.IsAnyAsync(u => u.Name == input.Name && u.Id != input.OrgId);
@ -374,7 +374,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// <param name="input"></param>
/// <returns></returns>
[UnitOfWork]
[DisplayName("授权租户管理员角色菜单")]
[DisplayName("授权租户管理员角色菜单")]
public async Task GrantMenu(RoleMenuInput input)
{
// 获取租户管理员角色【sys_admin】
@ -393,7 +393,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("获取租户管理员角色拥有菜单Id集合")]
[DisplayName("获取租户管理员角色拥有菜单Id集合")]
public async Task<List<long>> GetOwnMenuList([FromQuery] TenantUserInput input)
{
var roleIds = await _sysUserRoleService.GetUserRoleIdList(input.UserId);
@ -405,7 +405,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("重置租户管理员密码")]
[DisplayName("重置租户管理员密码")]
public async Task<string> ResetPwd(TenantUserInput input)
{
var password = await _sysConfigService.GetConfigValueByCode<string>(ConfigConst.SysPassword);
@ -418,7 +418,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// 同步所有租户数据库 🔖
/// </summary>
/// <returns></returns>
[DisplayName("同步所有租户数据库")]
[DisplayName("同步所有租户数据库")]
public async Task SyncTenantDb()
{
var tenantList = await _sysTenantRep.GetListAsync();
@ -433,7 +433,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// </summary>
/// <param name="tenantId"></param>
/// <returns></returns>
[NonAction]
[NonAction]
public async Task CacheTenant(long tenantId = 0)
{
// 移除 ISqlSugarClient 中的库连接并排除默认主库
@ -456,7 +456,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("创建租户数据库")]
[DisplayName("创建租户数据库")]
public async Task InitTenantDb(TenantInput input)
{
var tenant = await _sysTenantRep.GetByIdAsync(input.Id);
@ -492,7 +492,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("创建租户数据")]
[DisplayName("创建租户数据")]
public async Task InitTenantData(TenantInput input)
{
var tenant = await _sysTenantRep.GetByIdAsync(input.Id);
@ -509,7 +509,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("获取租户下的用户列表")]
[DisplayName("获取租户下的用户列表")]
public async Task<List<SysUser>> UserList(TenantIdInput input)
{
return await _sysUserRep.AsQueryable().ClearFilter().Where(u => u.TenantId == input.TenantId).ToListAsync();
@ -519,7 +519,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// 获取租户数据库连接
/// </summary>
/// <returns></returns>
[NonAction]
[NonAction]
public SqlSugarScopeProvider GetTenantDbConnectionScope(long tenantId)
{
var iTenant = _sysTenantRep.AsTenant();
@ -567,7 +567,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// <returns></returns>
[SuppressMonitor]
[AllowAnonymous]
[DisplayName("获取系统信息")]
[DisplayName("获取系统信息")]
public async Task<dynamic> GetSysInfo(long tenantId)
{
//// 还可以根据域名判断租户
@ -575,11 +575,9 @@ 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)
throw Oops.Bah($"租户信息不存在:{tenantId}");
// 若租户系统标题为空,则获取默认租户系统信息(兼容已有未配置的租户)
var tenant = await _sysTenantRep.GetFirstAsync(u => u.Id == tenantId) ?? throw Oops.Oh($"租户信息不存在:{tenantId}");
// 若租户系统标题为空,则获取默认租户系统信息(兼容已有未配置的租户)
if (string.IsNullOrWhiteSpace(tenant.Title))
tenant = await _sysTenantRep.GetFirstAsync(u => u.Id == SqlSugarConst.DefaultTenantId);
@ -625,7 +623,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// </summary>
/// <returns></returns>
[UnitOfWork]
[DisplayName("保存系统信息")]
[DisplayName("保存系统信息")]
public async Task SaveSysInfo(SysInfoInput input)
{
if (input.TenantId < 1)
@ -702,7 +700,7 @@ public class SysTenantService : IDynamicApiController, ITransient
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
[DisplayName("上传轮播图单文件")]
[DisplayName("上传轮播图单文件")]
public async Task<SysFile> UploadCarouselFile([Required] IFormFile file)
{
var tenantId = long.Parse(App.User?.FindFirst(ClaimConst.TenantId)?.Value ?? "0");

View File

@ -2,7 +2,7 @@
"name": "admin.net.pro",
"type": "module",
"version": "2.4.33",
"lastBuildTime": "2025.06.19",
"lastBuildTime": "2025.06.20",
"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
"author": "zuohuaijun",
"license": "MIT",
@ -24,7 +24,7 @@
"@vue-office/docx": "^1.6.2",
"@vue-office/excel": "^1.7.14",
"@vue-office/pdf": "^2.0.9",
"@vueuse/core": "^13.3.0",
"@vueuse/core": "^13.4.0",
"@vxe-ui/plugin-export-xlsx": "^4.2.2",
"@vxe-ui/plugin-render-element": "^4.0.11",
"@wangeditor/editor": "^5.1.23",
@ -83,8 +83,8 @@
"vue-router": "^4.5.1",
"vue-signature-pad": "^3.0.2",
"vue3-tree-org": "^4.2.2",
"vxe-pc-ui": "^4.6.26",
"vxe-table": "^4.13.43",
"vxe-pc-ui": "^4.6.27",
"vxe-table": "^4.13.44",
"xe-utils": "^3.7.5",
"xlsx-js-style": "^1.2.0"
},
@ -108,7 +108,7 @@
"prettier": "^3.5.3",
"rollup-plugin-visualizer": "^6.0.3",
"sass": "^1.89.2",
"terser": "^5.43.0",
"terser": "^5.43.1",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vite-plugin-cdn-import": "^1.0.1",

View File

@ -738,6 +738,59 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = localVarFormParams;
return {
url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
options: localVarRequestOptions,
};
},
/**
*
* @summary Id和MD5下载db存储模式路径对应 🔖
* @param {number} [id]
* @param {string} [fileMd5]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
uploadDownloadfileGet: async (id?: number, fileMd5?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/upload/downloadfile`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, 'https://example.com');
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication Bearer required
// http bearer authentication required
if (configuration && configuration.accessToken) {
const accessToken = typeof configuration.accessToken === 'function'
? await configuration.accessToken()
: await configuration.accessToken;
localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
}
if (id !== undefined) {
localVarQueryParameter['id'] = id;
}
if (fileMd5 !== undefined) {
localVarQueryParameter['fileMd5'] = fileMd5;
}
const query = new URLSearchParams(localVarUrlObj.search);
for (const key in localVarQueryParameter) {
query.set(key, localVarQueryParameter[key]);
}
for (const key in options.params) {
query.set(key, options.params[key]);
}
localVarUrlObj.search = (new URLSearchParams(query)).toString();
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
options: localVarRequestOptions,
@ -952,6 +1005,21 @@ export const SysFileApiFp = function(configuration?: Configuration) {
return axios.request(axiosRequestArgs);
};
},
/**
*
* @summary Id和MD5下载db存储模式路径对应 🔖
* @param {number} [id]
* @param {string} [fileMd5]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async uploadDownloadfileGet(id?: number, fileMd5?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminNETResultIActionResult>>> {
const localVarAxiosArgs = await SysFileApiAxiosParamCreator(configuration).uploadDownloadfileGet(id, fileMd5, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
return axios.request(axiosRequestArgs);
};
},
}
};
@ -1105,6 +1173,17 @@ export const SysFileApiFactory = function (configuration?: Configuration, basePa
async apiSysFileUploadSignaturePostForm(file?: Blob, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultSysFile>> {
return SysFileApiFp(configuration).apiSysFileUploadSignaturePostForm(file, options).then((request) => request(axios, basePath));
},
/**
*
* @summary Id和MD5下载db存储模式路径对应 🔖
* @param {number} [id]
* @param {string} [fileMd5]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async uploadDownloadfileGet(id?: number, fileMd5?: string, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultIActionResult>> {
return SysFileApiFp(configuration).uploadDownloadfileGet(id, fileMd5, options).then((request) => request(axios, basePath));
},
};
};
@ -1273,4 +1352,16 @@ export class SysFileApi extends BaseAPI {
public async apiSysFileUploadSignaturePostForm(file?: Blob, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultSysFile>> {
return SysFileApiFp(this.configuration).apiSysFileUploadSignaturePostForm(file, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary Id和MD5下载db存储模式路径对应 🔖
* @param {number} [id]
* @param {string} [fileMd5]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof SysFileApi
*/
public async uploadDownloadfileGet(id?: number, fileMd5?: string, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultIActionResult>> {
return SysFileApiFp(this.configuration).uploadDownloadfileGet(id, fileMd5, options).then((request) => request(this.axios, this.basePath));
}
}