From 2c3a8842642bb9c37bc551c2b4351754be8958cb Mon Sep 17 00:00:00 2001
From: xiaolipro <2357729423@qq.com>
Date: Wed, 16 Sep 2026 15:47:39 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=B9=E6=8E=A5=E8=85=BE=E8=AE=AF?=
=?UTF-8?q?=E4=BA=91cos?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Directory.Build.props | 2 +-
.../appsettings.Development.json | 15 +
.../ForwardCore/DefaultForwardFlow.cs | 49 +++-
.../InterfaceForward.Application.csproj | 10 +-
.../InterfaceForwardApplicationModule.cs | 2 +
.../OSS/OSSHelper.cs | 263 ------------------
.../OSS/OSSOptions.cs | 32 ---
.../OSS/UploadResultDto.cs | 30 --
.../Services/App/AppsService.cs | 36 ++-
9 files changed, 88 insertions(+), 351 deletions(-)
delete mode 100644 src/InterfaceForward.Application/OSS/OSSHelper.cs
delete mode 100644 src/InterfaceForward.Application/OSS/OSSOptions.cs
delete mode 100644 src/InterfaceForward.Application/OSS/UploadResultDto.cs
diff --git a/Directory.Build.props b/Directory.Build.props
index e7da47d..7d6f04d 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,6 @@
- 8.0.5.16
+ 8.0.5.17
diff --git a/src/InterfaceForward.Api/appsettings.Development.json b/src/InterfaceForward.Api/appsettings.Development.json
index a6e86ac..92b8138 100644
--- a/src/InterfaceForward.Api/appsettings.Development.json
+++ b/src/InterfaceForward.Api/appsettings.Development.json
@@ -4,5 +4,20 @@
"Default": "Debug",
"Microsoft.AspNetCore": "Warning"
}
+ },
+ "ObjectStorage": {
+ "Provider": "TencentCos",
+ "SignUrlsByDefault": true,
+ "DefaultSignedUrlExpiresSeconds": 900,
+ "DefaultUploadExpiresSeconds": 600,
+ "TencentCos": {
+ "Region": "ap-guangzhou",
+ "SecretId": "替换为腾讯云SecretId",
+ "SecretKey": "替换为腾讯云SecretKey",
+ "Bucket": "your-bucket-1250000000",
+ "UseHttps": true,
+ "SignUrls": true,
+ "DurationSecond": 600
+ }
}
}
diff --git a/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs b/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs
index 83ef74a..2e148c9 100644
--- a/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs
+++ b/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs
@@ -5,9 +5,9 @@ using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using Fake.DependencyInjection;
+using Fake.ObjectStorage;
using InterfaceForward.Application.Contracts.ForwardCore;
using InterfaceForward.Application.Helpers;
-using InterfaceForward.Application.OSS;
using InterfaceForward.Domain.Shared;
using InterfaceForward.Domain.Shared.Enum;
using Microsoft.AspNetCore.Components;
@@ -23,6 +23,7 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency
{
private static readonly ConcurrentDictionary DelegateDic = new();
[Inject] public IHttpClientFactory ClientFactory { get; set; } = null!;
+ [Inject] public IObjectStorage ObjectStorage { get; set; } = null!;
public virtual Task InParamRestructure(ForwardCoreContext context)
{
@@ -90,7 +91,7 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency
if (tokens.Count == 0) tokens = response.SelectTokens($"{pdfLabelConfig.Path}").ToList();
if (tokens.Count == 0) return mappedRes;
var token = tokens.First();
- UploadResultDto oss = null!;
+ ObjectStorageSaveResult? oss = null;
switch (Enum.Parse(pdfLabelConfig.Value))
{
case LabelType.Url:
@@ -98,14 +99,14 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency
string url = token.Value()!;
using var r = await ClientFactory.CreateClient().GetAsync(url);
await using var stream = await r.Content.ReadAsStreamAsync();
- oss = OSSHelper.Upload(stream, Guid.NewGuid().ToString("D") + ".pdf");
+ oss = await SaveObjectAsync(stream, Guid.NewGuid().ToString("D") + ".pdf");
break;
}
case LabelType.Base64:
byte[] bytes = Convert.FromBase64String(token.ToString());
if (bytes.Length > 0)
{
- oss = OSSHelper.Upload(new MemoryStream(bytes), Guid.NewGuid().ToString("D") + ".pdf");
+ oss = await SaveObjectAsync(new MemoryStream(bytes), Guid.NewGuid().ToString("D") + ".pdf");
}
break;
@@ -113,8 +114,11 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency
throw new ArgumentOutOfRangeException();
}
- mappedRes["oss_key"] = oss.Key;
- mappedRes["oss_url"] = oss.TempUrl;
+ if (oss != null)
+ {
+ mappedRes["oss_key"] = oss.ObjectKey;
+ mappedRes["oss_url"] = oss.Url;
+ }
}
#endregion
@@ -379,19 +383,19 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency
var base64 = await response.Content.ReadAsStringAsync();
byte[] pdfBytes = Convert.FromBase64String(base64);
using MemoryStream stream = new MemoryStream(pdfBytes);
- var ans = OSSHelper.Upload(stream, Guid.NewGuid().ToString("D") + ".pdf");
- return JsonConvert.SerializeObject(ans);
+ var ans = await SaveObjectAsync(stream, Guid.NewGuid().ToString("D") + ".pdf");
+ return SerializeObjectStorageResult(ans);
}
// 流
if (context.TargetInterface.ResponseContentType is ResponseContentType.csv or ResponseContentType.pdf)
{
- // 上传oss
+ // 上传对象存储
string suffix = context.TargetInterface.ResponseContentType == ResponseContentType.csv ? "csv" : "pdf";
await using var stream = await response.Content.ReadAsStreamAsync();
- var ans = OSSHelper.Upload(stream, Guid.NewGuid().ToString("D") + "." + suffix);
- return JsonConvert.SerializeObject(ans);
+ var ans = await SaveObjectAsync(stream, Guid.NewGuid().ToString("D") + "." + suffix);
+ return SerializeObjectStorageResult(ans);
}
if (response.Content.Headers.ContentType?.CharSet?.ToUpper() == "GBK")
@@ -697,4 +701,27 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency
@delegate.DynamicInvoke(context);
}
}
+
+ private Task SaveObjectAsync(Stream content, string objectKey)
+ {
+ return ObjectStorage.SaveAsync(new ObjectStorageSaveArgs
+ {
+ ObjectKey = objectKey,
+ Content = content,
+ Overwrite = true
+ });
+ }
+
+ ///
+ /// 序列化为历史字段名 Key / TempUrl,供出参映射读取。
+ ///
+ private static string SerializeObjectStorageResult(ObjectStorageSaveResult result)
+ {
+ return JsonConvert.SerializeObject(new
+ {
+ Key = result.ObjectKey,
+ TempUrl = result.Url,
+ IsSuccess = true
+ });
+ }
}
diff --git a/src/InterfaceForward.Application/InterfaceForward.Application.csproj b/src/InterfaceForward.Application/InterfaceForward.Application.csproj
index bf388ff..54aff0c 100644
--- a/src/InterfaceForward.Application/InterfaceForward.Application.csproj
+++ b/src/InterfaceForward.Application/InterfaceForward.Application.csproj
@@ -1,6 +1,6 @@
-
+
net8.0
@@ -8,20 +8,20 @@
-
-
-
+
+
+
-
+
diff --git a/src/InterfaceForward.Application/InterfaceForwardApplicationModule.cs b/src/InterfaceForward.Application/InterfaceForwardApplicationModule.cs
index a4d3809..64b433b 100644
--- a/src/InterfaceForward.Application/InterfaceForwardApplicationModule.cs
+++ b/src/InterfaceForward.Application/InterfaceForwardApplicationModule.cs
@@ -3,6 +3,7 @@ using Fake.AspNetCore;
using Fake.EventBus.RabbitMQ;
using Fake.Modularity;
using Fake.ObjectMapping.AutoMapper;
+using Fake.ObjectStorage;
using Fake.RabbitMQ;
using FreeRedis;
using InterfaceForward.Application.Contracts;
@@ -21,6 +22,7 @@ namespace InterfaceForward.Application;
[DependsOn(typeof(FakeAspNetCoreModule))]
[DependsOn(typeof(FakeObjectMappingAutoMapperModule))]
[DependsOn(typeof(FakeEventBusRabbitMqModule))]
+[DependsOn(typeof(FakeObjectStorageTencentCosModule))]
[DependsOn(typeof(InterfaceForwardRepositoriesModule))]
[DependsOn(typeof(InterfaceForwardDomainSharedModule))]
public class InterfaceForwardApplicationModule:FakeModule
diff --git a/src/InterfaceForward.Application/OSS/OSSHelper.cs b/src/InterfaceForward.Application/OSS/OSSHelper.cs
deleted file mode 100644
index a762150..0000000
--- a/src/InterfaceForward.Application/OSS/OSSHelper.cs
+++ /dev/null
@@ -1,263 +0,0 @@
-using System.Security.Cryptography;
-using Aliyun.OSS;
-
-namespace InterfaceForward.Application.OSS
-{
- ///
- /// 阿里云OSS帮助类
- ///
- public static class OSSHelper
- {
- #region 初始化
- private static OSSOptions _OSSOptions;
- internal static void Init(OSSOptions OSSOptions)
- {
- _OSSOptions = OSSOptions;
- }
- #endregion
-
- #region 获取client
- private static OssClient GetClient()
- {
- string errorMessage = null;
- if (string.IsNullOrEmpty(_OSSOptions?.Endpoint))
- {
- errorMessage += $"未找到核心配置参数【{nameof(_OSSOptions.Endpoint)}】!";
- }
-
- if (string.IsNullOrEmpty(_OSSOptions?.AccessKeyId))
- {
- errorMessage += $"未找到核心配置参数【{nameof(_OSSOptions.AccessKeyId)}】!";
- }
- if (string.IsNullOrEmpty(_OSSOptions?.AccessKeySecret))
- {
- errorMessage += $"未找到核心配置参数【{nameof(_OSSOptions.AccessKeySecret)}】!";
- }
-
- if (!string.IsNullOrEmpty(errorMessage))
- {
- throw new Exception(errorMessage);
- }
- var client = new OssClient(_OSSOptions.Endpoint, _OSSOptions.AccessKeyId, _OSSOptions.AccessKeySecret);
- return client;
- }
- #endregion
-
- #region 上传
-
- ///
- /// 上传文件
- ///
- /// 文档流
- /// 文件名称
- /// 是否使用自定义ossKey,如果为true,会以fileName为key
- ///
- public static UploadResultDto Upload(Stream data, string fileName = "", bool usCustomKey = false, bool isPublicBucket = false)
- {
- if (data == null || data.Length == 0)
- return new UploadResultDto(false);
-
- var result = Private_Upload(fileName, data, usCustomKey, isPublicBucket);
- return result;
- }
-
- private static UploadResultDto Private_Upload(string fileName, Stream data, bool usCustomKey = false, bool isPublicBucket = false)
- {
- var client = GetClient();
-
- var ossKey = "";
- if (usCustomKey)
- {
- ossKey = fileName;
- }
- else
- {
- var md5 = ComputeDataMd5(data).ToUpper();// 计算文件MD5值
- var ext = Path.GetExtension(fileName);
- ossKey = $"{md5}{ext}";
- }
-
-
- // 文件已经存在直接返回
- var existingObject = client.DoesObjectExist(_OSSOptions.BucketName, ossKey);
- if (existingObject)
- {
- var res = new UploadResultDto(true);
- res.TempUrl = ossKey.GetUrlByKey(isPublicBucket);
- res.Key = ossKey;
- res.FileName = fileName;
-
- return res;
- }
-
- // 上传文件
- bool putResult = false;
-
- using (var pubObj = client.PutObject(_OSSOptions.BucketName, ossKey, data))
- {
- putResult = pubObj.HttpStatusCode == System.Net.HttpStatusCode.OK;
- }
-
-
- if (putResult)//上传成功
- {
- var result = new UploadResultDto(true);
- result.FileName = fileName;
- result.TempUrl = ossKey.GetUrlByKey(isPublicBucket);
- result.Key = ossKey;
- return result;
- }
- else
- {
- // 上传失败
- var result = new UploadResultDto(false);
- result.FileName = fileName;
- return result;
- }
- }
- public static string ComputeDataMd5(Stream data)
- {
- using (var md5 = MD5.Create())
- {
- var hash = BitConverter.ToString(md5.ComputeHash(data)).Replace("-", "").Replace("=", "").ToLower();
- data.Position = 0;
- return hash;
- }
- }
- #endregion
-
- #region 下载
-
- ///
- /// 通过Key获取文件的URL - 同步
- ///
- /// Key
- /// 下载Url
- public static string GetUrlByKey(this string key, bool isPublicBucket = false)
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new Exception($"指定的key不能为空");
- }
-
- try
- {
- var client = GetClient();
- Uri url = client.GeneratePresignedUri(_OSSOptions.BucketName, key);
-
- if (isPublicBucket)
- {
- var urlStr = url?.GetLeftPart(UriPartial.Path);
- return (_OSSOptions.ReturnHttpsUrl && urlStr?.ToString()?.StartsWith("http://") == true) ? "https://" + urlStr.ToString().Substring(7) : urlStr.ToString();
- }
-
-
- return (_OSSOptions.ReturnHttpsUrl && url?.ToString()?.StartsWith("http://") == true) ? "https://" + url.ToString().Substring(7) : url.ToString();
- }
- catch (Exception ex)
- {
- throw new Exception(ex.Message);
- }
- }
-
-
-
- ///
- /// 根据一组key返回url
- ///
- ///
- ///
- ///
- public static Dictionary GetUrlByKeys(params string[] keys)
- {
- var resDic = new Dictionary();
- if (keys == null || keys.Count() <= 0)
- return resDic;
-
-
- var returnHttps = _OSSOptions.ReturnHttpsUrl;
- var client = GetClient();
-
- foreach (var item in keys)
- {
- if (resDic.ContainsKey(item))
- continue;
-
- Uri url = client.GeneratePresignedUri(_OSSOptions.BucketName, item);
- if (url == null)
- continue;
-
- resDic.Add(item, returnHttps ? url.ToString().Replace("http", "https") : url.ToString());
- }
- return resDic;
- }
-
-
- ///
- /// 通过Key获取文件流
- ///
- /// Key
- ///
- public static Stream GetStreamByKey(this string key)
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new Exception($"指定的key不能为空");
- }
-
-
- try
- {
- var client = GetClient();
- var obj = client.GetObject(_OSSOptions.BucketName, key);
- if (obj != null)
- {
- return obj.Content;
- }
- else
- {
- throw new Exception($"文件获取异常");
- }
- }
- catch (Exception ex)
- {
- throw new Exception(ex.Message);
- }
- }
- #endregion
-
- #region 删除
- ///
- /// 删除文件
- ///
- ///
- ///
- public static DeleteObjectsResult DeleteObjectsByKeys(params string[] keys)
- {
- if (keys == null || keys.Length <= 0)
- return default(DeleteObjectsResult);
-
- var client = GetClient();
- var request = new DeleteObjectsRequest(_OSSOptions.BucketName, keys);
-
- return client.DeleteObjects(request);
- }
- #endregion
-
- #region 查看数据信息
- ///
- /// 查看文档信息
- ///
- ///
- ///
- public static ObjectMetadata GetObjectMetadata(string ossKey)
- {
- var client = GetClient().GetObjectMetadata(_OSSOptions.BucketName, ossKey);
- return client;
- }
- #endregion
-
-
-
- }
-}
diff --git a/src/InterfaceForward.Application/OSS/OSSOptions.cs b/src/InterfaceForward.Application/OSS/OSSOptions.cs
deleted file mode 100644
index 647d977..0000000
--- a/src/InterfaceForward.Application/OSS/OSSOptions.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-namespace InterfaceForward.Application.OSS;
-
-public class OSSOptions
-{
-
- ///
- /// 秘钥Id(类似于公钥)
- ///
- public string AccessKeyId { get; set; }
-
- ///
- /// 访问秘钥
- ///
- public string AccessKeySecret { get; set; }
-
- ///
- /// OSS端点
- ///
- public string Endpoint { get; set; }
-
- ///
- /// 存储空间名称
- /// 注意,如果有多个BucketName的话,可以直接使用代码的方式设置这个值
- ///
- public string BucketName { get; set; }
-
- ///
- /// 返回https的url,默认false返回http
- /// 阿里OSS默认支持http,https,不需要额外配置
- ///
- public bool ReturnHttpsUrl { get; set; }
-}
\ No newline at end of file
diff --git a/src/InterfaceForward.Application/OSS/UploadResultDto.cs b/src/InterfaceForward.Application/OSS/UploadResultDto.cs
deleted file mode 100644
index 6873769..0000000
--- a/src/InterfaceForward.Application/OSS/UploadResultDto.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-namespace InterfaceForward.Application.OSS;
-
-public class UploadResultDto
-{
- ///
- /// 标识本次操作是否成功
- ///
- public bool IsSuccess { get; set; }
-
- ///
- /// 临时URL,过期时间15min
- ///
- public string TempUrl { get; set; }
-
- ///
- /// 文件唯一标识Key,通常保存在数据
- /// 后期所有的文件操作都是通过这个Key
- ///
- public string Key { get; set; }
-
- ///
- /// 文件原始名称
- ///
- public string FileName { get; set; }
-
- public UploadResultDto(bool isSuccess)
- {
- IsSuccess = isSuccess;
- }
-}
\ No newline at end of file
diff --git a/src/InterfaceForward.Application/Services/App/AppsService.cs b/src/InterfaceForward.Application/Services/App/AppsService.cs
index 93136ae..383835e 100644
--- a/src/InterfaceForward.Application/Services/App/AppsService.cs
+++ b/src/InterfaceForward.Application/Services/App/AppsService.cs
@@ -1,9 +1,9 @@
using System.ComponentModel.DataAnnotations;
using System.Text;
+using Fake.ObjectStorage;
using Fake.UnitOfWork;
using InterfaceForward.Application.Contracts.Dtos.App;
using InterfaceForward.Application.Helpers;
-using InterfaceForward.Application.OSS;
using InterfaceForward.Repositories;
using InterfaceForward.Repositories.App.Entitys;
using InterfaceForward.Repositories.App.Services;
@@ -24,6 +24,7 @@ public class AppsService : ApplicationService
private readonly IAppRepository _appRepository;
private readonly IAppScopeRepository _appScopeRepository;
private readonly IBasicRepository _interfaceRepository;
+ private readonly IObjectStorage _objectStorage;
///
///
@@ -31,11 +32,13 @@ public class AppsService : ApplicationService
public AppsService(
IAppRepository appRepository
, IAppScopeRepository appScopeRepository
- , IBasicRepository interfaceRepository)
+ , IBasicRepository interfaceRepository
+ , IObjectStorage objectStorage)
{
_appRepository = appRepository;
_appScopeRepository = appScopeRepository;
_interfaceRepository = interfaceRepository;
+ _objectStorage = objectStorage;
}
#region Get
@@ -53,13 +56,26 @@ public class AppsService : ApplicationService
.OrderByDescending(a => a.UpdateTime)
.ToPagedListAsync(input.PageIndex, input.PageSize);
- // var logoKeys = res.Items
- // .Where(x => !x.LogoKey.IsNullOrWhiteSpace())
- // .Select(x => x.LogoKey).ToArray();
- // var urlDic = OSSHelper.GetUrlByKeys(logoKeys);
+ var logoKeys = res.Items
+ .Where(x => !x.LogoKey.IsNullOrWhiteSpace())
+ .Select(x => x.LogoKey)
+ .Distinct()
+ .ToArray();
- // foreach (var url in urlDic)
- // res.Items.First(x => x.LogoKey == url.Key).LogoUrl = url.Value;
+ if (logoKeys.Length > 0)
+ {
+ var urlDic = new Dictionary();
+ foreach (var logoKey in logoKeys)
+ {
+ urlDic[logoKey] = await _objectStorage.GetUrlAsync(logoKey);
+ }
+
+ foreach (var item in res.Items.Where(x => !x.LogoKey.IsNullOrWhiteSpace()))
+ {
+ if (urlDic.TryGetValue(item.LogoKey, out var logoUrl))
+ item.LogoUrl = logoUrl;
+ }
+ }
return res;
}
@@ -135,7 +151,9 @@ public class AppsService : ApplicationService
.SingleAsync(x => x.Id == id);
_ = res ?? throw new BusinessException("在指定的编号下找不到数据");
- // if (res.LogoKey != null) res.LogoUrl = OSSHelper.GetUrlByKey(res.LogoKey);
+ if (!res.LogoKey.IsNullOrWhiteSpace())
+ res.LogoUrl = await _objectStorage.GetUrlAsync(res.LogoKey);
+
res.AppScope = await _appScopeRepository.AsQueryable()
.Where(x => x.AppId == id)
.Select(x => new AppScopeOutput { Id = x.Id.SelectAll() })