Browse Source

Merge pull request #1489 from colinin/aliyun-integration

feat: Optimize the BlobManagement module
pull/1492/head
yx lin 4 months ago
committed by GitHub
parent
commit
b1d91ffdff
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      Directory.Packages.props
  2. 1
      apps/vben5/apps/app-antd/src/store/auth.ts
  3. 5
      apps/vben5/packages/@abp/blob-management/package.json
  4. 26
      apps/vben5/packages/@abp/blob-management/src/api/useBlobsApi.ts
  5. 4
      apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal.vue
  6. 106
      apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal1.vue
  7. 9
      apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFileTable.vue
  8. 2
      apps/vben5/pnpm-workspace.yaml
  9. 2
      aspnet-core/LINGYUN.MicroService.SingleProject.slnx
  10. 2
      aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN.Abp.Aliyun.csproj
  11. 34
      aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AcsClientFactory.cs
  12. 11
      aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunBasicSessionCredentialsCacheItem.cs
  13. 97
      aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs
  14. 14
      aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/IAcsClientFactory.cs
  15. 2
      aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs
  16. 10
      aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs
  17. 4
      aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfigurationNames.cs
  18. 2
      aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentCloudBlobProvider.cs
  19. 5
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj
  20. 5
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs
  21. 111
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs
  22. 29
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs
  23. 12
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs
  24. 6
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/IOssClientFactory.cs
  25. 86
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs
  26. 18
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/Microsoft/Extensions/DependencyInjection/AliyunHttpClientFactoryServiceCollectionExtensions.cs
  27. 2
      aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN.Abp.Sms.Aliyun.csproj
  28. 119
      aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs
  29. 41
      aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DypnsClientFactory.cs
  30. 38
      aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DysmsClientFactory.cs
  31. 13
      aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDypnsClientFactory.cs
  32. 13
      aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDysmsClientFactory.cs
  33. 175
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs
  34. 12
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByIdInput.cs
  35. 5
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByNameInput.cs
  36. 10
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs
  37. 4
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPrivateBlobAppService.cs
  38. 4
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPublicBlobAppService.cs
  39. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Integration/IBlobIntegrationService.cs
  40. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionDefinitionProvider.cs
  41. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionNames.cs
  42. 41
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs
  43. 48
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs
  44. 26
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs
  45. 23
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs
  46. 14
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/PublicBlobAppService.cs
  47. 15
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/AbpBlobManagementContentTypeResolveOptions.cs
  48. 33
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobContentTypeResolveContext.cs
  49. 8
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobDownloadEto.cs
  50. 7
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureDefinitionProvider.cs
  51. 4
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureNames.cs
  52. 10
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolveContributor.cs
  53. 5
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/en.json
  54. 5
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/zh-Hans.json
  55. 11
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingDefinitionProvider.cs
  56. 7
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingNames.cs
  57. 42
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/Blob.cs
  58. 71
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobContentTypeResolver.cs
  59. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDataSeeder.cs
  60. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadIncreaser.cs
  61. 19
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs
  62. 108
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs
  63. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobTempCleanupService.cs
  64. 1
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs
  65. 3
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepositoryExtenssions.cs
  66. 9
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolver.cs
  67. 12
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs
  68. 3
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs
  69. 14
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs
  70. 14
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/AbpBlobManagementHttpApiModule.cs
  71. 43
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs
  72. 24
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/FileExtensionBlobContentTypeResolveContributor.cs
  73. 4
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationController.cs
  74. 12
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PrivateBlobController.cs
  75. 12
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PublicBlobController.cs
  76. 3
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xml
  77. 30
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xsd
  78. 25
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN.Abp.BlobManagement.MimeCheck.csproj
  79. 15
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/AbpBlobManagementMimeCheckModule.cs
  80. 33
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/MimeCheckBlobContentTypeResolveContributor.cs
  81. 67
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs
  82. 5
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.SettingManagement/LINGYUN/Abp/BlobManagement/SettingManagement/BlobManagementSettingAppService.cs
  83. 59
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs
  84. 2
      aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj
  85. 3
      aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs

5
Directory.Packages.props

@ -333,6 +333,10 @@
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
<PackageVersion Include="aliyun-net-sdk-core" Version="1.6.2" />
<PackageVersion Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageVersion Include="AlibabaCloud.SDK.Dypnsapi20170525" Version="2.0.0" />
<PackageVersion Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="4.3.1" />
<PackageVersion Include="AlibabaCloud.SDK.Sts20150401" Version="1.2.0" />
<PackageVersion Include="AlibabaCloud.OSS.V2" Version="0.1.2" />
<PackageVersion Include="AgileConfig.Client" Version="1.8.0" />
<PackageVersion Include="CommonMark.NET" Version="0.15.1" />
<PackageVersion Include="DocumentFormat.OpenXml" Version="3.5.1" />
@ -350,6 +354,7 @@
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.2" />
<PackageVersion Include="Magicodes.IE.Excel" Version="2.8.2" />
<PackageVersion Include="Markdig" Version="0.44.0" />
<PackageVersion Include="MimeCheck" Version="1.0.0" />
<PackageVersion Include="MiniExcel" Version="1.43.0" />
<PackageVersion Include="MiniWord" Version="0.9.2" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />

1
apps/vben5/apps/app-antd/src/store/auth.ts

@ -180,6 +180,7 @@ export const useAuthStore = defineStore('auth', () => {
userId: userInfoRes.sub ?? abpConfig.currentUser.id,
username: userInfoRes.uniqueName ?? abpConfig.currentUser.userName,
realName:
userInfoRes.given_name ??
userInfoRes.name ??
abpConfig.currentUser.name ??
abpConfig.currentUser.userName,

5
apps/vben5/packages/@abp/blob-management/package.json

@ -37,13 +37,10 @@
"@vben/locales": "workspace:*",
"@vben/stores": "workspace:*",
"@vben/utils": "workspace:*",
"@vue-office/docx": "catalog:",
"@vue-office/excel": "catalog:",
"@vue-office/pdf": "catalog:",
"@vue-office/pptx": "catalog:",
"ant-design-vue": "catalog:",
"vue": "catalog:",
"vue-demi": "catalog:",
"vue-files-preview": "catalog:",
"vue-simple-uploader": "catalog:",
"vxe-table": "catalog:"
}

26
apps/vben5/packages/@abp/blob-management/src/api/useBlobsApi.ts

@ -32,8 +32,26 @@ export function useBlobsApi() {
});
}
function getContentApi(id: string): Promise<Blob> {
return request<Blob>(`/api/blob-management/blobs/uid/${id}/content`, {
function generateDownloadUrlApi(id: string): Promise<string> {
return request<string>(
`/api/blob-management/blobs/generate/download-url/${id}`,
{
method: 'GET',
},
);
}
function generatePreviewUrlApi(id: string): Promise<string> {
return request<string>(
`/api/blob-management/blobs/generate/preview-url/${id}`,
{
method: 'GET',
},
);
}
function downloadApi(id: string): Promise<Blob> {
return request<Blob>(`/api/blob-management/blobs/download/${id}`, {
method: 'GET',
responseType: 'blob',
});
@ -70,8 +88,10 @@ export function useBlobsApi() {
cancel,
createFolderApi,
deleteApi,
downloadApi,
getApi,
getContentApi,
generateDownloadUrlApi,
generatePreviewUrlApi,
getFolderPagedListApi,
getFilePagedListApi,
getPagedListApi,

4
apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal.vue

@ -64,7 +64,7 @@ const blobType = ref<FileType>();
const errorMessage = ref<string>('');
const excelComponentKey = ref(0); // 只对 Excel 组件使用 key
const { getContentApi } = useBlobsApi();
const { downloadApi } = useBlobsApi();
// 文件类型配置
const allowFiles: Record<FileType, string[]> = {
@ -175,7 +175,7 @@ async function onInit() {
}
// 获取文件内容
const content = await getContentApi(blob.value.id);
const content = await downloadApi(blob.value.id);
// 检查文件大小
if (!isFileSizeValid(content)) {

106
apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal1.vue

@ -0,0 +1,106 @@
<script setup lang="ts">
import type { BlobDto } from '../../types/blobs';
import { defineAsyncComponent, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { downloadFileFromUrl } from '@vben/utils';
import { Button, Spin } from 'ant-design-vue';
import { useBlobsApi } from '../../api/useBlobsApi';
const VueFilesPreview = defineAsyncComponent({
loader: async () => {
const res = await import('vue-files-preview');
import('vue-files-preview/lib/style.css');
return res.VueFilesPreview;
},
loadingComponent: Spin,
delay: 200,
});
const fileName = ref<string>();
const previewUrl = ref<string>();
const errorMsg = ref<string>();
const { generatePreviewUrlApi } = useBlobsApi();
const [Modal, modalApi] = useVbenModal({
showCancelButton: false,
showConfirmButton: false,
fullscreen: true,
fullscreenButton: false,
onOpenChange: async (isOpen) => {
isOpen && onInit();
},
onClosed() {
previewUrl.value = undefined;
fileName.value = undefined;
errorMsg.value = undefined;
},
});
async function onInit() {
try {
modalApi.lock();
const blob = modalApi.getData<BlobDto>();
fileName.value = blob.name;
previewUrl.value = await generatePreviewUrlApi(blob.id);
} finally {
modalApi.unlock();
}
}
async function handleDownload() {
downloadFileFromUrl({
fileName: fileName.value!,
source: previewUrl.value!,
});
}
function onError(error: Error) {
errorMsg.value = error.message;
}
</script>
<template>
<Modal :title="fileName">
<div class="preview-container">
<VueFilesPreview
v-if="previewUrl"
:url="previewUrl"
:name="fileName"
@error="onError"
/>
<!-- 下载按钮 -->
<div v-if="errorMsg && previewUrl" class="download-button">
<Button type="primary" @click="handleDownload">
{{ $t('BlobManagement.Blobs:Download') }}
</Button>
</div>
</div>
</Modal>
</template>
<style scoped lang="scss">
.preview-container {
position: relative;
height: 100%;
.error-message {
padding: 40px 20px;
text-align: center;
}
.download-button {
position: sticky;
right: 20px;
bottom: 20px;
z-index: 10;
float: right;
margin-top: 20px;
}
}
</style>

9
apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFileTable.vue

@ -35,7 +35,7 @@ const kbUnit = 1 * 1024;
const mbUnit = kbUnit * 1024;
const gbUnit = mbUnit * 1024;
const { deleteApi, getPagedListApi } = useBlobsApi();
const { deleteApi, getPagedListApi, generateDownloadUrlApi } = useBlobsApi();
const selectedKeys = ref<string[]>([]);
@ -169,7 +169,7 @@ const [BlobFileUploadModal, uploadModalApi] = useVbenModal({
});
const [BlobFilePreviewModal, previewModalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./BlobFilePreviewModal.vue'),
() => import('./BlobFilePreviewModal1.vue'),
),
});
@ -191,10 +191,11 @@ function onDownload(row: BlobDto) {
centered: true,
content: $t('BlobManagement.BlobWellDownloadMessage', [row.name]),
onCancel: () => {},
onOk: () => {
onOk: async () => {
const downloadUrl = await generateDownloadUrlApi(row.id);
downloadFileFromUrl({
fileName: row.name,
source: `/api/blob-management/blobs/uid/${row.id}/content`,
source: downloadUrl,
});
},
title: $t('AbpUi.AreYouSure'),

2
apps/vben5/pnpm-workspace.yaml

@ -83,6 +83,7 @@ catalog:
'@vite-pwa/vitepress': ^1.1.0
'@vitejs/plugin-vue': ^6.0.4
'@vitejs/plugin-vue-jsx': ^5.1.4
'@vue-files-preview': ^0.0.0
'@vue-office/docx': ^1.6.3
'@vue-office/excel': ^1.7.14
'@vue-office/pdf': ^2.0.10
@ -235,6 +236,7 @@ catalog:
vue: ^3.5.27
vue-demi: ^0.14.6
vue-eslint-parser: ^10.2.0
vue-files-preview: ^1.0.43
vue-i18n: ^11.2.8
vue-json-viewer: ^3.0.4
vue-router: ^5.0.3

2
aspnet-core/LINGYUN.MicroService.SingleProject.slnx

@ -11,6 +11,7 @@
<Folder Name="/framework/alibaba-cloud/">
<Project Path="framework/cloud-aliyun/LINGYUN.Abp.Aliyun.SettingManagement/LINGYUN.Abp.Aliyun.SettingManagement.csproj" />
<Project Path="framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN.Abp.Aliyun.csproj" />
<Project Path="framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj" />
<Project Path="framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN.Abp.Sms.Aliyun.csproj" />
</Folder>
<Folder Name="/framework/authentication/">
@ -211,6 +212,7 @@
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN.Abp.BlobManagement.FileSystem.csproj" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi.Client/LINGYUN.Abp.BlobManagement.HttpApi.Client.csproj" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN.Abp.BlobManagement.HttpApi.csproj" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN.Abp.BlobManagement.MimeCheck.csproj" Id="bb981955-78c8-45b7-a46a-cc9eefa2166a" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN.Abp.BlobManagement.Minio.csproj" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.SettingManagement/LINGYUN.Abp.BlobManagement.SettingManagement.csproj" Id="649cb4c3-e7c9-4736-b254-0899bb26bfcb" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN.Abp.BlobManagement.Tencent.csproj" />

2
aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN.Abp.Aliyun.csproj

@ -25,7 +25,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="aliyun-net-sdk-core" />
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" />
<PackageReference Include="Volo.Abp.Caching" />
<PackageReference Include="Volo.Abp.Features" />
<PackageReference Include="Volo.Abp.Localization" />

34
aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AcsClientFactory.cs

@ -1,34 +0,0 @@
using Aliyun.Acs.Core;
using Aliyun.Acs.Core.Auth;
using Aliyun.Acs.Core.Profile;
using LINGYUN.Abp.Aliyun.Features;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Aliyun;
[RequiresFeature(AliyunFeatureNames.Enable)]
public class AcsClientFactory : AliyunClientFactory<IAcsClient>, IAcsClientFactory, ITransientDependency
{
public AcsClientFactory(
ISettingProvider settingProvider,
IDistributedCache<AliyunBasicSessionCredentialsCacheItem> cache)
: base(settingProvider, cache)
{
}
protected override IAcsClient GetClient(string regionId, string accessKeyId, string accessKeySecret)
{
return new DefaultAcsClient(
DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret));
}
protected override IAcsClient GetSecurityTokenClient(string regionId, string accessKeyId, string accessKeySecret, string securityToken)
{
var profile = DefaultProfile.GetProfile(regionId);
var credentials = new BasicSessionCredentials(accessKeyId, accessKeySecret, securityToken);
return new DefaultAcsClient(profile, credentials);
}
}

11
aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunBasicSessionCredentialsCacheItem.cs

@ -5,26 +5,21 @@ namespace LINGYUN.Abp.Aliyun;
[Serializable]
public class AliyunBasicSessionCredentialsCacheItem
{
private readonly static string _cacheKey;
public static string CacheKey => _cacheKey;
public string AccessKeyId { get; set; }
public string AccessKeySecret { get; set; }
public string SecurityToken { get; set; }
static AliyunBasicSessionCredentialsCacheItem()
{
_cacheKey = Guid.NewGuid().ToString("N");
}
public DateTime? Expiration { get; set; }
public AliyunBasicSessionCredentialsCacheItem()
{
}
public AliyunBasicSessionCredentialsCacheItem(string accessKeyId, string accessKeySecret, string securityToken)
public AliyunBasicSessionCredentialsCacheItem(string accessKeyId, string accessKeySecret, string securityToken, DateTime? expiration = null)
{
AccessKeyId = accessKeyId;
AccessKeySecret = accessKeySecret;
SecurityToken = securityToken;
Expiration = expiration;
}
}

97
aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs

@ -1,14 +1,12 @@
using Aliyun.Acs.Core;
using Aliyun.Acs.Core.Auth.Sts;
using Aliyun.Acs.Core.Http;
using Aliyun.Acs.Core.Profile;
using LINGYUN.Abp.Aliyun.Settings;
using LINGYUN.Abp.Aliyun.Settings;
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.Settings;
using OpenApiConfig = AlibabaCloud.OpenApiClient.Models.Config;
using StsClient = AlibabaCloud.SDK.Sts20150401.Client;
namespace LINGYUN.Abp.Aliyun;
@ -42,19 +40,33 @@ public abstract class AliyunClientFactory<TClient>
{
var cacheItem = await GetCacheItemAsync(accessKey, accessKeySecret, regionId);
return GetSecurityTokenClient(regionId, cacheItem.AccessKeyId, cacheItem.AccessKeySecret, cacheItem.SecurityToken);
return GetSecurityTokenClient(
regionId,
cacheItem.AccessKeyId,
cacheItem.AccessKeySecret,
cacheItem.SecurityToken,
cacheItem.Expiration);
}
return GetClient(regionId, accessKey, accessKeySecret);
}
protected abstract TClient GetClient(string regionId, string accessKeyId, string accessKeySecret);
protected abstract TClient GetClient(
string regionId,
string accessKeyId,
string accessKeySecret);
protected abstract TClient GetSecurityTokenClient(string regionId, string accessKeyId, string accessKeySecret, string securityToken);
protected abstract TClient GetSecurityTokenClient(
string regionId,
string accessKeyId,
string accessKeySecret,
string securityToken,
DateTime? expiration = null);
protected async virtual Task<AliyunBasicSessionCredentialsCacheItem> GetCacheItemAsync(string accessKeyId, string accessKeySecret, string regionId)
{
var cacheItem = await Cache.GetAsync(AliyunBasicSessionCredentialsCacheItem.CacheKey);
var cacheKey = $"{accessKeyId}:{accessKeySecret}".ToMd5();
var cacheItem = await Cache.GetAsync(cacheKey);
if (cacheItem == null)
{
var roleArn = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Authorization.RamRoleArn);
@ -64,30 +76,41 @@ public abstract class AliyunClientFactory<TClient>
var policy = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Authorization.Policy);
var durationSeconds = await SettingProvider.GetAsync(AliyunSettingNames.Authorization.DurationSeconds, 3000);
var profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret);
var request = new AssumeRoleRequest
{
AcceptFormat = FormatType.JSON,
RoleArn = roleArn,
RoleSessionName = roleSession,
DurationSeconds = durationSeconds,
Policy = policy.IsNullOrWhiteSpace() ? null : policy
};
var stsClient = new StsClient(
new OpenApiConfig
{
RegionId = regionId,
AccessKeyId = accessKeyId,
AccessKeySecret = accessKeySecret,
});
var client = new DefaultAcsClient(profile);
var response = client.GetAcsResponse(request);
var assumeRoleResponse = await stsClient.AssumeRoleAsync(
new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest
{
RoleArn = roleArn,
RoleSessionName = roleSession,
DurationSeconds = durationSeconds,
Policy = policy.IsNullOrWhiteSpace() ? null : policy,
});
cacheItem = new AliyunBasicSessionCredentialsCacheItem(
response.Credentials.AccessKeyId,
response.Credentials.AccessKeySecret,
response.Credentials.SecurityToken);
assumeRoleResponse.Body.Credentials.AccessKeyId,
assumeRoleResponse.Body.Credentials.AccessKeySecret,
assumeRoleResponse.Body.Credentials.SecurityToken);
var expirationTimeSpan = TimeSpan.FromSeconds(durationSeconds - 10);
if (DateTime.TryParse(assumeRoleResponse.Body.Credentials.Expiration, out var expiration))
{
cacheItem.Expiration = expiration;
expirationTimeSpan = new TimeSpan(expiration.AddSeconds(-10).Ticks);
}
await Cache.SetAsync(
AliyunBasicSessionCredentialsCacheItem.CacheKey,
cacheKey,
cacheItem,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(durationSeconds - 10)
AbsoluteExpirationRelativeToNow = expirationTimeSpan,
});
}
@ -122,13 +145,29 @@ public abstract class AliyunClientFactory<TClient, TConfiguration> : AliyunClien
{
var cacheItem = await GetCacheItemAsync(accessKey, accessKeySecret, regionId);
return GetSecurityTokenClient(configuration, regionId, cacheItem.AccessKeyId, cacheItem.AccessKeySecret, cacheItem.SecurityToken);
return GetSecurityTokenClient(
configuration,
regionId,
cacheItem.AccessKeyId,
cacheItem.AccessKeySecret,
cacheItem.SecurityToken,
cacheItem.Expiration);
}
return GetClient(configuration, regionId, accessKey, accessKeySecret);
}
protected abstract TClient GetClient(TConfiguration configuration, string regionId, string accessKeyId, string accessKeySecret);
protected abstract TClient GetSecurityTokenClient(TConfiguration configuration, string regionId, string accessKeyId, string accessKeySecret, string securityToken);
protected abstract TClient GetClient(
TConfiguration configuration,
string regionId,
string accessKeyId,
string accessKeySecret);
protected abstract TClient GetSecurityTokenClient(
TConfiguration configuration,
string regionId,
string accessKeyId,
string accessKeySecret,
string securityToken,
DateTime? expiration = null);
}

