refactor: ♻️ 文件上传功能调整

This commit is contained in:
tk 2025-03-19 16:29:25 +08:00 committed by nsnail
parent 6fed7ec752
commit e0a4a40314
25 changed files with 77 additions and 40 deletions

View File

@ -15,7 +15,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.13.2"> <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.14.2-alpha">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@ -1,9 +1,9 @@
{ {
"version": "2.3.1", "version": "2.3.1",
"devDependencies": { "devDependencies": {
"cz-git": "^1.11.0", "cz-git": "^1.11.1",
"commitizen": "^4.3.1", "commitizen": "^4.3.1",
"prettier": "^3.5.0", "prettier": "^3.5.3",
"standard-version": "^9.5.0" "standard-version": "^9.5.0"
}, },
"config": { "config": {

@ -1 +1 @@
Subproject commit 2f58f3291ae7a31bf935ed26d23fd587d181fb2b Subproject commit b16b1a35559281a9f53d6914f1195a204c9b4185

@ -1 +1 @@
Subproject commit f5509a75ffb26175f7024fb609579050f6f5250d Subproject commit 15717df4499d284ec77c624a281c199a29280a4e

@ -1 +1 @@
Subproject commit d4a92aca6f18b68875126f1974baa15fd2ab1fab Subproject commit 322cbc32df1363755d4b0588923ee7324676c3b6

View File

@ -1,4 +1,4 @@
#r "nuget: NSExt, 2.3.3" #r "nuget: NSExt, 2.3.4"
using NSExt.Extensions; using NSExt.Extensions;
Console.WriteLine("请输入原始名称NetAdmin"); Console.WriteLine("请输入原始名称NetAdmin");

View File

@ -0,0 +1,17 @@
namespace NetAdmin.Domain.Dto.Sys.File;
/// <summary>
/// 响应:文件上传
/// </summary>
public record UploadFileRsp : DataAbstraction
{
/// <summary>
/// 文件名
/// </summary>
public string FileName { get; init; }
/// <summary>
/// 可访问的url地址
/// </summary>
public string Url { get; init; }
}

View File

@ -5,7 +5,7 @@ namespace NetAdmin.Domain.Dto.Sys.Job;
/// </summary> /// </summary>
public sealed record EditJobReq : CreateJobReq public sealed record EditJobReq : CreateJobReq
{ {
/// <inheritdoc cref="System.Version" /> /// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)] [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; } public override long Version { get; init; }
} }

View File

@ -11,7 +11,7 @@ public sealed record SetAvatarReq : Sys_User
[Url(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.网络地址不正确))] [Url(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.网络地址不正确))]
public override string Avatar { get; init; } public override string Avatar { get; init; }
/// <inheritdoc cref="System.Version" /> /// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)] [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; } public override long Version { get; init; }
} }

View File

@ -4,7 +4,7 @@
<ProjectReference Include="../NetAdmin.Infrastructure/NetAdmin.Infrastructure.csproj"/> <ProjectReference Include="../NetAdmin.Infrastructure/NetAdmin.Infrastructure.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CronExpressionDescriptor" Version="2.38.0"/> <PackageReference Include="CronExpressionDescriptor" Version="2.39.0"/>
<PackageReference Include="Cronos" Version="0.9.0"/> <PackageReference Include="Cronos" Version="0.9.0"/>
<PackageReference Include="NetAdmin.CsvHelper" Version="1.0.0"/> <PackageReference Include="NetAdmin.CsvHelper" Version="1.0.0"/>
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14"/> <PackageReference Include="Yitter.IdGenerator" Version="1.0.14"/>

View File

