diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60742282a..894e61f8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: - "**.csproj" env: - DOTNET_VERSION: "9.0.304" + DOTNET_VERSION: "9.0.307" jobs: build: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9020906b2..a6cd4b815 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,7 @@ on: pull_request: branches: [ main ] env: - DOTNET_VERSION: "9.0.304" + DOTNET_VERSION: "9.0.307" jobs: publish: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b68f94f9a..97e184479 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,4 +14,4 @@ jobs: with: repo_token: "${{ secrets.GITHUB_TOKEN }}" prerelease: false - automatic_release_tag: "9.3.5" + automatic_release_tag: "9.3.6" diff --git a/Directory.Packages.props b/Directory.Packages.props index aacc3e141..0df58d491 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,8 +3,8 @@ 8.3.5 2.15.2 3.3.5 - 9.3.5 - 9.3.5 + 9.3.6 + 9.3.6 9.0.5 9.0.5 9.0.5 @@ -12,7 +12,7 @@ - + @@ -282,6 +282,7 @@ + @@ -333,4 +334,4 @@ - \ No newline at end of file + diff --git a/README.md b/README.md index dbcc172d0..d1143f3dd 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ## Build -[![Build](https://github.com/colinin/abp-next-admin/actions/workflows/build.yml/badge.svg)](https://github.com/colinin/abp-next-admin/actions/workflows/build.yml) +[![Build](https://github.com/colinin/abp-next-admin/actions/workflows/build.yml/badge.svg)](https://github.com/colinin/abp-next-admin/actions/workflows/build.yml) [![NuGet](https://img.shields.io/nuget/v/LINGYUN.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/LINGYUN.Abp.Core) ## 部署方案 diff --git a/apps/vben5/apps/app-antd/package.json b/apps/vben5/apps/app-antd/package.json index efbe91733..6b1852596 100644 --- a/apps/vben5/apps/app-antd/package.json +++ b/apps/vben5/apps/app-antd/package.json @@ -28,6 +28,7 @@ "dependencies": { "@abp/account": "workspace:*", "@abp/auditing": "workspace:*", + "@abp/components": "workspace:*", "@abp/core": "workspace:*", "@abp/data-protection": "workspace:*", "@abp/demo": "workspace:*", diff --git a/apps/vben5/apps/app-antd/src/locales/index.ts b/apps/vben5/apps/app-antd/src/locales/index.ts index 7fe299ca0..434984cfd 100644 --- a/apps/vben5/apps/app-antd/src/locales/index.ts +++ b/apps/vben5/apps/app-antd/src/locales/index.ts @@ -13,6 +13,7 @@ import { } from '@vben/locales'; import { preferences } from '@vben/preferences'; +import { loadComponentMessages } from '@abp/components/locales'; import { useAbpStore } from '@abp/core'; import { useLocalizationsApi } from '@abp/localization'; import { loadPaltformMessages } from '@abp/platform'; @@ -35,16 +36,17 @@ const localesMap = loadLocalesMapFromDir( * @param lang */ async function loadMessages(lang: SupportedLanguagesType) { - const [appLocaleMessages, platformLocales, _, abpLocales] = await Promise.all( - [ + const [appLocaleMessages, compLocales, platformLocales, _, abpLocales] = + await Promise.all([ localesMap[lang]?.(), + loadComponentMessages(lang), loadPaltformMessages(lang), loadThirdPartyMessage(lang), loadAbpLocale(lang), - ], - ); + ]); return { ...appLocaleMessages?.default, + ...compLocales?.default, ...platformLocales?.default, ...abpLocales, }; diff --git a/apps/vben5/packages/@abp/account/package.json b/apps/vben5/packages/@abp/account/package.json index 0b92c7213..c742f9666 100644 --- a/apps/vben5/packages/@abp/account/package.json +++ b/apps/vben5/packages/@abp/account/package.json @@ -20,6 +20,7 @@ } }, "dependencies": { + "@abp/components": "workspace:*", "@abp/core": "workspace:*", "@abp/gdpr": "workspace:*", "@abp/identity": "workspace:*", diff --git a/apps/vben5/packages/@abp/account/src/components/components/BasicSettings.vue b/apps/vben5/packages/@abp/account/src/components/components/BasicSettings.vue index bacd00aea..9f80295fa 100644 --- a/apps/vben5/packages/@abp/account/src/components/components/BasicSettings.vue +++ b/apps/vben5/packages/@abp/account/src/components/components/BasicSettings.vue @@ -1,7 +1,4 @@ + + diff --git a/apps/vben5/packages/@abp/components/src/cropper/CropperAvatar.vue b/apps/vben5/packages/@abp/components/src/cropper/CropperAvatar.vue new file mode 100644 index 000000000..c420f8d9b --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/cropper/CropperAvatar.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/apps/vben5/packages/@abp/components/src/cropper/CropperModal.vue b/apps/vben5/packages/@abp/components/src/cropper/CropperModal.vue new file mode 100644 index 000000000..f8e263e56 --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/cropper/CropperModal.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/apps/vben5/packages/@abp/components/src/cropper/index.ts b/apps/vben5/packages/@abp/components/src/cropper/index.ts new file mode 100644 index 000000000..8708c5109 --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/cropper/index.ts @@ -0,0 +1,2 @@ +export { default as CropperAvatar } from './CropperAvatar.vue'; +export { default as CropperModal } from './CropperModal.vue'; diff --git a/apps/vben5/packages/@abp/components/src/cropper/types.ts b/apps/vben5/packages/@abp/components/src/cropper/types.ts new file mode 100644 index 000000000..e76cc6f8e --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/cropper/types.ts @@ -0,0 +1,8 @@ +import type Cropper from 'cropperjs'; + +export interface CropendResult { + imgBase64: string; + imgInfo: Cropper.Data; +} + +export type { Cropper }; diff --git a/apps/vben5/packages/@abp/components/src/locales/index.ts b/apps/vben5/packages/@abp/components/src/locales/index.ts new file mode 100644 index 000000000..de3bd1b24 --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/locales/index.ts @@ -0,0 +1,20 @@ +import type { SupportedLanguagesType } from '@vben/locales'; + +import { loadLocalesMapFromDir } from '@vben/locales'; + +const modules = import.meta.glob('./langs/**/*.json'); + +const localesMap = loadLocalesMapFromDir( + /\.\/langs\/([^/]+)\/(.*)\.json$/, + modules, +); + +/** + * 加载自定义组件本地化资源 + * @param lang 当前语言 + * @returns 资源集合 + */ +export async function loadComponentMessages(lang: SupportedLanguagesType) { + const locales = localesMap[lang]?.(); + return locales; +} diff --git a/apps/vben5/packages/@abp/components/src/locales/langs/en-US/cropper.json b/apps/vben5/packages/@abp/components/src/locales/langs/en-US/cropper.json new file mode 100644 index 000000000..5ad017f87 --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/locales/langs/en-US/cropper.json @@ -0,0 +1,14 @@ +{ + "confirmText": "Confirm and upload", + "title": "Avatar upload", + "selectImage": "Select Image", + "btn_rotate_left": "Counterclockwise rotation", + "btn_rotate_right": "Clockwise rotation", + "btn_scale_x": "Flip horizontal", + "btn_scale_y": "Flip vertical", + "btn_zoom_in": "Zoom in", + "btn_zoom_out": "Zoom out", + "btn_reset": "Reset", + "preview": "Preivew", + "uploadSuccess": "Uploaded success!" +} diff --git a/apps/vben5/packages/@abp/components/src/locales/langs/zh-CN/cropper.json b/apps/vben5/packages/@abp/components/src/locales/langs/zh-CN/cropper.json new file mode 100644 index 000000000..f3bba22eb --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/locales/langs/zh-CN/cropper.json @@ -0,0 +1,14 @@ +{ + "confirmText": "确认并上传", + "title": "头像上传", + "selectImage": "选择图片", + "btn_rotate_left": "逆时针旋转", + "btn_rotate_right": "顺时针旋转", + "btn_scale_x": "水平翻转", + "btn_scale_y": "垂直翻转", + "btn_zoom_in": "放大", + "btn_zoom_out": "缩小", + "btn_reset": "重置", + "preview": "预览", + "uploadSuccess": "上传成功!" +} diff --git a/apps/vben5/packages/@abp/core/src/utils/file.ts b/apps/vben5/packages/@abp/core/src/utils/file.ts new file mode 100644 index 000000000..559c36b7b --- /dev/null +++ b/apps/vben5/packages/@abp/core/src/utils/file.ts @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/** + * @description: base64 to blob + */ +export function dataURLtoBlob(base64Buf: string): Blob { + const arr = base64Buf.split(','); + const typeItem = arr[0]; + const mime = typeItem?.match(/:(.*?);/)?.[1]; + const bstr = window.atob(arr[1]!); + let n = bstr.length; + const u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.codePointAt(n)!; + } + return new Blob([u8arr], { type: mime }); +} + +/** + * img url to base64 + * @param url + */ +export function urlToBase64(url: string, mineType?: string): Promise { + return new Promise((resolve, reject) => { + let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null; + const ctx = canvas!.getContext('2d'); + + const img = new Image(); + img.crossOrigin = ''; + img.addEventListener('load', () => { + if (!canvas || !ctx) { + return reject(new Error('canvas or ctx is null!')); + } + canvas.height = img.height; + canvas.width = img.width; + ctx.drawImage(img, 0, 0); + const dataURL = canvas.toDataURL(mineType || 'image/png'); + canvas = null; + resolve(dataURL); + }); + img.src = url; + }); +} diff --git a/apps/vben5/packages/@abp/core/src/utils/index.ts b/apps/vben5/packages/@abp/core/src/utils/index.ts index 382c90803..b00fa27c5 100644 --- a/apps/vben5/packages/@abp/core/src/utils/index.ts +++ b/apps/vben5/packages/@abp/core/src/utils/index.ts @@ -1,5 +1,6 @@ export * from './array'; export * from './date'; +export * from './file'; export * from './is'; export * from './mitt'; export * from './regex'; diff --git a/apps/vben5/pnpm-workspace.yaml b/apps/vben5/pnpm-workspace.yaml index 42210ddfc..5bd673e69 100644 --- a/apps/vben5/pnpm-workspace.yaml +++ b/apps/vben5/pnpm-workspace.yaml @@ -90,6 +90,7 @@ catalog: codemirror: ^5.65.3 commitlint-plugin-function-rules: ^4.0.1 consola: ^3.4.2 + cropperjs: ^1.5.12 cross-env: ^7.0.3 cspell: ^8.19.3 cssnano: ^7.0.6 diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln index 708d441b2..e8de430b4 100644 --- a/aspnet-core/LINGYUN.MicroService.All.sln +++ b/aspnet-core/LINGYUN.MicroService.All.sln @@ -795,8 +795,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Web", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Web.IdentityServer", "modules\account\LINGYUN.Abp.Account.Web.IdentityServer\LINGYUN.Abp.Account.Web.IdentityServer.csproj", "{0FF0A04C-B580-4A56-9171-CF2988B5DE5A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Emailing", "modules\account\LINGYUN.Abp.Account.Emailing\LINGYUN.Abp.Account.Emailing.csproj", "{29A87F09-CC03-4DB8-B584-98073AB50AA4}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Web.OpenIddict", "modules\account\LINGYUN.Abp.Account.Web.OpenIddict\LINGYUN.Abp.Account.Web.OpenIddict.csproj", "{F810C8A8-1256-440F-BAAF-7F3588291963}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.HttpApi.Client", "modules\account\LINGYUN.Abp.Account.HttpApi.Client\LINGYUN.Abp.Account.HttpApi.Client.csproj", "{FFBE3EC6-F11B-4B7C-9BAF-AFBBB12BEF59}" @@ -849,6 +847,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Exporter.Pdf.Li EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Exporter.Pdf.SpireLib", "framework\exporter\LINGYUN.Abp.Exporter.Pdf.SpireLib\LINGYUN.Abp.Exporter.Pdf.SpireLib.csproj", "{9950639D-AA4C-4FF1-A65E-9790EB561C8A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Security", "modules\account\LINGYUN.Abp.Account.Security\LINGYUN.Abp.Account.Security.csproj", "{5FA85E8E-3276-43DF-CC93-6A9847905166}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.AspNetCore.MultiTenancy", "framework\tenants\LINGYUN.Abp.AspNetCore.MultiTenancy\LINGYUN.Abp.AspNetCore.MultiTenancy.csproj", "{AEEA81D6-B282-93CF-862B-9FCF1A5052F7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -2103,10 +2105,6 @@ Global {0FF0A04C-B580-4A56-9171-CF2988B5DE5A}.Debug|Any CPU.Build.0 = Debug|Any CPU {0FF0A04C-B580-4A56-9171-CF2988B5DE5A}.Release|Any CPU.ActiveCfg = Release|Any CPU {0FF0A04C-B580-4A56-9171-CF2988B5DE5A}.Release|Any CPU.Build.0 = Release|Any CPU - {29A87F09-CC03-4DB8-B584-98073AB50AA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29A87F09-CC03-4DB8-B584-98073AB50AA4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29A87F09-CC03-4DB8-B584-98073AB50AA4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29A87F09-CC03-4DB8-B584-98073AB50AA4}.Release|Any CPU.Build.0 = Release|Any CPU {F810C8A8-1256-440F-BAAF-7F3588291963}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F810C8A8-1256-440F-BAAF-7F3588291963}.Debug|Any CPU.Build.0 = Debug|Any CPU {F810C8A8-1256-440F-BAAF-7F3588291963}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -2203,6 +2201,14 @@ Global {9950639D-AA4C-4FF1-A65E-9790EB561C8A}.Debug|Any CPU.Build.0 = Debug|Any CPU {9950639D-AA4C-4FF1-A65E-9790EB561C8A}.Release|Any CPU.ActiveCfg = Release|Any CPU {9950639D-AA4C-4FF1-A65E-9790EB561C8A}.Release|Any CPU.Build.0 = Release|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Release|Any CPU.Build.0 = Release|Any CPU + {AEEA81D6-B282-93CF-862B-9FCF1A5052F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEEA81D6-B282-93CF-862B-9FCF1A5052F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEEA81D6-B282-93CF-862B-9FCF1A5052F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEEA81D6-B282-93CF-862B-9FCF1A5052F7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2588,7 +2594,6 @@ Global {AE00DA82-B33A-CAF7-D9CD-D5E26608741B} = {9D1302BE-3886-49F8-B0CD-35D2AC1E5A37} {F9A0D88F-53AE-4AC7-8E15-163C34386E7C} = {9E72FEB9-A626-4312-892B-CDD043879758} {0FF0A04C-B580-4A56-9171-CF2988B5DE5A} = {9E72FEB9-A626-4312-892B-CDD043879758} - {29A87F09-CC03-4DB8-B584-98073AB50AA4} = {9E72FEB9-A626-4312-892B-CDD043879758} {F810C8A8-1256-440F-BAAF-7F3588291963} = {9E72FEB9-A626-4312-892B-CDD043879758} {FFBE3EC6-F11B-4B7C-9BAF-AFBBB12BEF59} = {9E72FEB9-A626-4312-892B-CDD043879758} {C95DE287-9D21-4DCD-9281-A060B6D99774} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} @@ -2615,6 +2620,8 @@ Global {546E4417-5409-40F4-A125-E08329DD82BB} = {A4633711-7FB6-411A-8D08-BB9A0A778046} {738A72FB-ED83-4127-AA3B-59BF90635F8F} = {A4633711-7FB6-411A-8D08-BB9A0A778046} {9950639D-AA4C-4FF1-A65E-9790EB561C8A} = {A4633711-7FB6-411A-8D08-BB9A0A778046} + {5FA85E8E-3276-43DF-CC93-6A9847905166} = {9E72FEB9-A626-4312-892B-CDD043879758} + {AEEA81D6-B282-93CF-862B-9FCF1A5052F7} = {A5543E56-DA53-494D-A531-DA75091D46FF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718} diff --git a/aspnet-core/LINGYUN.MicroService.SingleProject.sln b/aspnet-core/LINGYUN.MicroService.SingleProject.sln index eb0a92875..0952778d2 100644 --- a/aspnet-core/LINGYUN.MicroService.SingleProject.sln +++ b/aspnet-core/LINGYUN.MicroService.SingleProject.sln @@ -642,8 +642,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Gdpr.EntityFram EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Gdpr.Domain.Identity", "modules\gdpr\LINGYUN.Abp.Gdpr.Domain.Identity\LINGYUN.Abp.Gdpr.Domain.Identity.csproj", "{06FC3141-5F92-43A6-94C9-E2519EE9A91E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Emailing", "modules\account\LINGYUN.Abp.Account.Emailing\LINGYUN.Abp.Account.Emailing.csproj", "{9D53FA98-6BF1-AEF9-8CC3-949E24C03B76}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Web", "modules\account\LINGYUN.Abp.Account.Web\LINGYUN.Abp.Account.Web.csproj", "{4E3CE014-38BF-FF33-A107-B268E57CE8E9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Web.IdentityServer", "modules\account\LINGYUN.Abp.Account.Web.IdentityServer\LINGYUN.Abp.Account.Web.IdentityServer.csproj", "{57A61286-DE14-9313-5BAB-B077C6239377}" @@ -694,6 +692,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.OAuth", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Elsa.Designer", "modules\elsa\LINGYUN.Abp.Elsa.Designer\LINGYUN.Abp.Elsa.Designer.csproj", "{C9756AD3-3AEA-4AA8-99E3-8305D37E0903}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat.Work.ExternalContact", "framework\wechat\LINGYUN.Abp.WeChat.Work.ExternalContact\LINGYUN.Abp.WeChat.Work.ExternalContact.csproj", "{B4311504-B0C8-AC8D-02A5-81AD43B4087B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Security", "modules\account\LINGYUN.Abp.Account.Security\LINGYUN.Abp.Account.Security.csproj", "{5FA85E8E-3276-43DF-CC93-6A9847905166}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1748,10 +1750,6 @@ Global {06FC3141-5F92-43A6-94C9-E2519EE9A91E}.Debug|Any CPU.Build.0 = Debug|Any CPU {06FC3141-5F92-43A6-94C9-E2519EE9A91E}.Release|Any CPU.ActiveCfg = Release|Any CPU {06FC3141-5F92-43A6-94C9-E2519EE9A91E}.Release|Any CPU.Build.0 = Release|Any CPU - {9D53FA98-6BF1-AEF9-8CC3-949E24C03B76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9D53FA98-6BF1-AEF9-8CC3-949E24C03B76}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9D53FA98-6BF1-AEF9-8CC3-949E24C03B76}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9D53FA98-6BF1-AEF9-8CC3-949E24C03B76}.Release|Any CPU.Build.0 = Release|Any CPU {4E3CE014-38BF-FF33-A107-B268E57CE8E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E3CE014-38BF-FF33-A107-B268E57CE8E9}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E3CE014-38BF-FF33-A107-B268E57CE8E9}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1848,6 +1846,14 @@ Global {C9756AD3-3AEA-4AA8-99E3-8305D37E0903}.Debug|Any CPU.Build.0 = Debug|Any CPU {C9756AD3-3AEA-4AA8-99E3-8305D37E0903}.Release|Any CPU.ActiveCfg = Release|Any CPU {C9756AD3-3AEA-4AA8-99E3-8305D37E0903}.Release|Any CPU.Build.0 = Release|Any CPU + {B4311504-B0C8-AC8D-02A5-81AD43B4087B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4311504-B0C8-AC8D-02A5-81AD43B4087B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4311504-B0C8-AC8D-02A5-81AD43B4087B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4311504-B0C8-AC8D-02A5-81AD43B4087B}.Release|Any CPU.Build.0 = Release|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2160,7 +2166,6 @@ Global {CDC0F589-D716-4FCE-9BBA-CD1A0B3D8409} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {57C95347-CF48-43DD-BFC9-597A43041AC5} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {06FC3141-5F92-43A6-94C9-E2519EE9A91E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {9D53FA98-6BF1-AEF9-8CC3-949E24C03B76} = {4F837B81-EA7D-472A-8482-3D5A730DF810} {4E3CE014-38BF-FF33-A107-B268E57CE8E9} = {4F837B81-EA7D-472A-8482-3D5A730DF810} {57A61286-DE14-9313-5BAB-B077C6239377} = {4F837B81-EA7D-472A-8482-3D5A730DF810} {44DE8AC4-D7AC-C71C-9C77-4A1C0137D3E3} = {4F837B81-EA7D-472A-8482-3D5A730DF810} @@ -2186,6 +2191,8 @@ Global {2379F502-BBBD-4BF2-91F7-D0E5C61E91B7} = {4F837B81-EA7D-472A-8482-3D5A730DF810} {2E4C437A-989D-68D9-C5FB-1AE085B2CBC8} = {4F837B81-EA7D-472A-8482-3D5A730DF810} {C9756AD3-3AEA-4AA8-99E3-8305D37E0903} = {07DFEB1E-ED92-4E97-A801-FAB2D70F4F35} + {B4311504-B0C8-AC8D-02A5-81AD43B4087B} = {91867618-0D86-4410-91C6-B1166A9ACDF9} + {5FA85E8E-3276-43DF-CC93-6A9847905166} = {4F837B81-EA7D-472A-8482-3D5A730DF810} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {711A43C0-A2F8-4E5C-9B9F-F2551E4B3FF1} diff --git a/aspnet-core/LINGYUN.MicroService.TaskManagement.sln b/aspnet-core/LINGYUN.MicroService.TaskManagement.sln index 371275b10..f25619db0 100644 --- a/aspnet-core/LINGYUN.MicroService.TaskManagement.sln +++ b/aspnet-core/LINGYUN.MicroService.TaskManagement.sln @@ -186,6 +186,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Saas.EntityFram EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.MultiTenancy.Saas", "modules\saas\LINGYUN.Abp.MultiTenancy.Saas\LINGYUN.Abp.MultiTenancy.Saas.csproj", "{E09374FE-6B13-D69E-B171-94C63E6B80E5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Telemetry.OpenTelemetry", "framework\telemetry\LINGYUN.Abp.Telemetry.OpenTelemetry\LINGYUN.Abp.Telemetry.OpenTelemetry.csproj", "{164E4514-4E84-F13A-E24E-2A743753CDB3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -480,6 +482,10 @@ Global {E09374FE-6B13-D69E-B171-94C63E6B80E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {E09374FE-6B13-D69E-B171-94C63E6B80E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {E09374FE-6B13-D69E-B171-94C63E6B80E5}.Release|Any CPU.Build.0 = Release|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -557,6 +563,7 @@ Global {618C7EAF-8D6C-50B5-84E4-AE733CEA4A8C} = {DE7B8B71-635D-4113-AA49-B218D6998CEC} {F58A7141-AC0D-26EC-F22A-DF0DB1BA6955} = {DE7B8B71-635D-4113-AA49-B218D6998CEC} {E09374FE-6B13-D69E-B171-94C63E6B80E5} = {DE7B8B71-635D-4113-AA49-B218D6998CEC} + {164E4514-4E84-F13A-E24E-2A743753CDB3} = {5A41C31A-B966-418B-B446-5BA1D7E61A62} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E1FD1F4C-D344-408B-97CF-B6F1F6D7D293} diff --git a/aspnet-core/LINGYUN.MicroService.WebhooksManagement.sln b/aspnet-core/LINGYUN.MicroService.WebhooksManagement.sln index 8c77ae6e5..f3e3bb9ff 100644 --- a/aspnet-core/LINGYUN.MicroService.WebhooksManagement.sln +++ b/aspnet-core/LINGYUN.MicroService.WebhooksManagement.sln @@ -173,6 +173,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Quartz.SqlServe EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Quartz.SqlInstaller", "modules\task-management\LINGYUN.Abp.Quartz.SqlInstaller\LINGYUN.Abp.Quartz.SqlInstaller.csproj", "{0EEA73CA-D8DD-F044-045D-F070F8831E26}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Telemetry.OpenTelemetry", "framework\telemetry\LINGYUN.Abp.Telemetry.OpenTelemetry\LINGYUN.Abp.Telemetry.OpenTelemetry.csproj", "{164E4514-4E84-F13A-E24E-2A743753CDB3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -447,6 +449,10 @@ Global {0EEA73CA-D8DD-F044-045D-F070F8831E26}.Debug|Any CPU.Build.0 = Debug|Any CPU {0EEA73CA-D8DD-F044-045D-F070F8831E26}.Release|Any CPU.ActiveCfg = Release|Any CPU {0EEA73CA-D8DD-F044-045D-F070F8831E26}.Release|Any CPU.Build.0 = Release|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -533,6 +539,7 @@ Global {B6452D3F-E58C-C433-E52A-F65EE2657E01} = {8C3DF571-BAC3-48C4-B46A-AC0E0EAA9871} {D613F393-9CEE-2D3B-33C9-90630F8348E7} = {8C3DF571-BAC3-48C4-B46A-AC0E0EAA9871} {0EEA73CA-D8DD-F044-045D-F070F8831E26} = {8C3DF571-BAC3-48C4-B46A-AC0E0EAA9871} + {164E4514-4E84-F13A-E24E-2A743753CDB3} = {CE07B9F4-54E8-4E74-BE14-8E5C1FB7AFC8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {80ED12A5-C899-459F-A181-ADCC9D680DE5} diff --git a/aspnet-core/LINGYUN.MicroService.WechatManagement.sln b/aspnet-core/LINGYUN.MicroService.WechatManagement.sln index aeee78091..22ef88c5e 100644 --- a/aspnet-core/LINGYUN.MicroService.WechatManagement.sln +++ b/aspnet-core/LINGYUN.MicroService.WechatManagement.sln @@ -111,6 +111,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.Sessio EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Telemetry.SkyWalking", "framework\telemetry\LINGYUN.Abp.Telemetry.SkyWalking\LINGYUN.Abp.Telemetry.SkyWalking.csproj", "{7CF83493-6AF5-9C6D-01A7-AC7FC11BC2CE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Telemetry.OpenTelemetry", "framework\telemetry\LINGYUN.Abp.Telemetry.OpenTelemetry\LINGYUN.Abp.Telemetry.OpenTelemetry.csproj", "{164E4514-4E84-F13A-E24E-2A743753CDB3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Identity.WeChat.Work", "framework\wechat\LINGYUN.Abp.Identity.WeChat.Work\LINGYUN.Abp.Identity.WeChat.Work.csproj", "{CCC8A29C-1FC1-044F-895A-EC6894CDC8E5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat.Work.ExternalContact", "framework\wechat\LINGYUN.Abp.WeChat.Work.ExternalContact\LINGYUN.Abp.WeChat.Work.ExternalContact.csproj", "{3A6D0EA9-FA3F-4252-87D9-31845D57180D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.TestsBase", "tests\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj", "{757279EC-708D-D1AD-5E9E-7B757F3F7785}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat.Work.Tests", "tests\LINGYUN.Abp.WeChat.Work.Tests\LINGYUN.Abp.WeChat.Work.Tests.csproj", "{9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat.Work.ExternalContact.Tests", "tests\LINGYUN.Abp.WeChat.Work.ExternalContact.Tests\LINGYUN.Abp.WeChat.Work.ExternalContact.Tests.csproj", "{6628FB84-66C0-4023-9AA0-AAC4C0CC5917}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat.Work.Handlers", "framework\wechat\LINGYUN.Abp.WeChat.Work.Handlers\LINGYUN.Abp.WeChat.Work.Handlers.csproj", "{E0DFCAD9-8AFE-A816-10F7-B4CA6691E910}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -281,6 +297,34 @@ Global {7CF83493-6AF5-9C6D-01A7-AC7FC11BC2CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {7CF83493-6AF5-9C6D-01A7-AC7FC11BC2CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {7CF83493-6AF5-9C6D-01A7-AC7FC11BC2CE}.Release|Any CPU.Build.0 = Release|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Release|Any CPU.Build.0 = Release|Any CPU + {CCC8A29C-1FC1-044F-895A-EC6894CDC8E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCC8A29C-1FC1-044F-895A-EC6894CDC8E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCC8A29C-1FC1-044F-895A-EC6894CDC8E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCC8A29C-1FC1-044F-895A-EC6894CDC8E5}.Release|Any CPU.Build.0 = Release|Any CPU + {3A6D0EA9-FA3F-4252-87D9-31845D57180D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A6D0EA9-FA3F-4252-87D9-31845D57180D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A6D0EA9-FA3F-4252-87D9-31845D57180D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A6D0EA9-FA3F-4252-87D9-31845D57180D}.Release|Any CPU.Build.0 = Release|Any CPU + {757279EC-708D-D1AD-5E9E-7B757F3F7785}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {757279EC-708D-D1AD-5E9E-7B757F3F7785}.Debug|Any CPU.Build.0 = Debug|Any CPU + {757279EC-708D-D1AD-5E9E-7B757F3F7785}.Release|Any CPU.ActiveCfg = Release|Any CPU + {757279EC-708D-D1AD-5E9E-7B757F3F7785}.Release|Any CPU.Build.0 = Release|Any CPU + {9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39}.Release|Any CPU.Build.0 = Release|Any CPU + {6628FB84-66C0-4023-9AA0-AAC4C0CC5917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6628FB84-66C0-4023-9AA0-AAC4C0CC5917}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6628FB84-66C0-4023-9AA0-AAC4C0CC5917}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6628FB84-66C0-4023-9AA0-AAC4C0CC5917}.Release|Any CPU.Build.0 = Release|Any CPU + {E0DFCAD9-8AFE-A816-10F7-B4CA6691E910}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0DFCAD9-8AFE-A816-10F7-B4CA6691E910}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0DFCAD9-8AFE-A816-10F7-B4CA6691E910}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0DFCAD9-8AFE-A816-10F7-B4CA6691E910}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -337,6 +381,13 @@ Global {CC8DDC8C-CC0C-4534-8D9F-2C345E065869} = {52701ECE-3EBD-45EC-AD2C-0AAB15322311} {80EBBECC-EF11-4E5E-91DA-EEECED832F21} = {73ED64BB-7C39-42EA-B821-3DD697B9C36A} {7CF83493-6AF5-9C6D-01A7-AC7FC11BC2CE} = {FFACB4F0-33E0-4F8B-A97E-8FFFA10C12E6} + {164E4514-4E84-F13A-E24E-2A743753CDB3} = {FFACB4F0-33E0-4F8B-A97E-8FFFA10C12E6} + {CCC8A29C-1FC1-044F-895A-EC6894CDC8E5} = {EDBB7BC1-46F0-4803-A572-7F8FEF433BE2} + {3A6D0EA9-FA3F-4252-87D9-31845D57180D} = {EDBB7BC1-46F0-4803-A572-7F8FEF433BE2} + {757279EC-708D-D1AD-5E9E-7B757F3F7785} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {6628FB84-66C0-4023-9AA0-AAC4C0CC5917} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {E0DFCAD9-8AFE-A816-10F7-B4CA6691E910} = {EDBB7BC1-46F0-4803-A572-7F8FEF433BE2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {EC9D01C1-EA3C-48C7-A279-4D35C8AD312E} diff --git a/aspnet-core/LINGYUN.MicroService.Workflow.sln b/aspnet-core/LINGYUN.MicroService.Workflow.sln index 18ca08446..1d2e9392f 100644 --- a/aspnet-core/LINGYUN.MicroService.Workflow.sln +++ b/aspnet-core/LINGYUN.MicroService.Workflow.sln @@ -195,6 +195,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Quartz.SqlServe EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Quartz.SqlInstaller", "modules\task-management\LINGYUN.Abp.Quartz.SqlInstaller\LINGYUN.Abp.Quartz.SqlInstaller.csproj", "{0EEA73CA-D8DD-F044-045D-F070F8831E26}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Telemetry.OpenTelemetry", "framework\telemetry\LINGYUN.Abp.Telemetry.OpenTelemetry\LINGYUN.Abp.Telemetry.OpenTelemetry.csproj", "{164E4514-4E84-F13A-E24E-2A743753CDB3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -513,6 +515,10 @@ Global {0EEA73CA-D8DD-F044-045D-F070F8831E26}.Debug|Any CPU.Build.0 = Debug|Any CPU {0EEA73CA-D8DD-F044-045D-F070F8831E26}.Release|Any CPU.ActiveCfg = Release|Any CPU {0EEA73CA-D8DD-F044-045D-F070F8831E26}.Release|Any CPU.Build.0 = Release|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {164E4514-4E84-F13A-E24E-2A743753CDB3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -602,6 +608,7 @@ Global {B5C96536-6387-44F4-A2E2-D4D683588AF5} = {7844FF58-7DBF-46E1-88B7-9764382A4EE9} {52F5A9DE-50B8-42C6-9980-E908DC6A4B52} = {7844FF58-7DBF-46E1-88B7-9764382A4EE9} {0EEA73CA-D8DD-F044-045D-F070F8831E26} = {7844FF58-7DBF-46E1-88B7-9764382A4EE9} + {164E4514-4E84-F13A-E24E-2A743753CDB3} = {6DA78E72-BA55-4ECF-97DB-6258174D3E2A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6BB7A5DE-DA12-44DC-BC9B-0F6CA524346F} diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun.SettingManagement/LINGYUN/Abp/Aliyun/SettingManagement/AliyunSettingAppService.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun.SettingManagement/LINGYUN/Abp/Aliyun/SettingManagement/AliyunSettingAppService.cs index 1d9bcc629..08ddb96d7 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun.SettingManagement/LINGYUN/Abp/Aliyun/SettingManagement/AliyunSettingAppService.cs +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun.SettingManagement/LINGYUN/Abp/Aliyun/SettingManagement/AliyunSettingAppService.cs @@ -152,6 +152,26 @@ public class AliyunSettingAppService : ApplicationService, IAliyunSettingAppServ await SettingManager.GetOrNullAsync(AliyunSettingNames.Sms.VisableErrorToClient, providerName, providerKey), ValueType.Boolean, providerName); + + var smsVerifyCodeSetting = aliyunSettingGroup.AddSetting(L["DisplayName:Aliyun.SmsVerifyCode"], L["Description:Aliyun.SmsVerifyCode"]); + smsVerifyCodeSetting.AddDetail( + await SettingDefinitionManager.GetAsync(AliyunSettingNames.SmsVerifyCode.Domain), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(AliyunSettingNames.SmsVerifyCode.Domain, providerName, providerKey), + ValueType.String, + providerName); + smsVerifyCodeSetting.AddDetail( + await SettingDefinitionManager.GetAsync(AliyunSettingNames.SmsVerifyCode.DefaultSignName), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(AliyunSettingNames.SmsVerifyCode.DefaultSignName, providerName, providerKey), + ValueType.String, + providerName); + smsVerifyCodeSetting.AddDetail( + await SettingDefinitionManager.GetAsync(AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode, providerName, providerKey), + ValueType.String, + providerName); } #endregion diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/en.json b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/en.json index 314098cdf..0be8fdf50 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/en.json +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/en.json @@ -31,22 +31,6 @@ "DisplayName:DurationSeconds": "Duration Seconds,in seconds", "Description:DurationSeconds": "Duration Seconds,in seconds", "DisplayName:Policy": "Policy", - "Description:Policy": "Policy", - "DisplayName:Aliyun.Sms": "Sms", - "Description:Aliyun.Sms": "Sms", - "DisplayName:ActionName": "Action Name", - "Description:ActionName": "Action Name", - "DisplayName:DefaultSignName": "Default Sign Name", - "Description:DefaultSignName": "Default Sign Name", - "DisplayName:DefaultTemplateCode": "Default Template Code", - "Description:DefaultTemplateCode": "Default Template Code", - "DisplayName:DefaultPhoneNumber": "Default Phone Number", - "Description:DefaultPhoneNumber": "Default Phone Number", - "DisplayName:Domain": "Domain", - "Description:Domain": "Domain", - "DisplayName:Version": "Version", - "Description:Version": "Version", - "DisplayName:VisableErrorToClient": "Visable Error To Client", - "Description:VisableErrorToClient": "Visable Error To Client" + "Description:Policy": "Policy" } } \ No newline at end of file diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/zh-Hans.json b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/zh-Hans.json index 1e9a8b872..55c21c327 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/zh-Hans.json +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Localization/Resources/zh-Hans.json @@ -32,22 +32,6 @@ "Description:DurationSeconds": "过期时间最小值为900秒,默认3600秒", "DisplayName:Policy": "权限策略", "Description:Policy": "生成STS Token时可以指定一个额外的权限策略,以进一步限制STS Token的权限", - "DisplayName:Aliyun.Sms": "短信服务", - "Description:Aliyun.Sms": "阿里云短信服务", - "DisplayName:ActionName": "发送短信方法", - "Description:ActionName": "发送短信方法名称,详情见阿里云Sms服务", - "DisplayName:DefaultSignName": "默认短信签名", - "Description:DefaultSignName": "当用户未指定短信签名时的默认签名名称", - "DisplayName:DefaultTemplateCode": "默认短信模板号", - "Description:DefaultTemplateCode": "当用户未指定短信签名时的默认短信模板号", - "DisplayName:DefaultPhoneNumber": "默认接收短信手机号", - "Description:DefaultPhoneNumber": "当用户未指定短信接收方时的默认接收手机号码", - "DisplayName:Domain": "阿里云sms服务域名", - "Description:Domain": "阿里云sms服务域名", - "DisplayName:Version": "阿里云sms服务版本号", - "Description:Version": "阿里云sms服务版本号", - "DisplayName:VisableErrorToClient": "发送错误到客户端", - "Description:VisableErrorToClient": "当短信服务发送出现错误时是否发送错误详情到客户端", "Region:HangZhou": "华东1(杭州)", "Region:ShangHai": "华东2(上海)", "Region:NanJing": "华东5(南京-本地地域)", diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingNames.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingNames.cs index 0220a6ae2..855101c27 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingNames.cs +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingNames.cs @@ -79,4 +79,24 @@ public static class AliyunSettingNames /// public const string VisableErrorToClient = Prefix + ".VisableErrorToClient"; } + + /// + /// 云通信号码认证服务 + /// + public class SmsVerifyCode + { + public const string Prefix = AliyunSettingNames.Prefix + ".SmsVerifyCode"; + /// + /// 阿里云号码认证服务域名 + /// + public const string Domain = Prefix + ".Domain"; + /// + /// 默认签名 + /// + public const string DefaultSignName = Prefix + ".DefaultSignName"; + /// + /// 默认短信模板号 + /// + public const string DefaultTemplateCode = Prefix + ".DefaultTemplateCode"; + } } diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs index 55713d7af..5e5f98805 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs @@ -10,6 +10,7 @@ public class AliyunSettingProvider : SettingDefinitionProvider { context.Add(GetAuthorizationSettings()); context.Add(GetSmsSettings()); + context.Add(GetSmsVerifyCodeSettings()); } private SettingDefinition[] GetAuthorizationSettings() @@ -169,8 +170,8 @@ public class AliyunSettingProvider : SettingDefinitionProvider new SettingDefinition( AliyunSettingNames.Sms.Domain, defaultValue: "dysmsapi.aliyuncs.com", - displayName: L("DisplayName:Domain"), - description: L("Description:Domain"), + displayName: L("DisplayName:SmsDomain"), + description: L("Description:SmsDomain"), isVisibleToClients: false ) .WithProviders( @@ -204,6 +205,49 @@ public class AliyunSettingProvider : SettingDefinitionProvider TenantSettingValueProvider.ProviderName) }; } + + private SettingDefinition[] GetSmsVerifyCodeSettings() + { + return new SettingDefinition[] + { + new SettingDefinition( + AliyunSettingNames.SmsVerifyCode.Domain, + defaultValue: "dypnsapi.aliyuncs.com", + displayName: L("DisplayName:SmsVerifyCodeDomain"), + description: L("Description:SmsVerifyCodeDomain"), + isVisibleToClients: false + ) + .WithProviders( + DefaultValueSettingValueProvider.ProviderName, + ConfigurationSettingValueProvider.ProviderName, + GlobalSettingValueProvider.ProviderName, + TenantSettingValueProvider.ProviderName), + new SettingDefinition( + AliyunSettingNames.SmsVerifyCode.DefaultSignName, + displayName: L("DisplayName:DefaultSignName"), + description: L("Description:DefaultSignName"), + isVisibleToClients: false, + isEncrypted: true + ) + .WithProviders( + DefaultValueSettingValueProvider.ProviderName, + ConfigurationSettingValueProvider.ProviderName, + GlobalSettingValueProvider.ProviderName, + TenantSettingValueProvider.ProviderName), + new SettingDefinition( + AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode, + displayName: L("DisplayName:DefaultTemplateCode"), + description: L("Description:DefaultTemplateCode"), + isVisibleToClients: false, + isEncrypted: true + ) + .WithProviders( + DefaultValueSettingValueProvider.ProviderName, + ConfigurationSettingValueProvider.ProviderName, + GlobalSettingValueProvider.ProviderName, + TenantSettingValueProvider.ProviderName), + }; + } private ILocalizableString L(string name) { return LocalizableString.Create(name); diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN.Abp.BlobStoring.Tencent.csproj b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN.Abp.BlobStoring.Tencent.csproj index 73881e41b..700366a15 100644 --- a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN.Abp.BlobStoring.Tencent.csproj +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN.Abp.BlobStoring.Tencent.csproj @@ -15,20 +15,21 @@ - + - + + - + diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/AbpBlobStoringTencentCloudModule.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/AbpBlobStoringTencentCloudModule.cs index f65c17d9d..d47517557 100644 --- a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/AbpBlobStoringTencentCloudModule.cs +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/AbpBlobStoringTencentCloudModule.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.Tencent; using LINGYUN.Abp.Tencent.Localization; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.BlobStoring; using Volo.Abp.Localization; using Volo.Abp.Modularity; @@ -25,5 +26,7 @@ public class AbpBlobStoringTencentCloudModule : AbpModule .Get() .AddVirtualJson("/LINGYUN/Abp/BlobStoring/Tencent/Localization"); }); + + context.Services.AddTenantOssClient(); } } \ No newline at end of file diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/BlobStoringTencentConsts.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/BlobStoringTencentConsts.cs new file mode 100644 index 000000000..39774e7df --- /dev/null +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/BlobStoringTencentConsts.cs @@ -0,0 +1,5 @@ +namespace LINGYUN.Abp.BlobStoring.Tencent; +internal static class BlobStoringTencentConsts +{ + public const string HttpClient = "BlobStoring.Tencent"; +} diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/CosClientFactory.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/CosClientFactory.cs index 88d2579d5..a3142f5dc 100644 --- a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/CosClientFactory.cs +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/CosClientFactory.cs @@ -33,6 +33,13 @@ public class CosClientFactory : AbstractTencentCloudClientFactory GetConfigurationAsync() + { + var configuration = ConfigurationProvider.Get(); + + return Task.FromResult(configuration.GetTencentConfiguration()); + } + protected override CosXml CreateClient(TencentBlobProviderConfiguration configuration, TencentCloudClientCacheItem cloudCache) { // 推荐全局单个对象,需要解决缓存过期事件 @@ -46,8 +53,10 @@ public class CosClientFactory : AbstractTencentCloudClientFactory CreateAsync(); Task CreateAsync(TencentBlobProviderConfiguration configuration); + + Task GetConfigurationAsync(); } diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs index b9ec97936..5ebd4b5ba 100644 --- a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs @@ -40,7 +40,7 @@ public class TencentBlobProviderConfiguration /// 创建命名空间时防盗链列表 /// public List CreateBucketReferer { - get => _containerConfiguration.GetConfiguration>(TencentBlobProviderConfigurationNames.CreateBucketReferer); + get => _containerConfiguration.GetConfigurationOrDefault(TencentBlobProviderConfigurationNames.CreateBucketReferer, new List()); set { if (value == null) { diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentCloudBlobProvider.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentCloudBlobProvider.cs index ac6fa4629..65429bdfa 100644 --- a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentCloudBlobProvider.cs +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentCloudBlobProvider.cs @@ -2,10 +2,12 @@ using COSXML.Common; using COSXML.Model.Bucket; using COSXML.Model.Object; +using COSXML.Model.Tag; using LINGYUN.Abp.Tencent.Features; using System; using System.Collections.Generic; using System.IO; +using System.Net.Http; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.BlobStoring; @@ -19,15 +21,18 @@ public class TencentCloudBlobProvider : BlobProviderBase, ITransientDependency { protected IFeatureChecker FeatureChecker { get; } protected ICosClientFactory CosClientFactory { get; } + protected IHttpClientFactory HttpClientFactory { get; } protected ITencentBlobNameCalculator TencentBlobNameCalculator { get; } public TencentCloudBlobProvider( IFeatureChecker featureChecker, ICosClientFactory cosClientFactory, + IHttpClientFactory httpClientFactory, ITencentBlobNameCalculator tencentBlobNameCalculator) { FeatureChecker = featureChecker; CosClientFactory = cosClientFactory; + HttpClientFactory = httpClientFactory; TencentBlobNameCalculator = tencentBlobNameCalculator; } @@ -63,28 +68,28 @@ public class TencentCloudBlobProvider : BlobProviderBase, ITransientDependency return null; } - // TODO: 未经验证 - - var request = new GetObjectBytesRequest(GetBucketName(args), blobName); - var ossObject = ossClient.GetObject(request); - var memoryStream = new MemoryStream(); - await memoryStream.WriteAsync(ossObject.content, 0, ossObject.content.Length); - memoryStream.Seek(0, SeekOrigin.Begin); - return memoryStream; + var configuration = args.Configuration.GetTencentConfiguration(); + // See: https://cloud.tencent.com/document/product/436/47238 + var preSignatureStruct = new PreSignatureStruct + { + appid = configuration.AppId,//"1250000000"; //腾讯云账号 APPID + region = configuration.Region,//"COS_REGION"; //存储桶地域 + bucket = GetBucketName(args),//"examplebucket-1250000000"; //存储桶 + key = blobName, //对象键 + httpMethod = "GET", //HTTP 请求方法 + isHttps = true, //生成 HTTPS 请求 URL + signDurationSecond = 600, //请求签名时间为600s + headers = null, //签名中需要校验的 header + queryParameters = null //签名中需要校验的 URL 中请求参数 + }; + var requestSignURL = ossClient.GenerateSignURL(preSignatureStruct); + var client = HttpClientFactory.CreateTenantOssClient(); + + return await client.GetStreamAsync(requestSignURL); } public override async Task SaveAsync(BlobProviderSaveArgs args) { - var maxStreamSizeString = await FeatureChecker.GetOrNullAsync(TencentCloudFeatures.BlobStoring.MaximumStreamSize); - if (!"0".Equals(maxStreamSizeString) || - (int.TryParse(maxStreamSizeString, out var maxStreamSize) - && (maxStreamSize <= 0 - || maxStreamSize < args.BlobStream.Length / 1024 / 1024))) - { - throw new BusinessException("TencentCloud:10101") - .WithData("Size", maxStreamSizeString); - } - var ossClient = await GetOssClientAsync(args); var blobName = TencentBlobNameCalculator.Calculate(args); var configuration = args.Configuration.GetTencentConfiguration(); @@ -131,8 +136,8 @@ public class TencentCloudBlobProvider : BlobProviderBase, ITransientDependency var bucketName = GetBucketName(args); var request = new PutBucketRequest(bucketName); - // TODO: good! 这很Java - request.SetCosACL(CosACL.PublicReadWrite); + + request.SetCosACL(CosACL.Private); cos.PutBucket(request); diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/Microsoft/Extensions/DependencyInjection/BlobStoringTencentHttpClientFactoryServiceCollectionExtensions.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/Microsoft/Extensions/DependencyInjection/BlobStoringTencentHttpClientFactoryServiceCollectionExtensions.cs new file mode 100644 index 000000000..3deb883ca --- /dev/null +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/Microsoft/Extensions/DependencyInjection/BlobStoringTencentHttpClientFactoryServiceCollectionExtensions.cs @@ -0,0 +1,12 @@ +using LINGYUN.Abp.BlobStoring.Tencent; + +namespace Microsoft.Extensions.DependencyInjection; +internal static class BlobStoringTencentHttpClientFactoryServiceCollectionExtensions +{ + public static IServiceCollection AddTenantOssClient(this IServiceCollection services) + { + services.AddHttpClient(BlobStoringTencentConsts.HttpClient); + + return services; + } +} diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/System/Net/Http/BlobStoringTencentHttpClientFactoryExtenssions.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/System/Net/Http/BlobStoringTencentHttpClientFactoryExtenssions.cs new file mode 100644 index 000000000..585e32241 --- /dev/null +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/System/Net/Http/BlobStoringTencentHttpClientFactoryExtenssions.cs @@ -0,0 +1,11 @@ +using LINGYUN.Abp.BlobStoring.Tencent; + +namespace System.Net.Http; +public static class BlobStoringTencentHttpClientFactoryExtenssions +{ + public static HttpClient CreateTenantOssClient( + this IHttpClientFactory httpClientFactory) + { + return httpClientFactory.CreateClient(BlobStoringTencentConsts.HttpClient); ; + } +} diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.Tencent/LINGYUN/Abp/Tencent/AbpTencentCloudModule.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.Tencent/LINGYUN/Abp/Tencent/AbpTencentCloudModule.cs index 57251d25b..1ef5e57e6 100644 --- a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.Tencent/LINGYUN/Abp/Tencent/AbpTencentCloudModule.cs +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.Tencent/LINGYUN/Abp/Tencent/AbpTencentCloudModule.cs @@ -5,11 +5,15 @@ using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Features; +using Volo.Abp.Settings; namespace LINGYUN.Abp.Tencent; [DependsOn( typeof(AbpCachingModule), + typeof(AbpFeaturesModule), + typeof(AbpSettingsModule), typeof(AbpJsonModule), typeof(AbpLocalizationModule))] public class AbpTencentCloudModule : AbpModule diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs index 4fb3ce253..63c09c373 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs @@ -56,9 +56,7 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency } var ossObject = ossClient.GetObject(GetBucketName(args), blobName); - var memoryStream = new MemoryStream(); - await ossObject.Content.CopyToAsync(memoryStream); - return memoryStream; + return ossObject.Content; } public override async Task SaveAsync(BlobProviderSaveArgs args) diff --git a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.csproj b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.csproj index 3a4a9a829..2f50762b8 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.csproj +++ b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.csproj @@ -10,10 +10,9 @@ false false false - - Cap分布式事件总线 true - $(SolutionDir)framework\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.xml + Cap分布式事件总线 + diff --git a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPBootstrapper.cs b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPBootstrapper.cs new file mode 100644 index 000000000..55d753571 --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPBootstrapper.cs @@ -0,0 +1,156 @@ +using DotNetCore.CAP; +using DotNetCore.CAP.Internal; +using DotNetCore.CAP.Persistence; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.EventBus.CAP; + +public class AbpCAPBootstrapper : IBootstrapper +{ + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + + private CancellationTokenSource _cts; + private bool _disposed; + private IEnumerable _processors = default!; + + public bool IsStarted => !_cts?.IsCancellationRequested ?? false; + + public AbpCAPBootstrapper(IServiceProvider serviceProvider, ILogger logger) + { + _serviceProvider = serviceProvider; + _logger = logger; + } + + public async Task BootstrapAsync(CancellationToken cancellationToken = default) + { + if (_cts != null) + { + _logger.LogInformation("### CAP background task is already started!"); + + return; + } + + _logger.LogDebug("### CAP background task is starting."); + + _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + + CheckRequirement(); + + _processors = _serviceProvider.GetServices(); + + try + { + await _serviceProvider.GetRequiredService().InitializeAsync(_cts.Token).ConfigureAwait(false); + } + catch (Exception e) + { + if (e is InvalidOperationException) throw; + _logger.LogError(e, "Initializing the storage structure failed!"); + } + + _cts.Token.Register(() => + { + _logger.LogDebug("### CAP background task is stopping."); + + + foreach (var item in _processors) + try + { + item.Dispose(); + } + catch (OperationCanceledException ex) + { + _logger.LogWarning(ex, $"Expected an OperationCanceledException, but found '{ex.Message}'."); + } + }); + + await BootstrapCoreAsync().ConfigureAwait(false); + + _disposed = false; + _logger.LogInformation("### CAP started!"); + } + + protected virtual async Task BootstrapCoreAsync() + { + foreach (var item in _processors) + { + try + { + _cts!.Token.ThrowIfCancellationRequested(); + + await item.Start(_cts!.Token); + } + catch (OperationCanceledException) + { + // ignore + } + catch (Exception ex) + { + _logger.LogError(ex, "Starting the processors throw an exception."); + } + } + } + + public virtual void Dispose() + { + if (_disposed) return; + + _cts?.Cancel(); + _cts?.Dispose(); + _cts = null; + _disposed = true; + } + + public virtual async Task ExecuteAsync(CancellationToken stoppingToken) + { + await BootstrapAsync(stoppingToken).ConfigureAwait(false); + } + + public virtual Task StopAsync(CancellationToken cancellationToken) + { + _cts?.Cancel(); + + return Task.CompletedTask; + } + + private void CheckRequirement() + { + var marker = _serviceProvider.GetService(); + if (marker == null) + throw new InvalidOperationException( + "AddCap() must be added on the service collection. eg: services.AddCap(...)"); + + var messageQueueMarker = _serviceProvider.GetService(); + if (messageQueueMarker == null) + throw new InvalidOperationException( + "You must be config transport provider for CAP!" + Environment.NewLine + + "==================================================================================" + + Environment.NewLine + + "======== eg: services.AddCap( options => { options.UseRabbitMQ(...) }); ========" + + Environment.NewLine + + "=================================================================================="); + + var databaseMarker = _serviceProvider.GetService(); + if (databaseMarker == null) + throw new InvalidOperationException( + "You must be config storage provider for CAP!" + Environment.NewLine + + "===================================================================================" + + Environment.NewLine + + "======== eg: services.AddCap( options => { options.UseSqlServer(...) }); ========" + + Environment.NewLine + + "==================================================================================="); + } + + public ValueTask DisposeAsync() + { + Dispose(); + + return ValueTask.CompletedTask; + } +} diff --git a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPEventBusModule.cs b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPEventBusModule.cs index 39310dfd5..25b7fa275 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPEventBusModule.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPEventBusModule.cs @@ -1,8 +1,12 @@ using DotNetCore.CAP; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; using Volo.Abp.EventBus; using Volo.Abp.Modularity; +using Volo.Abp.Threading; namespace LINGYUN.Abp.EventBus.CAP; @@ -12,6 +16,7 @@ namespace LINGYUN.Abp.EventBus.CAP; [DependsOn(typeof(AbpEventBusModule))] public class AbpCAPEventBusModule : AbpModule { + private readonly CancellationTokenSource _cancellationTokenSource = new(); /// /// ConfigureServices /// @@ -48,4 +53,24 @@ public class AbpCAPEventBusModule : AbpModule } }); } + + public override void OnPreApplicationInitialization(ApplicationInitializationContext context) + { + AsyncHelper.RunSync(() => OnPreApplicationInitializationAsync(context)); + } + + public async override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context) + { + await context + .ServiceProvider + .GetRequiredService() + .BootstrapAsync(_cancellationTokenSource.Token); + } + + public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) + { + _cancellationTokenSource.Cancel(); + + return Task.CompletedTask; + } } diff --git a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs index 445567aff..81ae8168e 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs @@ -2,7 +2,9 @@ using DotNetCore.CAP.Internal; using DotNetCore.CAP.Serialization; using LINGYUN.Abp.EventBus.CAP; +using Microsoft.Extensions.Hosting; using System; +using System.Collections.Generic; namespace Microsoft.Extensions.DependencyInjection; @@ -23,6 +25,33 @@ public static class ServiceCollectionExtensions // 替换为自己的实现 services.AddSingleton(); services.AddSingleton(); + + // 移除默认CAP启动接口 + services.RemoveAll(service => + { + if (service.ServiceType.IsAssignableFrom(typeof(IBootstrapper))) + { + return true; + } + // 默认Bootstrapper + if (service.ImplementationType != null && + service.ImplementationType.IsAssignableTo(typeof(IBootstrapper))) + { + return true; + } + // 默认Bootstrapper HostService + if (service.ImplementationFactory != null && + service.ImplementationFactory.Method.ReturnType.IsAssignableTo(typeof(IBootstrapper))) + { + return true; + } + + return false; + }); + // 使用重写的接口,不使用BackgroundService + services.AddSingleton(); + services.AddSingleton(sp => sp.GetRequiredService()); + return services; } } diff --git a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.xml b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.xml deleted file mode 100644 index 35b6776bb..000000000 --- a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.xml +++ /dev/null @@ -1,397 +0,0 @@ - - - - LINGYUN.Abp.EventBus.CAP - - - - - 消费者查找器 - - - - - CAP配置 - - - - - Abp分布式事件配置 - - - - - 服务提供者 - - - - - Creates a new . - - - - - 查找消费者集合 - - - - - - - 获取事件处理器集合 - - - - - - - - AbpCAPEventBusModule - - - - - ConfigureServices - - - - - - 过期消息清理配置项 - - - - - 发布消息处理失败通知 - default: false - - - - - AbpECAPExecutionFailedException - - - - - MessageType - - - - - Message - - - - - constructor - - - - - - - constructor - - - - - - - - constructor - - - - - - - - - CAP消息扩展 - - - - - 尝试获取消息标头中的租户标识 - - - - - - - - 获取消息标头中的租户标识 - - - - - - - 尝试获取消息标头中的链路标识 - - - - - - - - 获取消息标头中的链路标识 - - - - - - - 重写 ISubscribeInvoker 实现 Abp 租户集成 - - - - - AbpCAPSubscribeInvoker - - - - - - - - - - 调用订阅者方法 - - - - - - - - - - - - - - - - - - 获取事件处理类实例 - - - - - - - - 通过给定的类型实例与参数调用订阅者方法 - - - - - - - - - CAP分布式事件总线 - - - - - CAP消息发布接口 - - - - - 自定义事件注册接口 - - - - - 本地事件处理器工厂对象集合 - - - - - 本地事件集合 - - - - - 当前用户 - - - - - 当前客户端 - - - - - typeof - - - - - 取消令牌 - - - - - constructor - - - - - - - - - - - - - - - - - - - - 订阅事件 - - - - - - - - 退订事件 - - 事件类型 - - - - - 退订事件 - - 事件类型 - 事件处理器 - - - - 退订事件 - - 事件类型 - 事件处理器工厂 - - - - 退订所有事件 - - 事件类型 - - - - 发布事件 - - 事件类型 - 事件数据对象 - - - - - 获取事件处理器工厂列表 - - - - - - - 自定义事件订阅者 - - - - - 订阅事件 - - - - - - - 取消订阅 - - - - - - - Executes the configured method on . This can be used whether or not - the configured method is asynchronous. - - - Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than - ExecuteAsync if you know at compile time what the return type is, because then you can directly - "await" that value (via a cast), and then the generated code will be able to reference the - resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated - code will have to treat the resulting awaitable as a boxed object, because it doesn't know at - compile time what type it would be. - - The object whose method is to be executed. - Parameters to pass to the method. - The method return value. - - - - Executes the configured method on . This can only be used if the configured - method is asynchronous. - - - If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync, - which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations - as compared with using Execute and then using "await" on the result value typecasted to the known - awaitable type. The possible extra heap allocations are for: - 1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally - it's a reference type, and you normally create a new instance per call). - 2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance - of it, and if it is, it will have to be boxed so the calling code can reference it as an object). - 3. The async result value, if it's a value type (it has to be boxed as an object, since the calling - code doesn't know what type it's going to be). - - The object whose method is to be executed. - Parameters to pass to the method. - An object that you can "await" to get the method return value. - - - - Provides a common awaitable structure that can - return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an - application-defined custom awaitable. - - - - - Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying - an for mapping instances of that type to a C# awaitable. - - - The main design goal here is to avoid taking a compile-time dependency on - FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references - to FSharp types have to be constructed dynamically at runtime. - - - - - CAP ServiceCollectionExtensions - - - - - Adds and configures the consistence services for the consistency. - - - - - - - diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs index 31326203b..2e749fb0e 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs @@ -20,9 +20,12 @@ using Volo.Abp.Sms; namespace LINGYUN.Abp.Sms.Aliyun; [Dependency(ServiceLifetime.Singleton)] -[ExposeServices(typeof(ISmsSender), typeof(AliyunSmsSender))] +[ExposeServices( + typeof(ISmsSender), + typeof(IAliyunSmsVerifyCodeSender), + typeof(AliyunSmsSender))] [RequiresFeature(AliyunFeatureNames.Sms.Enable)] -public class AliyunSmsSender : ISmsSender +public class AliyunSmsSender : ISmsSender, IAliyunSmsVerifyCodeSender { protected IJsonSerializer JsonSerializer { get; } protected ISettingProvider SettingProvider { get; } @@ -48,6 +51,21 @@ public class AliyunSmsSender : ISmsSender AliyunFeatureNames.Sms.DefaultSendLimitInterval)] public async virtual Task SendAsync(SmsMessage smsMessage) { + if (smsMessage.Properties.ContainsKey("SmsVerifyCode") && + smsMessage.Properties.TryGetValue("code", out var code)) + { + smsMessage.Properties.TryGetValue("SignName", out var signName); + smsMessage.Properties.TryGetValue("TemplateCode", out var templateCode); + // 调用短信验证码服务 + await SendAsync( + new SmsVerifyCodeMessage( + smsMessage.PhoneNumber, + new SmsVerifyCodeMessageParam(code.ToString(), "5"), + signName?.ToString(), + templateCode?.ToString())); + return; + } + var domain = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Sms.Domain); var action = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Sms.ActionName); var version = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Sms.Version); @@ -56,7 +74,7 @@ public class AliyunSmsSender : ISmsSender Check.NotNullOrWhiteSpace(action, AliyunSettingNames.Sms.ActionName); Check.NotNullOrWhiteSpace(version, AliyunSettingNames.Sms.Version); - CommonRequest request = new CommonRequest + var request = new CommonRequest { Method = MethodType.POST, Domain = domain, @@ -72,7 +90,7 @@ public class AliyunSmsSender : ISmsSender try { var client = await AcsClientFactory.CreateAsync(); - CommonResponse response = client.GetCommonResponse(request); + var response = client.GetCommonResponse(request); var responseContent = Encoding.Default.GetString(response.HttpResponse.Content); var aliyunResponse = JsonSerializer.Deserialize(responseContent); if (!aliyunResponse.IsSuccess()) @@ -94,6 +112,64 @@ public class AliyunSmsSender : ISmsSender } } + [RequiresLimitFeature( + AliyunFeatureNames.Sms.SendLimit, + AliyunFeatureNames.Sms.SendLimitInterval, + LimitPolicy.Month, + AliyunFeatureNames.Sms.DefaultSendLimit, + AliyunFeatureNames.Sms.DefaultSendLimitInterval)] + public async virtual Task SendAsync(SmsVerifyCodeMessage message) + { + var version = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Sms.Version); + var domain = await SettingProvider.GetOrNullAsync(AliyunSettingNames.SmsVerifyCode.Domain); + var signName = message.SignName ?? + await SettingProvider.GetOrNullAsync(AliyunSettingNames.SmsVerifyCode.DefaultSignName); + var templateCode = message.TemplateCode ?? + await SettingProvider.GetOrNullAsync(AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode); + + Check.NotNullOrWhiteSpace(version, AliyunSettingNames.Sms.Version); + Check.NotNullOrWhiteSpace(domain, AliyunSettingNames.SmsVerifyCode.Domain); + Check.NotNullOrWhiteSpace(signName, AliyunSettingNames.SmsVerifyCode.DefaultSignName); + Check.NotNullOrWhiteSpace(templateCode, AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode); + + var request = new CommonRequest + { + Domain = domain, + Version = version, + Product = "Dypnsapi", + Method = MethodType.POST, + Action = "SendSmsVerifyCode", + }; + request.AddBodyParameters("PhoneNumber", message.PhoneNumber); + request.AddBodyParameters("SignName", signName); + request.AddBodyParameters("TemplateCode", templateCode); + request.AddBodyParameters("TemplateParam", JsonSerializer.Serialize(message.TemplateParam)); + + try + { + var client = await AcsClientFactory.CreateAsync(); + var response = client.GetCommonResponse(request); + var responseContent = Encoding.Default.GetString(response.HttpResponse.Content); + var aliyunResponse = JsonSerializer.Deserialize(responseContent); + if (!aliyunResponse.Success) + { + 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}!"); + } + } + 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}"); + } + } + private async Task TryAddTemplateCodeAsync(CommonRequest request, SmsMessage smsMessage) { if (smsMessage.Properties.TryGetValue("TemplateCode", out object template) && template != null) diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeResponse.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeResponse.cs new file mode 100644 index 000000000..49d33db37 --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeResponse.cs @@ -0,0 +1,40 @@ +namespace LINGYUN.Abp.Sms.Aliyun; +public class AliyunSmsVerifyCodeResponse +{ + /// + /// 请求状态码, OK代表请求成功 + /// + public string Code { get; set; } + /// + /// 状态码的描述 + /// + public string Message { get; set; } + /// + /// 请求是否成功 + /// + public bool Success { get; set; } + /// + /// 请求结果数据 + /// + public AliyunSmsVerifyCodeModel Model { get; set; } +} + +public class AliyunSmsVerifyCodeModel +{ + /// + /// 请求Id + /// + public string RequestId { get; set; } + /// + /// 业务Id + /// + public string BizId { get; set; } + /// + /// 外部流水号 + /// + public string OutId { get; set; } + /// + /// 验证码, 仅当使用阿里云短信验证服务生成验证码时携带 + /// + public string VerifyCode { get; set; } +} diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IAliyunSmsVerifyCodeSender.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IAliyunSmsVerifyCodeSender.cs new file mode 100644 index 000000000..f9b61feae --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IAliyunSmsVerifyCodeSender.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Sms.Aliyun; +/// +/// 阿里云发送短信验证码接口 +/// +public interface IAliyunSmsVerifyCodeSender +{ + /// + /// 发送短信验证码 + /// + /// + /// + Task SendAsync(SmsVerifyCodeMessage message); +} diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/Localization/Resources/en.json b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/Localization/Resources/en.json index 5ef98e5a2..df50a12f6 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/Localization/Resources/en.json +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/Localization/Resources/en.json @@ -1,6 +1,26 @@ { "culture": "en", "texts": { + "DisplayName:Aliyun.Sms": "Sms", + "Description:Aliyun.Sms": "Sms", + "DisplayName:Aliyun.SmsVerifyCode": "Sms Verify Code", + "Description:Aliyun.SmsVerifyCode": "Sms Verify Code", + "DisplayName:ActionName": "Action Name", + "Description:ActionName": "Action Name", + "DisplayName:DefaultSignName": "Default Sign Name", + "Description:DefaultSignName": "Default Sign Name", + "DisplayName:DefaultTemplateCode": "Default Template Code", + "Description:DefaultTemplateCode": "Default Template Code", + "DisplayName:DefaultPhoneNumber": "Default Phone Number", + "Description:DefaultPhoneNumber": "Default Phone Number", + "DisplayName:SmsDomain": "Domain", + "Description:SmsDomain": "Domain", + "DisplayName:SmsVerifyCodeDomain": "Domain", + "Description:SmsVerifyCodeDomain": "Sms Verify Code Domain", + "DisplayName:Version": "Version", + "Description:Version": "Version", + "DisplayName:VisableErrorToClient": "Visable Error To Client", + "Description:VisableErrorToClient": "Visable Error To Client", "SendMessageFailed": "Text message sending failed:{0}", "SMS_SIGNATURE_SCENE_ILLEGAL": "Signature scene used by SMS is illegal", "DENY_IP_RANGE": "Region where source IP address is disabled", diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/Localization/Resources/zh-Hans.json b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/Localization/Resources/zh-Hans.json index f2ba1b6a5..390a43155 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/Localization/Resources/zh-Hans.json +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/Localization/Resources/zh-Hans.json @@ -1,6 +1,26 @@ { "culture": "zh-Hans", "texts": { + "DisplayName:Aliyun.Sms": "短信服务", + "Description:Aliyun.Sms": "阿里云短信服务", + "DisplayName:Aliyun.SmsVerifyCode": "短信认证服务", + "Description:Aliyun.SmsVerifyCode": "阿里云短信认证服务", + "DisplayName:ActionName": "发送短信方法", + "Description:ActionName": "发送短信方法名称,详情见阿里云Sms服务", + "DisplayName:DefaultSignName": "默认短信签名", + "Description:DefaultSignName": "当用户未指定短信签名时的默认签名名称", + "DisplayName:DefaultTemplateCode": "默认短信模板号", + "Description:DefaultTemplateCode": "当用户未指定短信签名时的默认短信模板号", + "DisplayName:DefaultPhoneNumber": "默认接收短信手机号", + "Description:DefaultPhoneNumber": "当用户未指定短信接收方时的默认接收手机号码", + "DisplayName:SmsDomain": "阿里云sms服务域名", + "Description:SmsDomain": "阿里云sms服务域名", + "DisplayName:SmsVerifyCodeDomain": "阿里云号码认证服务域名", + "Description:SmsVerifyCodeDomain": "阿里云号码认证服务域名", + "DisplayName:Version": "阿里云sms服务版本号", + "Description:Version": "阿里云sms服务版本号", + "DisplayName:VisableErrorToClient": "发送错误到客户端", + "Description:VisableErrorToClient": "当短信服务发送出现错误时是否发送错误详情到客户端", "SendMessageFailed": "发送短信错误:{0}", "SMS_SIGNATURE_SCENE_ILLEGAL": "短信所使用签名场景非法", "DENY_IP_RANGE": "源IP地址所在的地区被禁用", diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessage.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessage.cs new file mode 100644 index 000000000..745c9947f --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessage.cs @@ -0,0 +1,88 @@ +namespace LINGYUN.Abp.Sms.Aliyun; +public class SmsVerifyCodeMessage +{ + /// + /// 方案名称,如果不填则为“默认方案”。最多不超过 20 个字符。 + /// + public string SchemeName { get; set; } + /// + /// 号码国家编码。默认为 86,目前也仅支持中国国内号码发送。 + /// + public string CountryCode { get; set; } + /// + /// 上行短信扩展码。上行短信指发送给通信服务提供商的短信,用于定制某种服务、完成查询,或是办理某种业务等,需要收费,按运营商普通短信资费进行扣费。 + /// + public string SmsUpExtendCode { get; set; } + /// + /// 外部流水号。 + /// + public string OutId { get; set; } + /// + /// 验证码长度支持 4~8 位长度,默认是 4 位。 + /// + public long? CodeLength { get; set; } + /// + /// 验证码有效时长,单位秒,默认为 300 秒。 + /// + public long? ValidTime { get; set; } + /// + /// 核验规则,当有效时间内对同场景内的同号码重复发送验证码时,旧验证码如何处理。 + /// + /// + /// 1 - 覆盖处理(默认),即旧验证码会失效掉。
+ /// 2 - 保留,即多个验证码都是在有效期内都可以校验通过。 + ///
+ public long? DuplicatePolicy { get; set; } + /// + /// 时间间隔,单位:秒。即多久间隔可以发送一次验证码,用于频控,默认 60 秒。 + /// + public long? Interval { get; set; } + /// + /// 生成的验证码类型。当参数 TemplateParam 传入占位符时,此参数必填,将由系统根据指定的规则生成验证码。 + /// + /// + /// 1 - 纯数字(默认)。 + /// 2 - 纯大写字母。 + /// 3 - 纯小写字母。 + /// 4 - 大小字母混合。 + /// 5 - 数字+大写字母混合。 + /// 6 - 数字+小写字母混合。 + /// 7 - 数字+大小写字母混合。 + /// + public long? CodeType { get; set; } + /// + /// 是否返回验证码。 + /// + public bool? ReturnVerifyCode { get; set; } + /// + /// 是否自动替换签名重试(默认开启。 + /// + public bool? AutoRetry { get; set; } + /// + /// 短信接收方手机号。 + /// + public string PhoneNumber { get; } + /// + /// 签名名称。暂不支持使用自定义签名,请使用系统赠送的签名。 + /// + public string SignName { get; } + /// + /// 短信模板 CODE。参数SignName选择赠送签名时,必须搭配赠送模板下发短信。您可在赠送模板配置页面选择适用您业务场景的模板。 + /// + public string TemplateCode { get; } + /// + /// 短信模板参数。 + /// + public SmsVerifyCodeMessageParam TemplateParam { get; } + public SmsVerifyCodeMessage( + string phoneNumber, + SmsVerifyCodeMessageParam templateParam, + string signName = null, + string templateCode = null) + { + PhoneNumber = phoneNumber; + TemplateParam = templateParam; + SignName = signName; + TemplateCode = templateCode; + } +} diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessageParam.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessageParam.cs new file mode 100644 index 000000000..4158d44c6 --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/SmsVerifyCodeMessageParam.cs @@ -0,0 +1,11 @@ +namespace LINGYUN.Abp.Sms.Aliyun; +public class SmsVerifyCodeMessageParam +{ + public string Code { get; } + public string Min { get; } + public SmsVerifyCodeMessageParam(string code, string min = "5") + { + Code = code; + Min = min; + } +} diff --git a/aspnet-core/framework/telemetry/LINGYUN.Abp.Telemetry.APM/LINGYUNG/Abp/Telemetry/APM/AbpTelemetryAPMModule.cs b/aspnet-core/framework/telemetry/LINGYUN.Abp.Telemetry.APM/LINGYUNG/Abp/Telemetry/APM/AbpTelemetryAPMModule.cs index 13d8bed4c..3f5ed43ce 100644 --- a/aspnet-core/framework/telemetry/LINGYUN.Abp.Telemetry.APM/LINGYUNG/Abp/Telemetry/APM/AbpTelemetryAPMModule.cs +++ b/aspnet-core/framework/telemetry/LINGYUN.Abp.Telemetry.APM/LINGYUNG/Abp/Telemetry/APM/AbpTelemetryAPMModule.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using System; using Volo.Abp.Modularity; namespace LINGYUN.Abp.Telemetry.APM; @@ -7,6 +8,13 @@ public class AbpTelemetryAPMModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { + var configuration = context.Services.GetConfiguration(); + var isAPMEnabled = configuration["APM:IsEnabled"]; + if (isAPMEnabled.IsNullOrWhiteSpace() || "false".Equals(isAPMEnabled.ToLower())) + { + return; + } + context.Services.AddAllElasticApm(); } } diff --git a/aspnet-core/framework/telemetry/LINGYUN.Abp.Telemetry.OpenTelemetry/LINGYUN/Abp/Telemetry/OpenTelemetry/AbpTelemetryOpenTelemetryModule.cs b/aspnet-core/framework/telemetry/LINGYUN.Abp.Telemetry.OpenTelemetry/LINGYUN/Abp/Telemetry/OpenTelemetry/AbpTelemetryOpenTelemetryModule.cs index c8f162ca2..2b977113a 100644 --- a/aspnet-core/framework/telemetry/LINGYUN.Abp.Telemetry.OpenTelemetry/LINGYUN/Abp/Telemetry/OpenTelemetry/AbpTelemetryOpenTelemetryModule.cs +++ b/aspnet-core/framework/telemetry/LINGYUN.Abp.Telemetry.OpenTelemetry/LINGYUN/Abp/Telemetry/OpenTelemetry/AbpTelemetryOpenTelemetryModule.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using OpenTelemetry; +using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; @@ -38,7 +39,8 @@ public class AbpTelemetryOpenTelemetryModule : AbpModule var openTelmetrySetup = context.Services.GetPreConfigureActions(); - if (!configuration.GetValue("OpenTelemetry:IsEnabled", false)) + var isOpenTelmetryEnabled = configuration["OpenTelemetry:IsEnabled"]; + if (isOpenTelmetryEnabled.IsNullOrWhiteSpace() || "false".Equals(isOpenTelmetryEnabled.ToLower())) { return; } @@ -50,6 +52,12 @@ public class AbpTelemetryOpenTelemetryModule : AbpModule { applicationName = context.Services.GetApplicationName(); } + + if (applicationName.IsNullOrWhiteSpace()) + { + return; + } + var openTelmetryBuilder = context.Services .AddOpenTelemetry() .ConfigureResource(resource => @@ -64,6 +72,10 @@ public class AbpTelemetryOpenTelemetryModule : AbpModule .WithMetrics(metrics => { ConfigureMetrics(metrics, configuration); + }) + .WithLogging(logging => + { + ConfigureLogging(logging, configuration); }); openTelmetrySetup.Configure(openTelmetryBuilder); @@ -157,4 +169,20 @@ public class AbpTelemetryOpenTelemetryModule : AbpModule }); } } + + private static void ConfigureLogging(LoggerProviderBuilder logging, IConfiguration configuration) + { + if (configuration.GetValue("OpenTelemetry:Otlp:IsEnabled", false)) + { + logging.AddOtlpExporter(otlpOptions => + { + var otlpEndPoint = configuration["OpenTelemetry:Otlp:Endpoint"]; + Check.NotNullOrWhiteSpace(otlpEndPoint, nameof(otlpEndPoint)); + + otlpOptions.Headers = configuration["OpenTelemetry:Otlp:Headers"]; + otlpOptions.Endpoint = new Uri(otlpEndPoint.EnsureEndsWith('/') + "v1/logs"); + otlpOptions.Protocol = configuration.GetValue("OpenTelemetry:Otlp:Protocol", otlpOptions.Protocol); + }); + } + } } diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xml b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xml new file mode 100644 index 000000000..5d6962159 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xsd b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN.Abp.AspNetCore.MultiTenancy.csproj b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN.Abp.AspNetCore.MultiTenancy.csproj new file mode 100644 index 000000000..89b162615 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN.Abp.AspNetCore.MultiTenancy.csproj @@ -0,0 +1,21 @@ + + + + + + + net9.0 + LINGYUN.Abp.AspNetCore.MultiTenancy + LINGYUN.Abp.AspNetCore.MultiTenancy + false + false + false + Library + enable + + + + + + + diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs new file mode 100644 index 000000000..61668fc97 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Modularity; +using VoloAbpAspNetCoreMultiTenancyModule = Volo.Abp.AspNetCore.MultiTenancy.AbpAspNetCoreMultiTenancyModule; + +namespace LINGYUN.Abp.AspNetCore.MultiTenancy; + +[DependsOn(typeof(VoloAbpAspNetCoreMultiTenancyModule))] +public class AbpAspNetCoreMultiTenancyModule : AbpModule +{ + +} diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyResolveOptions.cs b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyResolveOptions.cs new file mode 100644 index 000000000..1b7d0abc8 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyResolveOptions.cs @@ -0,0 +1,13 @@ +namespace LINGYUN.Abp.AspNetCore.MultiTenancy; + +public class AbpAspNetCoreMultiTenancyResolveOptions +{ + /// + /// 仅解析域名中的租户, 默认: true + /// + public bool OnlyResolveDomain { get; set; } + public AbpAspNetCoreMultiTenancyResolveOptions() + { + OnlyResolveDomain = true; + } +} diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpMultiTenancyOptionsExtensions.cs b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpMultiTenancyOptionsExtensions.cs new file mode 100644 index 000000000..d78531af5 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpMultiTenancyOptionsExtensions.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.AspNetCore.MultiTenancy; + +public static class AbpMultiTenancyOptionsExtensions +{ + public static void AddOnlyDomainTenantResolver(this AbpTenantResolveOptions options, string domainFormat) + { + options.TenantResolvers.InsertAfter( + r => r is CurrentUserTenantResolveContributor, + new OnlyDomainTenantResolveContributor(domainFormat) + ); + } +} diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/OnlyDomainTenantResolveContributor.cs b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/OnlyDomainTenantResolveContributor.cs new file mode 100644 index 000000000..b272971ff --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/OnlyDomainTenantResolveContributor.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Net; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Text.Formatting; + +namespace LINGYUN.Abp.AspNetCore.MultiTenancy; + +public class OnlyDomainTenantResolveContributor : HttpTenantResolveContributorBase +{ + public const string ContributorName = "Domain"; + + public override string Name => ContributorName; + + private static readonly string[] ProtocolPrefixes = { "http://", "https://" }; + + private readonly string _domainFormat; + + public OnlyDomainTenantResolveContributor(string domainFormat) + { + _domainFormat = domainFormat.RemovePreFix(ProtocolPrefixes); + } + + protected override Task GetTenantIdOrNameFromHttpContextOrNullAsync(ITenantResolveContext context, HttpContext httpContext) + { + if (!httpContext.Request.Host.HasValue) + { + return Task.FromResult(null); + } + + var options = httpContext.RequestServices.GetRequiredService>(); + if (options.Value.OnlyResolveDomain) + { + // 仅仅解析域名, 如果请求的是IP地址, 则不使用这个解析贡献者 + if (IPAddress.TryParse(httpContext.Request.Host.Host, out var _)) + { + return Task.FromResult(null); + } + } + + var hostName = httpContext.Request.Host.Value.RemovePreFix(ProtocolPrefixes); + var extractResult = FormattedStringValueExtracter.Extract(hostName, _domainFormat, ignoreCase: true); + + context.Handled = true; + + return Task.FromResult(extractResult.IsMatch ? extractResult.Matches[0].Value : null); + } +} diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json new file mode 100644 index 000000000..8ac6471cc --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "LINGYUN.Abp.AspNetCore.MultiTenancy": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:61811;http://localhost:61812" + } + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/AbpWeChatWorkClaimsPrincipalContributor.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/AbpWeChatWorkClaimsPrincipalContributor.cs new file mode 100644 index 000000000..7087e0c6d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/AbpWeChatWorkClaimsPrincipalContributor.cs @@ -0,0 +1,36 @@ +using LINGYUN.Abp.WeChat.Work.Authorize; +using LINGYUN.Abp.WeChat.Work.Security.Claims; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Claims; + +namespace LINGYUN.Abp.Identity.WeChat.Work; +public class AbpWeChatWorkClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency +{ + public async virtual Task ContributeAsync(AbpClaimsPrincipalContributorContext context) + { + var claimsIdentity = context.ClaimsPrincipal.Identities.First(); + if (claimsIdentity.HasClaim(x => x.Type == AbpWeChatWorkClaimTypes.UserId)) + { + return; + } + var userId = claimsIdentity.FindUserId(); + if (userId.HasValue) + { + var userClaimProvider = context.ServiceProvider.GetService(); + + var weChatWorkUserId = await userClaimProvider?.FindUserIdentifierAsync(userId.Value); + if (!weChatWorkUserId.IsNullOrWhiteSpace()) + { + claimsIdentity.AddOrReplace(new Claim(AbpWeChatWorkClaimTypes.UserId, weChatWorkUserId)); + + context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); + } + } + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/AbpWeChatMessageResolveOptions.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/AbpWeChatMessageResolveOptions.cs deleted file mode 100644 index 91af5f200..000000000 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/AbpWeChatMessageResolveOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; - -namespace LINGYUN.Abp.WeChat.Common.Messages; -public class AbpWeChatMessageResolveOptions -{ - public List MessageResolvers { get; } - - public AbpWeChatMessageResolveOptions() - { - MessageResolvers = new List(); - } -} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/IMessageResolveContributor.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/IMessageResolveContributor.cs index 78bb587b6..dd1b0a93f 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/IMessageResolveContributor.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/IMessageResolveContributor.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; namespace LINGYUN.Abp.WeChat.Common.Messages; + public interface IMessageResolveContributor { string Name { get; } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/IMessageResolver.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/IMessageResolver.cs deleted file mode 100644 index c708a2aa1..000000000 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/IMessageResolver.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Threading.Tasks; - -namespace LINGYUN.Abp.WeChat.Common.Messages; -public interface IMessageResolver -{ - Task ResolveMessageAsync(MessageResolveData messageData); -} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/MessageResolveContributorBase.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/MessageResolveContributorBase.cs index 088b67e21..56f9089fc 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/MessageResolveContributorBase.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/MessageResolveContributorBase.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; namespace LINGYUN.Abp.WeChat.Common.Messages; + public abstract class MessageResolveContributorBase : IMessageResolveContributor { public abstract string Name { get; } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/MessageResolver.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/MessageResolverBase.cs similarity index 69% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/MessageResolver.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/MessageResolverBase.cs index 145b4efca..42952f73e 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/MessageResolver.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Common/LINGYUN/Abp/WeChat/Common/Messages/MessageResolverBase.cs @@ -1,7 +1,6 @@ using LINGYUN.Abp.WeChat.Common.Crypto; using LINGYUN.Abp.WeChat.Common.Crypto.Models; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using System; using System.Linq; using System.Threading.Tasks; @@ -9,29 +8,25 @@ using System.Xml.Linq; using Volo.Abp.DependencyInjection; namespace LINGYUN.Abp.WeChat.Common.Messages; -public class MessageResolver : IMessageResolver, ITransientDependency +public abstract class MessageResolverBase : ITransientDependency { private readonly IWeChatCryptoService _cryptoService; private readonly IServiceProvider _serviceProvider; - private readonly AbpWeChatMessageResolveOptions _options; - public MessageResolver( - IOptions options, + protected MessageResolverBase( IWeChatCryptoService cryptoService, IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; _cryptoService = cryptoService; - _options = options.Value; } /// /// 解析微信服务器推送消息/事件 /// /// /// - public async virtual Task ResolveMessageAsync(MessageResolveData messageData) + public async virtual Task ResolveAsync(MessageResolveData messageData) { - var result = new MessageResolveResult(messageData.Data); using (var serviceScope = _serviceProvider.CreateScope()) { /* 明文数据格式 @@ -65,24 +60,17 @@ public class MessageResolver : IMessageResolver, ITransientDependency // 经过解密函数得到如上真实数据 var decryptMessage = _cryptoService.Decrypt(cryptoDecryptData); xmlDocument = XDocument.Parse(decryptMessage); - result.Input = decryptMessage; } - var context = new MessageResolveContext(result.Input, xmlDocument, serviceScope.ServiceProvider); + var context = new MessageResolveContext(messageData.Data, xmlDocument, serviceScope.ServiceProvider); - foreach (var messageResolver in _options.MessageResolvers) - { - await messageResolver.ResolveAsync(context); - - result.AppliedResolvers.Add(messageResolver.Name); - - if (context.HasResolvedMessage()) - { - result.Message = context.Message; - break; - } - } + return await ResolveMessageAsync(context); } - return result; } + /// + /// 实现具体的解析微信服务器推送消息/事件方法 + /// + /// + /// + protected abstract Task ResolveMessageAsync(MessageResolveContext context); } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official.Application/LINGYUN/Abp/WeChat/Official/Message/WeChatMessageAppService.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official.Application/LINGYUN/Abp/WeChat/Official/Message/WeChatMessageAppService.cs index bc4096c6b..6ee32744d 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official.Application/LINGYUN/Abp/WeChat/Official/Message/WeChatMessageAppService.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official.Application/LINGYUN/Abp/WeChat/Official/Message/WeChatMessageAppService.cs @@ -1,6 +1,7 @@ using LINGYUN.Abp.WeChat.Common.Crypto; using LINGYUN.Abp.WeChat.Common.Crypto.Models; using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Official.Messages; using Microsoft.Extensions.Logging; using System.Threading.Tasks; using Volo.Abp; @@ -13,9 +14,9 @@ public class WeChatMessageAppService : ApplicationService, IWeChatMessageAppServ private readonly IWeChatCryptoService _cryptoService; private readonly AbpWeChatOfficialOptionsFactory _optionsFactory; private readonly IDistributedEventBus _distributedEventBus; - private readonly IMessageResolver _messageResolver; + private readonly IWeChatOfficialMessageResolver _messageResolver; public WeChatMessageAppService( - IMessageResolver messageResolver, + IWeChatOfficialMessageResolver messageResolver, IWeChatCryptoService cryptoService, IDistributedEventBus distributedEventBus, AbpWeChatOfficialOptionsFactory optionsFactory) @@ -67,7 +68,7 @@ public class WeChatMessageAppService : ApplicationService, IWeChatMessageAppServ input.Nonce, input.Data); - var result = await _messageResolver.ResolveMessageAsync(messageData); + var result = await _messageResolver.ResolveAsync(messageData); if (result.Message == null) { Logger.LogWarning(input.Data); diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/AbpWeChatOfficialModule.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/AbpWeChatOfficialModule.cs index f695ba86d..b265a0739 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/AbpWeChatOfficialModule.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/AbpWeChatOfficialModule.cs @@ -40,10 +40,7 @@ public class AbpWeChatOfficialModule : AbpModule options.MapMessage("shortvideo", context => context.GetWeChatMessage()); options.MapMessage("location", context => context.GetWeChatMessage()); options.MapMessage("link", context => context.GetWeChatMessage()); - }); - Configure(options => - { // 事件处理器 options.MessageResolvers.AddIfNotContains(new WeChatOfficialEventResolveContributor()); // 消息处理器 diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/AbpWeChatOfficialMessageResolveOptions.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/AbpWeChatOfficialMessageResolveOptions.cs index d145468e7..506e08e03 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/AbpWeChatOfficialMessageResolveOptions.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/AbpWeChatOfficialMessageResolveOptions.cs @@ -5,10 +5,12 @@ using System.Collections.Generic; namespace LINGYUN.Abp.WeChat.Official.Messages; public class AbpWeChatOfficialMessageResolveOptions { + public List MessageResolvers { get; } public IDictionary> EventMaps { get; } public IDictionary> MessageMaps { get; } public AbpWeChatOfficialMessageResolveOptions() { + MessageResolvers = new List(); EventMaps = new Dictionary>(); MessageMaps = new Dictionary>(); } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/IWeChatOfficialMessageResolver.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/IWeChatOfficialMessageResolver.cs new file mode 100644 index 000000000..570eb0662 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/IWeChatOfficialMessageResolver.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Official.Messages; +/// +/// 微信公众号消息解析器 +/// +public interface IWeChatOfficialMessageResolver +{ + /// + /// 解析微信公众号消息 + /// + /// 公众号消息 + /// + Task ResolveAsync(MessageResolveData messageData); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialEventMessageEto.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialEventMessageEto.cs index b63df0c34..6dd84a18f 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialEventMessageEto.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialEventMessageEto.cs @@ -3,7 +3,7 @@ using Volo.Abp.EventBus; namespace LINGYUN.Abp.WeChat.Official.Messages; -[GenericEventName(Prefix = "wechat.official.events")] +[GenericEventName(Prefix = "wechat.official.events.")] public class WeChatOfficialEventMessageEto : WeChatMessageEto where TEvent : WeChatEventMessage { diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialEventResolveContributor.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialEventResolveContributor.cs index 9db7070ca..4f62e84c8 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialEventResolveContributor.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialEventResolveContributor.cs @@ -1,6 +1,4 @@ using LINGYUN.Abp.WeChat.Common.Messages; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using System; using System.Threading.Tasks; @@ -12,9 +10,8 @@ public class WeChatOfficialEventResolveContributor : WeChatOfficialMessageResolv { public override string Name => "WeChat.Official.Event"; - protected override Task ResolveWeChatMessageAsync(IMessageResolveContext context) + protected override Task ResolveMessageAsync(IMessageResolveContext context, AbpWeChatOfficialMessageResolveOptions options) { - var options = context.ServiceProvider.GetRequiredService>().Value; var messageType = context.GetMessageData("MsgType"); var eventName = context.GetMessageData("Event"); if ("event".Equals(messageType, StringComparison.InvariantCultureIgnoreCase) && diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialGeneralMessageEto.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialGeneralMessageEto.cs index a5129d268..c27cc00af 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialGeneralMessageEto.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialGeneralMessageEto.cs @@ -3,7 +3,7 @@ using Volo.Abp.EventBus; namespace LINGYUN.Abp.WeChat.Official.Messages; -[GenericEventName(Prefix = "wechat.official.messages")] +[GenericEventName(Prefix = "wechat.official.messages.")] public class WeChatOfficialGeneralMessageEto : WeChatMessageEto where TMessage : WeChatOfficialGeneralMessage { diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialMessageResolveContributor.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialMessageResolveContributor.cs index 5a0b4aa3c..da22ec390 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialMessageResolveContributor.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialMessageResolveContributor.cs @@ -1,6 +1,4 @@ using LINGYUN.Abp.WeChat.Common.Messages; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using System.Threading.Tasks; namespace LINGYUN.Abp.WeChat.Official.Messages; @@ -11,9 +9,8 @@ public class WeChatOfficialMessageResolveContributor : WeChatOfficialMessageReso { public override string Name => "WeChat.Official.Message"; - protected override Task ResolveWeChatMessageAsync(IMessageResolveContext context) + protected override Task ResolveMessageAsync(IMessageResolveContext context, AbpWeChatOfficialMessageResolveOptions options) { - var options = context.ServiceProvider.GetRequiredService>().Value; var messageType = context.GetMessageData("MsgType"); if (options.MessageMaps.TryGetValue(messageType, out var messageFactory)) { diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialMessageResolveContributorBase.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialMessageResolveContributorBase.cs index cf82e7a8e..998b8ed8d 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialMessageResolveContributorBase.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialMessageResolveContributorBase.cs @@ -1,4 +1,6 @@ using LINGYUN.Abp.WeChat.Common.Messages; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using System.Threading.Tasks; namespace LINGYUN.Abp.WeChat.Official.Messages; @@ -6,13 +8,10 @@ public abstract class WeChatOfficialMessageResolveContributorBase : MessageResol { public override Task ResolveAsync(IMessageResolveContext context) { - if (!context.HasMessageKey("AgentID")) - { - return ResolveWeChatMessageAsync(context); - } + var options = context.ServiceProvider.GetRequiredService>().Value; - return Task.CompletedTask; + return ResolveMessageAsync(context, options); } - protected abstract Task ResolveWeChatMessageAsync(IMessageResolveContext context); + protected abstract Task ResolveMessageAsync(IMessageResolveContext context, AbpWeChatOfficialMessageResolveOptions options); } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialMessageResolver.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialMessageResolver.cs new file mode 100644 index 000000000..6ce0d69e5 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Messages/WeChatOfficialMessageResolver.cs @@ -0,0 +1,39 @@ +using LINGYUN.Abp.WeChat.Common.Crypto; +using LINGYUN.Abp.WeChat.Common.Messages; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Official.Messages; +public class WeChatOfficialMessageResolver : MessageResolverBase, IWeChatOfficialMessageResolver +{ + private readonly AbpWeChatOfficialMessageResolveOptions _options; + public WeChatOfficialMessageResolver( + IWeChatCryptoService cryptoService, + IServiceProvider serviceProvider, + IOptions options) + : base(cryptoService, serviceProvider) + { + _options = options.Value; + } + + protected async override Task ResolveMessageAsync(MessageResolveContext context) + { + var result = new MessageResolveResult(context.Origin); + + foreach (var messageResolver in _options.MessageResolvers) + { + await messageResolver.ResolveAsync(context); + + result.AppliedResolvers.Add(messageResolver.Name); + + if (context.HasResolvedMessage()) + { + result.Message = context.Message; + break; + } + } + + return result; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageAppService.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageAppService.cs index 52506017b..4c880252d 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageAppService.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageAppService.cs @@ -1,6 +1,7 @@ using LINGYUN.Abp.WeChat.Common.Crypto; using LINGYUN.Abp.WeChat.Common.Crypto.Models; using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; using LINGYUN.Abp.WeChat.Work.Settings; using Microsoft.Extensions.Logging; using System.Linq; @@ -14,9 +15,9 @@ public class WeChatWorkMessageAppService : ApplicationService, IWeChatWorkMessag { private readonly IWeChatCryptoService _cryptoService; private readonly IDistributedEventBus _distributedEventBus; - private readonly IMessageResolver _messageResolver; + private readonly IWeChatWorkMessageResolver _messageResolver; public WeChatWorkMessageAppService( - IMessageResolver messageResolver, + IWeChatWorkMessageResolver messageResolver, IWeChatCryptoService cryptoService, IDistributedEventBus distributedEventBus) { @@ -82,7 +83,7 @@ public class WeChatWorkMessageAppService : ApplicationService, IWeChatWorkMessag input.Nonce, input.Data); - var result = await _messageResolver.ResolveMessageAsync(messageData); + var result = await _messageResolver.ResolveAsync(messageData); if (result.Message == null) { Logger.LogWarning(input.Data); diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN.Abp.WeChat.Work.Common.csproj b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN.Abp.WeChat.Work.Common.csproj index 71d137f5f..44a9562dc 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN.Abp.WeChat.Work.Common.csproj +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN.Abp.WeChat.Work.Common.csproj @@ -10,6 +10,7 @@ false false false + True diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/AbpWeChatWorkCommonModule.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/AbpWeChatWorkCommonModule.cs index 6e2d64ae6..5fa41c334 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/AbpWeChatWorkCommonModule.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/AbpWeChatWorkCommonModule.cs @@ -29,6 +29,7 @@ public class AbpWeChatWorkCommonModule : AbpModule options.MapEvent("location_select", context => context.GetWeChatMessage()); options.MapEvent("batch_job_result", context => context.GetWeChatMessage()); options.MapEvent("open_approval_change", context => context.GetWeChatMessage()); + options.MapEvent("sys_approval_change", context => context.GetWeChatMessage()); options.MapEvent("share_agent_change", context => context.GetWeChatMessage()); options.MapEvent("share_chain_change", context => context.GetWeChatMessage()); options.MapEvent("template_card_event", context => context.GetWeChatMessage()); @@ -56,6 +57,8 @@ public class AbpWeChatWorkCommonModule : AbpModule _ => throw new AbpWeChatException($"Contact change event {changeType} is not mounted!"), }; }); + options.MapEvent("book_meeting_room", context => context.GetWeChatMessage()); + options.MapEvent("cancel_meeting_room", context => context.GetWeChatMessage()); options.MapMessage("text", context => context.GetWeChatMessage()); options.MapMessage("image", context => context.GetWeChatMessage()); @@ -64,10 +67,7 @@ public class AbpWeChatWorkCommonModule : AbpModule options.MapMessage("shortvideo", context => context.GetWeChatMessage()); options.MapMessage("location", context => context.GetWeChatMessage()); options.MapMessage("link", context => context.GetWeChatMessage()); - }); - Configure(options => - { // 事件处理器 options.MessageResolvers.AddIfNotContains(new WeChatWorkEventResolveContributor()); // 消息处理器 diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/AbpWeChatWorkMessageResolveOptions.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/AbpWeChatWorkMessageResolveOptions.cs index e994957b3..99be696f9 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/AbpWeChatWorkMessageResolveOptions.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/AbpWeChatWorkMessageResolveOptions.cs @@ -5,10 +5,12 @@ using System.Collections.Generic; namespace LINGYUN.Abp.WeChat.Work.Common.Messages; public class AbpWeChatWorkMessageResolveOptions { + public List MessageResolvers { get; } public IDictionary> EventMaps { get; } public IDictionary> MessageMaps { get; } public AbpWeChatWorkMessageResolveOptions() { + MessageResolvers = new List(); EventMaps = new Dictionary>(); MessageMaps = new Dictionary>(); } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/IWeChatWorkMessageResolver.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/IWeChatWorkMessageResolver.cs new file mode 100644 index 000000000..04e57b30b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/IWeChatWorkMessageResolver.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.Common.Messages; +/// +/// 企业微信消息解析器 +/// +public interface IWeChatWorkMessageResolver +{ + /// + /// 解析企业微信消息 + /// + /// 企业微信消息 + /// + Task ResolveAsync(MessageResolveData messageData); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/BatchJobResultEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/BatchJobResultEvent.cs index 504f8ec3c..fc86054cc 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/BatchJobResultEvent.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/BatchJobResultEvent.cs @@ -4,7 +4,7 @@ using Volo.Abp.EventBus; namespace LINGYUN.Abp.WeChat.Work.Common.Messages.Models; /// -/// 进入应用事件 +/// 异步任务完成事件 /// [EventName("batch_job_result")] public class BatchJobResultEvent : WeChatWorkEventMessage diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/BookMeetingRoomEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/BookMeetingRoomEvent.cs new file mode 100644 index 000000000..4efc03105 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/BookMeetingRoomEvent.cs @@ -0,0 +1,30 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using System.Xml.Serialization; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.Common.Messages.Models; +/// +/// 会议室预定事件 +/// +/// +/// 当用户在企业微信预定会议室时,会触发该事件回调给会议室系统应用。 +/// +[EventName("book_meeting_room")] +public class BookMeetingRoomEvent : WeChatWorkEventMessage +{ + /// + /// 会议室id + /// + [XmlElement("MeetingRoomId")] + public int MeetingRoomId { get; set; } + /// + /// 预定id,可根据该ID查询具体的会议预定情况 + /// + [XmlElement("BookingId")] + public string BookingId { get; set; } + + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/CancelMeetingRoomEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/CancelMeetingRoomEvent.cs new file mode 100644 index 000000000..9fa50cadc --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/CancelMeetingRoomEvent.cs @@ -0,0 +1,30 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using System.Xml.Serialization; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.Common.Messages.Models; +/// +/// 会议室取消事件 +/// +/// +/// 当用户在企业微信取消预定会议室时,会触发该事件回调给会议室系统应用;如果该会议室由自建应用预定,除了会议室系统应用外,也会回调给对应的自建应用。 +/// +[EventName("cancel_meeting_room")] +public class CancelMeetingRoomEvent : WeChatWorkEventMessage +{ + /// + /// 会议室id + /// + [XmlElement("MeetingRoomId")] + public int MeetingRoomId { get; set; } + /// + /// 预定id,可根据该ID查询具体的会议预定情况 + /// + [XmlElement("BookingId")] + public string BookingId { get; set; } + + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/SysApprovalStatusChangeEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/SysApprovalStatusChangeEvent.cs new file mode 100644 index 000000000..a08fa5988 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/SysApprovalStatusChangeEvent.cs @@ -0,0 +1,272 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using System; +using System.Collections.Generic; +using System.Xml.Serialization; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.Common.Messages.Models; +/// +/// 企业微信“审批应用”审批状态通知事件 +/// +[EventName("sys_approval_change")] +public class SysApprovalStatusChangeEvent : WeChatWorkEventMessage +{ + /// + /// 审批信息 + /// + [XmlElement("ApprovalInfo")] + public SysApprovalInfo ApprovalInfo { get; set; } + + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} + +public class SysApprovalInfo +{ + /// + /// 审批编号(字符串类型) + /// + [XmlElement("SpNoStr")] + public string SpNoStr { get; set; } + /// + /// 审批申请类型名称(审批模板名称) + /// + [XmlElement("SpName")] + public string SpName { get; set; } + /// + /// 申请单状态:1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付 + /// + [XmlElement("SpStatus")] + public byte SpStatus { get; set; } + /// + /// 审批模板id。可在“获取审批申请详情”、“审批状态变化回调通知”中获得,也可在审批模板的模板编辑页面链接中获得。 + /// + [XmlElement("TemplateId")] + public string TemplateId { get; set; } + /// + /// 审批申请提交时间,Unix时间戳 + /// + [XmlElement("ApplyTime")] + public int ApplyTime { get; set; } + /// + /// 申请人信息 + /// + [XmlElement("Applyer")] + public SysApprovalApplyer Applyer { get; set; } + /// + /// 审批流程信息,可能有多个审批节点。 + /// + [XmlElement("SpRecord")] + public List SpRecord { get; set; } + /// + /// 抄送信息,可能有多个抄送节点 + /// + [XmlElement("Notifyer")] + public List Notifyer { get; set; } + /// + /// 审批申请备注信息,可能有多个备注节点 + /// + [XmlElement("Comments")] + public List Comments { get; set; } + /// + /// 审批流程列表 + /// + [XmlElement("ProcessList")] + public List ProcessList { get; set; } + /// + /// 审批申请状态变化类型:1-提单;2-同意;3-驳回;4-转审;5-催办;6-撤销;8-通过后撤销;10-添加备注;11-回退给指定审批人;12-添加审批人;13-加签并同意; 14-已办理; 15-已转交 + /// + [XmlElement("StatuChangeEvent")] + public byte StatuChangeEvent { get; set; } + /// + /// 审批编号 + /// + /// + /// 局校审批单不返回此字段,其他类型审批单会返回此字段,不推荐使用此字段 + /// + [XmlElement("SpNo")] + [Obsolete("局校审批单不返回此字段,其他类型审批单会返回此字段,不推荐使用此字段")] + public string SpNo { get; set; } +} + +public class SysApprovalApplyer +{ + /// + /// 申请人userid + /// + [XmlElement("UserId")] + public string UserId { get; set; } + /// + /// 申请人所在部门pid + /// + [XmlElement("Party")] + public string Party { get; set; } +} + +public class SysApprovalRecord +{ + /// + /// 审批节点状态:1-审批中;2-已同意;3-已驳回;4-已转审 + /// + [XmlElement("SpStatus")] + public byte SpStatus { get; set; } + /// + /// 节点审批方式:1-或签;2-会签 + /// + [XmlElement("ApproverAttr")] + public byte ApproverAttr { get; set; } + /// + /// 节点审批方式:1-或签;2-会签 + /// + [XmlElement("Details")] + public List Details { get; set; } +} + +public class SysApprovalRecordDetail +{ + /// + /// 分支审批人 + /// + [XmlElement("Approver")] + public SysApprovalApplyer Approver { get; set; } + /// + /// 审批意见字段 + /// + [XmlElement("Speech")] + public string Speech { get; set; } + /// + /// 分支审批人审批状态:1-审批中;2-已同意;3-已驳回;4-已转审 + /// + [XmlElement("SpStatus")] + public byte SpStatus { get; set; } + /// + /// 节点分支审批人审批操作时间,0为尚未操作 + /// + [XmlElement("SpTime")] + public int SpTime { get; set; } + /// + /// 节点分支审批人审批意见附件,赋值为media_id具体使用请参考:文档-获取临时素材 + /// + [XmlElement("Attach")] + public string Attach { get; set; } +} + +public class SysApprovalNotifyer +{ + /// + /// 节点抄送人userid + /// + [XmlElement("UserId")] + public string UserId { get; set; } +} + +public class SysApprovalComment +{ + /// + /// 备注人信息 + /// + [XmlElement("CommentUserInfo")] + public SysApprovalCommenter CommentUserInfo { get; set; } + /// + /// 备注提交时间 + /// + [XmlElement("CommentTime")] + public int CommentTime { get; set; } + /// + /// 备注文本内容 + /// + [XmlElement("CommentContent")] + public string CommentContent { get; set; } + /// + /// 备注id + /// + [XmlElement("CommentId")] + public string CommentId { get; set; } + /// + /// 备注意见附件,值是附件media_id + /// + [XmlElement("Attach")] + public string Attach { get; set; } +} + +public class SysApprovalCommenter +{ + /// + /// 节点抄送人userid + /// + [XmlElement("UserId")] + public string UserId { get; set; } +} + +public class SysApprovalProcess +{ + /// + /// 流程节点 + /// + [XmlElement("NodeList")] + public List NodeList { get; set; } +} + +public class SysApprovalProcessNode +{ + /// + /// 节点类型 1 审批人 2 抄送人 3办理人 + /// + [XmlElement("NodeType")] + public byte NodeType { get; set; } + /// + /// 节点状态 1-审批中;2-同意;3-驳回;4-转审;11-退回给指定审批人;12-加签;13-同意并加签;14-办理;15-转交 + /// + [XmlElement("SpStatus")] + public byte SpStatus { get; set; } + /// + /// 多人办理方式 1-会签;2-或签 3-依次审批 + /// + [XmlElement("ApvRel")] + public byte ApvRel { get; set; } + /// + /// 子节点列表 + /// + [XmlElement("SubNodeList")] + public List SubNodeList { get; set; } +} + +public class SysApprovalProcessSubNode +{ + /// + /// 处理人信息 + /// + [XmlElement("UserInfo")] + public SysApprovalProcesser UserInfo { get; set; } + /// + /// 审批/办理意见 + /// + [XmlElement("Speech")] + public string Speech { get; set; } + /// + /// 子节点状态 1-审批中;2-同意;3-驳回;4-转审;11-退回给指定审批人;12-加签;13-同意并加签;14-办理;15-转交 + /// + [XmlElement("SpYj")] + public byte SpYj { get; set; } + /// + /// 操作时间 + /// + [XmlElement("Sptime")] + public int Sptime { get; set; } + /// + /// 备注意见附件,值是附件media_id + /// + [XmlElement("MediaIds")] + public string MediaIds { get; set; } +} + +public class SysApprovalProcesser +{ + /// + /// 处理人userid + /// + [XmlElement("UserId")] + public string UserId { get; set; } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventMessageEto.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventMessageEto.cs index 3d9d67723..5a72bd16e 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventMessageEto.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventMessageEto.cs @@ -3,7 +3,7 @@ using Volo.Abp.EventBus; namespace LINGYUN.Abp.WeChat.Work.Common.Messages; -[GenericEventName(Prefix = "wechat.work.events")] +[GenericEventName(Prefix = "wechat.work.events.")] public class WeChatWorkEventMessageEto : WeChatMessageEto where TEvent : WeChatWorkEventMessage { diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventResolveContributor.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventResolveContributor.cs index b2974c092..b4d337b00 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventResolveContributor.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventResolveContributor.cs @@ -1,20 +1,17 @@ using LINGYUN.Abp.WeChat.Common.Messages; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using System; using System.Threading.Tasks; namespace LINGYUN.Abp.WeChat.Work.Common.Messages; /// -/// 微信公众号事件处理器 +/// 企业微信事件处理器 /// public class WeChatWorkEventResolveContributor : WeChatWorkMessageResolveContributorBase { public override string Name => "WeChat.Work.Event"; - protected override Task ResolveWeComMessageAsync(IMessageResolveContext context) + protected override Task ResolveMessageAsync(IMessageResolveContext context, AbpWeChatWorkMessageResolveOptions options) { - var options = context.ServiceProvider.GetRequiredService>().Value; var messageType = context.GetMessageData("MsgType"); var eventName = context.GetMessageData("Event"); if ("event".Equals(messageType, StringComparison.InvariantCultureIgnoreCase) && diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkGeneralMessageEto.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkGeneralMessageEto.cs index 734642916..fb143b78c 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkGeneralMessageEto.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkGeneralMessageEto.cs @@ -3,7 +3,7 @@ using Volo.Abp.EventBus; namespace LINGYUN.Abp.WeChat.Work.Common.Messages; -[GenericEventName(Prefix = "wechat.work.messages")] +[GenericEventName(Prefix = "wechat.work.messages.")] public class WeChatWorkGeneralMessageEto : WeChatMessageEto where TMessage : WeChatWorkGeneralMessage { diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributor.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributor.cs index acf5ec6df..0a5be5f72 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributor.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributor.cs @@ -1,19 +1,16 @@ using LINGYUN.Abp.WeChat.Common.Messages; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using System.Threading.Tasks; namespace LINGYUN.Abp.WeChat.Work.Common.Messages; /// -/// 微信公众号消息处理器 +/// 企业微信消息处理器 /// public class WeChatWorkMessageResolveContributor : WeChatWorkMessageResolveContributorBase { public override string Name => "WeChat.Work.Message"; - protected override Task ResolveWeComMessageAsync(IMessageResolveContext context) + protected override Task ResolveMessageAsync(IMessageResolveContext context, AbpWeChatWorkMessageResolveOptions options) { - var options = context.ServiceProvider.GetRequiredService>().Value; var messageType = context.GetMessageData("MsgType"); if (options.MessageMaps.TryGetValue(messageType, out var messageFactory)) { diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributorBase.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributorBase.cs index 7d503ba01..8b45d819e 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributorBase.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributorBase.cs @@ -1,4 +1,6 @@ using LINGYUN.Abp.WeChat.Common.Messages; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using System.Threading.Tasks; namespace LINGYUN.Abp.WeChat.Work.Common.Messages; @@ -6,14 +8,10 @@ public abstract class WeChatWorkMessageResolveContributorBase : MessageResolveCo { public override Task ResolveAsync(IMessageResolveContext context) { - // 企业微信消息需要企业标识字段 - if (context.HasMessageKey("AgentID")) - { - return ResolveWeComMessageAsync(context); - } + var options = context.ServiceProvider.GetRequiredService>().Value; - return Task.CompletedTask; + return ResolveMessageAsync(context, options); } - protected abstract Task ResolveWeComMessageAsync(IMessageResolveContext context); + protected abstract Task ResolveMessageAsync(IMessageResolveContext context, AbpWeChatWorkMessageResolveOptions options); } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolver.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolver.cs new file mode 100644 index 000000000..f361e7f04 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolver.cs @@ -0,0 +1,39 @@ +using LINGYUN.Abp.WeChat.Common.Crypto; +using LINGYUN.Abp.WeChat.Common.Messages; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.Common.Messages; +public class WeChatWorkMessageResolver : MessageResolverBase, IWeChatWorkMessageResolver +{ + private readonly AbpWeChatWorkMessageResolveOptions _options; + public WeChatWorkMessageResolver( + IWeChatCryptoService cryptoService, + IServiceProvider serviceProvider, + IOptions options) + : base(cryptoService, serviceProvider) + { + _options = options.Value; + } + + protected async override Task ResolveMessageAsync(MessageResolveContext context) + { + var result = new MessageResolveResult(context.Origin); + + foreach (var messageResolver in _options.MessageResolvers) + { + await messageResolver.ResolveAsync(context); + + result.AppliedResolvers.Add(messageResolver.Name); + + if (context.HasResolvedMessage()) + { + result.Message = context.Message; + break; + } + } + + return result; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xml b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xsd b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN.Abp.WeChat.Work.ExternalContact.csproj b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN.Abp.WeChat.Work.ExternalContact.csproj new file mode 100644 index 000000000..730bd0b71 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN.Abp.WeChat.Work.ExternalContact.csproj @@ -0,0 +1,27 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0 + LINGYUN.Abp.WeChat.Work.ExternalContact + LINGYUN.Abp.WeChat.Work.ExternalContact + false + false + false + True + enable + + + + + + + + + + + + + diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactModule.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactModule.cs new file mode 100644 index 000000000..dd18a596b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactModule.cs @@ -0,0 +1,78 @@ +using LINGYUN.Abp.WeChat.Common; +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +using LINGYUN.Abp.WeChat.Work.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact; + +[DependsOn(typeof(AbpWeChatWorkModule))] +public class AbpWeChatWorkExternalContactModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + // 企业客户变更事件 + options.MapEvent("change_external_contact", context => + { + var changeType = context.GetMessageData("ChangeType"); + return changeType switch + { + "add_external_contact" => context.GetWeChatMessage(), + "edit_external_contact" => context.GetWeChatMessage(), + "add_half_external_contact" => context.GetWeChatMessage(), + "del_external_contact" => context.GetWeChatMessage(), + "del_follow_user" => context.GetWeChatMessage(), + "transfer_fail" => context.GetWeChatMessage(), + _ => throw new AbpWeChatException($"Contact change event change_external_contact:{changeType} is not mounted!"), + }; + }); + // 客户群变更事件 + options.MapEvent("change_external_chat", context => + { + var changeType = context.GetMessageData("ChangeType"); + switch (changeType) + { + case "create": return context.GetWeChatMessage(); + case "update": + // 客户群变更事件 + var updateDetail = context.GetMessageData("UpdateDetail"); + return updateDetail switch + { + "add_member" => context.GetWeChatMessage(), + "del_member" => context.GetWeChatMessage(), + "change_owner" => context.GetWeChatMessage(), + "change_name" => context.GetWeChatMessage(), + "change_notice" => context.GetWeChatMessage(), + _ => throw new AbpWeChatException($"Contact change event change_external_chat:{changeType}:{updateDetail} is not mounted!"), + }; + case "dismiss": return context.GetWeChatMessage(); + default: throw new AbpWeChatException($"Contact change event change_external_chat:{changeType} is not mounted!"); + } + }); + // 企业客户标签事件 + options.MapEvent("change_external_tag", context => + { + var changeType = context.GetMessageData("ChangeType"); + return changeType switch + { + "create" => context.GetWeChatMessage(), + "update" => context.GetWeChatMessage(), + "delete" => context.GetWeChatMessage(), + "shuffle" => context.GetWeChatMessage(), + _ => throw new AbpWeChatException($"Contact change event change_external_tag:{changeType} is not mounted!"), + }; + }); + }); + + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources"); + }); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/IWeChatWorkAttachmentProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/IWeChatWorkAttachmentProvider.cs new file mode 100644 index 000000000..e35baeb31 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/IWeChatWorkAttachmentProvider.cs @@ -0,0 +1,24 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments; +/// +/// 附件资源接口 +/// +public interface IWeChatWorkAttachmentProvider +{ + /// + /// 上传附件资源 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task UploadAsync( + WeChatWorkUploadAttachmentRequest request, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/AttachmentType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/AttachmentType.cs new file mode 100644 index 000000000..096e7bf8f --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/AttachmentType.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Models; +/// +/// 附件类型 +/// +[Description("附件类型")] +public enum AttachmentType +{ + /// + /// 朋友圈 + /// + [Description("朋友圈")] + Moments = 1, + /// + /// 商品图册 + /// + [Description("商品图册")] + ProductCatalogue = 2, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/MediaType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/MediaType.cs new file mode 100644 index 000000000..d122f663c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/MediaType.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Models; +/// +/// 媒体文件类型 +/// +[Description("媒体文件类型")] +public enum MediaType +{ + /// + /// 图片 + /// + [Description("图片")] + Image = 1, + /// + /// 视频 + /// + [Description("视频")] + Video = 2, + /// + /// 普通文件 + /// + [Description("普通文件")] + File = 3, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Request/WeChatWorkUploadAttachmentRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Request/WeChatWorkUploadAttachmentRequest.cs new file mode 100644 index 000000000..e1ac87d55 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Request/WeChatWorkUploadAttachmentRequest.cs @@ -0,0 +1,41 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Models; +using Volo.Abp; +using Volo.Abp.Content; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Request; +/// +/// 上传附件资源请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkUploadAttachmentRequest : WeChatWorkRequest +{ + /// + /// 媒体文件类型 + /// + [NotNull] + public MediaType MediaType { get; } + /// + /// 附件类型 + /// + [NotNull] + public AttachmentType AttachmentType { get; } + /// + /// 媒体文件 + /// + [NotNull] + public IRemoteStreamContent Content { get; } + public WeChatWorkUploadAttachmentRequest( + MediaType mediaType, + AttachmentType attachmentType, + IRemoteStreamContent content) + { + Check.NotNull(content, nameof(content)); + + MediaType = mediaType; + AttachmentType = attachmentType; + Content = content; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Response/WeChatWorkUploadAttachmentResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Response/WeChatWorkUploadAttachmentResponse.cs new file mode 100644 index 000000000..46f1045f7 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Response/WeChatWorkUploadAttachmentResponse.cs @@ -0,0 +1,35 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Response; +/// +/// 上传附件资源响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkUploadAttachmentResponse : WeChatWorkResponse +{ + /// + /// 媒体文件类型,分别有图片(image)、语音(voice)、视频(video),普通文件(file) + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public string MediaType { get; set; } + /// + /// 媒体文件上传后获取的唯一标识,三天有效,可使用获取临时素材接口获取 + /// + [NotNull] + [JsonProperty("media_id")] + [JsonPropertyName("media_id")] + public string MediaId { get; set; } + /// + /// 媒体文件上传时间戳 + /// + [NotNull] + [JsonProperty("created_at")] + [JsonPropertyName("created_at")] + public long CreatedAt { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/WeChatWorkAttachmentProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/WeChatWorkAttachmentProvider.cs new file mode 100644 index 000000000..d2e9d5610 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/WeChatWorkAttachmentProvider.cs @@ -0,0 +1,41 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Response; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +using LINGYUN.Abp.WeChat.Work.Token; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments; + +[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)] +public class WeChatWorkAttachmentProvider : IWeChatWorkAttachmentProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkAttachmentProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task UploadAsync( + WeChatWorkUploadAttachmentRequest request, + CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.UploadAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/IWeChatWorkExternalContactProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/IWeChatWorkExternalContactProvider.cs new file mode 100644 index 000000000..4efe1d1a1 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/IWeChatWorkExternalContactProvider.cs @@ -0,0 +1,24 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts; +/// +/// 外部联系人接口 +/// +public interface IWeChatWorkExternalContactProvider +{ + /// + /// 获取已服务的外部联系人 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GetExternalContactListAsync( + WeChatWorkGetExternalContactListRequest request, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Models/ExternalContactInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Models/ExternalContactInfo.cs new file mode 100644 index 000000000..2e53003fc --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Models/ExternalContactInfo.cs @@ -0,0 +1,73 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Models; +/// +/// 外部联系人 +/// +public class ExternalContactInfo +{ + /// + /// 是否被成员标记为客户 + /// + [NotNull] + [JsonProperty("is_customer")] + [JsonPropertyName("is_customer")] + public bool IsCustomer { get; set; } + /// + /// 外部联系人临时ID + /// + /// + /// + /// 外部联系人临时id是一个外部联系人的唯一标识,企业可根据此id对外部联系人进行去重统计。
+ /// 但外部联系人临时id仅在一轮遍历查询(从首个分页查询开始到最后一个分页查询完毕)中唯一;
+ /// 每次请求首个数据分页(cursor为空)时,返回的外部联系人临时id和next_cursor将发生变化。 + ///
+ [NotNull] + [JsonProperty("tmp_openid")] + [JsonPropertyName("tmp_openid")] + public string TmpOpenId { get; set; } + /// + /// 外部联系人的externaluserid(如果是客户才返回) + /// + [CanBeNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public string? ExternalUserId { get; set; } + /// + /// 脱敏后的外部联系人昵称(如果是其他外部联系人才返回) + /// + [CanBeNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string? Name { get; set; } + /// + /// 添加此外部联系人的企业成员或外部联系人所在群聊的群主userid + /// + [CanBeNull] + [JsonProperty("follow_userid")] + [JsonPropertyName("follow_userid")] + public string? FollowUserId { get; set; } + /// + /// 外部联系人所在的群聊ID(如果群聊被成员标记为客户群才返回) + /// + [CanBeNull] + [JsonProperty("chat_id")] + [JsonPropertyName("chat_id")] + public string? ChatId { get; set; } + /// + /// 外部联系人所在群聊的群名(如果群聊未被成员标记为客户群才返回) + /// + [CanBeNull] + [JsonProperty("chat_name")] + [JsonPropertyName("chat_name")] + public string? ChatName { get; set; } + /// + /// 外部联系人首次添加/进群的时间 + /// + [NotNull] + [JsonProperty("add_time")] + [JsonPropertyName("add_time")] + public long AddTime { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Request/WeChatWorkGetExternalContactListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Request/WeChatWorkGetExternalContactListRequest.cs new file mode 100644 index 000000000..de2874273 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Request/WeChatWorkGetExternalContactListRequest.cs @@ -0,0 +1,36 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Request; +/// +/// 获取已服务的外部联系人请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetExternalContactListRequest : WeChatWorkRequest +{ + /// + /// 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + /// + /// + /// cursor具有有效期,请勿缓存后使用 + /// + [CanBeNull] + [JsonProperty("cursor")] + [JsonPropertyName("cursor")] + public string? Cursor { get; } + /// + /// 返回的最大记录数,整型,默认为1000 + /// + [CanBeNull] + [JsonProperty("limit")] + [JsonPropertyName("limit")] + public int? Limit { get; } + public WeChatWorkGetExternalContactListRequest(string? cursor = null, int? limit = 1000) + { + Cursor = cursor; + Limit = limit; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Response/WeChatWorkGetExternalContactListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Response/WeChatWorkGetExternalContactListResponse.cs new file mode 100644 index 000000000..4492b93ca --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Response/WeChatWorkGetExternalContactListResponse.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Response; +/// +/// 获取已服务的外部联系人响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetExternalContactListResponse : WeChatWorkResponse +{ + /// + /// 外部联系人列表 + /// + [NotNull] + [JsonProperty("info_list")] + [JsonPropertyName("info_list")] + public ExternalContactInfo[] InfoList { get; set; } + /// + /// 分页游标,再下次请求时填写以获取之后分页的记录,如果已经没有更多的数据则返回空,有效期为4小时 + /// + [CanBeNull] + [JsonProperty("next_cursor")] + [JsonPropertyName("next_cursor")] + public string? NextCursor { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider.cs new file mode 100644 index 000000000..52179841b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider.cs @@ -0,0 +1,44 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Response; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +using LINGYUN.Abp.WeChat.Work.Token; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts; + +[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)] +public class WeChatWorkExternalContactProvider : IWeChatWorkExternalContactProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkExternalContactProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task GetExternalContactListAsync( + WeChatWorkGetExternalContactListRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetExternalContactListAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerProvider.cs new file mode 100644 index 000000000..caba3bf25 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerProvider.cs @@ -0,0 +1,62 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers; +/// +/// 客户管理接口 +/// +public interface IWeChatWorkCustomerProvider +{ + /// + /// 获取客户列表 + /// + /// + /// 详情见: + /// + /// 企业成员的userid + /// + /// + Task GetCustomerListAsync( + string userId, + CancellationToken cancellationToken = default); + /// + /// 批量获取客户详情 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task BulkGetCustomerAsync( + WeChatWorkBulkGetCustomerRequest request, + CancellationToken cancellationToken = default); + /// + /// 获取客户详情 + /// + /// + /// 详情见: + /// + /// 外部联系人的userid,注意不是企业成员的账号 + /// 上次请求返回的next_cursor + /// + /// + Task GetCustomerAsync( + string externalUserid, + string? cursor = null, + CancellationToken cancellationToken = default); + /// + /// 修改客户备注信息 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task UpdateCustomerRemarkAsync( + WeChatWorkUpdateCustomerRemarkRequest request, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerStrategyProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerStrategyProvider.cs new file mode 100644 index 000000000..c7dd5cead --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerStrategyProvider.cs @@ -0,0 +1,87 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers; +/// +/// 客户联系规则组管理 +/// +/// +/// 详情见: +/// +public interface IWeChatWorkCustomerStrategyProvider +{ + /// + /// 获取规则组列表 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GetCustomerStrategyListAsync( + WeChatWorkGetCustomerStrategyListRequest request, + CancellationToken cancellationToken = default); + /// + /// 获取规则组 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GetCustomerStrategyAsync( + WeChatWorkGetCustomerStrategyRequest request, + CancellationToken cancellationToken = default); + /// + /// 获取规则组管理范围 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GetCustomerStrategyRangeAsync( + WeChatWorkGetCustomerStrategyRangeRequest request, + CancellationToken cancellationToken = default); + /// + /// 创建新的规则组 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task CreateCustomerStrategyAsync( + WeChatWorkCreateCustomerStrategyRequest request, + CancellationToken cancellationToken = default); + /// + /// 编辑规则组及其管理范围 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task UpdateCustomerStrategyAsync( + WeChatWorkUpdateCustomerStrategyRequest request, + CancellationToken cancellationToken = default); + /// + /// 删除规则组 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task DeleteCustomerStrategyAsync( + WeChatWorkDeleteCustomerStrategyRequest request, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategy.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategy.cs new file mode 100644 index 000000000..27b81293a --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategy.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 规则组 +/// +public class CustomerStrategy +{ + /// + /// 规则组id + /// + [NotNull] + [JsonProperty("strategy_id")] + [JsonPropertyName("strategy_id")] + public int StrategyId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyInfo.cs new file mode 100644 index 000000000..dec0bdb1f --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyInfo.cs @@ -0,0 +1,46 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 规则组详情 +/// +public class CustomerStrategyInfo : CustomerStrategy +{ + /// + /// 父规则组id, 如果当前规则组没父规则组,则为0 + /// + [NotNull] + [JsonProperty("parent_id")] + [JsonPropertyName("parent_id")] + public int ParentId { get; set; } + /// + /// 规则组名称 + /// + [NotNull] + [JsonProperty("strategy_name")] + [JsonPropertyName("strategy_name")] + public string StrategyName { get; set; } + /// + /// 规则组创建时间戳 + /// + [NotNull] + [JsonProperty("create_time")] + [JsonPropertyName("create_time")] + public long CreateTime { get; set; } + /// + /// 规则组管理员userid列表 + /// + [NotNull] + [JsonProperty("admin_list")] + [JsonPropertyName("admin_list")] + public string[] AdminList { get; set; } + /// + /// 规则组权限 + /// + [NotNull] + [JsonProperty("privilege")] + [JsonPropertyName("privilege")] + public CustomerStrategyPrivilege Privilege { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyPrivilege.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyPrivilege.cs new file mode 100644 index 000000000..03ce9e057 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyPrivilege.cs @@ -0,0 +1,183 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 规则组权限 +/// +public class CustomerStrategyPrivilege +{ + /// + /// 查看客户列表,基础权限,不可取消 + /// + [NotNull] + [JsonProperty("view_customer_list")] + [JsonPropertyName("view_customer_list")] + public bool ViewCustomerList { get; set; } + /// + /// 查看客户统计数据,基础权限,不可取消 + /// + [NotNull] + [JsonProperty("view_customer_data")] + [JsonPropertyName("view_customer_data")] + public bool ViewCustomerData { get; set; } + /// + /// 查看群聊列表,基础权限,不可取消 + /// + [NotNull] + [JsonProperty("view_room_list")] + [JsonPropertyName("view_room_list")] + public bool ViewRoomList { get; set; } + /// + /// 可使用联系我,基础权限,不可取消 + /// + [NotNull] + [JsonProperty("contact_me")] + [JsonPropertyName("contact_me")] + public bool ContactMe { get; set; } + /// + /// 可加入群聊,基础权限,不可取消 + /// + [NotNull] + [JsonProperty("join_room")] + [JsonPropertyName("join_room")] + public bool JoinRoom { get; set; } + /// + /// 允许分享客户给其他成员,默认为true + /// + [NotNull] + [JsonProperty("share_customer")] + [JsonPropertyName("share_customer")] + public bool ShareCustomer { get; set; } + /// + /// 允许分配离职成员客户,默认为true + /// + [NotNull] + [JsonProperty("oper_resign_customer")] + [JsonPropertyName("oper_resign_customer")] + public bool OperResignCustomer { get; set; } + /// + /// 允许分配离职成员客户群,默认为true + /// + [NotNull] + [JsonProperty("oper_resign_group")] + [JsonPropertyName("oper_resign_group")] + public bool OperResignGroup { get; set; } + /// + /// 允许给企业客户发送消息,默认为true + /// + [NotNull] + [JsonProperty("send_customer_msg")] + [JsonPropertyName("send_customer_msg")] + public bool SendCustomerMsg { get; set; } + /// + /// 允许配置欢迎语,默认为true + /// + [NotNull] + [JsonProperty("edit_welcome_msg")] + [JsonPropertyName("edit_welcome_msg")] + public bool EditWelcomeMsg { get; set; } + /// + /// 允许查看成员联系客户统计,默认为true + /// + [NotNull] + [JsonProperty("view_behavior_data")] + [JsonPropertyName("view_behavior_data")] + public bool ViewBehaviorData { get; set; } + /// + /// 允许查看群聊数据统计,默认为true + /// + [NotNull] + [JsonProperty("view_room_data")] + [JsonPropertyName("view_room_data")] + public bool ViewRoomData { get; set; } + /// + /// 允许发送消息到企业的客户群,默认为true + /// + [NotNull] + [JsonProperty("send_group_msg")] + [JsonPropertyName("send_group_msg")] + public bool SendGroupMsg { get; set; } + /// + /// 允许对企业客户群进行去重,默认为true + /// + [NotNull] + [JsonProperty("room_deduplication")] + [JsonPropertyName("room_deduplication")] + public bool RoomDeduplication { get; set; } + /// + /// 配置快捷回复,默认为true + /// + [NotNull] + [JsonProperty("rapid_reply")] + [JsonPropertyName("rapid_reply")] + public bool RapidReply { get; set; } + /// + /// 转接在职成员的客户,默认为true + /// + [NotNull] + [JsonProperty("onjob_customer_transfer")] + [JsonPropertyName("onjob_customer_transfer")] + public bool OnjobCustomerTransfer { get; set; } + /// + /// 编辑企业成员防骚扰规则,默认为true + /// + [NotNull] + [JsonProperty("edit_anti_spam_rule")] + [JsonPropertyName("edit_anti_spam_rule")] + public bool EditAntiSpamRule { get; set; } + /// + /// 导出客户列表,默认为true + /// + [NotNull] + [JsonProperty("export_customer_list")] + [JsonPropertyName("export_customer_list")] + public bool ExportCustomerList { get; set; } + /// + /// 导出成员客户统计,默认为true + /// + [NotNull] + [JsonProperty("export_customer_data")] + [JsonPropertyName("export_customer_data")] + public bool ExportCustomerData { get; set; } + /// + /// 导出客户群列表,默认为true + /// + [NotNull] + [JsonProperty("export_customer_group_list")] + [JsonPropertyName("export_customer_group_list")] + public bool ExportCustomerGroupList { get; set; } + /// + /// 配置企业客户标签,默认为true + /// + [NotNull] + [JsonProperty("manage_customer_tag")] + [JsonPropertyName("manage_customer_tag")] + public bool ManageCustomerTag { get; set; } + + public static CustomerStrategyPrivilege Default() + { + return new CustomerStrategyPrivilege + { + ViewCustomerList = true, + ViewCustomerData = true, + ViewRoomList = true, + ContactMe = true, + JoinRoom = true, + ShareCustomer = true, + EditWelcomeMsg = true, + ViewBehaviorData = true, + ViewRoomData = true, + SendGroupMsg = true, + RoomDeduplication = true, + RapidReply = true, + OnjobCustomerTransfer = true, + EditAntiSpamRule = true, + ExportCustomerList = true, + ExportCustomerData = true, + ExportCustomerGroupList = true, + ManageCustomerTag = true, + }; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRange.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRange.cs new file mode 100644 index 000000000..f02127f27 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRange.cs @@ -0,0 +1,61 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 规则组管理范围 +/// +public class CustomerStrategyRange +{ + /// + /// 节点类型 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public CustomerStrategyRangeType Type { get; set; } + /// + /// 管理范围内配置的成员userid,仅 Type为 时返回 + /// + [CanBeNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string? UserId { get; set; } + /// + /// 管理范围内配置的部门partyid,仅 Type为 时返回 + /// + [CanBeNull] + [JsonProperty("partyid")] + [JsonPropertyName("partyid")] + public int? PartyId { get; set; } + + public CustomerStrategyRange() + { + + } + + private CustomerStrategyRange( + CustomerStrategyRangeType type, + string? userId = null, + int? partyId = null) + { + Type = type; + UserId = userId; + PartyId = partyId; + } + + public static CustomerStrategyRange Member(string userId) + { + Check.NotNullOrWhiteSpace(userId, nameof(userId)); + + return new CustomerStrategyRange(CustomerStrategyRangeType.Member, userId); + } + public static CustomerStrategyRange Party(int partyId) + { + Check.NotDefaultOrNull(partyId, nameof(partyId)); + + return new CustomerStrategyRange(CustomerStrategyRangeType.Part, partyId: partyId); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRangeType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRangeType.cs new file mode 100644 index 000000000..eb99edafd --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRangeType.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 节点类型 +/// +[Description("节点类型")] +public enum CustomerStrategyRangeType +{ + /// + /// 成员 + /// + [Description("成员")] + Member = 1, + /// + /// 部门 + /// + [Description("部门")] + Part = 2 +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactGender.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactGender.cs new file mode 100644 index 000000000..609f42df1 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactGender.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 外部联系人性别 +/// +[Description("外部联系人性别")] +public enum ExternalContactGender +{ + /// + /// 未知 + /// + [Description("未知")] + None = 0, + /// + /// 男性 + /// + [Description("男性")] + Male = 1, + /// + /// 女性 + /// + [Description("女性")] + FeMale = 2, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactInfo.cs new file mode 100644 index 000000000..cf26d5659 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactInfo.cs @@ -0,0 +1,88 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 外部联系人信息 +/// +public class ExternalContactInfo +{ + /// + /// 外部联系人的userid + /// + [NotNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public string ExternalUserId { get; set; } + /// + /// 外部联系人的名称 + /// + /// + /// 如果是微信用户,则返回其微信昵称。
+ /// 如果是企业微信联系人,则返回其设置对外展示的别名或实名 + ///
+ [NotNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string Name { get; set; } + /// + /// 外部联系人头像 + /// + [CanBeNull] + [JsonProperty("avatar")] + [JsonPropertyName("avatar")] + public string? Avatar { get; set; } + /// + /// 外部联系人的类型 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public ExternalContactType Type { get; set; } + /// + /// 外部联系人性别 + /// + [NotNull] + [JsonProperty("gender")] + [JsonPropertyName("gender")] + public ExternalContactGender Gender { get; set; } + /// + /// 外部联系人在微信开放平台的唯一身份标识(微信unionid),通过此字段企业可将外部联系人与公众号/小程序用户关联起来。
+ /// 仅当联系人类型是微信用户,且企业绑定了微信开发者ID有此字段 + ///
+ [CanBeNull] + [JsonProperty("unionid")] + [JsonPropertyName("unionid")] + public string? UnionId { get; set; } + /// + /// 外部联系人的职位,如果外部企业或用户选择隐藏职位,则不返回,仅当联系人类型是企业微信用户时有此字段 + /// + [CanBeNull] + [JsonProperty("position")] + [JsonPropertyName("position")] + public string? Position { get; set; } + /// + /// 外部联系人所在企业的简称,仅当联系人类型是企业微信用户时有此字段 + /// + [CanBeNull] + [JsonProperty("corp_name")] + [JsonPropertyName("corp_name")] + public string? CorpName { get; set; } + /// + /// 外部联系人所在企业的主体名称,仅当联系人类型是企业微信用户时有此字段
+ /// 仅企业自建应用可获取 + ///
+ [CanBeNull] + [JsonProperty("corp_full_name")] + [JsonPropertyName("corp_full_name")] + public string? CorpFullName { get; set; } + /// + /// 外部联系人的自定义展示信息 + /// + [CanBeNull] + [JsonProperty("external_profile")] + [JsonPropertyName("external_profile")] + public ExternalProfile? ExternalProfile { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactList.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactList.cs new file mode 100644 index 000000000..7ace31afb --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactList.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 外部联系人列表 +/// +public class ExternalContactList +{ + /// + /// 客户的基本信息 + /// + [NotNull] + [JsonProperty("external_contact")] + [JsonPropertyName("external_contact")] + public ExternalContactInfo ExternalContact { get; set; } + /// + /// 企业成员客户跟进信息 + /// + [NotNull] + [JsonProperty("follow_info")] + [JsonPropertyName("follow_info")] + public FollowUser FollowUser { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactType.cs new file mode 100644 index 000000000..7545230a7 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactType.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 外部联系人类型 +/// +[Description("外部联系人类型")] +public enum ExternalContactType +{ + /// + /// 微信用户 + /// + [Description("微信用户")] + WeChat = 1, + /// + /// 企业微信用户 + /// + [Description("企业微信用户")] + WeChatWork = 2, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUser.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUser.cs new file mode 100644 index 000000000..6225a3b76 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUser.cs @@ -0,0 +1,92 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 添加了外部联系人的企业成员 +/// +public class FollowUser +{ + /// + /// 添加了此外部联系人的企业成员userid + /// + [NotNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; set; } + /// + /// 发起添加的userid
+ /// 如果成员主动添加,为成员的userid;
+ /// 如果是客户主动添加,则为客户的外部联系人userid;
+ /// 如果是内部成员共享/管理员分配,则为对应的成员/管理员userid + ///
+ [NotNull] + [JsonProperty("oper_userid")] + [JsonPropertyName("oper_userid")] + public string OperUserId { get; set; } + /// + /// 企业自定义的state参数,用于区分客户具体是通过哪个「联系我」或获客链接添加 + /// + [CanBeNull] + [JsonProperty("state")] + [JsonPropertyName("state")] + public string? State { get; set; } + /// + /// 该成员对此外部联系人的备注 + /// + [CanBeNull] + [JsonProperty("remark")] + [JsonPropertyName("remark")] + public string? Remark { get; set; } + /// + /// 该成员对此外部联系人的描述 + /// + [CanBeNull] + [JsonProperty("description")] + [JsonPropertyName("description")] + public string? Description { get; set; } + /// + /// 该成员添加此外部联系人的时间 + /// + [NotNull] + [JsonProperty("createtime")] + [JsonPropertyName("createtime")] + public long Createtime { get; set; } + /// + /// 外部联系人所打标签列表 + /// + [CanBeNull] + [JsonProperty("tags")] + [JsonPropertyName("tags")] + public List? Tags { get; set; } + /// + /// 该成员对此微信客户备注的企业名称(仅微信客户有该字段) + /// + [CanBeNull] + [JsonProperty("remark_corp_name")] + [JsonPropertyName("remark_corp_name")] + public string? RemarkCorpName { get; set; } + /// + /// 该成员对此客户备注的手机号码,代开发自建应用需要管理员授权才可以获取 + /// + [CanBeNull] + [JsonProperty("remark_mobiles")] + [JsonPropertyName("remark_mobiles")] + public List? RemarkMobiles { get; set; } + /// + /// 该成员添加此客户的来源 + /// + [NotNull] + [JsonProperty("add_way")] + [JsonPropertyName("add_way")] + public FollowUserAddWay AddWay { get; set; } + /// + /// 该成员添加此客户的来源add_way为10时,对应的视频号信息 + /// + [CanBeNull] + [JsonProperty("wechat_channels")] + [JsonPropertyName("wechat_channels")] + public FollowUserWechatChannel? WechatChannel { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserAddWay.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserAddWay.cs new file mode 100644 index 000000000..79caed25c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserAddWay.cs @@ -0,0 +1,125 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 客户来源 +/// +[Description("客户来源")] +public enum FollowUserAddWay +{ + /// + /// 未知来源 + /// + [Description("未知来源")] + None = 0, + /// + /// 扫描二维码 + /// + [Description("扫描二维码")] + ScanQrCode = 1, + /// + /// 搜索手机号 + /// + [Description("搜索手机号")] + SearchPhoneNumber = 2, + /// + /// 名片分享 + /// + [Description("名片分享")] + SharedCard = 3, + /// + /// 群聊 + /// + [Description("群聊")] + GroupChat = 4, + /// + /// 手机通讯录 + /// + [Description("手机通讯录")] + PhoneBook = 5, + /// + /// 微信联系人 + /// + [Description("微信联系人")] + WeChatContact = 6, + /// + /// 安装第三方应用时自动添加的客服人员 + /// + [Description("安装第三方应用时自动添加的客服人员")] + InstallThirdPartyApp = 8, + /// + /// 搜索邮箱 + /// + [Description("搜索邮箱")] + SearchEmail = 9, + /// + /// 视频号添加 + /// + [Description("视频号添加")] + WechatChannel = 10, + /// + /// 通过日程参与人添加 + /// + [Description("通过日程参与人添加")] + SchedulePart = 11, + /// + /// 通过会议参与人添加 + /// + [Description("通过会议参与人添加")] + MeetPart = 12, + /// + /// 添加微信好友对应的企业微信 + /// + [Description("添加微信好友对应的企业微信")] + WeChatFriend = 13, + /// + /// 通过智慧硬件专属客服添加 + /// + [Description("通过智慧硬件专属客服添加")] + SmartHardware = 14, + /// + /// 通过上门服务客服添加 + /// + [Description("通过上门服务客服添加")] + DoorService = 15, + /// + /// 通过获客链接添加 + /// + [Description("通过获客链接添加")] + CustomerAcqLink = 16, + /// + /// 通过定制开发添加 + /// + [Description("通过定制开发添加")] + CustomDevelopment = 17, + /// + /// 通过需求回复添加 + /// + [Description("通过需求回复添加")] + DemandResponse = 18, + /// + /// 通过第三方售前客服添加 + /// + [Description("通过第三方售前客服添加")] + ThirdPartyPreSales = 21, + /// + /// 通过可能的商务伙伴添加 + /// + [Description("通过可能的商务伙伴添加")] + PotentialBusPart = 22, + /// + /// 通过接受微信账号收到的好友申请添加 + /// + [Description("通过接受微信账号收到的好友申请添加")] + WeChatFriendRequest = 24, + /// + /// 内部成员共享 + /// + [Description("内部成员共享")] + SharedByInternalMembers = 201, + /// + /// 管理员/负责人分配 + /// + [Description("管理员/负责人分配")] + AllocationByAdmin = 202 +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTag.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTag.cs new file mode 100644 index 000000000..d11bfbd9c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTag.cs @@ -0,0 +1,39 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 外部联系人所打标签 +/// +public class FollowUserTag +{ + /// + /// 该成员添加此外部联系人所打标签的分组名称 + /// + [CanBeNull] + [JsonProperty("group_name")] + [JsonPropertyName("group_name")] + public string? GroupName { get; set; } + /// + /// 该成员添加此外部联系人所打标签名称 + /// + [NotNull] + [JsonProperty("tag_name")] + [JsonPropertyName("tag_name")] + public string TagName { get; set; } + /// + /// 该成员添加此外部联系人所打标签类型 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public FollowUserTagType Type { get; set; } + /// + /// 该成员添加此外部联系人所打企业标签的id,用户自定义类型标签(type=2)不返回 + /// + [CanBeNull] + [JsonProperty("tag_id")] + [JsonPropertyName("tag_id")] + public string? TagId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTagType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTagType.cs new file mode 100644 index 000000000..d8a91a4f6 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTagType.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 标签类型 +/// +[Description("标签类型")] +public enum FollowUserTagType +{ + /// + /// 企业设置 + /// + [Description("企业设置")] + EnterpriseSettings = 1, + /// + /// 用户自定义 + /// + [Description("用户自定义")] + UserCustom = 2, + /// + /// 规则组标签 + /// + [Description("规则组标签")] + RuleGroupTags = 3, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannel.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannel.cs new file mode 100644 index 000000000..b1a1b6b26 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannel.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 视频号信息 +/// +public class FollowUserWechatChannel +{ + /// + /// 视频号名称 + /// + [NotNull] + [JsonProperty("nickname")] + [JsonPropertyName("nickname")] + public string NickName { get; set; } + /// + /// 视频号添加场景 + /// + [NotNull] + [JsonProperty("source")] + [JsonPropertyName("source")] + public FollowUserWechatChannelSource Source { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannelSource.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannelSource.cs new file mode 100644 index 000000000..fa8eb3433 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannelSource.cs @@ -0,0 +1,30 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +/// +/// 视频号添加场景 +/// +[Description("视频号添加场景")] +public enum FollowUserWechatChannelSource +{ + /// + /// 未知 + /// + [Description("未知")] + None = 0, + /// + /// 视频号主页 + /// + [Description("视频号主页")] + Home = 1, + /// + /// 视频号直播间 + /// + [Description("视频号直播间")] + LiveRoom = 2, + /// + /// 视频号留资服务 + /// + [Description("视频号留资服务")] + RetentionService = 3 +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkBulkGetCustomerRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkBulkGetCustomerRequest.cs new file mode 100644 index 000000000..48b84ab1a --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkBulkGetCustomerRequest.cs @@ -0,0 +1,55 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +/// +/// 创建新的规则组请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkBulkGetCustomerRequest : WeChatWorkRequest +{ + /// + /// 企业成员的userid列表,字符串类型,最多支持100个 + /// + [NotNull] + [JsonProperty("userid_list")] + [JsonPropertyName("userid_list")] + public List UserIds { get; } + /// + /// 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + /// + [CanBeNull] + [JsonProperty("cursor")] + [JsonPropertyName("cursor")] + public string? Cursor { get; } + /// + /// 返回的最大记录数,整型,最大值100,默认值50,超过最大值时取最大值 + /// + [CanBeNull] + [JsonProperty("limit")] + [JsonPropertyName("limit")] + public int Limit { get; } + public WeChatWorkBulkGetCustomerRequest( + List userIds, + string? cursor = null, + int limit = 50) + { + Check.NotNullOrEmpty(userIds, nameof(userIds)); + Check.Range(limit, nameof(limit), 1, 100); + + if (userIds.Count > 100) + { + throw new ArgumentException("The maximum number of userIds allowed in the list is only 100!"); + } + + UserIds = userIds; + Cursor = cursor; + Limit = limit; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkCreateCustomerStrategyRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkCreateCustomerStrategyRequest.cs new file mode 100644 index 000000000..1fa6506cb --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkCreateCustomerStrategyRequest.cs @@ -0,0 +1,74 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +using Newtonsoft.Json; +using System; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +/// +/// 批量获取客户详情请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkCreateCustomerStrategyRequest : WeChatWorkRequest +{ + /// + /// 父规则组id + /// + [CanBeNull] + [JsonProperty("parent_id")] + [JsonPropertyName("parent_id")] + public int? ParentId { get; set; } + /// + /// 规则组名称 + /// + [NotNull] + [JsonProperty("strategy_name")] + [JsonPropertyName("strategy_name")] + public string StrategyName { get; } + /// + /// 规则组管理员userid列表 + /// + [NotNull] + [JsonProperty("admin_list")] + [JsonPropertyName("admin_list")] + public string[] AdminList { get; } + /// + /// 规则组权限 + /// + [NotNull] + [JsonProperty("privilege")] + [JsonPropertyName("privilege")] + public CustomerStrategyPrivilege Privilege { get; } + /// + /// 规则组管理范围 + /// + [NotNull] + [JsonProperty("range")] + [JsonPropertyName("range")] + public CustomerStrategyRange[] Range { get; } + public WeChatWorkCreateCustomerStrategyRequest( + string strategyName, + string[] adminList, + CustomerStrategyPrivilege? privilege = null, + CustomerStrategyRange[]? range = null) + { + Check.NotNullOrWhiteSpace(strategyName, nameof(strategyName)); + Check.NotNullOrEmpty(adminList, nameof(adminList)); + + if (adminList.Length > 20) + { + throw new ArgumentException("Up to 20 admin list can be configured at a time!"); + } + if (range != null && range.Length > 100) + { + throw new ArgumentException("Up to 100 management range can be configured at a time!"); + } + + StrategyName = strategyName; + AdminList = adminList; + Privilege = privilege ?? CustomerStrategyPrivilege.Default(); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkDeleteCustomerStrategyRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkDeleteCustomerStrategyRequest.cs new file mode 100644 index 000000000..961e2fed6 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkDeleteCustomerStrategyRequest.cs @@ -0,0 +1,28 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +/// +/// 删除规则组请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkDeleteCustomerStrategyRequest : WeChatWorkRequest +{ + /// + /// 规则组id + /// + [NotNull] + [JsonProperty("strategy_id")] + [JsonPropertyName("strategy_id")] + public int StrategyId { get; } + public WeChatWorkDeleteCustomerStrategyRequest(int strategyId) + { + Check.NotDefaultOrNull(strategyId, nameof(strategyId)); + + StrategyId = strategyId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyListRequest.cs new file mode 100644 index 000000000..1b5240c7b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyListRequest.cs @@ -0,0 +1,39 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +/// +/// 获取规则组列表请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetCustomerStrategyListRequest : WeChatWorkRequest +{ + /// + /// 分页查询游标,首次调用可不填 + /// + [CanBeNull] + [JsonProperty("cursor")] + [JsonPropertyName("cursor")] + public string? Cursor { get; } + /// + /// 分页大小,默认为1000,最大不超过1000 + /// + [CanBeNull] + [JsonProperty("limit")] + [JsonPropertyName("limit")] + public int? Limit { get; } + public WeChatWorkGetCustomerStrategyListRequest(string? cursor = null, int? limit = 1000) + { + if (limit.HasValue) + { + Check.Range(limit.Value, nameof(limit), 1, 1000); + } + + Cursor = cursor; + Limit = limit; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRangeRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRangeRequest.cs new file mode 100644 index 000000000..415990088 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRangeRequest.cs @@ -0,0 +1,47 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +/// +/// 获取规则组管理范围请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetCustomerStrategyRangeRequest : WeChatWorkRequest +{ + /// + /// 规则组id + /// + [NotNull] + [JsonProperty("strategy_id")] + [JsonPropertyName("strategy_id")] + public int StrategyId { get; } + /// + /// 分页查询游标,首次调用可不填 + /// + [CanBeNull] + [JsonProperty("cursor")] + [JsonPropertyName("cursor")] + public string? Cursor { get; } + /// + /// 每个分页的成员/部门节点数,默认为1000,最大为1000 + /// + [CanBeNull] + [JsonProperty("limit")] + [JsonPropertyName("limit")] + public int? Limit { get; } + public WeChatWorkGetCustomerStrategyRangeRequest(int strategyId, string? cursor = null, int? limit = 1000) + { + if (limit.HasValue) + { + Check.Range(limit.Value, nameof(limit), 1, 1000); + } + + StrategyId = strategyId; + Cursor = cursor; + Limit = limit; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRequest.cs new file mode 100644 index 000000000..1efbfb196 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRequest.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +/// +/// 获取规则组请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetCustomerStrategyRequest : WeChatWorkRequest +{ + /// + /// 规则组id + /// + [NotNull] + [JsonProperty("strategy_id")] + [JsonPropertyName("strategy_id")] + public int StrategyId { get; } + public WeChatWorkGetCustomerStrategyRequest(int strategyId) + { + StrategyId = strategyId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerRemarkRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerRemarkRequest.cs new file mode 100644 index 000000000..9501a670d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerRemarkRequest.cs @@ -0,0 +1,86 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +/// +/// 修改客户备注信息请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkUpdateCustomerRemarkRequest : WeChatWorkRequest +{ + /// + /// 企业成员的userid + /// + [NotNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; } + /// + /// 外部联系人userid + /// + [NotNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public string ExternalUserId { get; } + /// + /// 此用户对外部联系人的备注,最多20个字符 + /// + [CanBeNull] + [StringLength(20)] + [JsonProperty("remark")] + [JsonPropertyName("remark")] + public string? Remark { get; } + /// + /// 此用户对外部联系人的描述,最多150个字符 + /// + [CanBeNull] + [StringLength(150)] + [JsonProperty("description")] + [JsonPropertyName("description")] + public string? Description { get; } + /// + /// 此用户对外部联系人备注的所属公司名称,最多20个字符 + /// + [CanBeNull] + [StringLength(20)] + [JsonProperty("remark_company")] + [JsonPropertyName("remark_company")] + public string? RemarkCompany { get; } + /// + /// 此用户对外部联系人备注的手机号 + /// + [CanBeNull] + [JsonProperty("remark_mobiles")] + [JsonPropertyName("remark_mobiles")] + public List? RemarkMobiles { get; } + /// + /// 备注图片的mediaid + /// + [CanBeNull] + [JsonProperty("remark_pic_mediaid")] + [JsonPropertyName("remark_pic_mediaid")] + public string? RemarkPictureMediaId { get; } + public WeChatWorkUpdateCustomerRemarkRequest( + string userId, + string externalUserId, + string? remark = null, + string? description = null, + string? remarkCompany = null, + List? remarkMobiles = null, + string? remarkPictureMediaId = null) + { + UserId = Check.NotNullOrWhiteSpace(userId, nameof(userId)); + ExternalUserId = Check.NotNullOrWhiteSpace(externalUserId, nameof(externalUserId)); + Remark = Check.Length(remark, nameof(remark), 20); + Description = Check.Length(description, nameof(description), 150); + RemarkCompany = Check.Length(remarkCompany, nameof(remarkCompany), 20); + RemarkMobiles = remarkMobiles; + RemarkPictureMediaId = remarkPictureMediaId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerStrategyRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerStrategyRequest.cs new file mode 100644 index 000000000..5167d3130 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerStrategyRequest.cs @@ -0,0 +1,75 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +/// +/// 编辑规则组及其管理范围请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkUpdateCustomerStrategyRequest : WeChatWorkRequest +{ + /// + /// 规则组id + /// + [NotNull] + [JsonProperty("strategy_id")] + [JsonPropertyName("strategy_id")] + public int StrategyId { get; } + /// + /// 规则组名称 + /// + [CanBeNull] + [JsonProperty("strategy_name")] + [JsonPropertyName("strategy_name")] + public string? StrategyName { get; set; } + /// + /// 规则组管理员userid列表 + /// + [CanBeNull] + [JsonProperty("admin_list")] + [JsonPropertyName("admin_list")] + public string[]? AdminList { get; set; } + /// + /// 规则组权限 + /// + [CanBeNull] + [JsonProperty("privilege")] + [JsonPropertyName("privilege")] + public CustomerStrategyPrivilege? Privilege { get; set; } + /// + /// 新增管理范围 + /// + [NotNull] + [JsonProperty("range_add")] + [JsonPropertyName("range_add")] + public List CreateRange { get; private set; } + /// + /// 删除管理范围 + /// + [NotNull] + [JsonProperty("range_del")] + [JsonPropertyName("range_del")] + public List DeleteRange { get; private set; } + public WeChatWorkUpdateCustomerStrategyRequest( + int strategyId, + string? strategyName = null, + string[]? adminList = null, + CustomerStrategyPrivilege? privilege = null) + { + Check.NotDefaultOrNull(strategyId, nameof(strategyId)); + + StrategyId = strategyId; + StrategyName = strategyName; + AdminList = adminList; + Privilege = privilege; + + CreateRange = new List(); + DeleteRange = new List(); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkBulkGetCustomerResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkBulkGetCustomerResponse.cs new file mode 100644 index 000000000..75780677a --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkBulkGetCustomerResponse.cs @@ -0,0 +1,48 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response; +/// +/// 批量获取客户详情响应参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkBulkGetCustomerResponse : WeChatWorkResponse +{ + /// + /// 外部联系人的userid列表 + /// + [NotNull] + [JsonProperty("external_contact_list")] + [JsonPropertyName("external_contact_list")] + public List ExternalUserId { get; set; } = new List(); + /// + /// 分页游标,再下次请求时填写以获取之后分页的记录,如果已经没有更多的数据则返回空 + /// + [CanBeNull] + [JsonProperty("next_cursor")] + [JsonPropertyName("next_cursor")] + public string? NextCursor { get; set; } + /// + /// 若请求中所有userid都无有效互通许可,接口直接报错701008。如果部分userid无有效互通许可,接口返回成功 + /// + [CanBeNull] + [JsonProperty("fail_info")] + [JsonPropertyName("fail_info")] + public WeChatWorkBulkGetCustomerFailInfo? FailInfo { get; set; } +} + +public class WeChatWorkBulkGetCustomerFailInfo +{ + /// + /// 无许可的userid列表 + /// + [NotNull] + [JsonProperty("next_cursor")] + [JsonPropertyName("next_cursor")] + public List UnlicensedUseridList { get; set; } = new List(); +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkCreateCustomerStrategyResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkCreateCustomerStrategyResponse.cs new file mode 100644 index 000000000..f6aec2f00 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkCreateCustomerStrategyResponse.cs @@ -0,0 +1,21 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response; +/// +/// 创建新的规则组响应参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkCreateCustomerStrategyResponse : WeChatWorkResponse +{ + /// + /// 规则组id + /// + [NotNull] + [JsonProperty("strategy_id")] + [JsonPropertyName("strategy_id")] + public int StrategyId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerListResponse.cs new file mode 100644 index 000000000..4f16db664 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerListResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response; +/// +/// 获取客户列表响应参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetCustomerListResponse : WeChatWorkResponse +{ + /// + /// 外部联系人的userid列表 + /// + [NotNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public List ExternalUserId { get; set; } = new List(); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerResponse.cs new file mode 100644 index 000000000..d465abe38 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerResponse.cs @@ -0,0 +1,37 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response; +/// +/// 获取客户详情响应参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetCustomerResponse : WeChatWorkResponse +{ + /// + /// 外部联系人信息 + /// + [NotNull] + [JsonProperty("external_contact")] + [JsonPropertyName("external_contact")] + public ExternalContactInfo ExternalContact { get; set; } + /// + /// 添加了此外部联系人的企业成员 + /// + [NotNull] + [JsonProperty("follow_user")] + [JsonPropertyName("follow_user")] + public List FollowUser { get; set; } + /// + /// 分页的cursor,当跟进人多于500人时返回 + /// + [CanBeNull] + [JsonProperty("next_cursor")] + [JsonPropertyName("next_cursor")] + public string? NextCursor { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyListResponse.cs new file mode 100644 index 000000000..b386da41c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyListResponse.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response; +/// +/// 获取规则组列表响应参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetCustomerStrategyListResponse : WeChatWorkResponse +{ + /// + /// 规则组列表 + /// + [NotNull] + [JsonProperty("strategy")] + [JsonPropertyName("strategy")] + public CustomerStrategy[] Strategy { get; set; } + /// + /// 分页游标,用于查询下一个分页的数据,无更多数据时不返回 + /// + [CanBeNull] + [JsonProperty("next_cursor")] + [JsonPropertyName("next_cursor")] + public string? NextCursor { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyRangeResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyRangeResponse.cs new file mode 100644 index 000000000..0c7c715cd --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyRangeResponse.cs @@ -0,0 +1,30 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response; +/// +/// 获取规则组管理范围响应参数 +/// +/// +/// +/// 详情见: +/// +public class WeChatWorkGetCustomerStrategyRangeResponse : WeChatWorkResponse +{ + /// + /// 规则组管理范围 + /// + [NotNull] + [JsonProperty("range")] + [JsonPropertyName("range")] + public CustomerStrategyRange[] Range { get; set; } + /// + /// 分页游标,用于查询下一个分页的数据,无更多数据时不返回 + /// + [CanBeNull] + [JsonProperty("next_cursor")] + [JsonPropertyName("next_cursor")] + public string? NextCursor { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyResponse.cs new file mode 100644 index 000000000..0b3eba400 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response; +/// +/// 获取规则组详情响应参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetCustomerStrategyResponse : WeChatWorkResponse +{ + /// + /// 规则组详情 + /// + [NotNull] + [JsonProperty("strategy")] + [JsonPropertyName("strategy")] + public CustomerStrategyInfo Strategy { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider.cs new file mode 100644 index 000000000..eb242ed94 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider.cs @@ -0,0 +1,93 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +using LINGYUN.Abp.WeChat.Work.Token; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers; + +[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)] +public class WeChatWorkCustomerProvider : IWeChatWorkCustomerProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkCustomerProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task GetCustomerListAsync( + string userId, + CancellationToken cancellationToken = default) + { + Check.NotNullOrWhiteSpace(userId, nameof(userId)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetCustomerListAsync(token.AccessToken, userId, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task BulkGetCustomerAsync( + WeChatWorkBulkGetCustomerRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.BulkGetCustomerAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task GetCustomerAsync( + string externalUserid, + string? cursor = null, + CancellationToken cancellationToken = default) + { + Check.NotNullOrWhiteSpace(externalUserid, nameof(externalUserid)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetCustomerAsync(token.AccessToken, externalUserid, cursor, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task UpdateCustomerRemarkAsync( + WeChatWorkUpdateCustomerRemarkRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.UpdateCustomerRemarkAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider.cs new file mode 100644 index 000000000..f2984bb5f --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider.cs @@ -0,0 +1,111 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +using LINGYUN.Abp.WeChat.Work.Token; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers; + +[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)] +public class WeChatWorkCustomerStrategyProvider : IWeChatWorkCustomerStrategyProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkCustomerStrategyProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task GetCustomerStrategyListAsync( + WeChatWorkGetCustomerStrategyListRequest request, + CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetCustomerStrategyListAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task GetCustomerStrategyAsync( + WeChatWorkGetCustomerStrategyRequest request, + CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetCustomerStrategyAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task GetCustomerStrategyRangeAsync( + WeChatWorkGetCustomerStrategyRangeRequest request, + CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetCustomerStrategyRangeAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task CreateCustomerStrategyAsync( + WeChatWorkCreateCustomerStrategyRequest request, + CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.CreateCustomerStrategyAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task UpdateCustomerStrategyAsync( + WeChatWorkUpdateCustomerStrategyRequest request, + CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.UpdateCustomerStrategyAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task DeleteCustomerStrategyAsync( + WeChatWorkDeleteCustomerStrategyRequest request, + CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.DeleteCustomerStrategyAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureDefinitionProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureDefinitionProvider.cs new file mode 100644 index 000000000..d516615eb --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureDefinitionProvider.cs @@ -0,0 +1,31 @@ +using LINGYUN.Abp.WeChat.Work.Features; +using LINGYUN.Abp.WeChat.Work.Localization; +using Volo.Abp.Features; +using Volo.Abp.Localization; +using Volo.Abp.Validation.StringValues; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +public class WeChatWorkExternalContactFeatureDefinitionProvider : FeatureDefinitionProvider +{ + public override void Define(IFeatureDefinitionContext context) + { + var weChatFeature = context.GetGroupOrNull(WeChatWorkFeatureNames.GroupName); + if (weChatFeature == null) + { + return; + } + + var group = weChatFeature.AddFeature(WeChatWorkExternalContactFeatureNames.GroupName); + group.CreateChild( + WeChatWorkExternalContactFeatureNames.Enable, + defaultValue: "false", + displayName: L("Features:ExternalContactEnable"), + description: L("Features:ExternalContactEnableDesc"), + valueType: new ToggleStringValueType(new BooleanValueValidator())); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureNames.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureNames.cs new file mode 100644 index 000000000..9a9a1b259 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureNames.cs @@ -0,0 +1,14 @@ +using LINGYUN.Abp.WeChat.Work.Features; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +/// +/// 企业微信客户联系模块功能 +/// +public static class WeChatWorkExternalContactFeatureNames +{ + public const string GroupName = WeChatWorkFeatureNames.GroupName + ".ExternalContact"; + /// + /// 启用企业微信客户联系 + /// + public const string Enable = GroupName + ".Enable"; +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/IWeChatWorkFollowUserProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/IWeChatWorkFollowUserProvider.cs new file mode 100644 index 000000000..5772d7a9e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/IWeChatWorkFollowUserProvider.cs @@ -0,0 +1,20 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Follows.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Follows; +/// +/// 企业服务人员管理接口 +/// +public interface IWeChatWorkFollowUserProvider +{ + /// + /// 获取配置了客户联系功能的成员列表 + /// + /// + /// 详情见: + /// + /// + /// + Task GetFollowUserListAsync(CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/Response/WeChatWorkGetFollowUserListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/Response/WeChatWorkGetFollowUserListResponse.cs new file mode 100644 index 000000000..ff0c5d728 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/Response/WeChatWorkGetFollowUserListResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Follows.Response; +/// +/// 获取配置了客户联系功能的成员列表响应参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetFollowUserListResponse : WeChatWorkResponse +{ + /// + /// 配置了客户联系功能的成员userid列表 + /// + [NotNull] + [JsonProperty("follow_user")] + [JsonPropertyName("follow_user")] + public List FollowUser { get; set; } = new List(); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider.cs new file mode 100644 index 000000000..0391d6b7e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider.cs @@ -0,0 +1,38 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Follows.Response; +using LINGYUN.Abp.WeChat.Work.Token; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Follows; + +[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)] +public class WeChatWorkFollowUserProvider : IWeChatWorkFollowUserProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkFollowUserProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task GetFollowUserListAsync(CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetFollowUserListAsync(token.AccessToken, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/IWeChatWorkGroupChatProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/IWeChatWorkGroupChatProvider.cs new file mode 100644 index 000000000..d0cf7d078 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/IWeChatWorkGroupChatProvider.cs @@ -0,0 +1,48 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats; +/// +/// 客户群管理接口 +/// +public interface IWeChatWorkGroupChatProvider +{ + /// + /// 获取客户群列表 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GetGroupChatListAsync( + WeChatWorkGetGroupChatListRequest request, + CancellationToken cancellationToken = default); + /// + /// 获取客户群详情 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GetGroupChatAsync( + WeChatWorkGetGroupChatRequest request, + CancellationToken cancellationToken = default); + /// + /// 客户群opengid转换 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task OpengIdToChatIdAsync( + WeChatWorkOpengIdToChatIdRequest request, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChat.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChat.cs new file mode 100644 index 000000000..e9d6f34bb --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChat.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +/// +/// 客户群 +/// +public class GroupChat +{ + /// + /// 客户群ID + /// + [NotNull] + [JsonProperty("chat_id")] + [JsonPropertyName("chat_id")] + public string ChatId { get; set; } + /// + /// 客户群跟进状态 + /// + [NotNull] + [JsonProperty("status")] + [JsonPropertyName("status")] + public GroupChatStatus Status { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInfo.cs new file mode 100644 index 000000000..84c173240 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInfo.cs @@ -0,0 +1,67 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +/// +/// 客户群详情 +/// +public class GroupChatInfo +{ + /// + /// 客户群ID + /// + [NotNull] + [JsonProperty("chat_id")] + [JsonPropertyName("chat_id")] + public string ChatId { get; set; } + /// + /// 群名 + /// + [NotNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string Name { get; set; } + /// + /// 群主ID + /// + [NotNull] + [JsonProperty("owner")] + [JsonPropertyName("owner")] + public string Owner { get; set; } + /// + /// 群的创建时间 + /// + [NotNull] + [JsonProperty("create_time")] + [JsonPropertyName("create_time")] + public long CreateTime { get; set; } + /// + /// 群公告 + /// + [CanBeNull] + [JsonProperty("notice")] + [JsonPropertyName("notice")] + public string Notice { get; set; } + /// + /// 群成员列表 + /// + [NotNull] + [JsonProperty("member_list")] + [JsonPropertyName("member_list")] + public GroupChatMember[] MemberList { get; set; } + /// + /// 群管理员列表 + /// + [NotNull] + [JsonProperty("admin_list")] + [JsonPropertyName("admin_list")] + public GroupChatManager[] AdminList { get; set; } + /// + /// 当前群成员版本号。可以配合客户群变更事件减少主动调用本接口的次数 + /// + [NotNull] + [JsonProperty("member_version")] + [JsonPropertyName("member_version")] + public string MemberVersion { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInvitor.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInvitor.cs new file mode 100644 index 000000000..201decd88 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInvitor.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +/// +/// 邀请者 +/// +public class GroupChatInvitor +{ + /// + /// 邀请者的userid + /// + [NotNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatManager.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatManager.cs new file mode 100644 index 000000000..1a53a0781 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatManager.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +/// +/// 群管理员 +/// +public class GroupChatManager +{ + /// + /// 群管理员userid + /// + [NotNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMember.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMember.cs new file mode 100644 index 000000000..937f07841 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMember.cs @@ -0,0 +1,70 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +/// +/// 群成员 +/// +public class GroupChatMember +{ + /// + /// 群成员id + /// + [NotNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; set; } + /// + /// 成员类型 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public GroupChatMemberType Type { get; set; } + /// + /// 外部联系人在微信开放平台的唯一身份标识(微信unionid),通过此字段企业可将外部联系人与公众号/小程序用户关联起来。
+ /// 仅当群成员类型是微信用户(包括企业成员未添加好友),且企业绑定了微信开发者ID有此字段 + ///
+ [CanBeNull] + [JsonProperty("unionid")] + [JsonPropertyName("unionid")] + public string? UnionId { get; set; } + /// + /// 入群时间 + /// + [NotNull] + [JsonProperty("join_time")] + [JsonPropertyName("join_time")] + public long JoinTime { get; set; } + /// + /// 入群方式 + /// + [NotNull] + [JsonProperty("join_scene")] + [JsonPropertyName("join_scene")] + public GroupChatMemberJoinScene JoinScene { get; set; } + /// + /// 邀请者。目前仅当是由本企业内部成员邀请入群时会返回该值 + /// + [CanBeNull] + [JsonProperty("invitor")] + [JsonPropertyName("invitor")] + public GroupChatInvitor? Invitor { get; set; } + /// + /// 在群里的昵称 + /// + [CanBeNull] + [JsonProperty("group_nickname")] + [JsonPropertyName("group_nickname")] + public string? GroupNickname { get; set; } + /// + /// 名字。仅当 need_name = 1 时返回
+ /// 如果是微信用户,则返回其在微信中设置的名字
+ /// 如果是企业微信联系人,则返回其设置对外展示的别名或实名 + ///
+ [CanBeNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string? Name { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberJoinScene.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberJoinScene.cs new file mode 100644 index 000000000..8163b711c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberJoinScene.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +/// +/// 入群方式 +/// +[Description("入群方式")] +public enum GroupChatMemberJoinScene +{ + /// + /// 由群成员邀请入群(直接邀请入群) + /// + [Description("直接邀请入群")] + DirectInvitation = 1, + /// + /// 由群成员邀请入群(通过邀请链接入群) + /// + [Description("通过邀请链接入群")] + InvitationLink = 2, + /// + /// 通过扫描群二维码入群 + /// + [Description("通过扫描群二维码入群")] + ScanQrCode = 3, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberType.cs new file mode 100644 index 000000000..f8f4a6219 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberType.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +/// +/// 成员类型 +/// +[Description("成员类型")] +public enum GroupChatMemberType +{ + /// + /// 企业成员 + /// + [Description("企业成员")] + Internal = 1, + /// + /// 外部联系人 + /// + [Description("外部联系人")] + External = 2 +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatStatus.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatStatus.cs new file mode 100644 index 000000000..5393313cd --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatStatus.cs @@ -0,0 +1,30 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +/// +/// 客户群跟进状态 +/// +[Description("客户群跟进状态")] +public enum GroupChatStatus +{ + /// + /// 跟进人正常 + /// + [Description("跟进人正常")] + Normal = 0, + /// + /// 跟进人离职 + /// + [Description("跟进人离职")] + Leaves = 1, + /// + /// 离职继承中 + /// + [Description("离职继承中")] + Resiging = 2, + /// + /// 离职继承完成 + /// + [Description(" 离职继承完成")] + ResignCompleted = 3, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/OwnerFilter.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/OwnerFilter.cs new file mode 100644 index 000000000..bd0bc24c8 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/OwnerFilter.cs @@ -0,0 +1,31 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +/// +/// 群主过滤 +/// +public class OwnerFilter +{ + /// + /// 用户ID列表。最多100个 + /// + [NotNull] + [JsonProperty("userid_list")] + [JsonPropertyName("userid_list")] + public string[] UserIdList { get; } + public OwnerFilter(string[] userIdList) + { + Check.NotNullOrEmpty(userIdList, nameof(userIdList)); + + if (userIdList.Length > 100) + { + throw new ArgumentException("The maximum number of parameters allowed for group owner filtering is only 100!"); + } + + UserIdList = userIdList; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/StatusFilter.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/StatusFilter.cs new file mode 100644 index 000000000..e4d78e910 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/StatusFilter.cs @@ -0,0 +1,30 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +/// +/// 客户群跟进状态过滤 +/// +[Description("客户群跟进状态过滤")] +public enum StatusFilter +{ + /// + /// 所有列表 + /// + [Description("所有列表")] + All = 0, + /// + /// 跟进人离职 + /// + [Description("跟进人离职")] + Leaves = 1, + /// + /// 离职继承中 + /// + [Description("离职继承中")] + Resiging = 2, + /// + /// 离职继承完成 + /// + [Description(" 离职继承完成")] + ResignCompleted = 3, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatListRequest.cs new file mode 100644 index 000000000..5c86a3e9d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatListRequest.cs @@ -0,0 +1,50 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request; +/// +/// 获取客户群列表请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetGroupChatListRequest : WeChatWorkRequest +{ + /// + /// 客户群跟进状态过滤 + /// + [CanBeNull] + [JsonProperty("status_filter")] + [JsonPropertyName("status_filter")] + public StatusFilter? StatusFilter { get; set; } + /// + /// 客户群跟进状态过滤 + /// + [CanBeNull] + [JsonProperty("owner_filter")] + [JsonPropertyName("owner_filter")] + public OwnerFilter[]? OwnerFilter { get; set; } + /// + /// 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用不填 + /// + [CanBeNull] + [JsonProperty("cursor")] + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } + /// + /// 分页,预期请求的数据量,取值范围 1 ~ 1000 + /// + [CanBeNull] + [JsonProperty("limit")] + [JsonPropertyName("limit")] + public int Limit { get; } + public WeChatWorkGetGroupChatListRequest(int limit = 1000) + { + Check.Range(limit, nameof(limit), 1, 1000); + + Limit = limit; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatRequest.cs new file mode 100644 index 000000000..4949e0d87 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatRequest.cs @@ -0,0 +1,36 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request; +/// +/// 获取客户群详情 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetGroupChatRequest : WeChatWorkRequest +{ + /// + /// 客户群ID + /// + [NotNull] + [JsonProperty("chat_id")] + [JsonPropertyName("chat_id")] + public string ChatId { get; } + /// + /// 是否需要返回群成员的名字group_chat.member_list.name。0-不返回;1-返回。默认不返回 + /// + [CanBeNull] + [JsonProperty("need_name")] + [JsonPropertyName("need_name")] + public int? NeedName { get; } + public WeChatWorkGetGroupChatRequest(string chatId, bool needName = false) + { + Check.NotNullOrWhiteSpace(chatId, nameof(chatId)); + + ChatId = chatId; + NeedName = needName ? 1 : 0; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkOpengIdToChatIdRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkOpengIdToChatIdRequest.cs new file mode 100644 index 000000000..0c3343a3f --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkOpengIdToChatIdRequest.cs @@ -0,0 +1,28 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request; +/// +/// 客户群opengid转换请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkOpengIdToChatIdRequest : WeChatWorkRequest +{ + /// + /// 小程序在微信获取到的群ID + /// + [NotNull] + [JsonProperty("opengid")] + [JsonPropertyName("opengid")] + public string OpengId { get; } + public WeChatWorkOpengIdToChatIdRequest(string opengId) + { + Check.NotNullOrWhiteSpace(opengId, nameof(opengId)); + + OpengId = opengId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatListResponse.cs new file mode 100644 index 000000000..1a0d2b81e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatListResponse.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Response; +/// +/// 获取客户群列表响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetGroupChatListResponse : WeChatWorkResponse +{ + /// + /// 客户群列表 + /// + [NotNull] + [JsonProperty("group_chat_list")] + [JsonPropertyName("group_chat_list")] + public GroupChat[] GroupChatList { get; set; } + /// + /// 分页游标,下次请求时填写以获取之后分页的记录。如果该字段返回空则表示已没有更多数据 + /// + [CanBeNull] + [JsonProperty("next_cursor")] + [JsonPropertyName("next_cursor")] + public string? NextCursor { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatResponse.cs new file mode 100644 index 000000000..8f58bbfb3 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Response; +/// +/// 获取客户群详情响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetGroupChatResponse : WeChatWorkResponse +{ + /// + /// 客户群详情 + /// + [NotNull] + [JsonProperty("group_chat")] + [JsonPropertyName("group_chat")] + public GroupChatInfo GroupChat { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkOpengIdToChatIdResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkOpengIdToChatIdResponse.cs new file mode 100644 index 000000000..ab1b981d5 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkOpengIdToChatIdResponse.cs @@ -0,0 +1,21 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Response; +/// +/// 客户群opengid转换响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkOpengIdToChatIdResponse : WeChatWorkResponse +{ + /// + /// 客户群ID,可以用来调用获取客户群详情 + /// + [NotNull] + [JsonProperty("chat_id")] + [JsonPropertyName("chat_id")] + public string ChatId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider.cs new file mode 100644 index 000000000..6600d08a9 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider.cs @@ -0,0 +1,76 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Response; +using LINGYUN.Abp.WeChat.Work.Token; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats; + +[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)] +public class WeChatWorkGroupChatProvider : IWeChatWorkGroupChatProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkGroupChatProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task GetGroupChatListAsync( + WeChatWorkGetGroupChatListRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetGroupChatListAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task GetGroupChatAsync( + WeChatWorkGetGroupChatRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetGroupChatAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task OpengIdToChatIdAsync( + WeChatWorkOpengIdToChatIdRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.OpengIdToChatIdAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/en.json b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/en.json new file mode 100644 index 000000000..555a57daa --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/en.json @@ -0,0 +1,7 @@ +{ + "culture": "en", + "texts": { + "Features:ExternalContactEnable": "Enable External Contact", + "Features:ExternalContactEnableDesc": "Enable the ability to provide the application with an Enterprise wechat customer contact interface." + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/zh-Hans.json b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..038e57240 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "culture": "zh-Hans", + "texts": { + "Features:ExternalContactEnable": "启用客户联系", + "Features:ExternalContactEnableDesc": "启用以使应用拥有企业微信客户联系接口的能力." + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChaDelMemberEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChaDelMemberEvent.cs new file mode 100644 index 000000000..c5ff046e8 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChaDelMemberEvent.cs @@ -0,0 +1,25 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using System.Xml.Serialization; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 客户群成员退群事件推送 +/// +[EventName("external_chat_del_member")] +public class ExternalChaDelMemberEvent : ExternalChatChangeMemberEvent +{ + /// + /// 当是成员退群时有值。表示成员的退群方式
+ /// 0 - 自己退群
+ /// 1 - 群主/群管理员移出
+ ///
+ [XmlElement("QuitScene")] + public ExternalChatMemberQuitScene QuitScene { get; set; } + + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatAddMemberEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatAddMemberEvent.cs new file mode 100644 index 000000000..ebd36da90 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatAddMemberEvent.cs @@ -0,0 +1,25 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using System.Xml.Serialization; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 客户群成员入群事件推送 +/// +[EventName("external_chat_add_member")] +public class ExternalChatAddMemberEvent : ExternalChatChangeMemberEvent +{ + /// + /// 当是成员入群时有值。表示成员的入群方式
+ /// 0 - 由成员邀请入群(包括直接邀请入群和通过邀请链接入群)
+ /// 3 - 通过扫描群二维码入群
+ ///
+ [XmlElement("JoinScene")] + public ExternalChatMemberJoinScene JoinScene { get; set; } + + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeEvent.cs new file mode 100644 index 000000000..9e7ab1eb6 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeEvent.cs @@ -0,0 +1,21 @@ +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using System.Xml.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 客户群变更事件推送 +/// +public abstract class ExternalChatChangeEvent : WeChatWorkEventMessage +{ + /// + /// 变更类型 + /// + [XmlElement("ChangeType")] + public string ChangeType { get; set; } + /// + /// 群ID + /// + [XmlElement("ChatId")] + public string ChatId { get; set; } +} + diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeMemberEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeMemberEvent.cs new file mode 100644 index 000000000..b64e10b84 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeMemberEvent.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 客户群成员变更事件推送 +/// +public abstract class ExternalChatChangeMemberEvent : ExternalChatUpdateEvent +{ + /// + /// 成员变更数量 + /// + [XmlElement("MemChangeCnt")] + public int MemChangeCnt { get; set; } + /// + /// 变更的成员列表 + /// + [XmlElement("MemChangeList")] + public List MemChangeList { get; set; } = new List(); + /// + /// 变更前的群成员版本号 + /// + [XmlElement("LastMemVer")] + public string LastMemVer { get; set; } + /// + /// 变更后的群成员版本号 + /// + [XmlElement("CurMemVer")] + public string CurMemVer { get; set; } +} + +public class ExternalChatChangeMember +{ + /// + /// 成员Id + /// + [XmlElement("Item")] + public string UserId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNameEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNameEvent.cs new file mode 100644 index 000000000..e3480f2a7 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNameEvent.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 客户群群名变更事件推送 +/// +[EventName("external_chat_change_name")] +public class ExternalChatChangeNameEvent : ExternalChatUpdateEvent +{ + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNoticeEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNoticeEvent.cs new file mode 100644 index 000000000..ec7d5d485 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNoticeEvent.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 客户群群公告变更事件推送 +/// +[EventName("external_chat_change_notice")] +public class ExternalChatChangeNoticeEvent : ExternalChatUpdateEvent +{ + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeOwnerEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeOwnerEvent.cs new file mode 100644 index 000000000..ad955f907 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeOwnerEvent.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 客户群群主变更事件推送 +/// +[EventName("external_chat_change_owner")] +public class ExternalChatChangeOwnerEvent : ExternalChatUpdateEvent +{ + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatCreateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatCreateEvent.cs new file mode 100644 index 000000000..417082072 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatCreateEvent.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 客户群创建事件推送 +/// +[EventName("external_chat_create")] +public class ExternalChatCreateEvent : ExternalChatChangeEvent +{ + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatDismissEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatDismissEvent.cs new file mode 100644 index 000000000..e3fa20623 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatDismissEvent.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 客户群解散事件推送 +/// +[EventName("external_chat_dismiss")] +public class ExternalChatDismissEvent : ExternalChatChangeEvent +{ + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberJoinScene.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberJoinScene.cs new file mode 100644 index 000000000..1eb129638 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberJoinScene.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 成员的入群方式 +/// +[Description("成员的入群方式")] +public enum ExternalChatMemberJoinScene +{ + /// + /// 由成员邀请入群 + /// + [Description("由成员邀请入群")] + MemberInvitation = 0, + /// + /// 通过扫描群二维码入群 + /// + [Description("通过扫描群二维码入群")] + ScanQrCode = 3 +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberQuitScene.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberQuitScene.cs new file mode 100644 index 000000000..1a02803ac --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberQuitScene.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 成员的退群方式 +/// +[Description("成员的退群方式")] +public enum ExternalChatMemberQuitScene +{ + /// + /// 自己退群 + /// + [Description("自己退群")] + UserSelf = 0, + /// + /// 群主/群管理员移出 + /// + [Description("群主/群管理员移出")] + Admin = 1 +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatUpdateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatUpdateEvent.cs new file mode 100644 index 000000000..7b981dcdf --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatUpdateEvent.cs @@ -0,0 +1,19 @@ +using System.Xml.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 客户群变更事件推送 +/// +public abstract class ExternalChatUpdateEvent : ExternalChatChangeEvent +{ + /// + /// 变更详情。目前有以下几种:
+ /// add_member : 成员入群
+ /// del_member : 成员退群
+ /// change_owner : 群主变更
+ /// change_name : 群名变更
+ /// change_notice : 群公告变更 + ///
+ [XmlElement("UpdateDetail")] + public string UpdateDetail { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactChangeEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactChangeEvent.cs new file mode 100644 index 000000000..9d6fe930c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactChangeEvent.cs @@ -0,0 +1,25 @@ +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using System.Xml.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 企业客户变更事件推送 +/// +public abstract class ExternalContactChangeEvent : WeChatWorkEventMessage +{ + /// + /// 变更类型 + /// + [XmlElement("ChangeType")] + public string ChangeType { get; set; } + /// + /// 企业服务人员的UserID + /// + [XmlElement("UserID")] + public string UserId { get; set; } + /// + /// 外部联系人的userid,注意不是企业成员的账号 + /// + [XmlElement("ExternalUserID")] + public string ExternalUserId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateEvent.cs new file mode 100644 index 000000000..03ddacffb --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateEvent.cs @@ -0,0 +1,28 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using System.Xml.Serialization; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 添加企业客户事件推送 +/// +[EventName("external_contact_create")] +public class ExternalContactCreateEvent : ExternalContactChangeEvent +{ + /// + /// 添加此用户的「联系我」方式配置的state参数,或在获客链接中指定的customer_channel参数,可用于识别添加此用户的渠道 + /// + [XmlElement("State")] + public string State { get; set; } + /// + /// 欢迎语code,可用于发送欢迎语 + /// + [XmlElement("WelcomeCode")] + public string WelcomeCode { get; set; } + + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateHalfEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateHalfEvent.cs new file mode 100644 index 000000000..943b84f86 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateHalfEvent.cs @@ -0,0 +1,28 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using System.Xml.Serialization; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 外部联系人免验证添加成员事件 +/// +[EventName("external_contact_create_half")] +public class ExternalContactCreateHalfEvent : ExternalContactChangeEvent +{ + /// + /// 添加此用户的「联系我」方式配置的state参数,或在获客链接中指定的customer_channel参数,可用于识别添加此用户的渠道 + /// + [XmlElement("State")] + public string State { get; set; } + /// + /// 欢迎语code,可用于发送欢迎语 + /// + [XmlElement("WelcomeCode")] + public string WelcomeCode { get; set; } + + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteEvent.cs new file mode 100644 index 000000000..c67b70d30 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteEvent.cs @@ -0,0 +1,23 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using System.Xml.Serialization; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 删除企业客户事件 +/// +[EventName("external_contact_delete")] +public class ExternalContactDeleteEvent : ExternalContactChangeEvent +{ + /// + /// 删除客户的操作来源,DELETE_BY_TRANSFER表示此客户是因在职继承自动被转接成员删除 + /// + [XmlElement("Source")] + public string Source { get; set; } + + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteFollowUserEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteFollowUserEvent.cs new file mode 100644 index 000000000..cdd043073 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteFollowUserEvent.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 删除跟进成员事件 +/// +[EventName("external_contact_del_follow_user")] +public class ExternalContactDeleteFollowUserEvent : ExternalContactChangeEvent +{ + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactTransferFailEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactTransferFailEvent.cs new file mode 100644 index 000000000..dd60b40a4 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactTransferFailEvent.cs @@ -0,0 +1,23 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using System.Xml.Serialization; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 客户接替失败事件 +/// +[EventName("external_contact_transfer_fail")] +public class ExternalContactTransferFailEvent : ExternalContactChangeEvent +{ + /// + /// 接替失败的原因, customer_refused-客户拒绝, customer_limit_exceed-接替成员的客户数达到上限 + /// + [XmlElement("FailReason")] + public string FailReason { get; set; } + + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactUpdateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactUpdateEvent.cs new file mode 100644 index 000000000..e1f9a777c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactUpdateEvent.cs @@ -0,0 +1,17 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 编辑企业客户事件推送 +/// +[EventName("external_contact_update")] +public class ExternalContactUpdateEvent : ExternalContactChangeEvent +{ + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} + diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagChangeEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagChangeEvent.cs new file mode 100644 index 000000000..e6bc43cb7 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagChangeEvent.cs @@ -0,0 +1,20 @@ +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using System.Xml.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 企业客户标签变更事件推送 +/// +public abstract class ExternalTagChangeEvent : WeChatWorkEventMessage +{ + /// + /// 变更类型 + /// + [XmlElement("ChangeType")] + public string ChangeType { get; set; } + /// + /// 标签或标签组所属的规则组id,只回调给“客户联系”应用 + /// + [XmlElement("StrategyId")] + public string StrategyId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagCreateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagCreateEvent.cs new file mode 100644 index 000000000..cd7e0ff33 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagCreateEvent.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 企业客户标签创建事件推送 +/// +[EventName("change_external_tag_create")] +public class ExternalTagCreateEvent : ExternalTagChangeEvent +{ + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagDeleteEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagDeleteEvent.cs new file mode 100644 index 000000000..730b10915 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagDeleteEvent.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 企业客户标签删除事件推送 +/// +[EventName("change_external_tag_delete")] +public class ExternalTagDeleteEvent : ExternalTagChangeEvent +{ + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagShuffleEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagShuffleEvent.cs new file mode 100644 index 000000000..390a0f7d9 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagShuffleEvent.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 企业客户标签重排事件推送 +/// +[EventName("change_external_tag_shuffle")] +public class ExternalTagShuffleEvent : ExternalTagChangeEvent +{ + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagUpdateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagUpdateEvent.cs new file mode 100644 index 000000000..1e728d5c2 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagUpdateEvent.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.WeChat.Common.Messages; +using LINGYUN.Abp.WeChat.Work.Common.Messages; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; +/// +/// 企业客户标签变更事件推送 +/// +[EventName("change_external_tag_update")] +public class ExternalTagUpdateEvent : ExternalTagChangeEvent +{ + public override WeChatMessageEto ToEto() + { + return new WeChatWorkEventMessageEto(this); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttribute.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttribute.cs new file mode 100644 index 000000000..ef161f3c2 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttribute.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +/// +/// 成员对外属性 +/// +public abstract class ExternalAttribute +{ + /// + /// 属性名称: 需要先确保在管理端有创建该属性,否则会忽略 + /// + [NotNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string Name { get; set; } + /// + /// 属性类型 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public ExternalAttributeType Type { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeDeserializeFactory.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeDeserializeFactory.cs new file mode 100644 index 000000000..fb0975217 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeDeserializeFactory.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Text.Json; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +internal static class ExternalAttributeDeserializeFactory +{ + /// + /// 根据属性类型创建属性(System.Text.Json) + /// + public static ExternalAttribute CreateExternalAttribute(ExternalAttributeType type, JsonElement configElement) + { + return type switch + { + ExternalAttributeType.Text => JsonSerializer.Deserialize(configElement.GetRawText())!, + ExternalAttributeType.Web => JsonSerializer.Deserialize(configElement.GetRawText())!, + ExternalAttributeType.MiniProgram => JsonSerializer.Deserialize(configElement.GetRawText())!, + _ => throw new NotSupportedException($"Attribute type {type} is not supported for the time being"), + }; + } + + /// + /// 根据属性类型创建属性(Newtonsoft.Json) + /// + public static ExternalAttribute CreateExternalAttribute(ExternalAttributeType type, JToken configToken) + { + return type switch + { + ExternalAttributeType.Text => configToken.ToObject()!, + ExternalAttributeType.Web => configToken.ToObject()!, + ExternalAttributeType.MiniProgram => configToken.ToObject()!, + _ => throw new NotSupportedException($"Attribute type {type} is not supported for the time being"), + }; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeType.cs new file mode 100644 index 000000000..8b003cf05 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeType.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +/// +/// 属性类型 +/// +[Description("属性类型")] +public enum ExternalAttributeType +{ + /// + /// 文本 + /// + [Description("文本")] + Text = 0, + /// + /// 网页 + /// + [Description("网页")] + Web = 1, + /// + /// 小程序 + /// + [Description("小程序")] + MiniProgram = 2 +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalMiniProgramAttribute.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalMiniProgramAttribute.cs new file mode 100644 index 000000000..5935b1ebf --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalMiniProgramAttribute.cs @@ -0,0 +1,43 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +/// +/// 小程序类型属性 +/// +public class ExternalMiniProgramAttribute : ExternalAttribute +{ + /// + /// 小程序 + /// + [NotNull] + [JsonProperty("miniprogram")] + [JsonPropertyName("miniprogram")] + public ExternalMiniProgramModel MiniProgram { get; set; } +} + +public class ExternalMiniProgramModel +{ + /// + /// 小程序appid,必须是有在本企业安装授权的小程序,否则会被忽略 + /// + [NotNull] + [JsonProperty("appid")] + [JsonPropertyName("appid")] + public string AppId { get; set; } + /// + /// 小程序的展示标题,长度限制12个UTF8字符 + /// + [NotNull] + [JsonProperty("title")] + [JsonPropertyName("title")] + public string Title { get; set; } + /// + /// 小程序的页面路径 + /// + [NotNull] + [JsonProperty("pagepath")] + [JsonPropertyName("pagepath")] + public string PagePath { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalProfile.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalProfile.cs new file mode 100644 index 000000000..2b625199c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalProfile.cs @@ -0,0 +1,36 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +/// +/// 成员对外信息 +/// +[System.Text.Json.Serialization.JsonConverter(typeof(ExternalProfileSystemTextJsonConverter))] +[Newtonsoft.Json.JsonConverter(typeof(ExternalProfileNewtonsoftJsonConverter))] +public class ExternalProfile +{ + /// + /// 企业对外简称,需从已认证的企业简称中选填。可在“我的企业”页中查看企业简称认证状态。 + /// + [NotNull] + [JsonProperty("external_corp_name")] + [JsonPropertyName("external_corp_name")] + public string ExternalCorpName { get; set; } + /// + /// 视频号属性。须从企业绑定到企业微信的视频号中选择,可在“我的企业”页中查看绑定的视频号。 + /// 第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取 + /// + [NotNull] + [JsonProperty("wechat_channels")] + [JsonPropertyName("wechat_channels")] + public List WechatChannels { get; set; } + /// + /// 属性列表 + /// + [NotNull] + [JsonProperty("external_attr")] + [JsonPropertyName("external_attr")] + public List ExternalAttributes { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalTextAttribute.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalTextAttribute.cs new file mode 100644 index 000000000..b8f3f7d70 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalTextAttribute.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +/// +/// 文本类型属性 +/// +public class ExternalTextAttribute : ExternalAttribute +{ + /// + /// 文本 + /// + [NotNull] + [JsonProperty("text")] + [JsonPropertyName("text")] + public ExternalTextModel Text { get; set; } +} + +public class ExternalTextModel +{ + /// + /// 文本 + /// + [NotNull] + [JsonProperty("value")] + [JsonPropertyName("value")] + public string Value { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalWebAttribute.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalWebAttribute.cs new file mode 100644 index 000000000..e882697a2 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalWebAttribute.cs @@ -0,0 +1,36 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +/// +/// 网页类型属性 +/// +public class ExternalWebAttribute : ExternalAttribute +{ + /// + /// 网页 + /// + [NotNull] + [JsonProperty("web")] + [JsonPropertyName("web")] + public ExternalWebModel Web { get; set; } +} + +public class ExternalWebModel +{ + /// + /// 网页的url,必须包含http或者https头 + /// + [NotNull] + [JsonProperty("url")] + [JsonPropertyName("url")] + public string Url { get; set; } + /// + /// 网页的展示标题,长度限制12个UTF8字符 + /// + [NotNull] + [JsonProperty("title")] + [JsonPropertyName("title")] + public string Title { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannel.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannel.cs new file mode 100644 index 000000000..953c929fa --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannel.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +/// +/// 视频号属性 +/// +public class WechatChannel +{ + /// + /// 视频号名字(设置后,成员将对外展示该视频号) + /// + [NotNull] + [JsonProperty("nickname")] + [JsonPropertyName("nickname")] + public string NickName { get; set; } + /// + /// 对外展示视频号状态 + /// + [NotNull] + [JsonProperty("status")] + [JsonPropertyName("status")] + public WechatChannelStatus Status { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannelStatus.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannelStatus.cs new file mode 100644 index 000000000..3d994e996 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannelStatus.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +/// +/// 视频号状态 +/// +[Description("视频号状态")] +public enum WechatChannelStatus +{ + /// + /// 已确认 + /// + [Description("已确认")] + Confirmed = 0, + /// + /// 待确认 + /// + [Description("待确认")] + UnConfirmed = 1, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkCropTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkCropTagProvider.cs new file mode 100644 index 000000000..f943e1e3c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkCropTagProvider.cs @@ -0,0 +1,72 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags; +/// +/// 客户标签管理 +/// +public interface IWeChatWorkCropTagProvider +{ + /// + /// 获取企业标签库 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GetCropTagListAsync( + WeChatWorkGetCropTagListRequest request, + CancellationToken cancellationToken = default); + /// + /// 添加企业客户标签 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task CreateCropTagAsync( + WeChatWorkCreateCropTagRequest request, + CancellationToken cancellationToken = default); + /// + /// 编辑企业客户标签 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task UpdateCropTagAsync( + WeChatWorkUpdateCropTagRequest request, + CancellationToken cancellationToken = default); + /// + /// 删除企业客户标签 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task DeleteCropTagAsync( + WeChatWorkDeleteCropTagRequest request, + CancellationToken cancellationToken = default); + /// + /// 编辑客户企业标签 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task MarkCropTagAsync( + WeChatWorkMarkCropTagRequest request, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkStrategyTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkStrategyTagProvider.cs new file mode 100644 index 000000000..794b6e007 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkStrategyTagProvider.cs @@ -0,0 +1,60 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags; +/// +/// 规则组标签管理 +/// +public interface IWeChatWorkStrategyTagProvider +{ + /// + /// 获取指定规则组下的企业客户标签 + /// + /// + /// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E4%B8%8B%E7%9A%84%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE + /// + /// 请求参数 + /// + /// + Task GetStrategyTagListAsync( + WeChatWorkGetStrategyTagListRequest request, + CancellationToken cancellationToken = default); + /// + /// 为指定规则组创建企业客户标签 + /// + /// + /// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E4%B8%8B%E7%9A%84%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE + /// + /// 请求参数 + /// + /// + Task CreateStrategyTagAsync( + WeChatWorkCreateStrategyTagRequest request, + CancellationToken cancellationToken = default); + /// + /// 编辑指定规则组下的企业客户标签 + /// + /// + /// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E7%BC%96%E8%BE%91%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E4%B8%8B%E7%9A%84%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE + /// + /// 请求参数 + /// + /// + Task UpdateStrategyTagAsync( + WeChatWorkUpdateStrategyTagRequest request, + CancellationToken cancellationToken = default); + /// + /// 删除指定规则组下的企业客户标签 + /// + /// + /// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E5%88%A0%E9%99%A4%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E4%B8%8B%E7%9A%84%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE + /// + /// 请求参数 + /// + /// + Task DeleteStrategyTagAsync( + WeChatWorkDeleteStrategyTagRequest request, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTag.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTag.cs new file mode 100644 index 000000000..ebcf7ba48 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTag.cs @@ -0,0 +1,15 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models; +public class CropTag : Tag +{ + /// + /// 标签是否已经被删除,只在指定tag_id/group_id进行查询时返回 + /// + [NotNull] + [JsonProperty("deleted")] + [JsonPropertyName("deleted")] + public bool Deleted { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTagGroup.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTagGroup.cs new file mode 100644 index 000000000..db8f9770e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTagGroup.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models; +public class CropTagGroup : TagGroup +{ + /// + /// 标签组是否已经被删除,只在指定tag_id进行查询时返回 + /// + [NotNull] + [JsonProperty("deleted")] + [JsonPropertyName("deleted")] + public bool Deleted { get; set; } + /// + /// 标签列表 + /// + [NotNull] + [JsonProperty("tag")] + [JsonPropertyName("tag")] + public CropTag[] Tag { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTag.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTag.cs new file mode 100644 index 000000000..f0ebd22c8 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTag.cs @@ -0,0 +1,4 @@ +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models; +public class StrategyTag : Tag +{ +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTagGroup.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTagGroup.cs new file mode 100644 index 000000000..7f49f7e41 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTagGroup.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models; +public class StrategyTagGroup : TagGroup +{ + /// + /// 标签组所属的规则组id + /// + [NotNull] + [JsonProperty("strategy_id")] + [JsonPropertyName("strategy_id")] + public int StrategyId { get; set; } + /// + /// 标签列表 + /// + [NotNull] + [JsonProperty("tag")] + [JsonPropertyName("tag")] + public StrategyTag[] Tag { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/Tag.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/Tag.cs new file mode 100644 index 000000000..e625fcb66 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/Tag.cs @@ -0,0 +1,36 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models; +public abstract class Tag +{ + /// + /// 标签id + /// + [NotNull] + [JsonProperty("id")] + [JsonPropertyName("id")] + public string Id { get; set; } + /// + /// 标签名称 + /// + [NotNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string Name { get; set; } + /// + /// 标签创建时间 + /// + [NotNull] + [JsonProperty("create_time")] + [JsonPropertyName("create_time")] + public long CreateTime { get; set; } + /// + /// 标签排序的次序值,order值大的排序靠前。有效的值范围是[0, 2^32) + /// + [NotNull] + [JsonProperty("order")] + [JsonPropertyName("order")] + public int Order { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/TagGroup.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/TagGroup.cs new file mode 100644 index 000000000..6fcd9ddac --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/TagGroup.cs @@ -0,0 +1,36 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models; +public abstract class TagGroup +{ + /// + /// 标签组id + /// + [NotNull] + [JsonProperty("group_id")] + [JsonPropertyName("group_id")] + public string GroupId { get; set; } + /// + /// 标签组名称 + /// + [NotNull] + [JsonProperty("group_name")] + [JsonPropertyName("group_name")] + public string GroupName { get; set; } + /// + /// 标签组创建时间 + /// + [NotNull] + [JsonProperty("create_time")] + [JsonPropertyName("create_time")] + public long CreateTime { get; set; } + /// + /// 标签组排序的次序值,order值大的排序靠前。有效的值范围是[0, 2^32) + /// + [NotNull] + [JsonProperty("order")] + [JsonPropertyName("order")] + public int Order { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateCropTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateCropTagRequest.cs new file mode 100644 index 000000000..061aa937a --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateCropTagRequest.cs @@ -0,0 +1,100 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +/// +/// 添加企业客户标签请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkCreateCropTagRequest : WeChatWorkRequest +{ + /// + /// 标签组id + /// + [CanBeNull] + [JsonProperty("group_id")] + [JsonPropertyName("group_id")] + public string? GroupId { get; set; } + + private string? _groupName; + /// + /// 标签组名称,最长为30个字符 + /// + [CanBeNull] + [JsonProperty("group_name")] + [JsonPropertyName("group_name")] + public string? GroupName { + get => _groupName; + set { + + Check.Length(value, nameof(GroupName), 30); + _groupName = value; + } + } + /// + /// 标签组排序的次序值,order值大的排序靠前。有效的值范围是[0, 2^32) + /// + [CanBeNull] + [JsonProperty("order")] + [JsonPropertyName("order")] + public int? Order { get; set; } + /// + /// 授权方安装的应用agentid。仅旧的第三方多应用套件需要填此参数 + /// + [CanBeNull] + [JsonProperty("agentid")] + [JsonPropertyName("agentid")] + public string? AgentId { get; set; } + /// + /// 添加的标签组 + /// + [CanBeNull] + [JsonProperty("tag")] + [JsonPropertyName("tag")] + public List Tag { get; } + + public WeChatWorkCreateCropTagRequest() + { + Tag = new List(); + } + + protected override void Validate() + { + if (GroupName.IsNullOrWhiteSpace() && + Tag.IsNullOrEmpty()) + { + throw new ArgumentException("The name of the tag group or the tag list cannot be empty at the same time!"); + } + } +} + +public class NewCropTag +{ + /// + /// 添加的标签名称,最长为30个字符 + /// + [NotNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string Name { get; } + /// + /// 标签次序值。order值大的排序靠前。有效的值范围是[0, 2^32) + /// + [CanBeNull] + [JsonProperty("order")] + [JsonPropertyName("order")] + public int? Order { get; set; } + public NewCropTag(string name, int? order = null) + { + Check.NotNullOrWhiteSpace(name, nameof(name), 30); + + Name = name; + Order = order; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateStrategyTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateStrategyTagRequest.cs new file mode 100644 index 000000000..a1e7a1bb1 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateStrategyTagRequest.cs @@ -0,0 +1,88 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +public class WeChatWorkCreateStrategyTagRequest : WeChatWorkRequest +{ + /// + /// 规则组id + /// + [NotNull] + [JsonProperty("strategy_id")] + [JsonPropertyName("strategy_id")] + public int StrategyId { get; } + /// + /// 标签组id + /// + [CanBeNull] + [JsonProperty("group_id")] + [JsonPropertyName("group_id")] + public string? GroupId { get; set; } + + private string? _groupName; + /// + /// 标签组名称,最长为30个字符 + /// + [CanBeNull] + [JsonProperty("group_name")] + [JsonPropertyName("group_name")] + public string? GroupName { + get => _groupName; + set { + + Check.Length(value, nameof(GroupName), 30); + _groupName = value; + } + } + /// + /// 标签组排序的次序值,order值大的排序靠前。有效的值范围是[0, 2^32) + /// + [CanBeNull] + [JsonProperty("order")] + [JsonPropertyName("order")] + public int? Order { get; set; } + /// + /// 添加的标签组 + /// + [CanBeNull] + [JsonProperty("tag")] + [JsonPropertyName("tag")] + public List Tag { get; } + + public WeChatWorkCreateStrategyTagRequest(int strategyId) + { + Check.NotDefaultOrNull(strategyId, nameof(strategyId)); + + StrategyId = strategyId; + + Tag = new List(); + } +} + +public class NewStrategyTag +{ + /// + /// 添加的标签名称,最长为30个字符 + /// + [NotNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string Name { get; } + /// + /// 标签次序值。order值大的排序靠前。有效的值范围是[0, 2^32) + /// + [CanBeNull] + [JsonProperty("order")] + [JsonPropertyName("order")] + public int? Order { get; set; } + public NewStrategyTag(string name, int? order = null) + { + Check.NotNullOrWhiteSpace(name, nameof(name), 30); + + Name = name; + Order = order; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteCropTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteCropTagRequest.cs new file mode 100644 index 000000000..c5b86b07e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteCropTagRequest.cs @@ -0,0 +1,53 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +/// +/// 删除企业客户标签请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkDeleteCropTagRequest : WeChatWorkRequest +{ + /// + /// 标签组的id列表 + /// + [CanBeNull] + [JsonProperty("group_id")] + [JsonPropertyName("group_id")] + public string[]? GroupId { get; } + /// + /// 标签组的id列表 + /// + [CanBeNull] + [JsonProperty("tag_id")] + [JsonPropertyName("tag_id")] + public string[]? TagId { get; } + /// + /// 授权方安装的应用agentid。仅旧的第三方多应用套件需要填此参数 + /// + [CanBeNull] + [JsonProperty("agentid")] + [JsonPropertyName("agentid")] + public string? AgentId { get; set; } + + private WeChatWorkDeleteCropTagRequest( + string[]? groupId = null, + string[]? tagId = null) + { + GroupId = groupId; + TagId = tagId; + } + + public static WeChatWorkDeleteCropTagRequest Tag(string[] tagId) + { + return new WeChatWorkDeleteCropTagRequest(tagId: tagId); + } + + public static WeChatWorkDeleteCropTagRequest Group(string[] groupId) + { + return new WeChatWorkDeleteCropTagRequest(groupId); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteStrategyTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteStrategyTagRequest.cs new file mode 100644 index 000000000..018ed9505 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteStrategyTagRequest.cs @@ -0,0 +1,40 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +public class WeChatWorkDeleteStrategyTagRequest : WeChatWorkRequest +{ + /// + /// 标签组的id列表 + /// + [CanBeNull] + [JsonProperty("group_id")] + [JsonPropertyName("group_id")] + public string[]? GroupId { get; } + /// + /// 标签组的id列表 + /// + [CanBeNull] + [JsonProperty("tag_id")] + [JsonPropertyName("tag_id")] + public string[]? TagId { get; } + + private WeChatWorkDeleteStrategyTagRequest( + string[]? groupId = null, + string[]? tagId = null) + { + GroupId = groupId; + TagId = tagId; + } + + public static WeChatWorkDeleteStrategyTagRequest Tag(string[] tagId) + { + return new WeChatWorkDeleteStrategyTagRequest(tagId: tagId); + } + + public static WeChatWorkDeleteStrategyTagRequest Group(string[] groupId) + { + return new WeChatWorkDeleteStrategyTagRequest(groupId); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetCropTagListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetCropTagListRequest.cs new file mode 100644 index 000000000..051a09701 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetCropTagListRequest.cs @@ -0,0 +1,33 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +/// +/// 获取企业标签库请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetCropTagListRequest : WeChatWorkRequest +{ + /// + /// 要查询的标签id + /// + [CanBeNull] + [JsonProperty("tag_id")] + [JsonPropertyName("tag_id")] + public string[]? TagId { get; } + /// + /// 要查询的标签组id,返回该标签组以及其下的所有标签信息 + /// + [CanBeNull] + [JsonProperty("group_id")] + [JsonPropertyName("group_id")] + public string[]? GroupId { get; } + public WeChatWorkGetCropTagListRequest(string[]? tagId = null, string[]? groupId = null) + { + TagId = tagId; + GroupId = groupId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetStrategyTagListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetStrategyTagListRequest.cs new file mode 100644 index 000000000..e17cc35d0 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetStrategyTagListRequest.cs @@ -0,0 +1,41 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +public class WeChatWorkGetStrategyTagListRequest : WeChatWorkRequest +{ + /// + /// 规则组id + /// + [NotNull] + [JsonProperty("strategy_id")] + [JsonPropertyName("strategy_id")] + public int StrategyId { get; } + /// + /// 要查询的标签id + /// + [CanBeNull] + [JsonProperty("tag_id")] + [JsonPropertyName("tag_id")] + public string[]? TagId { get; } + /// + /// 要查询的标签组id,返回该标签组以及其下的所有标签信息 + /// + [CanBeNull] + [JsonProperty("group_id")] + [JsonPropertyName("group_id")] + public string[]? GroupId { get; } + public WeChatWorkGetStrategyTagListRequest( + int strategyId, + string[]? tagId = null, + string[]? groupId = null) + { + Check.NotDefaultOrNull(strategyId, nameof(strategyId)); + + StrategyId = strategyId; + TagId = tagId; + GroupId = groupId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkMarkCropTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkMarkCropTagRequest.cs new file mode 100644 index 000000000..fc90029c3 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkMarkCropTagRequest.cs @@ -0,0 +1,65 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +/// +/// 编辑客户企业标签请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkMarkCropTagRequest : WeChatWorkRequest +{ + /// + /// 添加外部联系人的userid + /// + [NotNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; } + /// + /// 外部联系人userid + /// + [NotNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public string ExternalUserId { get; } + /// + /// 要标记的标签列表 + /// + [NotNull] + [JsonProperty("add_tag")] + [JsonPropertyName("add_tag")] + public List CreateTag { get; } + /// + /// 要移除的标签列表 + /// + [NotNull] + [JsonProperty("remove_tag")] + [JsonPropertyName("remove_tag")] + public List RemoveTag { get; } + public WeChatWorkMarkCropTagRequest(string userId, string externalUserId) + { + Check.NotNullOrWhiteSpace(userId, nameof(userId)); + Check.NotNullOrWhiteSpace(externalUserId, nameof(externalUserId)); + + UserId = userId; + ExternalUserId = externalUserId; + + CreateTag = new List(); + RemoveTag = new List(); + } + + protected override void Validate() + { + if (CreateTag.IsNullOrEmpty() && + RemoveTag.IsNullOrEmpty()) + { + throw new ArgumentException("CreateTag and RemoveTag cannot be empty simultaneously!"); + } + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateCropTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateCropTagRequest.cs new file mode 100644 index 000000000..b79ec152c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateCropTagRequest.cs @@ -0,0 +1,52 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +/// +/// 编辑企业客户标签请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkUpdateCropTagRequest : WeChatWorkRequest +{ + /// + /// 标签或标签组的id + /// + [NotNull] + [JsonProperty("id")] + [JsonPropertyName("id")] + public string Id { get; } + /// + /// 新的标签或标签组名称,最长为30个字符 + /// + [CanBeNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string? Name { get; } + /// + /// 标签/标签组的次序值。order值大的排序靠前。有效的值范围是[0, 2^32) + /// + [CanBeNull] + [JsonProperty("order")] + [JsonPropertyName("order")] + public int? Order { get; set; } + /// + /// 授权方安装的应用agentid。仅旧的第三方多应用套件需要填此参数 + /// + [CanBeNull] + [JsonProperty("agentid")] + [JsonPropertyName("agentid")] + public string? AgentId { get; set; } + + public WeChatWorkUpdateCropTagRequest(string id, string? name = null) + { + Check.NotNullOrWhiteSpace(id, nameof(id)); + Check.Length(name, nameof(name), 30); + + Id = id; + Name = name; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateStrategyTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateStrategyTagRequest.cs new file mode 100644 index 000000000..b8f1f1a84 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateStrategyTagRequest.cs @@ -0,0 +1,39 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +public class WeChatWorkUpdateStrategyTagRequest : WeChatWorkRequest +{ + /// + /// 标签或标签组的id + /// + [NotNull] + [JsonProperty("id")] + [JsonPropertyName("id")] + public string Id { get; } + /// + /// 新的标签或标签组名称,最长为30个字符 + /// + [CanBeNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string? Name { get; } + /// + /// 标签/标签组的次序值。order值大的排序靠前。有效的值范围是[0, 2^32) + /// + [CanBeNull] + [JsonProperty("order")] + [JsonPropertyName("order")] + public int? Order { get; set; } + + public WeChatWorkUpdateStrategyTagRequest(string id, string? name = null) + { + Check.NotNullOrWhiteSpace(id, nameof(id)); + Check.Length(name, nameof(name), 30); + + Id = id; + Name = name; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateCropTagResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateCropTagResponse.cs new file mode 100644 index 000000000..d10cb037d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateCropTagResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response; +/// +/// 添加企业客户标签响应参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkCreateCropTagResponse : WeChatWorkResponse +{ + /// + /// 标签组 + /// + [NotNull] + [JsonProperty("tag_group")] + [JsonPropertyName("tag_group")] + public CropTagGroup TagGroup { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateStrategyTagResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateStrategyTagResponse.cs new file mode 100644 index 000000000..6f40217d5 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateStrategyTagResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response; +/// +/// 为指定规则组创建企业客户标签 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E4%B8%BA%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E5%88%9B%E5%BB%BA%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE +/// +public class WeChatWorkCreateStrategyTagResponse : WeChatWorkResponse +{ + /// + /// 标签组 + /// + [NotNull] + [JsonProperty("tag_group")] + [JsonPropertyName("tag_group")] + public StrategyTagGroup TagGroup { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetCropTagListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetCropTagListResponse.cs new file mode 100644 index 000000000..b71bcb1a1 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetCropTagListResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response; +/// +/// 获取企业标签库响应参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetCropTagListResponse : WeChatWorkResponse +{ + /// + /// 标签组列表 + /// + [NotNull] + [JsonProperty("tag_group")] + [JsonPropertyName("tag_group")] + public StrategyTagGroup[] TagGroup { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetStrategyTagListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetStrategyTagListResponse.cs new file mode 100644 index 000000000..1a77eaa69 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetStrategyTagListResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response; +/// +/// 获取指定规则组下的企业客户标签响应参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E4%B8%8B%E7%9A%84%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE +/// +public class WeChatWorkGetStrategyTagListResponse : WeChatWorkResponse +{ + /// + /// 标签组列表 + /// + [NotNull] + [JsonProperty("tag_group")] + [JsonPropertyName("tag_group")] + public StrategyTagGroup[] TagGroup { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider.cs new file mode 100644 index 000000000..b73400b14 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider.cs @@ -0,0 +1,108 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response; +using LINGYUN.Abp.WeChat.Work.Token; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags; + +[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)] +public class WeChatWorkCropTagProvider : IWeChatWorkCropTagProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkCropTagProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task GetCropTagListAsync( + WeChatWorkGetCropTagListRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetCropTagListAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task CreateCropTagAsync( + WeChatWorkCreateCropTagRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.CreateCropTagAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task UpdateCropTagAsync( + WeChatWorkUpdateCropTagRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.UpdateCropTagAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task DeleteCropTagAsync( + WeChatWorkDeleteCropTagRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.DeleteCropTagAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task MarkCropTagAsync( + WeChatWorkMarkCropTagRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.MarkCropTagAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkStrategyTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkStrategyTagProvider.cs new file mode 100644 index 000000000..49fe5189e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkStrategyTagProvider.cs @@ -0,0 +1,92 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response; +using LINGYUN.Abp.WeChat.Work.Token; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags; + +[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)] +public class WeChatWorkStrategyTagProvider : IWeChatWorkStrategyTagProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkStrategyTagProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task GetStrategyTagListAsync( + WeChatWorkGetStrategyTagListRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetStrategyTagListAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task CreateStrategyTagAsync( + WeChatWorkCreateStrategyTagRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.CreateStrategyTagAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task UpdateStrategyTagAsync( + WeChatWorkUpdateStrategyTagRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.UpdateStrategyTagAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task DeleteStrategyTagAsync( + WeChatWorkDeleteStrategyTagRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.DeleteStrategyTagAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkEmployExtendProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkEmployExtendProvider.cs new file mode 100644 index 000000000..9622d4e54 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkEmployExtendProvider.cs @@ -0,0 +1,48 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers; +/// +/// 在职继承接口 +/// +public interface IWeChatWorkEmployExtendProvider +{ + /// + /// 分配在职成员的客户 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task TransferCustomerAsync( + WeChatWorkTransferCustomerRequest request, + CancellationToken cancellationToken = default); + /// + /// 查询客户接替状态 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GetTransferResultAsync( + WeChatWorkGetTransferResultRequest request, + CancellationToken cancellationToken = default); + /// + /// 分配在职成员的客户群 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GroupChatOnjobTransferAsync( + WeChatWorkGroupChatOnjobTransferRequest request, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkResignExtendProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkResignExtendProvider.cs new file mode 100644 index 000000000..3a837d768 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkResignExtendProvider.cs @@ -0,0 +1,60 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers; +/// +/// 离职继承接口 +/// +public interface IWeChatWorkResignExtendProvider +{ + /// + /// 获取待分配的离职成员列表 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GetUnassignedListAsync( + WeChatWorkGetUnassignedListRequest request, + CancellationToken cancellationToken = default); + /// + /// 分配离职成员的客户 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task ResignedTransferCustomerAsync( + WeChatWorkResignedTransferCustomerRequest request, + CancellationToken cancellationToken = default); + /// + /// 查询客户接替状态 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GetResignedTransferResultAsync( + WeChatWorkGetResignedTransferResultRequest request, + CancellationToken cancellationToken = default); + /// + /// 分配离职成员的客户群 + /// + /// + /// 详情见: + /// + /// 请求参数 + /// + /// + Task GroupChatTransferAsync( + WeChatWorkGroupChatTransferRequest request, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/GroupChatTransferFailed.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/GroupChatTransferFailed.cs new file mode 100644 index 000000000..a39fb70d4 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/GroupChatTransferFailed.cs @@ -0,0 +1,32 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +/// +/// 没能成功继承的群详情 +/// +public class GroupChatTransferFailed +{ + /// + /// 没能成功继承的群ID + /// + [NotNull] + [JsonProperty("chat_id")] + [JsonPropertyName("chat_id")] + public string ChatId { get; set; } + /// + /// 没能成功继承的群,错误码 + /// + [NotNull] + [JsonProperty("errcode")] + [JsonPropertyName("errcode")] + public int ErrCode { get; set; } + /// + /// 没能成功继承的群,错误描述 + /// + [NotNull] + [JsonProperty("errmsg")] + [JsonPropertyName("errmsg")] + public string ErrMsg { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomer.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomer.cs new file mode 100644 index 000000000..3402836b0 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomer.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +public class TransferCustomer +{ + /// + /// 客户的外部联系人userid + /// + [NotNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public string ExternalUserid { get; set; } + /// + /// 对此客户进行分配的结果, 具体可参考全局错误码, 0表示成功发起接替,待24小时后自动接替,并不代表最终接替成功 + /// + [NotNull] + [JsonProperty("errcode")] + [JsonPropertyName("errcode")] + public int ErrCode { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomerResult.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomerResult.cs new file mode 100644 index 000000000..c76f89df7 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomerResult.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +public class TransferCustomerResult +{ + /// + /// 客户的外部联系人userid + /// + [NotNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public string ExternalUserid { get; set; } + /// + /// 接替状态 + /// + [NotNull] + [JsonProperty("status")] + [JsonPropertyName("status")] + public TransferStatus Status { get; set; } + /// + /// 接替客户的时间,如果是等待接替状态,则为未来的自动接替时间 + /// + [NotNull] + [JsonProperty("takeover_time")] + [JsonPropertyName("takeover_time")] + public long TakeoverTime { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferStatus.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferStatus.cs new file mode 100644 index 000000000..acab61ff8 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferStatus.cs @@ -0,0 +1,30 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +/// +/// 接替状态 +/// +[Description("接替状态")] +public enum TransferStatus +{ + /// + /// 接替完毕 + /// + [Description("接替完毕")] + Completed = 1, + /// + /// 等待接替 + /// + [Description("等待接替")] + Pending = 2, + /// + /// 客户拒绝 + /// + [Description("客户拒绝")] + CustomerReject = 3, + /// + /// 接替成员客户达到上限 + /// + [Description("接替成员客户达到上限")] + ReachesLimit = 4 +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedCustomerInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedCustomerInfo.cs new file mode 100644 index 000000000..f4767e63e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedCustomerInfo.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +public class UnassignedCustomerInfo +{ + /// + /// 离职成员的userid + /// + [NotNull] + [JsonProperty("handover_userid")] + [JsonPropertyName("handover_userid")] + public string HandoverUserid { get; set; } + /// + /// 外部联系人userid + /// + [NotNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public string ExternalUserid { get; set; } + /// + /// 成员离职时间 + /// + [NotNull] + [JsonProperty("dimission_time")] + [JsonPropertyName("dimission_time")] + public long DimissionTime { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomer.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomer.cs new file mode 100644 index 000000000..f8942edf9 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomer.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +/// +/// 分配离职成员客户详情 +/// +public class UnassignedTransferCustomer +{ + /// + /// 客户的外部联系人userid + /// + [NotNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public string ExternalUserid { get; set; } + /// + /// 对此客户进行分配的结果,0表示开始分配流程,待24小时后自动接替,并不代表最终分配成功 + /// + [NotNull] + [JsonProperty("errcode")] + [JsonPropertyName("errcode")] + public int ErrCode { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomerResult.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomerResult.cs new file mode 100644 index 000000000..8379d2f65 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomerResult.cs @@ -0,0 +1,32 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +/// +/// 分配离职成员客户接替状态结果 +/// +public class UnassignedTransferCustomerResult +{ + /// + /// 转接客户的外部联系人userid + /// + [NotNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public string ExternalUserid { get; set; } + /// + /// 接替状态 + /// + [NotNull] + [JsonProperty("status")] + [JsonPropertyName("status")] + public TransferStatus Status { get; set; } + /// + /// 接替客户的时间,如果是等待接替状态,则为未来的自动接替时间 + /// + [NotNull] + [JsonProperty("takeover_time")] + [JsonPropertyName("takeover_time")] + public long TakeoverTime { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetResignedTransferResultRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetResignedTransferResultRequest.cs new file mode 100644 index 000000000..64960ea47 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetResignedTransferResultRequest.cs @@ -0,0 +1,48 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +/// +/// 查询离职成员的客户接替状态请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetResignedTransferResultRequest : WeChatWorkRequest +{ + /// + /// 原添加成员的userid + /// + [NotNull] + [JsonProperty("handover_userid")] + [JsonPropertyName("handover_userid")] + public string HandOverUserId { get; } + /// + /// 接替成员的userid + /// + [NotNull] + [JsonProperty("takeover_userid")] + [JsonPropertyName("takeover_userid")] + public string TakeOverUserId { get; } + /// + /// 分页查询的cursor,每个分页返回的数据不会超过1000条;不填或为空表示获取第一个分页 + /// + [CanBeNull] + [JsonProperty("cursor")] + [JsonPropertyName("cursor")] + public string? Cursor { get; } + public WeChatWorkGetResignedTransferResultRequest( + string handOverUserId, + string takeOverUserId, + string? cursor = null) + { + Check.NotNullOrWhiteSpace(handOverUserId, nameof(handOverUserId)); + Check.NotNullOrWhiteSpace(takeOverUserId, nameof(takeOverUserId)); + + HandOverUserId = handOverUserId; + TakeOverUserId = takeOverUserId; + Cursor = cursor; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetTransferResultRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetTransferResultRequest.cs new file mode 100644 index 000000000..2ea1f6298 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetTransferResultRequest.cs @@ -0,0 +1,48 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +/// +/// 查询客户接替状态请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetTransferResultRequest : WeChatWorkRequest +{ + /// + /// 原跟进成员的userid + /// + [NotNull] + [JsonProperty("handover_userid")] + [JsonPropertyName("handover_userid")] + public string HandOverUserId { get; } + /// + /// 接替成员的userid + /// + [NotNull] + [JsonProperty("takeover_userid")] + [JsonPropertyName("takeover_userid")] + public string TakeOverUserId { get; } + /// + /// 分页查询的cursor,每个分页返回的数据不会超过1000条;不填或为空表示获取第一个分页 + /// + [CanBeNull] + [JsonProperty("cursor")] + [JsonPropertyName("cursor")] + public string? Cursor { get; } + public WeChatWorkGetTransferResultRequest( + string handOverUserId, + string takeOverUserId, + string? cursor = null) + { + Check.NotNullOrWhiteSpace(handOverUserId, nameof(handOverUserId)); + Check.NotNullOrWhiteSpace(takeOverUserId, nameof(takeOverUserId)); + + HandOverUserId = handOverUserId; + TakeOverUserId = takeOverUserId; + Cursor = cursor; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetUnassignedListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetUnassignedListRequest.cs new file mode 100644 index 000000000..75c102ad9 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetUnassignedListRequest.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +public class WeChatWorkGetUnassignedListRequest : WeChatWorkRequest +{ + /// + /// 分页查询游标,字符串类型,适用于数据量较大的情况,如果使用该参数则无需填写page_id,该参数由上一次调用返回 + /// + [CanBeNull] + [JsonProperty("cursor")] + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } + /// + /// 每次返回的最大记录数,默认为1000,最大值为1000 + /// + [CanBeNull] + [JsonProperty("page_size")] + [JsonPropertyName("page_size")] + public int? PageSize { get; set; } + public WeChatWorkGetUnassignedListRequest(string? cursor = null, int? pageSize = 1000) + { + Cursor = cursor; + PageSize = pageSize; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatOnjobTransferRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatOnjobTransferRequest.cs new file mode 100644 index 000000000..dda7a5ed2 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatOnjobTransferRequest.cs @@ -0,0 +1,43 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +/// +/// 分配在职成员的客户群请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGroupChatOnjobTransferRequest : WeChatWorkRequest +{ + /// + /// 新群主ID + /// + [NotNull] + [JsonProperty("new_owner")] + [JsonPropertyName("new_owner")] + public string NewOwner { get; } + /// + /// 需要转群主的客户群ID列表。取值范围: 1 ~ 100 + /// + [NotNull] + [JsonProperty("chat_id_list")] + [JsonPropertyName("chat_id_list")] + public string[] ChatIdList { get; } + public WeChatWorkGroupChatOnjobTransferRequest(string newOwner, string[] chatIdList) + { + Check.NotNullOrWhiteSpace(newOwner, nameof(newOwner)); + Check.NotNullOrEmpty(chatIdList, nameof(chatIdList)); + + if (chatIdList.Length < 1 || chatIdList.Length > 100) + { + throw new ArgumentException("The list of customer group ids that need to be transferred to the group owner must have more than 1 item or less than 100 items"); + } + + NewOwner = newOwner; + ChatIdList = chatIdList; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatTransferRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatTransferRequest.cs new file mode 100644 index 000000000..364427048 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatTransferRequest.cs @@ -0,0 +1,43 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +/// +/// 分配离职成员的客户群请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkGroupChatTransferRequest : WeChatWorkRequest +{ + /// + /// 新群主ID + /// + [NotNull] + [JsonProperty("new_owner")] + [JsonPropertyName("new_owner")] + public string NewOwner { get; } + /// + /// 需要转群主的客户群ID列表。取值范围: 1 ~ 100 + /// + [NotNull] + [JsonProperty("chat_id_list")] + [JsonPropertyName("chat_id_list")] + public string[] ChatIdList { get; } + public WeChatWorkGroupChatTransferRequest(string newOwner, string[] chatIdList) + { + Check.NotNullOrWhiteSpace(newOwner, nameof(newOwner)); + Check.NotNullOrEmpty(chatIdList, nameof(chatIdList)); + + if (chatIdList.Length < 1 || chatIdList.Length > 100) + { + throw new ArgumentException("The list of customer group ids that need to be transferred to the group owner must have more than 1 item or less than 100 items"); + } + + NewOwner = newOwner; + ChatIdList = chatIdList; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkResignedTransferCustomerRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkResignedTransferCustomerRequest.cs new file mode 100644 index 000000000..60ed2712d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkResignedTransferCustomerRequest.cs @@ -0,0 +1,52 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +/// +/// 分配离职成员的客户请求参数 +/// +public class WeChatWorkResignedTransferCustomerRequest : WeChatWorkRequest +{ + /// + /// 原跟进成员的userid + /// + [NotNull] + [JsonProperty("handover_userid")] + [JsonPropertyName("handover_userid")] + public string HandoverUserId { get; } + /// + /// 接替成员的userid + /// + [NotNull] + [JsonProperty("takeover_userid")] + [JsonPropertyName("takeover_userid")] + public string TakeoverUserId { get; } + /// + /// 客户的external_userid列表,每次最多分配100个客户 + /// + [NotNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public string[] ExternalUserId { get; } + public WeChatWorkResignedTransferCustomerRequest(string handoverUserId, string takeoverUserId, string[] externalUserId) + { + Check.NotNullOrWhiteSpace(handoverUserId, nameof(handoverUserId)); + Check.NotNullOrWhiteSpace(takeoverUserId, nameof(takeoverUserId)); + Check.NotNullOrEmpty(externalUserId, nameof(externalUserId)); + + HandoverUserId = handoverUserId; + TakeoverUserId = takeoverUserId; + ExternalUserId = externalUserId; + } + + protected override void Validate() + { + if (ExternalUserId.Length > 100) + { + throw new ArgumentException("Transfer a maximum of 100 customers at a time!"); + } + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkTransferCustomerRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkTransferCustomerRequest.cs new file mode 100644 index 000000000..61fee48e4 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkTransferCustomerRequest.cs @@ -0,0 +1,60 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +/// +/// 分配在职成员的客户请求参数 +/// +/// +/// 详情见: +/// +public class WeChatWorkTransferCustomerRequest : WeChatWorkRequest +{ + /// + /// 原跟进成员的userid + /// + [NotNull] + [JsonProperty("handover_userid")] + [JsonPropertyName("handover_userid")] + public string HandoverUserId { get; } + /// + /// 接替成员的userid + /// + [NotNull] + [JsonProperty("takeover_userid")] + [JsonPropertyName("takeover_userid")] + public string TakeoverUserId { get; } + /// + /// 客户的external_userid列表,每次最多分配100个客户 + /// + [NotNull] + [JsonProperty("external_userid")] + [JsonPropertyName("external_userid")] + public string[] ExternalUserId { get; } + /// + /// 转移成功后发给客户的消息,最多200个字符,不填则使用默认文案 + /// + [CanBeNull] + [JsonProperty("transfer_success_msg")] + [JsonPropertyName("transfer_success_msg")] + public string? TransferSuccessMsg { get; set; } + public WeChatWorkTransferCustomerRequest(string handoverUserId, string takeoverUserId, string[] externalUserId, string? transferSuccessMsg = null) + { + Check.NotNullOrWhiteSpace(handoverUserId, nameof(handoverUserId)); + Check.NotNullOrWhiteSpace(takeoverUserId, nameof(takeoverUserId)); + Check.NotNullOrEmpty(externalUserId, nameof(externalUserId)); + Check.Length(transferSuccessMsg, nameof(transferSuccessMsg), 200); + + HandoverUserId = handoverUserId; + TakeoverUserId = takeoverUserId; + ExternalUserId = externalUserId; + TransferSuccessMsg = transferSuccessMsg; + } + + protected override void Validate() + { + Check.Length(TransferSuccessMsg, nameof(TransferSuccessMsg), 200); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetResignedTransferResultResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetResignedTransferResultResponse.cs new file mode 100644 index 000000000..256950e43 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetResignedTransferResultResponse.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response; +/// +/// 查询离职人员客户接替状态响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetResignedTransferResultResponse : WeChatWorkResponse +{ + /// + /// 转接客户 + /// + [NotNull] + [JsonProperty("customer")] + [JsonPropertyName("customer")] + public UnassignedTransferCustomerResult[] Customer { get; set; } + /// + /// 下个分页的起始cursor + /// + [CanBeNull] + [JsonProperty("next_cursor")] + [JsonPropertyName("next_cursor")] + public string? NextCursor { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetTransferResultResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetTransferResultResponse.cs new file mode 100644 index 000000000..7aed70bf7 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetTransferResultResponse.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response; +/// +/// 查询客户接替状态响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetTransferResultResponse : WeChatWorkResponse +{ + /// + /// 分配客户 + /// + [NotNull] + [JsonProperty("customer")] + [JsonPropertyName("customer")] + public TransferCustomerResult[] Customer { get; set; } + /// + /// 下个分页的起始cursor + /// + [CanBeNull] + [JsonProperty("next_cursor")] + [JsonPropertyName("next_cursor")] + public string? NextCursor { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetUnassignedListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetUnassignedListResponse.cs new file mode 100644 index 000000000..66e42c82b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetUnassignedListResponse.cs @@ -0,0 +1,36 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response; +/// +/// 获取待分配的离职成员列表响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkGetUnassignedListResponse : WeChatWorkResponse +{ + /// + /// 待分配离职成员的客户 + /// + [NotNull] + [JsonProperty("info")] + [JsonPropertyName("info")] + public UnassignedCustomerInfo[] Info { get; set; } + /// + /// 是否是最后一条记录 + /// + [NotNull] + [JsonProperty("is_last")] + [JsonPropertyName("is_last")] + public bool IsLast { get; set; } + /// + /// 分页查询游标,已经查完则返回空(""),使用page_id作为查询参数时不返回 + /// + [CanBeNull] + [JsonProperty("next_cursor")] + [JsonPropertyName("next_cursor")] + public string? NextCursor { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatOnjobTransferResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatOnjobTransferResponse.cs new file mode 100644 index 000000000..7f05c077d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatOnjobTransferResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response; +/// +/// 分配在职成员的客户群响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkGroupChatOnjobTransferResponse : WeChatWorkResponse +{ + /// + /// 没能成功继承的群 + /// + [NotNull] + [JsonProperty("failed_chat_list")] + [JsonPropertyName("failed_chat_list")] + public GroupChatTransferFailed[] FailedChatList { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatTransferResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatTransferResponse.cs new file mode 100644 index 000000000..ad0112bf4 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatTransferResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response; +/// +/// 分配离职成员的客户群响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkGroupChatTransferResponse : WeChatWorkResponse +{ + /// + /// 没能成功继承的群 + /// + [NotNull] + [JsonProperty("failed_chat_list")] + [JsonPropertyName("failed_chat_list")] + public GroupChatTransferFailed[] FailedChatList { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkResignedTransferCustomerResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkResignedTransferCustomerResponse.cs new file mode 100644 index 000000000..255fdab7d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkResignedTransferCustomerResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response; +/// +/// 分配离职成员的客户响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkResignedTransferCustomerResponse : WeChatWorkResponse +{ + /// + /// 分配离职成员客户结果 + /// + [NotNull] + [JsonProperty("customer")] + [JsonPropertyName("customer")] + public UnassignedTransferCustomer[] Customer { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkTransferCustomerResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkTransferCustomerResponse.cs new file mode 100644 index 000000000..638d3b4ef --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkTransferCustomerResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response; +/// +/// 分配在职成员的客户响应结果 +/// +/// +/// 详情见: +/// +public class WeChatWorkTransferCustomerResponse : WeChatWorkResponse +{ + /// + /// 分配客户 + /// + [NotNull] + [JsonProperty("customer")] + [JsonPropertyName("customer")] + public TransferCustomer[] Customer { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkEmployExtendProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkEmployExtendProvider.cs new file mode 100644 index 000000000..63baaeeb1 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkEmployExtendProvider.cs @@ -0,0 +1,76 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response; +using LINGYUN.Abp.WeChat.Work.Token; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers; + +[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)] +public class WeChatWorkEmployExtendProvider : IWeChatWorkEmployExtendProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkEmployExtendProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task TransferCustomerAsync( + WeChatWorkTransferCustomerRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.AssignCustomerAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task GetTransferResultAsync( + WeChatWorkGetTransferResultRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetTransferResultAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task GroupChatOnjobTransferAsync( + WeChatWorkGroupChatOnjobTransferRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.OnjobTransferAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkResignExtendProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkResignExtendProvider.cs new file mode 100644 index 000000000..3ff4fae55 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkResignExtendProvider.cs @@ -0,0 +1,92 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response; +using LINGYUN.Abp.WeChat.Work.Token; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers; + +[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)] +public class WeChatWorkResignExtendProvider : IWeChatWorkResignExtendProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkResignExtendProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task GetUnassignedListAsync( + WeChatWorkGetUnassignedListRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetUnassignedListAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task ResignedTransferCustomerAsync( + WeChatWorkResignedTransferCustomerRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.ResignedTransferCustomerAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task GetResignedTransferResultAsync( + WeChatWorkGetResignedTransferResultRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GetResignedTransferResultAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } + + public async virtual Task GroupChatTransferAsync( + WeChatWorkGroupChatTransferRequest request, + CancellationToken cancellationToken = default) + { + Check.NotNull(request, nameof(request)); + + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateWeChatWorkApiClient(); + + using var response = await client.GroupChatTransferAsync(token.AccessToken, request, cancellationToken); + + var wechatResponse = await response.DeserializeObjectAsync(); + wechatResponse.ThrowIfNotSuccess(); + return wechatResponse; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/Newtonsoft/Json/ExternalProfileNewtonsoftJsonConverter.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/Newtonsoft/Json/ExternalProfileNewtonsoftJsonConverter.cs new file mode 100644 index 000000000..e3521bde5 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/Newtonsoft/Json/ExternalProfileNewtonsoftJsonConverter.cs @@ -0,0 +1,70 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; + +namespace Newtonsoft.Json; +internal class ExternalProfileNewtonsoftJsonConverter : JsonConverter +{ + public override bool CanWrite => true; + + public override void WriteJson(JsonWriter writer, ExternalProfile? value, JsonSerializer serializer) + { + writer.WriteStartObject(); + + if (value?.ExternalCorpName != null) + { + writer.WritePropertyName("external_corp_name"); + serializer.Serialize(writer, value.ExternalCorpName); + } + + if (value?.WechatChannels != null) + { + writer.WritePropertyName("wechat_channels"); + serializer.Serialize(writer, value.WechatChannels, value.WechatChannels.GetType()); + } + + if (value?.ExternalAttributes != null) + { + writer.WritePropertyName("external_attr"); + serializer.Serialize(writer, value.ExternalAttributes, value.ExternalAttributes.GetType()); + } + + writer.WriteEndObject(); + } + + public override ExternalProfile? ReadJson(JsonReader reader, Type objectType, ExternalProfile? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var jObject = JObject.Load(reader); + + var externalProfile = new ExternalProfile + { + WechatChannels = new List(), + ExternalAttributes = new List() + }; + + if (jObject.TryGetValue("external_corp_name", out var externalCorpNameToken)) + { + externalProfile.ExternalCorpName = externalCorpNameToken.ToString(); + } + if (jObject.TryGetValue("wechat_channels", out var wechatChannelsToken) && wechatChannelsToken.Type == JTokenType.Array) + { + externalProfile.WechatChannels = wechatChannelsToken.ToObject>(serializer)!; + } + if (jObject.TryGetValue("external_attr", out var externalAttrsToken) && externalAttrsToken.Type == JTokenType.Array) + { + foreach ( var externalAttrToken in externalAttrsToken) + { + var typeToken = externalAttrToken.SelectToken("type"); + if (typeToken != null) + { + var type = typeToken.Value(); + externalProfile.ExternalAttributes.Add( + ExternalAttributeDeserializeFactory.CreateExternalAttribute(type, externalAttrToken)); + } + } + } + + return externalProfile; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/README.md b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/README.md new file mode 100644 index 000000000..1ff6cd2c2 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/README.md @@ -0,0 +1,29 @@ +# LINGYUN.Abp.WeChat.Work.ExternalContact + +企业微信客户联系模块,提供企业微信应用开发的客户联系功能实现。 + +## 功能特性 + + +## 模块引用 + +```csharp +[DependsOn(typeof(AbpWeChatWorkExternalContactModule))] +public class YouProjectModule : AbpModule +{ + // other +} +``` + +## 配置项 + + +## 消息处理 + + +## 事件处理 + + +## 更多文档 + +* [企业微信客户联系文档](https://developer.work.weixin.qq.com/document/path/92109) diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Attachments.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Attachments.cs new file mode 100644 index 000000000..43d71ccc7 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Attachments.cs @@ -0,0 +1,33 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Request; +using LINGYUN.Abp.WeChat.Work.Utils; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + public async static Task UploadAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkUploadAttachmentRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/media/upload_attachment"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + urlBuilder.AppendFormat("&media_type={0}", request.MediaType.ToString().ToLowerInvariant()); + urlBuilder.AppendFormat("&attachment_type={0}", request.AttachmentType); + + var fileBytes = await request.Content.GetStream().GetAllBytesAsync(); + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = WeChatWorkHttpContentBuildHelper.BuildUploadMediaContent("media", fileBytes, request.Content.FileName) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Contacts.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Contacts.cs new file mode 100644 index 000000000..fde7c64cf --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Contacts.cs @@ -0,0 +1,28 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Request; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + public async static Task GetExternalContactListAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetExternalContactListRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/contact_list"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Customers.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Customers.cs new file mode 100644 index 000000000..2a3ce8111 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Customers.cs @@ -0,0 +1,89 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + public async static Task GetCustomerListAsync( + this HttpMessageInvoker client, + string accessToken, + string userId, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/list"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + urlBuilder.AppendFormat("&userid={0}", userId); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Get, + urlBuilder.ToString()); + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task BulkGetCustomerAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkBulkGetCustomerRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/batch/get_by_user"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GetCustomerAsync( + this HttpMessageInvoker client, + string accessToken, + string externalUserid, + string? cursor = null, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/get"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + urlBuilder.AppendFormat("&external_userid={0}", externalUserid); + if (!cursor.IsNullOrWhiteSpace()) + { + urlBuilder.AppendFormat("&cursor={0}", cursor); + } + + var httpRequest = new HttpRequestMessage( + HttpMethod.Get, + urlBuilder.ToString()); + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task UpdateCustomerRemarkAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkUpdateCustomerRemarkRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/remark"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Follows.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Follows.cs new file mode 100644 index 000000000..868b0e865 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Follows.cs @@ -0,0 +1,23 @@ +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + public async static Task GetFollowUserListAsync( + this HttpMessageInvoker client, + string accessToken, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/get_follow_user_list"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Get, + urlBuilder.ToString()); + + return await client.SendAsync(httpRequest, cancellationToken); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.GroupChats.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.GroupChats.cs new file mode 100644 index 000000000..c8a2d3e80 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.GroupChats.cs @@ -0,0 +1,69 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + public async static Task GetGroupChatListAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetGroupChatListRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/groupchat/list"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GetGroupChatAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetGroupChatRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/groupchat/get"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task OpengIdToChatIdAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkOpengIdToChatIdRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/opengid_to_chatid"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Strategies.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Strategies.cs new file mode 100644 index 000000000..833baac6b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Strategies.cs @@ -0,0 +1,128 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + public async static Task GetCustomerStrategyListAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetCustomerStrategyListRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/list"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GetCustomerStrategyAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetCustomerStrategyRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/get"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GetCustomerStrategyRangeAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetCustomerStrategyRangeRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/get_range"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task CreateCustomerStrategyAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkCreateCustomerStrategyRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/create"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task UpdateCustomerStrategyAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkUpdateCustomerStrategyRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/edit"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task DeleteCustomerStrategyAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkDeleteCustomerStrategyRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/del"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tags.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tags.cs new file mode 100644 index 000000000..8f6fb24b0 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tags.cs @@ -0,0 +1,188 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + public async static Task GetCropTagListAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetCropTagListRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/get_corp_tag_list"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task CreateCropTagAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkCreateCropTagRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/add_corp_tag"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task UpdateCropTagAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkUpdateCropTagRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/edit_corp_tag"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task DeleteCropTagAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkDeleteCropTagRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/del_corp_tag"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task MarkCropTagAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkMarkCropTagRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/mark_tag"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GetStrategyTagListAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetStrategyTagListRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/get_strategy_tag_list"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task CreateStrategyTagAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkCreateStrategyTagRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/add_strategy_tag"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task UpdateStrategyTagAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkUpdateStrategyTagRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/edit_strategy_tag"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task DeleteStrategyTagAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkDeleteStrategyTagRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/del_strategy_tag"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Transfers.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Transfers.cs new file mode 100644 index 000000000..3b32435bb --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Transfers.cs @@ -0,0 +1,148 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + public async static Task AssignCustomerAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkTransferCustomerRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/transfer_customer"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GetTransferResultAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetTransferResultRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/transfer_result"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task OnjobTransferAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGroupChatOnjobTransferRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/groupchat/onjob_transfer"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GetUnassignedListAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetUnassignedListRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/get_unassigned_list"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task ResignedTransferCustomerAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkResignedTransferCustomerRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/resigned/transfer_customer"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GetResignedTransferResultAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetResignedTransferResultRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/resigned/transfer_result"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GroupChatTransferAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGroupChatTransferRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/externalcontact/groupchat/transfer"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()), + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.cs new file mode 100644 index 000000000..072b15046 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.cs @@ -0,0 +1,5 @@ +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpResponseDeserializeExtensions.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpResponseDeserializeExtensions.cs new file mode 100644 index 000000000..f3abd415d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpResponseDeserializeExtensions.cs @@ -0,0 +1,19 @@ +using LINGYUN.Abp.WeChat.Work; +using Newtonsoft.Json; +using System.Threading.Tasks; +using Volo.Abp.Http.Client; + +namespace System.Net.Http; +internal static class HttpResponseDeserializeExtensions +{ + public async static Task DeserializeObjectAsync(this HttpResponseMessage response) where T : WeChatWorkResponse + { + if (!response.IsSuccessStatusCode) + { + throw new AbpRemoteCallException($"Wechat work request error: {response.StatusCode} - {response.ReasonPhrase}"); + } + var responseContent = await response.Content.ReadAsStringAsync(); + + return JsonConvert.DeserializeObject(responseContent)!; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Text/Json/Serialization/ExternalProfileSystemTextJsonConverter.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Text/Json/Serialization/ExternalProfileSystemTextJsonConverter.cs new file mode 100644 index 000000000..ec532e9f1 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Text/Json/Serialization/ExternalProfileSystemTextJsonConverter.cs @@ -0,0 +1,63 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Models; +using System.Collections.Generic; + +namespace System.Text.Json.Serialization; +internal class ExternalProfileSystemTextJsonConverter : JsonConverter +{ + public override ExternalProfile Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var doc = JsonDocument.ParseValue(ref reader); + var root = doc.RootElement; + + var externalProfile = new ExternalProfile + { + WechatChannels = new List(), + ExternalAttributes = new List() + }; + + if (root.TryGetProperty("external_corp_name", out var corpNameElement)) + { + externalProfile.ExternalCorpName = corpNameElement.GetRawText(); + } + if (root.TryGetProperty("wechat_channels", out var wechatChannelsElement) && wechatChannelsElement.ValueKind == JsonValueKind.Array) + { + externalProfile.WechatChannels = JsonSerializer.Deserialize>(wechatChannelsElement.GetRawText(), options)!; + } + if (root.TryGetProperty("external_attr", out var externalAttrsElement) && externalAttrsElement.ValueKind == JsonValueKind.Array) + { + foreach ( var externalAttrElement in externalAttrsElement.EnumerateArray()) + { + if (externalAttrElement.TryGetProperty("type", out var typeElement) && typeElement.ValueKind != JsonValueKind.Null) + { + var type = typeElement.Deserialize(); + + externalProfile.ExternalAttributes.Add( + ExternalAttributeDeserializeFactory.CreateExternalAttribute(type, externalAttrElement)); + } + } + } + + return externalProfile; + } + + public override void Write(Utf8JsonWriter writer, ExternalProfile value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WritePropertyName("external_corp_name"); + JsonSerializer.Serialize(writer, value.ExternalCorpName, options); + + if (value.WechatChannels != null) + { + writer.WritePropertyName("wechat_channels"); + JsonSerializer.Serialize(writer, value.WechatChannels, value.WechatChannels.GetType(), options); + } + if (value.ExternalAttributes != null) + { + writer.WritePropertyName("external_attr"); + JsonSerializer.Serialize(writer, value.ExternalAttributes, value.ExternalAttributes.GetType(), options); + } + + writer.WriteEndObject(); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Handlers/LINGYUN/Abp/WeChat/Work/Handlers/Messages/WeChatWorkEventEventHandler.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Handlers/LINGYUN/Abp/WeChat/Work/Handlers/Messages/WeChatWorkEventEventHandler.cs index af5ef347c..3c078f657 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Handlers/LINGYUN/Abp/WeChat/Work/Handlers/Messages/WeChatWorkEventEventHandler.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Handlers/LINGYUN/Abp/WeChat/Work/Handlers/Messages/WeChatWorkEventEventHandler.cs @@ -21,6 +21,7 @@ public class WeChatWorkEventEventHandler : IDistributedEventHandler>, IDistributedEventHandler>, IDistributedEventHandler>, + IDistributedEventHandler>, IDistributedEventHandler>, IDistributedEventHandler>, IDistributedEventHandler>, @@ -113,6 +114,11 @@ public class WeChatWorkEventEventHandler : await _messageHandler.HandleEventAsync(eventData.Event); } + public async virtual Task HandleEventAsync(WeChatWorkEventMessageEto eventData) + { + await _messageHandler.HandleEventAsync(eventData.Event); + } + public async virtual Task HandleEventAsync(WeChatWorkEventMessageEto eventData) { await _messageHandler.HandleEventAsync(eventData.Event); diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN.Abp.WeChat.Work.csproj b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN.Abp.WeChat.Work.csproj index 64de80866..7ad47e365 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN.Abp.WeChat.Work.csproj +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN.Abp.WeChat.Work.csproj @@ -10,6 +10,7 @@ false false false + True diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkModule.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkModule.cs index fa7c3c2c3..c7855d4d9 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkModule.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkModule.cs @@ -42,20 +42,8 @@ public class AbpWeChatWorkModule : AbpModule options.MapCodeNamespace(WeChatWorkErrorCodes.Namespace, typeof(WeChatWorkResource)); }); - context.Services.AddHttpClient(AbpWeChatWorkGlobalConsts.ApiClient, - options => - { - options.BaseAddress = new Uri("https://qyapi.weixin.qq.com"); - }); - context.Services.AddHttpClient(AbpWeChatWorkGlobalConsts.OAuthClient, - options => - { - options.BaseAddress = new Uri("https://open.weixin.qq.com"); - }); - context.Services.AddHttpClient(AbpWeChatWorkGlobalConsts.LoginClient, - options => - { - options.BaseAddress = new Uri("https://login.work.weixin.qq.com"); - }); + context.Services.AddApiClient(); + context.Services.AddOAuthClient(); + context.Services.AddLoginClient(); } } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/IWeChatWorkApprovalTemplateProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/IWeChatWorkApprovalTemplateProvider.cs new file mode 100644 index 000000000..f9af22a59 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/IWeChatWorkApprovalTemplateProvider.cs @@ -0,0 +1,88 @@ +using LINGYUN.Abp.WeChat.Work.Approvals.Request; +using LINGYUN.Abp.WeChat.Work.Approvals.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.Approvals; +// TODO: 自建应用审批未实现: https://developer.work.weixin.qq.com/document/path/90269 +/// +/// 审批模板接口 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/91854 +/// +public interface IWeChatWorkApprovalTemplateProvider +{ + /// + /// 创建审批模板 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/97437 + /// + /// 创建审批模板请求参数 + /// + /// 创建审批模板响应参数 + Task CreateTemplateAsync( + WeChatWorkCreateTemplateRequest request, + CancellationToken cancellationToken = default); + /// + /// 更新审批模板 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/97438 + /// + /// 更新审批模板请求参数 + /// + /// 更新审批模板响应参数 + Task UpdateTemplateAsync( + WeChatWorkCreateTemplateRequest request, + CancellationToken cancellationToken = default); + /// + /// 获取审批模板详情 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/91982 + /// + /// 获取审批模板请求参数 + /// + /// 审批模板响应参数 + Task GetTemplateAsync( + WeChatWorkGetTemplateRequest request, + CancellationToken cancellationToken = default); + /// + /// 批量获取审批单号 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/91816 + /// + /// 批量获取审批单号请求参数 + /// + /// 批量获取审批单号响应参数 + Task GetApprovalInfoAsync( + WeChatWorkGetApprovalInfoRequest request, + CancellationToken cancellationToken = default); + /// + /// 获取审批申请详情 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/91983 + /// + /// 获取审批申请详情请求参数 + /// + /// 获取审批申请详情响应参数 + Task GetApprovalDetailAsync( + WeChatWorkGetApprovalDetailRequest request, + CancellationToken cancellationToken = default); + /// + /// 提交审批申请 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/91853 + /// + /// 提交审批申请请求参数 + /// + /// 提交审批申请响应参数 + Task ApplyEventAsync( + WeChatWorkApplyEventRequest request, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyData.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyData.cs new file mode 100644 index 000000000..622947202 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyData.cs @@ -0,0 +1,28 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 审批申请数据 +/// +public class ApprovalApplyData +{ + /// + /// 审批申请详情,由多个表单控件及其内容组成,其中包含需要对控件赋值的信息 + /// + [NotNull] + [JsonProperty("contents")] + [JsonPropertyName("contents")] + public List Contents { get; set; } + public ApprovalApplyData() + { + + } + + public ApprovalApplyData(List contents) + { + Contents = contents; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyProcess.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyProcess.cs new file mode 100644 index 000000000..4877fdac9 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyProcess.cs @@ -0,0 +1,28 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 申请流程 +/// +public class ApprovalApplyProcess +{ + /// + /// 流程节点列表 + /// + [NotNull] + [JsonProperty("node_list")] + [JsonPropertyName("node_list")] + public List Nodes { get; set; } + public ApprovalApplyProcess() + { + + } + + public ApprovalApplyProcess(List nodes) + { + Nodes = nodes; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyProcessNode.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyProcessNode.cs new file mode 100644 index 000000000..3bc9b6201 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyProcessNode.cs @@ -0,0 +1,75 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 申请流程节点 +/// +public class ApprovalApplyProcessNode +{ + /// + /// 节点类型 1:审批人 2:抄送人 3:办理人 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public ApprovalProcessNodeType Type { get; set; } + /// + /// 多人审批方式 1-会签;2-或签 3-依次审批 + /// + /// + /// type为1、3时必填 + /// + [NotNull] + [JsonProperty("apv_rel")] + [JsonPropertyName("apv_rel")] + public byte? ApvRel { get; set; } + /// + /// 用户id + /// + [NotNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; set; } + public ApprovalApplyProcessNode() + { + + } + + private ApprovalApplyProcessNode(ApprovalProcessNodeType type, string userId, byte? apvRel = null) + { + Type = type; + UserId = userId; + ApvRel = apvRel; + } + /// + /// 审批人节点 + /// + /// 审批人Id + /// 多人审批方式 1-会签;2-或签 3-依次审批 + /// + public static ApprovalApplyProcessNode Approver(string userId, byte apvRel) + { + return new ApprovalApplyProcessNode(ApprovalProcessNodeType.Approver, userId, apvRel); + } + /// + /// 办理人节点 + /// + /// 办理人Id + /// 多人审批方式 1-会签;2-或签 3-依次审批 + /// + public static ApprovalApplyProcessNode Handler(string userId, byte apvRel) + { + return new ApprovalApplyProcessNode(ApprovalProcessNodeType.Handler, userId, apvRel); + } + /// + /// 抄送人节点 + /// + /// 抄送人Id + /// + public static ApprovalApplyProcessNode To(string userId) + { + return new ApprovalApplyProcessNode(ApprovalProcessNodeType.CC, userId); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyer.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyer.cs new file mode 100644 index 000000000..446985def --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApplyer.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 申请人信息 +/// +public class ApprovalApplyer : ApprovalUser +{ + /// + /// 申请人所在部门id + /// + [NotNull] + [JsonProperty("partyid")] + [JsonPropertyName("partyid")] + public int PartyId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApproverAttr.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApproverAttr.cs new file mode 100644 index 000000000..b72a1821c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalApproverAttr.cs @@ -0,0 +1,19 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 节点审批方式 +/// +public enum ApprovalApproverAttr : byte +{ + /// + /// 或签 + /// + [Description("或签")] + OrSign = 1, + /// + /// 会签 + /// + [Description("会签")] + CoSign = 2, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalComment.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalComment.cs new file mode 100644 index 000000000..cebab51ca --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalComment.cs @@ -0,0 +1,47 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 审批申请备注信息 +/// +public class ApprovalComment +{ + /// + /// 备注人信息 + /// + [CanBeNull] + [JsonProperty("commentUserInfo")] + [JsonPropertyName("commentUserInfo")] + public ApprovalUser CommentUserInfo { get; set; } + /// + /// 审批申请提交时间,Unix时间戳 + /// + [NotNull] + [JsonProperty("commenttime")] + [JsonPropertyName("commenttime")] + public long CommentTime { get; set; } + /// + /// 备注文本内容 + /// + [NotNull] + [JsonProperty("commentcontent")] + [JsonPropertyName("commentcontent")] + public string CommentContent { get; set; } + /// + /// 备注id + /// + [NotNull] + [JsonProperty("commentid")] + [JsonPropertyName("commentid")] + public string CommentId { get; set; } + /// + /// 备注附件id,可能有多个,微盘文件无法获取 + /// + [CanBeNull] + [JsonProperty("media_id")] + [JsonPropertyName("media_id")] + public List MediaId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalControlData.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalControlData.cs new file mode 100644 index 000000000..c98e6d9bb --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalControlData.cs @@ -0,0 +1,65 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 审批申请单控件数据 +/// +public class ApprovalControlData +{ + /// + /// 控件类型 + /// + /// + /// Text-文本
+ /// Textarea-多行文本
+ /// Number-数字
+ /// Money-金额
+ /// Date-日期/日期+时间
+ /// Selector-单选/多选
+ /// Contact-成员/部门
+ /// Tips-说明文字
+ /// File-附件
+ /// Table-明细
+ /// Location-位置
+ /// RelatedApproval-关联审批单
+ /// Formula-公式
+ /// DateRange-时长 + ///
+ [NotNull] + [JsonProperty("control")] + [JsonPropertyName("control")] + public string Control { get; set; } + /// + /// 控件id:控件的唯一id,可通过“获取审批模板详情”接口获取 + /// + [NotNull] + [JsonProperty("id")] + [JsonPropertyName("id")] + public string Id { get; set; } + /// + /// 控件值 ,需在此为申请人在各个控件中填写内容不同控件有不同的赋值参数 + /// + /// + /// 模板配置的控件属性为必填时,对应value值需要有值。 + /// + [NotNull] + [JsonProperty("value")] + [JsonPropertyName("value")] + public ApprovalControlValue Value { get; set; } + /// + /// 控件隐藏标识,为1表示控件被隐藏 + /// + [CanBeNull] + [JsonProperty("display")] + [JsonPropertyName("display")] + public int? Display { get; set; } + /// + /// 控件必输标识,为1表示控件必输 + /// + [CanBeNull] + [JsonProperty("require")] + [JsonPropertyName("require")] + public int? Require { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalControlValue.cs new file mode 100644 index 000000000..f82d35684 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalControlValue.cs @@ -0,0 +1,86 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 审批申请单数据 +/// +public class ApprovalControlValue +{ + /// + /// 文本内容,即申请人在此控件填写的文本内容 + /// + [CanBeNull] + [JsonProperty("text")] + [JsonPropertyName("text")] + public string Text { get; set; } + /// + /// 数字内容,即申请人在此控件填写的数字内容 + /// + [CanBeNull] + [JsonProperty("new_number")] + [JsonPropertyName("new_number")] + public decimal? NewNumber { get; set; } + /// + /// 金额内容,即申请人在此控件填写的金额内容 + /// + [CanBeNull] + [JsonProperty("new_money")] + [JsonPropertyName("new_money")] + public decimal? NewMoney { get; set; } + /// + /// 所选成员内容,即申请人在此控件选择的成员,多选模式下可以有多个 + /// + [NotNull] + [JsonProperty("members")] + [JsonPropertyName("members")] + public List Members { get; set; } + /// + /// 所选部门内容,即申请人在此控件选择的部门,多选模式下可能有多个 + /// + [NotNull] + [JsonProperty("departments")] + [JsonPropertyName("departments")] + public List Departments { get; set; } + /// + /// 附件列表 + /// + [NotNull] + [JsonProperty("files")] + [JsonPropertyName("files")] + public List Files { get; set; } + /// + /// 明细内容,一个明细控件可能包含多个子明细 + /// + [NotNull] + [JsonProperty("children")] + [JsonPropertyName("children")] + public List Children { get; set; } + /// + /// 选择内容 + /// + [CanBeNull] + [JsonProperty("selector")] + [JsonPropertyName("selector")] + public SelectorValue Selector { get; set; } + /// + /// 关联审批单 + /// + [NotNull] + [JsonProperty("related_approval")] + [JsonPropertyName("related_approval")] + public List RelatedApproval { get; set; } +} + +public class ApprovalDataChildrenValue +{ + /// + /// 明细内容,一个明细控件可能包含多个子明细 + /// + [NotNull] + [JsonProperty("list")] + [JsonPropertyName("list")] + public List List { get; set; } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalData.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalData.cs new file mode 100644 index 000000000..baf466a4d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalData.cs @@ -0,0 +1,30 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; + +/// +/// 审批申请单数据 +/// +public class ApprovalData +{ + /// + /// 审批申请详情,由多个表单控件及其内容组成,其中包含需要对控件赋值的信息 + /// + [NotNull] + [JsonProperty("contents")] + [JsonPropertyName("contents")] + public List Contents { get; set; } + public ApprovalData() + { + + } + + public ApprovalData(List contents) + { + Contents = contents; + } +} + diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalDetailInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalDetailInfo.cs new file mode 100644 index 000000000..71c747dd3 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalDetailInfo.cs @@ -0,0 +1,96 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 审批申请详情 +/// +public class ApprovalDetailInfo +{ + /// + /// 审批编号 + /// + [NotNull] + [JsonProperty("sp_no")] + [JsonPropertyName("sp_no")] + public string SpNo { get; set; } + /// + /// 审批申请类型名称(审批模板名称) + /// + [NotNull] + [JsonProperty("sp_name")] + [JsonPropertyName("sp_name")] + public string SpName { get; set; } + /// + /// 申请单状态 + /// + [NotNull] + [JsonProperty("sp_status")] + [JsonPropertyName("sp_status")] + public ApprovalSpStatus SpStatus { get; set; } + /// + /// 审批模板id。可在“获取审批申请详情”、“审批状态变化回调通知”中获得,也可在审批模板的模板编辑页面链接中获得。 + /// + [NotNull] + [JsonProperty("template_id")] + [JsonPropertyName("template_id")] + public string TemplateId { get; set; } + /// + /// 审批申请提交时间,Unix时间戳 + /// + [NotNull] + [JsonProperty("apply_time")] + [JsonPropertyName("apply_time")] + public long ApplyTime { get; set; } + /// + /// 申请人信息 + /// + [CanBeNull] + [JsonProperty("applyer")] + [JsonPropertyName("applyer")] + public ApprovalApplyer Applyer { get; set; } + /// + /// 批量申请人信息(和applyer字段互斥) + /// + [CanBeNull] + [JsonProperty("batch_applyer")] + [JsonPropertyName("batch_applyer")] + public List BatchApplyer { get; set; } + /// + /// 审批流程信息,可能有多个审批节点 + /// + [NotNull] + [JsonProperty("sp_record")] + [JsonPropertyName("sp_record")] + public List SpRecord { get; set; } + /// + /// 抄送信息,可能有多个抄送节点 + /// + [CanBeNull] + [JsonProperty("notifyer")] + [JsonPropertyName("notifyer")] + public List Notifyer { get; set; } + /// + /// 审批申请数据 + /// + [NotNull] + [JsonProperty("apply_data")] + [JsonPropertyName("apply_data")] + public ApprovalData ApplyData { get; set; } + /// + /// 审批申请备注信息,可能有多个备注节点 + /// + [NotNull] + [JsonProperty("comments")] + [JsonPropertyName("comments")] + public List Comments { get; set; } + /// + /// 审批流程列表 + /// + [NotNull] + [JsonProperty("process_list")] + [JsonPropertyName("process_list")] + public ApprovalProcess Process { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalInfoFilter.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalInfoFilter.cs new file mode 100644 index 000000000..47d6be320 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalInfoFilter.cs @@ -0,0 +1,51 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 筛选条件 +/// +public class ApprovalInfoFilter +{ + /// + /// 筛选类型 + /// + /// + /// 包括如下值:
+ /// template_id - 模板类型/模板id;
+ /// creator - 申请人;
+ /// department - 审批单提单者所在部门;
+ /// sp_status - 审批状态;
+ /// record_type - 审批单类型属性;
+ ///
+ /// 注意:
+ /// 1、仅“部门”支持同时配置多个筛选条件。
+ /// 2、不同类型的筛选条件之间为“与”的关系,同类型筛选条件之间为“或”的关系
+ /// 3、record_type筛选类型仅支持2021/05/31以后新提交的审批单,历史单不支持表单类型属性过滤
+ ///
+ [NotNull] + [JsonProperty("key")] + [JsonPropertyName("key")] + public string Key { get; set; } + /// + /// 筛选值 + /// + /// + /// 对应值:
+ /// template_id-模板id;
+ /// creator-申请人userid;
+ /// department-所在部门id;
+ /// sp_status-审批单状态(1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付);
+ /// record_type-审批单类型属性(1-请假;2-打卡补卡;3-出差;4-外出;5-加班; 6- 调班;7-会议室预定;8-退款审批;9-红包报销审批)
+ ///
+ [NotNull] + [JsonProperty("value")] + [JsonPropertyName("value")] + public string Value { get; set; } + public ApprovalInfoFilter(string key, string value) + { + Key = key; + Value = value; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcess.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcess.cs new file mode 100644 index 000000000..3c4554e23 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcess.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 流程 +/// +public class ApprovalProcess +{ + /// + /// 流程节点 + /// + [NotNull] + [JsonProperty("node_list")] + [JsonPropertyName("node_list")] + public List Nodes { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessNode.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessNode.cs new file mode 100644 index 000000000..9135ed3a5 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessNode.cs @@ -0,0 +1,40 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 流程节点 +/// +public class ApprovalProcessNode +{ + /// + /// 节点类型 1:审批人 2:抄送人 3:办理人 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public ApprovalProcessNodeType Type { get; set; } + /// + /// 节点状态 1-审批中;2-同意;3-驳回;4-转审;11-退回给指定审批人;12-加签;13-同意并加签;14-办理;15-转交 + /// + [NotNull] + [JsonProperty("sp_status")] + [JsonPropertyName("sp_status")] + public ApprovalProcessNodeStatus Status { get; set; } + /// + /// 多人审批方式 1-会签;2-或签 3-依次审批 + /// + [CanBeNull] + [JsonProperty("apv_rel")] + [JsonPropertyName("apv_rel")] + public byte? ApvRel { get; set; } + /// + /// 子节点列表 + /// + [CanBeNull] + [JsonProperty("sub_node_list")] + [JsonPropertyName("sub_node_list")] + public List SubNodes { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessNodeStatus.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessNodeStatus.cs new file mode 100644 index 000000000..dee8610f9 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessNodeStatus.cs @@ -0,0 +1,54 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 节点状态 +/// +public enum ApprovalProcessNodeStatus : byte +{ + /// + /// 审批中 + /// + [Description("审批中")] + UnderApproval = 1, + /// + /// 同意 + /// + [Description("同意")] + Agree = 2, + /// + /// 驳回 + /// + [Description("驳回")] + Rejection = 3, + /// + /// 转审 + /// + [Description("转审")] + TransferForReview = 4, + /// + /// 退回给指定审批人 + /// + [Description("退回给指定审批人")] + ReturnToApprover = 11, + /// + /// 加签 + /// + [Description("加签")] + Additional = 12, + /// + /// 同意并加签 + /// + [Description("同意并加签")] + AgreeAndSign = 13, + /// + /// 办理 + /// + [Description("办理")] + Processing = 14, + /// + /// 转交 + /// + [Description("转交")] + Transfer = 15, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessNodeType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessNodeType.cs new file mode 100644 index 000000000..c1bfa0a0e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessNodeType.cs @@ -0,0 +1,24 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 节点类型 +/// +public enum ApprovalProcessNodeType : byte +{ + /// + /// 审批人 + /// + [Description("审批人")] + Approver = 1, + /// + /// 抄送人 + /// + [Description("抄送人")] + CC = 2, + /// + /// 办理人 + /// + [Description("办理人")] + Handler = 3, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessSubNode.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessSubNode.cs new file mode 100644 index 000000000..f4734e9ad --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalProcessSubNode.cs @@ -0,0 +1,47 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 子节点 +/// +public class ApprovalProcessSubNode +{ + /// + /// 处理人信息 + /// + [NotNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; set; } + /// + /// 审批/办理意见 + /// + [NotNull] + [JsonProperty("speech")] + [JsonPropertyName("speech")] + public string Speech { get; set; } + /// + /// 子节点状态 1-审批中;2-同意;3-驳回;4-转审;11-退回给指定审批人;12-加签;13-同意并加签;14-办理;15-转交 + /// + [NotNull] + [JsonProperty("sp_yj")] + [JsonPropertyName("sp_yj")] + public ApprovalProcessNodeStatus Status { get; set; } + /// + /// 操作时间 + /// + [NotNull] + [JsonProperty("sptime")] + [JsonPropertyName("sptime")] + public long SpTime { get; set; } + /// + /// 附件,微盘文件无法获取 + /// + [CanBeNull] + [JsonProperty("media_ids")] + [JsonPropertyName("media_ids")] + public List MediaIds { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpRecord.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpRecord.cs new file mode 100644 index 000000000..83ce480a2 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpRecord.cs @@ -0,0 +1,33 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 审批流程信息 +/// +public class ApprovalSpRecord +{ + /// + /// 审批节点状态 + /// + [NotNull] + [JsonProperty("sp_status")] + [JsonPropertyName("sp_status")] + public ApprovalSpRecordStatus SpStatus { get; set; } + /// + /// 节点审批方式 + /// + [NotNull] + [JsonProperty("approverattr")] + [JsonPropertyName("approverattr")] + public ApprovalApproverAttr ApproverAttr { get; set; } + /// + /// 审批节点详情,一个审批节点有多个审批人 + /// + [NotNull] + [JsonProperty("details")] + [JsonPropertyName("details")] + public List Details { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpRecordDetail.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpRecordDetail.cs new file mode 100644 index 000000000..ee2c44e17 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpRecordDetail.cs @@ -0,0 +1,47 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 审批节点详情 +/// +public class ApprovalSpRecordDetail +{ + /// + /// 分支审批人 + /// + [NotNull] + [JsonProperty("approver")] + [JsonPropertyName("approver")] + public ApprovalUser Approver { get; set; } + /// + /// 审批意见 + /// + [NotNull] + [JsonProperty("speech")] + [JsonPropertyName("speech")] + public string Speech { get; set; } + /// + /// 分支审批人审批状态 + /// + [NotNull] + [JsonProperty("sp_status")] + [JsonPropertyName("sp_status")] + public ApprovalSpRecordStatus SpStatus { get; set; } + /// + /// 节点分支审批人审批操作时间戳,0表示未操作 + /// + [NotNull] + [JsonProperty("sptime")] + [JsonPropertyName("sptime")] + public long SpTime { get; set; } + /// + /// 节点分支审批人审批意见附件,微盘文件无法获取 + /// + [CanBeNull] + [JsonProperty("media_id")] + [JsonPropertyName("media_id")] + public List MediaId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpRecordStatus.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpRecordStatus.cs new file mode 100644 index 000000000..2b0ab36c9 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpRecordStatus.cs @@ -0,0 +1,44 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 审批节点状态 +/// +public enum ApprovalSpRecordStatus : byte +{ + /// + /// 审批中 + /// + [Description("审批中")] + UnderApproval = 1, + /// + /// 已同意 + /// + [Description("已同意")] + Passed = 2, + /// + /// 已驳回 + /// + [Description("已驳回")] + Rejected = 3, + /// + /// 已转审 + /// + [Description("已转审")] + Transferred = 4, + /// + /// 已退回 + /// + [Description("已退回")] + Returned = 11, + /// + /// 已加签 + /// + [Description("已加签")] + Signed = 12, + /// + /// 已同意并加签 + /// + [Description("已同意并加签")] + AgreedAndSigned = 13, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpStatus.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpStatus.cs new file mode 100644 index 000000000..fb7b02f8e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSpStatus.cs @@ -0,0 +1,44 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 申请单状态 +/// +public enum ApprovalSpStatus : byte +{ + /// + /// 审批中 + /// + [Description("审批中")] + UnderApproval = 1, + /// + /// 已通过 + /// + [Description("已通过")] + Passed = 2, + /// + /// 已驳回 + /// + [Description("已驳回")] + Rejected = 3, + /// + /// 已撤销 + /// + [Description("已撤销")] + Revoked = 4, + /// + /// 通过后撤销 + /// + [Description("通过后撤销")] + RevokeAfterApproval = 6, + /// + /// 已删除 + /// + [Description("已删除")] + Deleted = 7, + /// + /// 已支付 + /// + [Description("已支付")] + Paid = 10, +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSummary.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSummary.cs new file mode 100644 index 000000000..56eaef46d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSummary.cs @@ -0,0 +1,28 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 摘要信息 +/// +public class ApprovalSummary +{ + /// + /// 摘要行信息,用于定义某一行摘要显示的内容 + /// + [NotNull] + [JsonProperty("summary_info")] + [JsonPropertyName("summary_info")] + public List SummaryInfo { get; set; } + public ApprovalSummary() + { + + } + + public ApprovalSummary(List summaryInfo) + { + SummaryInfo = summaryInfo; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSummaryInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSummaryInfo.cs new file mode 100644 index 000000000..4f31ad09e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalSummaryInfo.cs @@ -0,0 +1,38 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 摘要信息 +/// +public class ApprovalSummaryInfo +{ + /// + /// 摘要行显示文字,用于记录列表和消息通知的显示,不要超过20个字符 + /// + [NotNull] + [StringLength(20)] + [JsonProperty("text")] + [JsonPropertyName("text")] + public string Text { get; set; } + /// + /// 摘要行显示语言,中文:zh_CN(注意不是zh-CN),英文:en。 + /// + [NotNull] + [StringLength(30)] + [JsonProperty("lang")] + [JsonPropertyName("lang")] + public string Lang { get; set; } + public ApprovalSummaryInfo() + { + + } + + public ApprovalSummaryInfo(string text, string lang = "zh_CN") + { + Text = text; + Lang = lang; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalUser.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalUser.cs new file mode 100644 index 000000000..e7e97c6c0 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ApprovalUser.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 审批节点关联人员 +/// +public class ApprovalUser +{ + /// + /// 人员Id + /// + [NotNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/AttendanceControlConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/AttendanceControlConfig.cs new file mode 100644 index 000000000..3c8997b90 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/AttendanceControlConfig.cs @@ -0,0 +1,55 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 假勤组件-出差/外出/加班组件配置 +/// +public class AttendanceControlConfig : ControlConfig +{ + /// + /// 假勤组件-出差/外出/加班组件 + /// + [NotNull] + [JsonProperty("attendance")] + [JsonPropertyName("attendance")] + public AttendanceConfig Attendance { get; set; } + public AttendanceControlConfig() + { + + } + + public AttendanceControlConfig(AttendanceConfig attendance) + { + Attendance = attendance; + } +} + +public class AttendanceConfig +{ + /// + /// 假勤组件类型:3-出差;4-外出;5-加班 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public byte Type { get; set; } + /// + /// 假勤组件时间选择范围 + /// + [NotNull] + [JsonProperty("date_range")] + [JsonPropertyName("date_range")] + public DateRangeConfig DateRange { get; set; } + public AttendanceConfig() + { + + } + + public AttendanceConfig(byte type, DateRangeConfig dateRange) + { + Type = type; + DateRange = dateRange; + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/AttendanceControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/AttendanceControlValue.cs new file mode 100644 index 000000000..656c13f98 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/AttendanceControlValue.cs @@ -0,0 +1,109 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 假勤组件-出差/外出/加班组件值 +/// +public class AttendanceControlValue : ControlValue +{ + /// + /// 时长 + /// + [NotNull] + [JsonProperty("attendance")] + [JsonPropertyName("attendance")] + public AttendanceValue Attendance { get; set; } + public AttendanceControlValue() + { + + } + + public AttendanceControlValue(AttendanceValue attendance) + { + Attendance = attendance; + } +} + +public class AttendanceValue +{ + /// + /// 假勤组件时间选择范围 + /// + [NotNull] + [JsonProperty("date_range")] + [JsonPropertyName("date_range")] + public DateRangeValue DateRange { get; set; } + /// + /// 假勤组件类型:1-请假;3-出差;4-外出;5-加班 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public byte Type { get; set; } + /// + /// 非必填。时长分片信息 + /// + [CanBeNull] + [JsonProperty("slice_info")] + [JsonPropertyName("slice_info")] + public AttendanceSliceInfo SliceInfo { get; set; } +} + +public class AttendanceSliceInfo +{ + /// + /// 假勤组件类型:1-请假;3-出差;4-外出;5-加班 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public byte Type { get; set; } + /// + /// 某一天的分片 + /// + [NotNull] + [JsonProperty("day_items")] + [JsonPropertyName("day_items")] + public List DayItems { get; set; } + public AttendanceSliceInfo() + { + + } + + public AttendanceSliceInfo(byte type, List dayItems) + { + Type = type; + DayItems = dayItems; + } +} + +public class AttendanceSliceDayItem +{ + /// + /// 当天零点时间戳 (当天的东八区的零点时间戳) + /// + [NotNull] + [JsonProperty("daytime")] + [JsonPropertyName("daytime")] + public long Daytime { get; set; } + /// + /// 某一天的时长,秒数,可以为0,(type为halfday时:加班:需为8640整倍数,其他:需为43200的整倍数,type为hour时需为360的整倍数) + /// + [NotNull] + [JsonProperty("duration")] + [JsonPropertyName("duration")] + public long Duration { get; set; } + public AttendanceSliceDayItem() + { + + } + + public AttendanceSliceDayItem(long daytime, long duration) + { + Daytime = daytime; + Duration = duration; + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ContactControlConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ContactControlConfig.cs new file mode 100644 index 000000000..2c9ca393b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ContactControlConfig.cs @@ -0,0 +1,75 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 成员控件配置 +/// +public class ContactControlConfig : ControlConfig +{ + /// + /// 成员、部门控件 + /// + [NotNull] + [JsonProperty("contact")] + [JsonPropertyName("contact")] + public ContactConfig Contact { get; set; } + public ContactControlConfig() + { + + } + + public ContactControlConfig(ContactConfig contact) + { + Contact = contact; + } +} + +public class ContactConfig +{ + /// + /// single-单选、multi-多选 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public string Type { get; set; } + /// + /// user-成员、department-部门 + /// + [NotNull] + [JsonProperty("mode")] + [JsonPropertyName("mode")] + public string Mode { get; set; } + public ContactConfig() + { + + } + + private ContactConfig(string type, string mode) + { + Type = type; + Mode = mode; + } + + public static ContactConfig SingleUser() + { + return new ContactConfig("single", "user"); + } + + public static ContactConfig SingleDepartment() + { + return new ContactConfig("single", "department"); + } + + public static ContactConfig MultipleUser() + { + return new ContactConfig("multi", "user"); + } + + public static ContactConfig MultipleDepartment() + { + return new ContactConfig("multi", "department"); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ContactControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ContactControlValue.cs new file mode 100644 index 000000000..27121ce3e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ContactControlValue.cs @@ -0,0 +1,7 @@ +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 成员/部门控件值 +/// +public abstract class ContactControlValue : ControlValue +{ +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/Control.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/Control.cs new file mode 100644 index 000000000..9df1dd23e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/Control.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 模板控件 +/// +[Newtonsoft.Json.JsonConverter(typeof(ControlNewtonsoftJsonConverter))] +[System.Text.Json.Serialization.JsonConverter(typeof(ControlSystemTextJsonConverter))] +public class Control +{ + /// + /// 控件属性 + /// + [NotNull] + [JsonProperty("property")] + [JsonPropertyName("property")] + public ControlInfo Property { get; set; } + /// + /// 控件配置 + /// + [CanBeNull] + [JsonProperty("config")] + [JsonPropertyName("config")] + public ControlConfig Config { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlConfig.cs new file mode 100644 index 000000000..83b0f6825 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlConfig.cs @@ -0,0 +1,7 @@ +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 控件配置 +/// +public abstract class ControlConfig +{ +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlConfigFactory.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlConfigFactory.cs new file mode 100644 index 000000000..b97d2ff2e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlConfigFactory.cs @@ -0,0 +1,50 @@ +using Newtonsoft.Json.Linq; +using System.Text.Json; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +internal static class ControlConfigFactory +{ + /// + /// 根据控件类型创建配置(System.Text.Json) + /// + public static ControlConfig CreateConfig(string controlType, JsonElement configElement) + { + return controlType switch + { + "Attendance" => JsonSerializer.Deserialize(configElement.GetRawText()), + "Contact" => JsonSerializer.Deserialize(configElement.GetRawText()), + "Date" => JsonSerializer.Deserialize(configElement.GetRawText()), + "DateRange" => JsonSerializer.Deserialize(configElement.GetRawText()), + "File" => JsonSerializer.Deserialize(configElement.GetRawText()), + "Location" => JsonSerializer.Deserialize(configElement.GetRawText()), + "RelatedApproval" => JsonSerializer.Deserialize(configElement.GetRawText()), + "Selector" => JsonSerializer.Deserialize(configElement.GetRawText()), + "Table" => JsonSerializer.Deserialize(configElement.GetRawText()), + "Tips" => JsonSerializer.Deserialize(configElement.GetRawText()), + // 添加其他控件类型... + _ => null + }; + } + + /// + /// 根据控件类型创建配置(Newtonsoft.Json) + /// + public static ControlConfig CreateConfig(string controlType, JToken configToken) + { + return controlType switch + { + "Attendance" => configToken.ToObject(), + "Contact" => configToken.ToObject(), + "Date" => configToken.ToObject(), + "DateRange" => configToken.ToObject(), + "File" => configToken.ToObject(), + "Location" => configToken.ToObject(), + "RelatedApproval" => configToken.ToObject(), + "Selector" => configToken.ToObject(), + "Table" => configToken.ToObject(), + "Tips" => configToken.ToObject(), + // 添加其他控件类型... + _ => null + }; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlData.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlData.cs new file mode 100644 index 000000000..2863784ae --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlData.cs @@ -0,0 +1,202 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 审批申请详情 +/// +public class ControlData +{ + /// + /// 控件类型 + /// + /// + /// Text-文本
+ /// Textarea-多行文本
+ /// Number-数字
+ /// Money-金额
+ /// Date-日期/日期+时间
+ /// Selector-单选/多选
+ /// Contact-成员/部门
+ /// Tips-说明文字
+ /// File-附件
+ /// Table-明细
+ /// Location-位置
+ /// RelatedApproval-关联审批单
+ /// Formula-公式
+ /// DateRange-时长 + ///
+ [NotNull] + [JsonProperty("control")] + [JsonPropertyName("control")] + public string Control { get; set; } + /// + /// 控件id:控件的唯一id,可通过“获取审批模板详情”接口获取 + /// + [NotNull] + [JsonProperty("id")] + [JsonPropertyName("id")] + public string Id { get; set; } + /// + /// 控件值 ,需在此为申请人在各个控件中填写内容不同控件有不同的赋值参数 + /// + /// + /// 模板配置的控件属性为必填时,对应value值需要有值。 + /// + [NotNull] + [JsonProperty("value")] + [JsonPropertyName("value")] + public ControlValue Value { get; set; } + public ControlData() + { + + } + + private ControlData(string id, string control, ControlValue value) + { + Id = id; + Control = control; + Value = value; + } + + /// + /// 创建一个Text控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData Text(string id, TextControlValue value) + { + return new ControlData(id, "Text", value); + } + /// + /// 创建一个Textarea控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData Textarea(string id, TextareaControlValue value) + { + return new ControlData(id, "Textarea", value); + } + /// + /// 创建一个Number控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData Number(string id, NumberControlValue value) + { + return new ControlData(id, "Number", value); + } + /// + /// 创建一个Money控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData Money(string id, MoneyControlValue value) + { + return new ControlData(id, "Money", value); + } + /// + /// 创建一个Date控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData Date(string id, DateControlValue value) + { + return new ControlData(id, "Date", value); + } + /// + /// 创建一个Selector控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData Selector(string id, SelectorControlValue value) + { + return new ControlData(id, "Selector", value); + } + /// + /// 创建一个Contact控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData Contact(string id, ContactControlValue value) + { + return new ControlData(id, "Contact", value); + } + /// + /// 创建一个Tips控件 + /// + /// 控件Id + /// + public static ControlData Tips(string id) + { + return new ControlData(id, "Tips", null); + } + /// + /// 创建一个File控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData File(string id, FileControlValue value) + { + return new ControlData(id, "File", value); + } + /// + /// 创建一个Table控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData Table(string id, TableControlValue value) + { + return new ControlData(id, "Table", value); + } + /// + /// 创建一个Location控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData Location(string id, LocationControlValue value) + { + return new ControlData(id, "Location", value); + } + /// + /// 创建一个RelatedApproval控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData RelatedApproval(string id, RelatedApprovalControlValue value) + { + return new ControlData(id, "RelatedApproval", value); + } + /// + /// 创建一个Formula控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData Formula(string id, FormulaControlValue value) + { + return new ControlData(id, "Formula", value); + } + /// + /// 创建一个DateRange控件 + /// + /// 控件Id + /// 控件值 + /// + public static ControlData DateRange(string id, DateRangeControlValue value) + { + return new ControlData(id, "DateRange", value); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlInfo.cs new file mode 100644 index 000000000..425289a13 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlInfo.cs @@ -0,0 +1,95 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 模板控件属性 +/// +public class ControlInfo +{ + /// + /// 控件id。1-模版内控件id必须唯一;2-控件id格式:control-数字,如"Text-01" + /// + [NotNull] + [JsonProperty("id")] + [JsonPropertyName("id")] + public string Id { get; set; } + /// + /// 控件类型 + /// + /// + /// Text-文本
+ /// Textarea-多行文本
+ /// Number-数字
+ /// Money-金额
+ /// Date-日期/日期+时间
+ /// Selector-单选/多选
+ /// Contact-成员/部门
+ /// Tips-说明文字
+ /// File-附件
+ /// Table-明细
+ /// Location-位置
+ /// RelatedApproval-关联审批单
+ /// DateRange-时长
+ /// PhoneNumber-电话号码
+ /// Vacation-假期
+ /// Attendance-外出/出差/加班
+ /// BankAccount-收款账户
+ /// 以上为目前可支持的控件类型 + ///
+ [NotNull] + [JsonProperty("control")] + [JsonPropertyName("control")] + public string Control { get; set; } + /// + /// 控件名称 + /// + [NotNull] + [JsonProperty("title")] + [JsonPropertyName("title")] + public List Title { get; set; } + /// + /// 控件说明,假勤组件(Vacation、Attendance)暂不支持设置 + /// + [CanBeNull] + [JsonProperty("placeholder")] + [JsonPropertyName("placeholder")] + public List Placeholder { get; set; } + /// + /// 控件是否必填。0-非必填;1-必填;默认为0;假勤组件(Vacation、Attendance)不支持设置非必填 + /// + [CanBeNull] + [JsonProperty("require")] + [JsonPropertyName("require")] + public byte Require { get; set; } + /// + /// 控件是否可打印。0-可打印;1-不可打印;默认为0;假勤组件(Vacation、Attendance)不支持设置不可打印 + /// + [CanBeNull] + [JsonProperty("un_print")] + [JsonPropertyName("un_print")] + public byte UnPrint { get; set; } + /// + /// un_replace + /// + [CanBeNull] + [JsonProperty("un_replace")] + [JsonPropertyName("un_replace")] + public byte UnReplace { get; set; } + /// + /// inner_id + /// + [CanBeNull] + [JsonProperty("inner_id")] + [JsonPropertyName("inner_id")] + public string InnerId { get; set; } + /// + /// 控件是否隐藏 + /// + [CanBeNull] + [JsonProperty("display")] + [JsonPropertyName("display")] + public byte Display { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlPlaceholder.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlPlaceholder.cs new file mode 100644 index 000000000..114fa2e93 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlPlaceholder.cs @@ -0,0 +1,37 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 控件说明 +/// +public class ControlPlaceholder +{ + /// + /// 控件说明。需满足以下条件:长度不得超过80个字符。 + /// + [NotNull] + [StringLength(80)] + [JsonProperty("text")] + [JsonPropertyName("text")] + public string Text { get; set; } + /// + /// 显示语言,中文:zh_CN(注意不是zh-CN);若text填写,则该项为必填 + /// + [NotNull] + [JsonProperty("lang")] + [JsonPropertyName("lang")] + public string Lang { get; set; } + public ControlPlaceholder() + { + + } + + public ControlPlaceholder(string text, string lang = "zh_CN") + { + Text = text; + Lang = lang; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlTtile.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlTtile.cs new file mode 100644 index 000000000..7496de8e2 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlTtile.cs @@ -0,0 +1,37 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 控件名称 +/// +public class ControlTtile +{ + /// + /// 控件名称。需满足以下条件:1-控件名称不得和现有控件名称重复;2-长度不得超过40个字符。3-Attendance-外出/出差/加班控件title固定为外出/出差/加班,暂不支持自定义 + /// + [NotNull] + [StringLength(40)] + [JsonProperty("text")] + [JsonPropertyName("text")] + public string Text { get; set; } + /// + /// 显示语言,中文:zh_CN(注意不是zh-CN) + /// + [NotNull] + [JsonProperty("lang")] + [JsonPropertyName("lang")] + public string Lang { get; set; } + public ControlTtile() + { + + } + + public ControlTtile(string text, string lang = "zh_CN") + { + Text = text; + Lang = lang; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlValue.cs new file mode 100644 index 000000000..832a206a7 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/ControlValue.cs @@ -0,0 +1,8 @@ +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 控件值 +/// +public abstract class ControlValue +{ + +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateControlConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateControlConfig.cs new file mode 100644 index 000000000..7593456ba --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateControlConfig.cs @@ -0,0 +1,38 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 日期/日期+时间控件配置 +/// +public class DateControlConfig : ControlConfig +{ + /// + /// 日期/日期+时间内容 + /// + [NotNull] + [JsonProperty("date")] + [JsonPropertyName("date")] + public DateConfig Date { get; set; } + public DateControlConfig() + { + + } + + public DateControlConfig(DateConfig date) + { + Date = date; + } +} + +public class DateConfig +{ + /// + /// 时间展示类型:day-日期;hour-日期+时间 ,和对应模板控件属性一致 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public string Type { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateControlValue.cs new file mode 100644 index 000000000..143ac3a60 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateControlValue.cs @@ -0,0 +1,65 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 日期/日期+时间控件值 +/// +public class DateControlValue : ControlValue +{ + /// + /// 金额内容,在此填写金额控件的输入值 + /// + [NotNull] + [JsonProperty("date")] + [JsonPropertyName("date")] + public DateValue Date { get; set; } + public DateControlValue() + { + + } + + public DateControlValue(DateValue date) + { + Date = date; + } +} + +public class DateValue +{ + /// + /// 时间展示类型:day-日期;hour-日期+时间 ,和对应模板控件属性一致 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public string Type { get; set; } + /// + /// 时间戳-字符串类型,在此填写日期/日期+时间控件的选择值,以此为准 + /// + [NotNull] + [JsonProperty("s_timestamp")] + [JsonPropertyName("s_timestamp")] + public string Timestamp { get; set; } + public DateValue() + { + + } + + private DateValue(string type, string timestamp) + { + Type = type; + Timestamp = timestamp; + } + + public static DateValue Day(string timestamp) + { + return new DateValue("day", timestamp); + } + + public static DateValue Hour(string timestamp) + { + return new DateValue("hour", timestamp); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateRangeConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateRangeConfig.cs new file mode 100644 index 000000000..6be00ef61 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateRangeConfig.cs @@ -0,0 +1,43 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 时长配置 +/// +public class DateRangeConfig +{ + /// + /// 时间展示类型:halfday-日期;hour-日期+时间 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public string Type { get; set; } + /// + /// 0-自然日;1-工作日 + /// + [NotNull] + [JsonProperty("official_holiday")] + [JsonPropertyName("official_holiday")] + public byte OfficialHoliday { get; set; } + /// + /// 一天的时长(单位为秒),必须大于0小于等于86400 + /// + [NotNull] + [JsonProperty("perday_duration")] + [JsonPropertyName("perday_duration")] + public int PerdayDuration { get; set; } + public DateRangeConfig() + { + + } + + public DateRangeConfig(string type, byte officialHoliday, int perdayDuration) + { + Type = type; + OfficialHoliday = officialHoliday; + PerdayDuration = perdayDuration; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateRangeControlConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateRangeControlConfig.cs new file mode 100644 index 000000000..318c9ccd8 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateRangeControlConfig.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 时长组件配置 +/// +public class DateRangeControlConfig : ControlConfig +{ + /// + /// 时长组件 + /// + [NotNull] + [JsonProperty("date_range")] + [JsonPropertyName("date_range")] + public DateRangeConfig DateRange { get; set; } + public DateRangeControlConfig() + { + + } + + public DateRangeControlConfig(DateRangeConfig dateRange) + { + DateRange = dateRange; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateRangeControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateRangeControlValue.cs new file mode 100644 index 000000000..3816a71e3 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DateRangeControlValue.cs @@ -0,0 +1,71 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 时长控件值 +/// +public class DateRangeControlValue : ControlValue +{ + /// + /// 时长 + /// + [NotNull] + [JsonProperty("date_range")] + [JsonPropertyName("date_range")] + public DateRangeValue DateRange { get; set; } + public DateRangeControlValue() + { + + } + + public DateRangeControlValue(DateRangeValue dateRange) + { + DateRange = dateRange; + } +} + +public class DateRangeValue +{ + /// + /// 时长粒度:halfday:按天 hour:按小时 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public string Type { get; set; } + /// + /// 开始时间,unix时间戳。当type 为halfday时,取值只能为固定两个时间点 上午:当天00:00:00点时间戳 下午:当天12:00:00时间戳 + /// + [NotNull] + [JsonProperty("new_begin")] + [JsonPropertyName("new_begin")] + public long NewBegin { get; set; } + /// + /// 结束时间,unix时间戳。 当type 为halfday时,取值只能为固定两个时间点 上午:当天00:00:00点时间戳 下午:当天12:00:00时间戳 + /// + [NotNull] + [JsonProperty("new_end")] + [JsonPropertyName("new_end")] + public long NewEnd { get; set; } + /// + /// 时长范围, 单位秒 + /// + [NotNull] + [JsonProperty("new_duration")] + [JsonPropertyName("new_duration")] + public long NewDuration { get; set; } + public DateRangeValue() + { + + } + + public DateRangeValue(string type, long newBegin, long newEnd, long newDuration) + { + Type = type; + NewBegin = newBegin; + NewEnd = newEnd; + NewDuration = newDuration; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DepartmentControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DepartmentControlValue.cs new file mode 100644 index 000000000..268d0150d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/DepartmentControlValue.cs @@ -0,0 +1,66 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 部门控件值 +/// +public class DepartmentControlValue : ContactControlValue +{ + /// + /// 所选部门内容,即申请人在此控件选择的部门,多选模式下可能有多个 + /// + [NotNull] + [JsonProperty("departments")] + [JsonPropertyName("departments")] + public List Departments { get; set; } + public DepartmentControlValue() + { + + } + + private DepartmentControlValue(List departments) + { + Departments = departments; + } + + public static DepartmentControlValue Single(DepartmentValue department) + { + return new DepartmentControlValue(new List { department }); + } + + public static DepartmentControlValue Multiple(List departments) + { + return new DepartmentControlValue(departments); + } +} + +public class DepartmentValue +{ + /// + /// 所选部门id + /// + [NotNull] + [JsonProperty("openapi_id")] + [JsonPropertyName("openapi_id")] + public string DepartmentId { get; set; } + /// + /// 所选部门名 + /// + [NotNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string Name { get; set; } + public DepartmentValue() + { + + } + + public DepartmentValue(string departmentId, string name) + { + DepartmentId = departmentId; + Name = name; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/FileControlConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/FileControlConfig.cs new file mode 100644 index 000000000..bd6d082e4 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/FileControlConfig.cs @@ -0,0 +1,47 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 附件控件配置 +/// +public class FileControlConfig : ControlConfig +{ + /// + /// 附件控件 + /// + [NotNull] + [JsonProperty("file")] + [JsonPropertyName("file")] + public FileConfig File { get; set; } + public FileControlConfig() + { + + } + + public FileControlConfig(FileConfig file) + { + File = file; + } +} + +public class FileConfig +{ + /// + /// 是否只允许拍照,1--是, 0--否 + /// + [NotNull] + [JsonProperty("is_only_photo")] + [JsonPropertyName("is_only_photo")] + public byte IsOnlyPhoto { get; set; } + public FileConfig() + { + + } + + public FileConfig(byte isOnlyPhoto) + { + IsOnlyPhoto = isOnlyPhoto; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/FileControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/FileControlValue.cs new file mode 100644 index 000000000..ad5a31b95 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/FileControlValue.cs @@ -0,0 +1,76 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 附件控件值 +/// +public class FileControlValue : ControlValue +{ + /// + /// 附件列表 + /// + [NotNull] + [JsonProperty("files")] + [JsonPropertyName("files")] + public List Files { get; set; } + public FileControlValue() + { + + } + + public FileControlValue(List files) + { + Files = files; + } +} + +public class FileValue +{ + /// + /// 文件id,该id为临时素材上传接口返回的的media_id,注:提单后将作为单据内容转换为长期文件存储;目前一个审批申请单,全局仅支持上传6个附件,否则将失败。 + /// + [NotNull] + [JsonProperty("file_id")] + [JsonPropertyName("file_id")] + public string FileId { get; set; } + /// + /// 文件名称,类型为string,如果没有可以填空字符串。 + /// + [CanBeNull] + [JsonProperty("file_name")] + [JsonPropertyName("file_name")] + public string FileName { get; set; } + /// + /// 文件大小,类型为number,如果没有可以填空字符串。 + /// + [CanBeNull] + [JsonProperty("file_size")] + [JsonPropertyName("file_size")] + public long? FileSize { get; set; } + /// + /// 文件类型,类型为string,如果没有可以填空字符串。 + /// + [CanBeNull] + [JsonProperty("file_type")] + [JsonPropertyName("file_type")] + public string FileType { get; set; } + /// + /// 文件地址,类型为string,如果没有可以填空字符串。 + /// + [CanBeNull] + [JsonProperty("file_url")] + [JsonPropertyName("file_url")] + public string FileUrl { get; set; } + public FileValue() + { + + } + + public FileValue(string fileId) + { + FileId = fileId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/FormulaControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/FormulaControlValue.cs new file mode 100644 index 000000000..ca1be35eb --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/FormulaControlValue.cs @@ -0,0 +1,47 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 公式控件值 +/// +public class FormulaControlValue : ControlValue +{ + /// + /// 公式 + /// + [NotNull] + [JsonProperty("formula")] + [JsonPropertyName("formula")] + public FormulaValue Formula { get; set; } + public FormulaControlValue() + { + + } + + public FormulaControlValue(FormulaValue formula) + { + Formula = formula; + } +} + +public class FormulaValue +{ + /// + /// 公式的值,提交表单时无需填写,后台自动计算 + /// + [NotNull] + [JsonProperty("value")] + [JsonPropertyName("value")] + public string Value { get; set; } + public FormulaValue() + { + + } + + public FormulaValue(string value) + { + Value = value; + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/LocationControlConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/LocationControlConfig.cs new file mode 100644 index 000000000..f1a0243d6 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/LocationControlConfig.cs @@ -0,0 +1,47 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 位置控件配置 +/// +public class LocationControlConfig : ControlConfig +{ + /// + /// 位置控件 + /// + [NotNull] + [JsonProperty("location")] + [JsonPropertyName("location")] + public LocationConfig Location { get; set; } + public LocationControlConfig() + { + + } + + public LocationControlConfig(LocationConfig location) + { + Location = location; + } +} + +public class LocationConfig +{ + /// + /// 距离,目前支持100、200、300 + /// + [NotNull] + [JsonProperty("distance")] + [JsonPropertyName("distance")] + public int Distance { get; set; } + public LocationConfig() + { + + } + + public LocationConfig(int distance) + { + Distance = distance; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/LocationControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/LocationControlValue.cs new file mode 100644 index 000000000..9d8b18358 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/LocationControlValue.cs @@ -0,0 +1,79 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 位置控件值 +/// +public class LocationControlValue : ControlValue +{ + /// + /// 位置 + /// + [NotNull] + [JsonProperty("location")] + [JsonPropertyName("location")] + public LocationValue Location { get; set; } + public LocationControlValue() + { + + } + + public LocationControlValue(LocationValue location) + { + Location = location; + } +} + +public class LocationValue +{ + /// + /// 纬度,精确到6位小数 + /// + [NotNull] + [JsonProperty("latitude")] + [JsonPropertyName("latitude")] + public decimal Latitude { get; set; } + /// + /// 经度,精确到6位小数 + /// + [NotNull] + [JsonProperty("longitude")] + [JsonPropertyName("longitude")] + public decimal Longitude { get; set; } + /// + /// 地点标题 + /// + [NotNull] + [JsonProperty("title")] + [JsonPropertyName("title")] + public string Title { get; set; } + /// + /// 地点详情地址 + /// + [NotNull] + [JsonProperty("address")] + [JsonPropertyName("address")] + public string Address { get; set; } + /// + /// 选择地点的时间 + /// + [NotNull] + [JsonProperty("time")] + [JsonPropertyName("time")] + public long Time { get; set; } + public LocationValue() + { + + } + + public LocationValue(decimal latitude, decimal longitude, string title, string address, long time) + { + Latitude = latitude; + Longitude = longitude; + Title = title; + Address = address; + Time = time; + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/MemberControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/MemberControlValue.cs new file mode 100644 index 000000000..f87d7be49 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/MemberControlValue.cs @@ -0,0 +1,66 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 成员控件值 +/// +public class MemberControlValue : ContactControlValue +{ + /// + /// 所选成员内容,即申请人在此控件选择的成员,多选模式下可以有多个 + /// + [NotNull] + [JsonProperty("members")] + [JsonPropertyName("members")] + public List Members { get; set; } + public MemberControlValue() + { + + } + + private MemberControlValue(List members) + { + Members = members; + } + + public static MemberControlValue Single(MemberValue member) + { + return new MemberControlValue(new List { member }); + } + + public static MemberControlValue Multiple(List members) + { + return new MemberControlValue(members); + } +} + +public class MemberValue +{ + /// + /// 所选成员的userid + /// + [NotNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; set; } + /// + /// 成员名 + /// + [NotNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string Name { get; set; } + public MemberValue() + { + + } + + public MemberValue(string userId, string name) + { + UserId = userId; + Name = name; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/MoneyControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/MoneyControlValue.cs new file mode 100644 index 000000000..57861c4db --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/MoneyControlValue.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 金额控件值 +/// +public class MoneyControlValue : ControlValue +{ + /// + /// 金额内容,在此填写金额控件的输入值 + /// + [NotNull] + [JsonProperty("new_money")] + [JsonPropertyName("new_money")] + public string NewMoney { get; set; } + public MoneyControlValue() + { + + } + + public MoneyControlValue(decimal newMoney) + { + NewMoney = newMoney.ToString(); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/NumberControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/NumberControlValue.cs new file mode 100644 index 000000000..41cb7e5d2 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/NumberControlValue.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 数字控件值 +/// +public class NumberControlValue : ControlValue +{ + /// + /// 数字内容,在此填写数字控件的输入值 + /// + [NotNull] + [JsonProperty("new_number")] + [JsonPropertyName("new_number")] + public string NewMumber { get; set; } + public NumberControlValue() + { + + } + + public NumberControlValue(decimal newMumber) + { + NewMumber = newMumber.ToString(); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/RelatedApprovalControlConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/RelatedApprovalControlConfig.cs new file mode 100644 index 000000000..4dcd885e4 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/RelatedApprovalControlConfig.cs @@ -0,0 +1,47 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 关联审批单控件配置 +/// +public class RelatedApprovalControlConfig : ControlConfig +{ + /// + /// 关联审批单控件 + /// + [NotNull] + [JsonProperty("related_approval")] + [JsonPropertyName("related_approval")] + public RelatedApprovalConfig RelatedApproval { get; set; } + public RelatedApprovalControlConfig() + { + + } + + public RelatedApprovalControlConfig(RelatedApprovalConfig relatedApproval) + { + RelatedApproval = relatedApproval; + } +} + +public class RelatedApprovalConfig +{ + /// + /// 关联审批单的template_id ,不填时表示可以关联所有模版,该template_id可通过获取审批模版接口获取 + /// + [NotNull] + [JsonProperty("template_id")] + [JsonPropertyName("template_id")] + public string TemplateId { get; set; } + public RelatedApprovalConfig() + { + + } + + public RelatedApprovalConfig(string templateId) + { + TemplateId = templateId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/RelatedApprovalControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/RelatedApprovalControlValue.cs new file mode 100644 index 000000000..750060c67 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/RelatedApprovalControlValue.cs @@ -0,0 +1,48 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 关联审批单控件值 +/// +public class RelatedApprovalControlValue : ControlValue +{ + /// + /// 关联审批单 + /// + [NotNull] + [JsonProperty("related_approval")] + [JsonPropertyName("related_approval")] + public RelatedApprovalValue RelatedApproval { get; set; } + public RelatedApprovalControlValue() + { + + } + + public RelatedApprovalControlValue(RelatedApprovalValue relatedApproval) + { + RelatedApproval = relatedApproval; + } +} + + +public class RelatedApprovalValue +{ + /// + /// 关联审批单的审批单号 + /// + [NotNull] + [JsonProperty("sp_no")] + [JsonPropertyName("sp_no")] + public string SpNo { get; set; } + public RelatedApprovalValue() + { + + } + + public RelatedApprovalValue(string spNo) + { + SpNo = spNo; + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/SelectorControlConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/SelectorControlConfig.cs new file mode 100644 index 000000000..84ff4ff96 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/SelectorControlConfig.cs @@ -0,0 +1,235 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 选择控件配置 +/// +public class SelectorControlConfig : ControlConfig +{ + /// + /// 选择控件内容,即申请人在此控件选择的选项内容 + /// + [NotNull] + [JsonProperty("selector")] + [JsonPropertyName("selector")] + public SelectorConfig Selector { get; set; } + public SelectorControlConfig() + { + + } + + public SelectorControlConfig(SelectorConfig selector) + { + Selector = selector; + } +} + +public class SelectorConfig +{ + /// + /// 选择方式:single-单选;multi-多选 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public string Type { get; set; } + /// + /// 多选选项,多选属性的选择控件允许输入多个 + /// + [NotNull] + [JsonProperty("options")] + [JsonPropertyName("options")] + public List Options { get; set; } + /// + /// 关联控件 + /// + [NotNull] + [JsonProperty("op_relations")] + [JsonPropertyName("op_relations")] + public List OptionRelations { get; set; } + /// + /// 关联外部选项 + /// + [NotNull] + [JsonProperty("external_option")] + [JsonPropertyName("external_option")] + public SelectorOptionExternal ExternalOption { get; set; } + public SelectorConfig() + { + + } + + private SelectorConfig( + string type, + List options, + List optionRelations = null, + SelectorOptionExternal optionExternal = null) + { + Type = type; + Options = options; + OptionRelations = optionRelations; + ExternalOption = optionExternal; + } + + public static SelectorConfig Single( + List options, + List optionRelations = null, + SelectorOptionExternal optionExternal = null) + { + return new SelectorConfig("single", options, optionRelations, optionExternal); + } + + public static SelectorConfig Multiple( + List options, + List optionRelations = null, + SelectorOptionExternal optionExternal = null) + { + return new SelectorConfig("multi", options, optionRelations, optionExternal); + } +} + +public class SelectorOption +{ + /// + /// 选项key + /// + [NotNull] + [JsonProperty("key")] + [JsonPropertyName("key")] + public string Key { get; set; } + /// + /// 选项说明 + /// + [NotNull] + [JsonProperty("value")] + [JsonPropertyName("value")] + public List Value { get; set; } + public SelectorOption() + { + + } + + public SelectorOption(string key, List value) + { + Key = key; + Value = value; + } +} + +public class SelectorOptionValue +{ + /// + /// 名称 + /// + [NotNull] + [JsonProperty("text")] + [JsonPropertyName("text")] + public string Text { get; set; } + /// + /// 显示语言 + /// + [NotNull] + [JsonProperty("lang")] + [JsonPropertyName("lang")] + public string Lang { get; set; } + public SelectorOptionValue() + { + + } + + public SelectorOptionValue(string text, string lang = "zh_CN") + { + Text = text; + Lang = lang; + } +} + +public class SelectorOptionRelation +{ + /// + /// 选项key + /// + [NotNull] + [JsonProperty("key")] + [JsonPropertyName("key")] + public string Key { get; set; } + /// + /// 关联控件列表 + /// + [NotNull] + [JsonProperty("relation_list")] + [JsonPropertyName("relation_list")] + public List Relations { get; set; } + public SelectorOptionRelation() + { + + } + + public SelectorOptionRelation(string key, List relations) + { + Key = key; + Relations = relations; + } +} + +public class SelectorRelation +{ + /// + /// 控件Id + /// + [NotNull] + [JsonProperty("related_control_id")] + [JsonPropertyName("related_control_id")] + public string ControlId { get; set; } + /// + /// 操作方法 + /// + /// + /// 1 - 显示对应控件 + /// + [NotNull] + [JsonProperty("action")] + [JsonPropertyName("action")] + public int Action { get; set; } + public SelectorRelation() + { + + } + + public SelectorRelation(string controlId, int action = 1) + { + ControlId = controlId; + Action = action; + } +} + +public class SelectorOptionExternal +{ + /// + /// 关联外部选项 + /// + [NotNull] + [JsonProperty("use_external_option")] + [JsonPropertyName("use_external_option")] + public bool UseExternalOption { get; set; } + /// + /// 外部选项页面地址 + /// + [NotNull] + [JsonProperty("external_url")] + [JsonPropertyName("external_url")] + public string ExternalUrl { get; set; } + public SelectorOptionExternal() + { + + } + + public SelectorOptionExternal(string externalUrl) + { + ExternalUrl = externalUrl; + UseExternalOption = true; + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/SelectorControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/SelectorControlValue.cs new file mode 100644 index 000000000..59ed5c808 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/SelectorControlValue.cs @@ -0,0 +1,133 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 单选/多选控件值 +/// +public class SelectorControlValue : ControlValue +{ + /// + /// 单选/多选内容 + /// + [NotNull] + [JsonProperty("selector")] + [JsonPropertyName("selector")] + public SelectorValue Selector { get; set; } + public SelectorControlValue() + { + + } + + public SelectorControlValue(SelectorValue selector) + { + Selector = selector; + } +} + +public class SelectorValue +{ + /// + /// 选择方式:single-单选;multi-多选 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public string Type { get; set; } + /// + /// 多选选项,多选属性的选择控件允许输入多个 + /// + [NotNull] + [JsonProperty("options")] + [JsonPropertyName("options")] + public List Options { get; set; } + public SelectorValue() + { + + } + + private SelectorValue(string type, List options) + { + Type = type; + Options = options; + } + /// + /// 创建一个单选选项 + /// + /// + /// + public static SelectorValue Single(SelectorValueOption option) + { + return new SelectorValue("single", new List { option }); + } + + /// + /// 创建一个多选选项 + /// + /// + /// + public static SelectorValue Multiple(List options) + { + return new SelectorValue("multi", options); + } +} + +public class SelectorValueOption +{ + /// + /// 选项key,可通过“获取审批模板详情”接口获得 + /// + [NotNull] + [JsonProperty("key")] + [JsonPropertyName("key")] + public string Key { get; set; } + /// + /// 选项值,若配置了多语言则会包含中英文的选项值 + /// + [NotNull] + [JsonProperty("value")] + [JsonPropertyName("value")] + public List Value { get; set; } + public SelectorValueOption() + { + + } + + public SelectorValueOption(string key, List value) + { + Key = key; + Value = value; + } +} + +public class SelectorValueOptionValue +{ + /// + /// 选项值 + /// + [NotNull] + [StringLength(40)] + [JsonProperty("text")] + [JsonPropertyName("text")] + public string Text { get; set; } + /// + /// 多语言名称 + /// + [NotNull] + [JsonProperty("lang")] + [JsonPropertyName("lang")] + public string Lang { get; set; } + public SelectorValueOptionValue() + { + + } + + public SelectorValueOptionValue(string text, string lang = "zh_CN") + { + Text = text; + Lang = lang; + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TableControlConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TableControlConfig.cs new file mode 100644 index 000000000..2b6d4aebe --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TableControlConfig.cs @@ -0,0 +1,56 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 明细控件配置 +/// +public class TableControlConfig : ControlConfig +{ + /// + /// 明细控件 + /// + [NotNull] + [JsonProperty("table")] + [JsonPropertyName("table")] + public TableConfig Table { get; set; } + public TableControlConfig() + { + + } + + public TableControlConfig(TableConfig table) + { + Table = table; + } +} + +public class TableConfig +{ + /// + /// 打印格式;0-合并成一行打印,1-拆分成多行打印 + /// + [NotNull] + [JsonProperty("print_format")] + [JsonPropertyName("print_format")] + public byte PrintFormat { get; set; } + /// + /// 明细内容,一个明细控件可能包含多个子控件,每个子控件的属性和控件相同 + /// + [NotNull] + [JsonProperty("children")] + [JsonPropertyName("children")] + public List Children { get; set; } + public TableConfig() + { + + } + + public TableConfig(byte printFormat, List children) + { + PrintFormat = printFormat; + Children = children; + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TableControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TableControlValue.cs new file mode 100644 index 000000000..8dac8ea13 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TableControlValue.cs @@ -0,0 +1,51 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 明细控件值 +/// +public class TableControlValue : ControlValue +{ + /// + /// 明细内容,一个明细控件可能包含多个子明细 + /// + [NotNull] + [JsonProperty("children")] + [JsonPropertyName("children")] + public List Children { get; set; } + public TableControlValue() + { + + } + + public TableControlValue(List children) + { + Children = children; + } +} + +public class TableValue +{ + /// + /// 子明细列表,在此填写子明细的所有子控件的值,子控件的数据结构同一般控件。 + /// + /// + /// 注意:不能为空数组,至少需要包含一个子明细,子明细中必须包括模板中设置的全部子控件,如果子明细为空,则需要将所有子控件的值设为空 + /// + [NotNull] + [JsonProperty("list")] + [JsonPropertyName("list")] + public List List { get; set; } + public TableValue() + { + + } + + public TableValue(List list) + { + List = list; + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TemplateContent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TemplateContent.cs new file mode 100644 index 000000000..7bd41bad7 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TemplateContent.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 模版控件定义 +/// +public class TemplateContent +{ + /// + /// 控件数组,模版中可以设置多个控件类型,排列顺序和管理端展示的相同 + /// + [NotNull] + [JsonProperty("controls")] + [JsonPropertyName("controls")] + public List Controls { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TemplateName.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TemplateName.cs new file mode 100644 index 000000000..fc81a46bb --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TemplateName.cs @@ -0,0 +1,35 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 模版名称定义 +/// +public class TemplateName +{ + /// + /// 模板名称 + /// + [NotNull] + [JsonProperty("text")] + [JsonPropertyName("text")] + public string Text { get; set; } + /// + /// 多语言名称 + /// + [NotNull] + [JsonProperty("lang")] + [JsonPropertyName("lang")] + public string Lang { get; set; } + public TemplateName() + { + + } + + public TemplateName(string text, string lang = "zh_CN") + { + Text = text; + Lang = lang; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TextControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TextControlValue.cs new file mode 100644 index 000000000..7d79b16ca --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TextControlValue.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 文本控件值 +/// +public class TextControlValue : ControlValue +{ + /// + /// 文本内容,在此填写文本/多行文本控件的输入值。文本控件Text内容不支持包含换行符。 + /// + [NotNull] + [JsonProperty("text")] + [JsonPropertyName("text")] + public string Text { get; set; } + public TextControlValue() + { + + } + + public TextControlValue(string text) + { + Text = text; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TextareaControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TextareaControlValue.cs new file mode 100644 index 000000000..bece9895a --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TextareaControlValue.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 多行文本控件值 +/// +public class TextareaControlValue : ControlValue +{ + /// + /// 文本内容,在此填写文本/多行文本控件的输入值。文本控件Text内容不支持包含换行符。 + /// + [NotNull] + [JsonProperty("text")] + [JsonPropertyName("text")] + public string Text { get; set; } + public TextareaControlValue() + { + + } + + public TextareaControlValue(string text) + { + Text = text; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TipsControlConfig.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TipsControlConfig.cs new file mode 100644 index 000000000..d88aa0cab --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/TipsControlConfig.cs @@ -0,0 +1,169 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 说明文字控件配置 +/// +public class TipsControlConfig : ControlConfig +{ + /// + /// 说明文字控件 + /// + [NotNull] + [JsonProperty("tips")] + [JsonPropertyName("tips")] + public TipsConfig Tips { get; set; } + public TipsControlConfig() + { + + } + + public TipsControlConfig(TipsConfig tips) + { + Tips = tips; + } +} + +public class TipsConfig +{ + /// + /// 说明文字数组,元素为不同语言的富文本说明文字 + /// + [NotNull] + [JsonProperty("tips_content")] + [JsonPropertyName("tips_content")] + public List TipsContents { get; set; } + public TipsConfig() + { + + } + + public TipsConfig(List tipsContents) + { + TipsContents = tipsContents; + } +} + +public class TipsContent +{ + /// + /// 某个语言的富文本说明 + /// + [NotNull] + [JsonProperty("text")] + [JsonPropertyName("text")] + public TipsContentText Text { get; set; } + /// + /// 多语言名称 + /// + [NotNull] + [JsonProperty("lang")] + [JsonPropertyName("lang")] + public string Lang { get; set; } + public TipsContent() + { + + } + + public TipsContent(TipsContentText text, string lang = "zh_CN") + { + Text = text; + Lang = lang; + } +} + +public class TipsContentText +{ + /// + /// 说明文字分段 + /// + [NotNull] + [JsonProperty("sub_text")] + [JsonPropertyName("sub_text")] + public List SubText { get; set; } + public TipsContentText() + { + + } + + public TipsContentText(List subText) + { + SubText = subText; + } +} + +public class TipsContentSubText +{ + /// + /// 文本类型 1:纯文本 2:链接,每个说明文字中只支持包含一个链接 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public byte Type { get; set; } + /// + /// 内容 + /// + [NotNull] + [JsonProperty("content")] + [JsonPropertyName("content")] + public SubTextContent Content { get; set; } + public TipsContentSubText() + { + + } + + private TipsContentSubText(byte type, SubTextContent content) + { + Type = type; + Content = content; + } + + public static TipsContentSubText Text(TipsContentPlainText content) + { + return new TipsContentSubText(1, content); + } + + public static TipsContentSubText Link(TipsContentLinkText content) + { + return new TipsContentSubText(2, content); + } +} + +public abstract class SubTextContent +{ +} + +public class TipsContentPlainText : SubTextContent +{ + /// + /// 纯文本文字 + /// + [NotNull] + [JsonProperty("content")] + [JsonPropertyName("content")] + public string Content { get; set; } +} + +public class TipsContentLinkText : SubTextContent +{ + /// + /// 链接标题 + /// + [NotNull] + [JsonProperty("title")] + [JsonPropertyName("title")] + public string Title { get; set; } + /// + /// 链接url,不能超过600个字符 + /// + [NotNull] + [StringLength(600)] + [JsonProperty("url")] + [JsonPropertyName("url")] + public string Url { get; set; } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/VacationControlValue.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/VacationControlValue.cs new file mode 100644 index 000000000..17ccc373d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Models/VacationControlValue.cs @@ -0,0 +1,141 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Models; +/// +/// 假勤组件-请假组件值 +/// +public class VacationControlValue : ControlValue +{ + /// + /// 请假内容,即申请人在此组件内选择的请假信息 + /// + [NotNull] + [JsonProperty("vacation")] + [JsonPropertyName("vacation")] + public VacationValue Vacation { get; set; } + public VacationControlValue() + { + + } + + public VacationControlValue(VacationValue vacation) + { + Vacation = vacation; + } +} + +public class VacationValue +{ + /// + /// 请假类型,所选选项与假期管理关联,为假期管理中的假期类型 + /// + [NotNull] + [JsonProperty("selector")] + [JsonPropertyName("selector")] + public VacationSelector Selector { get; set; } + /// + /// 假勤组件 + /// + [NotNull] + [JsonProperty("attendance")] + [JsonPropertyName("attendance")] + public AttendanceValue Attendance { get; set; } + public VacationValue() + { + + } + + public VacationValue(VacationSelector selector, AttendanceValue attendance) + { + Selector = selector; + Attendance = attendance; + } +} + +public class VacationSelector +{ + /// + /// 选择方式:single-单选;multi-多选,在假勤控件中固定为单选 + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public string Type { get; set; } + /// + /// 用户所选选项 + /// + [NotNull] + [JsonProperty("options")] + [JsonPropertyName("options")] + public List Options { get; set; } + public VacationSelector() + { + + } + + public VacationSelector(List options) + { + Type = "single"; + Options = options; + } +} + +public class VacationSelectorOption +{ + /// + /// 选项key,选项的唯一id,可通过“获取审批模板详情”接口获得vacation_list中item的id值 + /// + [NotNull] + [JsonProperty("key")] + [JsonPropertyName("key")] + public string Key { get; set; } + /// + /// 选项值,若配置了多语言则会包含中英文的选项值 + /// + [NotNull] + [JsonProperty("value")] + [JsonPropertyName("value")] + public VacationSelectorOptionValue Value { get; set; } + public VacationSelectorOption() + { + + } + + public VacationSelectorOption(string key, VacationSelectorOptionValue value) + { + Key = key; + Value = value; + } +} + +public class VacationSelectorOptionValue +{ + /// + /// 选项值 + /// + [NotNull] + [JsonProperty("text")] + [JsonPropertyName("text")] + public string Text { get; set; } + /// + /// 多语言名称 + /// + [NotNull] + [JsonProperty("lang")] + [JsonPropertyName("lang")] + public string Lang { get; set; } + public VacationSelectorOptionValue() + { + + } + + public VacationSelectorOptionValue(string text, string lang = "zh_CN") + { + Text = text; + Lang = lang; + } +} + diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkApplyEventRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkApplyEventRequest.cs new file mode 100644 index 000000000..cc3f921e2 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkApplyEventRequest.cs @@ -0,0 +1,94 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.Approvals.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Request; +/// +/// 提交审批申请请求参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/91853 +/// +public class WeChatWorkApplyEventRequest +{ + /// + /// 流程 + /// + /// + /// use_template_approver = 0 时必填 + /// + [CanBeNull] + [JsonProperty("process")] + [JsonPropertyName("process")] + public ApprovalApplyProcess Process { get; set; } + /// + /// 摘要信息,用于显示在审批通知卡片、审批列表的摘要信息,最多3行 + /// + [NotNull] + [JsonProperty("summary_list")] + [JsonPropertyName("summary_list")] + public List Summaries { get; set; } + /// + /// 审批申请数据,可定义审批申请中各个控件的值,其中必填项必须有值,选填项可为空,数据结构同“获取审批申请详情”接口返回值中同名参数“apply_data” + /// + [NotNull] + [JsonProperty("apply_data")] + [JsonPropertyName("apply_data")] + public ApprovalApplyData ApplyData { get; set; } + /// + /// 模板id。可在“获取审批申请详情”、“审批状态变化回调通知”中获得,也可在审批模板的模板编辑页面链接中获得。暂不支持通过接口提交[打卡补卡][调班]模板审批单。 + /// + [NotNull] + [JsonProperty("template_id")] + [JsonPropertyName("template_id")] + public string TemplateId { get; set; } + /// + /// 申请人userid,此审批申请将以此员工身份提交,申请人需在应用可见范围内 + /// + [NotNull] + [JsonProperty("creator_userid")] + [JsonPropertyName("creator_userid")] + public string CreatorUserId { get; set; } + /// + /// 审批人模式 + /// + /// + /// 0-通过接口指定审批人、抄送人(此时process参数必填);
+ /// 1-使用此模板在管理后台设置的审批流程(需要保证审批流程中没有“申请人自选”节点),支持条件审批。默认为0 + ///
+ [NotNull] + [JsonProperty("use_template_approver")] + [JsonPropertyName("use_template_approver")] + public byte UseTemplateApprover + { + get { + if (Process == null) + { + return 1; + } + return 0; + } + } + /// + /// 提单者提单部门id,不填默认为主部门 + /// + [CanBeNull] + [JsonProperty("choose_department")] + [JsonPropertyName("choose_department")] + public byte? ChooseDepartment { get; set; } + public WeChatWorkApplyEventRequest( + string templateId, + string creatorUserid, + ApprovalApplyData applyData, + ApprovalApplyProcess process = null, + byte? chooseDepartment = null) + { + TemplateId = templateId; + ApplyData = applyData; + CreatorUserId = creatorUserid; + ChooseDepartment = chooseDepartment; + Process = process; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkCreateTemplateRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkCreateTemplateRequest.cs new file mode 100644 index 000000000..6ae74ccd6 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkCreateTemplateRequest.cs @@ -0,0 +1,36 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.Approvals.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Request; +/// +/// 创建审批模板请求参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/97437 +/// +public class WeChatWorkCreateTemplateRequest +{ + /// + /// 模版名称数组 + /// + [NotNull] + [JsonProperty("template_name")] + [JsonPropertyName("template_name")] + public List TemplateName { get; set; } + /// + /// 审批模版控件设置,由多个表单控件及其内容组成,其中包含需要对控件赋值的信息 + /// + [NotNull] + [JsonProperty("template_content")] + [JsonPropertyName("template_content")] + public TemplateContent TemplateContent { get; set; } + + public WeChatWorkCreateTemplateRequest(List templateName, TemplateContent templateContent) + { + TemplateName = templateName; + TemplateContent = templateContent; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkGetApprovalDetailRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkGetApprovalDetailRequest.cs new file mode 100644 index 000000000..ae02b9035 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkGetApprovalDetailRequest.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Request; +/// +/// 获取审批申请详情请求参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/91983 +/// +public class WeChatWorkGetApprovalDetailRequest +{ + /// + /// 审批单编号 + /// + [NotNull] + [JsonProperty("sp_no")] + [JsonPropertyName("sp_no")] + public string SpNo { get; set; } + public WeChatWorkGetApprovalDetailRequest(string spNo) + { + SpNo = spNo; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkGetApprovalInfoRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkGetApprovalInfoRequest.cs new file mode 100644 index 000000000..fde25a87f --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkGetApprovalInfoRequest.cs @@ -0,0 +1,65 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.Approvals.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Request; +/// +/// 批量获取审批单号请求参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/91816 +/// +public class WeChatWorkGetApprovalInfoRequest +{ + /// + /// 审批单提交的时间范围,开始时间,UNix时间戳 + /// + [NotNull] + [JsonProperty("starttime")] + [JsonPropertyName("starttime")] + public long StartTime { get; set; } + /// + /// 审批单提交的时间范围,结束时间,Unix时间戳 + /// + [NotNull] + [JsonProperty("endtime")] + [JsonPropertyName("endtime")] + public long EndTime { get; set; } + /// + /// 分页查询游标,默认为空串,后续使用返回的new_next_cursor进行分页拉取 + /// + [CanBeNull] + [JsonProperty("new_cursor")] + [JsonPropertyName("new_cursor")] + public string NewCursor { get; set; } = ""; + /// + /// 一次请求拉取审批单数量,默认值为100,上限值为100。 + /// 若accesstoken为自建应用,仅允许获取在应用可见范围内申请人提交的表单,返回的sp_no_list个数可能和size不一致,开发者需用next_cursor判断表单记录是否拉取完 + /// + [NotNull] + [JsonProperty("size")] + [JsonPropertyName("size")] + public int Size { get; set; } + /// + /// 筛选条件,可对批量拉取的审批申请设置约束条件,支持设置多个条件 + /// + [CanBeNull] + [JsonProperty("filters")] + [JsonPropertyName("filters")] + public List Filters { get; set; } + public WeChatWorkGetApprovalInfoRequest( + long sartTime, + long endTime, + int size = 100, + List filters = null) + { + StartTime = sartTime; + EndTime = endTime; + Size = Check.Range(size, nameof(size), 1, 100); + + Filters = filters; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkGetTemplateRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkGetTemplateRequest.cs new file mode 100644 index 000000000..03bd3b25b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkGetTemplateRequest.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Request; +/// +/// 获取审批模板请求参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/91982 +/// +public class WeChatWorkGetTemplateRequest +{ + /// + /// 模版id + /// + [NotNull] + [JsonProperty("template_id")] + [JsonPropertyName("template_id")] + public string TemplateId { get; set; } + public WeChatWorkGetTemplateRequest(string templateId) + { + TemplateId = templateId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkUpdateTemplateRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkUpdateTemplateRequest.cs new file mode 100644 index 000000000..307e55f9b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Request/WeChatWorkUpdateTemplateRequest.cs @@ -0,0 +1,47 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.Approvals.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Request; +/// +/// 更新审批模板请求参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/97438 +/// +public class WeChatWorkUpdateTemplateRequest +{ + /// + /// 模版id + /// + [NotNull] + [JsonProperty("template_id")] + [JsonPropertyName("template_id")] + public string TemplateId { get; set; } + /// + /// 模版名称数组 + /// + [NotNull] + [JsonProperty("template_name")] + [JsonPropertyName("template_name")] + public List TemplateName { get; set; } + /// + /// 审批模版控件设置,由多个表单控件及其内容组成,其中包含需要对控件赋值的信息 + /// + [NotNull] + [JsonProperty("template_content")] + [JsonPropertyName("template_content")] + public TemplateContent TemplateContent { get; set; } + + public WeChatWorkUpdateTemplateRequest( + string templateId, + List templateName, + TemplateContent templateContent) + { + TemplateId = templateId; + TemplateName = templateName; + TemplateContent = templateContent; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkApplyEventResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkApplyEventResponse.cs new file mode 100644 index 000000000..d62fea7d2 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkApplyEventResponse.cs @@ -0,0 +1,21 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Response; +/// +/// 提交审批申请响应参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/91853 +/// +public class WeChatWorkApplyEventResponse : WeChatWorkResponse +{ + /// + /// 表单提交成功后,返回的表单编号 + /// + [NotNull] + [JsonProperty("sp_no")] + [JsonPropertyName("sp_no")] + public string SpNo { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkCreateTemplateResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkCreateTemplateResponse.cs new file mode 100644 index 000000000..a44492a58 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkCreateTemplateResponse.cs @@ -0,0 +1,21 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Response; +/// +/// 创建审批模板响应参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/97437 +/// +public class WeChatWorkCreateTemplateResponse : WeChatWorkResponse +{ + /// + /// 模版创建成功后返回的模版id + /// + [NotNull] + [JsonProperty("template_id")] + [JsonPropertyName("template_id")] + public string TemplateId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkGetApprovalDetailResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkGetApprovalDetailResponse.cs new file mode 100644 index 000000000..81189f650 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkGetApprovalDetailResponse.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.Approvals.Models; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Response; +/// +/// 获取审批申请详情响应参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/91983 +/// +public class WeChatWorkGetApprovalDetailResponse : WeChatWorkResponse +{ + /// + /// 审批申请详情 + /// + [NotNull] + [JsonProperty("info")] + [JsonPropertyName("info")] + public ApprovalDetailInfo Info { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkGetApprovalInfoResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkGetApprovalInfoResponse.cs new file mode 100644 index 000000000..4b9015c58 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkGetApprovalInfoResponse.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Response; +/// +/// 批量获取审批单号响应参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/91816 +/// +public class WeChatWorkGetApprovalInfoResponse : WeChatWorkResponse +{ + /// + /// 审批单号列表,包含满足条件的审批申请 + /// + [NotNull] + [JsonProperty("sp_no_list")] + [JsonPropertyName("sp_no_list")] + public List SpNos { get; set; } + /// + /// 后续请求查询的游标,当返回结果没有该字段时表示审批单已经拉取完 + /// + [CanBeNull] + [JsonProperty("new_next_cursor")] + [JsonPropertyName("new_next_cursor")] + public string NewNextCursor { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkTemplateResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkTemplateResponse.cs new file mode 100644 index 000000000..8700db44f --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/Response/WeChatWorkTemplateResponse.cs @@ -0,0 +1,40 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.Approvals.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Approvals.Response; +/// +/// 审批模板响应参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/91982 +/// +public class WeChatWorkTemplateResponse : WeChatWorkResponse +{ + /// + /// 模版名称数组 + /// + [NotNull] + [JsonProperty("template_names")] + [JsonPropertyName("template_names")] + public List TemplateNames { get; set; } + /// + /// 审批模版控件设置,由多个表单控件及其内容组成,其中包含需要对控件赋值的信息 + /// + [NotNull] + [JsonProperty("template_content")] + [JsonPropertyName("template_content")] + public TemplateContent TemplateContent { get; set; } + public WeChatWorkTemplateResponse() + { + + } + + public WeChatWorkTemplateResponse(List templateNames, TemplateContent templateContent) + { + TemplateNames = templateNames; + TemplateContent = templateContent; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/WeChatWorkApprovalTemplateProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/WeChatWorkApprovalTemplateProvider.cs new file mode 100644 index 000000000..a3d9ee79a --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/WeChatWorkApprovalTemplateProvider.cs @@ -0,0 +1,80 @@ +using LINGYUN.Abp.WeChat.Work.Approvals.Request; +using LINGYUN.Abp.WeChat.Work.Approvals.Response; +using LINGYUN.Abp.WeChat.Work.Features; +using LINGYUN.Abp.WeChat.Work.Token; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.Approvals; + +[RequiresFeature(WeChatWorkFeatureNames.Enable)] +public class WeChatWorkApprovalTemplateProvider : IWeChatWorkApprovalTemplateProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkApprovalTemplateProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task ApplyEventAsync(WeChatWorkApplyEventRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.ApplyEventAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } + + public async virtual Task CreateTemplateAsync(WeChatWorkCreateTemplateRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.CreateTemplateAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } + + public async virtual Task GetApprovalDetailAsync(WeChatWorkGetApprovalDetailRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.GetApprovalDetailAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } + + public async virtual Task GetApprovalInfoAsync(WeChatWorkGetApprovalInfoRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.GetApprovalInfoAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } + + public async virtual Task GetTemplateAsync(WeChatWorkGetTemplateRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.GetTemplateAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } + + public async virtual Task UpdateTemplateAsync(WeChatWorkCreateTemplateRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.UpdateTemplateAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureDefinitionProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureDefinitionProvider.cs index 7548aab46..afb755c03 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureDefinitionProvider.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureDefinitionProvider.cs @@ -21,6 +21,7 @@ public class WeChatWorkFeatureDefinitionProvider : FeatureDefinitionProvider AddMessageFeature(wechatWorkFeature); AddAppChatFeature(wechatWorkFeature); + AddWebhookFeature(wechatWorkFeature); } protected virtual void AddMessageFeature(FeatureDefinition wechatWorkFeature) @@ -67,6 +68,28 @@ public class WeChatWorkFeatureDefinitionProvider : FeatureDefinitionProvider valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1000))); } + protected virtual void AddWebhookFeature(FeatureDefinition wechatWorkFeature) + { + var messageEnableFeature = wechatWorkFeature.CreateChild( + name: WeChatWorkFeatureNames.Webhook.Enable, + defaultValue: "false", + displayName: L("Features:WebhookMessageEnable"), + description: L("Features:WebhookMessageEnableDesc"), + valueType: new ToggleStringValueType(new BooleanValueValidator())); + messageEnableFeature.CreateChild( + name: WeChatWorkFeatureNames.Webhook.Limit, + defaultValue: "20", + displayName: L("Features:WebhookMessage.Limit"), + description: L("Features:WebhookMessage.LimitDesc"), + valueType: new FreeTextStringValueType(new NumericValueValidator(1, 20000))); + messageEnableFeature.CreateChild( + name: WeChatWorkFeatureNames.Webhook.LimitInterval, + defaultValue: "1", + displayName: L("Features:WebhookMessage.LimitInterval"), + description: L("Features:WebhookMessage.LimitIntervalDesc"), + valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1000))); + } + private static LocalizableString L(string name) { return LocalizableString.Create(name); diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureNames.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureNames.cs index 1a423a6b0..592698ba4 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureNames.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureNames.cs @@ -44,4 +44,21 @@ public static class WeChatWorkFeatureNames public const string LimitInterval = GroupName + ".LimitInterval"; } } + + public static class Webhook + { + public const string GroupName = WeChatWorkFeatureNames.GroupName + ".Webhook"; + /// + /// 启用消息推送 + /// + public const string Enable = GroupName + ".Enable"; + /// + /// 发送次数上限 + /// + public const string Limit = GroupName + ".Limit"; + /// + /// 发送次数上限时长 + /// + public const string LimitInterval = GroupName + ".LimitInterval"; + } } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/IWeChatWorkMessageSender.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/IWeChatWorkMessageSender.cs index 174de9d63..0f133bf67 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/IWeChatWorkMessageSender.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/IWeChatWorkMessageSender.cs @@ -32,4 +32,18 @@ public interface IWeChatWorkMessageSender Task SendAsync( WeChatWorkAppChatMessage message, CancellationToken cancellationToken = default); + /// + /// 发送Webhook消息 + /// + /// + /// 参考:https://developer.work.weixin.qq.com/document/path/99110 + /// + /// 消息推送的webhook Key + /// 继承自 的企业微信Webhook消息载体 + /// + /// + Task SendAsync( + string webhookKey, + WeChatWorkWebhookMessage message, + CancellationToken cancellationToken = default); } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCard.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCard.cs similarity index 92% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCard.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCard.cs index 2434e5989..b46a65a83 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCard.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCard.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; public abstract class TemplateCard { diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardAction.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardAction.cs similarity index 93% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardAction.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardAction.cs index fd40a9fd4..eab336981 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardAction.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardAction.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// /// 卡片操作按钮 /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardActionMenu.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardActionMenu.cs similarity index 93% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardActionMenu.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardActionMenu.cs index 155b62601..ea44e33dc 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardActionMenu.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardActionMenu.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; using System.Collections.Generic; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// /// 卡片右上角更多操作按钮 /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardCardAction.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardCardAction.cs similarity index 96% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardCardAction.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardCardAction.cs index 205b9b87d..3710ea510 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardCardAction.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardCardAction.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// /// 整体卡片的点击跳转事件 /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardEmphasisContent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardEmphasisContent.cs similarity index 93% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardEmphasisContent.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardEmphasisContent.cs index d0b5dea7d..d93d74973 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardEmphasisContent.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardEmphasisContent.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// /// 关键数据样式 /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardHorizontalContent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardHorizontalContent.cs similarity index 98% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardHorizontalContent.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardHorizontalContent.cs index 8b2e68687..55dc5fc6e 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardHorizontalContent.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardHorizontalContent.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// /// 二级标题+文本 /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardJump.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardJump.cs similarity index 96% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardJump.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardJump.cs index 94b04d81c..f996ec1a7 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardJump.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardJump.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Text; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// /// 跳转指引样式 /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardMainTitle.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardMainTitle.cs similarity index 94% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardMainTitle.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardMainTitle.cs index 0bf2b873c..b8a0f78cc 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardMainTitle.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardMainTitle.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// /// 卡票标题 /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardQuoteArea.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardQuoteArea.cs similarity index 97% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardQuoteArea.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardQuoteArea.cs index 1549ceabb..0626cc7a9 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardQuoteArea.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardQuoteArea.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// /// 引用文献样式 /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardSource.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardSource.cs similarity index 96% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardSource.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardSource.cs index 0eee1cb87..7000f010e 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TemplateCardSource.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TemplateCardSource.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// /// 来源文字颜色 /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TextMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TextMessage.cs index fb308af8a..e047ff840 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TextMessage.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TextMessage.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Newtonsoft.Json; using System.Text.Json.Serialization; +using Volo.Abp; namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// @@ -10,7 +11,7 @@ public class TextMessage { public TextMessage(string content) { - Content = content; + Content = Check.NotNullOrWhiteSpace(content, nameof(content), 2048); } /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TextTemplateCard.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TextTemplateCard.cs similarity index 98% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TextTemplateCard.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TextTemplateCard.cs index 7f4ea3f64..7e9955712 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/TextTemplateCard.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/TextTemplateCard.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; using System.Collections.Generic; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// /// 文本模板卡片消息 /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/WeChatWorkTemplateCardMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkTemplateCardMessage.cs similarity index 96% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/WeChatWorkTemplateCardMessage.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkTemplateCardMessage.cs index 5eca3e4fd..8c4f08c23 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Templates/WeChatWorkTemplateCardMessage.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkTemplateCardMessage.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using System.Text.Json.Serialization; -namespace LINGYUN.Abp.WeChat.Work.Messages.Templates; +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; /// /// 企业微信模板卡片消息 /// diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookFileMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookFileMessage.cs new file mode 100644 index 000000000..d584188fb --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookFileMessage.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 企业微信Webhook文件消息 +/// +public class WeChatWorkWebhookFileMessage : WeChatWorkWebhookMessage +{ + /// + /// 文件消息体 + /// + [NotNull] + [JsonProperty("file")] + [JsonPropertyName("file")] + public WebhookFileMessage File { get; set; } + /// + /// 创建一个企业微信Webhook文件消息 + /// + /// 文件消息体 + public WeChatWorkWebhookFileMessage(WebhookFileMessage file) + : base("file") + { + File = file; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookImageMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookImageMessage.cs new file mode 100644 index 000000000..fad686e12 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookImageMessage.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 企业微信Webhook图片消息 +/// +public class WeChatWorkWebhookImageMessage : WeChatWorkWebhookMessage +{ + /// + /// 图片消息体 + /// + [NotNull] + [JsonProperty("image")] + [JsonPropertyName("image")] + public WebhookImageMessage Image { get; set; } + /// + /// 创建一个企业微信Webhook图片消息 + /// + /// 图片消息体 + public WeChatWorkWebhookImageMessage(WebhookImageMessage image) + : base("image") + { + Image = image; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookMarkdownMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookMarkdownMessage.cs new file mode 100644 index 000000000..0824f4f2c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookMarkdownMessage.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 企业微信Webhook Markdown消息 +/// +public class WeChatWorkWebhookMarkdownMessage : WeChatWorkWebhookMessage +{ + /// + /// Markdown消息体 + /// + [NotNull] + [JsonProperty("markdown")] + [JsonPropertyName("markdown")] + public WebhookMarkdownMessage Markdown { get; set; } + /// + /// 创建一个企业微信Webhook Markdown消息 + /// + /// Markdown消息体 + public WeChatWorkWebhookMarkdownMessage(WebhookMarkdownMessage markdown) + : base("markdown") + { + Markdown = markdown; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookMarkdownV2Message.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookMarkdownV2Message.cs new file mode 100644 index 000000000..bf3cee876 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookMarkdownV2Message.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 企业微信Webhook MarkdownV2消息 +/// +public class WeChatWorkWebhookMarkdownV2Message : WeChatWorkWebhookMessage +{ + /// + /// markdown_v2消息体 + /// + [NotNull] + [JsonProperty("markdown_v2")] + [JsonPropertyName("markdown_v2")] + public WebhookMarkdownV2Message Markdown { get; set; } + /// + /// 创建一个企业微信Webhook MarkdownV2消息 + /// + /// markdown_v2消息体 + public WeChatWorkWebhookMarkdownV2Message(WebhookMarkdownV2Message markdown) + : base("markdown_v2") + { + Markdown = markdown; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookNewsMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookNewsMessage.cs new file mode 100644 index 000000000..c0b7a6c83 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookNewsMessage.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 企业微信Webhook图文消息 +/// +internal class WeChatWorkWebhookNewsMessage : WeChatWorkWebhookMessage +{ + /// + /// 图文消息体 + /// + [NotNull] + [JsonProperty("news")] + [JsonPropertyName("news")] + public WebhookNewsMessage News { get; set; } + /// + /// 创建一个企业微信Webhook图文消息 + /// + /// 图文消息体 + public WeChatWorkWebhookNewsMessage(WebhookNewsMessage news) + : base("news") + { + News = news; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookTemplateCardMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookTemplateCardMessage.cs new file mode 100644 index 000000000..3ff3b7231 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookTemplateCardMessage.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 企业微信Webhook模版卡片消息 +/// +public class WeChatWorkWebhookTemplateCardMessage : WeChatWorkWebhookMessage +{ + /// + /// 模版卡片消息体 + /// + [NotNull] + [JsonProperty("template_card")] + [JsonPropertyName("template_card")] + public WebhookTemplateCardMessage TemplateCard { get; set; } + /// + /// 创建一个企业微信Webhook模版卡片消息 + /// + /// 模版卡片消息体 + public WeChatWorkWebhookTemplateCardMessage(WebhookTemplateCardMessage templateCard) + : base("template_card") + { + TemplateCard = templateCard; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookTextMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookTextMessage.cs new file mode 100644 index 000000000..48676d4a1 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookTextMessage.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 企业微信Webhook文本消息 +/// +public class WeChatWorkWebhookTextMessage : WeChatWorkWebhookMessage +{ + /// + /// 文本消息体 + /// + [NotNull] + [JsonProperty("text")] + [JsonPropertyName("text")] + public WebhookTextMessage Text { get; set; } + /// + /// 创建一个企业微信Webhook文本消息 + /// + /// 文本消息体 + public WeChatWorkWebhookTextMessage(WebhookTextMessage text) + : base("text") + { + Text = text; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookVoiceMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookVoiceMessage.cs new file mode 100644 index 000000000..21eeda107 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WeChatWorkWebhookVoiceMessage.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 企业微信Webhook语音消息 +/// +public class WeChatWorkWebhookVoiceMessage : WeChatWorkWebhookMessage +{ + /// + /// 语音消息体 + /// + [NotNull] + [JsonProperty("voice")] + [JsonPropertyName("voice")] + public WebhookVoiceMessage Voice { get; set; } + /// + /// 创建一个企业微信Webhook语音消息 + /// + /// 语音消息体 + public WeChatWorkWebhookVoiceMessage(WebhookVoiceMessage voice) + : base("voice") + { + Voice = voice; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookFileMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookFileMessage.cs new file mode 100644 index 000000000..ae4dd53ff --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookFileMessage.cs @@ -0,0 +1,26 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// Webhook文件消息体 +/// +public class WebhookFileMessage +{ + /// + /// 文件id,通过下文的文件上传接口获取 + /// + [NotNull] + [JsonProperty("media_id")] + [JsonPropertyName("media_id")] + public string MediaId { get; set; } + /// + /// 创建一个Webhook文件消息体 + /// + /// 文件id,通过下文的文件上传接口获取 + public WebhookFileMessage(string mediaId) + { + MediaId = mediaId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookImageMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookImageMessage.cs new file mode 100644 index 000000000..58735b604 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookImageMessage.cs @@ -0,0 +1,35 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// Webhook图片消息体 +/// +public class WebhookImageMessage +{ + /// + /// 图片内容的base64编码 + /// + [NotNull] + [JsonProperty("base64")] + [JsonPropertyName("base64")] + public string Base64 { get; set; } + /// + /// 图片的md5 + /// + [NotNull] + [JsonProperty("md5")] + [JsonPropertyName("md5")] + public string Md5 { get; set; } + /// + /// 创建一个Webhook图片消息体 + /// + /// 图片内容的base64编码 + /// 图片的md5 + public WebhookImageMessage(string base64, string md5) + { + Base64 = base64; + Md5 = md5; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookMarkdownMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookMarkdownMessage.cs new file mode 100644 index 000000000..a65d4b8f5 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookMarkdownMessage.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// Webhook Markdown消息体 +/// +public class WebhookMarkdownMessage +{ + /// + /// markdown内容,最长不超过4096个字节,必须是utf8编码 + /// + [NotNull] + [StringLength(4096)] + [JsonProperty("content")] + [JsonPropertyName("content")] + public string Content { get; set; } + /// + /// 创建一个Webhook Markdown消息体 + /// + /// markdown内容,最长不超过4096个字节,必须是utf8编码 + public WebhookMarkdownMessage(string content) + { + Content = Check.NotNullOrWhiteSpace(content, nameof(content), 4096); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookMarkdownV2Message.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookMarkdownV2Message.cs new file mode 100644 index 000000000..7a6f1109b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookMarkdownV2Message.cs @@ -0,0 +1,33 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// Webhook MarkdownV2消息体 +/// +public class WebhookMarkdownV2Message +{ + /// + /// markdown_v2内容,最长不超过4096个字节,必须是utf8编码。 + /// + /// + /// 1. markdown_v2不支持字体颜色、@群成员的语法, 具体支持的语法可参考下面说明
+ /// 2. 消息内容在客户端 4.1.36 版本以下(安卓端为4.1.38以下) 消息表现为纯文本,建议使用最新客户端版本体验 + ///
+ [NotNull] + [StringLength(4096)] + [JsonProperty("content")] + [JsonPropertyName("content")] + public string Content { get; set; } + /// + /// 创建一个Webhook MarkdownV2消息体 + /// + /// markdown_v2内容,最长不超过4096个字节,必须是utf8编码 + public WebhookMarkdownV2Message(string content) + { + Content = Check.NotNullOrWhiteSpace(content, nameof(content), 4096); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookNewsMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookNewsMessage.cs new file mode 100644 index 000000000..920e04067 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookNewsMessage.cs @@ -0,0 +1,88 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// Webhook图文消息体 +/// +public class WebhookNewsMessage +{ + /// + /// 图文消息,一个图文消息支持1到8条图文 + /// + [NotNull] + [JsonProperty("articles")] + [JsonPropertyName("articles")] + public List Articles { get; set; } + /// + /// 创建一个Webhook图文消息体 + /// + /// 图文消息列表 + /// + public WebhookNewsMessage(List articles) + { + Articles = Check.NotNull(articles, nameof(articles)); + + if (Articles.Count < 1 || Articles.Count > 8) + { + throw new ArgumentException("One image-text message supports 1 to 8 image-text messages!"); + } + } +} + +public class WebhookArticleMessage +{ + /// + /// 标题,不超过128个字节,超过会自动截断 + /// + [NotNull] + [StringLength(128)] + [JsonProperty("title")] + [JsonPropertyName("title")] + public string Title { get; set; } + /// + /// 描述,不超过512个字节,超过会自动截断 + /// + [CanBeNull] + [StringLength(512)] + [JsonProperty("description")] + [JsonPropertyName("description")] + public string Description { get; set; } + /// + /// 点击后跳转的链接。 + /// + [NotNull] + [JsonProperty("url")] + [JsonPropertyName("url")] + public string Url { get; set; } + /// + /// 图文消息的图片链接,支持JPG、PNG格式,较好的效果为大图 1068*455,小图150*150。 + /// + [CanBeNull] + [JsonProperty("picurl")] + [JsonPropertyName("picurl")] + public string PictureUrl { get; set; } + /// + /// 创建一个图文消息 + /// + /// 标题 + /// 点击后跳转的链接 + /// 描述 + /// 图文消息的图片链接 + public WebhookArticleMessage( + string title, + string url, + string description = "", + string pictureUrl = "") + { + Title = Check.NotNullOrWhiteSpace(title, nameof(title), 128); + Url = Check.NotNullOrWhiteSpace(url, nameof(url)); + Description = Check.Length(description, nameof(description), 512); + PictureUrl = pictureUrl; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookNewsNoticeCardMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookNewsNoticeCardMessage.cs new file mode 100644 index 000000000..f45675cac --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookNewsNoticeCardMessage.cs @@ -0,0 +1,72 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// Webhook 图文展示模版卡片消息体 +/// +public class WebhookNewsNoticeCardMessage : WebhookTemplateCardMessage +{ + /// + /// 图片样式 + /// + [NotNull] + [JsonProperty("aspect_ratio")] + [JsonPropertyName("aspect_ratio")] + public WebhookTemplateCardImage CardImage { get; set; } + /// + /// 左图右文样式 + /// + [CanBeNull] + [JsonProperty("image_text_area")] + [JsonPropertyName("image_text_area")] + public WebhookTemplateCardImageTextArea ImageTextArea { get; set; } + /// + /// 卡片二级垂直内容,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过4 + /// + [CanBeNull] + [JsonProperty("vertical_content_list")] + [JsonPropertyName("vertical_content_list")] + public List VerticalContents { get; set; } + /// + /// 创建一个Webhook 图文展示模版卡片消息体 + /// + /// 图片样式 + /// 整体卡片的点击跳转事件 + /// 模版卡片的主要内容 + /// 左图右文样式 + /// 卡片来源样式信息 + /// 引用文献样式 + /// 二级标题+文本列表,列表长度不超过6 + /// 卡片二级垂直内容,列表长度不超过4 + /// 跳转指引样式的列表,列表长度不超过3 + /// + public WebhookNewsNoticeCardMessage( + WebhookTemplateCardImage cardImage, + WebhookTemplateCardAction action, + WebhookTemplateCardMainTitle mainTitle, + WebhookTemplateCardImageTextArea imageTextArea = null, + WebhookTemplateCardSource source = null, + WebhookTemplateCardQuoteArea quoteArea = null, + List horizontalContents = null, + List verticalContents = null, + List jumps = null) + : base("news_notice", action, mainTitle, source, quoteArea, horizontalContents, jumps) + { + Check.NotNull(mainTitle, nameof(mainTitle)); + Check.NotNull(cardImage, nameof(cardImage)); + + CardImage = cardImage; + ImageTextArea = imageTextArea; + VerticalContents = verticalContents; + + if (verticalContents != null && verticalContents.Count > 4) + { + throw new ArgumentException("The length of the secondary vertical content list on the card cannot exceed 4!"); + } + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardAction.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardAction.cs new file mode 100644 index 000000000..a02ada44c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardAction.cs @@ -0,0 +1,75 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 卡片的点击跳转事件 +/// +public class WebhookTemplateCardAction +{ + /// + /// 卡片跳转类型,1 代表跳转url,2 代表打开小程序。text_notice模版卡片中该字段取值范围为[1,2] + /// + [NotNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public int Type { get; set; } + /// + /// 跳转链接的url,type是1时必填 + /// + [CanBeNull] + [JsonProperty("url")] + [JsonPropertyName("url")] + public string Url { get; set; } + /// + /// 点击跳转的小程序的appid,type是2时必填 + /// + [CanBeNull] + [JsonProperty("appid")] + [JsonPropertyName("appid")] + public string AppId { get; set; } + /// + /// 点击跳转的小程序的pagepath,type是2时选填 + /// + [CanBeNull] + [JsonProperty("pagepath")] + [JsonPropertyName("pagepath")] + public string PagePath { get; set; } + private WebhookTemplateCardAction( + int type, + string url = null, + string appId = null, + string pagePath = null) + { + Type = type; + Url = url; + AppId = appId; + PagePath = pagePath; + } + /// + /// 创建一个跳转链接卡片事件 + /// + /// 跳转链接的url + /// + public static WebhookTemplateCardAction Link(string url) + { + Check.NotNullOrWhiteSpace(url, nameof(url)); + + return new WebhookTemplateCardAction(1, url); + } + /// + /// 创建一个跳转小程序卡片事件 + /// + /// 小程序的appid + /// 小程序的pagePath + /// + public static WebhookTemplateCardAction MiniProgram(string appId, string pagePath) + { + Check.NotNullOrWhiteSpace(appId, nameof(appId)); + Check.NotNullOrWhiteSpace(pagePath, nameof(pagePath)); + + return new WebhookTemplateCardAction(2, appId: appId, pagePath: pagePath); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardEmphasisContent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardEmphasisContent.cs new file mode 100644 index 000000000..91394867a --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardEmphasisContent.cs @@ -0,0 +1,35 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 关键数据样式 +/// +public class WebhookTemplateCardEmphasisContent +{ + /// + /// 关键数据样式的数据内容,建议不超过10个字 + /// + [CanBeNull] + [JsonProperty("title")] + [JsonPropertyName("title")] + public string Title { get; set; } + /// + /// 关键数据样式的数据描述内容,建议不超过15个字 + /// + [CanBeNull] + [JsonProperty("desc")] + [JsonPropertyName("desc")] + public string Description { get; set; } + /// + /// 创建一个关键数据样式 + /// + /// 关键数据样式的数据内容 + /// 关键数据样式的数据描述内容 + public WebhookTemplateCardEmphasisContent(string title, string description = null) + { + Title = title; + Description = description; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardHorizontalContent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardHorizontalContent.cs new file mode 100644 index 000000000..96e2c3341 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardHorizontalContent.cs @@ -0,0 +1,123 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 二级标题+文本 +/// +public class WebhookTemplateCardHorizontalContent +{ + /// + /// 模版卡片的二级标题信息内容支持的类型,1是url,2是文件附件,3 代表点击跳转成员详情 + /// + [CanBeNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public int? Type { get; set; } + /// + /// 二级标题,建议不超过5个字 + /// + [NotNull] + [JsonProperty("keyname")] + [JsonPropertyName("keyname")] + public string KeyName { get; set; } + /// + /// 二级文本,如果type是2,该字段代表文件名称(要包含文件类型),建议不超过26个字 + /// + [CanBeNull] + [JsonProperty("value")] + [JsonPropertyName("value")] + public string Value { get; set; } + /// + /// 链接跳转的url,type是1时必填 + /// + [CanBeNull] + [JsonProperty("url")] + [JsonPropertyName("url")] + public string Url { get; set; } + /// + /// 附件的media_id,type是2时必填 + /// + [CanBeNull] + [JsonProperty("media_id")] + [JsonPropertyName("media_id")] + public string MediaId { get; set; } + /// + /// 成员详情的userid,type是3时必填 + /// + [CanBeNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; set; } + private WebhookTemplateCardHorizontalContent( + string keyName, + int? type = null, + string value = null, + string url = null, + string mediaId = null, + string userId = null) + { + Type = type; + KeyName = keyName; + Value = value; + Url = url; + MediaId = mediaId; + UserId = userId; + } + /// + /// 创建一个默认二级标题+文本 + /// + /// 二级标题 + /// 二级文本 + /// + public static WebhookTemplateCardHorizontalContent Default(string keyName, string value = null) + { + Check.NotNullOrWhiteSpace(keyName, nameof(keyName)); + + return new WebhookTemplateCardHorizontalContent(keyName, value: value); + } + /// + /// 创建一个跳转链接的二级标题+文本 + /// + /// 二级标题 + /// 链接跳转的url + /// 二级文本 + /// + public static WebhookTemplateCardHorizontalContent Link(string keyName, string url, string value = null) + { + Check.NotNullOrWhiteSpace(keyName, nameof(keyName)); + Check.NotNullOrWhiteSpace(url, nameof(url)); + + return new WebhookTemplateCardHorizontalContent(keyName, 1, value: value, url: url); + } + /// + /// 创建一个引用文件的二级标题+文本 + /// + /// 二级标题 + /// 文件名称 + /// 附件的mediaId + /// + public static WebhookTemplateCardHorizontalContent File(string keyName, string fileName, string mediaId) + { + Check.NotNullOrWhiteSpace(keyName, nameof(keyName)); + Check.NotNullOrWhiteSpace(fileName, nameof(fileName)); + Check.NotNullOrWhiteSpace(mediaId, nameof(mediaId)); + + return new WebhookTemplateCardHorizontalContent(keyName, 2, value: fileName, mediaId: mediaId); + } + /// + /// 创建一个成员详情的二级标题+文本 + /// + /// 二级标题 + /// 成员的userid + /// + public static WebhookTemplateCardHorizontalContent UserInfo(string keyName, string userId) + { + Check.NotNullOrWhiteSpace(keyName, nameof(keyName)); + Check.NotNullOrWhiteSpace(userId, nameof(userId)); + + return new WebhookTemplateCardHorizontalContent(keyName, 3, userId: userId); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardImage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardImage.cs new file mode 100644 index 000000000..ce6f91021 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardImage.cs @@ -0,0 +1,35 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 图片样式 +/// +public class WebhookTemplateCardImage +{ + /// + /// 图片的url + /// + [NotNull] + [JsonProperty("url")] + [JsonPropertyName("url")] + public string Url { get; set; } + /// + /// 图片的宽高比,宽高比要小于2.25,大于1.3,不填该参数默认1.3 + /// + [CanBeNull] + [JsonProperty("aspect_ratio")] + [JsonPropertyName("aspect_ratio")] + public float? AspectRatio { get; set; } + /// + /// 创建一个图片样式 + /// + /// 图片的url + /// 图片的宽高比,不填该参数默认1.3 + public WebhookTemplateCardImage(string url, float? aspectRatio = 1.3f) + { + Url = url; + AspectRatio = aspectRatio; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardImageTextArea.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardImageTextArea.cs new file mode 100644 index 000000000..ef5ff32fb --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardImageTextArea.cs @@ -0,0 +1,119 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 左图右文样式 +/// +public class WebhookTemplateCardImageTextArea +{ + /// + /// 左图右文样式区域点击事件,0或不填代表没有点击事件,1 代表跳转url,2 代表跳转小程序 + /// + [CanBeNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public int? Type { get; set; } + /// + /// 左图右文样式的图片url + /// + [NotNull] + [JsonProperty("image_url")] + [JsonPropertyName("image_url")] + public string ImageUrl { get; set; } + /// + /// 点击跳转的url,type是1时必填 + /// + [NotNull] + [JsonProperty("url")] + [JsonPropertyName("url")] + public string Url { get; set; } + /// + /// 点击跳转的小程序的appid,type是2时必填 + /// + [CanBeNull] + [JsonProperty("appid")] + [JsonPropertyName("appid")] + public string AppId { get; set; } + /// + /// 点击跳转的小程序的pagepath,type是2时选填 + /// + [CanBeNull] + [JsonProperty("pagepath")] + [JsonPropertyName("pagepath")] + public string PagePath { get; set; } + /// + /// 左图右文样式的标题 + /// + [CanBeNull] + [JsonProperty("title")] + [JsonPropertyName("title")] + public string Title { get; set; } + /// + /// 左图右文样式的描述 + /// + [CanBeNull] + [JsonProperty("desc")] + [JsonPropertyName("desc")] + public string Description { get; set; } + private WebhookTemplateCardImageTextArea( + string imageUrl, + int? type = null, + string url = null, + string appId = null, + string pagePath = null, + string title = null, + string description = null) + { + Type = type; + ImageUrl = imageUrl; + Url = url; + AppId = appId; + PagePath = pagePath; + Title = title; + Description = description; + } + /// + /// 创建一个跳转链接左图右文样式 + /// + /// 左图右文样式的图片url + /// 点击跳转的url + /// 左图右文样式的标题 + /// 左图右文样式的描述 + /// + public static WebhookTemplateCardImageTextArea Link( + string imageUrl, + string url, + string title = null, + string description = null) + { + Check.NotNullOrWhiteSpace(imageUrl, nameof(imageUrl)); + Check.NotNullOrWhiteSpace(url, nameof(url)); + + return new WebhookTemplateCardImageTextArea(imageUrl, 1, url: url, title: title, description: description); + } + /// + /// 创建一个跳转小程序左图右文样式 + /// + /// 左图右文样式的图片url + /// 小程序AppId + /// 小程序PagePath + /// 左图右文样式的标题 + /// 左图右文样式的描述 + /// + public static WebhookTemplateCardImageTextArea MiniProgram( + string imageUrl, + string appId, + string pagePath, + string title = null, + string description = null) + { + Check.NotNullOrWhiteSpace(imageUrl, nameof(imageUrl)); + Check.NotNullOrWhiteSpace(appId, nameof(appId)); + Check.NotNullOrWhiteSpace(pagePath, nameof(pagePath)); + + return new WebhookTemplateCardImageTextArea(imageUrl, 2, appId: appId, pagePath: pagePath, title: title, description: description); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardJump.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardJump.cs new file mode 100644 index 000000000..45a33fbc6 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardJump.cs @@ -0,0 +1,99 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 跳转指引样式 +/// +public class WebhookTemplateCardJump +{ + /// + /// 跳转链接类型,0或不填代表不是链接,1 代表跳转url,2 代表跳转小程序 + /// + [CanBeNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public int? Type { get; set; } + /// + /// 跳转链接样式的文案内容,建议不超过13个字 + /// + [NotNull] + [JsonProperty("title")] + [JsonPropertyName("title")] + public string Title { get; set; } + /// + /// 跳转链接的url,type是1时必填 + /// + [CanBeNull] + [JsonProperty("url")] + [JsonPropertyName("url")] + public string Url { get; set; } + /// + /// 点击跳转的小程序的appid,type是2时必填 + /// + [CanBeNull] + [JsonProperty("appid")] + [JsonPropertyName("appid")] + public string AppId { get; set; } + /// + /// 点击跳转的小程序的pagepath,type是2时选填 + /// + [CanBeNull] + [JsonProperty("pagepath")] + [JsonPropertyName("pagepath")] + public string PagePath { get; set; } + private WebhookTemplateCardJump( + string title, + int? type = null, + string url = null, + string appId = null, + string pagePath = null) + { + Type = type; + Title = title; + Url = url; + AppId = appId; + PagePath = pagePath; + } + /// + /// 创建一个默认指引样式 + /// + /// 跳转链接样式的文案内容 + /// + public static WebhookTemplateCardJump Default(string title) + { + Check.NotNullOrWhiteSpace(title, nameof(title)); + + return new WebhookTemplateCardJump(title); + } + /// + /// 创建一个跳转链接的指引样式 + /// + /// 跳转链接样式的文案内容 + /// 跳转链接的url + /// + public static WebhookTemplateCardJump Link(string title, string url) + { + Check.NotNullOrWhiteSpace(title, nameof(title)); + Check.NotNullOrWhiteSpace(url, nameof(url)); + + return new WebhookTemplateCardJump(title, 1, url); + } + /// + /// 创建一个跳转小程序的指引样式 + /// + /// 跳转链接样式的文案内容 + /// 跳转链接的小程序的appid + /// 跳转链接的小程序的pagepath + /// + public static WebhookTemplateCardJump MiniProgram(string title, string appId, string pagePath) + { + Check.NotNullOrWhiteSpace(title, nameof(title)); + Check.NotNullOrWhiteSpace(appId, nameof(appId)); + Check.NotNullOrWhiteSpace(pagePath, nameof(pagePath)); + + return new WebhookTemplateCardJump(title, 2, appId: appId, pagePath: pagePath); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardMainTitle.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardMainTitle.cs new file mode 100644 index 000000000..9a4db37b1 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardMainTitle.cs @@ -0,0 +1,35 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 模版卡片的主要内容 +/// +public class WebhookTemplateCardMainTitle +{ + /// + /// 一级标题,建议不超过26个字。模版卡片主要内容的一级标题main_title.title和二级普通文本sub_title_text必须有一项填写 + /// + [CanBeNull] + [JsonProperty("title")] + [JsonPropertyName("title")] + public string Title { get; set; } + /// + /// 标题辅助信息,建议不超过30个字 + /// + [CanBeNull] + [JsonProperty("desc")] + [JsonPropertyName("desc")] + public string Description { get; set; } + /// + /// 创建一个模版卡片的主要内容 + /// + /// 一级标题 + /// 标题辅助信息 + public WebhookTemplateCardMainTitle(string title, string description = null) + { + Title = title; + Description = description; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardMessage.cs new file mode 100644 index 000000000..ef801dde8 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardMessage.cs @@ -0,0 +1,100 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// Webhook模板卡片消息体 +/// +public abstract class WebhookTemplateCardMessage +{ + /// + /// 模版卡片的模版类型 + /// + [CanBeNull] + [JsonProperty("card_type")] + [JsonPropertyName("card_type")] + public string CardType { get; set; } + /// + /// 卡片来源样式信息,不需要来源样式可不填写 + /// + [CanBeNull] + [JsonProperty("source")] + [JsonPropertyName("source")] + public WebhookTemplateCardSource Source { get; set; } + /// + /// 模版卡片的主要内容,包括一级标题和标题辅助信息 + /// + [CanBeNull] + [JsonProperty("main_title")] + [JsonPropertyName("main_title")] + public WebhookTemplateCardMainTitle MainTitle { get; set; } + /// + /// 引用文献样式,建议不与关键数据共用 + /// + [CanBeNull] + [JsonProperty("quote_area")] + [JsonPropertyName("quote_area")] + public WebhookTemplateCardQuoteArea QuoteArea { get; set; } + /// + /// 二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6 + /// + [CanBeNull] + [JsonProperty("horizontal_content_list")] + [JsonPropertyName("horizontal_content_list")] + public List HorizontalContents { get; set; } + /// + /// 跳转指引样式的列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过3 + /// + [CanBeNull] + [JsonProperty("jump_list")] + [JsonPropertyName("jump_list")] + public List Jumps { get; set; } + /// + /// 整体卡片的点击跳转事件,text_notice模版卡片中该字段为必填项 + /// + [CanBeNull] + [JsonProperty("card_action")] + [JsonPropertyName("card_action")] + public WebhookTemplateCardAction Action { get; set; } + /// + /// 创建一个Webhook模板卡片消息体 + /// + /// 整体卡片的点击跳转事件 + /// 模版卡片的主要内容 + /// 卡片来源样式信息 + /// 引用文献样式 + /// 二级标题+文本列表,列表长度不超过6 + /// 跳转指引样式的列表,列表长度不超过3 + /// + protected WebhookTemplateCardMessage( + string cardType, + WebhookTemplateCardAction action, + WebhookTemplateCardMainTitle mainTitle = null, + WebhookTemplateCardSource source = null, + WebhookTemplateCardQuoteArea quoteArea = null, + List horizontalContents = null, + List jumps = null) + { + CardType = Check.NotNullOrWhiteSpace(cardType, nameof(cardType)); + Action = Check.NotNull(action, nameof(action)); + + MainTitle = mainTitle; + Source = source; + QuoteArea = quoteArea; + HorizontalContents = horizontalContents; + Jumps = jumps; + + if (horizontalContents != null && horizontalContents.Count > 6) + { + throw new ArgumentException("The length of the secondary title and the text list should not exceed 6!"); + } + if (jumps != null && jumps.Count > 3) + { + throw new ArgumentException("The length of the list in the style of jump guidance should not exceed 3!"); + } + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardQuoteArea.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardQuoteArea.cs new file mode 100644 index 000000000..dd00cd366 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardQuoteArea.cs @@ -0,0 +1,111 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 引用文献 +/// +public class WebhookTemplateCardQuoteArea +{ + /// + /// 引用文献样式区域点击事件,0或不填代表没有点击事件,1 代表跳转url,2 代表跳转小程序 + /// + [CanBeNull] + [JsonProperty("type")] + [JsonPropertyName("type")] + public int? Type { get; set; } + /// + /// 点击跳转的url,type是1时必填 + /// + [CanBeNull] + [JsonProperty("url")] + [JsonPropertyName("url")] + public string Url { get; set; } + /// + /// 点击跳转的小程序的appid,type是2时必填 + /// + [CanBeNull] + [JsonProperty("appid")] + [JsonPropertyName("appid")] + public string AppId { get; set; } + /// + /// 点击跳转的小程序的pagepath,type是2时选填 + /// + [CanBeNull] + [JsonProperty("pagepath")] + [JsonPropertyName("pagepath")] + public string PagePath { get; set; } + /// + /// 引用文献样式的标题 + /// + [CanBeNull] + [JsonProperty("title")] + [JsonPropertyName("title")] + public string Title { get; set; } + /// + /// 引用文献样式的引用文案 + /// + [CanBeNull] + [JsonProperty("quote_text")] + [JsonPropertyName("quote_text")] + public string QuoteText { get; set; } + private WebhookTemplateCardQuoteArea( + string title, + int? type = null, + string url = null, + string appId = null, + string pagePath = null, + string quoteText = null) + { + Title = title; + QuoteText = quoteText; + Type = type; + Url = url; + AppId = appId; + PagePath = pagePath; + } + /// + /// 创建一个默认引用文献 + /// + /// 引用文献样式的标题 + /// 引用文献样式的引用文案 + /// + public static WebhookTemplateCardQuoteArea Default(string title, string quoteText = null) + { + Check.NotNullOrWhiteSpace(title, nameof(title)); + + return new WebhookTemplateCardQuoteArea(title, quoteText: quoteText); + } + /// + /// 创建一个跳转链接的引用文献 + /// + /// 引用文献样式的标题 + /// 点击跳转的url + /// 引用文献样式的引用文案 + /// + public static WebhookTemplateCardQuoteArea Link(string title, string url, string quoteText = null) + { + Check.NotNullOrWhiteSpace(title, nameof(title)); + Check.NotNullOrWhiteSpace(url, nameof(url)); + + return new WebhookTemplateCardQuoteArea(title, 1, url, quoteText: quoteText); + } + /// + /// 创建一个跳转小程序的引用文献 + /// + /// 引用文献样式的标题 + /// 跳转链接的小程序的appid + /// 跳转链接的小程序的pagepath + /// 引用文献样式的引用文案 + /// + public static WebhookTemplateCardQuoteArea MiniProgram(string title, string appId, string pagePath, string quoteText = null) + { + Check.NotNullOrWhiteSpace(title, nameof(title)); + Check.NotNullOrWhiteSpace(appId, nameof(appId)); + Check.NotNullOrWhiteSpace(pagePath, nameof(pagePath)); + + return new WebhookTemplateCardQuoteArea(title, 2, appId: appId, pagePath: pagePath, quoteText: quoteText); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardSource.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardSource.cs new file mode 100644 index 000000000..322dec87b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardSource.cs @@ -0,0 +1,81 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 卡片来源样式信息 +/// +public class WebhookTemplateCardSource +{ + /// + /// 来源图片的url + /// + [CanBeNull] + [JsonProperty("icon_url")] + [JsonPropertyName("icon_url")] + public string IconUrl { get; set; } + /// + /// 来源图片的描述,建议不超过13个字 + /// + [CanBeNull] + [JsonProperty("desc")] + [JsonPropertyName("desc")] + public string Description { get; set; } + /// + /// 来源文字的颜色,目前支持:0(默认) 灰色,1 黑色,2 红色,3 绿色 + /// + [CanBeNull] + [JsonProperty("desc_color")] + [JsonPropertyName("desc_color")] + public int? DescriptionColor { get; set; } + private WebhookTemplateCardSource( + string iconUrl = null, + string description = null, + int? descriptionColor = 0) + { + IconUrl = iconUrl; + Description = description; + DescriptionColor = descriptionColor; + } + /// + /// 创建一个灰色卡片来源样式 + /// + /// 来源图片的url + /// 来源图片的描述 + /// + public static WebhookTemplateCardSource Grey(string iconUrl, string description = null) + { + return new WebhookTemplateCardSource(iconUrl, description, 0); + } + /// + /// 创建一个黑色卡片来源样式 + /// + /// 来源图片的url + /// 来源图片的描述 + /// + public static WebhookTemplateCardSource Black(string iconUrl, string description = null) + { + return new WebhookTemplateCardSource(iconUrl, description, 1); + } + /// + /// 创建一个红色卡片来源样式 + /// + /// 来源图片的url + /// 来源图片的描述 + /// + public static WebhookTemplateCardSource Red(string iconUrl, string description = null) + { + return new WebhookTemplateCardSource(iconUrl, description, 2); + } + /// + /// 创建一个绿色卡片来源样式 + /// + /// 来源图片的url + /// 来源图片的描述 + /// + public static WebhookTemplateCardSource Green(string iconUrl, string description = null) + { + return new WebhookTemplateCardSource(iconUrl, description, 3); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardVerticalContent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardVerticalContent.cs new file mode 100644 index 000000000..3301e0a33 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTemplateCardVerticalContent.cs @@ -0,0 +1,35 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// 卡片二级垂直内容 +/// +public class WebhookTemplateCardVerticalContent +{ + /// + /// 卡片二级标题,建议不超过26个字 + /// + [CanBeNull] + [JsonProperty("title")] + [JsonPropertyName("title")] + public string Title { get; set; } + /// + /// 二级普通文本,建议不超过112个字 + /// + [CanBeNull] + [JsonProperty("desc")] + [JsonPropertyName("desc")] + public string Description { get; set; } + /// + /// 创建一个卡片二级垂直内容 + /// + /// 卡片二级标题 + /// 二级普通文本 + public WebhookTemplateCardVerticalContent(string title, string description = null) + { + Title = title; + Description = description; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTextMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTextMessage.cs new file mode 100644 index 000000000..cba2ec275 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTextMessage.cs @@ -0,0 +1,45 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// Webhook文本消息体 +/// +public class WebhookTextMessage +{ + /// + /// 消文本内容,最长不超过2048个字节,必须是utf8编码 + /// + [NotNull] + [StringLength(2048)] + [JsonProperty("content")] + [JsonPropertyName("content")] + public string Content { get; set; } + /// + /// userid的列表,提醒群中的指定成员(@某个成员),@all表示提醒所有人,如果开发者获取不到userid,可以使用mentioned_mobile_list + /// + [NotNull] + [JsonProperty("mentioned_list")] + [JsonPropertyName("mentioned_list")] + public List MentionedList { get; set; } + /// + /// 手机号列表,提醒手机号对应的群成员(@某个成员),@all表示提醒所有人 + /// + [NotNull] + [StringLength(2048)] + [JsonProperty("mentioned_mobile_list")] + [JsonPropertyName("mentioned_mobile_list")] + public List MentionedMobileList { get; set; } + /// + /// 创建一个Webhook文本消息体 + /// + /// 消息内容 + public WebhookTextMessage(string content) + { + Content = Check.NotNullOrWhiteSpace(content, nameof(content), 2048); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTextNoticeCardMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTextNoticeCardMessage.cs new file mode 100644 index 000000000..24e208f0b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookTextNoticeCardMessage.cs @@ -0,0 +1,63 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// Webhook 文本通知模版卡片消息体 +/// +public class WebhookTextNoticeCardMessage : WebhookTemplateCardMessage +{ + /// + /// 二级普通文本,建议不超过112个字。模版卡片主要内容的一级标题main_title.title和二级普通文本sub_title_text必须有一项填写 + /// + [CanBeNull] + [JsonProperty("sub_title_text")] + [JsonPropertyName("sub_title_text")] + public string SubTitleText { get; set; } + /// + /// 关键数据样式 + /// + [CanBeNull] + [JsonProperty("emphasis_content")] + [JsonPropertyName("emphasis_content")] + public WebhookTemplateCardEmphasisContent EmphasisContent { get; set; } + /// + /// 创建一个Webhook 文本通知模版卡片消息体 + /// + /// 整体卡片的点击跳转事件 + /// 模版卡片的主要内容 + /// 二级普通文本 + /// 关键数据样式 + /// 卡片来源样式信息 + /// 引用文献样式 + /// 二级标题+文本列表,列表长度不超过6 + /// 跳转指引样式的列表,列表长度不超过3 + /// + public WebhookTextNoticeCardMessage( + WebhookTemplateCardAction action, + WebhookTemplateCardMainTitle mainTitle = null, + string subTitleText = null, + WebhookTemplateCardEmphasisContent emphasisContent = null, + WebhookTemplateCardSource source = null, + WebhookTemplateCardQuoteArea quoteArea = null, + List horizontalContents = null, + List jumps = null) + : base("text_notice", action, mainTitle, source, quoteArea, horizontalContents, jumps) + { + MainTitle = mainTitle; + SubTitleText = subTitleText; + EmphasisContent = emphasisContent; + Source = source; + QuoteArea = quoteArea; + HorizontalContents = horizontalContents; + Jumps = jumps; + + if (mainTitle == null && subTitleText.IsNullOrWhiteSpace()) + { + throw new ArgumentException("One of the primary title mainTitle and the secondary ordinary text subTitleText of the main content of the template card must be filled in!"); + } + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookVoiceMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookVoiceMessage.cs new file mode 100644 index 000000000..76c7ab67c --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/Models/WebhookVoiceMessage.cs @@ -0,0 +1,26 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Messages.Models; +/// +/// Webhook语音消息体 +/// +public class WebhookVoiceMessage +{ + /// + /// 语音文件id,通过下文的文件上传接口获取 + /// + [NotNull] + [JsonProperty("media_id")] + [JsonPropertyName("media_id")] + public string MediaId { get; set; } + /// + /// 创建一个Webhook语音消息体 + /// + /// 语音文件id + public WebhookVoiceMessage(string mediaId) + { + MediaId = mediaId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/README.md b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/README.md new file mode 100644 index 000000000..90f1bb428 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/README.md @@ -0,0 +1,51 @@ +# 企业微信消息通知 + +## 功能特性 + +* [发送应用消息](https://developer.work.weixin.qq.com/document/path/90236) +* [发送群聊消息](https://developer.work.weixin.qq.com/document/path/90248) +* [发送Webhook消息](https://developer.work.weixin.qq.com/document/path/99110) + + +## 使用方法 + +- 发送Webhook消息 +```csharp +public class Demo +{ + private readonly IWeChatWorkMessageSender _sender; + + public Demo(IWeChatWorkMessageSender sender) + { + _sender = sender; + } + + public async Task Send() + { + await _sender.SendAsync( + "693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa", + new WeChatWorkWebhookTemplateCardMessage( + new WebhookTextNoticeCardMessage( + WebhookTemplateCardAction.Link("https://developer.work.weixin.qq.com/document/path/99110"), + new WebhookTemplateCardMainTitle("请假通过通知", "您的请假申请已通过,请前往查看!"), + source: WebhookTemplateCardSource.Black("https://wwcdn.weixin.qq.com/node/wework/images/wecom-logo.a61830413b.svg", "企业微信"), + horizontalContents: new List + { + WebhookTemplateCardHorizontalContent.Default("审批单号", "QJ20251000000136"), + WebhookTemplateCardHorizontalContent.Default("请假日期", "2025/10/01-2025/10/10"), + WebhookTemplateCardHorizontalContent.Default("通过时间", "2025-10-01 15:30:00"), + WebhookTemplateCardHorizontalContent.Default("审批备注", "做好考勤及交接事项"), + }, + jumps: new List + { + WebhookTemplateCardJump.Link("去OA查看", "https://developer.work.weixin.qq.com/document/path/99110") + }))); + } +} +``` + + +## 更多文档 + +* [企业微信文档](https://developer.work.weixin.qq.com/document/path/90664) + diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/WeChatWorkMessageSender.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/WeChatWorkMessageSender.cs index af0c56981..f1bf103e1 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/WeChatWorkMessageSender.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/WeChatWorkMessageSender.cs @@ -76,7 +76,29 @@ public class WeChatWorkMessageSender : IWeChatWorkMessageSender, ISingletonDepen if (!messageResponse.IsSuccessed) { - Logger.LogWarning("Send wechat work message failed"); + Logger.LogWarning("Send wechat work app chat message failed"); + Logger.LogWarning($"Error code: {messageResponse.ErrorCode}, message: {messageResponse.ErrorMessage}"); + } + + return messageResponse; + } + + [RequiresFeature(WeChatWorkFeatureNames.Webhook.Enable)] + [RequiresLimitFeature( + WeChatWorkFeatureNames.Webhook.Limit, + WeChatWorkFeatureNames.Webhook.LimitInterval, + LimitPolicy.Minute)] + // 消息发送频率限制: https://developer.work.weixin.qq.com/document/path/99110#%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E9%A2%91%E7%8E%87%E9%99%90%E5%88%B6 + public async virtual Task SendAsync(string webhookKey, WeChatWorkWebhookMessage message, CancellationToken cancellationToken = default) + { + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.SendMessageAsync(webhookKey, message, cancellationToken); + var messageResponse = await response.DeserializeObjectAsync(); + + if (!messageResponse.IsSuccessed) + { + Logger.LogWarning("Send wechat work webhook message failed"); Logger.LogWarning($"Error code: {messageResponse.ErrorCode}, message: {messageResponse.ErrorMessage}"); } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/WeChatWorkWebhookMessage.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/WeChatWorkWebhookMessage.cs new file mode 100644 index 000000000..290569913 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Messages/WeChatWorkWebhookMessage.cs @@ -0,0 +1,23 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Messages; +/// +/// 企业微信Webhook消息结构体 +/// +public abstract class WeChatWorkWebhookMessage : WeChatWorkRequest +{ + /// + /// 消息类型 + /// + [NotNull] + [JsonProperty("msgtype")] + [JsonPropertyName("msgtype")] + public string MsgType { get; set; } + protected WeChatWorkWebhookMessage(string msgType) + { + MsgType = Check.NotNullOrWhiteSpace(msgType, nameof(msgType)); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Claims/AbpWeChatWorkClaimTypes.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Claims/AbpWeChatWorkClaimTypes.cs index 0dbcc3538..d95611d59 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Claims/AbpWeChatWorkClaimTypes.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Claims/AbpWeChatWorkClaimTypes.cs @@ -4,17 +4,17 @@ public static class AbpWeChatWorkClaimTypes /// /// 唯一标识 /// - public static string UserId { get; set; } = "userid"; + public static string UserId { get; set; } = "wecom_userid"; /// /// 二维码名片 /// - public static string QrCode { get; set; } = "qr_code"; + public static string QrCode { get; set; } = "wecom_qr_code"; /// /// 企业邮箱 /// - public static string BizMail { get; set; } = "biz_mail"; + public static string BizMail { get; set; } = "wecom_biz_mail"; /// /// 地址 /// - public static string Address { get; set; } = "address"; + public static string Address { get; set; } = "wecom_address"; } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/IWeChatWorkServerProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/IWeChatWorkServerProvider.cs new file mode 100644 index 000000000..8f527f4d0 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/IWeChatWorkServerProvider.cs @@ -0,0 +1,17 @@ +using LINGYUN.Abp.WeChat.Work.Security.Models; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.Security; +public interface IWeChatWorkServerProvider +{ + /// + /// 获取企业微信域名IP信息 + /// + /// + /// 参考:https://developer.work.weixin.qq.com/document/path/100079 + /// + /// + /// + Task GetWeChatServerAsync(CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatDomainModel.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatDomainModel.cs new file mode 100644 index 000000000..0105cb67d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatDomainModel.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Security.Models; +public class WeChatDomainModel +{ + /// + /// 域名 + /// + [JsonProperty("domain")] + [JsonPropertyName("domain")] + public string Domain { get; set; } = default!; + /// + /// 泛域名 + /// + [JsonProperty("universal_domian")] + [JsonPropertyName("universal_domian")] + public string UniversalDomian { get; set; } + /// + /// 协议 如TCP UDP + /// + [JsonProperty("protocol")] + [JsonPropertyName("protocol")] + public string Protocol { get; set; } = default!; + /// + /// 端口号列表 + /// + [JsonProperty("port")] + [JsonPropertyName("port")] + public List Port { get; set; } = new List(); + /// + /// 是否必要,0-否 1-是, 如果必要的域名或IP被拦截,将导致企业微信的功能出现异常 + /// + [JsonProperty("is_necessary")] + [JsonPropertyName("is_necessary")] + public int IsNecessary { get; set; } = default!; + /// + /// IP涉及到的功能的描述信息 + /// + [JsonProperty("description")] + [JsonPropertyName("description")] + public string Description { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatIpModel.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatIpModel.cs new file mode 100644 index 000000000..ab8e0e4bc --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatIpModel.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Security.Models; +public class WeChatIpModel +{ + /// + /// ip地址 + /// + [JsonProperty("ip")] + [JsonPropertyName("ip")] + public string Ip { get; set; } = default!; + /// + /// 协议 如TCP UDP + /// + [JsonProperty("protocol")] + [JsonPropertyName("protocol")] + public string Protocol { get; set; } = default!; + /// + /// 端口号列表 + /// + [JsonProperty("port")] + [JsonPropertyName("port")] + public List Port { get; set; } = new List(); + /// + /// 是否必要,0-否 1-是, 如果必要的域名或IP被拦截,将导致企业微信的功能出现异常 + /// + [JsonProperty("is_necessary")] + [JsonPropertyName("is_necessary")] + public int IsNecessary { get; set; } = default!; + /// + /// IP涉及到的功能的描述信息 + /// + [JsonProperty("description")] + [JsonPropertyName("description")] + public string Description { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainModel.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainModel.cs new file mode 100644 index 000000000..dd84447fa --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainModel.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Security.Models; +public class WeChatServerDomainModel +{ + /// + /// 域名列表 + /// + [JsonProperty("domain_list")] + [JsonPropertyName("domain_list")] + public List Domains { get; } + /// + /// Ip列表 + /// + [JsonProperty("ip_list")] + [JsonPropertyName("ip_list")] + public List Ips { get; } + public WeChatServerDomainModel(List domains, List ips) + { + Domains = domains; + Ips = ips; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainResponse.cs new file mode 100644 index 000000000..3d65b35ba --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainResponse.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace LINGYUN.Abp.WeChat.Work.Security.Models; +public class WeChatServerDomainResponse : WeChatWorkResponse +{ + /// + /// 域名列表 + /// + [JsonProperty("domain_list")] + public List Domains { get; set; } = new List(); + /// + /// Ip列表 + /// + [JsonProperty("ip_list")] + public List Ips { get; set; } = new List(); + public WeChatServerDomainModel ToServerDomain() + { + ThrowIfNotSuccess(); + return new WeChatServerDomainModel(Domains, Ips); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/WeChatWorkServerProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/WeChatWorkServerProvider.cs new file mode 100644 index 000000000..fcca18361 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/WeChatWorkServerProvider.cs @@ -0,0 +1,32 @@ +using LINGYUN.Abp.WeChat.Work.Security.Models; +using LINGYUN.Abp.WeChat.Work.Token; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.WeChat.Work.Security; +public class WeChatWorkServerProvider : IWeChatWorkServerProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkServerProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task GetWeChatServerAsync(CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.GetServerDomainIpAsync(token.AccessToken, cancellationToken); + var serverDomainResponse = await response.DeserializeObjectAsync(); + + return serverDomainResponse.ToServerDomain(); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/IWeChatWorkTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/IWeChatWorkTagProvider.cs new file mode 100644 index 000000000..64e97fb56 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/IWeChatWorkTagProvider.cs @@ -0,0 +1,93 @@ +using LINGYUN.Abp.WeChat.Work.Tags.Request; +using LINGYUN.Abp.WeChat.Work.Tags.Response; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.Tags; +/// +/// 标签管理接口 +/// +public interface IWeChatWorkTagProvider +{ + /// + /// 创建标签 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/90210 + /// + /// 创建标签请求参数 + /// + /// 创建标签响应参数 + Task CreateAsync( + WeChatWorkTagCreateRequest request, + CancellationToken cancellationToken = default); + /// + /// 更新标签名字 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/90211 + /// + /// 更新标签名字请求参数 + /// + /// 更新标签名字响应参数 + Task UpdateAsync( + WeChatWorkTagUpdateRequest request, + CancellationToken cancellationToken = default); + /// + /// 删除标签 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/90212 + /// + /// 删除标签请求参数 + /// + /// 删除标签响应参数 + Task DeleteAsync( + WeChatWorkGetTagRequest request, + CancellationToken cancellationToken = default); + /// + /// 获取标签成员 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/90213 + /// + /// 获取标签成员请求参数 + /// + /// 获取标签成员响应参数 + Task GetMemberAsync( + WeChatWorkGetTagRequest request, + CancellationToken cancellationToken = default); + /// + /// 增加标签成员 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/90214 + /// + /// 增加标签成员请求参数 + /// + /// 增加标签成员响应参数 + Task AddMemberAsync( + WeChatWorkTagChangeMemberRequest request, + CancellationToken cancellationToken = default); + /// + /// 删除标签成员 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/90214 + /// + /// 增加标签成员请求参数 + /// + /// 增加标签成员响应参数 + Task DeleteMemberAsync( + WeChatWorkTagChangeMemberRequest request, + CancellationToken cancellationToken = default); + /// + /// 获取标签列表 + /// + /// + /// 详情见:https://developer.work.weixin.qq.com/document/path/90214 + /// + /// + /// 获取标签列表响应参数 + Task GetListAsync(CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagInfo.cs new file mode 100644 index 000000000..24cbeffb8 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagInfo.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Tags.Models; +/// +/// 标签 +/// +public class TagInfo +{ + /// + /// 标签id + /// + [NotNull] + [JsonProperty("tagid")] + [JsonPropertyName("tagid")] + public int TagId { get; set; } + /// + /// 标签名 + /// + [NotNull] + [JsonProperty("tagname")] + [JsonPropertyName("tagname")] + public string TagName { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagUserInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagUserInfo.cs new file mode 100644 index 000000000..720ba0b1b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagUserInfo.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Tags.Models; +/// +/// 标签中包含的成员 +/// +public class TagUserInfo +{ + /// + /// 成员账号 + /// + [NotNull] + [JsonProperty("userid")] + [JsonPropertyName("userid")] + public string UserId { get; set; } + /// + /// 成员名称 + /// + [NotNull] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string Name { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkGetTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkGetTagRequest.cs new file mode 100644 index 000000000..f1c4fb22a --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkGetTagRequest.cs @@ -0,0 +1,23 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Tags.Request; +/// +/// 获取标签请求参数 +/// +public class WeChatWorkGetTagRequest +{ + /// + /// 标签id + /// + [NotNull] + [JsonProperty("tagid")] + [JsonPropertyName("tagid")] + public int TagId { get; set; } + public WeChatWorkGetTagRequest(int tagId) + { + TagId = Check.Positive(tagId, nameof(tagId)); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagChangeMemberRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagChangeMemberRequest.cs new file mode 100644 index 000000000..bedfa721a --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagChangeMemberRequest.cs @@ -0,0 +1,57 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Tags.Request; +/// +/// 标签成员变更请求参数 +/// +public class WeChatWorkTagChangeMemberRequest +{ + /// + /// 标签id,非负整型,指定此参数时新增的标签会生成对应的标签id,不指定时则以目前最大的id自增。 + /// + [NotNull] + [JsonProperty("tagid")] + [JsonPropertyName("tagid")] + public int TagId { get; set; } + /// + /// 企业成员ID列表,注意:userlist、partylist不能同时为空,单次请求个数不超过1000 + /// + [CanBeNull] + [JsonProperty("userlist")] + [JsonPropertyName("userlist")] + public List Users { get; set; } + /// + /// 企业部门ID列表,注意:userlist、partylist不能同时为空,单次请求个数不超过100 + /// + [CanBeNull] + [JsonProperty("partylist")] + [JsonPropertyName("partylist")] + public List Parts { get; set; } + public WeChatWorkTagChangeMemberRequest( + int tagId, + List users = null, + List parts = null) + { + TagId = Check.Positive(tagId, nameof(tagId)); + Users = users; + Parts = parts; + + if (users == null && parts == null) + { + throw new ArgumentNullException("users/parts", "userlist、partylist不能同时为空!"); + } + if (users?.Count > 1000) + { + throw new ArgumentOutOfRangeException(nameof(users), "企业成员ID列表单次请求个数不超过1000!"); + } + if (parts?.Count > 100) + { + throw new ArgumentOutOfRangeException(nameof(users), "企业部门ID列表单次请求个数不超过100!"); + } + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagCreateRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagCreateRequest.cs new file mode 100644 index 000000000..27a6d6dcd --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagCreateRequest.cs @@ -0,0 +1,33 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Tags.Request; +/// +/// 创建标签请求参数 +/// +public class WeChatWorkTagCreateRequest +{ + /// + /// 标签id,非负整型,指定此参数时新增的标签会生成对应的标签id,不指定时则以目前最大的id自增。 + /// + [NotNull] + [JsonProperty("tagid")] + [JsonPropertyName("tagid")] + public int? TagId { get; set; } + /// + /// 标签名称,长度限制为32个字以内(汉字或英文字母),标签名不可与其他标签重名。 + /// + [NotNull] + [StringLength(32)] + [JsonProperty("tagname")] + [JsonPropertyName("tagname")] + public string TagName { get; set; } + public WeChatWorkTagCreateRequest(string tagName, int? tagId = null) + { + TagName = Check.NotNullOrWhiteSpace(tagName, nameof(tagName), 32); + TagId = tagId; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagUpdateRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagUpdateRequest.cs new file mode 100644 index 000000000..902d70e4b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagUpdateRequest.cs @@ -0,0 +1,33 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using Volo.Abp; + +namespace LINGYUN.Abp.WeChat.Work.Tags.Request; +/// +/// 更新标签请求参数 +/// +public class WeChatWorkTagUpdateRequest +{ + /// + /// 标签id,非负整型,指定此参数时新增的标签会生成对应的标签id,不指定时则以目前最大的id自增。 + /// + [NotNull] + [JsonProperty("tagid")] + [JsonPropertyName("tagid")] + public int TagId { get; set; } + /// + /// 标签名称,长度限制为32个字以内(汉字或英文字母),标签名不可与其他标签重名。 + /// + [NotNull] + [StringLength(32)] + [JsonProperty("tagname")] + [JsonPropertyName("tagname")] + public string TagName { get; set; } + public WeChatWorkTagUpdateRequest(int tagId, string tagName) + { + TagId = Check.Positive(tagId, nameof(tagId)); + TagName = Check.NotNullOrWhiteSpace(tagName, nameof(tagName), 32); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagChangeMemberResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagChangeMemberResponse.cs new file mode 100644 index 000000000..d0e6cd002 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagChangeMemberResponse.cs @@ -0,0 +1,26 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Tags.Response; +/// +/// 标签成员变更响应参数 +/// +public class WeChatWorkTagChangeMemberResponse : WeChatWorkResponse +{ + /// + /// 若部分userid非法,则返回 + /// + [CanBeNull] + [JsonProperty("invalidlist")] + [JsonPropertyName("invalidlist")] + public string InvalidList { get; set; } + /// + /// 若部分partylist非法,则返回 + /// + [CanBeNull] + [JsonProperty("invalidparty")] + [JsonPropertyName("invalidparty")] + public List InvalidPart { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagCreateResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagCreateResponse.cs new file mode 100644 index 000000000..8020606c2 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagCreateResponse.cs @@ -0,0 +1,21 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Tags.Response; +/// +/// 创建标签响应参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/90210 +/// +public class WeChatWorkTagCreateResponse : WeChatWorkResponse +{ + /// + /// 标签id + /// + [NotNull] + [JsonProperty("tagid")] + [JsonPropertyName("tagid")] + public int TagId { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagListResponse.cs new file mode 100644 index 000000000..ed905495d --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagListResponse.cs @@ -0,0 +1,23 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.Tags.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Tags.Response; +/// +/// 获取标签列表响应参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/90216 +/// +public class WeChatWorkTagListResponse : WeChatWorkResponse +{ + /// + /// 标签列表 + /// + [NotNull] + [JsonProperty("taglist")] + [JsonPropertyName("taglist")] + public List Tags { get; set; } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagMemberInfoResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagMemberInfoResponse.cs new file mode 100644 index 000000000..fce599a69 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagMemberInfoResponse.cs @@ -0,0 +1,37 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.WeChat.Work.Tags.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LINGYUN.Abp.WeChat.Work.Tags.Response; +/// +/// 标签成员响应参数 +/// +/// +/// 详情见: https://developer.work.weixin.qq.com/document/path/90213 +/// +public class WeChatWorkTagMemberInfoResponse : WeChatWorkResponse +{ + /// + /// 标签名 + /// + [NotNull] + [JsonProperty("tagname")] + [JsonPropertyName("tagname")] + public string TagName { get; set; } + /// + /// 标签中包含的成员列表 + /// + [NotNull] + [JsonProperty("userlist")] + [JsonPropertyName("userlist")] + public List Users { get; set; } + /// + /// 标签中包含的部门id列表 + /// + [NotNull] + [JsonProperty("partylist")] + [JsonPropertyName("partylist")] + public List Parts { get; set; } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/WeChatWorkTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/WeChatWorkTagProvider.cs new file mode 100644 index 000000000..3eb40560a --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/WeChatWorkTagProvider.cs @@ -0,0 +1,89 @@ +using LINGYUN.Abp.WeChat.Work.Features; +using LINGYUN.Abp.WeChat.Work.Tags.Request; +using LINGYUN.Abp.WeChat.Work.Tags.Response; +using LINGYUN.Abp.WeChat.Work.Token; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.WeChat.Work.Tags; + +[RequiresFeature(WeChatWorkFeatureNames.Enable)] +public class WeChatWorkTagProvider : IWeChatWorkTagProvider, ISingletonDependency +{ + protected IHttpClientFactory HttpClientFactory { get; } + protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; } + + public WeChatWorkTagProvider( + IHttpClientFactory httpClientFactory, + IWeChatWorkTokenProvider weChatWorkTokenProvider) + { + HttpClientFactory = httpClientFactory; + WeChatWorkTokenProvider = weChatWorkTokenProvider; + } + + public async virtual Task AddMemberAsync(WeChatWorkTagChangeMemberRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.AddTagMemberAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } + + public async virtual Task CreateAsync(WeChatWorkTagCreateRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.CreateTagAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } + + public async virtual Task DeleteAsync(WeChatWorkGetTagRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.DeleteTagAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } + + public async virtual Task DeleteMemberAsync(WeChatWorkTagChangeMemberRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.DeleteTagMemberAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } + + public async virtual Task GetListAsync(CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.GetTagListAsync(token.AccessToken, cancellationToken); + return await response.DeserializeObjectAsync(); + } + + public async virtual Task GetMemberAsync(WeChatWorkGetTagRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.GetTagMemberAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } + + public async virtual Task UpdateAsync(WeChatWorkTagUpdateRequest request, CancellationToken cancellationToken = default) + { + var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken); + var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + + using var response = await client.UpdateTagAsync(token.AccessToken, request, cancellationToken); + return await response.DeserializeObjectAsync(); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/HttpContentBuildHelper.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/WeChatWorkHttpContentBuildHelper.cs similarity index 96% rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/HttpContentBuildHelper.cs rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/WeChatWorkHttpContentBuildHelper.cs index 190a1ff89..1542c47da 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/HttpContentBuildHelper.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/WeChatWorkHttpContentBuildHelper.cs @@ -5,7 +5,7 @@ using System.Text; namespace LINGYUN.Abp.WeChat.Work.Utils; -internal static class HttpContentBuildHelper +public static class WeChatWorkHttpContentBuildHelper { public static HttpContent BuildUploadMediaContent( string mediaName, diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/WeChatWorkRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/WeChatWorkRequest.cs index d63e96a2d..698440684 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/WeChatWorkRequest.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/WeChatWorkRequest.cs @@ -7,6 +7,13 @@ public abstract class WeChatWorkRequest { public virtual string SerializeToJson() { + Validate(); + return WeChatObjectSerializeExtensions.SerializeToJson(this); } + + protected virtual void Validate() + { + + } } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/Microsoft/Extensions/DependencyInjection/IHttpClientFactoryWeChatWorkExtensions.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/Microsoft/Extensions/DependencyInjection/IHttpClientFactoryWeChatWorkExtensions.cs new file mode 100644 index 000000000..db1ef7009 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/Microsoft/Extensions/DependencyInjection/IHttpClientFactoryWeChatWorkExtensions.cs @@ -0,0 +1,61 @@ +using LINGYUN.Abp.WeChat.Work; +using System; +using System.Net.Http; + +namespace Microsoft.Extensions.DependencyInjection; +public static class IHttpClientFactoryWeChatWorkExtensions +{ + internal static IServiceCollection AddApiClient(this IServiceCollection services, Action configureClient = null) + { + services.AddHttpClient(AbpWeChatWorkGlobalConsts.ApiClient, + options => + { + options.BaseAddress = new Uri("https://qyapi.weixin.qq.com"); + + configureClient?.Invoke(options); + }); + + return services; + } + + public static HttpClient CreateWeChatWorkApiClient(this IHttpClientFactory httpClientFactory) + { + return httpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient); + } + + internal static IServiceCollection AddOAuthClient(this IServiceCollection services, Action configureClient = null) + { + services.AddHttpClient(AbpWeChatWorkGlobalConsts.OAuthClient, + options => + { + options.BaseAddress = new Uri("https://open.weixin.qq.com"); + + configureClient?.Invoke(options); + }); + + return services; + } + + public static HttpClient CreateWeChatWorkOAuthClient(this IHttpClientFactory httpClientFactory) + { + return httpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.OAuthClient); + } + + internal static IServiceCollection AddLoginClient(this IServiceCollection services, Action configureClient = null) + { + services.AddHttpClient(AbpWeChatWorkGlobalConsts.LoginClient, + options => + { + options.BaseAddress = new Uri("https://login.work.weixin.qq.com"); + + configureClient?.Invoke(options); + }); + + return services; + } + + public static HttpClient CreateWeChatWorkLoginClient(this IHttpClientFactory httpClientFactory) + { + return httpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.LoginClient); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/Newtonsoft/Json/ControlNewtonsoftJsonConverter.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/Newtonsoft/Json/ControlNewtonsoftJsonConverter.cs new file mode 100644 index 000000000..ba9bc173b --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/Newtonsoft/Json/ControlNewtonsoftJsonConverter.cs @@ -0,0 +1,47 @@ +using LINGYUN.Abp.WeChat.Work.Approvals.Models; +using Newtonsoft.Json.Linq; +using System; + +namespace Newtonsoft.Json; +internal class ControlNewtonsoftJsonConverter : JsonConverter +{ + public override bool CanWrite => true; + + public override void WriteJson(JsonWriter writer, Control value, JsonSerializer serializer) + { + writer.WriteStartObject(); + + if (value.Property != null) + { + writer.WritePropertyName("property"); + serializer.Serialize(writer, value.Property); + } + + if (value.Config != null) + { + writer.WritePropertyName("config"); + serializer.Serialize(writer, value.Config, value.Config.GetType()); + } + + writer.WriteEndObject(); + } + + public override Control ReadJson(JsonReader reader, Type objectType, Control existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var jObject = JObject.Load(reader); + + var control = new Control(); + + if (jObject.TryGetValue("property", out var propertyToken)) + { + control.Property = propertyToken.ToObject(serializer); + } + // 根据 Control 类型动态反序列化 Config + if (jObject.TryGetValue("config", out var configToken) && configToken.Type != JTokenType.Null) + { + control.Config = ControlConfigFactory.CreateConfig(control.Property.Control, configToken); + } + + return control; + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Approval.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Approval.cs new file mode 100644 index 000000000..afed091c8 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Approval.cs @@ -0,0 +1,128 @@ +using LINGYUN.Abp.WeChat.Work.Approvals.Request; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + public async static Task ApplyEventAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkApplyEventRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/oa/applyevent"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task CreateTemplateAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkCreateTemplateRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/oa/approval/create_template"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GetApprovalDetailAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetApprovalDetailRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/oa/getapprovaldetail"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GetApprovalInfoAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetApprovalInfoRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/oa/getapprovalinfo"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task GetTemplateAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetTemplateRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/oa/gettemplatedetail"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + + public async static Task UpdateTemplateAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkCreateTemplateRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/oa/approval/update_template"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Media.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Media.cs index fb4c2e77e..8ab66dcdc 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Media.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Media.cs @@ -41,7 +41,7 @@ internal static partial class HttpClientWeChatWorkRequestExtensions HttpMethod.Post, urlBuilder.ToString()) { - Content = HttpContentBuildHelper.BuildUploadMediaContent("media", fileBytes, request.Content.FileName) + Content = WeChatWorkHttpContentBuildHelper.BuildUploadMediaContent("media", fileBytes, request.Content.FileName) }; return await client.SendAsync(httpRequest, cancellationToken); @@ -61,7 +61,7 @@ internal static partial class HttpClientWeChatWorkRequestExtensions HttpMethod.Post, urlBuilder.ToString()) { - Content = HttpContentBuildHelper.BuildUploadMediaContent("file", fileBytes, request.Content.FileName) + Content = WeChatWorkHttpContentBuildHelper.BuildUploadMediaContent("file", fileBytes, request.Content.FileName) }; return await client.SendAsync(httpRequest, cancellationToken); diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Message.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Message.cs index 6b354d260..a54c3fa64 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Message.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Message.cs @@ -64,4 +64,24 @@ internal static partial class HttpClientWeChatWorkRequestExtensions return await client.SendAsync(httpRequest, cancellationToken); } + + public async static Task SendMessageAsync( + this HttpMessageInvoker client, + string webhookKey, + WeChatWorkWebhookMessage request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/webhook/send"); + urlBuilder.AppendFormat("?key={0}", webhookKey); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } } diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Security.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Security.cs new file mode 100644 index 000000000..44d84925e --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Security.cs @@ -0,0 +1,17 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + public async static Task GetServerDomainIpAsync( + this HttpMessageInvoker client, + string accessToken, + CancellationToken cancellationToken = default) + { + var httpRequest = new HttpRequestMessage(HttpMethod.Get, + $"/cgi-bin/security/get_server_domain_ip?access_token={accessToken}"); ; + + return await client.SendAsync(httpRequest, cancellationToken); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tag.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tag.cs new file mode 100644 index 000000000..e9162de12 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tag.cs @@ -0,0 +1,134 @@ +using LINGYUN.Abp.WeChat.Work.Tags.Request; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http; +internal static partial class HttpClientWeChatWorkRequestExtensions +{ + public async static Task AddTagMemberAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkTagChangeMemberRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/tag/addtagusers"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + public async static Task CreateTagAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkTagCreateRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/tag/create"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + public async static Task DeleteTagAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetTagRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/tag/delete"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + urlBuilder.AppendFormat("&tagid={0}", request.TagId); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Get, + urlBuilder.ToString()); + + return await client.SendAsync(httpRequest, cancellationToken); + } + public async static Task DeleteTagMemberAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkTagChangeMemberRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/tag/deltagusers"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } + public async static Task GetTagListAsync( + this HttpMessageInvoker client, + string accessToken, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/tag/list"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Get, + urlBuilder.ToString()); + + return await client.SendAsync(httpRequest, cancellationToken); + } + public async static Task GetTagMemberAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkGetTagRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/tag/get"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + urlBuilder.AppendFormat("&tagid={0}", request.TagId); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Get, + urlBuilder.ToString()); + + return await client.SendAsync(httpRequest, cancellationToken); + } + public async static Task UpdateTagAsync( + this HttpMessageInvoker client, + string accessToken, + WeChatWorkTagUpdateRequest request, + CancellationToken cancellationToken = default) + { + var urlBuilder = new StringBuilder(); + urlBuilder.Append("/cgi-bin/tag/update"); + urlBuilder.AppendFormat("?access_token={0}", accessToken); + + var httpRequest = new HttpRequestMessage( + HttpMethod.Post, + urlBuilder.ToString()) + { + Content = new StringContent(request.SerializeToJson()) + }; + + return await client.SendAsync(httpRequest, cancellationToken); + } +} diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Text/Json/Serialization/ControlSystemTextJsonConverter.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Text/Json/Serialization/ControlSystemTextJsonConverter.cs new file mode 100644 index 000000000..344250745 --- /dev/null +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Text/Json/Serialization/ControlSystemTextJsonConverter.cs @@ -0,0 +1,42 @@ +using LINGYUN.Abp.WeChat.Work.Approvals.Models; + +namespace System.Text.Json.Serialization; +internal class ControlSystemTextJsonConverter : JsonConverter +{ + public override Control Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var doc = JsonDocument.ParseValue(ref reader); + var root = doc.RootElement; + + var control = new Control(); + + // 反序列化基本字段 + if (root.TryGetProperty("property", out var propertyElement)) + { + control.Property = JsonSerializer.Deserialize(propertyElement.GetRawText(), options); + } + // 根据 Control 类型动态反序列化 Config + if (root.TryGetProperty("config", out var configElement) && configElement.ValueKind != JsonValueKind.Null) + { + control.Config = ControlConfigFactory.CreateConfig(control.Property.Control, configElement); + } + + return control; + } + + public override void Write(Utf8JsonWriter writer, Control value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WritePropertyName("property"); + JsonSerializer.Serialize(writer, value.Property, options); + + if (value.Config != null) + { + writer.WritePropertyName("config"); + JsonSerializer.Serialize(writer, value.Config, value.Config.GetType(), options); + } + + writer.WriteEndObject(); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN.Abp.Account.Application.csproj b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN.Abp.Account.Application.csproj index eda58b75f..13f7a3fe6 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN.Abp.Account.Application.csproj +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN.Abp.Account.Application.csproj @@ -25,7 +25,7 @@ - + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs index f5590e8f8..ef781343e 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs @@ -1,5 +1,5 @@ -using LINGYUN.Abp.Account.Emailing; -using LINGYUN.Abp.Account.Emailing.Localization; +using LINGYUN.Abp.Account.Security; +using LINGYUN.Abp.Account.Security.Localization; using LINGYUN.Abp.Identity; using LINGYUN.Abp.WeChat.MiniProgram; using Microsoft.Extensions.DependencyInjection; @@ -16,7 +16,7 @@ namespace LINGYUN.Abp.Account; [DependsOn( typeof(Volo.Abp.Account.AbpAccountApplicationModule), typeof(AbpAccountApplicationContractsModule), - typeof(AbpAccountEmailingModule), + typeof(AbpAccountSecurityModule), typeof(AbpIdentityDomainModule), typeof(AbpBlobStoringModule), typeof(AbpWeChatMiniProgramModule))] @@ -45,7 +45,7 @@ public class AbpAccountApplicationModule : AbpModule { options.Resources .Get() - .AddBaseTypes(typeof(AccountEmailingResource)); + .AddBaseTypes(typeof(AccountSecurityResource)); }); } } diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs index e27278844..c4f87f336 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs @@ -1,4 +1,4 @@ -using LINGYUN.Abp.Account.Emailing; +using LINGYUN.Abp.Account.Security; using LINGYUN.Abp.Identity; using LINGYUN.Abp.Identity.Security; using LINGYUN.Abp.Identity.Settings; @@ -123,7 +123,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi var code = TotpService.GenerateCode(Encoding.Unicode.GetBytes(securityToken), securityTokenCacheKey); securityTokenCacheItem = new SecurityTokenCacheItem(code.ToString(), securityToken); - await SecurityCodeSender.SendSmsCodeAsync( + await SecurityCodeSender.SendAsync( input.PhoneNumber, securityTokenCacheItem.Token, template); await SecurityTokenCache @@ -227,7 +227,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi // 生成二次认证码 var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider); // 发送短信验证码 - await SecurityCodeSender.SendSmsCodeAsync(input.PhoneNumber, code, template); + await SecurityCodeSender.SendAsync(input.PhoneNumber, code, template); // 缓存这个手机号的记录,防重复 securityTokenCacheItem = new SecurityTokenCacheItem(code, user.SecurityStamp); await SecurityTokenCache @@ -294,7 +294,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsUserSignin); // 发送登录验证码短信 - await SecurityCodeSender.SendSmsCodeAsync(input.PhoneNumber, code, template); + await SecurityCodeSender.SendAsync(input.PhoneNumber, code, template); // 缓存登录验证码状态,防止同一手机号重复发送 securityTokenCacheItem = new SecurityTokenCacheItem(code, user.SecurityStamp); await SecurityTokenCache @@ -307,7 +307,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi public async virtual Task SendEmailSigninCodeAsync(SendEmailSigninCodeDto input) { - var sender = LazyServiceProvider.LazyGetRequiredService(); + var sender = LazyServiceProvider.LazyGetRequiredService(); var user = await UserManager.FindByEmailAsync(input.EmailAddress); @@ -322,7 +322,7 @@ public class AccountAppService : AccountApplicationServiceBase, IAccountAppServi var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultEmailProvider); - await sender.SendMailLoginVerifyCodeAsync(code, user.UserName, user.Email); + await sender.SendLoginCodeAsync(code, user.UserName, user.Email); } public async virtual Task> GetTwoFactorProvidersAsync(GetTwoFactorProvidersInput input) diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/IAccountSmsSecurityCodeSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/IAccountSmsSecurityCodeSender.cs deleted file mode 100644 index d5c89d474..000000000 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/IAccountSmsSecurityCodeSender.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Account; - -public interface IAccountSmsSecurityCodeSender -{ - Task SendSmsCodeAsync( - string phone, - string token, - string template, // 传递模板号 - CancellationToken cancellation = default); -} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/MyProfileAppService.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/MyProfileAppService.cs index a0cb80856..a04a78274 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/MyProfileAppService.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/MyProfileAppService.cs @@ -1,4 +1,4 @@ -using LINGYUN.Abp.Account.Emailing; +using LINGYUN.Abp.Account.Security; using LINGYUN.Abp.Identity; using LINGYUN.Abp.Identity.Security; using LINGYUN.Abp.Identity.Session; @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using System; -using System.IO; using System.Linq; using System.Net; using System.Text; @@ -20,7 +19,6 @@ using Volo.Abp.Caching; using Volo.Abp.Content; using Volo.Abp.Data; using Volo.Abp.Identity; -using Volo.Abp.Security.Claims; using Volo.Abp.Settings; using Volo.Abp.Users; using IIdentitySessionRepository = LINGYUN.Abp.Identity.IIdentitySessionRepository; @@ -31,23 +29,21 @@ namespace LINGYUN.Abp.Account; public class MyProfileAppService : AccountApplicationServiceBase, IMyProfileAppService { protected IDistributedCache SecurityTokenCache { get; } - protected IAccountSmsSecurityCodeSender SecurityCodeSender { get; } protected Identity.IIdentityUserRepository UserRepository { get; } protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } + protected IAccountSmsSecurityCodeSender SmsSecurityCodeSender => LazyServiceProvider.LazyGetRequiredService(); + protected IAccountEmailSecurityCodeSender EmailSecurityCodeSender => LazyServiceProvider.LazyGetRequiredService(); protected IAuthenticatorUriGenerator AuthenticatorUriGenerator => LazyServiceProvider.LazyGetRequiredService(); - protected IIdentitySessionManager IdentitySessionManager => LazyServiceProvider.LazyGetRequiredService(); protected IIdentitySessionRepository IdentitySessionRepository => LazyServiceProvider.LazyGetRequiredService(); protected IUserPictureProvider UserPictureProvider => LazyServiceProvider.LazyGetRequiredService(); public MyProfileAppService( Identity.IIdentityUserRepository userRepository, - IAccountSmsSecurityCodeSender securityCodeSender, IdentitySecurityLogManager identitySecurityLogManager, IDistributedCache securityTokenCache) { UserRepository = userRepository; - SecurityCodeSender = securityCodeSender; IdentitySecurityLogManager = identitySecurityLogManager; SecurityTokenCache = securityTokenCache; @@ -147,7 +143,7 @@ public class MyProfileAppService : AccountApplicationServiceBase, IMyProfileAppS var template = await SettingProvider.GetOrNullAsync(Identity.Settings.IdentitySettingNames.User.SmsPhoneNumberConfirmed); var token = await UserManager.GenerateChangePhoneNumberTokenAsync(user, input.NewPhoneNumber); // 发送验证码 - await SecurityCodeSender.SendSmsCodeAsync(input.NewPhoneNumber, token, template); + await SmsSecurityCodeSender.SendAsync(input.NewPhoneNumber, token, template); securityTokenCacheItem = new SecurityTokenCacheItem(token, user.ConcurrencyStamp); await SecurityTokenCache @@ -191,9 +187,8 @@ public class MyProfileAppService : AccountApplicationServiceBase, IMyProfileAppS var token = await UserManager.GenerateEmailConfirmationTokenAsync(user); var confirmToken = WebUtility.UrlEncode(token); - var sender = LazyServiceProvider.LazyGetRequiredService(); - await sender.SendEmailConfirmLinkAsync( + await EmailSecurityCodeSender.SendConfirmLinkAsync( user.Id, user.Email, confirmToken, diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN.Abp.Account.Emailing.csproj b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN.Abp.Account.Emailing.csproj index 01b39652a..64c007a84 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN.Abp.Account.Emailing.csproj +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN.Abp.Account.Emailing.csproj @@ -10,6 +10,7 @@ false false false + false diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/AbpAccountEmailingModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/AbpAccountEmailingModule.cs index a8328c793..13170d5a4 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/AbpAccountEmailingModule.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/AbpAccountEmailingModule.cs @@ -1,4 +1,5 @@ using LINGYUN.Abp.Account.Emailing.Localization; +using System; using Volo.Abp.Emailing; using Volo.Abp.Localization; using Volo.Abp.Modularity; @@ -10,6 +11,7 @@ namespace LINGYUN.Abp.Account.Emailing; [DependsOn( typeof(AbpEmailingModule), typeof(AbpUiNavigationModule))] +[Obsolete("This module has been deprecated. Please use AbpAccountSecurityModule.")] public class AbpAccountEmailingModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailConfirmSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailConfirmSender.cs index 6a2f693f4..4a233f340 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailConfirmSender.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailConfirmSender.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; namespace LINGYUN.Abp.Account.Emailing; +[Obsolete("This interface has been deprecated. Please use LINGYUN.Abp.Account.Security.IAccountEmailSecurityCodeSender.")] public interface IAccountEmailConfirmSender { Task SendEmailConfirmLinkAsync( diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailVerifySender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailVerifySender.cs index 10c898dbc..2ada5d647 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailVerifySender.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Emailing/LINGYUN/Abp/Account/Emailing/IAccountEmailVerifySender.cs @@ -1,7 +1,9 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; namespace LINGYUN.Abp.Account.Emailing; +[Obsolete("This interface has been deprecated. Please use LINGYUN.Abp.Account.Security.IAccountEmailSecurityCodeSender.")] public interface IAccountEmailVerifySender { Task SendMailLoginVerifyCodeAsync( diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/FodyWeavers.xml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/FodyWeavers.xsd b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/LINGYUN.Abp.Account.Security.Aliyun.csproj b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/LINGYUN.Abp.Account.Security.Aliyun.csproj new file mode 100644 index 000000000..6171d68a8 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/LINGYUN.Abp.Account.Security.Aliyun.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0 + LINGYUN.Abp.Account.Security.Aliyun + LINGYUN.Abp.Account.Security.Aliyun + false + false + false + + + + + + + + + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/LINGYUN/Abp/Account/Security/Aliyun/AbpAccountSecurityAliyunModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/LINGYUN/Abp/Account/Security/Aliyun/AbpAccountSecurityAliyunModule.cs new file mode 100644 index 000000000..fdf5d3b2f --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/LINGYUN/Abp/Account/Security/Aliyun/AbpAccountSecurityAliyunModule.cs @@ -0,0 +1,11 @@ +using LINGYUN.Abp.Sms.Aliyun; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Account.Security.Aliyun; + +[DependsOn( + typeof(AbpAccountSecurityModule), + typeof(AbpAliyunSmsModule))] +public class AbpAccountSecurityAliyunModule : AbpModule +{ +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/LINGYUN/Abp/Account/Security/Aliyun/AliyunAccountSmsSecurityCodeSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/LINGYUN/Abp/Account/Security/Aliyun/AliyunAccountSmsSecurityCodeSender.cs new file mode 100644 index 000000000..8d9517194 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security.Aliyun/LINGYUN/Abp/Account/Security/Aliyun/AliyunAccountSmsSecurityCodeSender.cs @@ -0,0 +1,32 @@ +using LINGYUN.Abp.Sms.Aliyun; +using Microsoft.Extensions.DependencyInjection; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Account.Security.Aliyun; + +[Dependency(ServiceLifetime.Transient, ReplaceServices = true)] +[ExposeServices(typeof(IAccountSmsSecurityCodeSender), typeof(AliyunAccountSmsSecurityCodeSender))] +public class AliyunAccountSmsSecurityCodeSender : IAccountSmsSecurityCodeSender +{ + protected IAliyunSmsVerifyCodeSender SmsVerifyCodeSender { get; } + public AliyunAccountSmsSecurityCodeSender( + IAliyunSmsVerifyCodeSender smsVerifyCodeSender) + { + SmsVerifyCodeSender = smsVerifyCodeSender; + } + + public async virtual Task SendAsync( + string phone, + string code, + string templateCode, + CancellationToken cancellation = default) + { + // TODO: 传递验证码有效期 + await SmsVerifyCodeSender.SendAsync( + new SmsVerifyCodeMessage(phone, + new SmsVerifyCodeMessageParam(code, "5"), + templateCode: templateCode)); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xsd b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN.Abp.Account.Security.csproj b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN.Abp.Account.Security.csproj new file mode 100644 index 000000000..6ac829797 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN.Abp.Account.Security.csproj @@ -0,0 +1,29 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0 + LINGYUN.Abp.Account.Security + LINGYUN.Abp.Account.Security + false + false + false + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AbpAccountSecurityModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AbpAccountSecurityModule.cs new file mode 100644 index 000000000..37fd2513d --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AbpAccountSecurityModule.cs @@ -0,0 +1,31 @@ +using LINGYUN.Abp.Account.Security.Localization; +using Volo.Abp.Emailing; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.Sms; +using Volo.Abp.UI.Navigation; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.Account.Security; + +[DependsOn( + typeof(AbpEmailingModule), + typeof(AbpSmsModule), + typeof(AbpUiNavigationModule))] +public class AbpAccountSecurityModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/LINGYUN/Abp/Account/Security/Localization/Resources"); + }); + } +} \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountEmailSecurityCodeSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountEmailSecurityCodeSender.cs new file mode 100644 index 000000000..64e16fb20 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountEmailSecurityCodeSender.cs @@ -0,0 +1,135 @@ +using LINGYUN.Abp.Account.Security.Localization; +using LINGYUN.Abp.Account.Security.Templates; +using Microsoft.Extensions.Localization; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using System.Web; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Emailing; +using Volo.Abp.MultiTenancy; +using Volo.Abp.TextTemplating; +using Volo.Abp.UI.Navigation.Urls; + +namespace LINGYUN.Abp.Account.Security; + +[Dependency(ReplaceServices = true)] +[ExposeServices( + typeof(IAccountEmailSecurityCodeSender), + typeof(AccountEmailSecurityCodeSender))] +public class AccountEmailSecurityCodeSender : + IAccountEmailSecurityCodeSender, + ITransientDependency +{ + protected ITemplateRenderer TemplateRenderer { get; } + protected IEmailSender EmailSender { get; } + protected IStringLocalizer StringLocalizer { get; } + protected IAppUrlProvider AppUrlProvider { get; } + protected ICurrentTenant CurrentTenant { get; } + + public AccountEmailSecurityCodeSender( + IEmailSender emailSender, + ICurrentTenant currentTenant, + IAppUrlProvider appUrlProvider, + ITemplateRenderer templateRenderer, + IStringLocalizer accountLocalizer) + { + EmailSender = emailSender; + CurrentTenant = currentTenant; + AppUrlProvider = appUrlProvider; + StringLocalizer = accountLocalizer; + TemplateRenderer = templateRenderer; + } + + public async virtual Task SendLoginCodeAsync( + string code, + string userName, + string emailAddress) + { + var emailContent = await TemplateRenderer.RenderAsync( + AccountEmailTemplates.MailSecurityVerifyLink, + new { code = code, user = userName } + ); + + await EmailSender.SendAsync( + emailAddress, + StringLocalizer["MailSecurityVerify"], + emailContent + ); + } + + public async virtual Task SendConfirmLinkAsync( + Guid userId, + string userEmail, + string confirmToken, + string appName, + string returnUrl = null, + string returnUrlHash = null, + Guid? userTenantId = null) + { + Debug.Assert(CurrentTenant.Id == userTenantId, "This method can only work for current tenant!"); + + var url = await AppUrlProvider.GetUrlAsync(appName, AccountUrlNames.EmailConfirm); + + var link = $"{url}?userId={userId}&{TenantResolverConsts.DefaultTenantKey}={userTenantId}&confirmToken={UrlEncoder.Default.Encode(confirmToken)}"; + + if (!returnUrl.IsNullOrEmpty()) + { + link += "&returnUrl=" + NormalizeReturnUrl(returnUrl); + } + + if (!returnUrlHash.IsNullOrEmpty()) + { + link += "&returnUrlHash=" + returnUrlHash; + } + + var emailContent = await TemplateRenderer.RenderAsync( + AccountEmailTemplates.MailConfirmLink, + new { link = link } + ); + + await EmailSender.SendAsync( + userEmail, + StringLocalizer["EmailConfirm"], + emailContent + ); + } + + protected virtual string NormalizeReturnUrl(string returnUrl) + { + if (returnUrl.IsNullOrEmpty()) + { + return returnUrl; + } + + //Handling openid connect login + if (returnUrl.StartsWith("/connect/authorize/callback", StringComparison.OrdinalIgnoreCase)) + { + if (returnUrl.Contains("?")) + { + var queryPart = returnUrl.Split('?')[1]; + var queryParameters = queryPart.Split('&'); + foreach (var queryParameter in queryParameters) + { + if (queryParameter.Contains("=")) + { + var queryParam = queryParameter.Split('='); + if (queryParam[0] == "redirect_uri") + { + return HttpUtility.UrlDecode(queryParam[1]); + } + } + } + } + } + + if (returnUrl.StartsWith("/connect/authorize?", StringComparison.OrdinalIgnoreCase)) + { + return HttpUtility.UrlEncode(returnUrl); + } + + return returnUrl; + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountSmsSecurityCodeSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountSmsSecurityCodeSender.cs similarity index 85% rename from aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountSmsSecurityCodeSender.cs rename to aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountSmsSecurityCodeSender.cs index 551bb10a2..be9e18d1d 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountSmsSecurityCodeSender.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountSmsSecurityCodeSender.cs @@ -4,29 +4,30 @@ using Volo.Abp; using Volo.Abp.DependencyInjection; using Volo.Abp.Sms; -namespace LINGYUN.Abp.Account; +namespace LINGYUN.Abp.Account.Security; -public class AccountSmsSecurityCodeSender : IAccountSmsSecurityCodeSender, ITransientDependency -{ - protected ISmsSender SmsSender { get; } - - public AccountSmsSecurityCodeSender(ISmsSender smsSender) - { - SmsSender = smsSender; - } - - public async virtual Task SendSmsCodeAsync( - string phone, - string token, - string template, - CancellationToken cancellation = default) - { - Check.NotNullOrWhiteSpace(template, nameof(template)); - - var smsMessage = new SmsMessage(phone, token); - smsMessage.Properties.Add("code", token); - smsMessage.Properties.Add("TemplateCode", template); - - await SmsSender.SendAsync(smsMessage); - } +public class AccountSmsSecurityCodeSender : IAccountSmsSecurityCodeSender, ITransientDependency +{ + protected ISmsSender SmsSender { get; } + + public AccountSmsSecurityCodeSender(ISmsSender smsSender) + { + SmsSender = smsSender; + } + + public async virtual Task SendAsync( + string phone, + string token, + string template, + CancellationToken cancellation = default) + { + Check.NotNullOrWhiteSpace(template, nameof(template)); + + var smsMessage = new SmsMessage(phone, token); + smsMessage.Properties.Add("code", token); + smsMessage.Properties.Add("TemplateCode", template); + smsMessage.Properties.Add("SmsVerifyCode", true); + + await SmsSender.SendAsync(smsMessage); + } } diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountUrlNames.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountUrlNames.cs new file mode 100644 index 000000000..28104f22a --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountUrlNames.cs @@ -0,0 +1,7 @@ +namespace LINGYUN.Abp.Account.Security; + +public static class AccountUrlNames +{ + public const string EmailConfirm = "Abp.Account.EmailConfirm"; + public const string EmailVerifyLogin = "Abp.Account.EmailVerifyLogin"; +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountEmailSecurityCodeSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountEmailSecurityCodeSender.cs new file mode 100644 index 000000000..c2f1f7d9b --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountEmailSecurityCodeSender.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Account.Security; +/// +/// 邮件安全码发送接口 +/// +public interface IAccountEmailSecurityCodeSender +{ + /// + /// 发送邮件登录验证码 + /// + /// 验证码 + /// 用户名 + /// 邮件地址 + /// + Task SendLoginCodeAsync( + string code, + string userName, + string emailAddress); + /// + /// 发送邮件确认链接 + /// + /// 用户Id + /// 用户邮件 + /// 确认Token + /// 应用名称 + /// 回调路径 + /// 回调路径Hash + /// 用户租户Id + /// + Task SendConfirmLinkAsync( + Guid userId, + string userEmail, + string confirmToken, + string appName, + string returnUrl = null, + string returnUrlHash = null, + Guid? userTenantId = null + ); +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountSmsSecurityCodeSender.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountSmsSecurityCodeSender.cs new file mode 100644 index 000000000..6b22163d1 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/IAccountSmsSecurityCodeSender.cs @@ -0,0 +1,23 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Account.Security; +/// +/// 短信安全码发送接口 +/// +public interface IAccountSmsSecurityCodeSender +{ + /// + /// 发送短信验证码 + /// + /// 手机号 + /// 短信验证码 + /// 短信验证模板 + /// + /// + Task SendAsync( + string phone, + string code, + string templateCode, + CancellationToken cancellation = default); +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/AccountSecurityResource.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/AccountSecurityResource.cs new file mode 100644 index 000000000..2121fe1e3 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/AccountSecurityResource.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Account.Security.Localization; + +[LocalizationResourceName("AbpAccountSecurity")] +public class AccountSecurityResource +{ +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/en.json b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/en.json new file mode 100644 index 000000000..ff2ebb3ca --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/en.json @@ -0,0 +1,16 @@ +{ + "culture": "en", + "texts": { + "TextTemplate:Abp.Account.MailSecurityVerifyLink": "Mail security validation template", + "TextTemplate:Abp.Account.MailConfirmLink": "Mail confirm template", + "MailSecurityVerify": "Mail security verification", + "VerifyMyEmailAddress": "Hello {0}

Your email security verification code is as follows. Please enter the verification code to proceed to the next step.

If not operated by you, please ignore this email.

", + "MailSecurityVerifyRemarks": "

(If it is not in the form of a link, copy the address to the browser address bar for further access)

Thank you for your visit and have a good time!

", + "ClickToValidation": "Click link verification", + "Validation": "Validation", + "EmailConfirm": "Email confirm", + "EmailConfirmInfo": "We received an email confirmation request! If you initiated this request, please click the link below to confirm the email address.", + "ConfirmMyEmail": "Bind my email address", + "YourEmailIsSuccessfullyConfirm": "Your email address was bound successfully." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/zh-Hans.json b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..bf4fe71f5 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Localization/Resources/zh-Hans.json @@ -0,0 +1,16 @@ +{ + "culture": "zh-Hans", + "texts": { + "TextTemplate:Abp.Account.MailSecurityVerifyLink": "邮件验证模板", + "TextTemplate:Abp.Account.MailConfirmLink": "邮件确认模板", + "MailSecurityVerify": "邮件安全验证", + "VerifyMyEmailAddress": "您好

您此次邮件安全验证码如下,请输入验证码进行下一步操作。

如非你本人操作,请忽略此邮件。

", + "MailSecurityVerifyRemarks": "此邮件为系统所发,请勿直接回复。", + "ClickToValidation": "点击进行验证", + "Validation": "验证", + "EmailConfirm": "邮件确认", + "EmailConfirmInfo": "我们收到了邮件确认请求!如果你发起了此请求,请单击以下链接以确认邮件地址.", + "ConfirmMyEmail": "绑定我的邮箱地址", + "YourEmailIsSuccessfullyConfirm": "您的邮件地址绑定成功." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountEmailTemplates.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountEmailTemplates.cs new file mode 100644 index 000000000..f7d1509e5 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountEmailTemplates.cs @@ -0,0 +1,13 @@ +namespace LINGYUN.Abp.Account.Security.Templates; + +public static class AccountEmailTemplates +{ + /// + /// 邮件地址确认 + /// + public const string MailConfirmLink = "Abp.Account.MailConfirmLink"; + /// + /// 邮件安全验证 + /// + public const string MailSecurityVerifyLink = "Abp.Account.MailSecurityVerifyLink"; +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountTemplateDefinitionProvider.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountTemplateDefinitionProvider.cs new file mode 100644 index 000000000..3cfef3389 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/AccountTemplateDefinitionProvider.cs @@ -0,0 +1,32 @@ +using LINGYUN.Abp.Account.Security.Localization; +using Volo.Abp.Emailing.Templates; +using Volo.Abp.Localization; +using Volo.Abp.TextTemplating; + +namespace LINGYUN.Abp.Account.Security.Templates; + +public class AccountTemplateDefinitionProvider : TemplateDefinitionProvider +{ + public override void Define(ITemplateDefinitionContext context) + { + context.Add( + new TemplateDefinition( + AccountEmailTemplates.MailSecurityVerifyLink, + displayName: L($"TextTemplate:{AccountEmailTemplates.MailSecurityVerifyLink}"), + layout: StandardEmailTemplates.Layout, + localizationResource: typeof(AccountSecurityResource) + ).WithVirtualFilePath("/LINGYUN/Abp/Account/Security/Templates/MailSecurityVerify.tpl", true), + new TemplateDefinition( + AccountEmailTemplates.MailConfirmLink, + displayName: L($"TextTemplate:{AccountEmailTemplates.MailConfirmLink}"), + layout: StandardEmailTemplates.Layout, + localizationResource: typeof(AccountSecurityResource) + ).WithVirtualFilePath("/LINGYUN/Abp/Account/Security/Templates/MailConfirm.tpl", true) + ); + } + + private static ILocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailConfirm.tpl b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailConfirm.tpl new file mode 100644 index 000000000..49bdcf0f8 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailConfirm.tpl @@ -0,0 +1,7 @@ +

{{L "EmailConfirm"}}

+ +

{{L "EmailConfirmInfo"}}

+ + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailSecurityVerify.tpl b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailSecurityVerify.tpl new file mode 100644 index 000000000..60a3bbf0a --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/Templates/MailSecurityVerify.tpl @@ -0,0 +1,5 @@ +
+ {{L "VerifyMyEmailAddress"}}{{model.user}} +

{{model.code}}

+ {{L "MailSecurityVerifyRemarks"}} +
\ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs index 827cd6c77..115c671d4 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs @@ -1,4 +1,4 @@ -using LINGYUN.Abp.Account.Emailing; +using LINGYUN.Abp.Account.Security; using LINGYUN.Abp.Account.Web.Bundling; using LINGYUN.Abp.Account.Web.ProfileManagement; using LINGYUN.Abp.Identity; @@ -25,8 +25,8 @@ namespace LINGYUN.Abp.Account.Web; [DependsOn( typeof(AbpSmsModule), typeof(VoloAbpAccountWebModule), + typeof(AbpAccountSecurityModule), typeof(AbpIdentityDomainModule), - typeof(AbpAccountEmailingModule), typeof(AbpIdentityAspNetCoreQrCodeModule), typeof(AbpAccountApplicationContractsModule))] public class AbpAccountWebModule : AbpModule diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj index 6b5fb6bfd..f3802064a 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj @@ -44,7 +44,7 @@ - + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml index 09526ceeb..626e74bd6 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml @@ -12,6 +12,8 @@

@L["ChangePassword"]

+ +
@if (!Model.HideOldPasswordInput) { diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml.cs index 9d54fb631..730624c8d 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml.cs @@ -54,9 +54,11 @@ public class ChangePasswordModel : AccountPageModel [BindProperty] public ChangePasswordInputModel Input { get; set; } + [HiddenInput] [BindProperty(SupportsGet = true)] public string ReturnUrl { get; set; } + [HiddenInput] [BindProperty(SupportsGet = true)] public string ReturnUrlHash { get; set; } diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/EmailConfirm.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/EmailConfirm.cshtml index 1e5aa47f9..2de79383b 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/EmailConfirm.cshtml +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/EmailConfirm.cshtml @@ -8,6 +8,8 @@

@L["EmailConfirm"]

+ + @L["Cancel"] diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml index cdda0a3d3..569d3d5ff 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml @@ -63,6 +63,7 @@ }
+ @* @L["PlatformLogin"] *@ @L["Login"] @if (Model.ShowCancelButton) { @@ -103,17 +104,20 @@
- -
- - - -
-
-
-
-
-
+ @if (Model.QrCodeLoginInput.IsEnabled) + { + +
+ + + +
+
+
+
+
+
+ } } diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 4c14d1399..59b8439fd 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using MyCSharp.HttpUserAgentParser.Providers; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -65,6 +66,7 @@ public class LoginModel : AccountPageModel protected IIdentityUserRepository UserRepository => LazyServiceProvider.LazyGetRequiredService(); protected IQrCodeLoginProvider QrCodeLoginProvider => LazyServiceProvider.LazyGetRequiredService(); + protected IHttpUserAgentParserProvider HttpUserAgentParserProvider => LazyServiceProvider.LazyGetRequiredService(); protected IExternalProviderService ExternalProviderService { get; } protected IAuthenticationSchemeProvider SchemeProvider { get; } @@ -103,6 +105,8 @@ public class LoginModel : AccountPageModel ReturnUrlHash = ReturnUrlHash, }; + AllowQrCodeLoginIfNotMobileDevice(); + ExternalProviders = await GetExternalProviders(); EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin); @@ -596,6 +600,18 @@ public class LoginModel : AccountPageModel Alerts.Danger(L["InvalidUserNameOrPassword"]); return Task.FromResult(Page()); } + + protected virtual void AllowQrCodeLoginIfNotMobileDevice() + { + if (HttpContext?.Request?.Headers?.UserAgent.IsNullOrEmpty() == false) + { + var userAgentInfo = HttpUserAgentParserProvider.Parse(HttpContext.Request.Headers.UserAgent); + if (userAgentInfo.MobileDeviceType.IsNullOrWhiteSpace()) + { + QrCodeLoginInput.IsEnabled = true; + } + } + } } public abstract class LoginInputModel @@ -642,6 +658,9 @@ public class QrCodeLoginInputModel : LoginInputModel { [HiddenInput] public string Key { get; set; } + + [HiddenInput] + public bool IsEnabled { get; set; } } public enum LoginType diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml index e72e79491..539a1c36c 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml @@ -8,6 +8,8 @@

@L["Register"]

+ + @if (Model.EnableLocalRegister || Model.IsExternalLogin) { diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml.cs index 1ba76bb5f..70a1e12e5 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -29,10 +29,11 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account; public class RegisterModel : AccountPageModel { - + [HiddenInput] [BindProperty(SupportsGet = true)] public string ReturnUrl { get; set; } + [HiddenInput] [BindProperty(SupportsGet = true)] public string ReturnUrlHash { get; set; } diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs index 2bb13dd6d..cc8fe74b9 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs @@ -1,4 +1,4 @@ -using LINGYUN.Abp.Account.Emailing; +using LINGYUN.Abp.Account.Security; using LINGYUN.Abp.Identity.Settings; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; @@ -33,14 +33,14 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account protected ISmsSender SmsSender { get; } - protected IAccountEmailVerifySender AccountEmailVerifySender { get; } + protected IAccountEmailSecurityCodeSender EmailSecurityCodeSender { get; } public SendCodeModel( ISmsSender smsSender, - IAccountEmailVerifySender accountEmailVerifySender) + IAccountEmailSecurityCodeSender emailSecurityCodeSender) { SmsSender = smsSender; - AccountEmailVerifySender = accountEmailVerifySender; + EmailSecurityCodeSender = emailSecurityCodeSender; LocalizationResourceType = typeof(AccountResource); } @@ -91,8 +91,8 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account if (Input.SelectedProvider == "Email") { - await AccountEmailVerifySender - .SendMailLoginVerifyCodeAsync( + await EmailSecurityCodeSender + .SendLoginCodeAsync( code, user.UserName, user.Email); diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendEmailConfirm.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendEmailConfirm.cshtml index 018dbb60c..026725e62 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendEmailConfirm.cshtml +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendEmailConfirm.cshtml @@ -8,6 +8,8 @@

@L["EmailConfirm"]

+ + @L["Cancel"] diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Bundling/AbpElsaScriptBundleContributor.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Bundling/AbpElsaScriptBundleContributor.cs index 1426e116c..5e818c6bb 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Bundling/AbpElsaScriptBundleContributor.cs +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Bundling/AbpElsaScriptBundleContributor.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; @@ -9,9 +8,6 @@ public class AbpElsaScriptBundleContributor : BundleContributor { public override void ConfigureBundle(BundleConfigurationContext context) { - var configuration = context.ServiceProvider.GetRequiredService(); - var basePath = configuration["Hosting:BasePath"] ?? ""; - - context.Files.AddIfNotContains($"{basePath}/_content/Elsa.Designer.Components.Web/monaco-editor/min/vs/loader.js"); + context.Files.AddIfNotContains("/_content/Elsa.Designer.Components.Web/monaco-editor/min/vs/loader.js"); } } diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Bundling/AbpElsaStyleBundleContributor.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Bundling/AbpElsaStyleBundleContributor.cs index e86ff13f5..1aa37b517 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Bundling/AbpElsaStyleBundleContributor.cs +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Bundling/AbpElsaStyleBundleContributor.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; @@ -9,10 +8,7 @@ public class AbpElsaStyleBundleContributor : BundleContributor { public override void ConfigureBundle(BundleConfigurationContext context) { - var configuration = context.ServiceProvider.GetRequiredService(); - var basePath = configuration["Hosting:BasePath"] ?? ""; - - context.Files.AddIfNotContains($"{basePath}/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/fonts/inter/inter.css"); - context.Files.AddIfNotContains($"{basePath}/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.css"); + context.Files.AddIfNotContains("/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/fonts/inter/inter.css"); + context.Files.AddIfNotContains("/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.css"); } } diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Pages/Elsa/Index.cshtml b/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Pages/Elsa/Index.cshtml index 4b2a6cfd4..7d1ec89e9 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Pages/Elsa/Index.cshtml +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Designer/Pages/Elsa/Index.cshtml @@ -10,7 +10,7 @@ @inject IHtmlLocalizer L @{ var serverUrl = Configuration["Elsa:Server:BaseUrl"]; - var basePath = Configuration["Hosting:BasePath"] ?? ""; + var basePath = Request.PathBase.Value; PageLayout.Content.Title = L["Elsa:Designer"].Value; PageLayout.Content.BreadCrumb.Add(L["Elsa:Designer"].Value); @@ -25,9 +25,9 @@ } - + - + @@ -43,4 +43,9 @@ const elsa = e.detail; elsa.pluginManager.registerPlugin(WebhooksPlugin); }); + + // Some components publish DOM events that we can handle directly: + elsaStudioRoot.addEventListener('workflow-changed', e => { + console.log('Workflow model changed! New model: ${e.detail}'); + }) \ No newline at end of file diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Server/LINGYUN/Abp/Elsa/AbpElsaServerModule.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Server/LINGYUN/Abp/Elsa/AbpElsaServerModule.cs index 762afef26..2dae8f825 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Server/LINGYUN/Abp/Elsa/AbpElsaServerModule.cs +++ b/aspnet-core/modules/elsa/LINGYUN.Abp.Elsa.Server/LINGYUN/Abp/Elsa/AbpElsaServerModule.cs @@ -21,6 +21,7 @@ public class AbpElsaServerModule : AbpModule .AddSingleton() .AddScoped() .AddSingleton() + .AddElsaApiEndpoints() .AddSignalR(); PreConfigure(mvcBuilder => diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentitySessionRepository.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentitySessionRepository.cs index e8a413ffe..5b0e04d51 100644 --- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentitySessionRepository.cs +++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentitySessionRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Volo.Abp.Identity; +using Volo.Abp.Specifications; namespace LINGYUN.Abp.Identity; public interface IIdentitySessionRepository : Volo.Abp.Identity.IIdentitySessionRepository @@ -22,4 +23,15 @@ public interface IIdentitySessionRepository : Volo.Abp.Identity.IIdentitySession Task> GetListAsync(Guid userId, CancellationToken cancellationToken = default); Task DeleteAllSessionAsync(string sessionId, Guid? exceptSessionId = null, CancellationToken cancellationToken = default); + + Task GetCountAsync( + ISpecification specification, + CancellationToken cancellationToken = default); + + Task> GetListAsync( + ISpecification specification, + string sorting = $"{nameof(IdentitySession.SignedIn)} DESC", + int maxResultCount = 10, + int skipCount = 0, + CancellationToken cancellationToken = default); } diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IIdentitySessionStore.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IIdentitySessionStore.cs index 4a4596e8d..5e76c2704 100644 --- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IIdentitySessionStore.cs +++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IIdentitySessionStore.cs @@ -22,6 +22,7 @@ public interface IIdentitySessionStore /// 登录时间 /// 上次访问时间 /// IP地域 + /// 用户名称 /// 租户id /// /// 创建完成的 @@ -35,6 +36,7 @@ public interface IIdentitySessionStore DateTime signedIn, DateTime? lastAccessed = null, string ipRegion = null, + string userName = null, Guid? tenantId = null, CancellationToken cancellationToken = default); /// diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionCacheItemSynchronizer.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionCacheItemSynchronizer.cs index 1650e7730..65a261094 100644 --- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionCacheItemSynchronizer.cs +++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionCacheItemSynchronizer.cs @@ -1,42 +1,31 @@ -using LINGYUN.Abp.Identity.Settings; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using System; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.DistributedLocking; -using Volo.Abp.Domain.Entities.Events; using Volo.Abp.Domain.Entities.Events.Distributed; -using Volo.Abp.EventBus; using Volo.Abp.EventBus.Distributed; -using Volo.Abp.Identity; using Volo.Abp.Settings; -using Volo.Abp.Uow; namespace LINGYUN.Abp.Identity.Session; public class IdentitySessionCacheItemSynchronizer : IDistributedEventHandler>, IDistributedEventHandler>, - IDistributedEventHandler, - ILocalEventHandler>, ITransientDependency { public ILogger Logger { protected get; set; } protected ISettingProvider SettingProvider { get; } protected IAbpDistributedLock DistributedLock { get; } protected IIdentitySessionCache IdentitySessionCache { get; } - protected IIdentitySessionStore IdentitySessionStore { get; } public IdentitySessionCacheItemSynchronizer( ISettingProvider settingProvider, IAbpDistributedLock distributedLock, - IIdentitySessionCache identitySessionCache, - IIdentitySessionStore identitySessionStore) + IIdentitySessionCache identitySessionCache) { SettingProvider = settingProvider; DistributedLock = distributedLock; IdentitySessionCache = identitySessionCache; - IdentitySessionStore = identitySessionStore; Logger = NullLogger.Instance; } @@ -46,7 +35,6 @@ public class IdentitySessionCacheItemSynchronizer : await IdentitySessionCache.RemoveAsync(eventData.Entity.SessionId); } - [UnitOfWork] public async virtual Task HandleEventAsync(EntityCreatedEto eventData) { var lockKey = $"{nameof(IdentitySessionCacheItemSynchronizer)}_{nameof(EntityCreatedEto)}"; @@ -61,58 +49,6 @@ public class IdentitySessionCacheItemSynchronizer : } await RefreshSessionCache(eventData.Entity); - await CheckConcurrentLoginStrategy(eventData.Entity); - } - } - - [UnitOfWork] - public async virtual Task HandleEventAsync(IdentitySessionChangeAccessedEvent eventData) - { - var lockKey = $"{nameof(IdentitySessionCacheItemSynchronizer)}_{nameof(IdentitySessionChangeAccessedEvent)}"; - await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) - { - Logger.LogInformation($"Lock is acquired for {lockKey}"); - - if (handle == null) - { - Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); - return; - } - - var idetitySession = await IdentitySessionStore.FindAsync(eventData.SessionId); - if (idetitySession != null) - { - if (!eventData.IpAddresses.IsNullOrWhiteSpace()) - { - idetitySession.SetIpAddresses(eventData.IpAddresses.Split(",")); - } - idetitySession.UpdateLastAccessedTime(eventData.LastAccessed); - - await IdentitySessionStore.UpdateAsync(idetitySession); - } - else - { - // 数据库中不存在会话, 清理缓存, 后续请求会话失效 - await IdentitySessionCache.RemoveAsync(eventData.SessionId); - } - } - } - - public async virtual Task HandleEventAsync(EntityDeletedEventData eventData) - { - var lockKey = $"{nameof(IdentitySessionCacheItemSynchronizer)}_{nameof(EntityDeletedEventData)}"; - await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) - { - Logger.LogInformation($"Lock is acquired for {lockKey}"); - - if (handle == null) - { - Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); - return; - } - - // 用户被删除, 移除所有会话 - await IdentitySessionStore.RevokeAllAsync(eventData.Entity.Id); } } @@ -132,51 +68,4 @@ public class IdentitySessionCacheItemSynchronizer : session.SessionId, identitySessionCacheItem); } - - protected async virtual Task CheckConcurrentLoginStrategy(IdentitySessionEto session) - { - // 创建一个会话后根据策略使其他会话失效 - var strategySet = await SettingProvider.GetOrNullAsync(IdentitySettingNames.Session.ConcurrentLoginStrategy); - - Logger.LogDebug($"The concurrent login strategy is: {strategySet}"); - - if (!strategySet.IsNullOrWhiteSpace() && Enum.TryParse(strategySet, true, out var strategy)) - { - switch (strategy) - { - // 限制用户相同设备 - case ConcurrentLoginStrategy.LogoutFromSameTypeDevicesLimit: - - var sameTypeDevicesCountSet = await SettingProvider.GetAsync(IdentitySettingNames.Session.LogoutFromSameTypeDevicesLimit, 1); - - Logger.LogDebug($"Clear other sessions on the device {session.Device} and save only {sameTypeDevicesCountSet} sessions."); - - await IdentitySessionStore.RevokeWithAsync( - session.UserId, - session.Device, - session.Id, - sameTypeDevicesCountSet); - break; - // 限制登录设备 - case ConcurrentLoginStrategy.LogoutFromSameTypeDevices: - - Logger.LogDebug($"Clear all other sessions on the device {session.Device}."); - - await IdentitySessionStore.RevokeAllAsync( - session.UserId, - session.Device, - session.Id); - break; - // 限制多端登录 - case ConcurrentLoginStrategy.LogoutFromAllDevices: - - Logger.LogDebug($"Clear all other user sessions."); - - await IdentitySessionStore.RevokeAllAsync( - session.UserId, - session.Id); - break; - } - } - } } diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionManager.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionManager.cs index c4db147aa..e365ad9f2 100644 --- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionManager.cs +++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionManager.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Volo.Abp.Auditing; using Volo.Abp.Domain.Services; using Volo.Abp.Identity; +using Volo.Abp.Security.Claims; namespace LINGYUN.Abp.Identity.Session; public class IdentitySessionManager : DomainService, IIdentitySessionManager @@ -54,6 +55,7 @@ public class IdentitySessionManager : DomainService, IIdentitySessionManager var device = deviceInfo.Device ?? IdentitySessionDevices.OAuth; var deviceDesc = deviceInfo.Description; var clientIpAddress = deviceInfo.ClientIpAddress; + var userName = claimsPrincipal.FindFirstValue(AbpClaimTypes.UserName); var clientId = claimsPrincipal.FindClientId(); @@ -69,6 +71,7 @@ public class IdentitySessionManager : DomainService, IIdentitySessionManager Clock.Now, Clock.Now, deviceInfo.IpRegion, + userName, tenantId, cancellationToken); diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionStore.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionStore.cs index ed127f7d9..7a9b29632 100644 --- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionStore.cs +++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionStore.cs @@ -37,6 +37,7 @@ public class IdentitySessionStore : IIdentitySessionStore, ITransientDependency DateTime signedIn, DateTime? lastAccessed = null, string ipRegion = null, + string userName = null, Guid? tenantId = null, CancellationToken cancellationToken = default) { @@ -55,10 +56,15 @@ public class IdentitySessionStore : IIdentitySessionStore, ITransientDependency signedIn, lastAccessed ); + if (!ipRegion.IsNullOrWhiteSpace()) { identitySession.SetProperty("Location", ipRegion); } + if (!userName.IsNullOrWhiteSpace()) + { + identitySession.SetProperty("UserName", userName); + } identitySession = await IdentitySessionRepository.InsertAsync(identitySession, cancellationToken: cancellationToken); diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/EfCoreIdentitySessionRepository.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/EfCoreIdentitySessionRepository.cs index 49e9b0b49..973019971 100644 --- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/EfCoreIdentitySessionRepository.cs +++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/EfCoreIdentitySessionRepository.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Identity; using Volo.Abp.Identity.EntityFrameworkCore; +using Volo.Abp.Specifications; namespace LINGYUN.Abp.Identity.EntityFrameworkCore; @@ -59,4 +60,27 @@ public class EfCoreIdentitySessionRepository : Volo.Abp.Identity.EntityFramework { await DeleteAsync(x => x.SessionId == sessionId && x.Id != exceptSessionId, cancellationToken: cancellationToken); } + + public async virtual Task GetCountAsync( + ISpecification specification, + CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .Where(specification.ToExpression()) + .CountAsync(GetCancellationToken(cancellationToken)); + } + + public async virtual Task> GetListAsync( + ISpecification specification, + string sorting = $"{nameof(IdentitySession.SignedIn)} DESC", + int maxResultCount = 10, + int skipCount = 0, + CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .Where(specification.ToExpression()) + .OrderBy(!sorting.IsNullOrWhiteSpace() ? sorting : $"{nameof(IdentitySession.SignedIn)} DESC") + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } } diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationDefinitionProvider.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationDefinitionProvider.cs index 0d905cad6..0bcfe88ff 100644 --- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationDefinitionProvider.cs +++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationDefinitionProvider.cs @@ -19,7 +19,20 @@ public class IdentityNotificationDefinitionProvider : NotificationDefinitionProv NotificationType.ServiceCallback, NotificationLifetime.Persistent, NotificationContentType.Json, - allowSubscriptionToClients: true); + allowSubscriptionToClients: false) // 客户端禁用, 所有新用户必须订阅此通知 + .WithProviders( + NotificationProviderNames.SignalR, // 实时通知处理会话过期事件 + NotificationProviderNames.Emailing); // 邮件通知会话过期 + + group.AddNotification( + IdentityNotificationNames.IdentityUser.CleaningUpInactiveUsers, + L("Notifications:CleaningUpInactiveUsers"), + L("Notifications:CleaningUpInactiveUsers"), + NotificationType.Application, + NotificationLifetime.Persistent, + NotificationContentType.Markdown, + allowSubscriptionToClients: false) // 客户端禁用, 所有新用户必须订阅此通知 + .WithProviders(NotificationProviderNames.Emailing); } private static ILocalizableString L(string name) diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationNames.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationNames.cs index 3614c4935..3ee5aed89 100644 --- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationNames.cs +++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationNames.cs @@ -11,4 +11,13 @@ public static class IdentityNotificationNames /// public const string ExpirationSession = Prefix + ".Expiration"; } + + public static class IdentityUser + { + public const string Prefix = GroupName + ".IdentityUser"; + /// + /// 不活跃用户清理通知 + /// + public const string CleaningUpInactiveUsers = Prefix + ".CleaningUpInactiveUsers"; + } } diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/LanguageAppService.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/LanguageAppService.cs index ac5220099..643b87755 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/LanguageAppService.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/LanguageAppService.cs @@ -2,15 +2,15 @@ using LINGYUN.Abp.LocalizationManagement.Permissions; using Microsoft.AspNetCore.Authorization; using System.Globalization; -using System.Runtime.Versioning; using System.Threading.Tasks; using Volo.Abp; +using Volo.Abp.Features; using Volo.Abp.Localization; namespace LINGYUN.Abp.LocalizationManagement; +[RequiresFeature(LocalizationManagementFeatures.Enable)] [Authorize(LocalizationManagementPermissions.Language.Default)] -[RequiresPreviewFeatures(LocalizationManagementFeatures.Enable)] public class LanguageAppService : LocalizationAppServiceBase, ILanguageAppService { private readonly ILanguageRepository _repository; diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/ResourceAppService.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/ResourceAppService.cs index 83d4b58aa..b3f022b90 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/ResourceAppService.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/ResourceAppService.cs @@ -1,14 +1,14 @@ using LINGYUN.Abp.LocalizationManagement.Features; using LINGYUN.Abp.LocalizationManagement.Permissions; using Microsoft.AspNetCore.Authorization; -using System.Runtime.Versioning; using System.Threading.Tasks; using Volo.Abp; +using Volo.Abp.Features; namespace LINGYUN.Abp.LocalizationManagement; +[RequiresFeature(LocalizationManagementFeatures.Enable)] [Authorize(LocalizationManagementPermissions.Resource.Default)] -[RequiresPreviewFeatures(LocalizationManagementFeatures.Enable)] public class ResourceAppService : LocalizationAppServiceBase, IResourceAppService { private readonly IResourceRepository _repository; diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/TextAppService.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/TextAppService.cs index 040a1502f..9ba131619 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/TextAppService.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application/LINGYUN/Abp/LocalizationManagement/TextAppService.cs @@ -6,8 +6,8 @@ using Volo.Abp.Features; namespace LINGYUN.Abp.LocalizationManagement; -[Authorize(LocalizationManagementPermissions.Text.Default)] [RequiresFeature(LocalizationManagementFeatures.Enable)] +[Authorize(LocalizationManagementPermissions.Text.Default)] public class TextAppService : LocalizationAppServiceBase, ITextAppService { private readonly ITextRepository _textRepository; diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AbpOssManagementAliyunModule.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AbpOssManagementAliyunModule.cs index e78424fba..9a94ec8fd 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AbpOssManagementAliyunModule.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AbpOssManagementAliyunModule.cs @@ -1,4 +1,8 @@ using LINGYUN.Abp.BlobStoring.Aliyun; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using Volo.Abp.BlobStoring; using Volo.Abp.Modularity; namespace LINGYUN.Abp.OssManagement.Aliyun; @@ -8,4 +12,26 @@ namespace LINGYUN.Abp.OssManagement.Aliyun; typeof(AbpOssManagementDomainModule))] public class AbpOssManagementAliyunModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + var ossConfiguration = configuration.GetSection("OssManagement"); + var ossProvider = ossConfiguration["Provider"]; + + if (!ossProvider.IsNullOrWhiteSpace() && + "Aliyun".Equals(ossProvider, StringComparison.InvariantCultureIgnoreCase)) + { + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseAliyun(oss => + { + ossConfiguration.GetSection("Aliyun").Bind(oss); + }); + }); + }); + context.Services.AddAliyunContainer(); + } + } } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObject.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObject.cs index 074987114..1f57728b2 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObject.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObject.cs @@ -44,7 +44,7 @@ public class OssObject public void SetContent(Stream stream) { _content = stream; - if (!_content.IsNullOrEmpty()) + if (!_content.IsNullOrEmpty() && _content.CanSeek && _content.Position != 0) { _content.Seek(0, SeekOrigin.Begin); } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/AbpOssManagementFileSystemModule.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/AbpOssManagementFileSystemModule.cs index 9bd406970..bab53ee80 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/AbpOssManagementFileSystemModule.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/AbpOssManagementFileSystemModule.cs @@ -1,4 +1,9 @@ -using Volo.Abp.BlobStoring.FileSystem; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.IO; +using Volo.Abp.BlobStoring; +using Volo.Abp.BlobStoring.FileSystem; using Volo.Abp.Modularity; namespace LINGYUN.Abp.OssManagement.FileSystem; @@ -8,4 +13,29 @@ namespace LINGYUN.Abp.OssManagement.FileSystem; typeof(AbpOssManagementDomainModule))] public class AbpOssManagementFileSystemModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + var ossConfiguration = configuration.GetSection("OssManagement"); + var ossProvider = ossConfiguration["Provider"]; + + if (!ossProvider.IsNullOrWhiteSpace() && + "FileSystem".Equals(ossProvider, StringComparison.InvariantCultureIgnoreCase)) + { + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseFileSystem(oss => + { + oss.BasePath = Path.Combine( + Directory.GetCurrentDirectory(), + ossConfiguration["Bucket"] ?? "blobs"); + oss.AppendContainerNameToBasePath = ossConfiguration.GetValue("AppendContainerNameToBasePath", true); + }); + }); + }); + context.Services.AddFileSystemContainer(); + } + } } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN.Abp.OssManagement.Minio.csproj b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN.Abp.OssManagement.Minio.csproj index fc49e5d60..321471295 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN.Abp.OssManagement.Minio.csproj +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN.Abp.OssManagement.Minio.csproj @@ -15,6 +15,7 @@ + diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioModule.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioModule.cs index b7882b2f0..0096428a7 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioModule.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioModule.cs @@ -1,4 +1,8 @@ -using Volo.Abp.BlobStoring.Minio; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using Volo.Abp.BlobStoring; +using Volo.Abp.BlobStoring.Minio; using Volo.Abp.Modularity; namespace LINGYUN.Abp.OssManagement.Minio; @@ -8,4 +12,27 @@ namespace LINGYUN.Abp.OssManagement.Minio; typeof(AbpOssManagementDomainModule))] public class AbpOssManagementMinioModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + var ossConfiguration = configuration.GetSection("OssManagement"); + var ossProvider = ossConfiguration["Provider"]; + + if (!ossProvider.IsNullOrWhiteSpace() && + "Minio".Equals(ossProvider, StringComparison.InvariantCultureIgnoreCase)) + { + context.Services.AddMinioHttpClient(); + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseMinio(oss => + { + ossConfiguration.GetSection("Minio").Bind(oss); + }); + }); + }); + context.Services.AddMinioContainer(); + } + } } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer.cs index be388e879..470f29b33 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Linq.Dynamic.Core; +using System.Net.Http; using System.Reactive.Linq; using System.Threading.Tasks; using Volo.Abp; @@ -29,6 +30,7 @@ public class MinioOssContainer : OssContainerBase, IOssObjectExpireor protected IMinioBlobNameCalculator MinioBlobNameCalculator { get; } protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; } protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } + protected IHttpClientFactory HttpClientFactory { get; } protected IClock Clock { get; } protected ICurrentTenant CurrentTenant { get; } @@ -38,16 +40,18 @@ public class MinioOssContainer : OssContainerBase, IOssObjectExpireor IClock clock, ICurrentTenant currentTenant, ILogger logger, - IMinioBlobNameCalculator minioBlobNameCalculator, - IBlobNormalizeNamingService blobNormalizeNamingService, + IMinioBlobNameCalculator minioBlobNameCalculator, + IBlobNormalizeNamingService blobNormalizeNamingService, IBlobContainerConfigurationProvider configurationProvider, IServiceScopeFactory serviceScopeFactory, + IHttpClientFactory httpClientFactory, IOptions options) : base(options, serviceScopeFactory) { Clock = clock; Logger = logger; CurrentTenant = currentTenant; + HttpClientFactory = httpClientFactory; MinioBlobNameCalculator = minioBlobNameCalculator; BlobNormalizeNamingService = blobNormalizeNamingService; ConfigurationProvider = configurationProvider; @@ -147,7 +151,7 @@ public class MinioOssContainer : OssContainerBase, IOssObjectExpireor .WithObject(isDir ? $"{objectName}/_dir" : objectName) .WithStreamData(request.Content) .WithObjectSize(request.Content.Length)); - + if (request.ExpirationTime.HasValue) { var lifecycleRule = new LifecycleRule @@ -156,8 +160,7 @@ public class MinioOssContainer : OssContainerBase, IOssObjectExpireor ID = putResponse.Etag, Expiration = new Expiration(Clock.Now.Add(request.ExpirationTime.Value)) }; - var lifecycleConfiguration = new LifecycleConfiguration(); - lifecycleConfiguration.Rules.Add(lifecycleRule); + var lifecycleConfiguration = new LifecycleConfiguration([lifecycleRule]); var lifecycleArgs = new SetBucketLifecycleArgs() .WithBucket(bucket) @@ -266,14 +269,15 @@ public class MinioOssContainer : OssContainerBase, IOssObjectExpireor await foreach (var item in expiredObjects) { - var lifecycleRule = new LifecycleRule + var lifecycleConfiguration = new LifecycleConfiguration(new List { - Status = LifecycleRule.LifecycleRuleStatusEnabled, - ID = item.Key, - Expiration = new Expiration(Clock.Normalize(request.ExpirationTime.DateTime)) - }; - var lifecycleConfiguration = new LifecycleConfiguration(); - lifecycleConfiguration.Rules.Add(lifecycleRule); + new LifecycleRule + { + ID = item.Key, + Status = LifecycleRule.LifecycleRuleStatusEnabled, + Expiration = new Expiration(Clock.Normalize(request.ExpirationTime.DateTime)) + } + }); var lifecycleArgs = new SetBucketLifecycleArgs() .WithBucket(bucket) @@ -440,23 +444,10 @@ public class MinioOssContainer : OssContainerBase, IOssObjectExpireor throw new BusinessException(code: OssManagementErrorCodes.ObjectNotFound); } - var memoryStream = new MemoryStream(); - var getObjectArgs = new GetObjectArgs() + var getObjectResult = await client.StatObjectAsync( + new StatObjectArgs() .WithBucket(bucket) - .WithObject(objectName) - .WithCallbackStream((stream) => - { - if (stream != null) - { - stream.CopyTo(memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - } - else - { - memoryStream = null; - } - }); - var getObjectResult = await client.GetObjectAsync(getObjectArgs); + .WithObject(objectName)); var ossObject = new OssObject( !objectPath.IsNullOrWhiteSpace() @@ -465,7 +456,7 @@ public class MinioOssContainer : OssContainerBase, IOssObjectExpireor request.Path, getObjectResult.ETag, getObjectResult.LastModified, - memoryStream.Length, + getObjectResult.Size, getObjectResult.LastModified, getObjectResult.MetaData, getObjectResult.ObjectName.EndsWith("/")) @@ -473,9 +464,16 @@ public class MinioOssContainer : OssContainerBase, IOssObjectExpireor FullName = getObjectResult.ObjectName.Replace(prefixPath, "") }; - if (memoryStream.Length > 0) + if (getObjectResult.Size > 0) { - ossObject.SetContent(memoryStream); + var objectUrl = await client.PresignedGetObjectAsync( + new PresignedGetObjectArgs() + .WithBucket(bucket) + .WithObject(objectName) + .WithExpiry(3600)); + var httpClient = HttpClientFactory.CreateMinioHttpClient(); + + ossObject.SetContent(await httpClient.GetStreamAsync(objectUrl)); } return ossObject; @@ -544,7 +542,7 @@ public class MinioOssContainer : OssContainerBase, IOssObjectExpireor { return bucket; } - + return bucket; } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainerFactory.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainerFactory.cs index cd0c9a28e..adab172c5 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainerFactory.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainerFactory.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using System.Net.Http; using Volo.Abp.BlobStoring; using Volo.Abp.BlobStoring.Minio; using Volo.Abp.MultiTenancy; @@ -16,7 +17,7 @@ public class MinioOssContainerFactory : IOssContainerFactory protected IClock Clock { get; } protected ICurrentTenant CurrentTenant { get; } protected ILogger Logger { get; } - + protected IHttpClientFactory HttpClientFactory { get; } protected IServiceScopeFactory ServiceScopeFactory { get; } protected IOptions Options { get; } @@ -24,6 +25,7 @@ public class MinioOssContainerFactory : IOssContainerFactory IClock clock, ICurrentTenant currentTenant, ILogger logger, + IHttpClientFactory httpClientFactory, IMinioBlobNameCalculator minioBlobNameCalculator, IBlobNormalizeNamingService blobNormalizeNamingService, IBlobContainerConfigurationProvider configurationProvider, @@ -34,6 +36,7 @@ public class MinioOssContainerFactory : IOssContainerFactory Logger = logger; CurrentTenant = currentTenant; Options = options; + HttpClientFactory = httpClientFactory; ServiceScopeFactory = serviceScopeFactory; MinioBlobNameCalculator = minioBlobNameCalculator; BlobNormalizeNamingService = blobNormalizeNamingService; @@ -50,6 +53,7 @@ public class MinioOssContainerFactory : IOssContainerFactory BlobNormalizeNamingService, ConfigurationProvider, ServiceScopeFactory, + HttpClientFactory, Options); } } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/Microsoft/Extensions/DependencyInjection/MinioHttpClientFactoryServiceCollectionExtensions.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/Microsoft/Extensions/DependencyInjection/MinioHttpClientFactoryServiceCollectionExtensions.cs new file mode 100644 index 000000000..f2e48fcb8 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/Microsoft/Extensions/DependencyInjection/MinioHttpClientFactoryServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using System.Net.Http; + +namespace Microsoft.Extensions.DependencyInjection; +internal static class MinioHttpClientFactoryServiceCollectionExtensions +{ + private const string HttpClientName = "__AbpMinioHttpClient"; + public static IServiceCollection AddMinioHttpClient(this IServiceCollection services) + { + services.AddHttpClient(HttpClientName); + + return services; + } + + public static HttpClient CreateMinioHttpClient(this IHttpClientFactory httpClientFactory) + { + return httpClientFactory.CreateClient(HttpClientName); + } +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusModule.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusModule.cs index 90584bfd2..b521a366b 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusModule.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusModule.cs @@ -1,6 +1,8 @@ using LINGYUN.Abp.BlobStoring.Nexus; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; +using Volo.Abp.BlobStoring; using Volo.Abp.Modularity; namespace LINGYUN.Abp.OssManagement.Nexus; @@ -12,12 +14,25 @@ public class AbpOssManagementNexusModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddTransient(); + var configuration = context.Services.GetConfiguration(); + var ossConfiguration = configuration.GetSection("OssManagement"); + var ossProvider = ossConfiguration["Provider"]; - context.Services.AddTransient(provider => - provider - .GetRequiredService() - .Create() - .As()); + if (!ossProvider.IsNullOrWhiteSpace() && + "Nexus".Equals(ossProvider, StringComparison.InvariantCultureIgnoreCase)) + { + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseNexus(oss => + { + ossConfiguration.GetSection("Nexus").Bind(oss); + }); + }); + }); + + context.Services.AddNexusContainer(); + } } } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainer.cs index 197059da4..8e2b328c0 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainer.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainer.cs @@ -301,7 +301,7 @@ internal class NexusOssContainer : OssContainerBase, IOssObjectExpireor } } - return new OssObject( + var ossObject = new OssObject( blobName, blobPath, checksum?.GetOrDefault("md5")?.ToString(), @@ -309,7 +309,13 @@ internal class NexusOssContainer : OssContainerBase, IOssObjectExpireor coreUIResponse.Result.Data.Size, coreUIResponse.Result.Data.BlobUpdated, metadata - ); + ); + + var ossContent = await NexusAssetManager.GetContentOrNullAsync(nexusAssetItem); + + ossObject.SetContent(ossContent); + + return ossObject; } protected virtual NexusBlobProviderConfiguration GetNexusConfiguration() diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/Microsoft/Extensions/DependencyInjection/NexusOssContainerServiceCollectionExtensions.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/Microsoft/Extensions/DependencyInjection/NexusOssContainerServiceCollectionExtensions.cs new file mode 100644 index 000000000..aabe45a85 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/Microsoft/Extensions/DependencyInjection/NexusOssContainerServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +using LINGYUN.Abp.OssManagement; +using LINGYUN.Abp.OssManagement.Nexus; +using System; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class NexusOssContainerServiceCollectionExtensions +{ + public static IServiceCollection AddNexusContainer(this IServiceCollection services) + { + services.AddTransient(); + + services.AddTransient(provider => + provider + .GetRequiredService() + .Create() + .As()); + + return services; + } +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/AbpOssManagementTencentModule.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/AbpOssManagementTencentModule.cs index b9159213c..937915dcf 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/AbpOssManagementTencentModule.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/AbpOssManagementTencentModule.cs @@ -1,4 +1,8 @@ using LINGYUN.Abp.BlobStoring.Tencent; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using Volo.Abp.BlobStoring; using Volo.Abp.Modularity; namespace LINGYUN.Abp.OssManagement.Tencent; @@ -8,4 +12,26 @@ namespace LINGYUN.Abp.OssManagement.Tencent; typeof(AbpOssManagementDomainModule))] public class AbpOssManagementTencentModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + var ossConfiguration = configuration.GetSection("OssManagement"); + var ossProvider = ossConfiguration["Provider"]; + + if (!ossProvider.IsNullOrWhiteSpace() && + "Tencent".Equals(ossProvider, StringComparison.InvariantCultureIgnoreCase)) + { + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseTencentCloud(oss => + { + ossConfiguration.GetSection("Tencent").Bind(oss); + }); + }); + }); + context.Services.AddTencentContainer(); + } + } } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs index 87153273f..1a9763916 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs @@ -1,7 +1,10 @@ using COSXML; +using COSXML.Common; using COSXML.Model.Bucket; using COSXML.Model.Object; using COSXML.Model.Service; +using COSXML.Model.Tag; +using COSXML.Transfer; using LINGYUN.Abp.BlobStoring.Tencent; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -9,6 +12,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.MultiTenancy; @@ -24,10 +28,12 @@ internal class TencentOssContainer : OssContainerBase, IOssObjectExpireor protected IClock Clock { get; } protected ICurrentTenant CurrentTenant { get; } protected ICosClientFactory CosClientFactory { get; } + protected IHttpClientFactory HttpClientFactory { get; } public TencentOssContainer( IClock clock, ICurrentTenant currentTenant, ICosClientFactory cosClientFactory, + IHttpClientFactory httpClientFactory, IServiceScopeFactory serviceScopeFactory, IOptions options) : base(options, serviceScopeFactory) @@ -35,6 +41,7 @@ internal class TencentOssContainer : OssContainerBase, IOssObjectExpireor Clock = clock; CurrentTenant = currentTenant; CosClientFactory = cosClientFactory; + HttpClientFactory = httpClientFactory; } public async override Task BulkDeleteObjectsAsync(BulkDeleteObjectRequest request) { @@ -380,33 +387,51 @@ internal class TencentOssContainer : OssContainerBase, IOssObjectExpireor // throw new ContainerNotFoundException($"Can't not found object {objectName} in container {request.Bucket} with aliyun blob storing"); } - var getObjectRequest = new GetObjectBytesRequest(request.Bucket, objectName); - if (!request.Process.IsNullOrWhiteSpace()) - { - getObjectRequest.SetQueryParameter(request.Process, null); - } - var objectResult = ossClient.GetObject(getObjectRequest); + var headObjectRequest = new HeadObjectRequest(request.Bucket, objectName); + var headObjectResult = ossClient.HeadObject(headObjectRequest); + var ossObject = new OssObject( !objectPath.IsNullOrWhiteSpace() - ? objectResult.Key.Replace(objectPath, "") - : objectResult.Key, + ? headObjectResult.Key.Replace(objectPath, "") + : headObjectResult.Key, request.Path, - objectResult.eTag, + headObjectResult.eTag, null, - objectResult.content.Length, + headObjectResult.size, null, new Dictionary(), - objectResult.Key.EndsWith("/")) + headObjectResult.Key.EndsWith("/")) { - FullName = objectResult.Key + FullName = headObjectResult.Key }; - if (objectResult.content.Length > 0) + if (headObjectResult.size > 0) { - var memoryStream = new MemoryStream(); - await memoryStream.WriteAsync(objectResult.content, 0, objectResult.content.Length); - memoryStream.Seek(0, SeekOrigin.Begin); - ossObject.SetContent(memoryStream); + var configuration = await GetConfigurationAsync(); + // See: https://cloud.tencent.com/document/product/436/47238 + var preSignatureStruct = new PreSignatureStruct + { + appid = configuration.AppId,//"1250000000"; //腾讯云账号 APPID + region = configuration.Region,//"COS_REGION"; //存储桶地域 + bucket = request.Bucket,//"examplebucket-1250000000"; //存储桶 + key = objectName, //对象键 + httpMethod = "GET", //HTTP 请求方法 + isHttps = true, //生成 HTTPS 请求 URL + signDurationSecond = 600, //请求签名时间为600s + headers = null, //签名中需要校验的 header + queryParameters = null //签名中需要校验的 URL 中请求参数 + }; + if (!request.Process.IsNullOrWhiteSpace()) + { + preSignatureStruct.queryParameters = new Dictionary + { + { request.Process, "" } + }; + } + var requestSignURL = ossClient.GenerateSignURL(preSignatureStruct); + var client = HttpClientFactory.CreateTenantOssClient(); + + ossObject.SetContent(await client.GetStreamAsync(requestSignURL)); } return ossObject; @@ -441,6 +466,11 @@ internal class TencentOssContainer : OssContainerBase, IOssObjectExpireor return cos.DoesObjectExist(request); } + protected async virtual Task GetConfigurationAsync() + { + return await CosClientFactory.GetConfigurationAsync(); + } + protected async virtual Task CreateClientAsync() { return await CosClientFactory.CreateAsync(); diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainerFactory.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainerFactory.cs index 625a736dd..8f59ea33e 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainerFactory.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainerFactory.cs @@ -1,6 +1,7 @@ using LINGYUN.Abp.BlobStoring.Tencent; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using System.Net.Http; using Volo.Abp.MultiTenancy; using Volo.Abp.Timing; @@ -11,7 +12,7 @@ public class TencentOssContainerFactory : IOssContainerFactory protected IClock Clock { get; } protected ICurrentTenant CurrentTenant { get; } protected ICosClientFactory CosClientFactory { get; } - + protected IHttpClientFactory HttpClientFactory { get; } protected IServiceScopeFactory ServiceScopeFactory { get; } protected IOptions Options { get; } @@ -19,12 +20,14 @@ public class TencentOssContainerFactory : IOssContainerFactory IClock clock, ICurrentTenant currentTenant, ICosClientFactory cosClientFactory, + IHttpClientFactory httpClientFactory, IServiceScopeFactory serviceScopeFactory, IOptions options) { Clock = clock; CurrentTenant = currentTenant; CosClientFactory = cosClientFactory; + HttpClientFactory = httpClientFactory; Options = options; ServiceScopeFactory = serviceScopeFactory; } @@ -35,6 +38,7 @@ public class TencentOssContainerFactory : IOssContainerFactory Clock, CurrentTenant, CosClientFactory, + HttpClientFactory, ServiceScopeFactory, Options); } diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/AbpNotificationsApplicationAutoMapperProfile.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/AbpNotificationsApplicationAutoMapperProfile.cs index 4c41a76c0..d3f0b8623 100644 --- a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/AbpNotificationsApplicationAutoMapperProfile.cs +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Application/LINGYUN/Abp/Notifications/AbpNotificationsApplicationAutoMapperProfile.cs @@ -12,12 +12,18 @@ public class AbpNotificationsApplicationAutoMapperProfile : Profile .ForMember(dto => dto.Lifetime, map => map.Ignore()) .ForMember(dto => dto.Data, map => map.MapFrom((src, nfi) => { - var dataType = Type.GetType(src.NotificationTypeName); - var data = Activator.CreateInstance(dataType); - if (data is NotificationData notificationData) + if (src != null) { - notificationData.ExtraProperties = src.ExtraProperties; - return notificationData; + var dataType = Type.GetType(src.NotificationTypeName); + if (dataType != null) + { + var data = Activator.CreateInstance(dataType); + if (data is NotificationData notificationData) + { + notificationData.ExtraProperties = src.ExtraProperties; + return notificationData; + } + } } return new NotificationData(); })); diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Emailing/LINGYUN/Abp/Notifications/Emailing/EmailingNotificationPublishProvider.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Emailing/LINGYUN/Abp/Notifications/Emailing/EmailingNotificationPublishProvider.cs index 1ecbbf5bb..f726a9c3b 100644 --- a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Emailing/LINGYUN/Abp/Notifications/Emailing/EmailingNotificationPublishProvider.cs +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Emailing/LINGYUN/Abp/Notifications/Emailing/EmailingNotificationPublishProvider.cs @@ -1,4 +1,5 @@ using LINGYUN.Abp.Identity; +using Markdig; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -26,7 +27,23 @@ public class EmailingNotificationPublishProvider : NotificationPublishProvider { var userIds = identifiers.Select(x => x.UserId).ToList(); var userList = await UserRepository.GetListByIdListAsync(userIds, cancellationToken: cancellationToken); - var emailAddress = userList.Where(x => x.EmailConfirmed).Select(x => x.Email).Distinct().JoinAsString(","); + + var emailAddress = userList + .Where(x => x.EmailConfirmed) + .Select(x => + { + var userEmail = x.Email; + if (!x.Name.IsNullOrWhiteSpace()) + { + // "admin" + return $"\"{x.Name}\"<{userEmail}>"; + } + + return $"\"{x.UserName}\"<{userEmail}>"; + }) + .Distinct() + .JoinAsString(","); + if (emailAddress.IsNullOrWhiteSpace()) { @@ -35,6 +52,14 @@ public class EmailingNotificationPublishProvider : NotificationPublishProvider } var notificationData = await NotificationDataSerializer.ToStandard(notification.Data); + // markdown进行处理 + if (notification.ContentType == NotificationContentType.Markdown) + { + notificationData.Message = Markdown.ToHtml(notificationData.Message); + } + await EmailSender.SendAsync(emailAddress, notificationData.Title, notificationData.Message); + + Logger.LogDebug("The notification: {0} with provider: {1} has successfully published!", notification.Name, Name); } } diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/PushPlusNotificationPublishProvider.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/PushPlusNotificationPublishProvider.cs index ef5d79241..4cc9ddbcd 100644 --- a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/PushPlusNotificationPublishProvider.cs +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.PushPlus/LINGYUN/Abp/Notifications/PushPlus/PushPlusNotificationPublishProvider.cs @@ -64,5 +64,7 @@ public class PushPlusNotificationPublishProvider : NotificationPublishProvider webhook: webhook, callbackUrl: callbackUrl, cancellationToken: cancellationToken); + + Logger.LogDebug("The notification: {0} with provider: {1} has successfully published!", notification.Name, Name); } } diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/SignalRNotificationPublishProvider.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/SignalRNotificationPublishProvider.cs index 8f2e9b4c1..f1672afee 100644 --- a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/SignalRNotificationPublishProvider.cs +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/SignalRNotificationPublishProvider.cs @@ -40,6 +40,8 @@ public class SignalRNotificationPublishProvider : NotificationPublishProvider // 租户通知群发 Logger.LogDebug($"Found a singalr group, begin senging notifications"); await singalRGroup.SendAsync(_options.MethodName, notification, cancellationToken); + + Logger.LogDebug("The notification: {0} with provider: {1} has successfully published!", notification.Name, Name); } catch (Exception ex) { diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Sms/LINGYUN/Abp/Notifications/Sms/SmsNotificationPublishProvider.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Sms/LINGYUN/Abp/Notifications/Sms/SmsNotificationPublishProvider.cs index 9306ddf09..57fc4f564 100644 --- a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Sms/LINGYUN/Abp/Notifications/Sms/SmsNotificationPublishProvider.cs +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Sms/LINGYUN/Abp/Notifications/Sms/SmsNotificationPublishProvider.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -30,5 +31,7 @@ public class SmsNotificationPublishProvider : NotificationPublishProvider return; } await Sender.SendAsync(notification, sendToPhones.JoinAsString(",")); + + Logger.LogDebug("The notification: {0} with provider: {1} has successfully published!", notification.Name, Name); } } diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WeChat.MiniProgram/LINGYUN/Abp/Notifications/WeChat/MiniProgram/WeChatMiniProgramNotificationPublishProvider.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WeChat.MiniProgram/LINGYUN/Abp/Notifications/WeChat/MiniProgram/WeChatMiniProgramNotificationPublishProvider.cs index 65e7e1b50..6315b2a56 100644 --- a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WeChat.MiniProgram/LINGYUN/Abp/Notifications/WeChat/MiniProgram/WeChatMiniProgramNotificationPublishProvider.cs +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WeChat.MiniProgram/LINGYUN/Abp/Notifications/WeChat/MiniProgram/WeChatMiniProgramNotificationPublishProvider.cs @@ -93,6 +93,8 @@ public class WeChatMiniProgramNotificationPublishProvider : NotificationPublishP // 发送小程序订阅消息 await SubscribeMessager.SendAsync(weChatWeAppNotificationData, cancellationToken); + + Logger.LogDebug("The notification: {0} with provider: {1} has successfully published!", notification.Name, Name); } } diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/WeChat/Work/WeChatWorkNotificationPublishProvider.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/WeChat/Work/WeChatWorkNotificationPublishProvider.cs index ae84a648b..ce8a01230 100644 --- a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/WeChat/Work/WeChatWorkNotificationPublishProvider.cs +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/WeChat/Work/WeChatWorkNotificationPublishProvider.cs @@ -55,40 +55,13 @@ public class WeChatWorkNotificationPublishProvider : NotificationPublishProvider } var notificationData = await NotificationDataSerializer.ToStandard(notification.Data); var toTag = notification.Data.GetTagOrNull() ?? notificationDefine?.GetTagOrNull(); - if (!toTag.IsNullOrWhiteSpace()) - { - // 指定发送标签 - await PublishToAgentAsync( - agentId, - notification, - notificationData.Title, - notificationData.Message, - notificationData.Description, - toTag: toTag, - cancellationToken: cancellationToken); - return; - } var toParty = notification.Data.GetPartyOrNull() ?? notificationDefine?.GetPartyOrNull(); - if (!toParty.IsNullOrWhiteSpace()) - { - // 指定发送部门 - await PublishToAgentAsync( - agentId, - notification, - notificationData.Title, - notificationData.Message, - notificationData.Description, - toParty: toParty, - cancellationToken: cancellationToken); - return; - } + var toUsers = await WeChatWorkInternalUserFinder.FindUserIdentifierListAsync(identifiers.Select(id => id.UserId)); - var findUserList = await WeChatWorkInternalUserFinder - .FindUserIdentifierListAsync(identifiers.Select(id => id.UserId)); - - if (findUserList.Count == 0) + if (toUsers.IsNullOrEmpty() && toTag.IsNullOrWhiteSpace() && toParty.IsNullOrWhiteSpace()) { - Logger.LogWarning("Unable to send work weixin messages because findUserList is empty."); + // touser、toparty、totag不能同时为空:https://developer.work.weixin.qq.com/document/path/90236 + Logger.LogWarning("Unable to send work weixin messages because The recipient/department/label cannot be empty simultaneously."); return; } @@ -99,7 +72,9 @@ public class WeChatWorkNotificationPublishProvider : NotificationPublishProvider notificationData.Title, notificationData.Message, notificationData.Description, - toUser: findUserList.JoinAsString("|"), + toTag: toTag, + toParty: toParty, + toUser: toUsers.JoinAsString("|"), cancellationToken: cancellationToken); } @@ -137,17 +112,12 @@ public class WeChatWorkNotificationPublishProvider : NotificationPublishProvider return; } - if (toUser.IsNullOrWhiteSpace() && toTag.IsNullOrWhiteSpace() && toParty.IsNullOrWhiteSpace()) - { - // touser、toparty、totag不能同时为空:https://developer.work.weixin.qq.com/document/path/90236 - Logger.LogWarning("Unable to send work weixin messages because The recipient/department/label cannot be empty simultaneously."); - return; - } - message.ToUser = toUser; message.ToTag = toTag; message.ToParty = toParty; await WeChatWorkMessageSender.SendAsync(message, cancellationToken); + + Logger.LogDebug("The notification: {0} with provider: {1} has successfully published!", notification.Name, Name); } } diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/FodyWeavers.xml b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/FodyWeavers.xsd b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN.Abp.Notifications.Webhook.WeChat.Work.csproj b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN.Abp.Notifications.Webhook.WeChat.Work.csproj new file mode 100644 index 000000000..c3ab836ae --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN.Abp.Notifications.Webhook.WeChat.Work.csproj @@ -0,0 +1,25 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0 + LINGYUN.Abp.Notifications.Webhook.WeChat.Work + LINGYUN.Abp.Notifications.Webhook.WeChat.Work + false + false + false + + + + + + + + + + + + + diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/AbpNotificationsWebhookWeChatWorkModule.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/AbpNotificationsWebhookWeChatWorkModule.cs new file mode 100644 index 000000000..a9fd1580d --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/AbpNotificationsWebhookWeChatWorkModule.cs @@ -0,0 +1,23 @@ +using LINGYUN.Abp.WeChat.Work; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Notifications.Webhook.WeChat.Work; + +[DependsOn( + typeof(AbpNotificationsWebhookModule), + typeof(AbpWeChatWorkModule))] +public class AbpNotificationsWebhookWeChatWorkModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.UseMarkdownV2 = true; + }); + + Configure(options => + { + options.Contributors.Add(new WeChatWorkWebhookNotificationContributor()); + }); + } +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/AbpNotificationsWebhookWeChatWorkOptions.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/AbpNotificationsWebhookWeChatWorkOptions.cs new file mode 100644 index 000000000..d8e9cf700 --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/AbpNotificationsWebhookWeChatWorkOptions.cs @@ -0,0 +1,15 @@ +namespace LINGYUN.Abp.Notifications.Webhook.WeChat.Work; +public class AbpNotificationsWebhookWeChatWorkOptions +{ + /// + /// 发送Markdown类型通知时是否使用MarkdownV2格式通知, 默认: true + /// + /// + /// 详见: https://developer.work.weixin.qq.com/document/path/99110#markdown-v2%E7%B1%BB%E5%9E%8B + /// + public bool UseMarkdownV2 { get; set; } + public AbpNotificationsWebhookWeChatWorkOptions() + { + UseMarkdownV2 = true; + } +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/WeChatWorkWebhookNotificationContributor.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/WeChatWorkWebhookNotificationContributor.cs new file mode 100644 index 000000000..a3d702b23 --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook.WeChat.Work/LINGYUN/Abp/Notifications/Webhook/WeChat/Work/WeChatWorkWebhookNotificationContributor.cs @@ -0,0 +1,74 @@ +using LINGYUN.Abp.WeChat.Work.Messages; +using LINGYUN.Abp.WeChat.Work.Messages.Models; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Notifications.Webhook.WeChat.Work; +public class WeChatWorkWebhookNotificationContributor : IWebhookNotificationContributor +{ + public string Name => "WeChat.Work"; + + public async virtual Task ContributeAsync(IWebhookNotificationContext context) + { + var options = context.ServiceProvider.GetRequiredService>().Value; + var notificationDataSerializer = context.ServiceProvider.GetRequiredService(); + + var data = await notificationDataSerializer.ToStandard(context.Notification.Data); + var notificationContent = data.Message; + + try + { + if (context.Notification.ContentType == NotificationContentType.Html) + { + notificationContent = CommonMark.CommonMarkConverter.Convert(notificationContent); + } + + WeChatWorkWebhookMessage message; + + switch (context.Notification.ContentType) + { + case NotificationContentType.Text: + message = new WeChatWorkWebhookTextMessage( + new WebhookTextMessage(notificationContent)); + break; + case NotificationContentType.Html: + case NotificationContentType.Markdown: + if (options.UseMarkdownV2) + { + message = new WeChatWorkWebhookMarkdownV2Message( + new WebhookMarkdownV2Message(notificationContent)); + } + else + { + message = new WeChatWorkWebhookMarkdownMessage( + new WebhookMarkdownMessage(notificationContent)); + } + break; + default: + return; + } + + if (message == null) + { + context.ServiceProvider + .GetService>() + ?.LogWarning("Unable to send work weixin messages because WeChatWorkMessage is null."); + return; + } + + context.Webhook = new WebhookNotificationData( + context.Notification.Name, + message); + context.Handled = true; + } + catch (Exception ex) + { + context.ServiceProvider + .GetService>() + ?.LogWarning("Failed to parse the content of the Webhook message: {message}", ex.Message); + } + } +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/FodyWeavers.xml b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/FodyWeavers.xsd b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN.Abp.Notifications.Webhook.csproj b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN.Abp.Notifications.Webhook.csproj new file mode 100644 index 000000000..2ca416a4c --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN.Abp.Notifications.Webhook.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0 + LINGYUN.Abp.Notifications.Webhook + LINGYUN.Abp.Notifications.Webhook + false + false + false + + + + + + + + + diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/AbpNotificationsWebhookModule.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/AbpNotificationsWebhookModule.cs new file mode 100644 index 000000000..c11621169 --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/AbpNotificationsWebhookModule.cs @@ -0,0 +1,18 @@ +using LINGYUN.Abp.Webhooks; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Notifications.Webhook; + +[DependsOn( + typeof(AbpNotificationsCoreModule), + typeof(AbpWebhooksModule))] +public class AbpNotificationsWebhookModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.PublishProviders.Add(); + }); + } +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/AbpNotificationsWebhookOptions.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/AbpNotificationsWebhookOptions.cs new file mode 100644 index 000000000..5561b9f46 --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/AbpNotificationsWebhookOptions.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.Notifications.Webhook; +public class AbpNotificationsWebhookOptions +{ + public IList Contributors { get; } + public AbpNotificationsWebhookOptions() + { + Contributors = new List(); + } +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/IWebhookNotificationContext.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/IWebhookNotificationContext.cs new file mode 100644 index 000000000..f23a8b129 --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/IWebhookNotificationContext.cs @@ -0,0 +1,9 @@ +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Notifications.Webhook; +public interface IWebhookNotificationContext : IServiceProviderAccessor +{ + WebhookNotificationData Webhook { get; set; } + NotificationInfo Notification { get; } + bool Handled { get; set; } +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/IWebhookNotificationContributor.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/IWebhookNotificationContributor.cs new file mode 100644 index 000000000..757ff88fd --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/IWebhookNotificationContributor.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Notifications.Webhook; +public interface IWebhookNotificationContributor +{ + string Name { get; } + Task ContributeAsync(IWebhookNotificationContext context); +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationContext.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationContext.cs new file mode 100644 index 000000000..0f351a71e --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationContext.cs @@ -0,0 +1,19 @@ +using System; + +namespace LINGYUN.Abp.Notifications.Webhook; +public class WebhookNotificationContext : IWebhookNotificationContext +{ + public IServiceProvider ServiceProvider { get; } + public NotificationInfo Notification { get; } + public WebhookNotificationData Webhook { get; set; } + public bool Handled { get; set; } + public WebhookNotificationContext(IServiceProvider serviceProvider, NotificationInfo notification) + { + ServiceProvider = serviceProvider; + Notification = notification; + } + public bool HasResolved() + { + return Handled || Webhook != null; + } +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationData.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationData.cs new file mode 100644 index 000000000..561d6324a --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationData.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.Webhooks; +using Volo.Abp; + +namespace LINGYUN.Abp.Notifications.Webhook; +public class WebhookNotificationData +{ + public string WebhookName { get; } + public object Data { get; } + public bool SendExactSameData { get; set; } + public WebhookHeader Headers { get; set; } + public WebhookNotificationData(string webhookName, object data) + { + WebhookName = Check.NotNullOrWhiteSpace(webhookName, nameof(webhookName)); + Data = Check.NotNull(data, nameof(data)); + } +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationPublishProvider.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationPublishProvider.cs new file mode 100644 index 000000000..6aaa905b8 --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Webhook/LINGYUN/Abp/Notifications/Webhook/WebhookNotificationPublishProvider.cs @@ -0,0 +1,61 @@ +using LINGYUN.Abp.Webhooks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.Notifications.Webhook; +public class WebhookNotificationPublishProvider : NotificationPublishProvider +{ + public const string ProviderName = "Webhook"; + public override string Name => ProviderName; + + protected IFeatureChecker FeatureChecker => ServiceProvider.LazyGetRequiredService(); + protected IWebhookPublisher WebhookPublisher => ServiceProvider.LazyGetRequiredService(); + protected IServiceScopeFactory ServiceScopeFactory => ServiceProvider.LazyGetRequiredService(); + protected IOptions Options => ServiceProvider.LazyGetRequiredService>(); + + protected override Task CanPublishAsync(NotificationInfo notification, CancellationToken cancellationToken = default) + { + if (Options.Value.Contributors.Count == 0) + { + Logger.LogWarning("The Webhook notification publishing contributor is empty, and the Webhook notification cannot be sent!"); + return Task.FromResult(false); + } + + return base.CanPublishAsync(notification, cancellationToken); + } + + protected async override Task PublishAsync(NotificationInfo notification, IEnumerable identifiers, CancellationToken cancellationToken = default) + { + using var scope = ServiceScopeFactory.CreateScope(); + + foreach (var contributor in Options.Value.Contributors) + { + var context = new WebhookNotificationContext(scope.ServiceProvider, notification); + + await contributor.ContributeAsync(context); + + if (!context.HasResolved()) + { + Logger.LogWarning("The Webhook notifies the contributor: {0} that the Webhook data for the given notification: {1} cannot be parsed. Skip it.", + contributor.Name, notification.Name); + continue; + } + else + { + await WebhookPublisher.PublishAsync( + context.Webhook.WebhookName, + context.Webhook.Data, + notification.TenantId, + context.Webhook.SendExactSameData, + context.Webhook.Headers); + + Logger.LogDebug("The webhook: {webhookName} with contributor: {name} has successfully published!", context.Webhook.WebhookName, Name); + } + } + } +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WxPusher/LINGYUN/Abp/Notifications/WxPusher/WxPusherNotificationPublishProvider.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WxPusher/LINGYUN/Abp/Notifications/WxPusher/WxPusherNotificationPublishProvider.cs index 5cddd3712..13c04ddd4 100644 --- a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WxPusher/LINGYUN/Abp/Notifications/WxPusher/WxPusherNotificationPublishProvider.cs +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.WxPusher/LINGYUN/Abp/Notifications/WxPusher/WxPusherNotificationPublishProvider.cs @@ -64,5 +64,7 @@ public class WxPusherNotificationPublishProvider : NotificationPublishProvider uids: uids, url: url, cancellationToken: cancellationToken); + + Logger.LogDebug("The notification: {0} with provider: {1} has successfully published!", notification.Name, Name); } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/DistributedJobDispatcher.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/DistributedJobDispatcher.cs index 59c04a238..6fb12bc86 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/DistributedJobDispatcher.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/DistributedJobDispatcher.cs @@ -52,4 +52,28 @@ public class DistributedJobDispatcher : IJobDispatcher, ITransientDependency return true; } + + public async virtual Task CleanExpiredJobAsync( + Guid? tenantId = null, + CancellationToken cancellationToken = default) + { + var eventData = new JobCleanupExpiredEventData + { + TenantId = tenantId, + }; + + await EventBus.PublishAsync(eventData); + } + + public async virtual Task CheckRuningJobAsync( + Guid? tenantId = null, + CancellationToken cancellationToken = default) + { + var eventData = new JobCheckRuningEventData + { + TenantId = tenantId, + }; + + await EventBus.PublishAsync(eventData); + } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/JobCheckRuningEventData.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/JobCheckRuningEventData.cs new file mode 100644 index 000000000..f00d8d612 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/JobCheckRuningEventData.cs @@ -0,0 +1,12 @@ +using System; +using Volo.Abp.EventBus; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.BackgroundTasks.EventBus; + +[Serializable] +[EventName("abp.background-tasks.job.check_running")] +public class JobCheckRuningEventData : IMultiTenant +{ + public Guid? TenantId { get; set; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/JobCleanupExpiredEventData.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/JobCleanupExpiredEventData.cs new file mode 100644 index 000000000..958bd88ff --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/JobCleanupExpiredEventData.cs @@ -0,0 +1,12 @@ +using System; +using Volo.Abp.EventBus; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.BackgroundTasks.EventBus; + +[Serializable] +[EventName("abp.background-tasks.job.cleanup_expired")] +public class JobCleanupExpiredEventData : IMultiTenant +{ + public Guid? TenantId { get; set; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/JobSynchronizer.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/JobSynchronizer.cs index f9007944d..aca183095 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/JobSynchronizer.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.EventBus/LINGYUN/Abp/BackgroundTasks/EventBus/JobSynchronizer.cs @@ -11,19 +11,24 @@ public class JobSynchronizer : IDistributedEventHandler, IDistributedEventHandler, IDistributedEventHandler, + IDistributedEventHandler, + IDistributedEventHandler, ITransientDependency { protected IJobStore JobStore { get; } protected IJobScheduler JobScheduler { get; } + protected IJobStateChecker JobStateChecker { get; } protected AbpBackgroundTasksOptions BackgroundTasksOptions { get; } public JobSynchronizer( IJobStore jobStore, IJobScheduler jobScheduler, + IJobStateChecker jobStateChecker, IOptions options) { JobStore = jobStore; JobScheduler = jobScheduler; + JobStateChecker = jobStateChecker; BackgroundTasksOptions = options.Value; } @@ -134,4 +139,14 @@ public class JobSynchronizer : } } } + + public async virtual Task HandleEventAsync(JobCleanupExpiredEventData eventData) + { + await JobStateChecker.CleanExpiredJobAsync(eventData.TenantId); + } + + public async virtual Task HandleEventAsync(JobCheckRuningEventData eventData) + { + await JobStateChecker.CheckRuningJobAsync(eventData.TenantId); + } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobCreator.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobCreator.cs index 2385fbb64..91e40d67b 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobCreator.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobCreator.cs @@ -38,13 +38,10 @@ public class QuartzJobCreator : IQuartzJobCreator, ISingletonDependency // 运行时搜寻本地作业 jobType = typeof(QuartzJobSearchJobAdapter); } - else + else if (!typeof(IJob).IsAssignableFrom(jobType)) { - if (!typeof(IJob).IsAssignableFrom(jobType)) - { - var adapterType = typeof(QuartzJobSimpleAdapter<>); - jobType = adapterType.MakeGenericType(jobType); - } + var adapterType = typeof(QuartzJobSimpleAdapter<>); + jobType = adapterType.MakeGenericType(jobType); } if (jobType == null) diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobAdapter.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobAdapter.cs index be72c3341..5fa086544 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobAdapter.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobAdapter.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using System.Threading.Tasks; @@ -14,15 +13,12 @@ public class BackgroundJobAdapter : IJobRunnable protected AbpBackgroundJobOptions Options { get; } protected IBackgroundJobExecuter JobExecuter { get; } - protected IServiceScopeFactory ServiceScopeFactory { get; } public BackgroundJobAdapter( IOptions options, - IBackgroundJobExecuter jobExecuter, - IServiceScopeFactory serviceScopeFactory) + IBackgroundJobExecuter jobExecuter) { JobExecuter = jobExecuter; - ServiceScopeFactory = serviceScopeFactory; Options = options.Value; Logger = NullLogger>.Instance; @@ -30,17 +26,20 @@ public class BackgroundJobAdapter : IJobRunnable public async virtual Task ExecuteAsync(JobRunnableContext context) { - using var scope = ServiceScopeFactory.CreateScope(); - object args = null; + object jobArgs = null; if (context.TryGetString(nameof(TArgs), out var argsJson)) { var jsonSerializer = context.GetRequiredService(); - args = jsonSerializer.Deserialize(argsJson); - // args = JsonConvert.DeserializeObject(argsJson); + jobArgs = jsonSerializer.Deserialize(argsJson); } - var jobType = Options.GetJob(typeof(TArgs)).JobType; - var jobContext = new JobExecutionContext(scope.ServiceProvider, jobType, args); + var jobConfiguration = Options.GetJob(); + + var jobContext = new JobExecutionContext( + context.ServiceProvider, + jobConfiguration.JobType, + jobArgs, + context.CancellationToken); await JobExecuter.ExecuteAsync(jobContext); } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundWorkerAdapter.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundWorkerAdapter.cs index b6ddcf4db..2fcaca088 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundWorkerAdapter.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundWorkerAdapter.cs @@ -86,7 +86,7 @@ public class BackgroundWorkerAdapter : BackgroundWorkerBase, IBackgroun public async Task ExecuteAsync(JobRunnableContext context) { var worker = (IBackgroundWorker)context.GetService(typeof(TWorker)); - var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider, context.CancellationToken); + var workerContext = new PeriodicBackgroundWorkerContext(context.ServiceProvider, context.CancellationToken); switch (worker) { diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/DefaultJobStateChecker.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/DefaultJobStateChecker.cs new file mode 100644 index 000000000..7fba55221 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/DefaultJobStateChecker.cs @@ -0,0 +1,92 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.BackgroundTasks; + +public class DefaultJobStateChecker : IJobStateChecker, ITransientDependency +{ + private readonly ICurrentTenant _currentTenant; + + private readonly IJobStore _jobStore; + private readonly IJobScheduler _jobScheduler; + private readonly AbpBackgroundTasksOptions _options; + private readonly ILogger _logger; + + public DefaultJobStateChecker( + ICurrentTenant currentTenant, + IJobStore jobStore, + IJobScheduler jobScheduler, + IOptions options, + ILogger logger) + { + _currentTenant = currentTenant; + _jobStore = jobStore; + _jobScheduler = jobScheduler; + _options = options.Value; + _logger = logger; + } + + public async virtual Task CheckRuningJobAsync(Guid? tenantId = null, CancellationToken cancellationToken = default) + { + try + { + using (_currentTenant.Change(tenantId)) + { + var runingJobs = await _jobStore.GetRuningListAsync( + _options.MaxJobCheckCount, + _options.NodeName, + cancellationToken); + + if (runingJobs.Count == 0) + { + return; + } + + foreach (var runingJob in runingJobs) + { + // 当标记为运行中的作业不在调度器中时,改变为已停止作业 + if (!await _jobScheduler.ExistsAsync(runingJob, cancellationToken)) + { + runingJob.Status = JobStatus.Stopped; + + await _jobStore.StoreAsync(runingJob); + } + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "check job status error."); + } + } + + public async virtual Task CleanExpiredJobAsync(Guid? tenantId = null, CancellationToken cancellationToken = default) + { + try + { + using (_currentTenant.Change(tenantId)) + { + var expiredJobs = await _jobStore.CleanupAsync( + _options.MaxJobCleanCount, + _options.JobExpiratime, + _options.NodeName, + cancellationToken); + + foreach (var expiredJob in expiredJobs) + { + // 从队列强制移除作业 + await _jobScheduler.RemoveAsync(expiredJob, cancellationToken); + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "cleanup expired job error."); + } + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobDispatcher.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobDispatcher.cs index 5802ec247..86525f478 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobDispatcher.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobDispatcher.cs @@ -32,4 +32,28 @@ public interface IJobDispatcher string nodeName = null, Guid? tenantId = null, CancellationToken cancellationToken = default); + /// + /// 通知清理过期作业 + /// + /// + /// 通知各节点清理当前节点中过期作业 + /// + /// 租户标识 + /// + /// + Task CleanExpiredJobAsync( + Guid? tenantId = null, + CancellationToken cancellationToken = default); + /// + /// 检查运行中的作业 + /// + /// + /// 通知各节点检查当前节点中运行中作业 + /// + /// 租户标识 + /// + /// + Task CheckRuningJobAsync( + Guid? tenantId = null, + CancellationToken cancellationToken = default); } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStateChecker.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStateChecker.cs new file mode 100644 index 000000000..a60aeaf63 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStateChecker.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.BackgroundTasks; +/// +/// 作业状态检查接口 +/// +public interface IJobStateChecker +{ + /// + /// 清理过期作业 + /// + /// 租户标识 + /// + /// + Task CleanExpiredJobAsync( + Guid? tenantId = null, + CancellationToken cancellationToken = default); + /// + /// 检查运行中的作业 + /// + /// 租户标识 + /// + /// + Task CheckRuningJobAsync( + Guid? tenantId = null, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStore.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStore.cs index 95fa9d6ae..f4dd712dd 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStore.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStore.cs @@ -9,6 +9,7 @@ public interface IJobStore { Task> GetRuningListAsync( int maxResultCount, + string nodeName = null, CancellationToken cancellationToken = default); Task> GetWaitingListAsync( @@ -35,5 +36,6 @@ public interface IJobStore Task> CleanupAsync( int maxResultCount, TimeSpan jobExpiratime, + string nodeName = null, CancellationToken cancellationToken = default); } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCheckingJob.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCheckingJob.cs index bfb9eb553..ba4a98bba 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCheckingJob.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCheckingJob.cs @@ -1,11 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; -using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; using Volo.Abp.Auditing; -using Volo.Abp.MultiTenancy; namespace LINGYUN.Abp.BackgroundTasks.Internal; @@ -15,43 +9,13 @@ public class BackgroundCheckingJob : IJobRunnable { public async virtual Task ExecuteAsync(JobRunnableContext context) { - try - { - var options = context.ServiceProvider.GetRequiredService>().Value; - var store = context.ServiceProvider.GetRequiredService(); - var currentTenant = context.ServiceProvider.GetRequiredService(); + context.TryGetMultiTenantId(out var tenantId); - context.TryGetMultiTenantId(out var tenantId); + var jobDispatcher = context.GetRequiredService(); + var jobStateChecker = context.GetRequiredService(); - using (currentTenant.Change(tenantId)) - { - var runingTasks = await store.GetRuningListAsync( - options.MaxJobCheckCount, context.CancellationToken); + await jobStateChecker.CheckRuningJobAsync(tenantId, context.CancellationToken); - if (!runingTasks.Any()) - { - return; - } - - var jobScheduler = context.ServiceProvider.GetRequiredService(); - - foreach (var job in runingTasks) - { - // 当标记为运行中的作业不在调度器中时,改变为已停止作业 - if (!await jobScheduler.ExistsAsync(job, context.CancellationToken)) - { - job.Status = JobStatus.Stopped; - - await store.StoreAsync(job); - } - } - } - } - catch(Exception ex) - { - context.ServiceProvider - .GetService>() - ?.LogError(ex, "check job status error."); - } + await jobDispatcher.CheckRuningJobAsync(tenantId, context.CancellationToken); } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCleaningJob.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCleaningJob.cs index 6300efe4d..1db4f1a80 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCleaningJob.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCleaningJob.cs @@ -1,9 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using System.Threading; -using System.Threading.Tasks; +using System.Threading.Tasks; using Volo.Abp.Auditing; -using Volo.Abp.MultiTenancy; namespace LINGYUN.Abp.BackgroundTasks.Internal; @@ -13,26 +9,13 @@ public class BackgroundCleaningJob : IJobRunnable { public async virtual Task ExecuteAsync(JobRunnableContext context) { - var options = context.ServiceProvider.GetRequiredService>().Value; - var store = context.ServiceProvider.GetRequiredService(); - var currentTenant = context.ServiceProvider.GetRequiredService(); - context.TryGetMultiTenantId(out var tenantId); - using (currentTenant.Change(tenantId)) - { - var expiredJobs = await store.CleanupAsync( - options.MaxJobCleanCount, - options.JobExpiratime, - context.CancellationToken); + var jobDispatcher = context.GetRequiredService(); + var jobStateChecker = context.GetRequiredService(); - var jobScheduler = context.ServiceProvider.GetRequiredService(); + await jobStateChecker.CleanExpiredJobAsync(tenantId, context.CancellationToken); - foreach (var expiredJob in expiredJobs) - { - // 从队列强制移除作业 - await jobScheduler.RemoveAsync(expiredJob, context.CancellationToken); - } - } + await jobDispatcher.CleanExpiredJobAsync(tenantId, context.CancellationToken); } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/InMemoryJobStore.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/InMemoryJobStore.cs index 47769a2c7..4725c79b2 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/InMemoryJobStore.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/InMemoryJobStore.cs @@ -34,14 +34,14 @@ internal class InMemoryJobStore : IJobStore, ISingletonDependency return Task.FromResult(jobs); } - public virtual Task> GetRuningListAsync(int maxResultCount, CancellationToken cancellationToken = default) + public virtual Task> GetRuningListAsync(int maxResultCount, string nodeName = null, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); var status = new JobStatus[] { JobStatus.Running }; var jobs = _memoryJobStore - .Where(x => status.Contains(x.Status)) + .Where(x => x.NodeName == nodeName && status.Contains(x.Status)) .Take(maxResultCount) .ToList(); @@ -130,7 +130,11 @@ internal class InMemoryJobStore : IJobStore, ISingletonDependency return Task.CompletedTask; } - public virtual Task> CleanupAsync(int maxResultCount, TimeSpan jobExpiratime, CancellationToken cancellationToken = default) + public virtual Task> CleanupAsync( + int maxResultCount, + TimeSpan jobExpiratime, + string nodeName = null, + CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -141,6 +145,7 @@ internal class InMemoryJobStore : IJobStore, ISingletonDependency var expiratime = DateTime.Now - jobExpiratime; expriaJobs = _memoryJobStore + .WhereIf(!nodeName.IsNullOrWhiteSpace(), x => x.NodeName == nodeName) .Where(x => x.Status == JobStatus.Completed && expiratime.CompareTo(x.LastRunTime ?? x.EndTime ?? x.CreationTime) <= 0) .Take(maxResultCount) diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/NullJobDispatcher.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/NullJobDispatcher.cs index 31edfb4a8..4dd9b041d 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/NullJobDispatcher.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/NullJobDispatcher.cs @@ -23,4 +23,18 @@ public class NullJobDispatcher : IJobDispatcher, ISingletonDependency { return Task.FromResult(false); } + + public Task CleanExpiredJobAsync( + Guid? tenantId = null, + CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public Task CheckRuningJobAsync( + Guid? tenantId = null, + CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/FodyWeavers.xml b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/FodyWeavers.xsd b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN.Abp.Identity.Jobs.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN.Abp.Identity.Jobs.csproj new file mode 100644 index 000000000..5bb62d4ca --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN.Abp.Identity.Jobs.csproj @@ -0,0 +1,27 @@ + + + + + + + net9.0 + LINGYUN.Abp.Identity.Jobs + LINGYUN.Abp.Identity.Jobs + false + false + false + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/AbpIdentityJobsModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/AbpIdentityJobsModule.cs new file mode 100644 index 000000000..518b0f3df --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/AbpIdentityJobsModule.cs @@ -0,0 +1,29 @@ +using LINGYUN.Abp.BackgroundTasks; +using LINGYUN.Abp.Identity.Notifications; +using Volo.Abp.Identity.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.Identity.Jobs; + +[DependsOn(typeof(AbpIdentityDomainModule))] +[DependsOn(typeof(AbpIdentityNotificationsModule))] +[DependsOn(typeof(AbpBackgroundTasksAbstractionsModule))] +public class AbpIdentityJobsModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add() + .AddVirtualJson("/LINGYUN/Abp/Identity/Jobs/Localization/Resources"); + }); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/IdentityJobDefinitionProvider.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/IdentityJobDefinitionProvider.cs new file mode 100644 index 000000000..8b65287e8 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/IdentityJobDefinitionProvider.cs @@ -0,0 +1,21 @@ +using LINGYUN.Abp.BackgroundTasks; + +namespace LINGYUN.Abp.Identity.Jobs; +public class IdentityJobDefinitionProvider : JobDefinitionProvider +{ + public override void Define(IJobDefinitionContext context) + { + context.Add( + new JobDefinition( + InactiveIdentitySessionCleanupJob.Name, + typeof(InactiveIdentitySessionCleanupJob), + LocalizableStatic.Create("InactiveIdentitySessionCleanupJob"), + InactiveIdentitySessionCleanupJob.Paramters) + // TODO: 实现用户过期清理作业需要增加用户会话实体 + //new JobDefinition( + // InactiveIdentityUserCleanupJob.Name, + // typeof(InactiveIdentityUserCleanupJob), + // LocalizableStatic.Create("InactiveIdentityUserCleanupJob")) + ); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/InactiveIdentitySessionCleanupJob.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/InactiveIdentitySessionCleanupJob.cs new file mode 100644 index 000000000..7c2aa50af --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/InactiveIdentitySessionCleanupJob.cs @@ -0,0 +1,49 @@ +using LINGYUN.Abp.BackgroundTasks; +using LINGYUN.Abp.Identity.Session; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Identity.Jobs; +/// +/// 用户会话清理作业 +/// +/// +/// 此作业启用时,建议禁用 +/// +public class InactiveIdentitySessionCleanupJob : IJobRunnable +{ + public const string Name = "InactiveIdentitySessionCleanupJob"; + + #region Definition Paramters + + public readonly static IReadOnlyList Paramters = + new List + { + new JobDefinitionParamter( + PropertySessionInactiveDays, + LocalizableStatic.Create("DisplayName:SessionInactiveDays"), + LocalizableStatic.Create("Description:SessionInactiveDays")) + }; + + #endregion + /// + /// 不活跃会话保持时长, 单位天 + /// + public const string PropertySessionInactiveDays = "SessionInactiveDays"; + + public async virtual Task ExecuteAsync(JobRunnableContext context) + { + var logger = context.GetRequiredService>(); + var sessionStore = context.GetRequiredService(); + + var inactiveDays = context.GetOrDefaultJobData(PropertySessionInactiveDays, 30); + + logger.LogInformation("Prepare to clean up sessions that have been inactive for more than {inactiveDays} days.", inactiveDays); + + await sessionStore.RevokeAllAsync(TimeSpan.FromDays(inactiveDays)); + + logger.LogInformation($"Cleaned inactive user session."); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/InactiveIdentityUserCleanupJob.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/InactiveIdentityUserCleanupJob.cs new file mode 100644 index 000000000..c577f18d0 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/InactiveIdentityUserCleanupJob.cs @@ -0,0 +1,125 @@ +//using LINGYUN.Abp.BackgroundTasks; +//using LINGYUN.Abp.Identity.Notifications; +//using LINGYUN.Abp.Notifications; +//using Microsoft.Extensions.Logging; +//using System.Collections.Generic; +//using System.Linq; +//using System.Threading.Tasks; +//using Volo.Abp.Domain.Repositories; +//using Volo.Abp.Identity; +//using Volo.Abp.Specifications; +//using Volo.Abp.Timing; + +//namespace LINGYUN.Abp.Identity.Jobs; + +///// +///// 用户清理作业 +///// +///// +///// 清理长期未登录用户 +///// +//public class InactiveIdentityUserCleanupJob : IJobRunnable +//{ +// public const string Name = "InactiveIdentityUserCleanupJob"; + +// #region Definition Paramters + +// public readonly static IReadOnlyList Paramters = +// new List +// { +// new JobDefinitionParamter( +// PropertyUserInactiveCleanupDays, +// LocalizableStatic.Create("DisplayName:UserInactiveCleanupDays"), +// LocalizableStatic.Create("Description:UserInactiveCleanupDays")), +// new JobDefinitionParamter( +// PropertyUserInactiveNotifierDays, +// LocalizableStatic.Create("DisplayName:UserInactiveNotifierDays"), +// LocalizableStatic.Create("Description:UserInactiveNotifierDays")), +// }; + +// #endregion +// /// +// /// 不活跃用户清理时间, 单位: 天 +// /// +// public const string PropertyUserInactiveCleanupDays = "UserInactiveCleanupDays"; +// /// +// /// 不活跃用户通知时间, 单位: 天 +// /// +// public const string PropertyUserInactiveNotifierDays = "UserInactiveNotifierDays"; + +// public async virtual Task ExecuteAsync(JobRunnableContext context) +// { +// var logger = context.GetRequiredService>(); + +// // 不活跃用户清理时间 +// var inactiveCleanupDays = context.GetOrDefaultJobData(PropertyUserInactiveCleanupDays, 30); +// // 不活跃用户通知时间 +// var inactiveNotifierDays = context.GetOrDefaultJobData(PropertyUserInactiveNotifierDays, 180); + +// var clock = context.GetRequiredService(); +// var identityUserRepo = context.GetRequiredService(); +// var identityUserSessionRepo = context.GetRequiredService(); + +// // 获取需要清理的用户集合 +// var specification = new ExpressionSpecification( +// x => x.LastAccessed <= clock.Now.AddDays(-inactiveNotifierDays)); + +// using (identityUserSessionRepo.DisableTracking()) +// { +// var inactiveUserCount = await identityUserSessionRepo.GetCountAsync(specification); +// if (inactiveUserCount == 0) +// { +// logger.LogInformation("There are no inactive users to be notified."); +// return; +// } +// // 不活跃用户会话集合 +// var inactiveUsers = await identityUserSessionRepo.GetListAsync(specification, maxResultCount: inactiveUserCount); + +// // 直接清理的不活跃用户集合 +// var inactiveCleanupUsers = inactiveUsers.Where(x => x.LastAccessed <= clock.Now.AddDays(-inactiveCleanupDays)); + +// // 需要通知的不活跃用户 +// var inactiveNotifierUsers = inactiveUsers.ExceptBy(inactiveCleanupUsers.Select(x => x.Id), x => x.Id); +// if (inactiveNotifierUsers.Count() != 0) +// { +// await SendInactiveUserNotifier(context, inactiveNotifierUsers); +// } + +// if (inactiveCleanupUsers.Count() != 0) +// { +// logger.LogInformation( +// "Prepare to clean up {count} users who have been inactive for more than {inactiveCleanupDays} days.", +// inactiveCleanupUsers.Count(), +// inactiveCleanupDays); + +// // 清理不活跃用户 +// await identityUserRepo.DeleteManyAsync(inactiveCleanupUsers.Select(x => x.UserId)); +// } +// } +// logger.LogInformation($"Cleaned inactive users."); +// } + +// /// +// /// 发送不活跃用户清理通知 +// /// +// /// +// /// +// /// +// private async Task SendInactiveUserNotifier(JobRunnableContext context, IEnumerable userSessions) +// { +// // TODO: 完成不活跃用户清理通知 +// var notificationSender = context.GetRequiredService(); + +// var notificationTemplate = new NotificationTemplate( +// IdentityNotificationNames.IdentityUser.CleaningUpInactiveUsers, +// data: new Dictionary +// { + +// }); + +// await notificationSender.SendNofiterAsync( +// IdentityNotificationNames.IdentityUser.CleaningUpInactiveUsers, +// notificationTemplate +// ); +// } +//} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/LocalizableStatic.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/LocalizableStatic.cs new file mode 100644 index 000000000..12d1be750 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/LocalizableStatic.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Identity.Localization; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Identity.Jobs; + +internal static class LocalizableStatic +{ + public static ILocalizableString Create(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/Localization/Resources/en.json b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/Localization/Resources/en.json new file mode 100644 index 000000000..97e971ee2 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/Localization/Resources/en.json @@ -0,0 +1,9 @@ +{ + "culture": "en", + "texts": { + "InactiveIdentitySessionCleanupJob": "Inactive Session Cleanup Job", + "InactiveIdentityUserCleanupJob": "Inactive User Cleanup Job", + "DisplayName:SessionInactiveDays": "Duration of inactive conversation", + "Description:SessionInactiveDays": "Duration of inactive conversation, unit: days." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/Localization/Resources/zh-Hans.json b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..3fd0242bd --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/Localization/Resources/zh-Hans.json @@ -0,0 +1,9 @@ +{ + "culture": "zh-Hans", + "texts": { + "InactiveIdentitySessionCleanupJob": "不活跃会话清理作业", + "InactiveIdentityUserCleanupJob": "不活跃用户清理作业", + "DisplayName:SessionInactiveDays": "不活跃会话保持时长", + "Description:SessionInactiveDays": "不活跃会话保持时长,单位: 天." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoFilter.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoFilter.cs index bece73b02..91453f508 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoFilter.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoFilter.cs @@ -25,6 +25,10 @@ public class BackgroundJobInfoFilter ///
public string Type { get; set; } /// + /// 节点名称 + /// + public string NodeName { get; set; } + /// /// 任务状态 /// public JobStatus? Status { get; set; } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoSpecification.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoSpecification.cs index e1028c5d0..3d2c5e115 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoSpecification.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoSpecification.cs @@ -16,6 +16,7 @@ public class BackgroundJobInfoSpecification : Specification Expression> expression = _ => true; return expression + .AndIf(!Filter.NodeName.IsNullOrWhiteSpace(), x => x.NodeName == Filter.NodeName) .AndIf(!Filter.Type.IsNullOrWhiteSpace(), x => x.Type.Contains(Filter.Type)) .AndIf(!Filter.Group.IsNullOrWhiteSpace(), x => x.Group.Equals(Filter.Group)) .AndIf(!Filter.Name.IsNullOrWhiteSpace(), x => x.Name.Equals(Filter.Name)) diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobStore.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobStore.cs index df1e7ac23..21fbd131d 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobStore.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobStore.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; using Volo.Abp.ObjectMapping; +using Volo.Abp.Specifications; using Volo.Abp.Uow; namespace LINGYUN.Abp.TaskManagement; @@ -40,13 +41,10 @@ public class BackgroundJobStore : IJobStore, ITransientDependency return ObjectMapper.Map, List>(jobInfos); } - public async virtual Task> GetRuningListAsync(int maxResultCount, CancellationToken cancellationToken = default) + public async virtual Task> GetRuningListAsync(int maxResultCount, string nodeName = null, CancellationToken cancellationToken = default) { - var filter = new BackgroundJobInfoFilter - { - Status = JobStatus.Running - }; - var specification = new BackgroundJobInfoSpecification(filter); + var specification = new ExpressionSpecification( + x => x.NodeName == nodeName && x.Status == JobStatus.Running); var jobInfos = await JobInfoRepository.GetListAsync( specification, maxResultCount: maxResultCount, cancellationToken: cancellationToken); @@ -175,12 +173,14 @@ public class BackgroundJobStore : IJobStore, ITransientDependency public async virtual Task> CleanupAsync( int maxResultCount, TimeSpan jobExpiratime, + string nodeName = null, CancellationToken cancellationToken = default) { using var unitOfWork = UnitOfWorkManager.Begin(); var jobs = await JobInfoRepository.GetExpiredJobsAsync( maxResultCount, jobExpiratime, + nodeName, cancellationToken); var expiredJobs = ObjectMapper.Map, List>(jobs); diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoRepository.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoRepository.cs index 40bd4bba9..0aa573e86 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoRepository.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoRepository.cs @@ -30,11 +30,13 @@ public interface IBackgroundJobInfoRepository : IRepository /// /// + /// /// /// Task> GetExpiredJobsAsync( int maxResultCount, TimeSpan jobExpiratime, + string nodeName = null, CancellationToken cancellationToken = default); /// /// 获取所有周期性任务 diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobInfoRepository.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobInfoRepository.cs index 672ff068e..7e1adcdac 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobInfoRepository.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobInfoRepository.cs @@ -51,12 +51,13 @@ public class EfCoreBackgroundJobInfoRepository : public async virtual Task> GetExpiredJobsAsync( int maxResultCount, TimeSpan jobExpiratime, + string nodeName = null, CancellationToken cancellationToken = default) { var expiratime = Clock.Now.Subtract(jobExpiratime); return await (await GetDbSetAsync()) - .Where(x => x.Status == JobStatus.Completed && x.LastRunTime <= expiratime) + .Where(x => x.NodeName == nodeName && x.Status == JobStatus.Completed && x.LastRunTime <= expiratime) .OrderBy(x => x.CreationTime) .Take(maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); diff --git a/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Application/LINGYUN/Abp/TextTemplating/TextTemplateContentAppService.cs b/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Application/LINGYUN/Abp/TextTemplating/TextTemplateContentAppService.cs index f6eeaa2aa..1259b8744 100644 --- a/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Application/LINGYUN/Abp/TextTemplating/TextTemplateContentAppService.cs +++ b/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Application/LINGYUN/Abp/TextTemplating/TextTemplateContentAppService.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Authorization; +using System; using System.Threading.Tasks; using Volo.Abp; +using Volo.Abp.Localization; using Volo.Abp.TextTemplating; namespace LINGYUN.Abp.TextTemplating; @@ -11,22 +13,41 @@ public class TextTemplateContentAppService : AbpTextTemplatingAppServiceBase, IT protected ITextTemplateRepository TextTemplateRepository { get; } protected ITemplateContentProvider TemplateContentProvider { get; } protected ITemplateDefinitionManager TemplateDefinitionManager { get; } + protected ILocalizableStringSerializer LocalizableStringSerializer { get; } public TextTemplateContentAppService( ITextTemplateRepository textTemplateRepository, ITemplateContentProvider templateContentProvider, - ITemplateDefinitionManager templateDefinitionManager) + ITemplateDefinitionManager templateDefinitionManager, + ILocalizableStringSerializer localizableStringSerializer) { TextTemplateRepository = textTemplateRepository; TemplateContentProvider = templateContentProvider; TemplateDefinitionManager = templateDefinitionManager; + LocalizableStringSerializer = localizableStringSerializer; } public async virtual Task GetAsync(TextTemplateContentGetInput input) { var templateDefinition = await GetTemplateDefinition(input.Name); + string content = null; - var content = await TemplateContentProvider.GetContentOrNullAsync(templateDefinition.Name, input.Culture); + try + { + content = await TemplateContentProvider.GetContentOrNullAsync(templateDefinition.Name, input.Culture); + } + catch + { + // Ignore + // 场景: 模板未在当前宿主服务中注册时, VirtualPath将抛出异常, 应忽略此异常 + // See: https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs#L66 + } + + if (content.IsNullOrWhiteSpace()) + { + var textTemplate = await TextTemplateRepository.FindByNameAsync(templateDefinition.Name, input.Culture); + content = textTemplate?.Content; + } return new TextTemplateContentDto { @@ -36,41 +57,6 @@ public class TextTemplateContentAppService : AbpTextTemplatingAppServiceBase, IT }; } - //public virtual Task> GetListAsync(TextTemplateDefinitionGetListInput input) - //{ - // var templates = new List(); - // var templateDefinitions = TemplateDefinitionManager.GetAll(); - // var filterTemplates = templateDefinitions - // .WhereIf(!input.Filter.IsNullOrWhiteSpace(), x => - // x.Name.Contains(input.Filter) || x.Layout.Contains(input.Filter)) - // .Skip(input.SkipCount) - // .Take(input.MaxResultCount); - - // foreach (var templateDefinition in filterTemplates) - // { - // var layout = templateDefinition.Layout; - // if (!layout.IsNullOrWhiteSpace()) - // { - // var layoutDefinition = GetTemplateDefinition(templateDefinition.Layout); - // layout = layoutDefinition.DisplayName.Localize(StringLocalizerFactory); - // } - - // var result = new TextTemplateDefinitionDto - // { - // DefaultCultureName = templateDefinition.DefaultCultureName, - // IsInlineLocalized = templateDefinition.IsInlineLocalized, - // IsLayout = templateDefinition.IsLayout, - // Layout = layout, - // Name = templateDefinition.Name, - // DisplayName = templateDefinition.DisplayName.Localize(StringLocalizerFactory), - // }; - - // templates.Add(result); - // } - - // return Task.FromResult(new PagedResultDto(templateDefinitions.Count, templates)); - //} - [Authorize(AbpTextTemplatingPermissions.TextTemplateContent.Delete)] public async virtual Task RestoreToDefaultAsync(string name, TextTemplateRestoreInput input) { @@ -89,13 +75,13 @@ public class TextTemplateContentAppService : AbpTextTemplatingAppServiceBase, IT { var templateDefinition = await GetTemplateDefinition(name); - var template = await TextTemplateRepository.FindByNameAsync(name, input.Culture); + var template = await TextTemplateRepository.FindByNameAsync(templateDefinition.Name, input.Culture); if (template == null) { template = new TextTemplate( GuidGenerator.Create(), templateDefinition.Name, - templateDefinition.DisplayName.Localize(StringLocalizerFactory), + LocalizableStringSerializer.Serialize(templateDefinition.DisplayName), input.Content, input.Culture); diff --git a/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Domain/LINGYUN/Abp/TextTemplating/AbpTextTemplatingDomainModule.cs b/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Domain/LINGYUN/Abp/TextTemplating/AbpTextTemplatingDomainModule.cs index 58e817f5b..a042827e0 100644 --- a/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Domain/LINGYUN/Abp/TextTemplating/AbpTextTemplatingDomainModule.cs +++ b/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Domain/LINGYUN/Abp/TextTemplating/AbpTextTemplatingDomainModule.cs @@ -34,6 +34,9 @@ public class AbpTextTemplatingDomainModule : AbpModule { options.EtoMappings.Add(); options.EtoMappings.Add(); + + // TODO: CAP组件异常将导致应用无法启动, 临时禁用 + // options.AutoEventSelectors.Add(); }); if (context.Services.IsDataMigrationEnvironment()) diff --git a/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Domain/LINGYUN/Abp/TextTemplating/TextTemplateContentContributor.cs b/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Domain/LINGYUN/Abp/TextTemplating/TextTemplateContentContributor.cs index 027a7b148..58c56fa83 100644 --- a/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Domain/LINGYUN/Abp/TextTemplating/TextTemplateContentContributor.cs +++ b/aspnet-core/modules/text-templating/LINGYUN.Abp.TextTemplating.Domain/LINGYUN/Abp/TextTemplating/TextTemplateContentContributor.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using System; using System.Threading.Tasks; @@ -11,6 +13,7 @@ namespace LINGYUN.Abp.TextTemplating; public class TextTemplateContentContributor : ITemplateContentContributor, ITransientDependency { + public ILogger Logger { protected get; set; } protected AbpTextTemplatingCachingOptions TemplatingCachingOptions { get; } protected IDistributedCache TextTemplateContentCache { get; } @@ -20,39 +23,49 @@ public class TextTemplateContentContributor : ITemplateContentContributor, ITran { TextTemplateContentCache = textTemplateContentCache; TemplatingCachingOptions = templatingCachingOptions.Value; + + Logger = NullLogger.Instance; } public async virtual Task GetOrNullAsync(TemplateContentContributorContext context) { - // 2024/05/27 fixed 内联本地化不需要多语言 - var culture = context.TemplateDefinition.IsInlineLocalized ? null : context.Culture; + return (await GetCacheItemAsync(context)).Content; + } + protected async virtual Task GetCacheItemAsync(TemplateContentContributorContext context) + { + var culture = context.TemplateDefinition.IsInlineLocalized ? null : context.Culture; var cacheKey = TextTemplateContentCacheItem.CalculateCacheKey(context.TemplateDefinition.Name, culture); - var cacheItem = await TextTemplateContentCache.GetOrAddAsync(cacheKey, - () => CreateTemplateContentCache(context), - () => CreateTemplateContentCacheOptions()); + Logger.LogDebug($"TextTemplateContentContributor.GetCacheItemAsync: {cacheKey}"); - return cacheItem?.Content; - } + var cacheItem = await TextTemplateContentCache.GetAsync(cacheKey); + + if (cacheItem != null) + { + Logger.LogDebug($"TextTemplateContent found in the cache: {cacheKey}"); + return cacheItem; + } + + Logger.LogDebug($"TextTemplateContent not found in the cache: {cacheKey}"); - protected async virtual Task CreateTemplateContentCache(TemplateContentContributorContext context) - { - // 2024/05/27 fixed 内联本地化不需要多语言 - var culture = context.TemplateDefinition.IsInlineLocalized ? null : context.Culture; var repository = context.ServiceProvider.GetRequiredService(); var template = await repository.FindByNameAsync(context.TemplateDefinition.Name, culture); - - // 2025/06/23 fixed 非内联本地化模板内容为空时,回退到默认文化 if (template == null && !culture.IsNullOrWhiteSpace()) { template = await repository.FindByNameAsync(context.TemplateDefinition.Name, context.TemplateDefinition.DefaultCultureName); } - return new TextTemplateContentCacheItem( + cacheItem = new TextTemplateContentCacheItem( template?.Name, template?.Content, template?.Culture); + + Logger.LogDebug($"TextTemplateContent set cache item: {cacheKey}"); + + await TextTemplateContentCache.SetAsync(cacheKey, cacheItem, CreateTemplateContentCacheOptions()); + + return cacheItem; } protected DistributedCacheEntryOptions CreateTemplateContentCacheOptions() diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj index e76326354..bea220ebb 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj +++ b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj @@ -115,6 +115,7 @@ + @@ -129,7 +130,6 @@ - diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs index 3648955d4..9f4538d21 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs +++ b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs @@ -312,39 +312,6 @@ public partial class MicroServiceApplicationsSingleModule } } - private void ConfigureOssManagement(IServiceCollection services, IConfiguration configuration) - { - var useMinio = configuration.GetValue("OssManagement:UseMinio"); - if (useMinio) - { - Configure(options => - { - options.Containers.ConfigureAll((containerName, containerConfiguration) => - { - containerConfiguration.UseMinio(minio => - { - configuration.GetSection("Minio").Bind(minio); - }); - }); - }); - services.AddMinioContainer(); - } - else - { - Configure(options => - { - options.Containers.ConfigureAll((containerName, containerConfiguration) => - { - containerConfiguration.UseFileSystem(fileSystem => - { - fileSystem.BasePath = Path.Combine(Directory.GetCurrentDirectory(), "blobs"); - }); - }); - }); - services.AddFileSystemContainer(); - } - } - private void ConfigureBackgroundTasks() { Configure(options => diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs index 61e3c3288..9df459858 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs +++ b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs @@ -1,4 +1,5 @@ using LINGYUN.Abp.Elsa.Designer; +using LINGYUN.Abp.WeChat.Work.ExternalContact; namespace LY.MicroService.Applications.Single; @@ -334,6 +335,8 @@ namespace LY.MicroService.Applications.Single; typeof(AbpWeChatOfficialHttpApiModule), // 微信模块 企业微信 typeof(AbpWeChatWorkModule), + // 微信模块 企业微信客户联系 + typeof(AbpWeChatWorkExternalContactModule), // 微信模块 企业微信 应用服务 typeof(AbpWeChatWorkApplicationModule), // 微信模块 企业微信 控制器 @@ -434,7 +437,6 @@ public partial class MicroServiceApplicationsSingleModule : AbpModule ConfigureNotificationManagement(configuration); ConfigureCors(context.Services, configuration); ConfigureSwagger(context.Services, configuration); - ConfigureOssManagement(context.Services, configuration); ConfigureDistributedLock(context.Services, configuration); ConfigureKestrelServer(configuration, hostingEnvironment); ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment()); diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Program.cs b/aspnet-core/services/LY.MicroService.Applications.Single/Program.cs index 50890bddf..2e6709ace 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Program.cs +++ b/aspnet-core/services/LY.MicroService.Applications.Single/Program.cs @@ -72,5 +72,6 @@ app.UseAbpSwaggerUI(options => app.UseAuditing(); app.UseAbpSerilogEnrichers(); app.UseConfiguredEndpoints(); +app.UseHttpActivities(); await app.RunAsync(); diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json b/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json index 5c9e3ef69..22c0f0b17 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json +++ b/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json @@ -115,7 +115,7 @@ } }, "Server": { - "BaseUrl": "http://127.0.0.1:30000" + "BaseUrl": "http://127.0.0.1:30001" } }, "Quartz": { @@ -189,6 +189,13 @@ "UserCode": "00:10:00" } }, + "OssManagement": { + "Provider": "FileSystem", + "FileSystem": { + "Bucket": "blobs", + "AppendContainerNameToBasePath": true + } + }, "Identity": { "Password": { "RequiredLength": 6, diff --git a/aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/AuthServerHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/AuthServerHttpApiHostModule.cs index b257c5bf6..23fdc6bb0 100644 --- a/aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/AuthServerHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/AuthServerHttpApiHostModule.cs @@ -23,6 +23,7 @@ using LINGYUN.Abp.Saas.EntityFrameworkCore; using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.Sms.Platform; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -83,6 +84,7 @@ namespace LY.MicroService.AuthServer; typeof(AbpIdentitySessionAspNetCoreModule), typeof(AbpAspNetCoreHttpOverridesModule), typeof(AbpAspNetCoreMvcWrapperModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpExporterMiniExcelModule), typeof(AbpClaimsMappingModule), diff --git a/aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/Handlers/IdentitySessionAccessEventHandler.cs b/aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/Handlers/IdentitySessionAccessEventHandler.cs new file mode 100644 index 000000000..9de763da8 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/Handlers/IdentitySessionAccessEventHandler.cs @@ -0,0 +1,164 @@ +using LINGYUN.Abp.Identity; +using LINGYUN.Abp.Identity.Session; +using LINGYUN.Abp.Identity.Settings; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Settings; +using Volo.Abp.Uow; +using Volo.Abp.Users; + +namespace LY.MicroService.AuthServer.Handlers; +/// +/// 会话控制事件处理器 +/// +public class IdentitySessionAccessEventHandler : + IDistributedEventHandler, + IDistributedEventHandler>, + IDistributedEventHandler>, + ITransientDependency +{ + public ILogger Logger { protected get; set; } + protected ISettingProvider SettingProvider { get; } + protected IAbpDistributedLock DistributedLock { get; } + protected IIdentitySessionCache IdentitySessionCache { get; } + protected IIdentitySessionStore IdentitySessionStore { get; } + + public IdentitySessionAccessEventHandler( + ISettingProvider settingProvider, + IAbpDistributedLock distributedLock, + IIdentitySessionCache identitySessionCache, + IIdentitySessionStore identitySessionStore) + { + SettingProvider = settingProvider; + DistributedLock = distributedLock; + IdentitySessionCache = identitySessionCache; + IdentitySessionStore = identitySessionStore; + + Logger = NullLogger.Instance; + } + + [UnitOfWork] + public async virtual Task HandleEventAsync(EntityCreatedEto eventData) + { + // 新会话创建时检查登录策略 + var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityCreatedEto)}"; + await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) + { + Logger.LogInformation($"Lock is acquired for {lockKey}"); + + if (handle == null) + { + Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); + return; + } + + await CheckConcurrentLoginStrategy(eventData.Entity); + } + } + + [UnitOfWork] + public async virtual Task HandleEventAsync(EntityDeletedEto eventData) + { + // 用户被删除, 移除所有会话 + var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityDeletedEto)}"; + await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) + { + Logger.LogInformation($"Lock is acquired for {lockKey}"); + + if (handle == null) + { + Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); + return; + } + + await IdentitySessionStore.RevokeAllAsync(eventData.Entity.Id); + } + } + + [UnitOfWork] + public async virtual Task HandleEventAsync(IdentitySessionChangeAccessedEvent eventData) + { + // 会话访问更新 + var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentitySessionChangeAccessedEvent)}"; + await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) + { + Logger.LogInformation($"Lock is acquired for {lockKey}"); + + if (handle == null) + { + Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); + return; + } + + var idetitySession = await IdentitySessionStore.FindAsync(eventData.SessionId); + if (idetitySession != null) + { + if (!eventData.IpAddresses.IsNullOrWhiteSpace()) + { + idetitySession.SetIpAddresses(eventData.IpAddresses.Split(",")); + } + idetitySession.UpdateLastAccessedTime(eventData.LastAccessed); + + await IdentitySessionStore.UpdateAsync(idetitySession); + } + else + { + // 数据库中不存在会话, 清理缓存, 后续请求会话失效 + await IdentitySessionCache.RemoveAsync(eventData.SessionId); + } + } + } + + protected async virtual Task CheckConcurrentLoginStrategy(IdentitySessionEto session) + { + // 创建一个会话后根据策略使其他会话失效 + var strategySet = await SettingProvider.GetOrNullAsync(IdentitySettingNames.Session.ConcurrentLoginStrategy); + + Logger.LogDebug($"The concurrent login strategy is: {strategySet}"); + + if (!strategySet.IsNullOrWhiteSpace() && Enum.TryParse(strategySet, true, out var strategy)) + { + switch (strategy) + { + // 限制用户相同设备 + case ConcurrentLoginStrategy.LogoutFromSameTypeDevicesLimit: + + var sameTypeDevicesCountSet = await SettingProvider.GetAsync(IdentitySettingNames.Session.LogoutFromSameTypeDevicesLimit, 1); + + Logger.LogDebug($"Clear other sessions on the device {session.Device} and save only {sameTypeDevicesCountSet} sessions."); + + await IdentitySessionStore.RevokeWithAsync( + session.UserId, + session.Device, + session.Id, + sameTypeDevicesCountSet); + break; + // 限制登录设备 + case ConcurrentLoginStrategy.LogoutFromSameTypeDevices: + + Logger.LogDebug($"Clear all other sessions on the device {session.Device}."); + + await IdentitySessionStore.RevokeAllAsync( + session.UserId, + session.Device, + session.Id); + break; + // 限制多端登录 + case ConcurrentLoginStrategy.LogoutFromAllDevices: + + Logger.LogDebug($"Clear all other user sessions."); + + await IdentitySessionStore.RevokeAllAsync( + session.UserId, + session.Id); + break; + } + } + } +} diff --git a/aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/LY.MicroService.AuthServer.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/LY.MicroService.AuthServer.HttpApi.Host.csproj index 872614c89..b4437933d 100644 --- a/aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/LY.MicroService.AuthServer.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/LY.MicroService.AuthServer.HttpApi.Host.csproj @@ -56,6 +56,7 @@ + diff --git a/aspnet-core/services/LY.MicroService.AuthServer/.gitignore b/aspnet-core/services/LY.MicroService.AuthServer/.gitignore deleted file mode 100644 index 60fc43a0b..000000000 --- a/aspnet-core/services/LY.MicroService.AuthServer/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -wwwroot/* -package-lock.json \ No newline at end of file diff --git a/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.Configure.cs b/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.Configure.cs index c9c9c2e7e..0080c2059 100644 --- a/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.Configure.cs +++ b/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.Configure.cs @@ -1,4 +1,5 @@ using DotNetCore.CAP; +using LINGYUN.Abp.AspNetCore.MultiTenancy; using LINGYUN.Abp.BlobStoring.OssManagement; using LINGYUN.Abp.Localization.CultureMap; using LINGYUN.Abp.LocalizationManagement; @@ -14,7 +15,6 @@ using LINGYUN.Abp.Wrapper; using LY.MicroService.AuthServer.Ui.Branding; using Medallion.Threading; using Medallion.Threading.Redis; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; @@ -446,7 +446,7 @@ public partial class AuthServerModule var domains = tenantResolveCfg.Get(); foreach (var domain in domains) { - options.AddDomainTenantResolver(domain); + options.AddOnlyDomainTenantResolver(domain); } }); } diff --git a/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.cs b/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.cs index f79f1b147..8c4b2d3c3 100644 --- a/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.cs +++ b/aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.cs @@ -2,6 +2,7 @@ using LINGYUN.Abp.Account.Web.OAuth; using LINGYUN.Abp.Account.Web.OpenIddict; using LINGYUN.Abp.AspNetCore.HttpOverrides; +using LINGYUN.Abp.AspNetCore.MultiTenancy; using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; using LINGYUN.Abp.AuditLogging.Elasticsearch; using LINGYUN.Abp.BlobStoring.OssManagement; @@ -24,6 +25,7 @@ using LINGYUN.Abp.OpenIddict.WeChat.Work; using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.Sms.Platform; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using LY.MicroService.AuthServer.EntityFrameworkCore; using Microsoft.AspNetCore.Builder; @@ -69,8 +71,10 @@ namespace LY.MicroService.AuthServer; typeof(AbpDataDbMigratorModule), typeof(AbpAuditLoggingElasticsearchModule), // 放在 AbpIdentity 模块之后,避免被覆盖 typeof(AbpLocalizationCultureMapModule), + typeof(AbpAspNetCoreMultiTenancyModule), typeof(AbpAspNetCoreMvcWrapperModule), typeof(AbpAspNetCoreHttpOverridesModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpExporterMiniExcelModule), typeof(AbpEmailingPlatformModule), diff --git a/aspnet-core/services/LY.MicroService.AuthServer/LY.MicroService.AuthServer.csproj b/aspnet-core/services/LY.MicroService.AuthServer/LY.MicroService.AuthServer.csproj index 6c80ea30c..a87167ef8 100644 --- a/aspnet-core/services/LY.MicroService.AuthServer/LY.MicroService.AuthServer.csproj +++ b/aspnet-core/services/LY.MicroService.AuthServer/LY.MicroService.AuthServer.csproj @@ -52,8 +52,9 @@ - + + diff --git a/aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.cs index e0e27ef62..b145888fa 100644 --- a/aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.cs @@ -29,6 +29,7 @@ using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.SettingManagement; using LINGYUN.Abp.Sms.Platform; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using LINGYUN.Abp.Tencent.SettingManagement; using LINGYUN.Abp.TextTemplating; @@ -115,6 +116,7 @@ namespace LY.MicroService.BackendAdmin; typeof(AbpDataDbMigratorModule), typeof(AbpAspNetCoreAuthenticationJwtBearerModule), typeof(AbpEmailingExceptionHandlingModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpHttpClientModule), typeof(AbpSmsPlatformModule), diff --git a/aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/LY.MicroService.BackendAdmin.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/LY.MicroService.BackendAdmin.HttpApi.Host.csproj index 67bd12f8c..f563b3809 100644 --- a/aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/LY.MicroService.BackendAdmin.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/LY.MicroService.BackendAdmin.HttpApi.Host.csproj @@ -64,7 +64,7 @@ - + @@ -83,7 +83,6 @@ - diff --git a/aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/Handlers/IdentitySessionAccessEventHandler.cs b/aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/Handlers/IdentitySessionAccessEventHandler.cs new file mode 100644 index 000000000..4dc22a3fe --- /dev/null +++ b/aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/Handlers/IdentitySessionAccessEventHandler.cs @@ -0,0 +1,164 @@ +using LINGYUN.Abp.Identity; +using LINGYUN.Abp.Identity.Session; +using LINGYUN.Abp.Identity.Settings; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Settings; +using Volo.Abp.Uow; +using Volo.Abp.Users; + +namespace LY.MicroService.IdentityServer.Handlers; +/// +/// 会话控制事件处理器 +/// +public class IdentitySessionAccessEventHandler : + IDistributedEventHandler, + IDistributedEventHandler>, + IDistributedEventHandler>, + ITransientDependency +{ + public ILogger Logger { protected get; set; } + protected ISettingProvider SettingProvider { get; } + protected IAbpDistributedLock DistributedLock { get; } + protected IIdentitySessionCache IdentitySessionCache { get; } + protected IIdentitySessionStore IdentitySessionStore { get; } + + public IdentitySessionAccessEventHandler( + ISettingProvider settingProvider, + IAbpDistributedLock distributedLock, + IIdentitySessionCache identitySessionCache, + IIdentitySessionStore identitySessionStore) + { + SettingProvider = settingProvider; + DistributedLock = distributedLock; + IdentitySessionCache = identitySessionCache; + IdentitySessionStore = identitySessionStore; + + Logger = NullLogger.Instance; + } + + [UnitOfWork] + public async virtual Task HandleEventAsync(EntityCreatedEto eventData) + { + // 新会话创建时检查登录策略 + var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityCreatedEto)}"; + await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) + { + Logger.LogInformation($"Lock is acquired for {lockKey}"); + + if (handle == null) + { + Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); + return; + } + + await CheckConcurrentLoginStrategy(eventData.Entity); + } + } + + [UnitOfWork] + public async virtual Task HandleEventAsync(EntityDeletedEto eventData) + { + // 用户被删除, 移除所有会话 + var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(EntityDeletedEto)}"; + await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) + { + Logger.LogInformation($"Lock is acquired for {lockKey}"); + + if (handle == null) + { + Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); + return; + } + + await IdentitySessionStore.RevokeAllAsync(eventData.Entity.Id); + } + } + + [UnitOfWork] + public async virtual Task HandleEventAsync(IdentitySessionChangeAccessedEvent eventData) + { + // 会话访问更新 + var lockKey = $"{nameof(IdentitySessionAccessEventHandler)}_{nameof(IdentitySessionChangeAccessedEvent)}"; + await using (var handle = await DistributedLock.TryAcquireAsync(lockKey)) + { + Logger.LogInformation($"Lock is acquired for {lockKey}"); + + if (handle == null) + { + Logger.LogInformation($"Handle is null because of the locking for : {lockKey}"); + return; + } + + var idetitySession = await IdentitySessionStore.FindAsync(eventData.SessionId); + if (idetitySession != null) + { + if (!eventData.IpAddresses.IsNullOrWhiteSpace()) + { + idetitySession.SetIpAddresses(eventData.IpAddresses.Split(",")); + } + idetitySession.UpdateLastAccessedTime(eventData.LastAccessed); + + await IdentitySessionStore.UpdateAsync(idetitySession); + } + else + { + // 数据库中不存在会话, 清理缓存, 后续请求会话失效 + await IdentitySessionCache.RemoveAsync(eventData.SessionId); + } + } + } + + protected async virtual Task CheckConcurrentLoginStrategy(IdentitySessionEto session) + { + // 创建一个会话后根据策略使其他会话失效 + var strategySet = await SettingProvider.GetOrNullAsync(IdentitySettingNames.Session.ConcurrentLoginStrategy); + + Logger.LogDebug($"The concurrent login strategy is: {strategySet}"); + + if (!strategySet.IsNullOrWhiteSpace() && Enum.TryParse(strategySet, true, out var strategy)) + { + switch (strategy) + { + // 限制用户相同设备 + case ConcurrentLoginStrategy.LogoutFromSameTypeDevicesLimit: + + var sameTypeDevicesCountSet = await SettingProvider.GetAsync(IdentitySettingNames.Session.LogoutFromSameTypeDevicesLimit, 1); + + Logger.LogDebug($"Clear other sessions on the device {session.Device} and save only {sameTypeDevicesCountSet} sessions."); + + await IdentitySessionStore.RevokeWithAsync( + session.UserId, + session.Device, + session.Id, + sameTypeDevicesCountSet); + break; + // 限制登录设备 + case ConcurrentLoginStrategy.LogoutFromSameTypeDevices: + + Logger.LogDebug($"Clear all other sessions on the device {session.Device}."); + + await IdentitySessionStore.RevokeAllAsync( + session.UserId, + session.Device, + session.Id); + break; + // 限制多端登录 + case ConcurrentLoginStrategy.LogoutFromAllDevices: + + Logger.LogDebug($"Clear all other user sessions."); + + await IdentitySessionStore.RevokeAllAsync( + session.UserId, + session.Id); + break; + } + } + } +} diff --git a/aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/IdentityServerHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/IdentityServerHttpApiHostModule.cs index 0a8f58514..b75f52138 100644 --- a/aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/IdentityServerHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/IdentityServerHttpApiHostModule.cs @@ -18,6 +18,7 @@ using LINGYUN.Abp.Saas.EntityFrameworkCore; using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.Sms.Platform; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -76,6 +77,7 @@ namespace LY.MicroService.IdentityServer; typeof(AbpLocalizationCultureMapModule), typeof(AbpIdentitySessionAspNetCoreModule), typeof(AbpAspNetCoreMvcWrapperModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpExporterMiniExcelModule), typeof(AbpClaimsMappingModule), diff --git a/aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/LY.MicroService.identityServer.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/LY.MicroService.identityServer.HttpApi.Host.csproj index 242664b85..dc975b5d6 100644 --- a/aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/LY.MicroService.identityServer.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/LY.MicroService.identityServer.HttpApi.Host.csproj @@ -63,6 +63,7 @@ + diff --git a/aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.Configure.cs b/aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.Configure.cs index b9a4dc9a0..66a6ef394 100644 --- a/aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.Configure.cs +++ b/aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.Configure.cs @@ -1,6 +1,7 @@ using DotNetCore.CAP; using LINGYUN.Abp.Account.Web; using LINGYUN.Abp.Account.Web.IdentityServer; +using LINGYUN.Abp.AspNetCore.MultiTenancy; using LINGYUN.Abp.IdentityServer.IdentityResources; using LINGYUN.Abp.Localization.CultureMap; using LINGYUN.Abp.LocalizationManagement; @@ -372,7 +373,7 @@ public partial class IdentityServerModule var domains = tenantResolveCfg.Get(); foreach (var domain in domains) { - options.AddDomainTenantResolver(domain); + options.AddOnlyDomainTenantResolver(domain); } }); } diff --git a/aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.cs b/aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.cs index 7bad8a130..b47801bd0 100644 --- a/aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.cs +++ b/aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.cs @@ -1,6 +1,7 @@ using LINGYUN.Abp.Account; using LINGYUN.Abp.Account.Web.IdentityServer; using LINGYUN.Abp.AspNetCore.HttpOverrides; +using LINGYUN.Abp.AspNetCore.MultiTenancy; using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; using LINGYUN.Abp.AuditLogging.Elasticsearch; using LINGYUN.Abp.Authentication.QQ; @@ -23,6 +24,7 @@ using LINGYUN.Abp.Localization.CultureMap; using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.Sms.Platform; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using LY.MicroService.IdentityServer.EntityFrameworkCore; using Microsoft.AspNetCore.Builder; @@ -68,8 +70,10 @@ namespace LY.MicroService.IdentityServer; typeof(AbpDataDbMigratorModule), typeof(AbpAuditLoggingElasticsearchModule), // 放在 AbpIdentity 模块之后,避免被覆盖 typeof(AbpLocalizationCultureMapModule), + typeof(AbpAspNetCoreMultiTenancyModule), typeof(AbpAspNetCoreMvcWrapperModule), typeof(AbpAspNetCoreHttpOverridesModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpExporterMiniExcelModule), typeof(AbpEmailingPlatformModule), diff --git a/aspnet-core/services/LY.MicroService.IdentityServer/LY.MicroService.IdentityServer.csproj b/aspnet-core/services/LY.MicroService.IdentityServer/LY.MicroService.IdentityServer.csproj index 4076974cd..487ae5ffa 100644 --- a/aspnet-core/services/LY.MicroService.IdentityServer/LY.MicroService.IdentityServer.csproj +++ b/aspnet-core/services/LY.MicroService.IdentityServer/LY.MicroService.IdentityServer.csproj @@ -28,10 +28,6 @@ - @@ -55,7 +51,9 @@ + + diff --git a/aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LY.MicroService.LocalizationManagement.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LY.MicroService.LocalizationManagement.HttpApi.Host.csproj index 4389e3b1f..a82e895c4 100644 --- a/aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LY.MicroService.LocalizationManagement.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LY.MicroService.LocalizationManagement.HttpApi.Host.csproj @@ -48,6 +48,7 @@ + diff --git a/aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LocalizationManagementHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LocalizationManagementHttpApiHostModule.cs index ae0f264fc..0f5efbc51 100644 --- a/aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LocalizationManagementHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LocalizationManagementHttpApiHostModule.cs @@ -2,6 +2,7 @@ using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; using LINGYUN.Abp.AuditLogging.Elasticsearch; using LINGYUN.Abp.Authorization.OrganizationUnits; +using LINGYUN.Abp.Claims.Mapping; using LINGYUN.Abp.Data.DbMigrator; using LINGYUN.Abp.Emailing.Platform; using LINGYUN.Abp.EventBus.CAP; @@ -14,6 +15,7 @@ using LINGYUN.Abp.Saas.EntityFrameworkCore; using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.Sms.Platform; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using LY.MicroService.LocalizationManagement.EntityFrameworkCore; using Microsoft.AspNetCore.Builder; @@ -56,12 +58,14 @@ namespace LY.MicroService.LocalizationManagement; typeof(AbpCachingStackExchangeRedisModule), typeof(AbpLocalizationCultureMapModule), typeof(AbpIdentitySessionAspNetCoreModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpHttpClientModule), typeof(AbpSmsPlatformModule), typeof(AbpEmailingPlatformModule), typeof(AbpAspNetCoreMvcWrapperModule), typeof(AbpAspNetCoreHttpOverridesModule), + typeof(AbpClaimsMappingModule), typeof(AbpSwashbuckleModule), typeof(AbpAutofacModule) )] diff --git a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/LY.MicroService.PlatformManagement.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/LY.MicroService.PlatformManagement.HttpApi.Host.csproj index 6332bb022..ff1d039ab 100644 --- a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/LY.MicroService.PlatformManagement.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/LY.MicroService.PlatformManagement.HttpApi.Host.csproj @@ -56,6 +56,7 @@ + @@ -63,12 +64,15 @@ + + + diff --git a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.Configure.cs b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.Configure.cs index cef8c6bd7..9c9defcbc 100644 --- a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.Configure.cs +++ b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.Configure.cs @@ -141,41 +141,6 @@ public partial class PlatformManagementHttpApiHostModule }); } - private void ConfigureOssManagement(IServiceCollection services, IConfiguration configuration) - { - var useMinio = configuration.GetValue("OssManagement:UseMinio"); - if (useMinio) - { - Configure(options => - { - options.Containers.ConfigureAll((containerName, containerConfiguration) => - { - containerConfiguration.UseMinio(minio => - { - configuration.GetSection("Minio").Bind(minio); - }); - }); - }); - services.AddMinioContainer(); - } - else - { - Configure(options => - { - options.Containers.ConfigureAll((containerName, containerConfiguration) => - { - containerConfiguration.UseFileSystem(fileSystem => - { - fileSystem.BasePath = Path.Combine( - Directory.GetCurrentDirectory(), - configuration["OssManagement:Bucket"] ?? "blobs"); - }); - }); - }); - services.AddFileSystemContainer(); - } - } - private void ConfigureExceptionHandling() { // 自定义需要处理的异常 diff --git a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.cs index bf951fef5..8e44f97c8 100644 --- a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.cs @@ -13,14 +13,18 @@ using LINGYUN.Abp.Localization.CultureMap; using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore; using LINGYUN.Abp.Notifications; using LINGYUN.Abp.OssManagement; +using LINGYUN.Abp.OssManagement.Aliyun; using LINGYUN.Abp.OssManagement.FileSystem; using LINGYUN.Abp.OssManagement.Imaging; using LINGYUN.Abp.OssManagement.Minio; +using LINGYUN.Abp.OssManagement.Nexus; using LINGYUN.Abp.OssManagement.SettingManagement; +using LINGYUN.Abp.OssManagement.Tencent; using LINGYUN.Abp.Saas.EntityFrameworkCore; using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.Sms.Aliyun; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using LINGYUN.Abp.UI.Navigation.VueVbenAdmin5; using LINGYUN.Platform; @@ -66,9 +70,11 @@ namespace LY.MicroService.PlatformManagement; typeof(AbpAspNetCoreMvcLocalizationModule), typeof(AbpUINavigationVueVbenAdmin5Module), typeof(PlatformThemeVueVbenAdminModule), - // typeof(AbpOssManagementAliyunModule), - typeof(AbpOssManagementMinioModule), // Minio存储提供者模块 - typeof(AbpOssManagementFileSystemModule), // 本地文件系统提供者模块 + typeof(AbpOssManagementAliyunModule), // 阿里云存储提供者模块 + typeof(AbpOssManagementTencentModule), // 腾讯云存储提供者模块 + typeof(AbpOssManagementNexusModule), // Nexus存储提供者模块 + typeof(AbpOssManagementMinioModule), // Minio存储提供者模块 + typeof(AbpOssManagementFileSystemModule),// 本地文件系统提供者模块 typeof(AbpOssManagementImagingModule), // 对象存储图形处理模块 typeof(AbpOssManagementApplicationModule), typeof(AbpOssManagementHttpApiModule), @@ -97,6 +103,7 @@ namespace LY.MicroService.PlatformManagement; typeof(AbpCachingStackExchangeRedisModule), typeof(AbpLocalizationCultureMapModule), typeof(AbpIdentitySessionAspNetCoreModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpHttpClientModule), typeof(AbpMailKitModule), @@ -140,7 +147,6 @@ public partial class PlatformManagementHttpApiHostModule : AbpModule ConfigureMvc(context.Services, configuration); ConfigureCors(context.Services, configuration); ConfigureSwagger(context.Services, configuration); - ConfigureOssManagement(context.Services, configuration); ConfigureDistributedLocking(context.Services, configuration); ConfigureSeedWorker(context.Services, hostingEnvironment.IsDevelopment()); ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment()); diff --git a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/appsettings.Development.json b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/appsettings.Development.json index e0398797b..962c34dd5 100644 --- a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/appsettings.Development.json +++ b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/appsettings.Development.json @@ -108,6 +108,13 @@ "Elasticsearch": { "NodeUris": "http://127.0.0.1:9200" }, + "OssManagement": { + "Provider": "FileSystem", + "FileSystem": { + "Bucket": "blobs", + "AppendContainerNameToBasePath": true + } + }, "Serilog": { "MinimumLevel": { "Default": "Information", diff --git a/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/EventBus/Local/UserSubscribeSessionExpirationEventHandler.cs b/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/EventBus/Local/UserSubscribeSessionExpirationEventHandler.cs index 870805fba..cc2deae80 100644 --- a/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/EventBus/Local/UserSubscribeSessionExpirationEventHandler.cs +++ b/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/EventBus/Local/UserSubscribeSessionExpirationEventHandler.cs @@ -19,10 +19,17 @@ public class UserSubscribeSessionExpirationEventHandler : ILocalEventHandler eventData) { + // 新用户订阅会话过期通知 await _notificationSubscriptionManager .SubscribeAsync( eventData.Entity.TenantId, new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName), IdentityNotificationNames.Session.ExpirationSession); + // 新用户订阅不活跃用户清理通知 + await _notificationSubscriptionManager + .SubscribeAsync( + eventData.Entity.TenantId, + new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName), + IdentityNotificationNames.IdentityUser.CleaningUpInactiveUsers); } } diff --git a/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/LY.MicroService.RealtimeMessage.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/LY.MicroService.RealtimeMessage.HttpApi.Host.csproj index 02423c02b..793ea0449 100644 --- a/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/LY.MicroService.RealtimeMessage.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/LY.MicroService.RealtimeMessage.HttpApi.Host.csproj @@ -56,6 +56,7 @@ + diff --git a/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/RealtimeMessageHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/RealtimeMessageHttpApiHostModule.cs index 12603407b..9e4ffc039 100644 --- a/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/RealtimeMessageHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/RealtimeMessageHttpApiHostModule.cs @@ -38,6 +38,7 @@ using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.Sms.Platform; using LINGYUN.Abp.TaskManagement.EntityFrameworkCore; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using LINGYUN.Abp.TextTemplating.EntityFrameworkCore; using LINGYUN.Abp.TextTemplating.Scriban; @@ -122,6 +123,7 @@ namespace LY.MicroService.RealtimeMessage; typeof(AbpSmsPlatformModule), typeof(AbpHttpClientModule), typeof(AbpClaimsMappingModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpAspNetCoreMvcWrapperModule), typeof(AbpAspNetCoreHttpOverridesModule), diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj index e5ffcc724..dae7860c7 100644 --- a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj @@ -50,6 +50,7 @@ + diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs index eb1f9a0ee..d4cf7af97 100644 --- a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs @@ -23,6 +23,7 @@ using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.Sms.Platform; using LINGYUN.Abp.TaskManagement; using LINGYUN.Abp.TaskManagement.EntityFrameworkCore; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using LY.MicroService.TaskManagement.EntityFrameworkCore; using Microsoft.AspNetCore.Builder; @@ -83,6 +84,7 @@ namespace LY.MicroService.TaskManagement; typeof(AbpLocalizationCultureMapModule), typeof(AbpAspNetCoreMvcWrapperModule), typeof(AbpAspNetCoreHttpOverridesModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpClaimsMappingModule), typeof(AbpCAPEventBusModule), diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/LY.MicroService.WebhooksManagement.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/LY.MicroService.WebhooksManagement.HttpApi.Host.csproj index 02eaf62aa..7a657439f 100644 --- a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/LY.MicroService.WebhooksManagement.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/LY.MicroService.WebhooksManagement.HttpApi.Host.csproj @@ -51,6 +51,7 @@ + diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.cs index ea00e0fa4..9dd5d6512 100644 --- a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.cs @@ -20,6 +20,7 @@ using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.Sms.Platform; using LINGYUN.Abp.TaskManagement.EntityFrameworkCore; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using LINGYUN.Abp.Webhooks.EventBus; using LINGYUN.Abp.Webhooks.Identity; @@ -84,6 +85,7 @@ namespace LY.MicroService.WebhooksManagement; typeof(AbpClaimsMappingModule), typeof(AbpEmailingPlatformModule), typeof(AbpSmsPlatformModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpAspNetCoreMvcWrapperModule), typeof(AbpAspNetCoreHttpOverridesModule), diff --git a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/LY.MicroService.WechatManagement.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/LY.MicroService.WechatManagement.HttpApi.Host.csproj index 358dfc4ef..4e876d8d7 100644 --- a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/LY.MicroService.WechatManagement.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/LY.MicroService.WechatManagement.HttpApi.Host.csproj @@ -33,6 +33,7 @@ + @@ -51,13 +52,17 @@ + + + + diff --git a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.Configure.cs b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.Configure.cs index e73992052..7fcfb0a18 100644 --- a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.Configure.cs +++ b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.Configure.cs @@ -42,6 +42,7 @@ using Volo.Abp.Security.Claims; using Volo.Abp.SettingManagement; using Volo.Abp.Threading; using Volo.Abp.Timing; +using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.VirtualFileSystem; namespace LY.MicroService.WechatManagement; @@ -177,6 +178,23 @@ public partial class WechatManagementHttpApiHostModule } }); } + + private void ConfigureUrls(IConfiguration configuration) + { + Configure(options => + { + var applicationConfiguration = configuration.GetSection("App:Urls:Applications"); + foreach (var appConfig in applicationConfiguration.GetChildren()) + { + options.Applications[appConfig.Key].RootUrl = appConfig["RootUrl"]; + foreach (var urlsConfig in appConfig.GetSection("Urls").GetChildren()) + { + options.Applications[appConfig.Key].Urls[urlsConfig.Key] = urlsConfig.Value; + } + } + }); + } + private void ConfigureTiming(IConfiguration configuration) { Configure(options => diff --git a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.cs index 85a179ab8..442fc1387 100644 --- a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.cs @@ -8,15 +8,19 @@ using LINGYUN.Abp.EventBus.CAP; using LINGYUN.Abp.ExceptionHandling.Emailing; using LINGYUN.Abp.Http.Client.Wrapper; using LINGYUN.Abp.Identity.Session.AspNetCore; +using LINGYUN.Abp.Identity.WeChat.Work; using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore; using LINGYUN.Abp.Saas.EntityFrameworkCore; using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using LINGYUN.Abp.WeChat.MiniProgram; using LINGYUN.Abp.WeChat.Official; using LINGYUN.Abp.WeChat.SettingManagement; using LINGYUN.Abp.WeChat.Work; +using LINGYUN.Abp.WeChat.Work.ExternalContact; +using LINGYUN.Abp.WeChat.Work.Handlers; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -30,6 +34,7 @@ using Volo.Abp.DistributedLocking; using Volo.Abp.EntityFrameworkCore.MySQL; using Volo.Abp.FeatureManagement.EntityFrameworkCore; using Volo.Abp.Http.Client.IdentityModel.Web; +using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.MailKit; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement.EntityFrameworkCore; @@ -45,11 +50,15 @@ namespace LY.MicroService.WechatManagement; typeof(AbpAspNetCoreSerilogModule), typeof(AbpWeChatWorkApplicationModule), typeof(AbpWeChatWorkHttpApiModule), + typeof(AbpWeChatWorkExternalContactModule), + typeof(AbpWeChatWorkHandlersModule), typeof(AbpWeChatOfficialApplicationModule), typeof(AbpWeChatOfficialHttpApiModule), typeof(AbpWeChatMiniProgramModule), + typeof(AbpIdentityWeChatWorkModule), typeof(AbpWeChatSettingManagementModule), typeof(AbpSaasEntityFrameworkCoreModule), + typeof(AbpIdentityEntityFrameworkCoreModule), typeof(AbpFeatureManagementEntityFrameworkCoreModule), typeof(AbpPermissionManagementEntityFrameworkCoreModule), typeof(AbpSettingManagementEntityFrameworkCoreModule), @@ -68,6 +77,7 @@ namespace LY.MicroService.WechatManagement; typeof(AbpHttpClientWrapperModule), typeof(AbpMailKitModule), typeof(AbpClaimsMappingModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpAspNetCoreMvcWrapperModule), typeof(AbpAspNetCoreHttpOverridesModule), @@ -100,6 +110,7 @@ public partial class WechatManagementHttpApiHostModule : AbpModule ConfigureFeatureManagement(); ConfigureSettingManagement(); ConfigurePermissionManagement(); + ConfigureUrls(configuration); ConfigureTiming(configuration); ConfigureCaching(configuration); ConfigureAuditing(configuration); diff --git a/aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/LY.MicroService.WorkflowManagement.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/LY.MicroService.WorkflowManagement.HttpApi.Host.csproj index 3f0007d6a..c80231ca2 100644 --- a/aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/LY.MicroService.WorkflowManagement.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/LY.MicroService.WorkflowManagement.HttpApi.Host.csproj @@ -59,6 +59,7 @@ + diff --git a/aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/WorkflowManagementHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/WorkflowManagementHttpApiHostModule.cs index b10cabe2f..5f9e0a95d 100644 --- a/aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/WorkflowManagementHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/WorkflowManagementHttpApiHostModule.cs @@ -1,5 +1,4 @@ -using Elsa; -using LINGYUN.Abp.AspNetCore.HttpOverrides; +using LINGYUN.Abp.AspNetCore.HttpOverrides; using LINGYUN.Abp.AspNetCore.Mvc.Localization; using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; using LINGYUN.Abp.AuditLogging.Elasticsearch; @@ -25,6 +24,7 @@ using LINGYUN.Abp.Saas.EntityFrameworkCore; using LINGYUN.Abp.Serilog.Enrichers.Application; using LINGYUN.Abp.Serilog.Enrichers.UniqueId; using LINGYUN.Abp.TaskManagement.EntityFrameworkCore; +using LINGYUN.Abp.Telemetry.OpenTelemetry; using LINGYUN.Abp.Telemetry.SkyWalking; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -87,6 +87,7 @@ namespace LY.MicroService.WorkflowManagement; typeof(AbpAspNetCoreMvcWrapperModule), typeof(AbpMailKitModule), typeof(AbpClaimsMappingModule), + typeof(AbpTelemetryOpenTelemetryModule), typeof(AbpTelemetrySkyWalkingModule), typeof(AbpAspNetCoreMvcNewtonsoftModule), typeof(AbpAspNetCoreHttpOverridesModule), @@ -144,7 +145,6 @@ public partial class WorkflowManagementHttpApiHostModule : AbpModule app.MapAbpStaticAssets(); app.UseRouting(); app.UseCors(); - app.UseElsaFeatures(); app.UseAuthentication(); app.UseJwtTokenMiddleware(); app.UseMultiTenancy(); @@ -163,5 +163,6 @@ public partial class WorkflowManagementHttpApiHostModule : AbpModule app.UseAuditing(); app.UseAbpSerilogEnrichers(); app.UseConfiguredEndpoints(); + app.UseHttpActivities(); } } diff --git a/aspnet-core/tests/LINGYUN.Abp.Aliyun.Tests/LINGYUN/Abp/Aliyun/AbpAliyunTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.Aliyun.Tests/LINGYUN/Abp/Aliyun/AbpAliyunTestModule.cs index 19ea7986a..25fb01f07 100644 --- a/aspnet-core/tests/LINGYUN.Abp.Aliyun.Tests/LINGYUN/Abp/Aliyun/AbpAliyunTestModule.cs +++ b/aspnet-core/tests/LINGYUN.Abp.Aliyun.Tests/LINGYUN/Abp/Aliyun/AbpAliyunTestModule.cs @@ -1,4 +1,8 @@ -using LINGYUN.Abp.Tests; +using LINGYUN.Abp.Aliyun.Features; +using LINGYUN.Abp.Tests; +using LINGYUN.Abp.Tests.Features; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; namespace LINGYUN.Abp.Aliyun @@ -8,5 +12,19 @@ namespace LINGYUN.Abp.Aliyun typeof(AbpTestsBaseModule))] public class AbpAliyunTestModule : AbpModule { + private const string UserSecretsId = "09233B21-9A8A-43A3-AA75-8D83C8A9537D"; + + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => + { + builder.AddUserSecrets(UserSecretsId); + })); + + Configure(options => + { + options.Map(AliyunFeatureNames.Enable, (_) => "true"); + }); + } } } diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN.Abp.BlobStoring.Tencent.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN.Abp.BlobStoring.Tencent.Tests.csproj new file mode 100644 index 000000000..fd33ade61 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN.Abp.BlobStoring.Tencent.Tests.csproj @@ -0,0 +1,20 @@ + + + + net9.0 + + false + Debug;Release;PostgreSQL + AnyCPU + + + + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/AbpBlobStoringTencentTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/AbpBlobStoringTencentTestBase.cs new file mode 100644 index 000000000..67fe9e7b4 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/AbpBlobStoringTencentTestBase.cs @@ -0,0 +1,8 @@ +using LINGYUN.Abp.Tests; + +namespace LINGYUN.Abp.BlobStoring.Tencent; + +public class AbpBlobStoringTencentTestBase : AbpTestsBase +{ + +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/AbpBlobStoringTencentTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/AbpBlobStoringTencentTestModule.cs new file mode 100644 index 000000000..8a0b38295 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/AbpBlobStoringTencentTestModule.cs @@ -0,0 +1,97 @@ +using COSXML; +using COSXML.Auth; +using LINGYUN.Abp.Tencent.Features; +using LINGYUN.Abp.Tencent.Settings; +using LINGYUN.Abp.Tests; +using LINGYUN.Abp.Tests.Features; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System.Linq; +using Volo.Abp; +using Volo.Abp.Autofac; +using Volo.Abp.BlobStoring; +using Volo.Abp.Modularity; +using Volo.Abp.Security.Encryption; + +namespace LINGYUN.Abp.BlobStoring.Tencent; + +[DependsOn( + typeof(AbpBlobStoringModule), + typeof(AbpBlobStoringTencentCloudModule), + typeof(AbpTestsBaseModule), + typeof(AbpAutofacModule) + )] +public class AbpBlobStoringTencentTestModule : AbpModule +{ + private string _bucketName; + private string _secretId; + private string _secretKey; + private string _region; + private string _appId; + + private IConfiguration _configuration; + + public override void ConfigureServices(ServiceConfigurationContext context) + { + _configuration = context.Services.GetConfiguration(); + + _appId = _configuration[TencentBlobProviderConfigurationNames.AppId]; + _region = _configuration[TencentBlobProviderConfigurationNames.Region]; + _bucketName = _configuration[TencentBlobProviderConfigurationNames.BucketName]; + + Configure(options => + { + options.Map(TencentCloudFeatures.BlobStoring.MaximumStreamSize, (feature) => (int.MaxValue - 1024).ToString()); + }); + + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseTencentCloud(blob => + { + blob.AppId = _appId; + blob.Region = _region; + blob.BucketName = _bucketName; + blob.CreateBucketIfNotExists = true; + }); + }); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var encryptionService = context.ServiceProvider.GetRequiredService(); + + _secretId = encryptionService.Decrypt(_configuration["Settings:" + TencentCloudSettingNames.SecretId]); + _secretKey = encryptionService.Decrypt(_configuration["Settings:" + TencentCloudSettingNames.SecretKey]); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + var configBuilder = new CosXmlConfig.Builder(); + configBuilder + .IsHttps(true) + .SetAppid(_appId) + .SetRegion(_region) + .SetDebugLog(true); + + var cred = new DefaultQCloudCredentialProvider( + _secretId, + _secretKey, + 60); + + var ossClient = new CosXmlServer(configBuilder.Build(), cred); + if (ossClient.DoesBucketExist(new COSXML.Model.Bucket.DoesBucketExistRequest(_bucketName))) + { + var bucket = ossClient.GetBucket(new COSXML.Model.Bucket.GetBucketRequest(_bucketName)); + if (bucket.listBucket.contentsList.Any()) + { + var request = new COSXML.Model.Object.DeleteMultiObjectRequest(_bucketName); + request.SetObjectKeys(bucket.listBucket.contentsList.Select(x => x.key).ToList()); + ossClient.DeleteMultiObjects(request); + } + ossClient.DeleteBucket(new COSXML.Model.Bucket.DeleteBucketRequest(_bucketName)); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/DefaultTencentBlobNamingNormalizerProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/DefaultTencentBlobNamingNormalizerProvider_Tests.cs new file mode 100644 index 000000000..0e1fedf57 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/DefaultTencentBlobNamingNormalizerProvider_Tests.cs @@ -0,0 +1,48 @@ +using Shouldly; +using Volo.Abp.BlobStoring; +using Xunit; + +namespace LINGYUN.Abp.BlobStoring.Tencent; + +public class DefaultTencentBlobNamingNormalizerProvider_Tests : AbpBlobStoringTencentTestBase +{ + private readonly IBlobNamingNormalizer _blobNamingNormalizer; + + public DefaultTencentBlobNamingNormalizerProvider_Tests() + { + _blobNamingNormalizer = GetRequiredService(); + } + + [Fact] + public void NormalizeContainerName_Lowercase() + { + var filename = "ThisIsMyContainerName"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("thisismycontainername"); + } + + [Fact] + public void NormalizeContainerName_Only_Letters_Numbers_Dash() + { + var filename = ",./this-i,./s-my-c,./ont,./ai+*/.=!@#$n^&*er-name.+/"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name"); + } + + [Fact] + public void NormalizeContainerName_Max_Length() + { + var filename = "abpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabp"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.Length.ShouldBeLessThanOrEqualTo(63); + } + + [Fact] + public void NormalizeContainerName_Max_Length_Dash() + { + var filename = "-this-is-my-container-name-abpabpabpabpabpabpabpabp-a-b-p-a--b-p-"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name-abpabpabpabpabpabpabpabp-a-b-p-a--"); + } + +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobContainer_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobContainer_Tests.cs new file mode 100644 index 000000000..bf1e9b332 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobContainer_Tests.cs @@ -0,0 +1,12 @@ +namespace LINGYUN.Abp.BlobStoring.Tencent; + + +//Please set the correct connection string in secrets.json and continue the test. +public class TencentBlobContainer_Tests : BlobContainer_Tests +{ + public TencentBlobContainer_Tests() + { + + + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobNameCalculator_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobNameCalculator_Tests.cs new file mode 100644 index 000000000..8dc65e2db --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tencent.Tests/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobNameCalculator_Tests.cs @@ -0,0 +1,55 @@ +using Shouldly; +using System; +using Volo.Abp.BlobStoring; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace LINGYUN.Abp.BlobStoring.Tencent; + +public class TencentBlobNameCalculator_Tests : AbpBlobStoringTencentTestBase +{ + private readonly ITencentBlobNameCalculator _calculator; + private readonly ICurrentTenant _currentTenant; + + private const string AliyunSeparator = "/"; + + public TencentBlobNameCalculator_Tests() + { + _calculator = GetRequiredService(); + _currentTenant = GetRequiredService(); + } + + [Fact] + public void Default_Settings() + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"host{AliyunSeparator}my-blob"); + } + + [Fact] + public void Default_Settings_With_TenantId() + { + var tenantId = Guid.NewGuid(); + + using (_currentTenant.Change(tenantId)) + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"tenants{AliyunSeparator}{tenantId:D}{AliyunSeparator}my-blob"); + } + } + + private static BlobProviderArgs GetArgs( + string containerName, + string blobName) + { + return new BlobProviderGetArgs( + containerName, + new BlobContainerConfiguration().UseTencentCloud(x => + { + }), + blobName + ); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN.Abp.BlobStoring.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN.Abp.BlobStoring.Tests.csproj new file mode 100644 index 000000000..79464a180 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN.Abp.BlobStoring.Tests.csproj @@ -0,0 +1,20 @@ + + + + net9.0 + + false + Debug;Release;PostgreSQL + AnyCPU + + + + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/AbpBlobStoringOptions_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/AbpBlobStoringOptions_Tests.cs new file mode 100644 index 000000000..f8112fa02 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/AbpBlobStoringOptions_Tests.cs @@ -0,0 +1,39 @@ +using LINGYUN.Abp.BlobStoring.Fakes; +using LINGYUN.Abp.BlobStoring.TestObjects; +using Shouldly; +using Volo.Abp.BlobStoring; +using Xunit; + +namespace LINGYUN.Abp.BlobStoring; + +public class AbpBlobStoringOptions_Tests : AbpBlobStoringTestBase +{ + private readonly IBlobContainerConfigurationProvider _configurationProvider; + + public AbpBlobStoringOptions_Tests() + { + _configurationProvider = GetRequiredService(); + } + + [Fact] + public void Should_Property_Set_And_Get_Options_For_Different_Containers() + { + var testContainer1Config = _configurationProvider.Get(); + testContainer1Config.ProviderType.ShouldBe(typeof(FakeBlobProvider1)); + testContainer1Config.GetConfigurationOrDefault("TestConfig1").ShouldBe("TestValue1"); + testContainer1Config.GetConfigurationOrDefault("TestConfigDefault").ShouldBe("TestValueDefault"); + + var testContainer2Config = _configurationProvider.Get(); + testContainer2Config.ProviderType.ShouldBe(typeof(FakeBlobProvider2)); + testContainer2Config.GetConfigurationOrNull("TestConfig2").ShouldBe("TestValue2"); + testContainer2Config.GetConfigurationOrNull("TestConfigDefault").ShouldBe("TestValueDefault"); + } + + [Fact] + public void Should_Fallback_To_Default_Configuration_If_Not_Specialized() + { + var config = _configurationProvider.Get(); + config.ProviderType.ShouldBe(typeof(FakeBlobProvider1)); + config.GetConfigurationOrNull("TestConfigDefault").ShouldBe("TestValueDefault"); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/AbpBlobStoringTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/AbpBlobStoringTestBase.cs new file mode 100644 index 000000000..1da6f37bc --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/AbpBlobStoringTestBase.cs @@ -0,0 +1,12 @@ +using Volo.Abp; +using Volo.Abp.Testing; + +namespace LINGYUN.Abp.BlobStoring; + +public abstract class AbpBlobStoringTestBase : AbpIntegratedTest +{ + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/AbpBlobStoringTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/AbpBlobStoringTestModule.cs new file mode 100644 index 000000000..b40fd6dcf --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/AbpBlobStoringTestModule.cs @@ -0,0 +1,44 @@ +using LINGYUN.Abp.BlobStoring.Fakes; +using LINGYUN.Abp.BlobStoring.TestObjects; +using LINGYUN.Abp.Tests; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Volo.Abp.Autofac; +using Volo.Abp.BlobStoring; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.BlobStoring; + +[DependsOn( + typeof(AbpBlobStoringModule), + typeof(AbpTestsBaseModule), + typeof(AbpAutofacModule) + )] +public class AbpBlobStoringTestModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddSingleton(Substitute.For()); + context.Services.AddSingleton(Substitute.For()); + + Configure(options => + { + options.Containers + .ConfigureDefault(container => + { + container.SetConfiguration("TestConfigDefault", "TestValueDefault"); + container.ProviderType = typeof(FakeBlobProvider1); + }) + .Configure(container => + { + container.SetConfiguration("TestConfig1", "TestValue1"); + container.ProviderType = typeof(FakeBlobProvider1); + }) + .Configure(container => + { + container.SetConfiguration("TestConfig2", "TestValue2"); + container.ProviderType = typeof(FakeBlobProvider2); + }); + }); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainerFactory_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainerFactory_Tests.cs new file mode 100644 index 000000000..758a36976 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainerFactory_Tests.cs @@ -0,0 +1,76 @@ +using LINGYUN.Abp.BlobStoring.Fakes; +using LINGYUN.Abp.BlobStoring.TestObjects; +using NSubstitute; +using System.Threading.Tasks; +using Volo.Abp.BlobStoring; +using Xunit; + +namespace LINGYUN.Abp.BlobStoring; + +public class BlobContainerFactory_Tests : AbpBlobStoringTestBase +{ + private readonly IBlobContainerFactory _factory; + private readonly FakeProviders _fakeProviders; + + public BlobContainerFactory_Tests() + { + _factory = GetRequiredService(); + _fakeProviders = GetRequiredService(); + } + + [Fact] + public async Task Should_Create_Containers_With_Configured_Providers() + { + // TestContainer1 with FakeBlobProvider1 + + await _fakeProviders.Provider1 + .DidNotReceiveWithAnyArgs() + .ExistsAsync(default); + + await _factory + .Create() + .ExistsAsync("TestBlob1"); + + await _fakeProviders.Provider1 + .Received(1) + .ExistsAsync(Arg.Is(args => + args.ContainerName == BlobContainerNameAttribute.GetContainerName() && + args.BlobName == "TestBlob1" + ) + ); + + // TestContainer2 with FakeBlobProvider2 + + await _fakeProviders.Provider2 + .DidNotReceiveWithAnyArgs() + .ExistsAsync(default); + + await _factory + .Create() + .ExistsAsync("TestBlob2"); + + await _fakeProviders.Provider2 + .Received(1) + .ExistsAsync(Arg.Is(args => + args.ContainerName == BlobContainerNameAttribute.GetContainerName() && + args.BlobName == "TestBlob2" + ) + ); + + // TestContainer3 with FakeBlobProvider1 (default provider) + + _fakeProviders.Provider1.ClearReceivedCalls(); + + await _factory + .Create() + .ExistsAsync("TestBlob3"); + + await _fakeProviders.Provider1 + .Received(1) + .ExistsAsync(Arg.Is(t => + t.ContainerName == BlobContainerNameAttribute.GetContainerName() && + t.BlobName == "TestBlob3" + ) + ); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainerNameAttribute_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainerNameAttribute_Tests.cs new file mode 100644 index 000000000..ad52c33d2 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainerNameAttribute_Tests.cs @@ -0,0 +1,25 @@ +using LINGYUN.Abp.BlobStoring.TestObjects; +using Shouldly; +using Volo.Abp.BlobStoring; +using Xunit; + +namespace LINGYUN.Abp.BlobStoring; + +public class BlobContainerNameAttribute_Tests +{ + [Fact] + public void Should_Get_Specified_Name() + { + BlobContainerNameAttribute + .GetContainerName() + .ShouldBe("Test2"); + } + + [Fact] + public void Should_Get_Full_Class_Name_If_Not_Specified() + { + BlobContainerNameAttribute + .GetContainerName() + .ShouldBe(typeof(TestContainer1).FullName); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainer_Injection_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainer_Injection_Tests.cs new file mode 100644 index 000000000..e5c5de339 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainer_Injection_Tests.cs @@ -0,0 +1,32 @@ +using LINGYUN.Abp.BlobStoring.TestObjects; +using Shouldly; +using Volo.Abp.BlobStoring; +using Xunit; + +namespace LINGYUN.Abp.BlobStoring; + +public class BlobContainer_Injection_Tests : AbpBlobStoringTestBase +{ + [Fact] + public void Should_Inject_DefaultContainer_For_Non_Generic_Interface() + { + GetRequiredService() + .ShouldBeOfType>(); + } + + [Fact] + public void Should_Inject_Specified_Container_For_Generic_Interface() + { + GetRequiredService>() + .ShouldBeOfType>(); + + GetRequiredService>() + .ShouldBeOfType>(); + + GetRequiredService>() + .ShouldBeOfType>(); + + GetRequiredService>() + .ShouldBeOfType>(); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainer_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainer_Tests.cs new file mode 100644 index 000000000..ef457569b --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobContainer_Tests.cs @@ -0,0 +1,143 @@ +using LINGYUN.Abp.BlobStoring.TestObjects; +using Shouldly; +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.BlobStoring; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Testing; +using Xunit; + +namespace LINGYUN.Abp.BlobStoring; + +public abstract class BlobContainer_Tests : AbpIntegratedTest + where TStartupModule : IAbpModule +{ + protected IBlobContainer Container { get; } + + protected ICurrentTenant CurrentTenant { get; } + + protected BlobContainer_Tests() + { + Container = GetRequiredService>(); + CurrentTenant = GetRequiredService(); + } + + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + [Theory] + [InlineData("test-blob-1")] + [InlineData("test-blob-1.txt")] + [InlineData("test-folder/test-blob-1")] + public async Task Should_Save_And_Get_Blobs(string blobName) + { + var testContent = "test content".GetBytes(); + await Container.SaveAsync(blobName, testContent); + + var result = await Container.GetAllBytesAsync(blobName); + result.SequenceEqual(testContent).ShouldBeTrue(); + } + + [Fact] + public async Task Should_Save_And_Get_Blobs_In_Different_Tenant() + { + var blobName = "test-blob-1"; + var testContent = "test content".GetBytes(); + + using (CurrentTenant.Change(Guid.NewGuid())) + { + await Container.SaveAsync(blobName, testContent); + (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue(); + } + + using (CurrentTenant.Change(Guid.NewGuid())) + { + await Container.SaveAsync(blobName, testContent); + (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue(); + + using (CurrentTenant.Change(null)) + { + // Could not find the requested BLOB... + await Assert.ThrowsAsync(async () => + await Container.GetAllBytesAsync(blobName) + ); + } + } + + using (CurrentTenant.Change(null)) + { + await Container.SaveAsync(blobName, testContent); + (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue(); + } + } + + [Fact] + public async Task Should_Overwrite_Pre_Saved_Blob_If_Requested() + { + var blobName = "test-blob-1"; + + var testContent = "test content".GetBytes(); + await Container.SaveAsync(blobName, testContent); + + var testContentOverwritten = "test content overwritten".GetBytes(); + await Container.SaveAsync(blobName, testContentOverwritten, true); + + var result = await Container.GetAllBytesAsync(blobName); + result.SequenceEqual(testContentOverwritten).ShouldBeTrue(); + } + + [Fact] + public async Task Should_Not_Allow_To_Overwrite_Pre_Saved_Blob_By_Default() + { + var blobName = "test-blob-1"; + + var testContent = "test content".GetBytes(); + await Container.SaveAsync(blobName, testContent); + + var testContentOverwritten = "test content overwritten".GetBytes(); + await Assert.ThrowsAsync(() => + Container.SaveAsync(blobName, testContentOverwritten) + ); + } + + [Theory] + [InlineData("test-blob-1")] + [InlineData("test-blob-1.txt")] + [InlineData("test-folder/test-blob-1")] + public async Task Should_Delete_Saved_Blobs(string blobName) + { + await Container.SaveAsync(blobName, "test content".GetBytes()); + (await Container.GetAllBytesAsync(blobName)).ShouldNotBeNull(); + + await Container.DeleteAsync(blobName); + (await Container.GetAllBytesOrNullAsync(blobName)).ShouldBeNull(); + } + + [Theory] + [InlineData("test-blob-1")] + [InlineData("test-blob-1.txt")] + [InlineData("test-folder/test-blob-1")] + public async Task Saved_Blobs_Should_Exists(string blobName) + { + await Container.SaveAsync(blobName, "test content".GetBytes()); + (await Container.ExistsAsync(blobName)).ShouldBeTrue(); + + await Container.DeleteAsync(blobName); + (await Container.ExistsAsync(blobName)).ShouldBeFalse(); + } + + [Theory] + [InlineData("test-blob-1")] + [InlineData("test-blob-1.txt")] + [InlineData("test-folder/test-blob-1")] + public async Task Unknown_Blobs_Should_Not_Exists(string blobName) + { + await Container.DeleteAsync(blobName); + (await Container.ExistsAsync(blobName)).ShouldBeFalse(); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobProviderSelector_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobProviderSelector_Tests.cs new file mode 100644 index 000000000..cbbb54fff --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/BlobProviderSelector_Tests.cs @@ -0,0 +1,30 @@ +using LINGYUN.Abp.BlobStoring.Fakes; +using LINGYUN.Abp.BlobStoring.TestObjects; +using Shouldly; +using Volo.Abp.BlobStoring; +using Xunit; + +namespace LINGYUN.Abp.BlobStoring; + +public class BlobProviderSelector_Tests : AbpBlobStoringTestBase +{ + private readonly IBlobProviderSelector _selector; + + public BlobProviderSelector_Tests() + { + _selector = GetRequiredService(); + } + + [Fact] + public void Should_Select_Default_Provider_If_Not_Configured() + { + _selector.Get().ShouldBeAssignableTo(); + } + + [Fact] + public void Should_Select_Configured_Provider() + { + _selector.Get().ShouldBeAssignableTo(); + _selector.Get().ShouldBeAssignableTo(); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/Fakes/FakeBlobProvider1.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/Fakes/FakeBlobProvider1.cs new file mode 100644 index 000000000..57850d959 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/Fakes/FakeBlobProvider1.cs @@ -0,0 +1,33 @@ +using System.IO; +using System.Threading.Tasks; +using Volo.Abp.BlobStoring; + +namespace LINGYUN.Abp.BlobStoring.Fakes; + +public class FakeBlobProvider1 : IBlobProvider +{ + public virtual Task SaveAsync(BlobProviderSaveArgs args) + { + throw new System.NotImplementedException(); + } + + public virtual Task DeleteAsync(BlobProviderDeleteArgs args) + { + throw new System.NotImplementedException(); + } + + public virtual Task ExistsAsync(BlobProviderExistsArgs args) + { + throw new System.NotImplementedException(); + } + + public virtual Task GetAsync(BlobProviderGetArgs args) + { + throw new System.NotImplementedException(); + } + + public virtual Task GetOrNullAsync(BlobProviderGetArgs args) + { + throw new System.NotImplementedException(); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/Fakes/FakeBlobProvider2.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/Fakes/FakeBlobProvider2.cs new file mode 100644 index 000000000..93bf525b6 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/Fakes/FakeBlobProvider2.cs @@ -0,0 +1,33 @@ +using System.IO; +using System.Threading.Tasks; +using Volo.Abp.BlobStoring; + +namespace LINGYUN.Abp.BlobStoring.Fakes; + +public class FakeBlobProvider2 : IBlobProvider +{ + public virtual Task SaveAsync(BlobProviderSaveArgs args) + { + throw new System.NotImplementedException(); + } + + public virtual Task DeleteAsync(BlobProviderDeleteArgs args) + { + throw new System.NotImplementedException(); + } + + public virtual Task ExistsAsync(BlobProviderExistsArgs args) + { + throw new System.NotImplementedException(); + } + + public virtual Task GetAsync(BlobProviderGetArgs args) + { + throw new System.NotImplementedException(); + } + + public virtual Task GetOrNullAsync(BlobProviderGetArgs args) + { + throw new System.NotImplementedException(); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/Fakes/FakeProviders.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/Fakes/FakeProviders.cs new file mode 100644 index 000000000..4a6864241 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/Fakes/FakeProviders.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.BlobStoring; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.BlobStoring.Fakes; + +public class FakeProviders : ISingletonDependency +{ + public FakeBlobProvider1 Provider1 { get; } + + public FakeBlobProvider2 Provider2 { get; } + + public FakeProviders(IEnumerable providers) + { + Provider1 = providers.OfType().Single(); + Provider2 = providers.OfType().Single(); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/TestObjects/TestContainer1.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/TestObjects/TestContainer1.cs new file mode 100644 index 000000000..26cfbe94d --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/TestObjects/TestContainer1.cs @@ -0,0 +1,6 @@ +namespace LINGYUN.Abp.BlobStoring.TestObjects; + +public class TestContainer1 +{ + +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/TestObjects/TestContainer2.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/TestObjects/TestContainer2.cs new file mode 100644 index 000000000..c937b90ba --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/TestObjects/TestContainer2.cs @@ -0,0 +1,9 @@ +using Volo.Abp.BlobStoring; + +namespace LINGYUN.Abp.BlobStoring.TestObjects; + +[BlobContainerName("Test2")] +public class TestContainer2 +{ + +} diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/TestObjects/TestContainer3.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/TestObjects/TestContainer3.cs new file mode 100644 index 000000000..21368073b --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Tests/LINGYUN/Abp/BlobStoring/TestObjects/TestContainer3.cs @@ -0,0 +1,6 @@ +namespace LINGYUN.Abp.BlobStoring.TestObjects; + +public class TestContainer3 +{ + +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AbpAliyunSmsTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AbpAliyunSmsTestModule.cs index 35078fdff..379ea4735 100644 --- a/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AbpAliyunSmsTestModule.cs +++ b/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AbpAliyunSmsTestModule.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.Aliyun; -using LINGYUN.Abp.Sms.Aliyun; +using LINGYUN.Abp.Aliyun.Features; +using LINGYUN.Abp.Tests.Features; using Volo.Abp.Modularity; namespace LINGYUN.Abp.Sms.Aliyun @@ -9,5 +10,12 @@ namespace LINGYUN.Abp.Sms.Aliyun typeof(AbpAliyunSmsModule))] public class AbpAliyunSmsTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Map(AliyunFeatureNames.Sms.Enable, (_) => "true"); + }); + } } } diff --git a/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeSender_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeSender_Tests.cs new file mode 100644 index 000000000..ea63851ed --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Sms.Aliyun.Tests/LINGYUN/Abp/Sms/Aliyun/AliyunSmsVerifyCodeSender_Tests.cs @@ -0,0 +1,62 @@ +using Microsoft.Extensions.Configuration; +using System.Threading.Tasks; +using Volo.Abp.Sms; +using Xunit; + +namespace LINGYUN.Abp.Sms.Aliyun; +public class AliyunSmsVerifyCodeSender_Tests : AbpAliyunTestBase +{ + protected IAliyunSmsVerifyCodeSender SmsVerifyCodeSender { get; } + protected ISmsSender SmsSender { get; } + protected IConfiguration Configuration { get; } + + public AliyunSmsVerifyCodeSender_Tests() + { + SmsSender = GetRequiredService(); + SmsVerifyCodeSender = GetRequiredService(); + Configuration = GetRequiredService(); + } + + /// + /// 阿里云短信测试 + /// + /// + /// + [Theory] + [InlineData("123456")] + public async Task Send_Sms_Verify_Code_Test(string code) + { + var signName = Configuration["Aliyun:Sms:Sender:SignName"]; + var phone = Configuration["Aliyun:Sms:Sender:PhoneNumber"]; + var template = Configuration["Aliyun:Sms:Sender:TemplateCode"]; + + await SmsVerifyCodeSender.SendAsync( + new SmsVerifyCodeMessage( + phone, + new SmsVerifyCodeMessageParam(code), + signName, + template)); + } + + /// + /// 阿里云短信测试 + /// + /// + /// + [Theory] + [InlineData("123456")] + public async Task Send_Sms_Test(string code) + { + var signName = Configuration["Aliyun:Sms:Sender:SignName"]; + var phone = Configuration["Aliyun:Sms:Sender:PhoneNumber"]; + var template = Configuration["Aliyun:Sms:Sender:TemplateCode"]; + + var message = new SmsMessage(phone, "test"); + message.Properties.Add("SignName", signName); + message.Properties.Add("SmsVerifyCode", true); + message.Properties.Add("TemplateCode", template); + message.Properties.Add("code", code); + + await SmsSender.SendAsync(message); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/GlobalUsings.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/GlobalUsings.cs new file mode 100644 index 000000000..bd8299f6f --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Xunit; +global using Shouldly; \ No newline at end of file diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests.csproj new file mode 100644 index 000000000..e65ecd7cd --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests.csproj @@ -0,0 +1,20 @@ + + + + net9.0 + + false + Debug;Release;PostgreSQL + AnyCPU + + + + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestBase.cs new file mode 100644 index 000000000..e78c65e39 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestBase.cs @@ -0,0 +1,6 @@ +using LINGYUN.Abp.Tests; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact; +public abstract class AbpWeChatWorkExternalContactTestBase : AbpTestsBase +{ +} diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestModule.cs new file mode 100644 index 000000000..ab8d15e07 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestModule.cs @@ -0,0 +1,22 @@ +using LINGYUN.Abp.Tests.Features; +using LINGYUN.Abp.WeChat.Work.ExternalContact.Features; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact; + +[DependsOn( + typeof(AbpWeChatWorkExternalContactModule), + typeof(AbpWeChatWorkTestModule))] +public class AbpWeChatWorkExternalContactTestModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Map(WeChatWorkExternalContactFeatureNames.Enable, (feature) => + { + return true.ToString(); + }); + }); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider_Tests.cs new file mode 100644 index 000000000..d5ed50793 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider_Tests.cs @@ -0,0 +1,22 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Request; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts; + +public class WeChatWorkExternalContactProvider_Tests : AbpWeChatWorkExternalContactTestBase +{ + public readonly IWeChatWorkExternalContactProvider _provider; + public WeChatWorkExternalContactProvider_Tests() + { + _provider = GetRequiredService(); + } + + [Fact] + public async virtual Task Should_Get_External_Contact_List() + { + var res = await _provider.GetExternalContactListAsync(new WeChatWorkGetExternalContactListRequest()); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider_Tests.cs new file mode 100644 index 000000000..7d96f6ade --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider_Tests.cs @@ -0,0 +1,62 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +using Microsoft.Extensions.Configuration; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers; +public class WeChatWorkCustomerProvider_Tests : AbpWeChatWorkExternalContactTestBase +{ + public readonly IWeChatWorkCustomerProvider _provider; + public readonly IConfiguration _configuration; + public WeChatWorkCustomerProvider_Tests() + { + _provider = GetRequiredService(); + _configuration = GetRequiredService(); + } + + [Fact] + public async virtual Task Should_Get_Customer_List() + { + var userId = _configuration["WeChat:Work:ExternalContact:Customers:GetCustomerList:UserId"]; + var res = await _provider.GetCustomerListAsync(userId); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } + + [Fact] + public async virtual Task Should_Bulk_Get_Customer() + { + var userId = _configuration["WeChat:Work:ExternalContact:Customers:BulkGetCustomer:UserId"]; + var res = await _provider.BulkGetCustomerAsync( + new WeChatWorkBulkGetCustomerRequest( + new List { userId })); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } + + [Fact] + public async virtual Task Should_Get_Customer() + { + var externalUserid = _configuration["WeChat:Work:ExternalContact:Customers:GetCustomer:ExternalUserid"]; + var res = await _provider.GetCustomerAsync(externalUserid); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } + + [Fact] + public async virtual Task Should_Update_Customer_Remark() + { + var userId = _configuration["WeChat:Work:ExternalContact:Customers:UpdateCustomerRemark:UserId"]; + var externalUserid = _configuration["WeChat:Work:ExternalContact:Customers:UpdateCustomerRemark:ExternalUserid"]; + var res = await _provider.UpdateCustomerRemarkAsync( + new WeChatWorkUpdateCustomerRemarkRequest( + userId, + externalUserid)); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider_Tests.cs new file mode 100644 index 000000000..56eeac713 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider_Tests.cs @@ -0,0 +1,49 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request; +using Microsoft.Extensions.Configuration; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers; +public class WeChatWorkCustomerStrategyProvider_Tests : AbpWeChatWorkExternalContactTestBase +{ + public readonly IWeChatWorkCustomerStrategyProvider _provider; + public readonly IConfiguration _configuration; + public WeChatWorkCustomerStrategyProvider_Tests() + { + _provider = GetRequiredService(); + _configuration = GetRequiredService(); + } + + [Fact] + public async virtual Task Should_Get_Customer_Strategy_List() + { + var res = await _provider.GetCustomerStrategyListAsync( + new WeChatWorkGetCustomerStrategyListRequest()); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } + + [Fact] + public async virtual Task Should_Get_Customer_Strategy() + { + var exception = await Assert.ThrowsAsync(async () => + await _provider.GetCustomerStrategyAsync( + new WeChatWorkGetCustomerStrategyRequest(0))); + + exception.Code.ShouldBe($"WeChatWork:40058"); + exception.Message.ShouldContain($"invalid strategy_id:0"); + } + + [Fact] + public async virtual Task Should_Get_Customer_Strategy_Range() + { + var exception = await Assert.ThrowsAsync(async () => + await _provider.GetCustomerStrategyRangeAsync( + new WeChatWorkGetCustomerStrategyRangeRequest(0))); + + exception.Code.ShouldBe($"WeChatWork:40058"); + exception.Message.ShouldContain($"invalid strategy_id:0"); + } + + // TODO: 其他敏感接口自行实现测试 +} diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider_Tests.cs new file mode 100644 index 000000000..37ebc3653 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider_Tests.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Follows; +public class WeChatWorkFollowUserProvider_Tests : AbpWeChatWorkExternalContactTestBase +{ + public readonly IWeChatWorkFollowUserProvider _provider; + public WeChatWorkFollowUserProvider_Tests() + { + _provider = GetRequiredService(); + } + + [Fact] + public async virtual Task Should_Get_Follow_User_List() + { + var res = await _provider.GetFollowUserListAsync(); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider_Tests.cs new file mode 100644 index 000000000..5a10debdd --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider_Tests.cs @@ -0,0 +1,47 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request; +using Microsoft.Extensions.Configuration; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats; +public class WeChatWorkGroupChatProvider_Tests : AbpWeChatWorkExternalContactTestBase +{ + public readonly IWeChatWorkGroupChatProvider _provider; + public readonly IConfiguration _configuration; + public WeChatWorkGroupChatProvider_Tests() + { + _provider = GetRequiredService(); + _configuration = GetRequiredService(); + } + + [Fact] + public async virtual Task Should_Get_Group_Chat_List() + { + var res = await _provider.GetGroupChatListAsync( + new WeChatWorkGetGroupChatListRequest()); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } + + [Fact] + public async virtual Task Should_Get_Group_Chat() + { + var chatId = _configuration["WeChat:Work:ExternalContact:GroupChats:GetGroupChat:ChatId"]; + var res = await _provider.GetGroupChatAsync( + new WeChatWorkGetGroupChatRequest(chatId, true)); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } + + [Fact] + public async virtual Task Should_OpengId_To_ChatId() + { + var opengId = _configuration["WeChat:Work:ExternalContact:GroupChats:GetGroupChat:OpengIdToChatId"]; + var res = await _provider.OpengIdToChatIdAsync( + new WeChatWorkOpengIdToChatIdRequest(opengId)); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider_Tests.cs new file mode 100644 index 000000000..a9b702fc0 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider_Tests.cs @@ -0,0 +1,90 @@ +using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request; +using Microsoft.Extensions.Configuration; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags; +public class WeChatWorkCropTagProvider_Tests : AbpWeChatWorkExternalContactTestBase +{ + public readonly IWeChatWorkCropTagProvider _provider; + public readonly IConfiguration _configuration; + public WeChatWorkCropTagProvider_Tests() + { + _provider = GetRequiredService(); + _configuration = GetRequiredService(); + } + + [Fact] + public async virtual Task Should_Get_Crop_Tag_List() + { + var res = await _provider.GetCropTagListAsync( + new WeChatWorkGetCropTagListRequest()); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } + + [Fact] + public async virtual Task Should_Create_Crop_Tag() + { + var res = await _provider.CreateCropTagAsync( + new WeChatWorkCreateCropTagRequest() + { + GroupName = "test", + }); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + + await _provider.DeleteCropTagAsync( + WeChatWorkDeleteCropTagRequest.Group(new string[1] { res.TagGroup.GroupId })); + } + + [Fact] + public async virtual Task Should_Update_Crop_Tag() + { + var createRes = await _provider.CreateCropTagAsync( + new WeChatWorkCreateCropTagRequest() + { + GroupName = "test", + }); + + var updateRes = await _provider.UpdateCropTagAsync( + new WeChatWorkUpdateCropTagRequest(createRes.TagGroup.GroupId, "test_update")); + + updateRes.ErrorCode.ShouldBe(0); + updateRes.ErrorMessage.ShouldBe("ok"); + + await _provider.DeleteCropTagAsync( + WeChatWorkDeleteCropTagRequest.Group(new string[1] { createRes.TagGroup.GroupId })); + } + + [Fact] + public async virtual Task Should_Delete_Crop_Tag() + { + var createRes = await _provider.CreateCropTagAsync( + new WeChatWorkCreateCropTagRequest() + { + GroupName = "test", + }); + + var deleteRes = await _provider.DeleteCropTagAsync( + WeChatWorkDeleteCropTagRequest.Group(new string[1] { createRes.TagGroup.GroupId })); + + deleteRes.ErrorCode.ShouldBe(0); + deleteRes.ErrorMessage.ShouldBe("ok"); + } + + [Fact] + public async virtual Task Should_Mark_Crop_Tag() + { + var userId = _configuration["WeChat:Work:ExternalContact:Tags:MarkCropTag:UserId"]; + var externalUserId = _configuration["WeChat:Work:ExternalContact:Tags:MarkCropTag:ExternalUserId"]; + + var req = new WeChatWorkMarkCropTagRequest(userId, externalUserId); + req.CreateTag.Add("test_tag"); + var res = await _provider.MarkCropTagAsync(req); + + res.ErrorCode.ShouldBe(0); + res.ErrorMessage.ShouldBe("ok"); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkTestModule.cs index 430f0b033..a95eb64f5 100644 --- a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkTestModule.cs +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkTestModule.cs @@ -17,15 +17,14 @@ namespace LINGYUN.Abp.WeChat.Work; typeof(AbpTestsBaseModule))] public class AbpWeChatWorkTestModule : AbpModule { + private const string UserSecretsId = "5709C35E-27FF-4F5A-BBFD-F451285AA012"; + public override void PreConfigureServices(ServiceConfigurationContext context) { - var configurationOptions = new AbpConfigurationBuilderOptions + context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => { - BasePath = @"D:\Projects\Development\Abp\WeChat\Work", - EnvironmentName = "Test" - }; - - context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(configurationOptions)); + builder.AddUserSecrets(UserSecretsId); + })); } public override void ConfigureServices(ServiceConfigurationContext context) @@ -56,6 +55,10 @@ public class AbpWeChatWorkTestModule : AbpModule { return true.ToString(); }); + options.Map(WeChatWorkFeatureNames.Webhook.Enable, (feature) => + { + return true.ToString(); + }); }); } } diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageSender_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageSender_Tests.cs index 39570eb6a..c1049b8b7 100644 --- a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageSender_Tests.cs +++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageSender_Tests.cs @@ -1,6 +1,5 @@ using LINGYUN.Abp.WeChat.Work.Messages; using LINGYUN.Abp.WeChat.Work.Messages.Models; -using LINGYUN.Abp.WeChat.Work.Messages.Templates; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -104,4 +103,29 @@ public class WeChatWorkMessageSender_Tests : AbpWeChatWorkTestBase var response = await Sender.SendAsync(message); response.IsSuccessed.ShouldBeTrue(); } + + [Theory] + [InlineData("cee71c5b-6e6a-478a-aafe-fe612198ffbd")] + public async Task Send_Webhook_Text_Template_Card_Message_Test(string webhookKey) + { + var message = new WeChatWorkWebhookTemplateCardMessage( + new WebhookTextNoticeCardMessage( + WebhookTemplateCardAction.Link("https://developer.work.weixin.qq.com/document/path/99110"), + new WebhookTemplateCardMainTitle("请假通过通知", "您的请假申请已通过,请前往查看!"), + source: WebhookTemplateCardSource.Black("https://wwcdn.weixin.qq.com/node/wework/images/wecom-logo.a61830413b.svg", "企业微信"), + horizontalContents: new List + { + WebhookTemplateCardHorizontalContent.Default("审批单号", "QJ20251000000136"), + WebhookTemplateCardHorizontalContent.Default("请假日期", "2025/10/01-2025/10/10"), + WebhookTemplateCardHorizontalContent.Default("通过时间", "2025-10-01 15:30:00"), + WebhookTemplateCardHorizontalContent.Default("审批备注", "做好考勤及交接事项"), + }, + jumps: new List + { + WebhookTemplateCardJump.Link("去OA查看", "https://developer.work.weixin.qq.com/document/path/99110") + })); + + var response = await Sender.SendAsync(webhookKey, message); + response.IsSuccessed.ShouldBeTrue(); + } } diff --git a/common.props b/common.props index 88563ee52..c9002ce4b 100644 --- a/common.props +++ b/common.props @@ -1,12 +1,12 @@ latest - 9.3.5 + 9.3.6 colin $(NoWarn);CS1591;CS0436;CS8618;NU1803 https://github.com/colinin/abp-next-admin $(SolutionDir)LocalNuget - 9.3.5 + 9.3.6 MIT git https://github.com/colinin/abp-next-admin