14
aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/IAcsClientFactory.cs

@ -1,14 +0,0 @@
using Aliyun.Acs.Core;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Aliyun;
public interface IAcsClientFactory
{
/// <summary>
/// 构造一个通用的Acs客户端调用
/// 通过CommonRequest调用可以不需要集成其他SDK包
/// </summary>
/// <returns></returns>
Task<IAcsClient> CreateAsync();
}

2
aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs

@ -79,7 +79,7 @@ public class AliyunSettingProvider : SettingDefinitionProvider
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
AliyunSettingNames.Authorization.RegionId,
defaultValue: "oss-cn-hangzhou",
defaultValue: "cn-hangzhou",
displayName: L("DisplayName:RegionId"),
description: L("Description:RegionId"),
isVisibleToClients: false

10
aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs

@ -53,6 +53,16 @@ public class TencentBlobProviderConfiguration
}
}
/// <summary>
/// Default value: 600.
/// </summary>
public int PresignedGetExpirySeconds {
get => _containerConfiguration.GetConfigurationOrDefault(TencentBlobProviderConfigurationNames.PresignedGetExpirySeconds, _defaultExpirySeconds);
set => _containerConfiguration.SetConfiguration(TencentBlobProviderConfigurationNames.PresignedGetExpirySeconds, value);
}
private int _defaultExpirySeconds = 600;
private readonly BlobContainerConfiguration _containerConfiguration;
public TencentBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)

4
aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfigurationNames.cs

@ -22,4 +22,8 @@ public static class TencentBlobProviderConfigurationNames
/// 创建命名空间时防盗链列表
/// </summary>
public const string CreateBucketReferer = "Tencent:OSS:CreateBucketReferer";
/// <summary>
/// 生成预签名Uri的过期时间(s)
/// </summary>
public const string PresignedGetExpirySeconds = "Tencent:OSS:PresignedGetExpirySeconds";
}

2
aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentCloudBlobProvider.cs

@ -78,7 +78,7 @@ public class TencentCloudBlobProvider : BlobProviderBase, ITransientDependency
key = blobName, //对象键
httpMethod = "GET", //HTTP 请求方法
isHttps = true, //生成 HTTPS 请求 URL
signDurationSecond = 600, //请求签名时间为600s
signDurationSecond = configuration.PresignedGetExpirySeconds, //请求签名时间为600s
headers = null, //签名中需要校验的 header
queryParameters = null //签名中需要校验的 URL 中请求参数
};

5
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj

@ -4,7 +4,7 @@
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0;net10.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<AssemblyName>LINGYUN.Abp.BlobStoring.Aliyun</AssemblyName>
<PackageId>LINGYUN.Abp.BlobStoring.Aliyun</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
@ -15,7 +15,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aliyun.OSS.SDK.NetCore" />
<PackageReference Include="AlibabaCloud.OSS.V2" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Volo.Abp.BlobStoring" />
</ItemGroup>

5
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.Aliyun;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BlobStoring;
using Volo.Abp.Modularity;
@ -9,6 +10,10 @@ namespace LINGYUN.Abp.BlobStoring.Aliyun;
typeof(AbpAliyunModule))]
public class AbpBlobStoringAliyunModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAliyunHttpClient();
}
// 需要时引用配置
//public override void ConfigureServices(ServiceConfigurationContext context)
//{

111
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs

@ -1,26 +1,35 @@
using Aliyun.OSS;
using AlibabaCloud.OSS.V2;
using AlibabaCloud.OSS.V2.Models;
using LINGYUN.Abp.Aliyun.Features;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Volo.Abp.BlobStoring;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.BlobStoring.Aliyun;
[RequiresFeature(AliyunFeatureNames.BlobStoring.Enable)]
public class AliyunBlobProvider : BlobProviderBase, ITransientDependency
{
protected IClock Clock { get; }
protected IOssClientFactory OssClientFactory { get; }
protected IHttpClientFactory HttpClientFactory { get; }
protected IAliyunBlobNameCalculator AliyunBlobNameCalculator { get; }
public AliyunBlobProvider(
IClock clock,
IOssClientFactory ossClientFactory,
IHttpClientFactory httpClientFactory,
IAliyunBlobNameCalculator aliyunBlobNameCalculator)
{
Clock = clock;
OssClientFactory = ossClientFactory;
HttpClientFactory = httpClientFactory;
AliyunBlobNameCalculator = aliyunBlobNameCalculator;
}
@ -31,7 +40,14 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency
if (await BlobExistsAsync(ossClient, args, blobName))
{
return ossClient.DeleteObject(GetBucketName(args), blobName).DeleteMarker;
var deleteObjectRes = await ossClient.DeleteObjectAsync(
new DeleteObjectRequest
{
Bucket = GetBucketName(args),
Key = blobName,
},
cancellationToken: args.CancellationToken);
return deleteObjectRes.DeleteMarker == true;
}
return false;
@ -55,8 +71,27 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency
return null;
}
var ossObject = ossClient.GetObject(GetBucketName(args), blobName);
return ossObject.Content;
var result = await ossClient.GetObjectAsync(
new GetObjectRequest
{
Bucket = GetBucketName(args),
Key = blobName,
});
return result.Body;
// TODO: 阿里云sdk预签名不可用[2026/05/23]
//var configuration = args.Configuration.GetAliyunConfiguration();
//var presignResult = ossClient.Presign(
// new GetObjectRequest
// {
// Bucket = GetBucketName(args),
// Key = blobName,
// },
// Clock.Now.AddSeconds(configuration.PresignedGetExpirySeconds));
//var httpClient = HttpClientFactory.CreateAliyunHttpClient();
//return await httpClient.GetStreamAsync(presignResult.Url, args.CancellationToken);
}
public override async Task SaveAsync(BlobProviderSaveArgs args)
@ -68,7 +103,7 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency
// 先检查Bucket
if (configuration.CreateBucketIfNotExists)
{
await CreateBucketIfNotExists(ossClient, args, configuration.CreateBucketReferer);
await CreateBucketIfNotExists(ossClient, args, configuration.CreateBucketAcl);
}
var bucketName = GetBucketName(args);
@ -84,60 +119,70 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency
else
{
// 删除原文件
ossClient.DeleteObject(bucketName, blobName);
await ossClient.DeleteObjectAsync(
new DeleteObjectRequest
{
Bucket = bucketName,
Key = blobName,
},
cancellationToken: args.CancellationToken);
}
}
// 保存
ossClient.PutObject(bucketName, blobName, args.BlobStream);
await ossClient.PutObjectAsync(
new PutObjectRequest
{
Bucket = bucketName,
Key = blobName,
Body = args.BlobStream,
},
cancellationToken: args.CancellationToken);
}
protected async virtual Task<IOss> GetOssClientAsync(BlobProviderArgs args)
protected async virtual Task<Client> GetOssClientAsync(BlobProviderArgs args)
{
var ossClient = await OssClientFactory.CreateAsync();
return ossClient;
return await OssClientFactory.CreateAsync();
}
protected async virtual Task CreateBucketIfNotExists(IOss ossClient, BlobProviderArgs args, IList<string> refererList = null)
protected async virtual Task CreateBucketIfNotExists(Client ossClient, BlobProviderArgs args, BucketAclType? bucketAcl = null)
{
if (! await BucketExistsAsync(ossClient, args))
if (!await BucketExistsAsync(ossClient, args))
{
var bucketName = GetBucketName(args);
var request = new CreateBucketRequest(bucketName)
{
//设置存储空间访问权限ACL。
ACL = CannedAccessControlList.PublicReadWrite,
//设置数据容灾类型。
DataRedundancyType = DataRedundancyType.ZRS
};
ossClient.CreateBucket(request);
await ossClient.PutBucketAsync(
new PutBucketRequest
{
Bucket = bucketName,
},
cancellationToken: args.CancellationToken);
if (refererList != null && refererList.Count > 0)
if (bucketAcl.HasValue)
{
var srq = new SetBucketRefererRequest(bucketName, refererList);
ossClient.SetBucketReferer(srq);
await ossClient.PutBucketAclAsync(
new PutBucketAclRequest
{
Bucket = bucketName,
Acl = bucketAcl.Value.GetString(),
},
cancellationToken: args.CancellationToken);
}
}
}
private async Task<bool> BlobExistsAsync(IOss ossClient, BlobProviderArgs args, string blobName)
private async Task<bool> BlobExistsAsync(Client ossClient, BlobProviderArgs args, string blobName)
{
var bucketExists = await BucketExistsAsync(ossClient, args);
if (bucketExists)
{
var objectExists = ossClient.DoesObjectExist(GetBucketName(args), blobName);
return objectExists;
return await ossClient.IsObjectExistAsync(GetBucketName(args), blobName, cancellationToken: args.CancellationToken);
}
return false;
}
private Task<bool> BucketExistsAsync(IOss ossClient, BlobProviderArgs args)
private async Task<bool> BucketExistsAsync(Client ossClient, BlobProviderArgs args)
{
var bucketExists = ossClient.DoesBucketExist(GetBucketName(args));
return Task.FromResult(bucketExists);
return await ossClient.IsBucketExistAsync(GetBucketName(args), args.CancellationToken);
}
private static string GetBucketName(BlobProviderArgs args)