@ -34,7 +34,6 @@ public sealed class ExampleController(IExampleCache cache) : ControllerBase<IExa
/// <summary> /// <summary>
/// 示例分组计数 /// 示例分组计数
/// </summary> /// </summary>
[NonAction]
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryExampleReq> req) public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryExampleReq> req)
{ {
return Cache.CountByAsync(req); return Cache.CountByAsync(req);

View File

@ -5,10 +5,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="NetAdmin.FreeSql.DbContext" Version="1.1.2" Label="refs"/> <PackageReference Include="NetAdmin.FreeSql.DbContext" Version="1.1.2" Label="refs"/>
<PackageReference Include="NetAdmin.FreeSql.Provider.Sqlite" Version="1.1.2" Label="refs"/> <PackageReference Include="NetAdmin.FreeSql.Provider.Sqlite" Version="1.1.2" Label="refs"/>
<PackageReference Include="Gurion" Version="1.2.11" Label="refs"/> <PackageReference Include="Gurion" Version="1.2.12" Label="refs"/>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.2"/> <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.2"/>
<PackageReference Include="Minio" Version="6.0.4"/> <PackageReference Include="Minio" Version="6.0.4"/>
<PackageReference Include="NSExt" Version="2.3.3"/> <PackageReference Include="NSExt" Version="2.3.4"/>
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.5"/> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.5"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -15,8 +15,8 @@ public sealed class MinioHelper(IOptions<UploadOptions> uploadOptions) : IScoped
/// <param name="fileStream">文件流</param> /// <param name="fileStream">文件流</param>
/// <param name="contentType">文件类型</param> /// <param name="contentType">文件类型</param>
/// <param name="fileSize">文件大小</param> /// <param name="fileSize">文件大小</param>
/// <returns>可访问的url地址</returns> /// <returns>文件名,可访问的url地址</returns>
public async Task<string> UploadAsync(string objectName, Stream fileStream, string contentType, long fileSize) public async Task<(string FileName, string Url)> UploadAsync(string objectName, Stream fileStream, string contentType, long fileSize)
{ {
using var minio = new MinioClient().WithEndpoint(uploadOptions.Value.Minio.ServerAddress) using var minio = new MinioClient().WithEndpoint(uploadOptions.Value.Minio.ServerAddress)
.WithCredentials( // .WithCredentials( //
@ -38,6 +38,6 @@ public sealed class MinioHelper(IOptions<UploadOptions> uploadOptions) : IScoped
.WithContentType(contentType); .WithContentType(contentType);
_ = await minio.PutObjectAsync(putArgs).ConfigureAwait(false); _ = await minio.PutObjectAsync(putArgs).ConfigureAwait(false);
return $"{uploadOptions.Value.Minio.AccessUrl}/{uploadOptions.Value.Minio.BucketName}/{objectName}"; return (objectName, $"{uploadOptions.Value.Minio.AccessUrl}/{uploadOptions.Value.Minio.BucketName}/{objectName}");
} }
} }

View File

@ -1,3 +1,5 @@
using NetAdmin.Domain.Dto.Sys.File;
namespace NetAdmin.SysComponent.Application.Modules.Sys; namespace NetAdmin.SysComponent.Application.Modules.Sys;
/// <summary> /// <summary>
@ -8,5 +10,5 @@ public interface IFileModule
/// <summary> /// <summary>
/// 文件上传 /// 文件上传
/// </summary> /// </summary>
Task<string> UploadAsync(IFormFile file); Task<UploadFileRsp> UploadAsync(IFormFile file);
} }

View File

@ -1,3 +1,5 @@
using NetAdmin.Domain.Dto.Sys.File;
namespace NetAdmin.SysComponent.Application.Services.Sys; namespace NetAdmin.SysComponent.Application.Services.Sys;
/// <inheritdoc cref="IFileService" /> /// <inheritdoc cref="IFileService" />
@ -8,7 +10,7 @@ public sealed class FileService(IOptions<UploadOptions> uploadOptions, MinioHelp
/// <exception cref="NetAdminInvalidOperationException">文件不能为空</exception> /// <exception cref="NetAdminInvalidOperationException">文件不能为空</exception>
/// <exception cref="NetAdminInvalidOperationException">允许上传的文件格式</exception> /// <exception cref="NetAdminInvalidOperationException">允许上传的文件格式</exception>
/// <exception cref="NetAdminInvalidOperationException">允许的文件大小</exception> /// <exception cref="NetAdminInvalidOperationException">允许的文件大小</exception>
public async Task<string> UploadAsync(IFormFile file) public async Task<UploadFileRsp> UploadAsync(IFormFile file)
{ {
if (file == null || file.Length < 1) { if (file == null || file.Length < 1) {
throw new NetAdminInvalidOperationException(Ln.); throw new NetAdminInvalidOperationException(Ln.);
@ -22,9 +24,9 @@ public sealed class FileService(IOptions<UploadOptions> uploadOptions, MinioHelp
throw new NetAdminInvalidOperationException($"{Ln.允许的文件大小} {uploadOptions.Value.MaxSize}"); throw new NetAdminInvalidOperationException($"{Ln.允许的文件大小} {uploadOptions.Value.MaxSize}");
} }
var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; var objectName = $"{UserToken.Id}/{file.FileName}";
var objectName = $"{UserToken.Id}/{fileName}";
await using var fs = file.OpenReadStream(); await using var fs = file.OpenReadStream();
return await minioHelper.UploadAsync(objectName, fs, file.ContentType, file.Length).ConfigureAwait(false); var (fileName, url) = await minioHelper.UploadAsync(objectName, fs, file.ContentType, file.Length).ConfigureAwait(false);
return new UploadFileRsp { FileName = fileName, Url = url };
} }
} }

