From 70f3212840190783ca9c6fa890142038278ee2f0 Mon Sep 17 00:00:00 2001 From: zpc Date: Sat, 15 Nov 2025 15:47:04 +0800 Subject: [PATCH] =?UTF-8?q?=E8=85=BE=E8=AE=AF=E4=BA=91cos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Infrastructure/Model/OptionsSetting.cs | 17 +++ .../Controllers/CommonController.cs | 21 +++- ZR.Admin.WebApi/appsettings.json | 12 +- ZR.Common/TencentCosHelper.cs | 110 ++++++++++++++++++ ZR.Common/ZR.Common.csproj | 2 + .../Services/IService/ISysFileService.cs | 9 ++ ZR.ServiceCore/Services/SysFileService.cs | 36 ++++++ 7 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 ZR.Common/TencentCosHelper.cs diff --git a/Infrastructure/Model/OptionsSetting.cs b/Infrastructure/Model/OptionsSetting.cs index 4f9f552..5190665 100644 --- a/Infrastructure/Model/OptionsSetting.cs +++ b/Infrastructure/Model/OptionsSetting.cs @@ -32,6 +32,10 @@ namespace Infrastructure.Model /// 阿里云oss /// public ALIYUN_OSS ALIYUN_OSS { get; set; } + /// + /// 腾讯云COS + /// + public TENCENT_COS TENCENT_COS { get; set; } public JwtSettings JwtSettings { get; set; } /// /// 代码生成配置 @@ -86,6 +90,19 @@ namespace Infrastructure.Model public int MaxSize { get; set; } = 100; } + /// + /// 腾讯云COS存储 + /// + public class TENCENT_COS + { + public string REGION { get; set; } + public string SECRET_ID { get; set; } + public string SECRET_KEY { get; set; } + public string BUCKET_NAME { get; set; } + public string DOMAIN_URL { get; set; } + public int MAX_SIZE { get; set; } = 100; + } + /// /// Jwt /// diff --git a/ZR.Admin.WebApi/Controllers/CommonController.cs b/ZR.Admin.WebApi/Controllers/CommonController.cs index d6dde76..2e39c71 100644 --- a/ZR.Admin.WebApi/Controllers/CommonController.cs +++ b/ZR.Admin.WebApi/Controllers/CommonController.cs @@ -98,7 +98,7 @@ namespace ZR.Admin.WebApi.Controllers /// [HttpPost] [ActionPermissionFilter(Permission = "common")] - public async Task UploadFile([FromForm] UploadDto uploadDto, IFormFile file, StoreType storeType = StoreType.LOCAL) + public async Task UploadFile([FromForm] UploadDto uploadDto, IFormFile file, StoreType storeType = StoreType.TENCENT) { if (file == null) throw new CustomException(ResultCode.PARAM_ERROR, "上传文件不能为空"); SysFile sysfile = new(); @@ -152,6 +152,25 @@ namespace ZR.Admin.WebApi.Controllers if (sysfile.Id <= 0) { return ToResponse(ApiResult.Error("阿里云连接失败")); } break; case StoreType.TENCENT: + int TencentMaxSize = OptionsSetting.TENCENT_COS.MAX_SIZE; + if (OptionsSetting.TENCENT_COS.REGION.IsEmpty()) + { + return ToResponse(ResultCode.CUSTOM_ERROR, "配置文件缺失"); + } + if ((fileSize / 1024) > TencentMaxSize) + { + return ToResponse(ResultCode.CUSTOM_ERROR, "上传文件过大,不能超过 " + TencentMaxSize + " MB"); + } + sysfile = new(formFile.FileName, uploadDto.FileName, fileExt, fileSize + "kb", uploadDto.FileDir, HttpContext.GetName()) + { + StoreType = (int)StoreType.TENCENT, + FileType = formFile.ContentType, + ClassifyType = uploadDto.ClassifyType, + CategoryId = uploadDto.CategoryId, + }; + sysfile = await SysFileService.SaveFileToTencent(sysfile, uploadDto, formFile); + + if (sysfile.Id <= 0) { return ToResponse(ApiResult.Error("腾讯云连接失败")); } break; case StoreType.QINIU: break; diff --git a/ZR.Admin.WebApi/appsettings.json b/ZR.Admin.WebApi/appsettings.json index 996787d..b012ca5 100644 --- a/ZR.Admin.WebApi/appsettings.json +++ b/ZR.Admin.WebApi/appsettings.json @@ -45,7 +45,7 @@ "RefreshTokenTime": 30, //分钟 "TokenType": "Bearer" }, - "MainDb": "0",// 多租户主库配置ID + "MainDb": "0", // 多租户主库配置ID "UseTenant": 0, //是否启用多租户 0:不启用 1:启用 "InjectClass": [ "ZR.Repository", "ZR.Service", "ZR.Tasks", "ZR.ServiceCore", "ZR.Mall", "ZR.LiveForum" ], //自动注入类 "ShowDbLog": true, //是否打印db日志 @@ -70,6 +70,16 @@ "domainUrl": "http://xxx.xxx.com", //访问资源域名 "maxSize": 100 //上传文件大小限制 100M }, + //腾讯云COS存储配置 + "TENCENT_COS": { + "APPID": "1308826010", + "REGION": "ap-shanghai", //eg:ap-beijing + "SECRET_ID": "AKIDVyMfzKZdZP8zkNyOdsFuSsBJDB7EScs0", + "SECRET_KEY": "89GWr7JPWYTL8ueHlAYowGZnvzKZjqs9", + "BUCKET_NAME": "miaoyu", + "DOMAIN_URL": "https://miaoyu-1308826010.cos.ap-shanghai.myqcloud.com", //访问资源域名 + "MAX_SIZE": 100 //上传文件大小限制 100M + }, //企业微信通知配置 "WxCorp": { "AgentID": "", diff --git a/ZR.Common/TencentCosHelper.cs b/ZR.Common/TencentCosHelper.cs new file mode 100644 index 0000000..33690dd --- /dev/null +++ b/ZR.Common/TencentCosHelper.cs @@ -0,0 +1,110 @@ +using COSXML; +using COSXML.Auth; +using COSXML.Model.Object; + +using Infrastructure; + +using System; +using System.IO; +using System.Net; + +namespace ZR.Common +{ + public class TencentCosHelper + { + static string secretId = AppSettings.GetConfig("TENCENT_COS:SECRET_ID"); + static string secretKey = AppSettings.GetConfig("TENCENT_COS:SECRET_KEY"); + static string region = AppSettings.GetConfig("TENCENT_COS:REGION"); + static string bucketName1 = AppSettings.GetConfig("TENCENT_COS:BUCKET_NAME"); + static string appId = AppSettings.GetConfig("TENCENT_COS:APPID"); + + + /// + /// 获取COS客户端 + /// + /// + private static CosXmlServer GetCosXmlServer() + { + var config = new CosXmlConfig.Builder() + .SetRegion(region) + .Build(); + + var qCloudCredentialProvider = new DefaultQCloudCredentialProvider(secretId, secretKey, 600); + return new CosXmlServer(config, qCloudCredentialProvider); + } + + /// + /// 上传到腾讯云COS + /// + /// 文件流 + /// 存储路径 eg: upload/2020/01/01/xxx.png + /// 存储桶 如果为空默认取配置文件 + /// + public static HttpStatusCode PutObjectFromFile(Stream filestreams, string dirPath, string bucketName = "") + { + if (string.IsNullOrEmpty(bucketName)) { bucketName = bucketName1; } + try + { + dirPath = dirPath.Replace("\\", "/"); + + var cosXml = GetCosXmlServer(); + + var request = new PutObjectRequest(bucketName, dirPath, filestreams); + request.APPID = appId; + var result = cosXml.PutObject(request); + + return result.IsSuccessful() ? HttpStatusCode.OK : HttpStatusCode.BadRequest; + } + catch (COSXML.CosException.CosClientException ex) + { + Console.WriteLine("Failed with CosClientException: {0}", ex.Message); + } + catch (COSXML.CosException.CosServerException ex) + { + Console.WriteLine("Failed with CosServerException. ErrorCode: {0}; ErrorMessage: {1}; RequestID: {2}", + ex.errorCode, ex.Message, ex.requestId); + } + catch (Exception ex) + { + Console.WriteLine("Failed with error info: {0}", ex.Message); + } + return HttpStatusCode.BadRequest; + } + + /// + /// 删除资源 + /// + /// 文件路径 + /// 存储桶 如果为空默认取配置文件 + /// + public static HttpStatusCode DeleteFile(string dirPath, string bucketName = "") + { + if (string.IsNullOrEmpty(bucketName)) { bucketName = bucketName1; } + try + { + dirPath = dirPath.Replace("\\", "/"); + + var cosXml = GetCosXmlServer(); + var request = new DeleteObjectRequest(bucketName, dirPath); + var result = cosXml.DeleteObject(request); + + return result.IsSuccessful() ? HttpStatusCode.OK : HttpStatusCode.BadRequest; + } + catch (COSXML.CosException.CosClientException ex) + { + Console.WriteLine("Failed with CosClientException: {0}", ex.Message); + } + catch (COSXML.CosException.CosServerException ex) + { + Console.WriteLine("Failed with CosServerException. ErrorCode: {0}; ErrorMessage: {1}; RequestID: {2}", + ex.errorCode, ex.errorMessage, ex.requestId); + } + catch (Exception ex) + { + Console.WriteLine("Failed with error info: {0}", ex.Message); + } + return HttpStatusCode.BadRequest; + } + } +} + diff --git a/ZR.Common/ZR.Common.csproj b/ZR.Common/ZR.Common.csproj index bbef2da..79bfdc8 100644 --- a/ZR.Common/ZR.Common.csproj +++ b/ZR.Common/ZR.Common.csproj @@ -8,6 +8,8 @@ + + diff --git a/ZR.ServiceCore/Services/IService/ISysFileService.cs b/ZR.ServiceCore/Services/IService/ISysFileService.cs index bd8be50..aeb88ab 100644 --- a/ZR.ServiceCore/Services/IService/ISysFileService.cs +++ b/ZR.ServiceCore/Services/IService/ISysFileService.cs @@ -31,6 +31,15 @@ namespace ZR.ServiceCore.Services /// Task SaveFileToAliyun(SysFile file, UploadDto dto, IFormFile formFile); + /// + /// 上传文件到腾讯云 + /// + /// + /// + /// + /// + Task SaveFileToTencent(SysFile file, UploadDto dto, IFormFile formFile); + /// /// 按时间来创建文件夹 /// diff --git a/ZR.ServiceCore/Services/SysFileService.cs b/ZR.ServiceCore/Services/SysFileService.cs index b8b39e6..755446e 100644 --- a/ZR.ServiceCore/Services/SysFileService.cs +++ b/ZR.ServiceCore/Services/SysFileService.cs @@ -133,6 +133,42 @@ namespace ZR.ServiceCore.Services return file; } + /// + /// 上传文件到腾讯云 + /// + /// + /// + /// + /// + public async Task SaveFileToTencent(SysFile file, UploadDto dto, IFormFile formFile) + { + string tencentDomainUrl = AppSettings.GetConfig("TENCENT_COS:DOMAIN_URL"); + file.FileName = (file.FileName.IsEmpty() ? HashFileName() : file.FileName) + file.FileExt; + file.StorePath = GetdirPath(file.StorePath); + string finalPath = Path.Combine(file.StorePath, file.FileName); + HttpStatusCode statusCode; + if (dto.Quality > 0) + { + // 压缩图片 + using var stream = new MemoryStream(); + await CompressImageAsync(formFile, stream, dto.Quality); + stream.Position = 0; + statusCode = TencentCosHelper.PutObjectFromFile(stream, finalPath, ""); + } + else + { + statusCode = TencentCosHelper.PutObjectFromFile(formFile.OpenReadStream(), finalPath, ""); + } + if (statusCode != HttpStatusCode.OK) return file; + + file.StorePath = file.StorePath; + file.FileUrl = finalPath; + file.AccessUrl = string.Concat(tencentDomainUrl, "/", file.StorePath.Replace("\\", "/"), "/", file.FileName); + file.Id = await InsertFile(file); + + return file; + } + /// /// 获取文件存储目录 ///