29
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using AlibabaCloud.OSS.V2.Models;
using System.Collections.Generic;
using Volo.Abp.BlobStoring;
namespace LINGYUN.Abp.BlobStoring.Aliyun;
@ -14,6 +15,13 @@ public class AliyunBlobProviderConfiguration
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.BucketName, value);
}
/// <summary>
/// 跳过服务器证书验证
/// </summary>
public bool InsecureSkipVerify {
get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.InsecureSkipVerify, false);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.InsecureSkipVerify, value);
}
/// <summary>
/// 命名空间不存在是否创建
/// </summary>
public bool CreateBucketIfNotExists
@ -22,11 +30,18 @@ public class AliyunBlobProviderConfiguration
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists, value);
}
/// <summary>
/// 创建命名空间时的Acl
/// </summary>
public BucketAclType? CreateBucketAcl {
get => _containerConfiguration.GetConfigurationOrDefault<BucketAclType?>(AliyunBlobProviderConfigurationNames.CreateBucketAcl);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateBucketAcl, value);
}
/// <summary>
/// 创建命名空间时防盗链列表
/// </summary>
public List<string> CreateBucketReferer
{
get => _containerConfiguration.GetConfiguration<List<string>>(AliyunBlobProviderConfigurationNames.CreateBucketReferer);
get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.CreateBucketReferer, new List<string>());
set
{
if (value == null)
@ -40,6 +55,16 @@ public class AliyunBlobProviderConfiguration
}
}
/// <summary>
/// Default value: 7 * 24 * 3600.
/// </summary>
public int PresignedGetExpirySeconds {
get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.PresignedGetExpirySeconds, _defaultExpirySeconds);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.PresignedGetExpirySeconds, value);
}
private int _defaultExpirySeconds = 7 * 24 * 3600;
private readonly BlobContainerConfiguration _containerConfiguration;
public AliyunBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)

12
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs

@ -11,6 +11,10 @@ public static class AliyunBlobProviderConfigurationNames
/// </summary>
public const string BucketName = "Aliyun:OSS:BucketName";
/// <summary>
/// 跳过服务器证书验证
/// </summary>
public const string InsecureSkipVerify = "Aliyun:OSS:InsecureSkipVerify";
/// <summary>
/// 命名空间不存在是否创建
/// </summary>
public const string CreateBucketIfNotExists = "Aliyun:OSS:CreateBucketIfNotExists";
@ -18,4 +22,12 @@ public static class AliyunBlobProviderConfigurationNames
/// 创建命名空间时防盗链列表
/// </summary>
public const string CreateBucketReferer = "Aliyun:OSS:CreateBucketReferer";
/// <summary>
/// 创建命名空间时的Acl
/// </summary>
public const string CreateBucketAcl = "Aliyun:OSS:CreateBucketAcl";
/// <summary>
/// 生成预签名Uri的过期时间(s)
/// </summary>
public const string PresignedGetExpirySeconds = "Aliyun:OSS:PresignedGetExpirySeconds";
}

6
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/IOssClientFactory.cs

@ -1,4 +1,4 @@
using Aliyun.OSS;
using AlibabaCloud.OSS.V2;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BlobStoring.Aliyun;
@ -9,11 +9,11 @@ public interface IOssClientFactory
/// 构建Oss客户端
/// </summary>
/// <returns></returns>
Task<IOss> CreateAsync();
Task<Client> CreateAsync();
/// <summary>
/// 构建Oss客户端
/// </summary>
/// <param name="configuration"></param>
/// <returns></returns>
Task<IOss> CreateAsync(AliyunBlobProviderConfiguration configuration);
Task<Client> CreateAsync(AliyunBlobProviderConfiguration configuration);
}

86
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs

@ -1,12 +1,14 @@
using Aliyun.OSS;
using AlibabaCloud.OSS.V2;
using AlibabaCloud.OSS.V2.Credentials;
using LINGYUN.Abp.Aliyun;
using System;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.BlobStoring.Aliyun;
public class OssClientFactory : AliyunClientFactory<IOss, AliyunBlobProviderConfiguration>, IOssClientFactory, ITransientDependency
public class OssClientFactory : AliyunClientFactory<Client, AliyunBlobProviderConfiguration>, IOssClientFactory, ITransientDependency
{
public OssClientFactory(
ISettingProvider settingProvider,
@ -22,12 +24,22 @@ public class OssClientFactory : AliyunClientFactory<IOss, AliyunBlobProviderConf
/// <param name="accessKeyId"></param>
/// <param name="accessKeySecret"></param>
/// <returns></returns>
protected override IOss GetClient(string regionId, string accessKeyId, string accessKeySecret)
protected override Client GetClient(string regionId, string accessKeyId, string accessKeySecret)
{
return new OssClient(
regionId,
accessKeyId,
accessKeySecret);
return new Client(
new Configuration
{
Region = regionId,
CredentialsProvider = new CredentialsProviderFunc(() =>
{
return new Credentials(accessKeyId, accessKeySecret);
}),
});
//return new OssClient(
// regionId,
// accessKeyId,
// accessKeySecret);
}
/// <summary>
@ -38,12 +50,18 @@ public class OssClientFactory : AliyunClientFactory<IOss, AliyunBlobProviderConf
/// <param name="accessKeyId"></param>
/// <param name="accessKeySecret"></param>
/// <returns></returns>
protected override IOss GetClient(AliyunBlobProviderConfiguration configuration, string regionId, string accessKeyId, string accessKeySecret)
protected override Client GetClient(AliyunBlobProviderConfiguration configuration, string regionId, string accessKeyId, string accessKeySecret)
{
return new OssClient(
regionId,
accessKeyId,
accessKeySecret);
return new Client(
new Configuration
{
Region = regionId,
CredentialsProvider = new CredentialsProviderFunc(() =>
{
return new Credentials(accessKeyId, accessKeySecret);
}),
InsecureSkipVerify = configuration.InsecureSkipVerify,
});
}
/// <summary>
@ -54,13 +72,22 @@ public class OssClientFactory : AliyunClientFactory<IOss, AliyunBlobProviderConf
/// <param name="accessKeySecret"></param>
/// <param name="securityToken"></param>
/// <returns></returns>
protected override IOss GetSecurityTokenClient(string regionId, string accessKeyId, string accessKeySecret, string securityToken)
protected override Client GetSecurityTokenClient(
string regionId,
string accessKeyId,
string accessKeySecret,
string securityToken,
DateTime? expiration = null)
{
return new OssClient(
regionId,
accessKeyId,
accessKeySecret,
securityToken);
return new Client(
new Configuration
{
Region = regionId,
CredentialsProvider = new CredentialsProviderFunc(() =>
{
return new Credentials(accessKeyId, accessKeySecret, securityToken, expiration);
}),
});
}
/// <summary>
/// 通过用户安全令牌构建Oss客户端
@ -71,12 +98,23 @@ public class OssClientFactory : AliyunClientFactory<IOss, AliyunBlobProviderConf
/// <param name="accessKeySecret"></param>
/// <param name="securityToken"></param>
/// <returns></returns>
protected override IOss GetSecurityTokenClient(AliyunBlobProviderConfiguration configuration, string regionId, string accessKeyId, string accessKeySecret, string securityToken)
protected override Client GetSecurityTokenClient(
AliyunBlobProviderConfiguration configuration,
string regionId,
string accessKeyId,
string accessKeySecret,
string securityToken,
DateTime? expiration = null)
{
return new OssClient(
regionId,
accessKeyId,
accessKeySecret,
securityToken);
return new Client(
new Configuration
{
Region = regionId,
CredentialsProvider = new CredentialsProviderFunc(() =>
{
return new Credentials(accessKeyId, accessKeySecret, securityToken, expiration);
}),
InsecureSkipVerify = configuration.InsecureSkipVerify,
});
}
}

18
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/Microsoft/Extensions/DependencyInjection/AliyunHttpClientFactoryServiceCollectionExtensions.cs

@ -0,0 +1,18 @@
using System.Net.Http;
namespace Microsoft.Extensions.DependencyInjection;
internal static class AliyunHttpClientFactoryServiceCollectionExtensions
{
private const string HttpClientName = "__AliyunBlobStoringApiClient";
public static IServiceCollection AddAliyunHttpClient(this IServiceCollection services)
{
services.AddHttpClient(HttpClientName);
return services;
}
public static HttpClient CreateAliyunHttpClient(this IHttpClientFactory httpClientFactory)
{
return httpClientFactory.CreateClient(HttpClientName);
}
}

2
aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN.Abp.Sms.Aliyun.csproj

@ -21,6 +21,8 @@
<ItemGroup>
<PackageReference Include="Volo.Abp.Sms" />
<PackageReference Include="AlibabaCloud.SDK.Dypnsapi20170525" />
<PackageReference Include="AlibabaCloud.SDK.Dysmsapi20170525" />
</ItemGroup>
<ItemGroup>

119
aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs

@ -1,14 +1,11 @@
using Aliyun.Acs.Core;
using Aliyun.Acs.Core.Exceptions;
using Aliyun.Acs.Core.Http;
using LINGYUN.Abp.Aliyun;
using AlibabaCloud.SDK.Dypnsapi20170525.Models;
using AlibabaCloud.SDK.Dysmsapi20170525.Models;
using LINGYUN.Abp.Aliyun.Features;
using LINGYUN.Abp.Aliyun.Settings;
using LINGYUN.Abp.Features.LimitValidation;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
@ -30,17 +27,20 @@ public class AliyunSmsSender : ISmsSender, IAliyunSmsVerifyCodeSender
protected IJsonSerializer JsonSerializer { get; }
protected ISettingProvider SettingProvider { get; }
protected IServiceProvider ServiceProvider { get; }
protected IAcsClientFactory AcsClientFactory { get; }
protected IDypnsClientFactory DypnsClientFactory { get; }
protected IDysmsClientFactory DysmsClientFactory { get; }
public AliyunSmsSender(
IJsonSerializer jsonSerializer,
ISettingProvider settingProvider,
IServiceProvider serviceProvider,
IAcsClientFactory acsClientFactory)
IDypnsClientFactory dypnsClientFactory,
IDysmsClientFactory dysmsClientFactory)
{
JsonSerializer = jsonSerializer;
SettingProvider = settingProvider;
ServiceProvider = serviceProvider;
AcsClientFactory = acsClientFactory;
DypnsClientFactory = dypnsClientFactory;
DysmsClientFactory = dysmsClientFactory;
}
[RequiresLimitFeature(
@ -74,41 +74,23 @@ public class AliyunSmsSender : ISmsSender, IAliyunSmsVerifyCodeSender
Check.NotNullOrWhiteSpace(action, AliyunSettingNames.Sms.ActionName);
Check.NotNullOrWhiteSpace(version, AliyunSettingNames.Sms.Version);
var request = new CommonRequest
{
Method = MethodType.POST,
Domain = domain,
Action = action,
Version = version
};
var request = new SendSmsRequest();
await TryAddTemplateCodeAsync(request, smsMessage);
await TryAddSignNameAsync(request, smsMessage);
await TryAddSendPhoneAsync(request, smsMessage);
TryAddTemplateParam(request, smsMessage);
try
var dysmsClient = await DysmsClientFactory.CreateAsync();
var response = await dysmsClient.SendSmsAsync(request);
if (!string.Equals(response.Body.Code, "OK", StringComparison.CurrentCultureIgnoreCase))
{
var client = await AcsClientFactory.CreateAsync();
var response = client.GetCommonResponse(request);
var responseContent = Encoding.Default.GetString(response.HttpResponse.Content);
var aliyunResponse = JsonSerializer.Deserialize<AliyunSmsResponse>(responseContent);
if (!aliyunResponse.IsSuccess())
if (await SettingProvider.IsTrueAsync(AliyunSettingNames.Sms.VisableErrorToClient))
{
if (await SettingProvider.IsTrueAsync(AliyunSettingNames.Sms.VisableErrorToClient))
{
throw new UserFriendlyException(aliyunResponse.Code, aliyunResponse.Message);
}
throw new AliyunSmsException(aliyunResponse.Code, $"Text message sending failed, code:{aliyunResponse.Code}, message:{aliyunResponse.Message}!");
throw new UserFriendlyException(response.Body.Code, response.Body.Message);
}
}
catch(ServerException se)
{
throw new AliyunSmsException(se.ErrorCode, $"Sending text messages to aliyun server is abnormal,type: {se.ErrorType}, error: {se.ErrorMessage}");
}
catch(ClientException ce)
{
throw new AliyunSmsException(ce.ErrorCode, $"A client exception occurred in sending SMS messages,type: {ce.ErrorType}, error: {ce.ErrorMessage}");
throw new AliyunSmsException(response.Body.Code, $"Text message sending failed, code:{response.Body.Code}, message:{response.Body.Message}!");
}
}
@ -132,75 +114,58 @@ public class AliyunSmsSender : ISmsSender, IAliyunSmsVerifyCodeSender
Check.NotNullOrWhiteSpace(signName, AliyunSettingNames.SmsVerifyCode.DefaultSignName);
Check.NotNullOrWhiteSpace(templateCode, AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode);
var request = new CommonRequest
var dypnsClient = await DypnsClientFactory.CreateAsync();
var request = new SendSmsVerifyCodeRequest
{
Domain = domain,
Version = version,
Product = "Dypnsapi",
Method = MethodType.POST,
Action = "SendSmsVerifyCode",
PhoneNumber = message.PhoneNumber,
SignName = signName,
TemplateCode = templateCode,
TemplateParam = JsonSerializer.Serialize(message.TemplateParam),
};
request.AddBodyParameters("PhoneNumber", message.PhoneNumber);
request.AddBodyParameters("SignName", signName);
request.AddBodyParameters("TemplateCode", templateCode);
request.AddBodyParameters("TemplateParam", JsonSerializer.Serialize(message.TemplateParam));
try
var response = await dypnsClient.SendSmsVerifyCodeAsync(request);
if (response.Body.Success == false)
{
var client = await AcsClientFactory.CreateAsync();
var response = client.GetCommonResponse(request);
var responseContent = Encoding.Default.GetString(response.HttpResponse.Content);
var aliyunResponse = JsonSerializer.Deserialize<AliyunSmsVerifyCodeResponse>(responseContent);
if (!aliyunResponse.Success)
if (await SettingProvider.IsTrueAsync(AliyunSettingNames.Sms.VisableErrorToClient))
{
if (await SettingProvider.IsTrueAsync(AliyunSettingNames.Sms.VisableErrorToClient))
{
throw new UserFriendlyException(aliyunResponse.Code, aliyunResponse.Message);
}
throw new AliyunSmsException(aliyunResponse.Code, $"Text message sending failed, code:{aliyunResponse.Code}, message:{aliyunResponse.Message}!");
throw new UserFriendlyException(response.Body.Code, response.Body.Message);
}
}
catch (ServerException se)
{
throw new AliyunSmsException(se.ErrorCode, $"Sending text messages to aliyun server is abnormal,type: {se.ErrorType}, error: {se.ErrorMessage}");
}
catch (ClientException ce)
{
throw new AliyunSmsException(ce.ErrorCode, $"A client exception occurred in sending SMS messages,type: {ce.ErrorType}, error: {ce.ErrorMessage}");
throw new AliyunSmsException(response.Body.Code, $"Text message sending failed, code:{response.Body.Code}, message:{response.Body.Message}!");
}
}
private async Task TryAddTemplateCodeAsync(CommonRequest request, SmsMessage smsMessage)
private async Task TryAddTemplateCodeAsync(SendSmsRequest request, SmsMessage smsMessage)
{
if (smsMessage.Properties.TryGetValue("TemplateCode", out object template) && template != null)
if (smsMessage.Properties.TryGetValue("TemplateCode", out var template) && template != null)
{
request.AddQueryParameters("TemplateCode", template.ToString());
request.TemplateCode = template.ToString();
smsMessage.Properties.Remove("TemplateCode");
}
else
{
var defaultTemplateCode = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Sms.DefaultTemplateCode);
Check.NotNullOrWhiteSpace(defaultTemplateCode, "TemplateCode");
request.AddQueryParameters("TemplateCode", defaultTemplateCode);
request.TemplateCode = defaultTemplateCode;
}
}
private async Task TryAddSignNameAsync(CommonRequest request, SmsMessage smsMessage)
private async Task TryAddSignNameAsync(SendSmsRequest request, SmsMessage smsMessage)
{
if (smsMessage.Properties.TryGetValue("SignName", out object signName) && signName != null)
if (smsMessage.Properties.TryGetValue("SignName", out var signName) && signName != null)
{
request.AddQueryParameters("SignName", signName.ToString());
request.SignName = signName.ToString();
smsMessage.Properties.Remove("SignName");
}
else
{
var defaultSignName = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Sms.DefaultSignName);
Check.NotNullOrWhiteSpace(defaultSignName, "SignName");
request.AddQueryParameters("SignName", defaultSignName);
request.SignName = defaultSignName;
}
}
private async Task TryAddSendPhoneAsync(CommonRequest request, SmsMessage smsMessage)
private async Task TryAddSendPhoneAsync(SendSmsRequest request, SmsMessage smsMessage)
{
if (smsMessage.PhoneNumber.IsNullOrWhiteSpace())
{
@ -210,20 +175,20 @@ public class AliyunSmsSender : ISmsSender, IAliyunSmsVerifyCodeSender
defaultPhoneNumber,
AliyunSettingNames.Sms.DefaultPhoneNumber,
maxLength: 11, minLength: 11);
request.AddQueryParameters("PhoneNumbers", defaultPhoneNumber);
request.PhoneNumbers = defaultPhoneNumber;
}
else
{
request.AddQueryParameters("PhoneNumbers", smsMessage.PhoneNumber);
request.PhoneNumbers = smsMessage.PhoneNumber;
}
}
private void TryAddTemplateParam(CommonRequest request, SmsMessage smsMessage)
private void TryAddTemplateParam(SendSmsRequest request, SmsMessage smsMessage)
{
if (smsMessage.Properties.Any())
{
var queryParamJson = JsonSerializer.Serialize(smsMessage.Properties);
request.AddQueryParameters("TemplateParam", queryParamJson);
request.TemplateParam = queryParamJson;
}
}
}