View File

@ -1,3 +1,5 @@
using NetAdmin.Domain.Dto.Sys.File;
namespace NetAdmin.SysComponent.Cache.Sys; namespace NetAdmin.SysComponent.Cache.Sys;
/// <inheritdoc cref="IFileCache" /> /// <inheritdoc cref="IFileCache" />
@ -5,7 +7,7 @@ public sealed class FileCache(IDistributedCache cache, IFileService service) //
: DistributedCache<IFileService>(cache, service), IScoped, IFileCache : DistributedCache<IFileService>(cache, service), IScoped, IFileCache
{ {
/// <inheritdoc /> /// <inheritdoc />
public Task<string> UploadAsync(IFormFile file) public Task<UploadFileRsp> UploadAsync(IFormFile file)
{ {
return Service.UploadAsync(file); return Service.UploadAsync(file);
} }

View File

@ -1,3 +1,5 @@
using NetAdmin.Domain.Dto.Sys.File;
namespace NetAdmin.SysComponent.Host.Controllers.Sys; namespace NetAdmin.SysComponent.Host.Controllers.Sys;
/// <summary> /// <summary>
@ -10,7 +12,7 @@ public sealed class FileController(IFileCache cache) : ControllerBase<IFileCache
/// <summary> /// <summary>
/// 文件上传 /// 文件上传
/// </summary> /// </summary>
public Task<string> UploadAsync(IFormFile file) public Task<UploadFileRsp> UploadAsync(IFormFile file)
{ {
return Cache.UploadAsync(file); return Cache.UploadAsync(file);
} }

View File

@ -3,7 +3,7 @@
<ProjectReference Include="../NetAdmin.Host/NetAdmin.Host.csproj"/> <ProjectReference Include="../NetAdmin.Host/NetAdmin.Host.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.2"/> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.3"/>
<PackageReference Include="xunit" Version="2.9.3"/> <PackageReference Include="xunit" Version="2.9.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2"> <PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@ -1,3 +1,5 @@
using NetAdmin.Domain.Dto.Sys.File;
namespace UnitTests.Sys; namespace UnitTests.Sys;
/// <summary> /// <summary>
@ -11,7 +13,7 @@ public class FileTests(WebTestApplicationFactory<Startup> factory, ITestOutputHe
/// <inheritdoc /> /// <inheritdoc />
[InlineData(null)] [InlineData(null)]
[Theory] [Theory]
public async Task<string> UploadAsync(IFormFile file) public async Task<UploadFileRsp> UploadAsync(IFormFile file)
{ {
var rsp = await PostJsonAsync(typeof(FileController), file); var rsp = await PostJsonAsync(typeof(FileController), file);
Assert.True(rsp.IsSuccessStatusCode); Assert.True(rsp.IsSuccessStatusCode);

View File

@ -52,9 +52,9 @@ public abstract class WebApiTestBase<T>(WebTestApplicationFactory<T> factory, IT
var req = new LoginByPwdReq // var req = new LoginByPwdReq //
{ {
Password Password
= Environment.GetEnvironmentVariable(nameof(WebTestApplicationFactory<T>.TEST_PASSWORD)) ?? = Environment.GetEnvironmentVariable(nameof(WebTestApplicationFactory<>.TEST_PASSWORD)) ??
WebTestApplicationFactory<T>.TEST_PASSWORD WebTestApplicationFactory<T>.TEST_PASSWORD
, Account = Environment.GetEnvironmentVariable(nameof(WebTestApplicationFactory<T>.TEST_ACCOUNT)) ?? , Account = Environment.GetEnvironmentVariable(nameof(WebTestApplicationFactory<>.TEST_ACCOUNT)) ??
WebTestApplicationFactory<T>.TEST_ACCOUNT WebTestApplicationFactory<T>.TEST_ACCOUNT
}; };
var loginAccount = JsonContent.Create(req); var loginAccount = JsonContent.Create(req);

View File

@ -10,12 +10,12 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.3.1", "@element-plus/icons-vue": "2.3.1",
"ace-builds": "1.38.0", "ace-builds": "1.39.0",
"aieditor": "1.3.5", "aieditor": "1.3.5",
"axios": "1.7.9", "axios": "1.8.1",
"crypto-js": "4.2.0", "crypto-js": "4.2.0",
"echarts": "5.6.0", "echarts": "5.6.0",
"element-plus": "2.9.4", "element-plus": "2.9.5",
"json-bigint": "1.0.0", "json-bigint": "1.0.0",
"markdown-it": "14.1.0", "markdown-it": "14.1.0",
"markdown-it-emoji": "3.0.0", "markdown-it-emoji": "3.0.0",
@ -23,7 +23,7 @@
"sortablejs": "1.15.6", "sortablejs": "1.15.6",
"vkbeautify": "0.99.3", "vkbeautify": "0.99.3",
"vue": "3.5.13", "vue": "3.5.13",
"vue-i18n": "11.1.1", "vue-i18n": "11.1.2",
"vue-router": "4.5.0", "vue-router": "4.5.0",
"vue3-ace-editor": "2.2.4", "vue3-ace-editor": "2.2.4",
"vue3-json-viewer": "2.2.2", "vue3-json-viewer": "2.2.2",
@ -32,11 +32,11 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "5.2.1", "@vitejs/plugin-vue": "5.2.1",
"prettier": "3.5.0", "prettier": "3.5.3",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"sass": "1.84.0", "sass": "1.85.1",
"terser": "5.38.2", "terser": "5.39.0",
"vite": "6.1.0" "vite": "6.2.1"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",

View File

@ -27,6 +27,17 @@ export default {
}, },
}, },
/**
* 示例分组计数
*/
countBy: {
url: `${config.API_URL}/api/tpl/example/count.by`,
name: `示例分组计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/** /**
* 创建示例 * 创建示例
*/ */

View File

@ -19,7 +19,7 @@
:on-success="success" :on-success="success"
:show-file-list="showFileList"> :show-file-list="showFileList">
<slot> <slot>
<el-button :disabled="disabled" type="primary">Click to upload</el-button> <el-button :disabled="disabled" type="primary">点击上传</el-button>
</slot> </slot>
<template #tip> <template #tip>
<div v-if="tip" class="el-upload__tip">{{ tip }}</div> <div v-if="tip" class="el-upload__tip">{{ tip }}</div>

View File

@ -134,8 +134,8 @@ export default {
// //
toArr(str) { toArr(str) {
const _arr = [] const _arr = []
const arr = str.split(',') const arr = str?.split(',')
arr.forEach((item) => { arr?.forEach((item) => {
if (item) { if (item) {
const urlArr = item.split('/') const urlArr = item.split('/')
const fileName = urlArr[urlArr.length - 1] const fileName = urlArr[urlArr.length - 1]

View File

@ -10,8 +10,8 @@ export default {
parseData: function (res) { parseData: function (res) {
return { return {
code: res.code, //分析状态字段结构 code: res.code, //分析状态字段结构
// fileName: null, //分析文件名称 fileName: res.data.fileName, //分析文件名称
src: res.data, //分析图片远程地址结构 src: res.data.url, //分析图片远程地址结构
msg: res.msg, //分析描述字段结构 msg: res.msg, //分析描述字段结构
} }
}, },