diff --git a/src/MilitaryTrainingManagement/Configuration/FileStorageOptions.cs b/src/MilitaryTrainingManagement/Configuration/FileStorageOptions.cs new file mode 100644 index 0000000..f863624 --- /dev/null +++ b/src/MilitaryTrainingManagement/Configuration/FileStorageOptions.cs @@ -0,0 +1,16 @@ +namespace MilitaryTrainingManagement.Configuration; + +public class FileStorageOptions +{ + public const string SectionName = "FileStorage"; + + /// + /// 文件存储基础路径(相对或绝对路径) + /// + public string BasePath { get; set; } = "./wwwroot/uploads"; + + /// + /// 访问文件的 URL 前缀 + /// + public string UrlPrefix { get; set; } = "/uploads"; +} diff --git a/src/MilitaryTrainingManagement/Program.cs b/src/MilitaryTrainingManagement/Program.cs index 494617f..787f3c3 100644 --- a/src/MilitaryTrainingManagement/Program.cs +++ b/src/MilitaryTrainingManagement/Program.cs @@ -7,6 +7,7 @@ using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using MilitaryTrainingManagement.Authorization; +using MilitaryTrainingManagement.Configuration; using MilitaryTrainingManagement.Data; using MilitaryTrainingManagement.Models.Enums; using MilitaryTrainingManagement.Services.Implementations; @@ -14,6 +15,10 @@ using MilitaryTrainingManagement.Services.Interfaces; var builder = WebApplication.CreateBuilder(args); +// 配置文件存储选项 +builder.Services.Configure( + builder.Configuration.GetSection(FileStorageOptions.SectionName)); + // 添加HttpContextAccessor用于审计拦截器 builder.Services.AddHttpContextAccessor(); @@ -138,6 +143,9 @@ var app = builder.Build(); // 配置HTTP请求管道 +// 启用静态文件服务 +app.UseStaticFiles(); + app.UseSwagger(); app.UseSwaggerUI(); diff --git a/src/MilitaryTrainingManagement/Services/Implementations/FileUploadService.cs b/src/MilitaryTrainingManagement/Services/Implementations/FileUploadService.cs index 670f4a7..e146164 100644 --- a/src/MilitaryTrainingManagement/Services/Implementations/FileUploadService.cs +++ b/src/MilitaryTrainingManagement/Services/Implementations/FileUploadService.cs @@ -1,4 +1,6 @@ +using MilitaryTrainingManagement.Configuration; using MilitaryTrainingManagement.Services.Interfaces; +using Microsoft.Extensions.Options; using SixLabors.ImageSharp; namespace MilitaryTrainingManagement.Services.Implementations; @@ -8,8 +10,8 @@ namespace MilitaryTrainingManagement.Services.Implementations; /// public class FileUploadService : IFileUploadService { - private readonly IWebHostEnvironment _environment; private readonly ILogger _logger; + private readonly FileStorageOptions _storageOptions; // 照片尺寸限制(放宽限制以适应各种照片) private const int MinPhotoWidth = 100; @@ -22,9 +24,9 @@ public class FileUploadService : IFileUploadService private static readonly string[] AllowedPhotoExtensions = { ".jpg", ".jpeg", ".png" }; private static readonly string[] AllowedDocumentExtensions = { ".pdf", ".doc", ".docx", ".jpg", ".jpeg", ".png" }; - public FileUploadService(IWebHostEnvironment environment, ILogger logger) + public FileUploadService(IOptions storageOptions, ILogger logger) { - _environment = environment; + _storageOptions = storageOptions.Value; _logger = logger; } @@ -108,7 +110,7 @@ public class FileUploadService : IFileUploadService public string GetFullPath(string relativePath) { - return Path.Combine(_environment.WebRootPath ?? _environment.ContentRootPath, "uploads", relativePath); + return Path.Combine(_storageOptions.BasePath, relativePath); } private void ValidateDocument(IFormFile file) @@ -132,7 +134,7 @@ public class FileUploadService : IFileUploadService private async Task SaveFileAsync(IFormFile file, string subFolder) { - var uploadsPath = Path.Combine(_environment.WebRootPath ?? _environment.ContentRootPath, "uploads", subFolder); + var uploadsPath = Path.Combine(_storageOptions.BasePath, subFolder); Directory.CreateDirectory(uploadsPath); var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName).ToLowerInvariant()}"; @@ -144,6 +146,9 @@ public class FileUploadService : IFileUploadService } _logger.LogInformation("文件已保存: {FilePath}", filePath); - return Path.Combine(subFolder, fileName); + + // 返回带 URL 前缀的完整访问路径 + var relativePath = Path.Combine(subFolder, fileName).Replace("\\", "/"); + return $"{_storageOptions.UrlPrefix.TrimEnd('/')}/{relativePath}"; } } diff --git a/src/frontend/src/views/personnel/PersonnelDetail.vue b/src/frontend/src/views/personnel/PersonnelDetail.vue index 0b9e0d2..eac469f 100644 --- a/src/frontend/src/views/personnel/PersonnelDetail.vue +++ b/src/frontend/src/views/personnel/PersonnelDetail.vue @@ -12,7 +12,7 @@
- +
暂无照片 @@ -64,7 +64,7 @@

佐证材料

- + 查看文件 diff --git a/src/frontend/src/views/personnel/PersonnelForm.vue b/src/frontend/src/views/personnel/PersonnelForm.vue index 20978a3..f5c45d9 100644 --- a/src/frontend/src/views/personnel/PersonnelForm.vue +++ b/src/frontend/src/views/personnel/PersonnelForm.vue @@ -312,7 +312,7 @@ async function loadPersonnel() { form.trainingParticipation = person.trainingParticipation || '' form.achievements = person.achievements || '' if (person.photoPath) { - photoPreview.value = `/api/files/${person.photoPath}` + photoPreview.value = person.photoPath } } catch { ElMessage.error('加载人才信息失败')