41
aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DypnsClientFactory.cs

@ -0,0 +1,41 @@
using AlibabaCloud.SDK.Dypnsapi20170525;
using LINGYUN.Abp.Aliyun;
using System;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Sms.Aliyun;
public class DypnsClientFactory : AliyunClientFactory<Client>, IDypnsClientFactory, ITransientDependency
{
public DypnsClientFactory(
ISettingProvider settingProvider,
IDistributedCache<AliyunBasicSessionCredentialsCacheItem> cache)
: base(settingProvider, cache)
{
}
protected override Client GetClient(string regionId, string accessKeyId, string accessKeySecret)
{
return new Client(
new AlibabaCloud.OpenApiClient.Models.Config
{
RegionId = regionId,
AccessKeyId = accessKeyId,
AccessKeySecret = accessKeySecret,
});
}
protected override Client GetSecurityTokenClient(string regionId, string accessKeyId, string accessKeySecret, string securityToken, DateTime? expiration = null)
{
return new Client(
new AlibabaCloud.OpenApiClient.Models.Config
{
RegionId = regionId,
AccessKeyId = accessKeyId,
AccessKeySecret = accessKeySecret,
SecurityToken = securityToken,
});
}
}

38
aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DysmsClientFactory.cs

@ -0,0 +1,38 @@
using AlibabaCloud.SDK.Dysmsapi20170525;
using LINGYUN.Abp.Aliyun;
using System;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Sms.Aliyun;
public class DysmsClientFactory : AliyunClientFactory<Client>, IDysmsClientFactory, ITransientDependency
{
public DysmsClientFactory(ISettingProvider settingProvider, IDistributedCache<AliyunBasicSessionCredentialsCacheItem> cache) : base(settingProvider, cache)
{
}
protected override Client GetClient(string regionId, string accessKeyId, string accessKeySecret)
{
return new Client(
new AlibabaCloud.OpenApiClient.Models.Config
{
RegionId = regionId,
AccessKeyId = accessKeyId,
AccessKeySecret = accessKeySecret,
});
}
protected override Client GetSecurityTokenClient(string regionId, string accessKeyId, string accessKeySecret, string securityToken, DateTime? expiration = null)
{
return new Client(
new AlibabaCloud.OpenApiClient.Models.Config
{
RegionId = regionId,
AccessKeyId = accessKeyId,
AccessKeySecret = accessKeySecret,
SecurityToken = securityToken,
});
}
}

13
aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDypnsClientFactory.cs

@ -0,0 +1,13 @@
using AlibabaCloud.SDK.Dypnsapi20170525;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Sms.Aliyun;
public interface IDypnsClientFactory
{
/// <summary>
/// 构建短信认证客户端
/// </summary>
/// <returns></returns>
Task<Client> CreateAsync();
}

13
aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDysmsClientFactory.cs

@ -0,0 +1,13 @@
using AlibabaCloud.SDK.Dysmsapi20170525;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Sms.Aliyun;
public interface IDysmsClientFactory
{
/// <summary>
/// 构建短信客户端
/// </summary>
/// <returns></returns>
Task<Client> CreateAsync();
}

175
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs

@ -1,4 +1,5 @@
using Aliyun.OSS;
using AlibabaCloud.OSS.V2;
using AlibabaCloud.OSS.V2.Models;
using LINGYUN.Abp.BlobStoring.Aliyun;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -7,8 +8,9 @@ using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.BlobStoring;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.BlobManagement.Aliyun;
@ -17,24 +19,30 @@ public class AliyunBlobProvider : IBlobProvider
public const string ProviderName = "Aliyun";
public string Name => ProviderName;
protected IClock Clock { get; }
protected IConfiguration Configuration { get; }
protected ICurrentTenant CurrentTenant { get; }
protected IOssClientFactory OssClientFactory { get; }
protected IHttpClientFactory HttpClientFactory { get; }
protected AliyunBlobNamingNormalizer BlobNamingNormalizer { get; }
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
public AliyunBlobProvider(
IClock clock,
IConfiguration configuration,
ICurrentTenant currentTenant,
IOssClientFactory ossClientFactory,
IHttpClientFactory httpClientFactory,
AliyunBlobNamingNormalizer blobNamingNormalizer)
AliyunBlobNamingNormalizer blobNamingNormalizer,
IBlobContainerConfigurationProvider configurationProvider)
{
Clock = clock;
Configuration = configuration;
CurrentTenant = currentTenant;
OssClientFactory = ossClientFactory;
HttpClientFactory = httpClientFactory;
BlobNamingNormalizer = blobNamingNormalizer;
ConfigurationProvider = configurationProvider;
}
public async virtual Task CreateContainerAsync(
@ -43,23 +51,29 @@ public class AliyunBlobProvider : IBlobProvider
{
var client = await CreateClientAsync();
var bucket = NormalizeContainerName(name);
var configuration = GetBlobConfiguration();
CreateBucketIfNotExists(client, bucket);
await CreateBucketIfNotExists(client, bucket, configuration.CreateBucketAcl, cancellationToken);
}
public async virtual Task DeleteContainerAsync(
string name,
CancellationToken cancellationToken = default)
{
var client = await CreateClientAsync();
var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(name);
if (!BucketExists(client, bucket))
if (!await BucketExists(ossClient, bucket, cancellationToken))
{
return;
}
client.DeleteBucket(bucket);
await ossClient.DeleteBucketAsync(
new DeleteBucketRequest
{
Bucket = bucket,
},
cancellationToken: cancellationToken);
}
public async virtual Task DeleteBlobAsync(
@ -67,13 +81,19 @@ public class AliyunBlobProvider : IBlobProvider
string blobName,
CancellationToken cancellationToken = default)
{
var client = await CreateClientAsync();
var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName);
var objectName = CalculateBlobName(blobName);
if (ObjectExists(client, bucket, objectName))
if (await ObjectExists(ossClient, bucket, objectName, cancellationToken))
{
client.DeleteObject(bucket, objectName);
await ossClient.DeleteObjectAsync(
new DeleteObjectRequest
{
Bucket = bucket,
Key = objectName,
},
cancellationToken: cancellationToken);
}
}
@ -91,20 +111,75 @@ public class AliyunBlobProvider : IBlobProvider
string blobName,
CancellationToken cancellationToken = default)
{
var client = await CreateClientAsync();
var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName);
var objectName = CalculateBlobName(blobName);
if (!ObjectExists(client, bucket, objectName))
if (!await ObjectExists(ossClient, bucket, objectName, cancellationToken))
{
return null;
}
var downloadUrl = client.GeneratePresignedUri(bucket, objectName);
var result = await ossClient.GetObjectAsync(
new GetObjectRequest
{
Bucket = bucket,
Key = blobName,
});
return result.Body;
// TODO: 阿里云sdk预签名不可用[2026/05/23]
//var configuration = GetBlobConfiguration();
//var downloadUrl = await GeneratePresignedUrlAsync(
// containerName,
// blobName,
// TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds),
// cancellationToken: cancellationToken);
//if (downloadUrl.IsNullOrWhiteSpace())
//{
// return null;
//}
//var httpClient = HttpClientFactory.CreateAliyunHttpClient();
//return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
}
public virtual Task<string?> GeneratePresignedUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
bool isAttachmentContent = true,
CancellationToken cancellationToken = default)
{
// TODO: 阿里云sdk预签名不可用[2026/05/23]
return Task.FromResult<string?>(null);
//var ossClient = await CreateClientAsync();
//var bucket = NormalizeContainerName(containerName);
//var objectName = CalculateBlobName(blobName);
//if (!await ObjectExists(ossClient, bucket, objectName, cancellationToken))
//{
// return null;
//}
var httpClient = HttpClientFactory.CreateAliyunHttpClient();
//var fileName = Path.GetFileName(blobName);
//var type = isAttachmentContent ? "attachment" : "inline";
//var disposition = $"{type}; filename=\"{Uri.EscapeDataString(fileName)}\"; " +
// $"filename*=UTF-8''{Uri.EscapeDataString(fileName)}";
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
//var presignResult = ossClient.Presign(
// new GetObjectRequest
// {
// Bucket = bucket,
// Key = blobName,
// ResponseContentDisposition = disposition,
// },
// Clock.Now.Add(expiration));
//return presignResult.Url;
}
public virtual Task CreateFolderAsync(
@ -120,39 +195,83 @@ public class AliyunBlobProvider : IBlobProvider
public async virtual Task UploadBlobAsync(
string containerName,
string blobName,
Stream content,
Stream content,
string? contentType = null,
CancellationToken cancellationToken = default)
{
var client = await CreateClientAsync();
var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName);
var objectName = CalculateBlobName(blobName);
var configuration = GetBlobConfiguration();
await CreateBucketIfNotExists(ossClient, bucket, configuration.CreateBucketAcl, cancellationToken);
CreateBucketIfNotExists(client, bucket);
await ossClient.PutObjectAsync(
new PutObjectRequest
{
Bucket = bucket,
Key = objectName,
Body = content,
ContentType = contentType,
},
cancellationToken: cancellationToken);
}
client.PutObject(bucket, objectName, content);
protected virtual AliyunBlobProviderConfiguration GetBlobConfiguration()
{
var configuration = ConfigurationProvider.Get<BlobManagementContainer>();
return configuration.GetAliyunConfiguration();
}
protected async virtual Task<IOss> CreateClientAsync()
protected async virtual Task<Client> CreateClientAsync()
{
return await OssClientFactory.CreateAsync();
var configuration = GetBlobConfiguration();
return await OssClientFactory.CreateAsync(configuration);
}
protected virtual void CreateBucketIfNotExists(IOss oss, string bucket)
protected async virtual Task CreateBucketIfNotExists(
Client ossClient,
string bucket,
BucketAclType? bucketAcl = null,
CancellationToken cancellationToken = default)
{
if (!BucketExists(oss, bucket))
if (!await BucketExists(ossClient, bucket, cancellationToken))
{
oss.CreateBucket(bucket);
await ossClient.PutBucketAsync(
new PutBucketRequest
{
Bucket = bucket,
},
cancellationToken: cancellationToken);
if (bucketAcl.HasValue)
{
await ossClient.PutBucketAclAsync(
new PutBucketAclRequest
{
Bucket = bucket,
Acl = bucketAcl.Value.GetString(),
},
cancellationToken: cancellationToken);
}
}
}
protected virtual bool BucketExists(IOss oss, string bucket)
protected async virtual Task<bool> BucketExists(
Client ossClient,
string bucket,
CancellationToken cancellationToken = default)
{
return oss.DoesBucketExist(bucket);
return await ossClient.IsBucketExistAsync(bucket, cancellationToken);
}
protected virtual bool ObjectExists(IOss oss, string bucket, string objectName)
protected async virtual Task<bool> ObjectExists(
Client ossClient,
string bucket,
string objectName,
CancellationToken cancellationToken = default)
{
return oss.DoesObjectExist(bucket, objectName);
return await ossClient.IsObjectExistAsync(bucket, objectName, cancellationToken: cancellationToken);
}
protected virtual string GetPrefixPath()

