From 4679bd6c1355efb4e761fd724a3c9c719342fddd Mon Sep 17 00:00:00 2001 From: fuenmao <980740792@qq.com> Date: Mon, 24 Aug 2026 18:16:08 +0800 Subject: [PATCH] =?UTF-8?q?oss=E6=9D=BF=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OnboardingContractDocumentService.cs | 5 +- .../OnboardingPublicSubmissionService.cs | 3 + SimpleAdmin.System/Const/SysDictConst.cs | 7 +- .../Options/OssSettingOptions.cs | 62 ++++++++++ .../Services/Dev/File/FileService.cs | 116 +++++++++++++++++- SimpleAdmin.System/SimpleAdmin.System.csproj | 4 +- SimpleAdmin.System/SimpleAdmin.System.xml | 85 +++++++++++++ SimpleAdmin.System/System.Development.json | 15 ++- SimpleAdmin.System/System.Production.json | 15 ++- .../OnboardingContractTemplateController.cs | 24 +++- .../OnboardingPublicSubmissionController.cs | 9 +- SimpleAdmin.Web.Core/GlobalUsing.cs | 3 +- SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml | 5 + 13 files changed, 341 insertions(+), 12 deletions(-) create mode 100644 SimpleAdmin.System/Options/OssSettingOptions.cs diff --git a/SimpleAdmin.Application/Services/Onboarding/ContractDocument/OnboardingContractDocumentService.cs b/SimpleAdmin.Application/Services/Onboarding/ContractDocument/OnboardingContractDocumentService.cs index ae6d39e..4771350 100644 --- a/SimpleAdmin.Application/Services/Onboarding/ContractDocument/OnboardingContractDocumentService.cs +++ b/SimpleAdmin.Application/Services/Onboarding/ContractDocument/OnboardingContractDocumentService.cs @@ -49,7 +49,10 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ sourceBytes.ToArray(), variables.ToDictionary(x => x.VariableCode, x => x.VariableValue ?? string.Empty)); var name = $"{task.TaskNo}_{contract.ContractName}.docx"; - var fileId = await _fileService.StoreGeneratedFile(source.Engine, name, rendered); + var fileId = await _fileService.StoreGeneratedFile( + SysDictConst.FILE_ENGINE_ALIYUN_OSS, + name, + rendered); contract.GeneratedDocxFileId = fileId; contract.GeneratedAt = DateTime.Now; diff --git a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs index 2066888..79f61aa 100644 --- a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs +++ b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs @@ -262,6 +262,9 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ private async Task GetEditableTask(string token) { + if (string.IsNullOrWhiteSpace(token)) + throw Oops.Bah("入职链接 Token 不能为空"); + var hash = Convert.ToHexString( global::System.Security.Cryptography.SHA256.HashData( global::System.Text.Encoding.UTF8.GetBytes(token))); diff --git a/SimpleAdmin.System/Const/SysDictConst.cs b/SimpleAdmin.System/Const/SysDictConst.cs index bc789cb..2007d26 100644 --- a/SimpleAdmin.System/Const/SysDictConst.cs +++ b/SimpleAdmin.System/Const/SysDictConst.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛 +// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛 // // SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款: // 1.请不要删除和修改根目录下的LICENSE文件。 @@ -68,6 +68,11 @@ public class SysDictConst /// public const string FILE_ENGINE_MINIO = "MINIO"; + /// + /// 阿里云 OSS,当前仅供入职业务文件使用。 + /// + public const string FILE_ENGINE_ALIYUN_OSS = "ALIYUN_OSS"; + #endregion 上传文件引擎 #region 系统字典分类 diff --git a/SimpleAdmin.System/Options/OssSettingOptions.cs b/SimpleAdmin.System/Options/OssSettingOptions.cs new file mode 100644 index 0000000..ebfbe32 --- /dev/null +++ b/SimpleAdmin.System/Options/OssSettingOptions.cs @@ -0,0 +1,62 @@ +namespace SimpleAdmin.System; + +/// +/// 阿里云 OSS 及文档预览服务配置。 +/// +public class OssSettingOptions +{ + /// + /// OSS 访问密钥 Id。 + /// + public string AccessKeyID { get; set; } + + /// + /// OSS 访问密钥。 + /// + public string AccessKeySecret { get; set; } + + /// + /// 用于换取临时凭证的 RAM 角色 ARN。 + /// + public string RoleArn { get; set; } + + /// + /// STS 角色会话名称。 + /// + public string RoleSessionName { get; set; } + + /// + /// STS 服务端点。 + /// + public string StsEndpoint { get; set; } + + /// + /// OSS Bucket 名称。 + /// + public string Bucket { get; set; } + + /// + /// OSS 服务端点。 + /// + public string EndPoint { get; set; } + + /// + /// IMM 服务端点。 + /// + public string IMMEndpoint { get; set; } + + /// + /// IMM 项目名称。 + /// + public string IMMProject { get; set; } + + /// + /// OSS 自定义访问域名。 + /// + public string OSSUrl { get; set; } + + /// + /// 阿里云地域 Id。 + /// + public string RegionId { get; set; } +} \ No newline at end of file diff --git a/SimpleAdmin.System/Services/Dev/File/FileService.cs b/SimpleAdmin.System/Services/Dev/File/FileService.cs index a93cfa2..849f898 100644 --- a/SimpleAdmin.System/Services/Dev/File/FileService.cs +++ b/SimpleAdmin.System/Services/Dev/File/FileService.cs @@ -8,6 +8,8 @@ // 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。 // 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。 +using Aliyun.OSS; + namespace SimpleAdmin.System; /// @@ -95,6 +97,8 @@ public class FileService : DbRepository, IFileService return GetFileStreamResult(devFile.StoragePath, devFile.Name); if (devFile.Engine == SysDictConst.FILE_ENGINE_MINIO) return await GetFileStreamResultFromMinio(devFile.ObjName, devFile.Name); + if (devFile.Engine == SysDictConst.FILE_ENGINE_ALIYUN_OSS) + return await GetFileStreamResultFromAliyunOss(devFile); return null; } return null; @@ -142,6 +146,9 @@ public class FileService : DbRepository, IFileService } break; + case SysDictConst.FILE_ENGINE_ALIYUN_OSS: + (bucketName, objName, storageUrl) = await StorageAliyunOss(objectId, file); + break; default: throw Oops.Bah("不支持的文件引擎"); @@ -154,13 +161,13 @@ public class FileService : DbRepository, IFileService Engine = engine, Bucket = bucketName, Name = file.FileName, - Suffix = fileSuffix.Split(".")[1], + Suffix = fileSuffix.TrimStart('.'), ObjName = objName, SizeKb = fileSizeKb, SizeInfo = GetSizeInfo(fileSizeKb), StoragePath = storageUrl }; - if (engine != CateGoryConst.CONFIG_FILE_LOCAL)//如果不是本地,设置下载地址 + if (engine != SysDictConst.FILE_ENGINE_LOCAL)//如果不是本地,设置下载地址 { devFile.DownloadPath = storageUrl; } @@ -236,6 +243,111 @@ public class FileService : DbRepository, IFileService return (fileObjectName, downloadUrl); } + /// + /// 将入职业务文件存储到阿里云 OSS。 + /// + private async Task<(string bucketName, string objName, string downloadUrl)> StorageAliyunOss( + long fileId, + IFormFile file) + { + var setting = GetOssSetting(); + var suffix = Path.GetExtension(file.FileName).ToLowerInvariant(); + var objectName = $"onboarding/{DateTime.Now:yyyy/MM/dd}/{fileId}{suffix}"; + var metadata = new ObjectMetadata(); + if (!string.IsNullOrWhiteSpace(file.ContentType)) + metadata.ContentType = file.ContentType; + + await using var stream = file.OpenReadStream(); + var client = CreateOssClient(setting); + client.PutObject(setting.Bucket, objectName, stream, metadata); + + var downloadUrl = string.IsNullOrWhiteSpace(setting.OSSUrl) + ? $"https://{setting.Bucket}.{setting.EndPoint.Trim().TrimEnd('/')}/{objectName}" + : $"{setting.OSSUrl.Trim().TrimEnd('/')}/{objectName}"; + + return (setting.Bucket, objectName, downloadUrl); + } + + /// + /// 从阿里云 OSS 读取业务文件并通过统一下载接口返回。 + /// + private async Task GetFileStreamResultFromAliyunOss(SysFile file) + { + var setting = GetOssSetting(); + var client = CreateOssClient(setting); + var ossObject = client.GetObject(file.Bucket, file.ObjName); + var memory = new MemoryStream(); + await using (ossObject.Content) + { + await ossObject.Content.CopyToAsync(memory); + } + + memory.Position = 0; + var fileName = HttpUtility.UrlEncode(file.Name, Encoding.GetEncoding("UTF-8")); + return new FileStreamResult(memory, "application/octet-stream") + { + FileDownloadName = fileName + }; + } + + /// + /// 读取并校验阿里云 OSS 配置。 + /// + private static OssSettingOptions GetOssSetting() + { + var setting = App.GetConfig("OssSetting", true); + var configurationIncomplete = setting == null + || string.IsNullOrWhiteSpace(setting.AccessKeyID) + || string.IsNullOrWhiteSpace(setting.AccessKeySecret) + || string.IsNullOrWhiteSpace(setting.Bucket) + || string.IsNullOrWhiteSpace(setting.EndPoint); + if (configurationIncomplete) + throw Oops.Bah("阿里云 OSS 配置不完整"); + + return setting; + } + + /// + /// 通过 STS 临时凭证创建阿里云 OSS 客户端。 + /// + private static OssClient CreateOssClient(OssSettingOptions setting) + { + var endpoint = setting.EndPoint.Trim(); + if (!endpoint.StartsWith("http://", StringComparison.OrdinalIgnoreCase) + && !endpoint.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + { + endpoint = "https://" + endpoint; + } + + if (string.IsNullOrWhiteSpace(setting.RoleArn)) + return new OssClient(endpoint, setting.AccessKeyID, setting.AccessKeySecret); + + var stsConfig = new AlibabaCloud.OpenApiClient.Models.Config + { + AccessKeyId = setting.AccessKeyID, + AccessKeySecret = setting.AccessKeySecret, + Endpoint = setting.StsEndpoint + }; + var stsClient = new AlibabaCloud.SDK.Sts20150401.Client(stsConfig); + var request = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest + { + RoleArn = setting.RoleArn, + RoleSessionName = string.IsNullOrWhiteSpace(setting.RoleSessionName) + ? "onboardx-server" + : setting.RoleSessionName, + DurationSeconds = 3600 + }; + var credentials = stsClient.AssumeRole(request).Body.Credentials; + if (credentials == null) + throw Oops.Bah("获取阿里云 OSS 临时凭证失败"); + + return new OssClient( + endpoint, + credentials.AccessKeyId, + credentials.AccessKeySecret, + credentials.SecurityToken); + } + /// /// 获取文件大小 /// diff --git a/SimpleAdmin.System/SimpleAdmin.System.csproj b/SimpleAdmin.System/SimpleAdmin.System.csproj index 3e50797..e09d0c4 100644 --- a/SimpleAdmin.System/SimpleAdmin.System.csproj +++ b/SimpleAdmin.System/SimpleAdmin.System.csproj @@ -1,7 +1,9 @@ - + + + diff --git a/SimpleAdmin.System/SimpleAdmin.System.xml b/SimpleAdmin.System/SimpleAdmin.System.xml index a1c6113..015a1a7 100644 --- a/SimpleAdmin.System/SimpleAdmin.System.xml +++ b/SimpleAdmin.System/SimpleAdmin.System.xml @@ -506,6 +506,11 @@ MINIO + + + 阿里云 OSS,当前仅供入职业务文件使用。 + + 系统 @@ -2670,6 +2675,66 @@ 对象映射 + + + 阿里云 OSS 及文档预览服务配置。 + + + + + OSS 访问密钥 Id。 + + + + + OSS 访问密钥。 + + + + + 用于换取临时凭证的 RAM 角色 ARN。 + + + + + STS 角色会话名称。 + + + + + STS 服务端点。 + + + + + OSS Bucket 名称。 + + + + + OSS 服务端点。 + + + + + IMM 服务端点。 + + + + + IMM 项目名称。 + + + + + OSS 自定义访问域名。 + + + + + 阿里云地域 Id。 + + 系统配置 @@ -3588,6 +3653,26 @@ + + + 将入职业务文件存储到阿里云 OSS。 + + + + + 从阿里云 OSS 读取业务文件并通过统一下载接口返回。 + + + + + 读取并校验阿里云 OSS 配置。 + + + + + 通过 STS 临时凭证创建阿里云 OSS 客户端。 + + 获取文件大小 diff --git a/SimpleAdmin.System/System.Development.json b/SimpleAdmin.System/System.Development.json index 1fd9f00..1f193d3 100644 --- a/SimpleAdmin.System/System.Development.json +++ b/SimpleAdmin.System/System.Development.json @@ -24,5 +24,18 @@ "InitSeedData": false, //是否初始化种子数据 "SuperAdminViewAllData": true, //是否超级管理员可以查看所有数据 "UseMessageCenter": false //是否启用通知中心 - } + }, + "OssSetting": { + "AccessKeyID": "LTAI5t9VQvLERXtMHC9BK31f", + "AccessKeySecret": "YomhludvaOmLZo1K0vUDoeBhWRjxWL", + "RoleArn": "acs:ram::1436008104961462:role/onboardx-sts", + "RoleSessionName": "onboardx-server", + "StsEndpoint": "sts.cn-beijing.aliyuncs.com", + "Bucket": "onboardx", + "EndPoint": "oss-cn-beijing.aliyuncs.com", + "IMMEndpoint": "", + "IMMProject": "", + "OSSUrl": "https://onboardx.oss-cn-beijing.aliyuncs.com", + "RegionId": "oss-cn-beijing" + } } \ No newline at end of file diff --git a/SimpleAdmin.System/System.Production.json b/SimpleAdmin.System/System.Production.json index 1fd9f00..1f193d3 100644 --- a/SimpleAdmin.System/System.Production.json +++ b/SimpleAdmin.System/System.Production.json @@ -24,5 +24,18 @@ "InitSeedData": false, //是否初始化种子数据 "SuperAdminViewAllData": true, //是否超级管理员可以查看所有数据 "UseMessageCenter": false //是否启用通知中心 - } + }, + "OssSetting": { + "AccessKeyID": "LTAI5t9VQvLERXtMHC9BK31f", + "AccessKeySecret": "YomhludvaOmLZo1K0vUDoeBhWRjxWL", + "RoleArn": "acs:ram::1436008104961462:role/onboardx-sts", + "RoleSessionName": "onboardx-server", + "StsEndpoint": "sts.cn-beijing.aliyuncs.com", + "Bucket": "onboardx", + "EndPoint": "oss-cn-beijing.aliyuncs.com", + "IMMEndpoint": "", + "IMMProject": "", + "OSSUrl": "https://onboardx.oss-cn-beijing.aliyuncs.com", + "RegionId": "oss-cn-beijing" + } } \ No newline at end of file diff --git a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingContractTemplateController.cs b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingContractTemplateController.cs index 640b06b..ed86c31 100644 --- a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingContractTemplateController.cs +++ b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingContractTemplateController.cs @@ -9,10 +9,32 @@ namespace SimpleAdmin.Web.Core; public class OnboardingContractTemplateController { private readonly IOnboardingContractTemplateService _service; + private readonly IFileService _fileService; - public OnboardingContractTemplateController(IOnboardingContractTemplateService service) + public OnboardingContractTemplateController( + IOnboardingContractTemplateService service, + IFileService fileService) { _service = service; + _fileService = fileService; + } + + /// + /// 将入职合同 DOCX 模板上传到阿里云 OSS。 + /// + [HttpPost("file/upload-docx")] + [Consumes("multipart/form-data")] + [RequestSizeLimit(20 * 1024 * 1024)] + public async Task UploadDocx([FromForm] IFormFile file) + { + if (file == null || file.Length == 0) + throw Oops.Bah("请选择需要上传的 DOCX 文件"); + + var suffix = Path.GetExtension(file.FileName); + if (!string.Equals(suffix, ".docx", StringComparison.OrdinalIgnoreCase)) + throw Oops.Bah("合同模板仅支持 DOCX 文件"); + + return await _fileService.UploadFile(SysDictConst.FILE_ENGINE_ALIYUN_OSS, file); } /// diff --git a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs index 7b8db3d..692d081 100644 --- a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs +++ b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs @@ -59,11 +59,14 @@ public class OnboardingPublicSubmissionController [HttpPost("upload-material")] [Consumes("multipart/form-data")] [RequestSizeLimit(20 * 1024 * 1024)] - public async Task UploadMaterial([FromForm] string token, [FromForm] long materialTypeId, IFormFile file) + public async Task UploadMaterial( + [FromQuery] string token, + [FromQuery] long materialTypeId, + [FromForm] IFormFile file) { if (file == null || file.Length == 0) { - throw new ArgumentException("请选择需要上传的文件"); + throw Oops.Bah("请选择需要上传的文件"); } await _service.ValidateMaterialUpload(new OnboardingMaterialUploadValidationInput @@ -74,7 +77,7 @@ public class OnboardingPublicSubmissionController FileLength = file.Length }); - var fileId = await _fileService.UploadFile(SysDictConst.FILE_ENGINE_LOCAL, file); + var fileId = await _fileService.UploadFile(SysDictConst.FILE_ENGINE_ALIYUN_OSS, file); await _service.SaveMaterial(new OnboardingSaveMaterialInput { Token = token, diff --git a/SimpleAdmin.Web.Core/GlobalUsing.cs b/SimpleAdmin.Web.Core/GlobalUsing.cs index aba71bf..d68469f 100644 --- a/SimpleAdmin.Web.Core/GlobalUsing.cs +++ b/SimpleAdmin.Web.Core/GlobalUsing.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛 +// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛 // // SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款: // 1.请不要删除和修改根目录下的LICENSE文件。 @@ -10,6 +10,7 @@ global using MoYu; global using MoYu.DynamicApiController; +global using MoYu.FriendlyException; global using MoYu.Logging; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; diff --git a/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml b/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml index 5d9d422..5e260ac 100644 --- a/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml +++ b/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml @@ -91,6 +91,11 @@ 提供合同模板、版本、变量和适用范围相关 HTTP 接口。 + + + 将入职合同 DOCX 模板上传到阿里云 OSS。 + + 分页查询合同模板。