12
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByIdInput.cs

@ -0,0 +1,12 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace LINGYUN.Abp.BlobManagement.Dtos;
public class BlobDownloadByIdInput
{
public Guid? TenantId { get; set; }
[Required]
public Guid Id { get; set; }
}

5
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByNameInput.cs

@ -1,10 +1,13 @@
using System.ComponentModel.DataAnnotations;
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.BlobManagement.Dtos;
public class BlobDownloadByNameInput
{
public Guid? TenantId { get; set; }
[Required]
[DynamicStringLength(typeof(BlobContainerConsts), nameof(BlobContainerConsts.MaxNameLength))]
public string ContainerName { get; set; }

10
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs

@ -17,9 +17,15 @@ public interface IBlobAppService : IApplicationService
Task DeleteAsync(Guid id);
Task<IRemoteStreamContent> GetContentAsync(Guid id);
Task<string> GeneratePreviewUrlAsync(Guid id);
Task<IRemoteStreamContent> GetContentByNameAsync(BlobDownloadByNameInput input);
Task<string> GenerateDownloadUrlAsync(Guid id);
Task<IRemoteStreamContent> DownloadAsync(BlobDownloadByIdInput input);
Task<IRemoteStreamContent> PreviewAsync(BlobDownloadByIdInput input);
Task<IRemoteStreamContent> DownloadByNameAsync(BlobDownloadByNameInput input);
Task<BlobDto> GetAsync(Guid id);

4
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPrivateBlobAppService.cs

@ -17,9 +17,9 @@ public interface IPrivateBlobAppService : IApplicationService
Task DeleteAsync(Guid id);
Task<IRemoteStreamContent> GetContentAsync(Guid id);
Task<IRemoteStreamContent> DownloadAsync(Guid id);
Task<IRemoteStreamContent> GetContentByNameAsync(string name);
Task<IRemoteStreamContent> DownloadByNameAsync(string name);
Task<BlobDto> GetAsync(Guid id);

4
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPublicBlobAppService.cs

@ -9,9 +9,9 @@ namespace LINGYUN.Abp.BlobManagement;
public interface IPublicBlobAppService : IApplicationService
{
Task<IRemoteStreamContent> GetContentAsync(Guid id);
Task<IRemoteStreamContent> DownloadAsync(Guid id);
Task<IRemoteStreamContent> GetContentByNameAsync(string name);
Task<IRemoteStreamContent> DownloadByNameAsync(string name);
Task<BlobDto> GetAsync(Guid id);

2
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Integration/IBlobIntegrationService.cs

@ -16,5 +16,5 @@ public interface IBlobIntegrationService : IApplicationService
Task<bool> ExistsAsync(BlobFileGetByNameIntegrationDto input);
Task<IRemoteStreamContent> GetContentAsync(BlobFileGetByNameIntegrationDto input);
Task<IRemoteStreamContent> DownloadAsync(BlobFileGetByNameIntegrationDto input);
}

2
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionDefinitionProvider.cs

@ -27,6 +27,8 @@ public class BlobManagementPermissionDefinitionProvider : PermissionDefinitionPr
blob.AddChild(BlobManagementPermissionNames.Blob.Delete, L("Permission:Delete"));
blob.AddChild(BlobManagementPermissionNames.Blob.Download, L("Permission:Download"))
.RequireFeatures(BlobManagementFeatureNames.Blob.DownloadFile);
blob.AddChild(BlobManagementPermissionNames.Blob.GenerateDownloadUrl, L("Permission:GenerateDownloadUrl"))
.RequireFeatures(BlobManagementFeatureNames.Blob.GenerateDownloadUrl);
context.AddResourcePermission(
name: BlobManagementPermissionNames.Blob.Resources.Delete,

2
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionNames.cs

@ -23,6 +23,8 @@ public static class BlobManagementPermissionNames
public const string Download = Default + ".Download";
public const string GenerateDownloadUrl = Default + ".GenerateDownloadUrl";
public const string ManagePermissions = Default + ".ManagePermissions";
public static class Resources

41
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs

@ -1,7 +1,6 @@
using LINGYUN.Abp.BlobManagement.Dtos;
using LINGYUN.Abp.BlobManagement.Features;
using LINGYUN.Abp.BlobManagement.Permissions;
using LINGYUN.Abp.Features.LimitValidation;
using System.Threading.Tasks;
using Volo.Abp.Content;
using Volo.Abp.Features;
@ -23,11 +22,6 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService
GetListPolicyName = BlobManagementPermissionNames.Blob.Default;
}
[RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.UploadLimit,
BlobManagementFeatureNames.Blob.UploadInterval,
LimitPolicy.Month)]
public async virtual Task CreateChunkFileAsync(BlobFileChunkCreateDto input)
{
await CheckCreatePolicyAsync();
@ -35,11 +29,6 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService
await base.CreateChunkFileAsync(input.ContainerId.ToString(), input);
}
[RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.UploadLimit,
BlobManagementFeatureNames.Blob.UploadInterval,
LimitPolicy.Month)]
public async virtual Task<BlobDto> CreateFileAsync(BlobFileCreateDto input)
{
await CheckCreatePolicyAsync();
@ -58,16 +47,30 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService
return await base.CreateFolderAsync(blobContainer, input);
}
[RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.DownloadLimit,
BlobManagementFeatureNames.Blob.DownloadInterval,
LimitPolicy.Month)]
public async virtual Task<IRemoteStreamContent> GetContentByNameAsync(BlobDownloadByNameInput input)
public async virtual Task<IRemoteStreamContent> DownloadAsync(BlobDownloadByIdInput input)
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName);
using (CurrentTenant.Change(input.TenantId ?? CurrentTenant.Id))
{
return await base.DownloadAsync(input.Id);
}
}
public async virtual Task<IRemoteStreamContent> PreviewAsync(BlobDownloadByIdInput input)
{
using (CurrentTenant.Change(input.TenantId ?? CurrentTenant.Id))
{
return await base.DownloadAsync(input.Id);
}
}
public async virtual Task<IRemoteStreamContent> DownloadByNameAsync(BlobDownloadByNameInput input)
{
using (CurrentTenant.Change(input.TenantId ?? CurrentTenant.Id))
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName);
return await base.GetContentByNameAsync(blobContainer, input.BlobName);
return await base.DownloadByNameAsync(blobContainer, input.BlobName);
}
}
protected async override Task CheckGetPolicyAsync(string containerName, Blob blob)

48
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs

@ -1,7 +1,5 @@
using LINGYUN.Abp.BlobManagement.Dtos;
using LINGYUN.Abp.BlobManagement.Features;
using LINGYUN.Abp.BlobManagement.Permissions;
using LINGYUN.Abp.Features.LimitValidation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
@ -13,7 +11,6 @@ using System.Web;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Content;
using Volo.Abp.Features;
using Volo.Abp.Specifications;
namespace LINGYUN.Abp.BlobManagement;
@ -49,12 +46,17 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
await DeleteBlobAsync(blob);
}
[RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.DownloadLimit,
BlobManagementFeatureNames.Blob.DownloadInterval,
LimitPolicy.Month)]
public async virtual Task<IRemoteStreamContent> GetContentAsync(Guid id)
public async virtual Task<string> GenerateDownloadUrlAsync(Guid id)
{
return await GenerateDownloadUrlAsync(id, "download");
}
public async virtual Task<string> GeneratePreviewUrlAsync(Guid id)
{
return await GenerateDownloadUrlAsync(id, "preview", isAttachmentContent: false);
}
public async virtual Task<IRemoteStreamContent> DownloadAsync(Guid id)
{
var blob = await BlobRepository.GetAsync(id);
@ -62,7 +64,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
var stream = await BlobManager.DownloadBlobsync(blob);
return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, blob.Size);
return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, stream?.Length);
}
public async virtual Task<BlobDto> GetAsync(Guid id)
@ -120,7 +122,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
blobStream.Length,
parentBlob);
blob = await BlobManager.UploadBlobAsync(blobContainer, blob, blobStream, input.File.ContentType, input.CompareMd5);
blob = await BlobManager.UploadBlobAsync(blobContainer, blob, blobStream, input.CompareMd5);
return ObjectMapper.Map<Blob, BlobDto>(blob);
}
@ -155,7 +157,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
return ObjectMapper.Map<Blob, BlobDto>(blob);
}
protected async virtual Task<IRemoteStreamContent> GetContentByNameAsync(BlobContainer blobContainer, string name)
protected async virtual Task<IRemoteStreamContent> DownloadByNameAsync(BlobContainer blobContainer, string name)
{
var blob = await FindBlobByNameAsync(blobContainer, name)
?? throw new BusinessException(
@ -167,7 +169,27 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
var stream = await BlobManager.DownloadBlobsync(blob);
return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, blob.Size);
return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, stream?.Length);
}
protected async virtual Task<string> GenerateDownloadUrlAsync(Guid id, string method, bool isAttachmentContent = true)
{
var blob = await BlobRepository.GetAsync(id);
var blobContainer = await BlobContainerRepository.GetAsync(blob.ContainerId);
await CheckGetPolicyAsync(blob);
var fallbackDownloadUrl = blob.TenantId.HasValue
? $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/{method}/t/{blob.TenantId:N}/{blob.Id:N}"
: $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/{method}/{blob.Id:N}";
var downloadUrl = await BlobManager.GenerateDownloadUrlAsync(
blobContainer,
blob,
fallbackDownloadUrl,
isAttachmentContent);
return downloadUrl;
}
protected async virtual Task<Blob?> FindBlobByNameAsync(BlobContainer blobContainer, string name)

26
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs

@ -1,10 +1,7 @@
using LINGYUN.Abp.BlobManagement.Dtos;
using LINGYUN.Abp.BlobManagement.Features;
using LINGYUN.Abp.Features.LimitValidation;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Content;
using Volo.Abp.Features;
namespace LINGYUN.Abp.BlobManagement;
@ -20,11 +17,6 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase
{
}
[RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.UploadLimit,
BlobManagementFeatureNames.Blob.UploadInterval,
LimitPolicy.Month)]
public async virtual Task CreateChunkFileAsync(BlobFileChunkCreateWithoutContainerDto input)
{
await CheckCreatePolicyAsync();
@ -32,11 +24,6 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase
await base.CreateChunkFileAsync(ContainerName, input);
}
[RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.UploadLimit,
BlobManagementFeatureNames.Blob.UploadInterval,
LimitPolicy.Month)]
public async virtual Task<BlobDto> CreateFileAsync(BlobFileCreateWithoutContainerDto input)
{
await CheckCreatePolicyAsync();
@ -55,16 +42,11 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase
return await base.CreateFolderAsync(blobContainer, input);
}
[RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.DownloadLimit,
BlobManagementFeatureNames.Blob.DownloadInterval,
LimitPolicy.Month)]
public async virtual Task<IRemoteStreamContent> GetContentByNameAsync(string name)
public async virtual Task<IRemoteStreamContent> DownloadByNameAsync(string name)
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(ContainerName);
var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), ContainerName);
return await base.GetContentByNameAsync(blobContainer, name);
return await base.DownloadByNameAsync(blobContainer, name);
}
public async virtual Task<PagedResultDto<BlobDto>> GetListAsync(BlobGetPagedListWithoutContainerInput input)
@ -85,7 +67,7 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase
protected async virtual Task<BlobContainer> GetOrCreateContainer()
{
var blobContainer = await BlobContainerRepository.FindByNameAsync(ContainerName)
var blobContainer = await BlobManager.FindContainerAsync(ContainerName)
?? await BlobManager.CreateContainerAsync(ContainerName);
return blobContainer;

23
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs

@ -1,7 +1,6 @@
using LINGYUN.Abp.BlobManagement.Dtos;
using LINGYUN.Abp.BlobManagement.Features;
using LINGYUN.Abp.BlobManagement.Integration.Dtos;
using LINGYUN.Abp.Features.LimitValidation;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
@ -27,14 +26,9 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic
{
}
[RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.UploadLimit,
BlobManagementFeatureNames.Blob.UploadInterval,
LimitPolicy.Month)]
public async virtual Task<BlobDto> CreateAsync(BlobFileCreateIntegrationDto input)
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName);
var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName);
// Example: /a/b/c/d/f.txt
var blobName = HttpUtility.UrlDecode(input.BlobName);
@ -62,14 +56,14 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic
parentBlob,
input.Overwrite == true);
await BlobManager.UploadBlobAsync(blobContainer, blob, blobStream, input.File.ContentType);
await BlobManager.UploadBlobAsync(blobContainer, blob, blobStream);
return ObjectMapper.Map<Blob, BlobDto>(blob);
}
public async virtual Task DeleteAsync(BlobFileGetByNameIntegrationDto input)
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName);
var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName);
var blob = await FindBlobByNameAsync(blobContainer, input.BlobName)
?? throw new BusinessException(
@ -96,17 +90,12 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic
}
}
[RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.DownloadLimit,
BlobManagementFeatureNames.Blob.DownloadInterval,
LimitPolicy.Month)]
public async virtual Task<IRemoteStreamContent> GetContentAsync(BlobFileGetByNameIntegrationDto input)
public async virtual Task<IRemoteStreamContent> DownloadAsync(BlobFileGetByNameIntegrationDto input)
{
try
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName);
return await GetContentByNameAsync(blobContainer, input.BlobName);
var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName);
return await DownloadByNameAsync(blobContainer, input.BlobName);
}
catch (Exception ex)
{

14
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/PublicBlobAppService.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.BlobManagement.Features;
using LINGYUN.Abp.BlobManagement.Permissions;
using System.Threading.Tasks;
using Volo.Abp.Features;
@ -15,16 +16,19 @@ public class PublicBlobAppService : BlobWithoutContainerAppService, IPublicBlobA
BlobManager blobManager)
: base(blobContainerRepository, blobRepository, blobManager)
{
DeletePolicyName = BlobManagementPermissionNames.Blob.Delete;
}
protected override Task CheckGetPolicyAsync(string containerName, Blob blob)
protected async override Task CheckGetPolicyAsync(string containerName, Blob blob)
{
// 公共文件访问不校验权限
return Task.CompletedTask;
if (!CurrentUser.IsAuthenticated)
{
await FeatureChecker.CheckEnabledAsync(BlobManagementFeatureNames.PublicAccess);
}
}
protected override Task CheckDeletePolicyAsync(string containerName, Blob blob)
protected async override Task CheckDeletePolicyAsync(string containerName, Blob blob)
{
return base.CheckDeletePolicyAsync(blob);
await CheckPolicyAsync(DeletePolicyName);
}
}

15
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/AbpBlobManagementContentTypeResolveOptions.cs

@ -0,0 +1,15 @@
using JetBrains.Annotations;
using System.Collections.Generic;
namespace LINGYUN.Abp.BlobManagement;
public class AbpBlobManagementContentTypeResolveOptions
{
[NotNull]
public List<IBlobContentTypeResolveContributor> BlobContentTypeResolvers { get; }
public AbpBlobManagementContentTypeResolveOptions()
{
BlobContentTypeResolvers = new List<IBlobContentTypeResolveContributor>();
}
}

33
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobContentTypeResolveContext.cs

@ -0,0 +1,33 @@
using System;
using System.IO;
namespace LINGYUN.Abp.BlobManagement;
public class BlobContentTypeResolveContext
{
public IServiceProvider ServiceProvider { get; }
public string BlobName { get; }
public Stream Stream { get; }
public bool Handled { get; private set; }
public string? ContentType { get; private set; }
public BlobContentTypeResolveContext(
IServiceProvider serviceProvider,
string blobName,
Stream stream)
{
ServiceProvider = serviceProvider;
BlobName = blobName;
Stream = stream;
}
public void Success(string contentType)
{
ContentType = contentType;
Handled = true;
}
public bool HasResolvedContentType()
{
return Handled && !ContentType.IsNullOrWhiteSpace();
}
}

8
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobDownloadEto.cs

@ -6,6 +6,7 @@ namespace LINGYUN.Abp.BlobManagement;
public class BlobDownloadEto : IMultiTenant
{
public Guid? TenantId { get; set; }
public string Provider { get; set; }
public string ContainerName { get; set; }
public string FullName { get; set; }
public BlobDownloadEto()
@ -13,8 +14,13 @@ public class BlobDownloadEto : IMultiTenant
}
public BlobDownloadEto(string containerName, string fullName, Guid? tenantId = null)
public BlobDownloadEto(
string provider,
string containerName,
string fullName,
Guid? tenantId = null)
{
Provider = provider;
ContainerName = containerName;
FullName = fullName;
TenantId = tenantId;

7
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureDefinitionProvider.cs

@ -32,7 +32,12 @@ public class BlobManagementFeatureDefinitionProvider : FeatureDefinitionProvider
displayName: L("Features:DisplayName:EnableBlob"),
description: L("Features:Description:EnableBlob"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
blobDefaultFeature.CreateChild(
name: BlobManagementFeatureNames.Blob.GenerateDownloadUrl,
defaultValue: true.ToString(),
displayName: L("Features:DisplayName:GenerateDownloadUrl"),
description: L("Features:Description:GenerateDownloadUrl"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
blobDefaultFeature.CreateChild(
name: BlobManagementFeatureNames.Blob.DownloadFile,
defaultValue: true.ToString(),

4
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureNames.cs

@ -20,6 +20,10 @@ public static class BlobManagementFeatureNames
/// </summary>
public const string Enable = Default + ".Enable";
/// <summary>
/// 生成临时文件下载链接
/// </summary>
public const string GenerateDownloadUrl = Default + ".GenerateDownloadUrl";
/// <summary>
/// 下载文件功能
/// </summary>
public const string DownloadFile = Default + ".DownloadFile";

10
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolveContributor.cs

@ -0,0 +1,10 @@
using System.Threading.Tasks;
namespace LINGYUN.Abp.BlobManagement;
public interface IBlobContentTypeResolveContributor
{
string Name { get; }
int Priority { get; }
Task ResolveAsync(BlobContentTypeResolveContext context);
}

5
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/en.json

@ -10,6 +10,7 @@
"Permission:Download": "Download",
"Permission:Blob:Delete": "Delete Blob",
"Permission:Blob:View": "View Blob",
"Permission:GenerateDownloadUrl": "Generate Download Url",
"Features:BlobManagement": "Blob Management",
"Features:DisplayName:EnableBlobManagement": "Enable Blob Management",
"Features:Description:EnableBlobManagement": "Enable to give the application the ability to have an blob storage module.",
@ -29,6 +30,8 @@
"Features:Description:UploadLimit": "Limits the total number of file uploads within a specified period",
"Features:DisplayName:UploadInterval": "Limit Uploads Interval",
"Features:Description:UploadInterval": "The update period that limits the number of files uploaded. The time scale is: month. Default: 1",
"Features:DisplayName:GenerateDownloadUrl": "Generate Download Url",
"Features:Description:GenerateDownloadUrl": "Whether to allow users to generate download links for temporary files",
"BlobManagement:001001": "A blob container named {Name} already exists!",
"BlobManagement:001002": "The blob container is not empty and cannot be deleted!",
"BlobManagement:001003": "The blob container is the system container and cannot be deleted!",
@ -55,6 +58,8 @@
"Description:FileLimitLength": "Limit size of uploaded file in MB",
"DisplayName:AllowFileExtensions": "File extension",
"Description:AllowFileExtensions": "List of allowed extensions to upload files, with multiple extensions separated by, don't need a notation",
"DisplayName:GenerateDownloadUrlExpirySeconds": "Download Url Expiry Seconds",
"Description:GenerateDownloadUrlExpirySeconds": "Set the validity period for the temporary download link (in seconds). If not set or left blank, it defaults to 600 seconds.",
"DisplayName:BlobManagement": "Blob Management",
"DisplayName:Blobs": "Blobs",
"DisplayName:CreationTime": "Creation Time",

5
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/zh-Hans.json

@ -10,6 +10,7 @@
"Permission:Download": "下载",
"Permission:Blob:Delete": "删除存储对象",
"Permission:Blob:View": "查看存储对象",
"Permission:GenerateDownloadUrl": "生成下载链接",
"Features:BlobManagement": "对象存储",
"Features:DisplayName:EnableBlobManagement": "启用对象存储",
"Features:Description:EnableBlobManagement": "启用以使应用程序拥有对象存储模块的能力.",
@ -29,6 +30,8 @@
"Features:Description:UploadLimit": "在指定的周期内限制上传文件的总次数",
"Features:DisplayName:UploadInterval": "限制上传文件周期",
"Features:Description:UploadInterval": "限制上传文件次数的更新周期,时间刻度为:月,默认:1",
"Features:DisplayName:GenerateDownloadUrl": "生成文件下载链接",
"Features:Description:GenerateDownloadUrl": "是否允许用户生成临时文件下载链接",
"BlobManagement:001001": "已经存在名为 {Name} 的存储容器!",
"BlobManagement:001002": "存储容器非空,不可删除!",
"BlobManagement:001003": "存储容器为系统容器,不可删除!",
@ -55,6 +58,8 @@
"Description:FileLimitLength": "上传文件的限制大小,单位(MB)",
"DisplayName:AllowFileExtensions": "文件扩展名",
"Description:AllowFileExtensions": "允许的上传文件扩展名列表,多个扩展名以,分隔,无需输入.符号",
"DisplayName:GenerateDownloadUrlExpirySeconds": "生成临时下载链接有效期",
"Description:GenerateDownloadUrlExpirySeconds": "生成临时下载链接有效时间, 单位(s), 未设置或为空时默认600s.",
"DisplayName:BlobManagement": "对象存储",
"DisplayName:Blobs": "存储管理",
"DisplayName:CreationTime": "创建时间",

11
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingDefinitionProvider.cs

@ -37,6 +37,17 @@ public class BlobManagementSettingDefinitionProvider : SettingDefinitionProvider
ConfigurationSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
name: BlobManagementSettingNames.GenerateDownloadUrlExpirySeconds,
defaultValue: BlobManagementSettingNames.DefaultGenerateDownloadUrlExpirySeconds.ToString(),
displayName: L("DisplayName:GenerateDownloadUrlExpirySeconds"),
description: L("Description:GenerateDownloadUrlExpirySeconds"),
isVisibleToClients: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
ConfigurationSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
};
}

7
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingNames.cs

@ -11,7 +11,12 @@ public static class BlobManagementSettingNames
/// 允许的文件扩展名类型
/// </summary>
public const string AllowFileExtensions = GroupName + ".AllowFileExtensions";
/// <summary>
/// 生成临时下载链接有效期(s)
/// </summary>
public const string GenerateDownloadUrlExpirySeconds = GroupName + ".GenerateDownloadUrlExpirySeconds";
public const long DefaultFileLimitLength = 5L;
public const string DefaultAllowFileExtensions = "dll,zip,rar,txt,log,xml,config,json,jpeg,jpg,png,bmp,ico,xlsx,xltx,xls,xlt,docs,dots,doc,dot,pptx,potx,ppt,pot,chm";
public const double DefaultGenerateDownloadUrlExpirySeconds = 600d;
public const string DefaultAllowFileExtensions = "dll,zip,rar,txt,log,xml,config,json,jpeg,jpg,png,bmp,ico,xlsx,xltx,xls,xlt,docs,dots,doc,dot,pdf,pptx,potx,ppt,pot,chm";
}

42
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/Blob.cs

@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities.Auditing;
@ -67,6 +68,14 @@ public class Blob : AuditedAggregateRoot<Guid>, IMultiTenant
this.SetDefaultsForExtraProperties();
}
public void MoveTo(Blob parentBlob)
{
ValidateSubBlob(parentBlob, Name);
ParentId = parentBlob.Id;
parentBlob.Blobs.Add(this);
parentBlob.CalculateSize();
}
public void SetFullName(string fullName)
{
FullName = Check.NotNullOrWhiteSpace(fullName, nameof(fullName), BlobConsts.MaxFullNameLength);
@ -99,20 +108,7 @@ public class Blob : AuditedAggregateRoot<Guid>, IMultiTenant
BlobType type,
long? size = null)
{
if (Type != BlobType.Folder)
{
throw new BusinessException(
BlobManagementErrorCodes.Blob.NonFolderChildBlob,
"Sub-files/directories cannot be added to non-directory types!");
}
if (HasBlob(name))
{
throw new BusinessException(
BlobManagementErrorCodes.Blob.NameAlreadyExists,
$"There is already a file/directory named {name} in the current directory!")
.WithData("Name", name);
}
ValidateSubBlob(this, name);
var blob = new Blob(
id,
@ -183,4 +179,22 @@ public class Blob : AuditedAggregateRoot<Guid>, IMultiTenant
return blobPath.EnsureEndsWith('/') + blobName.RemovePreFix("/");
}
public static void ValidateSubBlob(Blob parentBlob, string subBlobName)
{
if (parentBlob.Type != BlobType.Folder)
{
throw new BusinessException(
BlobManagementErrorCodes.Blob.NonFolderChildBlob,
"Sub-files/directories cannot be added to non-directory types!");
}
if (parentBlob.HasBlob(subBlobName))
{
throw new BusinessException(
BlobManagementErrorCodes.Blob.NameAlreadyExists,
$"There is already a file/directory named {subBlobName} in the current directory!")
.WithData("Name", subBlobName);
}
}
}

71
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobContentTypeResolver.cs

@ -0,0 +1,71 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.BlobManagement;
public class BlobContentTypeResolver : IBlobContentTypeResolver, ITransientDependency
{
public ILogger<BlobContentTypeResolver> Logger { get; set; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected AbpBlobManagementContentTypeResolveOptions Options { get; }
public BlobContentTypeResolver(
IOptions<AbpBlobManagementContentTypeResolveOptions> options,
IServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
Options = options.Value;
Logger = NullLogger<BlobContentTypeResolver>.Instance;
}
public async virtual Task<string> ResolveContentTypeAsync(string blobName, Stream blobStream)
{
string? result = null;
Logger.LogDebug("Starting resolving content type...");
using (var serviceScope = ServiceScopeFactory.CreateScope())
{
var context = new BlobContentTypeResolveContext(serviceScope.ServiceProvider, blobName, blobStream);
foreach (var blobContentTypeResolver in Options.BlobContentTypeResolvers.OrderByDescending(x => x.Priority))
{
var originalPosition = context.Stream.CanSeek ? context.Stream.Position : -1;
Logger.LogDebug("Trying to resolve content type '{Name}'...", blobContentTypeResolver.Name);
await blobContentTypeResolver.ResolveAsync(context);
if (originalPosition >= 0 && context.Stream.CanSeek)
{
context.Stream.Position = originalPosition;
}
if (context.HasResolvedContentType())
{
result = context.ContentType;
Logger.LogDebug("Content type resolved by '{Name}' as '{ContentType}'.", blobContentTypeResolver.Name, result);
break;
}
}
}
if (result.IsNullOrWhiteSpace())
{
Logger.LogWarning("No provider can resolve content type. Using the default \"application/octet-stream\".");
return "application/octet-stream";
}
return result;
}
}

2
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDataSeeder.cs

@ -41,7 +41,7 @@ public class BlobDataSeeder : IBlobDataSeeder, ITransientDependency
{
Logger.LogInformation("Seeding container {containerName}...", containerName);
if (await BlobContainerRepository.FindByNameAsync(containerName) == null)
if (await BlobManager.FindContainerAsync(containerName) == null)
{
await BlobManager.CreateContainerAsync(containerName);
}

2
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadIncreaser.cs

@ -37,7 +37,7 @@ public class BlobDownloadIncreaser :
return;
}
var blobContainer = await _blobContainerRepository.FindByNameAsync(eventData.ContainerName);
var blobContainer = await _blobContainerRepository.FindByNameAsync(eventData.Provider, eventData.ContainerName);
if (blobContainer == null)
{
return;

19
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs

@ -0,0 +1,19 @@
using System;
namespace LINGYUN.Abp.BlobManagement;
public class BlobDownloadKeyCacheItem
{
public string Url { get; set; }
public Guid BlobId { get; set; }
public BlobDownloadKeyCacheItem()
{
}
public BlobDownloadKeyCacheItem(Guid blobId, string url)
{
BlobId = blobId;
Url = url;
}
}

108
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs

@ -1,4 +1,7 @@
using LINGYUN.Abp.BlobManagement.Settings;
using LINGYUN.Abp.BlobManagement.Features;
using LINGYUN.Abp.BlobManagement.Settings;
using LINGYUN.Abp.Features.LimitValidation;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.IO;
@ -10,6 +13,7 @@ using Volo.Abp.Caching;
using Volo.Abp.Content;
using Volo.Abp.Domain.Services;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Features;
using Volo.Abp.IO;
using Volo.Abp.Settings;
using Volo.Abp.Specifications;
@ -19,11 +23,13 @@ namespace LINGYUN.Abp.BlobManagement;
public class BlobManager : DomainService
{
private readonly IDistributedCache<BlobDownloadKeyCacheItem> _blobDownloadKeyCache;
private readonly IDistributedCache<BlobDownloadCacheItem> _blobDownloadCache;
private readonly IDistributedEventBus _distributedEventBus;
private readonly IMemoryCache _blobFileValidateCache;
private readonly ISettingProvider _settingProvider;
private readonly IBlobContentTypeResolver _blobContentTypeResolver;
private readonly IBlobProvider _blobProvider;
private readonly IBlobRepository _blobRepository;
private readonly IBlobContainerRepository _blobContainerRepository;
@ -36,7 +42,9 @@ public class BlobManager : DomainService
IBlobContainerRepository blobContainerRepository,
ICancellationTokenProvider cancellationTokenProvider,
IDistributedEventBus distributedEventBus,
IDistributedCache<BlobDownloadKeyCacheItem> blobDownloadKeyCache,
IDistributedCache<BlobDownloadCacheItem> blobDownloadCache,
IBlobContentTypeResolver blobContentTypeResolver,
IApplicationInfoAccessor applicationInfoAccessor,
IMemoryCache blobFileValidateCache,
ISettingProvider settingProvider)
@ -46,17 +54,24 @@ public class BlobManager : DomainService
_blobContainerRepository = blobContainerRepository;
_cancellationTokenProvider = cancellationTokenProvider;
_distributedEventBus = distributedEventBus;
_blobDownloadKeyCache = blobDownloadKeyCache;
_blobDownloadCache = blobDownloadCache;
_blobContentTypeResolver = blobContentTypeResolver;
_applicationInfoAccessor = applicationInfoAccessor;
_blobFileValidateCache = blobFileValidateCache;
_settingProvider = settingProvider;
}
public async virtual Task<BlobContainer> CreateContainerAsync(string name)
public async virtual Task<BlobContainer?> FindContainerAsync(string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
if (await _blobContainerRepository.FindByNameAsync(name) != null)
return await _blobContainerRepository.FindByNameAsync(GetBlobProvider(), name);
}
public async virtual Task<BlobContainer> CreateContainerAsync(string name)
{
if (await FindContainerAsync(name) != null)
{
throw new BusinessException(
BlobManagementErrorCodes.Container.NameAlreadyExists,
@ -71,7 +86,7 @@ public class BlobManager : DomainService
await _blobProvider.CreateContainerAsync(blobContainer.Name, GetCancellationToken());
blobContainer.SetProvider(_blobProvider.Name);
blobContainer.SetProvider(GetBlobProvider());
await _blobContainerRepository.InsertAsync(blobContainer);
@ -143,7 +158,7 @@ public class BlobManager : DomainService
{
await _blobProvider.CreateFolderAsync(blobContainer.Name, blob.FullName, GetCancellationToken());
blob.SetProvider(_blobProvider.Name);
blob.SetProvider(GetBlobProvider());
}
await _blobRepository.InsertAsync(blob);
@ -183,11 +198,15 @@ public class BlobManager : DomainService
}
}
[RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.UploadLimit,
BlobManagementFeatureNames.Blob.UploadInterval,
LimitPolicy.Month)]
public async virtual Task<Blob> UploadBlobAsync(
BlobContainer blobContainer,
Blob blob,
Stream stream,
string? contentType = null,
string? compareMd5 = null)
{
await ValidateUploadFileAsync(blob.Name, stream.Length);
@ -204,14 +223,22 @@ public class BlobManager : DomainService
blob.SetContentMd5(blobMd5);
}
await _blobProvider.UploadBlobAsync(blobContainer.Name, blob.FullName, stream, GetCancellationToken());
var contentType = await _blobContentTypeResolver.ResolveContentTypeAsync(blob.Name, stream);
await _blobProvider.UploadBlobAsync(
blobContainer.Name,
blob.FullName,
stream,
contentType,
GetCancellationToken());
blob.SetProvider(_blobProvider.Name);
blob.SetProvider(GetBlobProvider());
blob.SetFileInfo(contentType, stream.Length);
return blob;
}
[RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)]
public async virtual Task CreateChunkBlobAsync(
string containerIdOrName,
string name,
@ -270,7 +297,7 @@ public class BlobManager : DomainService
}
else
{
blobContainer = await _blobContainerRepository.GetByNameAsync(containerIdOrName);
blobContainer = await _blobContainerRepository.GetByNameAsync(GetBlobProvider(), containerIdOrName);
}
if (parentId.HasValue)
{
@ -285,6 +312,7 @@ public class BlobManager : DomainService
// 多分片文件流合并上传
mergeFileStream.Seek(0, SeekOrigin.Begin);
await UploadBlobAsync(blobContainer, blob, mergeFileStream, compareMd5);
}
// 文件保存之后删除临时文件目录
@ -299,6 +327,11 @@ public class BlobManager : DomainService
}
}
[RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.DownloadLimit,
BlobManagementFeatureNames.Blob.DownloadInterval,
LimitPolicy.Month)]
public async virtual Task<Stream?> DownloadBlobsync(Blob blob)
{
var containerName = await _blobContainerRepository.GetNameAsync(blob.ContainerId);
@ -306,6 +339,61 @@ public class BlobManager : DomainService
return await DownloadBlobsync(containerName, blob.FullName);
}
[RequiresFeature(BlobManagementFeatureNames.Blob.GenerateDownloadUrl)]
public async virtual Task<string> GenerateDownloadUrlAsync(
BlobContainer blobContainer,
Blob blob,
string fallbackDownloadUrl,
bool isAttachmentContent = true)
{
var cacheKey = $"{fallbackDownloadUrl.ToMd5()}";
var cacheItem = await _blobDownloadKeyCache.GetAsync(cacheKey);
if (cacheItem == null)
{
var downloadUrlExpirySeconds = await _settingProvider.GetAsync(
BlobManagementSettingNames.GenerateDownloadUrlExpirySeconds,
BlobManagementSettingNames.DefaultGenerateDownloadUrlExpirySeconds);
var downloadUrl = await _blobProvider.GeneratePresignedUrlAsync(
blobContainer.Name,
blob.FullName,
TimeSpan.FromSeconds(downloadUrlExpirySeconds),
isAttachmentContent,
GetCancellationToken());
if (downloadUrl.IsNullOrWhiteSpace())
{
// 特殊对象存储提供者(FileSystem)无法生成下载链接, 回退指定的请求Url
downloadUrl = fallbackDownloadUrl;
}
cacheItem = new BlobDownloadKeyCacheItem(
blob.Id,
downloadUrl);
await _blobDownloadKeyCache.SetAsync(
cacheKey,
cacheItem,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(downloadUrlExpirySeconds),
});
}
return cacheItem.Url;
}
public async virtual Task<Blob?> FindBlobByDownloadKeyAsync(string downloadKey)
{
var cacheItem = await _blobDownloadKeyCache.GetAsync(downloadKey);
if (cacheItem == null)
{
return null;
}
return await _blobRepository.FindAsync(cacheItem.BlobId);
}
public virtual string GetBlobProvider()
{
return _blobProvider.Name;
@ -322,7 +410,7 @@ public class BlobManager : DomainService
downloadCacheItem.DownloadCount += 1;
await _blobDownloadCache.SetAsync(downloadCacheKey, downloadCacheItem);
await _distributedEventBus.PublishAsync(new BlobDownloadEto(containerName, blobName, CurrentTenant.Id));
await _distributedEventBus.PublishAsync(new BlobDownloadEto(GetBlobProvider(), containerName, blobName, CurrentTenant.Id));
}
return blobStream;

2
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobTempCleanupService.cs

@ -50,7 +50,7 @@ public class BlobTempCleanupService : ITransientDependency
try
{
var tempBlobContainer = await BlobContainerRepository.FindByNameAsync("temp");
var tempBlobContainer = await BlobManager.FindContainerAsync("temp");
if (tempBlobContainer == null)
{
Logger.LogInformation($"No temp container found.");

1
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs

@ -14,6 +14,7 @@ public interface IBlobContainerRepository : IBasicRepository<BlobContainer, Guid
CancellationToken cancellationToken = default);
Task<BlobContainer?> FindByNameAsync(
string provider,
string name,
CancellationToken cancellationToken = default);

3
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepositoryExtenssions.cs

@ -8,10 +8,11 @@ public static class IBlobContainerRepositoryExtenssions
{
public async static Task<BlobContainer> GetByNameAsync(
this IBlobContainerRepository repository,
string provider,
string name,
CancellationToken cancellationToken = default)
{
return await repository.FindByNameAsync(name, cancellationToken)
return await repository.FindByNameAsync(provider, name, cancellationToken)
?? throw new BusinessException(
BlobManagementErrorCodes.Container.NameNotFound,
$"There is no blob container named {name}!")

9
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolver.cs

@ -0,0 +1,9 @@
using System.IO;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BlobManagement;
public interface IBlobContentTypeResolver
{
Task<string> ResolveContentTypeAsync(string blobName, Stream blobStream);
}

12
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs

@ -1,4 +1,6 @@
using System.IO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -21,6 +23,13 @@ public interface IBlobProvider
string blobName,
CancellationToken cancellationToken = default);
Task<string?> GeneratePresignedUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
bool isAttachmentContent = true,
CancellationToken cancellationToken = default);
Task CreateFolderAsync(
string containerName,
string blobName,
@ -35,6 +44,7 @@ public interface IBlobProvider
string containerName,
string blobName,
Stream stream,
string? contentType = null,
CancellationToken cancellationToken = default);
Task DeleteBlobAsync(

3
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs

@ -30,11 +30,12 @@ public class EfCoreBlobContainerRepository : EfCoreRepository<IBlobManagementDbC
}
public async virtual Task<BlobContainer?> FindByNameAsync(
string provider,
string name,
CancellationToken cancellationToken = default)
{
return await (await GetQueryableAsync())
.Where(x => x.Name == name)
.Where(x => x.Provider == provider && x.Name == name)
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
}

14
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs

@ -99,6 +99,17 @@ public class FileSystemBlobProvider : IBlobProvider
return Task.FromResult<Stream?>(File.OpenRead(fileName));
}
public virtual Task<string?> GeneratePresignedUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
bool isAttachmentContent = true,
CancellationToken cancellationToken = default)
{
// 本地文件系统无需生成下载链接
return Task.FromResult<string?>(null);
}
public virtual Task CreateFolderAsync(
string containerName,
string blobName,
@ -114,7 +125,8 @@ public class FileSystemBlobProvider : IBlobProvider
public async virtual Task UploadBlobAsync(
string containerName,
string blobName,
Stream content,
Stream content,
string? contentType = null,
CancellationToken cancellationToken = default)
{
var fileName = CalculateBlobName(NormalizeContainerName(containerName.RemovePostFix("/")), blobName);

14
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/AbpBlobManagementHttpApiModule.cs

@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.BlobManagement;
@ -26,4 +27,17 @@ public class AbpBlobManagementHttpApiModule : AbpModule
typeof(AbpBlobManagementApplicationContractsModule).Assembly);
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAspNetCoreContentOptions>(options =>
{
options.ContentTypeMaps.TryAdd(".log", "text/plain");
options.ContentTypeMaps.TryAdd(".text", "text/plain");
});
Configure<AbpBlobManagementContentTypeResolveOptions>(options =>
{
options.BlobContentTypeResolvers.Add(new FileExtensionBlobContentTypeResolveContributor());
});
}
}

43
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs

@ -1,5 +1,6 @@
using LINGYUN.Abp.BlobManagement.Dtos;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using System;
using System.Threading.Tasks;
using Volo.Abp;
@ -50,16 +51,34 @@ public class BlobController : BlobControllerBase, IBlobAppService
return _service.DeleteAsync(id);
}
[HttpGet("uid/{id}/content")]
public virtual Task<IRemoteStreamContent> GetContentAsync(Guid id)
[HttpGet("download/{id}")]
[HttpGet("download/t/{tenantId}/{id}")]
public virtual Task<IRemoteStreamContent> DownloadAsync(BlobDownloadByIdInput input)
{
return _service.GetContentAsync(id);
return _service.DownloadAsync(input);
}
[HttpGet("content")]
public virtual Task<IRemoteStreamContent> GetContentByNameAsync(BlobDownloadByNameInput input)
[HttpGet("preview/{id}")]
[HttpGet("preview/t/{tenantId}/{id}")]
public async virtual Task<IRemoteStreamContent> PreviewAsync(BlobDownloadByIdInput input)
{
return _service.GetContentByNameAsync(input);
var content = await _service.PreviewAsync(input);
if (!content.FileName.IsNullOrWhiteSpace())
{
var contentDisposition = new ContentDispositionHeaderValue("inline");
contentDisposition.SetHttpFileName(content.FileName);
HttpContext.Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();
}
return content;
}
[HttpGet("download/by-name/{name}")]
[HttpGet("download/by-name/t/{tenantId}/{name}")]
public virtual Task<IRemoteStreamContent> DownloadByNameAsync(BlobDownloadByNameInput input)
{
return _service.DownloadByNameAsync(input);
}
[HttpGet("{id}")]
@ -73,4 +92,16 @@ public class BlobController : BlobControllerBase, IBlobAppService
{
return _service.GetListAsync(input);
}
[HttpGet("generate/download-url/{id}")]
public virtual Task<string> GenerateDownloadUrlAsync(Guid id)
{
return _service.GenerateDownloadUrlAsync(id);
}
[HttpGet("generate/preview-url/{id}")]
public virtual Task<string> GeneratePreviewUrlAsync(Guid id)
{
return _service.GeneratePreviewUrlAsync(id);
}
}

24
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/FileExtensionBlobContentTypeResolveContributor.cs

@ -0,0 +1,24 @@
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BlobManagement;
public class FileExtensionBlobContentTypeResolveContributor : IBlobContentTypeResolveContributor
{
public string Name => "FileExtension";
public int Priority => 0;
public virtual Task ResolveAsync(BlobContentTypeResolveContext context)
{
var contentTypeProvider = context.ServiceProvider.GetService<IContentTypeProvider>();
if (contentTypeProvider != null &&
contentTypeProvider.TryGetContentType(context.BlobName, out var contentType) &&
!contentType.IsNullOrWhiteSpace())
{
context.Success(contentType);
}
return Task.CompletedTask;
}
}

4
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationController.cs

@ -40,8 +40,8 @@ public class BlobIntegrationController : AbpControllerBase, IBlobIntegrationServ
}
[HttpGet("download")]
public virtual Task<IRemoteStreamContent> GetContentAsync(BlobFileGetByNameIntegrationDto input)
public virtual Task<IRemoteStreamContent> DownloadAsync(BlobFileGetByNameIntegrationDto input)
{
return _service.GetContentAsync(input);
return _service.DownloadAsync(input);
}
}

12
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PrivateBlobController.cs

@ -50,16 +50,16 @@ public class PrivateBlobController : BlobControllerBase, IPrivateBlobAppService
return _service.DeleteAsync(id);
}
[HttpGet("uid/{id}/content")]
public virtual Task<IRemoteStreamContent> GetContentAsync(Guid id)
[HttpGet("download/{id}")]
public virtual Task<IRemoteStreamContent> DownloadAsync(Guid id)
{
return _service.GetContentAsync(id);
return _service.DownloadAsync(id);
}
[HttpGet("content/{name}")]
public virtual Task<IRemoteStreamContent> GetContentByNameAsync(string name)
[HttpGet("download/by-name/{name}")]
public virtual Task<IRemoteStreamContent> DownloadByNameAsync(string name)
{
return _service.GetContentByNameAsync(name);
return _service.DownloadByNameAsync(name);
}
[HttpGet("{id}")]

12
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PublicBlobController.cs

@ -22,16 +22,16 @@ public class PublicBlobController : BlobControllerBase, IPublicBlobAppService
_service = service;
}
[HttpGet("uid/{id}/content")]
public virtual Task<IRemoteStreamContent> GetContentAsync(Guid id)
[HttpGet("download/{id}")]
public virtual Task<IRemoteStreamContent> DownloadAsync(Guid id)
{
return _service.GetContentAsync(id);
return _service.DownloadAsync(id);
}
[HttpGet("content/{name}")]
public virtual Task<IRemoteStreamContent> GetContentByNameAsync(string name)
[HttpGet("download/by-name/{name}")]
public virtual Task<IRemoteStreamContent> DownloadByNameAsync(string name)
{
return _service.GetContentByNameAsync(name);
return _service.DownloadByNameAsync(name);
}
[HttpGet("{id}")]

3
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

25
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN.Abp.BlobManagement.MimeCheck.csproj

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>LINGYUN.Abp.BlobManagement.MimeCheck</AssemblyName>
<PackageId>LINGYUN.Abp.BlobManagement.MimeCheck</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<Nullable>enable</Nullable>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MimeCheck" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.BlobManagement.Domain\LINGYUN.Abp.BlobManagement.Domain.csproj" />
</ItemGroup>
</Project>

15
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/AbpBlobManagementMimeCheckModule.cs

@ -0,0 +1,15 @@
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.BlobManagement.MimeCheck;
[DependsOn(typeof(AbpBlobManagementDomainModule))]
public class AbpBlobManagementMimeCheckModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBlobManagementContentTypeResolveOptions>(options =>
{
options.BlobContentTypeResolvers.Add(new MimeCheckBlobContentTypeResolveContributor());
});
}
}

33
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/MimeCheckBlobContentTypeResolveContributor.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MimeCheck.Detection;
using MimeCheck.Validation;
using System;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BlobManagement.MimeCheck;
public class MimeCheckBlobContentTypeResolveContributor : IBlobContentTypeResolveContributor
{
public string Name => "MimeCheck";
public int Priority => 200;
public async virtual Task ResolveAsync(BlobContentTypeResolveContext context)
{
try
{
var streamResult = await MimeValidator.DetectAsync(context.Stream);
if (!streamResult.MimeType.IsNullOrWhiteSpace() &&
!string.Equals(streamResult.MimeType, DetectionResult.Unknown.MimeType))
{
context.Success(streamResult.MimeType);
}
}
catch (Exception ex)
{
context.ServiceProvider
.GetService<ILogger<MimeCheckBlobContentTypeResolveContributor>>()
?.LogWarning("Failed to decetion the blob mime type: {message}!", ex.Message);
}
}
}

67
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs

@ -4,6 +4,7 @@ using Minio;
using Minio.DataModel.Args;
using Minio.Exceptions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
@ -11,6 +12,7 @@ using System.Threading.Tasks;
using Volo.Abp.BlobStoring;
using Volo.Abp.BlobStoring.Minio;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.BlobManagement.Minio;
@ -19,17 +21,20 @@ public class MinioBlobProvider : IBlobProvider
public const string ProviderName = "Minio";
public string Name => ProviderName;
protected IClock Clock { get; }
protected ICurrentTenant CurrentTenant { get; }
protected IHttpClientFactory HttpClientFactory { get; }
protected MinioBlobNamingNormalizer BlobNamingNormalizer { get; }
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
public MinioBlobProvider(
IClock clock,
ICurrentTenant currentTenant,
IHttpClientFactory httpClientFactory,
MinioBlobNamingNormalizer blobNamingNormalizer,
IBlobContainerConfigurationProvider configurationProvider)
{
Clock = clock;
CurrentTenant = currentTenant;
HttpClientFactory = httpClientFactory;
BlobNamingNormalizer = blobNamingNormalizer;
@ -101,6 +106,30 @@ public class MinioBlobProvider : IBlobProvider
string containerName,
string blobName,
CancellationToken cancellationToken = default)
{
var configuration = GetBlobConfiguration();
var downloadUrl = await GeneratePresignedUrlAsync(
containerName,
blobName,
TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds),
cancellationToken: cancellationToken);
if (downloadUrl.IsNullOrWhiteSpace())
{
return null;
}
var httpClient = HttpClientFactory.CreateMinioHttpClient();
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
}
public async virtual Task<string?> GeneratePresignedUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
bool isAttachmentContent = true,
CancellationToken cancellationToken = default)
{
var client = GetMinioClient();
var bucket = NormalizeContainerName(containerName);
@ -111,17 +140,21 @@ public class MinioBlobProvider : IBlobProvider
return null;
}
var configuration = GetBlobConfiguration();
var downloadUrl = await client.PresignedGetObjectAsync(
new PresignedGetObjectArgs()
.WithBucket(bucket)
.WithObject(objectName)
.WithExpiry(configuration.PresignedGetExpirySeconds));
var httpClient = HttpClientFactory.CreateMinioHttpClient();
var presignedGetObjectArgs = new PresignedGetObjectArgs()
.WithBucket(bucket)
.WithObject(objectName)
.WithExpiry(expiration.TotalSeconds.To<int>());
var fileName = Path.GetFileName(blobName);
var type = isAttachmentContent ? "attachment" : "inline";
var disposition = $"{type}; filename=\"{Uri.EscapeDataString(fileName)}\"; " +
$"filename*=UTF-8''{Uri.EscapeDataString(fileName)}";
presignedGetObjectArgs.WithHeaders(new Dictionary<string, string>()
{
{ "response-content-disposition", disposition },
});
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
return await client.PresignedGetObjectAsync(presignedGetObjectArgs);
}
public virtual Task CreateFolderAsync(
@ -136,7 +169,8 @@ public class MinioBlobProvider : IBlobProvider
public async virtual Task UploadBlobAsync(
string containerName,
string blobName,
Stream content,
Stream content,
string? contentType = null,
CancellationToken cancellationToken = default)
{
var client = GetMinioClient();
@ -145,12 +179,17 @@ public class MinioBlobProvider : IBlobProvider
await CreateBucketIfNotExists(client, bucket);
await client.PutObjectAsync(new PutObjectArgs()
var putObjectArgs = new PutObjectArgs()
.WithBucket(bucket)
.WithObject(objectName)
.WithStreamData(content)
.WithObjectSize(content.Length),
cancellationToken);
.WithObjectSize(content.Length);
if (!contentType.IsNullOrWhiteSpace())
{
putObjectArgs.WithContentType(contentType);
}
await client.PutObjectAsync(putObjectArgs, cancellationToken);
}
protected virtual IMinioClient GetMinioClient()

5
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.SettingManagement/LINGYUN/Abp/BlobManagement/SettingManagement/BlobManagementSettingAppService.cs

@ -51,6 +51,11 @@ public class BlobManagementSettingAppService : ApplicationService, IBlobManageme
var ossObjectSetting = ossSettingGroup.AddSetting(L["DisplayName:Blobs"], L["DisplayName:Blobs"]);
ossObjectSetting.AddDetail(
await SettingDefinitionManager.GetAsync(BlobManagementSettingNames.GenerateDownloadUrlExpirySeconds),
StringLocalizerFactory,
await SettingManager.GetOrNullAsync(BlobManagementSettingNames.GenerateDownloadUrlExpirySeconds, providerName, providerKey),
ValueType.Number);
ossObjectSetting.AddDetail(
await SettingDefinitionManager.GetAsync(BlobManagementSettingNames.FileLimitLength),
StringLocalizerFactory,

59
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs

@ -6,13 +6,14 @@ using LINGYUN.Abp.BlobStoring.Tencent;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.BlobStoring;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.BlobManagement.Tencent;
@ -21,6 +22,7 @@ public class TencentBlobProvider : IBlobProvider
public const string ProviderName = "TencentCloud";
public string Name => ProviderName;
protected IClock Clock { get; }
protected IConfiguration Configuration { get; }
protected ICurrentTenant CurrentTenant { get; }
protected ICosClientFactory CosClientFactory { get; }
@ -29,6 +31,7 @@ public class TencentBlobProvider : IBlobProvider
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
public TencentBlobProvider(
IClock clock,
IConfiguration configuration,
ICurrentTenant currentTenant,
ICosClientFactory cosClientFactory,
@ -36,6 +39,7 @@ public class TencentBlobProvider : IBlobProvider
TencentBlobNamingNormalizer blobNamingNormalizer,
IBlobContainerConfigurationProvider configurationProvider)
{
Clock = clock;
Configuration = configuration;
CurrentTenant = currentTenant;
CosClientFactory = cosClientFactory;
@ -97,6 +101,30 @@ public class TencentBlobProvider : IBlobProvider
string containerName,
string blobName,
CancellationToken cancellationToken = default)
{
var configuration = await GetBlobConfiguration();
var downloadUrl = await GeneratePresignedUrlAsync(
containerName,
blobName,
TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds),
cancellationToken: cancellationToken);
if (downloadUrl.IsNullOrWhiteSpace())
{
return null;
}
var httpClient = HttpClientFactory.CreateTencentHttpClient();
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
}
public async virtual Task<string?> GeneratePresignedUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
bool isAttachmentContent = true,
CancellationToken cancellationToken = default)
{
var client = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName);
@ -108,8 +136,6 @@ public class TencentBlobProvider : IBlobProvider
}
var configuration = await GetBlobConfiguration();
var blobProviderConfig = Configuration.GetSection($"BlobManagement:{ProviderName}");
// See: https://cloud.tencent.com/document/product/436/47238
var preSignatureStruct = new PreSignatureStruct
{
@ -119,16 +145,20 @@ public class TencentBlobProvider : IBlobProvider
key = objectName, //对象键
httpMethod = "GET", //HTTP 请求方法
isHttps = true, //生成 HTTPS 请求 URL
signDurationSecond = blobProviderConfig.GetValue("SignDurationSecond", 600), //请求签名时间
signDurationSecond = expiration.TotalSeconds.To<long>(), //请求签名时间
headers = null, //签名中需要校验的 header
queryParameters = null //签名中需要校验的 URL 中请求参数
};
var fileName = Path.GetFileName(blobName);
var type = isAttachmentContent ? "attachment" : "inline";
var disposition = $"{type}; filename=\"{Uri.EscapeDataString(fileName)}\"; " +
$"filename*=UTF-8''{Uri.EscapeDataString(fileName)}";
preSignatureStruct.queryParameters = new Dictionary<string, string>
{
{ "response-content-disposition", disposition },
};
var downloadUrl = client.GenerateSignURL(preSignatureStruct);
var httpClient = HttpClientFactory.CreateTencentHttpClient();
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
return client.GenerateSignURL(preSignatureStruct);
}
public virtual Task CreateFolderAsync(
@ -144,7 +174,8 @@ public class TencentBlobProvider : IBlobProvider
public async virtual Task UploadBlobAsync(
string containerName,
string blobName,
Stream content,
Stream content,
string? contentType = null,
CancellationToken cancellationToken = default)
{
var client = await CreateClientAsync();
@ -153,7 +184,13 @@ public class TencentBlobProvider : IBlobProvider
CreateBucketIfNotExists(client, bucket);
client.PutObject(new PutObjectRequest(bucket, objectName, content));
var putObjectRequest = new PutObjectRequest(bucket, objectName, content);
if (!contentType.IsNullOrWhiteSpace())
{
putObjectRequest.SetRequestHeader("Content-Type", contentType);
}
client.PutObject(putObjectRequest);
}
protected async virtual Task<CosXml> CreateClientAsync()

2
aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj

@ -6,6 +6,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>LY.MicroService.Applications.Single</RootNamespace>
<UserSecretsId>A47DB958-CC3F-430B-A18A-57AA535BAB49</UserSecretsId>
<ImplicitUsings>enable</ImplicitUsings>
<Version>10.2.0</Version>
</PropertyGroup>
@ -151,6 +152,7 @@
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.Domain\LINGYUN.Abp.BlobManagement.Domain.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.FileSystem\LINGYUN.Abp.BlobManagement.FileSystem.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.HttpApi\LINGYUN.Abp.BlobManagement.HttpApi.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.MimeCheck\LINGYUN.Abp.BlobManagement.MimeCheck.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.Minio\LINGYUN.Abp.BlobManagement.Minio.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.Tencent\LINGYUN.Abp.BlobManagement.Tencent.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.SettingManagement\LINGYUN.Abp.BlobManagement.SettingManagement.csproj" />

3
aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs

@ -1,3 +1,4 @@
using LINGYUN.Abp.BlobManagement.MimeCheck;
using LINGYUN.Abp.Exporter.MiniSoftware;
using LINGYUN.Abp.Notifications.Calendar;
@ -130,6 +131,8 @@ namespace LY.MicroService.Applications.Single;
typeof(AbpBlobManagementMinioModule),
// 对象存储模块 腾讯云
typeof(AbpBlobManagementTencentModule),
// 对象存储模块 MimeCheck扩展
typeof(AbpBlobManagementMimeCheckModule),
// 对象存储模块 应用服务
typeof(AbpBlobManagementDomainModule),
// 对象存储模块 控制器

Loading…
Cancel
Save