Browse Source

feat(wecom): Integrated WeCom

* See: https://developer.work.weixin.qq.com/
pull/878/head
colin 2 years ago
parent
commit
e0ca0f9b2c
  1. 1
      .gitignore
  2. 70
      aspnet-core/LINGYUN.MicroService.All.sln
  3. 4
      aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationProviderNames.cs
  4. 3
      aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/FodyWeavers.xml
  5. 30
      aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/FodyWeavers.xsd
  6. 24
      aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/LINGYUN.Abp.IdentityServer.WeChat.Work.csproj
  7. 40
      aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/LINGYUN/Abp/IdentityServer/WeChat/Work/AbpIdentityServerWeChatWorkModule.cs
  8. 8
      aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/LINGYUN/Abp/IdentityServer/WeChat/Work/Localization/Resources/en.json
  9. 8
      aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/LINGYUN/Abp/IdentityServer/WeChat/Work/Localization/Resources/zh-Hans.json
  10. 201
      aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/LINGYUN/Abp/IdentityServer/WeChat/Work/WeChatWorkGrantValidator.cs
  11. 25
      aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/README.md
  12. 3
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/FodyWeavers.xml
  13. 30
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/FodyWeavers.xsd
  14. 24
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/LINGYUN.Abp.OpenIddict.WeChat.Work.csproj
  15. 49
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/LINGYUN/Abp/OpenIddict/WeChat/Work/AbpOpenIddictWeChatWorkModule.cs
  16. 12
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/LINGYUN/Abp/OpenIddict/WeChat/Work/Localization/Resources/en.json
  17. 12
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/LINGYUN/Abp/OpenIddict/WeChat/Work/Localization/Resources/zh-Hans.json
  18. 233
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/LINGYUN/Abp/OpenIddict/WeChat/Work/WeChatWorkTokenExtensionGrant.cs
  19. 29
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/Microsoft/Extensions/DependencyInjection/WeChatWorkOpenIddictServerBuilderExtensions.cs
  20. 3
      aspnet-core/modules/wechat/LINGYUN.Abp.Identity.WeChat.Work/FodyWeavers.xml
  21. 30
      aspnet-core/modules/wechat/LINGYUN.Abp.Identity.WeChat.Work/FodyWeavers.xsd
  22. 20
      aspnet-core/modules/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN.Abp.Identity.WeChat.Work.csproj
  23. 13
      aspnet-core/modules/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/AbpIdentityWeChatWorkModule.cs
  24. 59
      aspnet-core/modules/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/WeChatWorkInternalUserFinder.cs
  25. 1
      aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.MiniProgram/LINGYUN.Abp.Notifications.WeChat.MiniProgram.csproj
  26. 3
      aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/FodyWeavers.xml
  27. 30
      aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/FodyWeavers.xsd
  28. 16
      aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN.Abp.Notifications.WeChat.Work.csproj
  29. 83
      aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/NotificationDataWeChatWorkExtensions.cs
  30. 103
      aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/NotificationDefinitionWeChatWorkExtensions.cs
  31. 18
      aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/WeChat/Work/AbpNotificationsWeChatWorkModule.cs
  32. 159
      aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/WeChat/Work/WeChatWorkNotificationPublishProvider.cs
  33. 3
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/FodyWeavers.xml
  34. 30
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/FodyWeavers.xsd
  35. 15
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN.Abp.WeChat.Work.Application.Contracts.csproj
  36. 11
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkApplicationContractsModule.cs
  37. 8
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkRemoteServiceConsts.cs
  38. 18
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeAppService.cs
  39. 10
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Message/Dto/MessageHandleInput.cs
  40. 12
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Message/Dto/MessageValidationInput.cs
  41. 30
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Message/IWeChatWorkMessageAppService.cs
  42. 25
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Models/WeChatWorkMessage.cs
  43. 3
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/FodyWeavers.xml
  44. 30
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/FodyWeavers.xsd
  45. 20
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN.Abp.WeChat.Work.Application.csproj
  46. 13
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkApplicationModule.cs
  47. 37
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeAppService.cs
  48. 62
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageAppService.cs
  49. 3
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/FodyWeavers.xml
  50. 30
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/FodyWeavers.xsd
  51. 19
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN.Abp.WeChat.Work.HttpApi.csproj
  52. 26
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkHttpApiModule.cs
  53. 42
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeController.cs
  54. 41
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageController.cs
  55. 3
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/FodyWeavers.xml
  56. 30
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/FodyWeavers.xsd
  57. 27
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN.Abp.WeChat.Work.csproj
  58. 26
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkException.cs
  59. 42
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkGlobalConsts.cs
  60. 58
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkModule.cs
  61. 41
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeGenerator.cs
  62. 31
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkInternalUserFinder.cs
  63. 20
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkUserFinder.cs
  64. 19
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Models/WeChatWorkGender.cs
  65. 77
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Models/WeChatWorkUserDetail.cs
  66. 25
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Models/WeChatWorkUserInfo.cs
  67. 30
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/NullWeChatWorkInternalUserFinder.cs
  68. 23
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Request/WeChatWorkUserDetailRequest.cs
  69. 35
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Response/WeChatWorkResponseExtensions.cs
  70. 69
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Response/WeChatWorkUserDetailResponse.cs
  71. 22
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Response/WeChatWorkUserInfoResponse.cs
  72. 78
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeGenerator.cs
  73. 55
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkUserFinder.cs
  74. 46
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureDefinitionProvider.cs
  75. 27
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureNames.cs
  76. 56
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Localization/Resources/en.json
  77. 56
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Localization/Resources/zh-Hans.json
  78. 8
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Localization/WeChatWorkResource.cs
  79. 59
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Media/IWeChatWorkMediaProvider.cs
  80. 11
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Media/WeChatWorkImageResponse.cs
  81. 117
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Media/WeChatWorkMediaProvider.cs
  82. 15
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Media/WeChatWorkMediaRequest.cs
  83. 27
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Media/WeChatWorkMediaResponse.cs
  84. 19
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/IWeChatWorkMessageSender.cs
  85. 22
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/MarkdownMessage.cs
  86. 22
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/MediaMessage.cs
  87. 87
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/MpNewMessage.cs
  88. 88
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/NewMessage.cs
  89. 51
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/TextCardMessage.cs
  90. 23
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/TextMessage.cs
  91. 42
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/VideoMessage.cs
  92. 46
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkFileMessage.cs
  93. 54
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkImageMessage.cs
  94. 37
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMarkdownMessage.cs
  95. 66
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessage.cs
  96. 11
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageRequest.cs
  97. 47
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageResponse.cs
  98. 59
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageSender.cs
  99. 55
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMpNewMessage.cs
  100. 45
      aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkNewMessage.cs

1
.gitignore

@ -7,6 +7,7 @@ obj
bin
Logs
appsettings.Production.json
appsettings.secrets.json
tempkey.jwk
.vs
Publish

70
aspnet-core/LINGYUN.MicroService.All.sln

@ -649,6 +649,26 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OssManagement.F
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OssManagement.FileSystem.Imaging.ImageSharp", "modules\oss-management\LINGYUN.Abp.OssManagement.FileSystem.Imaging.ImageSharp\LINGYUN.Abp.OssManagement.FileSystem.Imaging.ImageSharp.csproj", "{5177C729-7666-4A6C-9D54-D7E5DEF0E857}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WeChat.Work", "modules\wechat\LINGYUN.Abp.WeChat.Work\LINGYUN.Abp.WeChat.Work.csproj", "{E4CEED06-B8E9-41FA-82BF-5401AE101C4B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WeChat.Work.Tests", "tests\LINGYUN.Abp.WeChat.Work.Tests\LINGYUN.Abp.WeChat.Work.Tests.csproj", "{9BDE2F3E-6A95-47AC-9BBC-EC8570F2925A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WeChat.Work.AspNetCore", "modules\wechat\LINGYUN.Abp.WeChat.Work.AspNetCore\LINGYUN.Abp.WeChat.Work.AspNetCore.csproj", "{0C7B2C1B-CB57-4A89-B73F-ECB579FFBC81}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WeChat.Work.Application.Contracts", "modules\wechat\LINGYUN.Abp.WeChat.Work.Application.Contracts\LINGYUN.Abp.WeChat.Work.Application.Contracts.csproj", "{B4C8056F-7325-4DB1-9F09-A6F37B052192}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WeChat.Work.Application", "modules\wechat\LINGYUN.Abp.WeChat.Work.Application\LINGYUN.Abp.WeChat.Work.Application.csproj", "{D5AEBB8E-713C-4DD2-BA18-7B0B48489901}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WeChat.Work.HttpApi", "modules\wechat\LINGYUN.Abp.WeChat.Work.HttpApi\LINGYUN.Abp.WeChat.Work.HttpApi.csproj", "{CC07A6C2-CD79-4A1E-BE65-C6444AC89C2C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.IdentityServer.WeChat.Work", "modules\identityServer\LINGYUN.Abp.IdentityServer.WeChat.Work\LINGYUN.Abp.IdentityServer.WeChat.Work.csproj", "{DEDB69A9-657F-4B8B-81A7-4ADB19664F35}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.OpenIddict.WeChat.Work", "modules\openIddict\LINGYUN.Abp.OpenIddict.WeChat.Work\LINGYUN.Abp.OpenIddict.WeChat.Work.csproj", "{2C86306D-D626-41F8-BA3C-5C9B4123CE7D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Notifications.WeChat.Work", "modules\wechat\LINGYUN.Abp.Notifications.WeChat.Work\LINGYUN.Abp.Notifications.WeChat.Work.csproj", "{2DC43D15-F20F-44EC-B3A3-47BD8BBB50CA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Identity.WeChat.Work", "modules\wechat\LINGYUN.Abp.Identity.WeChat.Work\LINGYUN.Abp.Identity.WeChat.Work.csproj", "{3E32DBDA-1C63-42B4-85D1-E84BBD072D89}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1663,6 +1683,46 @@ Global
{5177C729-7666-4A6C-9D54-D7E5DEF0E857}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5177C729-7666-4A6C-9D54-D7E5DEF0E857}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5177C729-7666-4A6C-9D54-D7E5DEF0E857}.Release|Any CPU.Build.0 = Release|Any CPU
{E4CEED06-B8E9-41FA-82BF-5401AE101C4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E4CEED06-B8E9-41FA-82BF-5401AE101C4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4CEED06-B8E9-41FA-82BF-5401AE101C4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4CEED06-B8E9-41FA-82BF-5401AE101C4B}.Release|Any CPU.Build.0 = Release|Any CPU
{9BDE2F3E-6A95-47AC-9BBC-EC8570F2925A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9BDE2F3E-6A95-47AC-9BBC-EC8570F2925A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9BDE2F3E-6A95-47AC-9BBC-EC8570F2925A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9BDE2F3E-6A95-47AC-9BBC-EC8570F2925A}.Release|Any CPU.Build.0 = Release|Any CPU
{0C7B2C1B-CB57-4A89-B73F-ECB579FFBC81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C7B2C1B-CB57-4A89-B73F-ECB579FFBC81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C7B2C1B-CB57-4A89-B73F-ECB579FFBC81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C7B2C1B-CB57-4A89-B73F-ECB579FFBC81}.Release|Any CPU.Build.0 = Release|Any CPU
{B4C8056F-7325-4DB1-9F09-A6F37B052192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4C8056F-7325-4DB1-9F09-A6F37B052192}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4C8056F-7325-4DB1-9F09-A6F37B052192}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4C8056F-7325-4DB1-9F09-A6F37B052192}.Release|Any CPU.Build.0 = Release|Any CPU
{D5AEBB8E-713C-4DD2-BA18-7B0B48489901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5AEBB8E-713C-4DD2-BA18-7B0B48489901}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5AEBB8E-713C-4DD2-BA18-7B0B48489901}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5AEBB8E-713C-4DD2-BA18-7B0B48489901}.Release|Any CPU.Build.0 = Release|Any CPU
{CC07A6C2-CD79-4A1E-BE65-C6444AC89C2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC07A6C2-CD79-4A1E-BE65-C6444AC89C2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC07A6C2-CD79-4A1E-BE65-C6444AC89C2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC07A6C2-CD79-4A1E-BE65-C6444AC89C2C}.Release|Any CPU.Build.0 = Release|Any CPU
{DEDB69A9-657F-4B8B-81A7-4ADB19664F35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DEDB69A9-657F-4B8B-81A7-4ADB19664F35}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEDB69A9-657F-4B8B-81A7-4ADB19664F35}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEDB69A9-657F-4B8B-81A7-4ADB19664F35}.Release|Any CPU.Build.0 = Release|Any CPU
{2C86306D-D626-41F8-BA3C-5C9B4123CE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C86306D-D626-41F8-BA3C-5C9B4123CE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C86306D-D626-41F8-BA3C-5C9B4123CE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C86306D-D626-41F8-BA3C-5C9B4123CE7D}.Release|Any CPU.Build.0 = Release|Any CPU
{2DC43D15-F20F-44EC-B3A3-47BD8BBB50CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2DC43D15-F20F-44EC-B3A3-47BD8BBB50CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2DC43D15-F20F-44EC-B3A3-47BD8BBB50CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2DC43D15-F20F-44EC-B3A3-47BD8BBB50CA}.Release|Any CPU.Build.0 = Release|Any CPU
{3E32DBDA-1C63-42B4-85D1-E84BBD072D89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E32DBDA-1C63-42B4-85D1-E84BBD072D89}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E32DBDA-1C63-42B4-85D1-E84BBD072D89}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E32DBDA-1C63-42B4-85D1-E84BBD072D89}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1977,6 +2037,16 @@ Global
{2F49E870-DAE2-4D89-98CA-46BBD91C68E2} = {59627844-A66A-46AC-B882-E8F302D0EC24}
{6C8489F4-68B5-4CBC-8463-010C71C23245} = {B05CB08F-C088-4D6D-97EE-A94A5D1AE4A6}
{5177C729-7666-4A6C-9D54-D7E5DEF0E857} = {B05CB08F-C088-4D6D-97EE-A94A5D1AE4A6}
{E4CEED06-B8E9-41FA-82BF-5401AE101C4B} = {DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21}
{9BDE2F3E-6A95-47AC-9BBC-EC8570F2925A} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F}
{0C7B2C1B-CB57-4A89-B73F-ECB579FFBC81} = {DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21}
{B4C8056F-7325-4DB1-9F09-A6F37B052192} = {DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21}
{D5AEBB8E-713C-4DD2-BA18-7B0B48489901} = {DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21}
{CC07A6C2-CD79-4A1E-BE65-C6444AC89C2C} = {DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21}
{DEDB69A9-657F-4B8B-81A7-4ADB19664F35} = {0439B173-F41E-4CE0-A44A-CCB70328F272}
{2C86306D-D626-41F8-BA3C-5C9B4123CE7D} = {83E698F6-F8CD-4604-AB80-01A203389501}
{2DC43D15-F20F-44EC-B3A3-47BD8BBB50CA} = {DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21}
{3E32DBDA-1C63-42B4-85D1-E84BBD072D89} = {DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718}

4
aspnet-core/modules/common/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationProviderNames.cs

@ -21,4 +21,8 @@ public static class NotificationProviderNames
/// 微信小程序模板通知
/// </summary>
public const string WechatMiniProgram = "WeChat.MiniProgram";
/// <summary>
/// 企业微信应用消息
/// </summary>
public const string WechatWork = "WeChat.Work";
}

3
aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/FodyWeavers.xml

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

30
aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/FodyWeavers.xsd

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

24
aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/LINGYUN.Abp.IdentityServer.WeChat.Work.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<None Remove="LINGYUN\Abp\IdentityServer\WeChat\Work\Localization\Resources\*.json" />
<EmbeddedResource Include="LINGYUN\Abp\IdentityServer\WeChat\Work\Localization\Resources\*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.IdentityServer.Domain" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\wechat\LINGYUN.Abp.WeChat.Work\LINGYUN.Abp.WeChat.Work.csproj" />
</ItemGroup>
</Project>

40
aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/LINGYUN/Abp/IdentityServer/WeChat/Work/AbpIdentityServerWeChatWorkModule.cs

@ -0,0 +1,40 @@
using LINGYUN.Abp.WeChat.Work;
using LINGYUN.Abp.WeChat.Work.Localization;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Identity.Localization;
using Volo.Abp.IdentityServer;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.IdentityServer.WeChat.Work;
[DependsOn(
typeof(AbpIdentityServerDomainModule),
typeof(AbpWeChatWorkModule))]
public class AbpIdentityServerWeChatWorkModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<IIdentityServerBuilder>(builder =>
{
builder.AddExtensionGrantValidator<WeChatWorkGrantValidator>();
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpIdentityServerWeChatWorkModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<WeChatWorkResource>()
.AddBaseTypes(typeof(IdentityResource))
.AddVirtualJson("/LINGYUN/Abp/IdentityServer/WeChat/Work/Localization/Resources");
});
}
}

8
aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/LINGYUN/Abp/IdentityServer/WeChat/Work/Localization/Resources/en.json

@ -0,0 +1,8 @@
{
"culture": "en",
"texts": {
"InvalidGrant:GrantTypeInvalid": "The authorization type that is not allowed!",
"InvalidGrant:AgentIdOrCodeNotFound": "Enterprise identity(AgentId) not found or user cancelled login!",
"InvalidGrant:UserIdNotRegister": "Enterprise users are not registered!"
}
}

8
aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/LINGYUN/Abp/IdentityServer/WeChat/Work/Localization/Resources/zh-Hans.json

@ -0,0 +1,8 @@
{
"culture": "zh-Hans",
"texts": {
"InvalidGrant:GrantTypeInvalid": "不被允许的授权类型!",
"InvalidGrant:AgentIdOrCodeNotFound": "企业标识未找到或用户取消登录!",
"InvalidGrant:UserIdNotRegister": "企业用户未注册!"
}
}

201
aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/LINGYUN/Abp/IdentityServer/WeChat/Work/WeChatWorkGrantValidator.cs

@ -0,0 +1,201 @@
using IdentityModel;
using IdentityServer4.Events;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Validation;
using LINGYUN.Abp.WeChat.Work;
using LINGYUN.Abp.WeChat.Work.Authorize;
using LINGYUN.Abp.WeChat.Work.Localization;
using LINGYUN.Abp.WeChat.Work.Settings;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp.Guids;
using Volo.Abp.Identity;
using Volo.Abp.IdentityServer;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Settings;
using Volo.Abp.Uow;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
namespace LINGYUN.Abp.IdentityServer.WeChat.Work;
public class WeChatWorkGrantValidator : IExtensionGrantValidator
{
public string GrantType => AbpWeChatWorkGlobalConsts.GrantType;
protected ILogger<WeChatWorkGrantValidator> Logger { get; }
protected IEventService EventService { get; }
protected UserManager<IdentityUser> UserManager { get; }
protected IdentitySecurityLogManager IdentitySecurityLogManager { get; }
protected ICurrentTenant CurrentTenant { get; }
protected ISettingProvider SettingProvider { get; }
protected IGuidGenerator GuidGenerator { get; }
protected IStringLocalizer<WeChatWorkResource> WeChatWorkLocalizer { get; }
protected IWeChatWorkUserFinder WeChatWorkUserFinder { get; }
public WeChatWorkGrantValidator(
IEventService eventService,
ICurrentTenant currentTenant,
IGuidGenerator guidGenerator,
ISettingProvider settingProvider,
UserManager<IdentityUser> userManager,
IdentitySecurityLogManager identitySecurityLogManager,
IStringLocalizer<WeChatWorkResource> weChatWorkLocalizer,
IWeChatWorkUserFinder weChatWorkUserFinder,
ILogger<WeChatWorkGrantValidator> logger)
{
Logger = logger;
EventService = eventService;
CurrentTenant = currentTenant;
GuidGenerator = guidGenerator;
SettingProvider = settingProvider;
UserManager = userManager;
IdentitySecurityLogManager = identitySecurityLogManager;
WeChatWorkLocalizer = weChatWorkLocalizer;
WeChatWorkUserFinder = weChatWorkUserFinder;
}
[UnitOfWork]
public async virtual Task ValidateAsync(ExtensionGrantValidationContext context)
{
var raw = context.Request.Raw;
var credential = raw.Get(OidcConstants.TokenRequest.GrantType);
if (credential == null || !credential.Equals(GrantType))
{
Logger.LogInformation("Invalid grant type: not allowed");
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, WeChatWorkLocalizer["InvalidGrant:GrantTypeInvalid"]);
return;
}
var agentId = raw.Get(AbpWeChatWorkGlobalConsts.AgentId);
var code = raw.Get(AbpWeChatWorkGlobalConsts.Code);
if (agentId.IsNullOrWhiteSpace() || code.IsNullOrWhiteSpace())
{
Logger.LogInformation("Invalid grant type: agentId or code not found");
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, WeChatWorkLocalizer["InvalidGrant:AgentIdOrCodeNotFound"]);
return;
}
try
{
var userInfo = await WeChatWorkUserFinder.GetUserInfoAsync(agentId, code);
var currentUser = await UserManager.FindByLoginAsync(AbpWeChatWorkGlobalConsts.ProviderName, userInfo.UserId);
if (currentUser == null)
{
// TODO 检查启用用户注册是否有必要引用账户模块
if (!await SettingProvider.IsTrueAsync("Abp.Account.IsSelfRegistrationEnabled") ||
!await SettingProvider.IsTrueAsync(WeChatWorkSettingNames.EnabledQuickLogin))
{
Logger.LogWarning("Invalid grant type: wechat work user not register", userInfo.UserId);
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, WeChatWorkLocalizer["InvalidGrant:UserIdNotRegister"]);
return;
}
var userName = "wxid-work" + userInfo.UserId.ToMd5().ToLower();
var userEmail = $"{userName}@{CurrentTenant.Name ?? "default"}.io";
currentUser = new IdentityUser(GuidGenerator.Create(), userName, userEmail, CurrentTenant.Id);
(await UserManager.CreateAsync(currentUser)).CheckErrors();
(await UserManager.AddLoginAsync(
currentUser,
new UserLoginInfo(
AbpWeChatWorkGlobalConsts.ProviderName,
userInfo.UserId,
AbpWeChatWorkGlobalConsts.DisplayName))).CheckErrors();
}
if (await UserManager.IsLockedOutAsync(currentUser))
{
Logger.LogInformation("Authentication failed for username: {username}, reason: locked out", currentUser.UserName);
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, WeChatWorkLocalizer["Volo.Abp.Identity:UserLockedOut"]);
await SaveSecurityLogAsync(context, currentUser, IdentityServerSecurityLogActionConsts.LoginLockedout);
return;
}
await EventService.RaiseAsync(new UserLoginSuccessEvent(AbpWeChatWorkGlobalConsts.ProviderName, userInfo.UserId, null));
// 登录之后需要更新安全令牌
(await UserManager.UpdateSecurityStampAsync(currentUser)).CheckErrors();
await SetSuccessResultAsync(context, currentUser);
}
catch (AbpWeChatWorkException wwe)
{
Logger.LogInformation("Invalid get user info: {message}", wwe.Message);
var error = WeChatWorkLocalizer[wwe.Code];
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, error.ResourceNotFound ? wwe.Code : error.Value);
return;
}
}
protected async virtual Task SetSuccessResultAsync(ExtensionGrantValidationContext context, IdentityUser user)
{
var sub = await UserManager.GetUserIdAsync(user);
Logger.LogInformation("Credentials validated for username: {username}", user.UserName);
var additionalClaims = new List<Claim>();
await AddCustomClaimsAsync(additionalClaims, user, context);
context.Result = new GrantValidationResult(
sub,
AbpWeChatWorkGlobalConsts.AuthenticationMethod,
additionalClaims.ToArray()
);
await SaveSecurityLogAsync(
context,
user,
IdentityServerSecurityLogActionConsts.LoginSucceeded);
}
protected async virtual Task SaveSecurityLogAsync(
ExtensionGrantValidationContext context,
IdentityUser user,
string action)
{
var logContext = new IdentitySecurityLogContext
{
Identity = IdentityServerSecurityLogIdentityConsts.IdentityServer,
Action = action,
UserName = user.UserName,
ClientId = await FindClientIdAsync(context)
};
logContext.WithProperty("GrantType", GrantType);
await IdentitySecurityLogManager.SaveAsync(logContext);
}
protected virtual Task<string> FindClientIdAsync(ExtensionGrantValidationContext context)
{
return Task.FromResult(context.Request?.Client?.ClientId);
}
protected virtual Task AddCustomClaimsAsync(
List<Claim> customClaims,
IdentityUser user,
ExtensionGrantValidationContext context)
{
if (user.TenantId.HasValue)
{
customClaims.Add(
new Claim(
AbpClaimTypes.TenantId,
user.TenantId?.ToString()
)
);
}
return Task.CompletedTask;
}
}

25
aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat.Work/README.md

@ -0,0 +1,25 @@
# LINGYUN.Abp.IdentityServer.WeChat.Work
企业微信扩展登录集成
## 配置使用
```csharp
[DependsOn(typeof(AbpIdentityServerWeChatWorkModule))]
public class YouProjectModule : AbpModule
{
// other
}
```
```shell
curl -X POST "http://127.0.0.1:44385/connect/token" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=wx-work' \
--data-urlencode 'client_id=你的客户端标识' \
--data-urlencode 'client_secret=你的客户端密钥' \
--data-urlencode 'agent_id=你的企业微信应用标识' \
--data-urlencode 'code=用户扫描登录二维码后重定向页面携带的code标识, 换取用户信息的关键' \
```

3
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/FodyWeavers.xml

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

30
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/FodyWeavers.xsd

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

24
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/LINGYUN.Abp.OpenIddict.WeChat.Work.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<None Remove="LINGYUN\Abp\OpenIddict\WeChat\Work\Localization\Resources\*.json" />
<EmbeddedResource Include="LINGYUN\Abp\OpenIddict\WeChat\Work\Localization\Resources\*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.OpenIddict.AspNetCore" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\wechat\LINGYUN.Abp.WeChat.Work\LINGYUN.Abp.WeChat.Work.csproj" />
</ItemGroup>
</Project>

49
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/LINGYUN/Abp/OpenIddict/WeChat/Work/AbpOpenIddictWeChatWorkModule.cs

@ -0,0 +1,49 @@
using LINGYUN.Abp.WeChat.Work;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.OpenIddict;
using Volo.Abp.OpenIddict.ExtensionGrantTypes;
using Volo.Abp.OpenIddict.Localization;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.OpenIddict.WeChat.Work;
[DependsOn(
typeof(AbpWeChatWorkModule),
typeof(AbpOpenIddictAspNetCoreModule))]
public class AbpOpenIddictWeChatWorkModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder
.AllowWeChatWorkFlow()
.RegisterWeChatWorkScopes()
.RegisterWeChatWorkClaims();
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpOpenIddictExtensionGrantsOptions>(options =>
{
options.Grants.TryAdd(
AbpWeChatWorkGlobalConsts.GrantType,
new WeChatWorkTokenExtensionGrant());
});
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpOpenIddictWeChatWorkModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<AbpOpenIddictResource>()
.AddVirtualJson("/LINGYUN/Abp/OpenIddict/WeChat/Work/Localization/Resources");
});
}
}

12
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/LINGYUN/Abp/OpenIddict/WeChat/Work/Localization/Resources/en.json

@ -0,0 +1,12 @@
{
"culture": "en",
"texts": {
"MiniProgramAuthorizationDisabledMessage": "Applet authorization is not enabled for the application",
"OfficialAuthorizationDisabledMessage": "Official authorization is not enabled for the application",
"SelfRegistrationDisabledMessage": "Self-registration is disabled for this application. Please contact the application administrator to register a new user.",
"InvalidGrant:GrantTypeInvalid": "The type of authorization that is not allowed!",
"InvalidGrant:WeChatTokenInvalid": "WeChat authentication failed!",
"InvalidGrant:WeChatCodeNotFound": "The code obtained when WeChat is logged in is empty or does not exist!",
"InvalidGrant:WeChatNotRegister": "User WeChat account not registed!"
}
}

12
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/LINGYUN/Abp/OpenIddict/WeChat/Work/Localization/Resources/zh-Hans.json

@ -0,0 +1,12 @@
{
"culture": "zh-Hans",
"texts": {
"MiniProgramAuthorizationDisabledMessage": "应用程序未开放小程序授权",
"OfficialAuthorizationDisabledMessage": "应用程序未开放公众平台授权",
"SelfRegistrationDisabledMessage": "应用程序未开放注册,请联系管理员添加新用户.",
"InvalidGrant:GrantTypeInvalid": "不被允许的授权类型!",
"InvalidGrant:WeChatTokenInvalid": "微信认证失败!",
"InvalidGrant:WeChatCodeNotFound": "微信登录时获取的 code 为空或不存在!",
"InvalidGrant:WeChatNotRegister": "用户微信账号未绑定!"
}
}

233
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/LINGYUN/Abp/OpenIddict/WeChat/Work/WeChatWorkTokenExtensionGrant.cs

@ -0,0 +1,233 @@
using LINGYUN.Abp.WeChat.Work;
using LINGYUN.Abp.WeChat.Work.Authorize;
using LINGYUN.Abp.WeChat.Work.Security.Claims;
using LINGYUN.Abp.WeChat.Work.Settings;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp.Guids;
using Volo.Abp.Identity;
using Volo.Abp.Identity.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.OpenIddict;
using Volo.Abp.OpenIddict.ExtensionGrantTypes;
using Volo.Abp.OpenIddict.Localization;
using Volo.Abp.Settings;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
using SignInResult = Microsoft.AspNetCore.Mvc.SignInResult;
namespace LINGYUN.Abp.OpenIddict.WeChat.Work;
public class WeChatWorkTokenExtensionGrant : ITokenExtensionGrant
{
public string Name => AbpWeChatWorkGlobalConsts.GrantType;
public async virtual Task<IActionResult> HandleAsync(ExtensionGrantContext context)
{
await CheckFeatureAsync(context);
return await HandleWeChatAsync(context);
}
protected async virtual Task<IActionResult> HandleWeChatAsync(ExtensionGrantContext context)
{
var logger = GetRequiredService<ILogger<WeChatWorkTokenExtensionGrant>>(context);
var localizer = GetRequiredService<IStringLocalizer<AbpOpenIddictResource>>(context);
var agentId = context.Request.GetParameter(AbpWeChatWorkGlobalConsts.AgentId)?.ToString();
var code = context.Request.GetParameter(AbpWeChatWorkGlobalConsts.Code)?.ToString();
if (agentId.IsNullOrWhiteSpace() || code.IsNullOrWhiteSpace())
{
logger.LogWarning("Invalid grant type: agentId or code not found");
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = localizer["InvalidGrant:AgentIdOrCodeNotFound"]
});
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
var userFinder = GetRequiredService<IWeChatWorkUserFinder>(context);
try
{
var userInfo = await userFinder.GetUserInfoAsync(agentId, code);
var userManager = GetRequiredService<IdentityUserManager>(context);
var currentUser = await userManager.FindByLoginAsync(AbpWeChatWorkGlobalConsts.ProviderName, userInfo.UserId);
if (currentUser == null)
{
var currentTenant = GetRequiredService<ICurrentTenant>(context);
var settingProvider = GetRequiredService<ISettingProvider>(context);
var guidGenerator = GetRequiredService<IGuidGenerator>(context);
// TODO 检查启用用户注册是否有必要引用账户模块
if (!await settingProvider.IsTrueAsync("Abp.Account.IsSelfRegistrationEnabled") ||
!await settingProvider.IsTrueAsync(WeChatWorkSettingNames.EnabledQuickLogin))
{
logger.LogWarning("Invalid grant type: wechat work user not register", userInfo.UserId);
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = localizer["InvalidGrant:UserIdNotRegister"]
});
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
var userName = "wxid-work" + userInfo.UserId.ToMd5().ToLower();
var userEmail = $"{userName}@{currentTenant.Name ?? "default"}.io";
currentUser = new IdentityUser(guidGenerator.Create(), userName, userEmail, currentTenant.Id);
(await userManager.CreateAsync(currentUser)).CheckErrors();
(await userManager.AddLoginAsync(
currentUser,
new UserLoginInfo(
AbpWeChatWorkGlobalConsts.ProviderName,
userInfo.UserId,
AbpWeChatWorkGlobalConsts.DisplayName))).CheckErrors();
}
// 检查是否已锁定
if (await userManager.IsLockedOutAsync(currentUser))
{
var identityLocalizer = GetRequiredService<IStringLocalizer<IdentityResource>>(context);
logger.LogInformation("Authentication failed for username: {username}, reason: locked out", currentUser.UserName);
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = identityLocalizer["Volo.Abp.Identity:UserLockedOut"]
});
await SaveSecurityLogAsync(context, currentUser, OpenIddictSecurityLogActionConsts.LoginLockedout);
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
// 登录之后需要更新安全令牌
(await userManager.UpdateSecurityStampAsync(currentUser)).CheckErrors();
return await SetSuccessResultAsync(context, currentUser, userInfo.UserId, logger);
}
catch (AbpWeChatWorkException wwe)
{
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = wwe.Code
});
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
}
protected virtual Task CheckFeatureAsync(ExtensionGrantContext context)
{
return Task.CompletedTask;
}
protected virtual T GetRequiredService<T>(ExtensionGrantContext context)
{
return context.HttpContext.RequestServices.GetRequiredService<T>();
}
protected async virtual Task<IActionResult> SetSuccessResultAsync(
ExtensionGrantContext context,
IdentityUser user,
string userId,
ILogger<WeChatWorkTokenExtensionGrant> logger)
{
logger.LogInformation("Credentials validated for username: {username}", user.UserName);
var signInManager = GetRequiredService<SignInManager<IdentityUser>>(context);
var principal = await signInManager.CreateUserPrincipalAsync(user);
principal.SetScopes(context.Request.GetScopes());
principal.SetResources(await GetResourcesAsync(context));
principal.AddClaim(AbpWeChatWorkClaimTypes.UserId, userId);
await SetClaimsDestinationsAsync(context, principal);
await SaveSecurityLogAsync(
context,
user,
OpenIddictSecurityLogActionConsts.LoginSucceeded);
return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal);
}
protected async virtual Task SaveSecurityLogAsync(
ExtensionGrantContext context,
IdentityUser user,
string action)
{
var logContext = new IdentitySecurityLogContext
{
Identity = OpenIddictSecurityLogIdentityConsts.OpenIddict,
Action = action,
UserName = user.UserName,
ClientId = await FindClientIdAsync(context)
};
logContext.WithProperty("GrantType", Name);
logContext.WithProperty("Provider", AbpWeChatWorkGlobalConsts.ProviderName);
logContext.WithProperty("Method", AbpWeChatWorkGlobalConsts.AuthenticationMethod);
var identitySecurityLogManager = GetRequiredService<IdentitySecurityLogManager>(context);
await identitySecurityLogManager.SaveAsync(logContext);
}
protected virtual Task<string> FindClientIdAsync(ExtensionGrantContext context)
{
return Task.FromResult(context.Request.ClientId);
}
protected async virtual Task SetClaimsDestinationsAsync(ExtensionGrantContext context, ClaimsPrincipal principal)
{
var openIddictClaimsPrincipalManager = GetRequiredService<AbpOpenIddictClaimsPrincipalManager>(context);
await openIddictClaimsPrincipalManager.HandleAsync(context.Request, principal);
}
protected async virtual Task<IEnumerable<string>> GetResourcesAsync(ExtensionGrantContext context)
{
var scopes = context.Request.GetScopes();
var resources = new List<string>();
if (!scopes.Any())
{
return resources;
}
var scopeManager = GetRequiredService<IOpenIddictScopeManager>(context);
await foreach (var resource in scopeManager.ListResourcesAsync(scopes))
{
resources.Add(resource);
}
return resources;
}
public virtual ForbidResult Forbid(AuthenticationProperties properties, params string[] authenticationSchemes)
{
return new ForbidResult(
authenticationSchemes,
properties);
}
}

29
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat.Work/Microsoft/Extensions/DependencyInjection/WeChatWorkOpenIddictServerBuilderExtensions.cs

@ -0,0 +1,29 @@
using LINGYUN.Abp.WeChat.Work;
using LINGYUN.Abp.WeChat.Work.Security.Claims;
namespace Microsoft.Extensions.DependencyInjection;
public static class WeChatWorkOpenIddictServerBuilderExtensions
{
public static OpenIddictServerBuilder AllowWeChatWorkFlow(this OpenIddictServerBuilder builder)
{
return builder
.AllowCustomFlow(AbpWeChatWorkGlobalConsts.GrantType);
}
public static OpenIddictServerBuilder RegisterWeChatWorkScopes(this OpenIddictServerBuilder builder)
{
return builder.RegisterScopes(new[]
{
AbpWeChatWorkGlobalConsts.ProfileKey,
});
}
public static OpenIddictServerBuilder RegisterWeChatWorkClaims(this OpenIddictServerBuilder builder)
{
return builder.RegisterClaims(new[]
{
AbpWeChatWorkClaimTypes.UserId,
});
}
}

3
aspnet-core/modules/wechat/LINGYUN.Abp.Identity.WeChat.Work/FodyWeavers.xml

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

30
aspnet-core/modules/wechat/LINGYUN.Abp.Identity.WeChat.Work/FodyWeavers.xsd

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

20
aspnet-core/modules/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN.Abp.Identity.WeChat.Work.csproj

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Identity.Domain" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.WeChat.Work\LINGYUN.Abp.WeChat.Work.csproj" />
</ItemGroup>
</Project>

13
aspnet-core/modules/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/AbpIdentityWeChatWorkModule.cs

@ -0,0 +1,13 @@
using LINGYUN.Abp.WeChat.Work;
using Volo.Abp.Identity;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Identity.WeChat.Work;
[DependsOn(
typeof(AbpWeChatWorkModule),
typeof(AbpIdentityDomainModule))]
public class AbpIdentityWeChatWorkModule : AbpModule
{
}

59
aspnet-core/modules/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/WeChatWorkInternalUserFinder.cs

@ -0,0 +1,59 @@
using LINGYUN.Abp.WeChat.Work;
using LINGYUN.Abp.WeChat.Work.Authorize;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
namespace LINGYUN.Abp.Identity.WeChat.Work
{
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
[ExposeServices(typeof(IWeChatWorkInternalUserFinder))]
public class WeChatWorkInternalUserFinder : IWeChatWorkInternalUserFinder
{
protected IdentityUserManager UserManager { get; }
public WeChatWorkInternalUserFinder(
IdentityUserManager userManager)
{
UserManager = userManager;
}
protected string GetUserOpenIdOrNull(IdentityUser user, string provider)
{
// 微信扩展登录后openid存储在Login中
var userLogin = user?.Logins
.Where(login => login.LoginProvider == provider)
.FirstOrDefault();
return userLogin?.ProviderKey;
}
public async virtual Task<string> FindUserIdentifierAsync(string agentId, Guid userId, CancellationToken cancellationToken = default)
{
var user = await UserManager.FindByIdAsync(userId.ToString());
return GetUserOpenIdOrNull(user, AbpWeChatWorkGlobalConsts.ProviderName);
}
public async virtual Task<List<string>> FindUserIdentifierListAsync(string agentId, IEnumerable<Guid> userIdList, CancellationToken cancellationToken = default)
{
var userIdentifiers = new List<string>();
foreach (var userId in userIdList)
{
var user = await UserManager.FindByIdAsync(userId.ToString());
var weChatWorkUserId = GetUserOpenIdOrNull(user, AbpWeChatWorkGlobalConsts.ProviderName);
if (!weChatWorkUserId.IsNullOrWhiteSpace())
{
userIdentifiers.Add(weChatWorkUserId);
}
}
return userIdentifiers;
}
}
}

1
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.MiniProgram/LINGYUN.Abp.Notifications.WeChat.MiniProgram.csproj

@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>

3
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/FodyWeavers.xml

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

30
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/FodyWeavers.xsd

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

16
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN.Abp.Notifications.WeChat.Work.csproj

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\LINGYUN.Abp.Notifications\LINGYUN.Abp.Notifications.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.WeChat.Work\LINGYUN.Abp.WeChat.Work.csproj" />
</ItemGroup>
</Project>

83
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/NotificationDataWeChatWorkExtensions.cs

@ -0,0 +1,83 @@
namespace LINGYUN.Abp.Notifications;
public static class NotificationDataWeChatWorkExtensions
{
private const string Prefix = "wx-work:";
private const string AgentIdKey = Prefix + "agent_id";
private const string ToTagKey = Prefix + "to_tag";
private const string ToPartyKey = Prefix + "to_party";
/// <summary>
/// 设定发送到所有应用
/// </summary>
/// <param name="notificationData"></param>
/// <returns></returns>
public static void WithAllAgent(
this NotificationData notificationData)
{
notificationData.SetAgentId("@all");
}
/// <summary>
/// 设定消息应用标识
/// </summary>
/// <param name="notificationData"></param>
/// <param name="agentId"></param>
/// <returns></returns>
public static void SetAgentId(
this NotificationData notificationData,
string agentId)
{
notificationData.TrySetData(AgentIdKey, agentId);
}
/// <summary>
/// 获取消息应用标识
/// </summary>
/// <param name="notificationData"></param>
public static string GetAgentIdOrNull(
this NotificationData notificationData)
{
return notificationData.TryGetData(AgentIdKey)?.ToString();
}
/// <summary>
/// 指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。
/// </summary>
/// <param name="notificationData"></param>
/// <param name="tag"></param>
/// <returns></returns>
public static void SetTag(
this NotificationData notificationData,
string tag)
{
notificationData.TrySetData(ToTagKey, tag);
}
/// <summary>
/// 获取接收消息的标签
/// </summary>
/// <param name="notificationData"></param>
public static string GetTagOrNull(
this NotificationData notificationData)
{
return notificationData.TryGetData(ToTagKey)?.ToString();
}
/// <summary>
/// 指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。
/// </summary>
/// <param name="notificationData"></param>
/// <param name="party"></param>
/// <returns></returns>
public static void SetParty(
this NotificationData notificationData,
string party)
{
notificationData.TrySetData(ToPartyKey, party);
}
/// <summary>
/// 获取接收消息的部门
/// </summary>
/// <param name="notificationData"></param>
public static string GetPartyOrNull(
this NotificationData notificationData)
{
return notificationData.TryGetData(ToPartyKey)?.ToString();
}
}

103
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/NotificationDefinitionWeChatWorkExtensions.cs

@ -0,0 +1,103 @@
namespace LINGYUN.Abp.Notifications;
public static class NotificationDefinitionWeChatWorkExtensions
{
private const string Prefix = "wx-work:";
private const string AgentIdKey = Prefix + "agent_id";
private const string ToTagKey = Prefix + "to_tag";
private const string ToPartyKey = Prefix + "to_party";
/// <summary>
/// 设定发送到所有应用
/// </summary>
/// <param name="notification"></param>
/// <returns></returns>
public static NotificationDefinition WithAllAgent(
this NotificationDefinition notification)
{
return notification.WithAgentId("@all");
}
/// <summary>
/// 设定消息应用标识
/// </summary>
/// <param name="notification"></param>
/// <param name="agentId"></param>
/// <returns></returns>
public static NotificationDefinition WithAgentId(
this NotificationDefinition notification,
string agentId)
{
return notification.WithProperty(AgentIdKey, agentId);
}
/// <summary>
/// 获取消息应用标识
/// </summary>
/// <param name="notification"></param>
public static string GetAgentIdOrNull(
this NotificationDefinition notification)
{
if (notification.Properties.TryGetValue(AgentIdKey, out var agentIdDefine))
{
return agentIdDefine.ToString();
}
return null;
}
/// <summary>
/// 指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。
/// </summary>
/// <param name="notification"></param>
/// <param name="tag"></param>
/// <returns></returns>
public static NotificationDefinition WithTag(
this NotificationDefinition notification,
string tag)
{
return notification.WithProperty(ToTagKey, tag);
}
/// <summary>
/// 获取接收消息的标签
/// </summary>
/// <param name="notification"></param>
public static string GetTagOrNull(
this NotificationDefinition notification)
{
if (notification.Properties.TryGetValue(ToTagKey, out var tagDefine))
{
return tagDefine.ToString();
}
return null;
}
/// <summary>
/// 指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。
/// </summary>
/// <param name="notification"></param>
/// <param name="party"></param>
/// <returns></returns>
public static NotificationDefinition WithParty(
this NotificationDefinition notification,
string party)
{
return notification.WithProperty(ToPartyKey, party);
}
/// <summary>
/// 获取接收消息的部门
/// </summary>
/// <param name="notification"></param>
public static string GetPartyOrNull(
this NotificationDefinition notification)
{
if (notification.Properties.TryGetValue(ToPartyKey, out var partyDefine))
{
return partyDefine.ToString();
}
return null;
}
}

18
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/WeChat/Work/AbpNotificationsWeChatWorkModule.cs

@ -0,0 +1,18 @@
using LINGYUN.Abp.WeChat.Work;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Notifications.WeChat.Work;
[DependsOn(
typeof(AbpWeChatWorkModule),
typeof(AbpNotificationsModule))]
public class AbpNotificationsWeChatWorkModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpNotificationsPublishOptions>(options =>
{
options.PublishProviders.Add<WeChatWorkNotificationPublishProvider>();
});
}
}

159
aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat.Work/LINGYUN/Abp/Notifications/WeChat/Work/WeChatWorkNotificationPublishProvider.cs

@ -0,0 +1,159 @@
using LINGYUN.Abp.RealTime.Localization;
using LINGYUN.Abp.WeChat.Work;
using LINGYUN.Abp.WeChat.Work.Authorize;
using LINGYUN.Abp.WeChat.Work.Message;
using LINGYUN.Abp.WeChat.Work.Message.Models;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Features;
namespace LINGYUN.Abp.Notifications.WeChat.Work;
public class WeChatWorkNotificationPublishProvider : NotificationPublishProvider
{
public const string ProviderName = NotificationProviderNames.WechatWork;
public override string Name => ProviderName;
protected IFeatureChecker FeatureChecker { get; }
protected IStringLocalizerFactory LocalizerFactory { get; }
protected IWeChatWorkMessageSender WeChatWorkMessageSender { get; }
protected IWeChatWorkInternalUserFinder WeChatWorkInternalUserFinder { get; }
protected INotificationDefinitionManager NotificationDefinitionManager { get; }
protected WeChatWorkOptions WeChatWorkOptions { get; }
public WeChatWorkNotificationPublishProvider(
IFeatureChecker featureChecker,
IStringLocalizerFactory localizerFactory,
IWeChatWorkMessageSender weChatWorkMessageSender,
IWeChatWorkInternalUserFinder weChatWorkInternalUserFinder,
INotificationDefinitionManager notificationDefinitionManager,
IOptionsMonitor<WeChatWorkOptions> weChatWorkOptions)
{
FeatureChecker = featureChecker;
LocalizerFactory = localizerFactory;
WeChatWorkMessageSender = weChatWorkMessageSender;
WeChatWorkInternalUserFinder = weChatWorkInternalUserFinder;
NotificationDefinitionManager = notificationDefinitionManager;
WeChatWorkOptions = weChatWorkOptions.CurrentValue;
}
protected async override Task PublishAsync(
NotificationInfo notification,
IEnumerable<UserIdentifier> identifiers,
CancellationToken cancellationToken = default)
{
var sendToAgentIds = new List<string>();
var notificationDefine = await NotificationDefinitionManager.GetOrNullAsync(notification.Name);
var agentId = notification.Data.GetAgentIdOrNull() ?? notificationDefine?.GetAgentIdOrNull();
if (agentId.IsNullOrWhiteSpace())
{
return;
}
// 发送到所有应用
if (agentId.Contains("@all"))
{
foreach (var application in WeChatWorkOptions.Applications)
{
sendToAgentIds.Add(application.Key);
}
}
else
{
sendToAgentIds.AddRange(agentId.Split(';'));
}
var title = "";
var message = "";
var description = "";
var toTag = notification.Data.GetTagOrNull() ?? notificationDefine?.GetTagOrNull();
var toParty = notification.Data.GetPartyOrNull() ?? notificationDefine?.GetPartyOrNull();
if (!notification.Data.NeedLocalizer())
{
title = notification.Data.TryGetData("title").ToString();
message = notification.Data.TryGetData("message").ToString();
description = notification.Data.TryGetData("description")?.ToString() ?? "";
}
else
{
var titleInfo = notification.Data.TryGetData("title").As<LocalizableStringInfo>();
var titleLocalizer = await LocalizerFactory.CreateByResourceNameAsync(titleInfo.ResourceName);
title = titleLocalizer[titleInfo.Name, titleInfo.Values].Value;
var messageInfo = notification.Data.TryGetData("message").As<LocalizableStringInfo>();
var messageLocalizer = await LocalizerFactory.CreateByResourceNameAsync(messageInfo.ResourceName);
message = messageLocalizer[messageInfo.Name, messageInfo.Values].Value;
var descriptionInfo = notification.Data.TryGetData("description")?.As<LocalizableStringInfo>();
if (descriptionInfo != null)
{
var descriptionLocalizer = await LocalizerFactory.CreateByResourceNameAsync(descriptionInfo.ResourceName);
description = descriptionLocalizer[descriptionInfo.Name, descriptionInfo.Values].Value;
}
}
foreach (var sendToAgentId in sendToAgentIds)
{
var findUserList = await WeChatWorkInternalUserFinder
.FindUserIdentifierListAsync(sendToAgentId, identifiers.Select(id => id.UserId));
if (!findUserList.Any())
{
continue;
}
await PublishToAgentAsync(
sendToAgentId,
notification,
findUserList.JoinAsString("|"),
title,
message,
description,
toParty,
toTag,
cancellationToken);
}
}
protected async virtual Task PublishToAgentAsync(
string agentId,
NotificationInfo notification,
string toUser,
string title,
string content,
string description = "",
string toParty = null,
string toTag = null,
CancellationToken cancellationToken = default)
{
WeChatWorkMessage message = null;
switch (notification.ContentType)
{
case NotificationContentType.Text:
message = new WeChatWorkTextMessage(agentId, new TextMessage(content));
break;
case NotificationContentType.Html:
message = new WeChatWorkTextCardMessage(agentId, new TextCardMessage(title, content, "javascript(0);"));
break;
case NotificationContentType.Markdown:
message = new WeChatWorkMarkdownMessage(agentId, new MarkdownMessage(content));
break;
default:
break;
}
if (message == null)
{
return;
}
message.ToTag = toTag;
message.ToParty = toParty;
await WeChatWorkMessageSender.SendAsync(message, cancellationToken);
}
}

3
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/FodyWeavers.xml

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

30
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/FodyWeavers.xsd

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

15
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN.Abp.WeChat.Work.Application.Contracts.csproj

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
</Project>

11
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkApplicationContractsModule.cs

@ -0,0 +1,11 @@
using Volo.Abp.Application;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.WeChat.Work;
[DependsOn(
typeof(AbpDddApplicationContractsModule))]
public class AbpWeChatWorkApplicationContractsModule : AbpModule
{
}

8
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkRemoteServiceConsts.cs

@ -0,0 +1,8 @@
namespace LINGYUN.Abp.WeChat.Work;
public class AbpWeChatWorkRemoteServiceConsts
{
public const string RemoteServiceName = "AbpWeChatWork";
public const string ModuleName = "wechat-work";
}

18
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeAppService.cs

@ -0,0 +1,18 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
public interface IWeChatWorkAuthorizeAppService : IApplicationService
{
Task<string> GenerateOAuth2AuthorizeAsync(
string agentid,
string redirectUri,
string responseType = "code",
string scope = "snsapi_base");
Task<string> GenerateOAuth2LoginAsync(
string appid,
string redirectUri,
string loginType = "ServiceApp",
string agentid = "");
}

10
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Message/Dto/MessageHandleInput.cs

@ -0,0 +1,10 @@
using LINGYUN.Abp.WeChat.Work.Models;
using System;
namespace LINGYUN.Abp.WeChat.Work.Message;
[Serializable]
public class MessageHandleInput : WeChatWorkMessage
{
public string Data { get; set; }
}

12
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Message/Dto/MessageValidationInput.cs

@ -0,0 +1,12 @@
using LINGYUN.Abp.WeChat.Work.Models;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message;
public class MessageValidationInput : WeChatWorkMessage
{
/// <summary>
/// 加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid四个字段,其中msg即为消息内容明文
/// </summary>
[JsonPropertyName("echostr")]
public string EchoStr { get; set; }
}

30
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Message/IWeChatWorkMessageAppService.cs

@ -0,0 +1,30 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace LINGYUN.Abp.WeChat.Work.Message;
/// <summary>
/// 企业微信消息接口
/// </summary>
public interface IWeChatWorkMessageAppService : IApplicationService
{
/// <summary>
/// 校验企业微信消息
/// </summary>
/// <remarks>
/// 参考文档:<see cref="https://developer.work.weixin.qq.com/document/path/90238"/>
/// </remarks>
/// <param name="agentId"></param>
/// <param name="input"></param>
/// <returns></returns>
Task<string> Handle(string agentId, MessageValidationInput input);
/// <summary>
/// 处理企业微信消息
/// </summary>
/// <remarks>
/// 参考文档:<see cref="https://developer.work.weixin.qq.com/document/path/90238"/>
/// </remarks>
/// <param name="agentId"></param>
/// <param name="input"></param>
/// <returns></returns>
Task<string> Handle(string agentId, MessageHandleInput input);
}

25
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Models/WeChatWorkMessage.cs

@ -0,0 +1,25 @@
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Models;
public class WeChatWorkMessage
{
/// <summary>
/// 企业微信加密签名,
/// msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体
/// </summary>
/// <remarks>
/// 签名计算方法参考: https://developer.work.weixin.qq.com/document/path/90930#12976/%E6%B6%88%E6%81%AF%E4%BD%93%E7%AD%BE%E5%90%8D%E6%A0%A1%E9%AA%8C
/// </remarks>
[JsonPropertyName("msg_signature")]
public string Msg_Signature { get; set; }
/// <summary>
/// 时间戳。与nonce结合使用,用于防止请求重放攻击。
/// </summary>
[JsonPropertyName("timestamp")]
public int TimeStamp { get; set; }
/// <summary>
/// 随机数。与timestamp结合使用,用于防止请求重放攻击。
/// </summary>
[JsonPropertyName("nonce")]
public string Nonce { get; set; }
}

3
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/FodyWeavers.xml

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

30
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/FodyWeavers.xsd

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

20
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN.Abp.WeChat.Work.Application.csproj

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Application" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.WeChat.Work\LINGYUN.Abp.WeChat.Work.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.WeChat.Work.Application.Contracts\LINGYUN.Abp.WeChat.Work.Application.Contracts.csproj" />
</ItemGroup>
</Project>

13
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkApplicationModule.cs

@ -0,0 +1,13 @@
using Volo.Abp.Application;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.WeChat.Work;
[DependsOn(
typeof(AbpWeChatWorkApplicationContractsModule),
typeof(AbpWeChatWorkModule),
typeof(AbpDddApplicationModule))]
public class AbpWeChatWorkApplicationModule : AbpModule
{
}

37
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeAppService.cs

@ -0,0 +1,37 @@
using System;
using System.Threading.Tasks;
using System.Web;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.Security.Encryption;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
[IntegrationService]
public class WeChatWorkAuthorizeAppService : ApplicationService, IWeChatWorkAuthorizeAppService
{
private readonly IStringEncryptionService _encryptionService;
private readonly IWeChatWorkAuthorizeGenerator _authorizeGenerator;
public WeChatWorkAuthorizeAppService(
IStringEncryptionService encryptionService,
IWeChatWorkAuthorizeGenerator authorizeGenerator)
{
_encryptionService = encryptionService;
_authorizeGenerator = authorizeGenerator;
}
public async virtual Task<string> GenerateOAuth2AuthorizeAsync(string agentid, string redirectUri, string responseType = "code", string scope = "snsapi_base")
{
var state = _encryptionService.Encrypt($"agentid={agentid}&redirectUri={redirectUri}&responseType={responseType}&scope={scope}&random={Guid.NewGuid():D}").ToMd5();
return await _authorizeGenerator.GenerateOAuth2AuthorizeAsync(agentid, HttpUtility.UrlEncode(redirectUri), state, responseType, scope);
}
public async virtual Task<string> GenerateOAuth2LoginAsync(string appid, string redirectUri, string loginType = "ServiceApp", string agentid = "")
{
var state = _encryptionService.Encrypt($"agentid={agentid}&redirectUri={redirectUri}&loginType={loginType}&agentid={agentid}&random={Guid.NewGuid():D}").ToMd5();
return await _authorizeGenerator.GenerateOAuth2LoginAsync(agentid, HttpUtility.UrlEncode(redirectUri), state, loginType, agentid);
}
}

62
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageAppService.cs

@ -0,0 +1,62 @@
using LINGYUN.Abp.WeChat.Work.Security;
using LINGYUN.Abp.WeChat.Work.Settings;
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Services;
namespace LINGYUN.Abp.WeChat.Work.Message;
public class WeChatWorkMessageAppService : ApplicationService, IWeChatWorkMessageAppService
{
private readonly IWeChatWorkCryptoService _cryptoService;
private readonly WeChatWorkOptions _options;
public WeChatWorkMessageAppService(
IWeChatWorkCryptoService cryptoService,
IOptionsMonitor<WeChatWorkOptions> options)
{
_cryptoService = cryptoService;
_options = options.CurrentValue;
}
public async virtual Task<string> Handle(string agentId, MessageValidationInput input)
{
var corpId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.CorpId);
Check.NotNullOrEmpty(corpId, nameof(corpId));
var applicationConfiguration = _options.Applications.GetConfiguration(agentId);
var cryptoConfiguration = applicationConfiguration.GetCryptoConfiguration("Message");
var echoData = new WeChatWorkCryptoEchoData(
input.EchoStr,
corpId,
cryptoConfiguration.Token,
cryptoConfiguration.EncodingAESKey,
input.Msg_Signature,
input.TimeStamp.ToString(),
input.Nonce);
var echoStr = _cryptoService.Validation(echoData);
return echoStr;
}
public async virtual Task<string> Handle(string agentId, MessageHandleInput input)
{
var corpId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.CorpId);
Check.NotNullOrEmpty(corpId, nameof(corpId));
var applicationConfiguration = _options.Applications.GetConfiguration(agentId);
var cryptoConfiguration = applicationConfiguration.GetCryptoConfiguration("Message");
var decryptData = new WeChatWorkCryptoDecryptData(
input.Data,
corpId,
cryptoConfiguration.Token,
cryptoConfiguration.EncodingAESKey,
input.Msg_Signature,
input.TimeStamp.ToString(),
input.Nonce);
var msg = _cryptoService.Decrypt(decryptData);
return msg;
}
}

3
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/FodyWeavers.xml

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

30
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/FodyWeavers.xsd

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

19
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN.Abp.WeChat.Work.HttpApi.csproj

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.WeChat.Work.Application.Contracts\LINGYUN.Abp.WeChat.Work.Application.Contracts.csproj" />
</ItemGroup>
</Project>

26
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkHttpApiModule.cs

@ -0,0 +1,26 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.WeChat.Work;
[DependsOn(
typeof(AbpWeChatWorkApplicationContractsModule),
typeof(AbpAspNetCoreMvcModule))]
public class AbpWeChatWorkHttpApiModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<IMvcBuilder>(mvcBuilder =>
{
mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpWeChatWorkHttpApiModule).Assembly);
});
//PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
//{
// options.AddAssemblyResource(
// typeof(AbpTextTemplatingResource),
// typeof(AbpWeChatWorkApplicationContractsModule).Assembly);
//});
}
}

42
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeController.cs

@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
[Controller]
[RemoteService(Name = AbpWeChatWorkRemoteServiceConsts.RemoteServiceName)]
[Area(AbpWeChatWorkRemoteServiceConsts.ModuleName)]
[Route("api/wechat/work/authorize")]
public class WeChatWorkAuthorizeController : AbpControllerBase, IWeChatWorkAuthorizeAppService
{
private readonly IWeChatWorkAuthorizeAppService _service;
public WeChatWorkAuthorizeController(IWeChatWorkAuthorizeAppService service)
{
_service = service;
}
[HttpGet]
[Route("oauth2")]
public virtual Task<string> GenerateOAuth2AuthorizeAsync(
[FromQuery(Name = "agent_id")] string agentid,
[FromQuery(Name = "redirect_uri")] string redirectUri,
[FromQuery(Name = "response_type")] string responseType = "code",
[FromQuery] string scope = "snsapi_base")
{
return _service.GenerateOAuth2AuthorizeAsync(agentid, redirectUri, responseType, scope);
}
[HttpGet]
[Route("oauth2/login")]
public virtual Task<string> GenerateOAuth2LoginAsync(
[FromQuery] string appid,
[FromQuery(Name = "redirect_uri")] string redirectUri,
[FromQuery(Name = "login_type")] string loginType = "ServiceApp",
[FromQuery(Name = "agent_id")] string agentid = "")
{
return _service.GenerateOAuth2LoginAsync(agentid, redirectUri, loginType, agentid);
}
}

41
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageController.cs

@ -0,0 +1,41 @@
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
namespace LINGYUN.Abp.WeChat.Work.Message;
[Controller]
[RemoteService(Name = AbpWeChatWorkRemoteServiceConsts.RemoteServiceName)]
[Area(AbpWeChatWorkRemoteServiceConsts.ModuleName)]
[Route("api/wechat/work/messages")]
public class WeChatWorkMessageController : AbpControllerBase, IWeChatWorkMessageAppService
{
private readonly IWeChatWorkMessageAppService _service;
public WeChatWorkMessageController(IWeChatWorkMessageAppService service)
{
_service = service;
}
[HttpGet]
[Route("{agentId}")]
public virtual Task<string> Handle([FromRoute] string agentId, [FromQuery] MessageValidationInput input)
{
return _service.Handle(agentId, input);
}
[HttpPost]
[Route("{agentId}")]
public async virtual Task<string> Handle([FromRoute] string agentId, [FromQuery] MessageHandleInput input)
{
using var reader = new StreamReader(Request.Body, Encoding.UTF8);
var content = await reader.ReadToEndAsync();
input.Data = content;
return await _service.Handle(agentId, input);
}
}

3
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/FodyWeavers.xml

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

30
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/FodyWeavers.xsd

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

27
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN.Abp.WeChat.Work.csproj

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<None Remove="LINGYUN\Abp\WeChat\Work\Localization\Resources\*.json" />
<EmbeddedResource Include="LINGYUN\Abp\WeChat\Work\Localization\Resources\*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Features" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Volo.Abp.Caching" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Http" Version="$(MicrosoftPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\LINGYUN.Abp.Features.LimitValidation\LINGYUN.Abp.Features.LimitValidation.csproj" />
</ItemGroup>
</Project>

26
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkException.cs

@ -0,0 +1,26 @@
using System;
using System.Runtime.Serialization;
using Volo.Abp;
namespace LINGYUN.Abp.WeChat.Work;
public class AbpWeChatWorkException : BusinessException
{
public AbpWeChatWorkException()
{
}
public AbpWeChatWorkException(
string code = null,
string message = null,
string details = null,
Exception innerException = null)
: base(code, message, details, innerException)
{
}
public AbpWeChatWorkException(SerializationInfo serializationInfo, StreamingContext context)
: base(serializationInfo, context)
{
}
}

42
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkGlobalConsts.cs

@ -0,0 +1,42 @@
namespace LINGYUN.Abp.WeChat.Work
{
public class AbpWeChatWorkGlobalConsts
{
/// <summary>
/// 企业微信对应的Provider名称
/// </summary>
public static string ProviderName { get; set; } = "WeChat.Work";
/// <summary>
/// 企业微信授权类型
/// </summary>
public static string GrantType { get; set; } = "wx-work";
/// <summary>
/// 企业微信授权名称
/// </summary>
public static string AuthenticationScheme { get; set; }= "WeCom";
/// <summary>
/// 企业微信个人信息标识
/// </summary>
public static string ProfileKey { get; set; } = "wecom.profile";
/// <summary>
/// 企业微信授权应用标识参数
/// </summary>
public static string AgentId { get; set; } = "agent_id";
/// <summary>
/// 企业微信授权Code参数
/// </summary>
public static string Code { get; set; }= "code";
/// <summary>
/// 企业微信授权显示名称
/// </summary>
public static string DisplayName { get; set; } = "企业微信";
/// <summary>
///企业微信授权方法名称
/// </summary>
public static string AuthenticationMethod { get; set; } = "wecom";
internal static string ApiClient { get; set; } = "Abp.WeChat.Work";
internal static string OAuthClient { get; set; } = "Abp.WeChat.Work.OAuth";
internal static string LoginClient { get; set; } = "Abp.WeChat.Work.Login";
}
}

58
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkModule.cs

@ -0,0 +1,58 @@
using LINGYUN.Abp.Features.LimitValidation;
using LINGYUN.Abp.WeChat.Work.Localization;
using Microsoft.Extensions.DependencyInjection;
using System;
using Volo.Abp.Caching;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Modularity;
using Volo.Abp.Settings;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.WeChat.Work;
[DependsOn(
typeof(AbpCachingModule),
typeof(AbpFeaturesLimitValidationModule),
typeof(AbpSettingsModule))]
public class AbpWeChatWorkModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<WeChatWorkOptions>(configuration.GetSection("WeChat:Work"));
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpWeChatWorkModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Add<WeChatWorkResource>("zh-Hans")
.AddVirtualJson("/LINGYUN/Abp/WeChat/Work/Localization/Resources");
});
Configure<AbpExceptionLocalizationOptions>(options =>
{
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");
});
}
}

41
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeGenerator.cs

@ -0,0 +1,41 @@
using System.Threading.Tasks;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
public interface IWeChatWorkAuthorizeGenerator
{
/// <summary>
/// 构造网页授权链接
/// </summary>
/// <remarks>
/// 参考:https://developer.work.weixin.qq.com/document/path/91022
/// </remarks>
/// <param name="agentid"></param>
/// <param name="redirectUri"></param>
/// <param name="state"></param>
/// <param name="responseType"></param>
/// <param name="scope"></param>
/// <returns></returns>
Task<string> GenerateOAuth2AuthorizeAsync(
string agentid,
string redirectUri,
string state,
string responseType = "code",
string scope = "snsapi_base");
/// <summary>
/// 构建网页登录链接
/// </summary>
/// <param name="appid"></param>
/// <param name="redirectUri"></param>
/// <param name="state"></param>
/// <param name="loginType"></param>
/// <param name="agentid"></param>
/// <param name="lang"></param>
/// <returns></returns>
Task<string> GenerateOAuth2LoginAsync(
string appid,
string redirectUri,
string state,
string loginType = "ServiceApp",
string agentid = "",
string lang = "zh");
}

31
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkInternalUserFinder.cs

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
public interface IWeChatWorkInternalUserFinder
{
/// <summary>
/// 通过用户标识查询企业微信用户标识
/// </summary>
/// <param name="agentId"></param>
/// <param name="userId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<string> FindUserIdentifierAsync(
string agentId,
Guid userId,
CancellationToken cancellationToken = default);
/// <summary>
/// 通过用户标识列表查询企业微信用户标识列表
/// </summary>
/// <param name="agentId"></param>
/// <param name="userIdList"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<List<string>> FindUserIdentifierListAsync(
string agentId,
IEnumerable<Guid> userIdList,
CancellationToken cancellationToken = default);
}

20
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkUserFinder.cs

@ -0,0 +1,20 @@
using LINGYUN.Abp.WeChat.Work.Authorize.Models;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
/// <summary>
/// 企业微信用户信息查询接口
/// </summary>
public interface IWeChatWorkUserFinder
{
Task<WeChatWorkUserInfo> GetUserInfoAsync(
string agentId,
string code,
CancellationToken cancellationToken = default);
Task<WeChatWorkUserDetail> GetUserDetailAsync(
string agentId,
string userTicket,
CancellationToken cancellationToken = default);
}

19
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Models/WeChatWorkGender.cs

@ -0,0 +1,19 @@
namespace LINGYUN.Abp.WeChat.Work.Authorize.Models;
/// <summary>
/// 性别
/// </summary>
public enum WeChatWorkGender
{
/// <summary>
/// 未定义
/// </summary>
None = 0,
/// <summary>
/// 男性
/// </summary>
Man = 1,
/// <summary>
/// 女性
/// </summary>
Women = 2
}

77
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Models/WeChatWorkUserDetail.cs

@ -0,0 +1,77 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Authorize.Models;
/// <summary>
/// 企业微信用户详情
/// </summary>
public class WeChatWorkUserDetail
{
/// <summary>
/// 成员UserID
/// </summary>
[NotNull]
[JsonProperty("userid")]
[JsonPropertyName("userid")]
public string UserId { get; set; }
/// <summary>
/// 性别。
/// 0表示未定义,
/// 1表示男性,
/// 2表示女性。
/// 仅在用户同意snsapi_privateinfo授权时返回真实值,否则返回0
/// </summary>
[CanBeNull]
[JsonProperty("gender")]
[JsonPropertyName("gender")]
public WeChatWorkGender Gender { get; set; }
/// <summary>
/// 头像url。
/// 仅在用户同意snsapi_privateinfo授权时返回真实头像,否则返回默认头像
/// </summary>
[CanBeNull]
[JsonProperty("avatar")]
[JsonPropertyName("avatar")]
public string Avatar { get; set; }
/// <summary>
/// 员工个人二维码(扫描可添加为外部联系人)
/// 仅在用户同意snsapi_privateinfo授权时返回
/// </summary>
[CanBeNull]
[JsonProperty("qr_code")]
[JsonPropertyName("qr_code")]
public string QrCode { get; set; }
/// <summary>
/// 手机
/// 仅在用户同意snsapi_privateinfo授权时返回,第三方应用不可获取
/// </summary>
[CanBeNull]
[JsonProperty("mobile")]
[JsonPropertyName("mobile")]
public string Mobile { get; set; }
/// <summary>
/// 邮箱
/// 仅在用户同意snsapi_privateinfo授权时返回,第三方应用不可获取
/// </summary>
[CanBeNull]
[JsonProperty("email")]
[JsonPropertyName("email")]
public string Email { get; set; }
/// <summary>
/// 企业邮箱
/// 仅在用户同意snsapi_privateinfo授权时返回,第三方应用不可获取
/// </summary>
[CanBeNull]
[JsonProperty("biz_mail")]
[JsonPropertyName("biz_mail")]
public string WorkEmail { get; set; }
/// <summary>
/// 地址
/// 仅在用户同意snsapi_privateinfo授权时返回,第三方应用不可获取
/// </summary>
[CanBeNull]
[JsonProperty("address")]
[JsonPropertyName("address")]
public string Address { get; set; }
}

25
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Models/WeChatWorkUserInfo.cs

@ -0,0 +1,25 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Authorize.Models;
/// <summary>
/// 企业微信用户信息
/// </summary>
public class WeChatWorkUserInfo
{
/// <summary>
/// 成员UserID
/// </summary>
[NotNull]
[JsonProperty("userid")]
[JsonPropertyName("userid")]
public string UserId { get; set; }
/// <summary>
/// 成员票据,最大为512字节,有效期为1800s
/// </summary>
[NotNull]
[JsonProperty("user_ticket")]
[JsonPropertyName("user_ticket")]
public string UserTicket { get; set; }
}

30
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/NullWeChatWorkInternalUserFinder.cs

@ -0,0 +1,30 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
[Dependency(ServiceLifetime.Singleton, TryRegister = true)]
public class NullWeChatWorkInternalUserFinder : IWeChatWorkInternalUserFinder
{
public readonly static IWeChatWorkInternalUserFinder Instance = new NullWeChatWorkInternalUserFinder();
public Task<string> FindUserIdentifierAsync(
string agentId,
Guid userId,
CancellationToken cancellationToken = default)
{
string findUserId = null;
return Task.FromResult(findUserId);
}
public Task<List<string>> FindUserIdentifierListAsync(
string agentId,
IEnumerable<Guid> userIdList,
CancellationToken cancellationToken = default)
{
return Task.FromResult(new List<string>());
}
}

23
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Request/WeChatWorkUserDetailRequest.cs

@ -0,0 +1,23 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
using Volo.Abp;
namespace LINGYUN.Abp.WeChat.Work.Authorize.Request;
public class WeChatWorkUserDetailRequest
{
[NotNull]
[JsonProperty("user_ticket")]
[JsonPropertyName("user_ticket")]
public string UserTicket { get; set; }
public WeChatWorkUserDetailRequest([NotNull] string userTicket)
{
UserTicket = Check.NotNullOrWhiteSpace(userTicket, nameof(userTicket), 512);
}
public virtual string SerializeToJson()
{
return JsonConvert.SerializeObject(this);
}
}

35
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Response/WeChatWorkResponseExtensions.cs

@ -0,0 +1,35 @@
using LINGYUN.Abp.WeChat.Work.Authorize.Models;
namespace LINGYUN.Abp.WeChat.Work.Authorize.Response;
internal static class WeChatWorkResponseExtensions
{
public static WeChatWorkUserInfo ToUserInfo(
this WeChatWorkUserInfoResponse response)
{
response.ThrowIfNotSuccess();
return new WeChatWorkUserInfo
{
UserId = response.UserId,
UserTicket = response.UserTicket,
};
}
public static WeChatWorkUserDetail ToUserDetail(
this WeChatWorkUserDetailResponse response)
{
response.ThrowIfNotSuccess();
return new WeChatWorkUserDetail
{
UserId = response.UserId,
Address = response.Address,
Avatar = response.Avatar,
QrCode = response.QrCode,
Email = response.Email,
Gender = response.Gender,
Mobile = response.Mobile,
WorkEmail = response.WorkEmail,
};
}
}

69
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Response/WeChatWorkUserDetailResponse.cs

@ -0,0 +1,69 @@
using LINGYUN.Abp.WeChat.Work.Authorize.Models;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Authorize.Response;
/// <summary>
/// 企业微信用户详情响应
/// </summary>
public class WeChatWorkUserDetailResponse : WeChatWorkResponse
{
/// <summary>
/// 成员UserID
/// </summary>
[JsonProperty("userid")]
[JsonPropertyName("userid")]
public string UserId { get; set; }
/// <summary>
/// 性别。
/// 0表示未定义,
/// 1表示男性,
/// 2表示女性。
/// 仅在用户同意snsapi_privateinfo授权时返回真实值,否则返回0
/// </summary>
[JsonProperty("gender")]
[JsonPropertyName("gender")]
public WeChatWorkGender Gender { get; set; }
/// <summary>
/// 头像url。
/// 仅在用户同意snsapi_privateinfo授权时返回真实头像,否则返回默认头像
/// </summary>
[JsonProperty("avatar")]
[JsonPropertyName("avatar")]
public string Avatar { get; set; }
/// <summary>
/// 员工个人二维码(扫描可添加为外部联系人)
/// 仅在用户同意snsapi_privateinfo授权时返回
/// </summary>
[JsonProperty("qr_code")]
[JsonPropertyName("qr_code")]
public string QrCode { get; set; }
/// <summary>
/// 手机
/// 仅在用户同意snsapi_privateinfo授权时返回,第三方应用不可获取
/// </summary>
[JsonProperty("mobile")]
[JsonPropertyName("mobile")]
public string Mobile { get; set; }
/// <summary>
/// 邮箱
/// 仅在用户同意snsapi_privateinfo授权时返回,第三方应用不可获取
/// </summary>
[JsonProperty("email")]
[JsonPropertyName("email")]
public string Email { get; set; }
/// <summary>
/// 企业邮箱
/// 仅在用户同意snsapi_privateinfo授权时返回,第三方应用不可获取
/// </summary>
[JsonProperty("biz_mail")]
[JsonPropertyName("biz_mail")]
public string WorkEmail { get; set; }
/// <summary>
/// 地址
/// 仅在用户同意snsapi_privateinfo授权时返回,第三方应用不可获取
/// </summary>
[JsonProperty("address")]
[JsonPropertyName("address")]
public string Address { get; set; }
}

22
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/Response/WeChatWorkUserInfoResponse.cs

@ -0,0 +1,22 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Authorize.Response;
/// <summary>
/// 企业微信用户信息响应
/// </summary>
public class WeChatWorkUserInfoResponse : WeChatWorkResponse
{
/// <summary>
/// 成员UserID
/// </summary>
[JsonProperty("userid")]
[JsonPropertyName("userid")]
public string UserId { get; set; }
/// <summary>
/// 成员票据,最大为512字节,有效期为1800s
/// </summary>
[JsonProperty("user_ticket")]
[JsonPropertyName("user_ticket")]
public string UserTicket { get; set; }
}

78
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeGenerator.cs

@ -0,0 +1,78 @@
using LINGYUN.Abp.WeChat.Work.Settings;
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
public class WeChatWorkAuthorizeGenerator : IWeChatWorkAuthorizeGenerator, ISingletonDependency
{
protected ISettingProvider SettingProvider { get; }
protected IHttpClientFactory HttpClientFactory { get; }
public WeChatWorkAuthorizeGenerator(
ISettingProvider settingProvider,
IHttpClientFactory httpClientFactory)
{
SettingProvider = settingProvider;
HttpClientFactory = httpClientFactory;
}
public async virtual Task<string> GenerateOAuth2AuthorizeAsync(
string agentid,
string redirectUri,
string state,
string responseType = "code",
string scope = "snsapi_base")
{
var corpId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.CorpId);
Check.NotNullOrEmpty(corpId, nameof(corpId));
var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.OAuthClient);
var generatedUrlBuilder = new StringBuilder();
generatedUrlBuilder
.Append(client.BaseAddress.AbsoluteUri.EnsureEndsWith('/'))
.Append("connect/oauth2/authorize")
.AppendFormat("?appid={0}", corpId)
.AppendFormat("&redirect_uri={0}", HttpUtility.UrlEncode(redirectUri))
.AppendFormat("&response_type={0}", responseType)
.AppendFormat("&scope={0}", scope)
.AppendFormat("&state={0}", state)
.AppendFormat("&agentid={0}", agentid)
.Append("#wechat_redirect");
return generatedUrlBuilder.ToString();
}
public virtual Task<string> GenerateOAuth2LoginAsync(
string appid,
string redirectUri,
string state,
string loginType = "ServiceApp",
string agentid = "",
string lang = "zh")
{
var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.LoginClient);
var generatedUrlBuilder = new StringBuilder();
generatedUrlBuilder
.Append(client.BaseAddress.AbsoluteUri.EnsureEndsWith('/'))
.Append("wwlogin/sso/login")
.AppendFormat("?login_type={0}", loginType)
.AppendFormat("&appid={0}", appid)
.AppendFormat("&agentid={0}", agentid)
.AppendFormat("&redirect_uri={0}", HttpUtility.UrlEncode(redirectUri))
.AppendFormat("&state={0}", state)
.AppendFormat("&lang={0}", lang);
return Task.FromResult(generatedUrlBuilder.ToString());
}
}

55
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkUserFinder.cs

@ -0,0 +1,55 @@
using LINGYUN.Abp.WeChat.Work.Authorize.Models;
using LINGYUN.Abp.WeChat.Work.Authorize.Request;
using LINGYUN.Abp.WeChat.Work.Authorize.Response;
using LINGYUN.Abp.WeChat.Work.Token;
using Newtonsoft.Json;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
public class WeChatWorkUserFinder : IWeChatWorkUserFinder, ISingletonDependency
{
protected IHttpClientFactory HttpClientFactory { get; }
protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
public WeChatWorkUserFinder(
IHttpClientFactory httpClientFactory,
IWeChatWorkTokenProvider weChatWorkTokenProvider)
{
HttpClientFactory = httpClientFactory;
WeChatWorkTokenProvider = weChatWorkTokenProvider;
}
public async virtual Task<WeChatWorkUserInfo> GetUserInfoAsync(
string agentId,
string code,
CancellationToken cancellationToken = default)
{
var token = await WeChatWorkTokenProvider.GetTokenAsync(agentId, cancellationToken);
var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
using var response = await client.GetUserInfoAsync(token.AccessToken, code, cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync();
var userInfoResponse = JsonConvert.DeserializeObject<WeChatWorkUserInfoResponse>(responseContent);
return userInfoResponse.ToUserInfo();
}
public async virtual Task<WeChatWorkUserDetail> GetUserDetailAsync(
string agentId,
string userTicket,
CancellationToken cancellationToken = default)
{
var token = await WeChatWorkTokenProvider.GetTokenAsync(agentId, cancellationToken);
var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
var request = new WeChatWorkUserDetailRequest(userTicket);
using var response = await client.GetUserDetailAsync(token.AccessToken, request, cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync();
var userDetailResponse = JsonConvert.DeserializeObject<WeChatWorkUserDetailResponse>(responseContent);
return userDetailResponse.ToUserDetail();
}
}

46
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureDefinitionProvider.cs

@ -0,0 +1,46 @@
using LINGYUN.Abp.WeChat.Work.Localization;
using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.Validation.StringValues;
namespace LINGYUN.Abp.WeChat.Work.Features;
public class WeChatWorkFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var group = context.AddGroup(
name: WeChatWorkFeatureNames.GroupName,
displayName: L("Features:WeChatWork"));
var weChatWorkEnableFeature = group.AddFeature(
name: WeChatWorkFeatureNames.Enable,
defaultValue: "false",
displayName: L("Features:WeChatWorkEnable"),
description: L("Features:WeChatWorkEnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
var messageEnableFeature = weChatWorkEnableFeature.CreateChild(
name: WeChatWorkFeatureNames.Message.Enable,
defaultValue: "false",
displayName: L("Features:MessageEnable"),
description: L("Features:MessageEnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
messageEnableFeature.CreateChild(
name: WeChatWorkFeatureNames.Message.SendLimit,
defaultValue: "20000",
displayName: L("Features:Message.SendLimit"),
description: L("Features:Message.SendLimitDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 100000)));
messageEnableFeature.CreateChild(
name: WeChatWorkFeatureNames.Message.SendLimitInterval,
defaultValue: "1",
displayName: L("Features:Message.SendLimitInterval"),
description: L("Features:Message.SendLimitIntervalDesc"),
valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1)));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<WeChatWorkResource>(name);
}
}

27
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Features/WeChatWorkFeatureNames.cs

@ -0,0 +1,27 @@
namespace LINGYUN.Abp.WeChat.Work.Features;
public static class WeChatWorkFeatureNames
{
public const string GroupName = "WeChat.Work";
/// <summary>
/// 启用企业微信
/// </summary>
public const string Enable = GroupName + ".Enable";
public static class Message
{
public const string GroupName = WeChatWorkFeatureNames.GroupName + ".Message";
/// <summary>
/// 启用消息推送
/// </summary>
public const string Enable = GroupName + ".Enable";
/// <summary>
/// 发送次数上限
/// </summary>
public const string SendLimit = GroupName + ".SendLimit";
/// <summary>
/// 发送次数上限时长
/// </summary>
public const string SendLimitInterval = GroupName + ".SendLimitInterval";
}
}

56
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Localization/Resources/en.json

@ -0,0 +1,56 @@
{
"culture": "en",
"texts": {
"Features:WeChatWork": "WeCom",
"Features:WeChatWorkEnable": "Enable WeCom",
"Features:WeChatWorkEnableDesc": "Enable to enable the application to have WeCom capabilities.",
"Features:Message": "WeCom message push",
"Features:MessageEnable": "Enable WeCom message push",
"Features:MessageEnableDesc": "Enable so that the app will have the ability to push messages to the app via WeCom.",
"Features:Message.SendLimit": "Restrictions on WeCom message push,",
"Features:Message.SendLimitDesc": "Set to limit the WeCom application message push limit.",
"Features:Message.SendLimitInterval": "WeCom message limit period",
"Features:Message.SendLimitIntervalDesc": "Set the limit period of WeCom messages (time scale: days). Each application cannot exceed the limit number of accounts *200 people/day.",
"DisplayName:WeChatWork.Connection.CorpId": "Corp Id",
"Description:WeChatWork.Connection.CorpId": "Each enterprise has a unique corpid, to obtain this information, you can view \"Enterprise ID\" under \"My Enterprise\" - \"Enterprise Information\" in the management background (requires administrator permissions).",
"DisplayName:WeChatWork.EnabledQuickLogin": "Enabled Quick Login",
"Description:WeChatWork.EnabledQuickLogin": "Users can log in directly by scanning the code obtained when they are not registered.",
"WeChatWork:100400": "处理企业微信服务器消息失败,请检查应用签名配置!",
"WeChatWork:100404": "没有找到标识 {AgentId} 的内部应用配置!",
"WeChatWork:101404": "没有找到应用 {AgentId} 所属功能 {Feture} 的加解密配置!",
"WeChatWork:-31020": "redirect_uri 与配置的登录授权调域名不一致",
"WeChatWork:-31027": "appid 参数错误",
"WeChatWork:-31028": "agentid 参数错误",
"WeChatWork:-31033": "校验请求来源错误",
"WeChatWork:-31034": "该企业不是服务商",
"WeChatWork:-31035": "redirect_uri 不能为空",
"WeChatWork:-31037": "appid 非登录授权应用",
"WeChatWork:-31039": "redirect_uri 与配置的可信域名不一致",
"WeChatWork:-31040": "login_type 参数错误",
"WeChatWork:-40001": "签名验证错误",
"WeChatWork:-40002": "xml/json解析失败",
"WeChatWork:-40003": "sha加密生成签名失败",
"WeChatWork:-40004": "AESKey 非法",
"WeChatWork:-40005": "ReceiveId 校验错误",
"WeChatWork:-40006": "AES 加密失败",
"WeChatWork:-40007": "AES 解密失败",
"WeChatWork:-40008": "解密后得到的buffer非法",
"WeChatWork:-40009": "base64加密失败",
"WeChatWork:-40010": "base64解密失败",
"WeChatWork:-40011": "生成xml/json失败",
"WeChatWork:-1": "系统繁忙, 服务器暂不可用,建议稍候重试。建议重试次数不超过3次!",
"WeChatWork:0": "请求成功",
"WeChatWork:6000": "数据版本冲突,可能有多个调用端同时修改数据,稍后重试!",
"WeChatWork:40001": "不合法的secret参数,参考:https://developer.work.weixin.qq.com/document/path/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A40001",
"WeChatWork:40003": "无效的UserID,参考:https://developer.work.weixin.qq.com/document/path/90313#10649/%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A40003",
"WeChatWork:40004": "不合法的媒体文件类型,参考:https://developer.work.weixin.qq.com/document/path/90313#10112",
"WeChatWork:40005": "不合法的type参数, 参考:https://developer.work.weixin.qq.com/document/path/90313#10112",
"WeChatWork:40006": "不合法的文件大小,参考:https://developer.work.weixin.qq.com/document/path/90313#10112",
"WeChatWork:40007": "不合法的media_id参数,参考:https://developer.work.weixin.qq.com/document/path/90313#10649/%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A40007",
"WeChatWork:40008": "不合法的msgtype参数,参考:https://developer.work.weixin.qq.com/document/path/90313#10167",
"WeChatWork:40009": "上传图片大小不是有效值,参考:https://developer.work.weixin.qq.com/document/path/90313#10112/%E4%B8%8A%E4%BC%A0%E7%9A%84%E5%AA%92%E4%BD%93%E6%96%87%E4%BB%B6%E9%99%90%E5%88%B6",
"WeChatWork:40011": "上传视频大小不是有效值,参考:https://developer.work.weixin.qq.com/document/path/90313#10112/%E4%B8%8A%E4%BC%A0%E7%9A%84%E5%AA%92%E4%BD%93%E6%96%87%E4%BB%B6%E9%99%90%E5%88%B6",
"WeChatWork:40013": "不合法的CorpID",
"WeChatWork:40014": "不合法的access_token,参考:https://developer.work.weixin.qq.com/document/path/90313#10649/%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A40014"
}
}

56
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Localization/Resources/zh-Hans.json

@ -0,0 +1,56 @@
{
"culture": "zh-Hans",
"texts": {
"Features:WeChatWork": "企业微信",
"Features:WeChatWorkEnable": "启用企业微信",
"Features:WeChatWorkEnableDesc": "启用以使应用拥有企业微信的能力.",
"Features:Message": "企业微信消息推送",
"Features:MessageEnable": "启用企业微信消息推送",
"Features:MessageEnableDesc": "启用以使应用将拥有通过企业微信推送到应用消息的能力.",
"Features:Message.SendLimit": "企业微信消息推送限制",
"Features:Message.SendLimitDesc": "设置以限制企业微信应用消息推送上限.",
"Features:Message.SendLimitInterval": "企业微信消息限制周期",
"Features:Message.SendLimitIntervalDesc": "设置企业微信消息限制周期(时间刻度: 天).每应用不可超过账号上限数*200人次/天.",
"DisplayName:WeChatWork.Connection.CorpId": "企业Id",
"Description:WeChatWork.Connection.CorpId": "每个企业都拥有唯一的corpid,获取此信息可在管理后台“我的企业”-“企业信息”下查看“企业ID”(需要有管理员权限)",
"DisplayName:WeChatWork.EnabledQuickLogin": "启用快捷登录",
"Description:WeChatWork.EnabledQuickLogin": "用户可在未注册时通过扫码得到的code直接登录",
"WeChatWork:100400": "处理企业微信服务器消息失败,请检查应用签名配置!",
"WeChatWork:100404": "没有找到标识 {AgentId} 的内部应用配置!",
"WeChatWork:101404": "没有找到应用 {AgentId} 所属功能 {Feture} 的加解密配置!",
"WeChatWork:-31020": "redirect_uri 与配置的登录授权调域名不一致",
"WeChatWork:-31027": "appid 参数错误",
"WeChatWork:-31028": "agentid 参数错误",
"WeChatWork:-31033": "校验请求来源错误",
"WeChatWork:-31034": "该企业不是服务商",
"WeChatWork:-31035": "redirect_uri 不能为空",
"WeChatWork:-31037": "appid 非登录授权应用",
"WeChatWork:-31039": "redirect_uri 与配置的可信域名不一致",
"WeChatWork:-31040": "login_type 参数错误",
"WeChatWork:-40001": "签名验证错误",
"WeChatWork:-40002": "xml/json解析失败",
"WeChatWork:-40003": "sha加密生成签名失败",
"WeChatWork:-40004": "AESKey 非法",
"WeChatWork:-40005": "ReceiveId 校验错误",
"WeChatWork:-40006": "AES 加密失败",
"WeChatWork:-40007": "AES 解密失败",
"WeChatWork:-40008": "解密后得到的buffer非法",
"WeChatWork:-40009": "base64加密失败",
"WeChatWork:-40010": "base64解密失败",
"WeChatWork:-40011": "生成xml/json失败",
"WeChatWork:-1": "系统繁忙, 服务器暂不可用,建议稍候重试。建议重试次数不超过3次!",
"WeChatWork:0": "请求成功",
"WeChatWork:6000": "数据版本冲突,可能有多个调用端同时修改数据,稍后重试!",
"WeChatWork:40001": "不合法的secret参数,参考:https://developer.work.weixin.qq.com/document/path/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A40001",
"WeChatWork:40003": "无效的UserID,参考:https://developer.work.weixin.qq.com/document/path/90313#10649/%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A40003",
"WeChatWork:40004": "不合法的媒体文件类型,参考:https://developer.work.weixin.qq.com/document/path/90313#10112",
"WeChatWork:40005": "不合法的type参数, 参考:https://developer.work.weixin.qq.com/document/path/90313#10112",
"WeChatWork:40006": "不合法的文件大小,参考:https://developer.work.weixin.qq.com/document/path/90313#10112",
"WeChatWork:40007": "不合法的media_id参数,参考:https://developer.work.weixin.qq.com/document/path/90313#10649/%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A40007",
"WeChatWork:40008": "不合法的msgtype参数,参考:https://developer.work.weixin.qq.com/document/path/90313#10167",
"WeChatWork:40009": "上传图片大小不是有效值,参考:https://developer.work.weixin.qq.com/document/path/90313#10112/%E4%B8%8A%E4%BC%A0%E7%9A%84%E5%AA%92%E4%BD%93%E6%96%87%E4%BB%B6%E9%99%90%E5%88%B6",
"WeChatWork:40011": "上传视频大小不是有效值,参考:https://developer.work.weixin.qq.com/document/path/90313#10112/%E4%B8%8A%E4%BC%A0%E7%9A%84%E5%AA%92%E4%BD%93%E6%96%87%E4%BB%B6%E9%99%90%E5%88%B6",
"WeChatWork:40013": "不合法的CorpID",
"WeChatWork:40014": "不合法的access_token,参考:https://developer.work.weixin.qq.com/document/path/90313#10649/%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A40014"
}
}

8
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Localization/WeChatWorkResource.cs

@ -0,0 +1,8 @@
using Volo.Abp.Localization;
namespace LINGYUN.Abp.WeChat.Work.Localization;
[LocalizationResourceName("AbpWeChatWork")]
public class WeChatWorkResource
{
}

59
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Media/IWeChatWorkMediaProvider.cs

@ -0,0 +1,59 @@
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Content;
namespace LINGYUN.Abp.WeChat.Work.Media;
/// <summary>
/// 素材管理接口
/// </summary>
/// <remarks>
/// API: <see cref="https://developer.work.weixin.qq.com/document/path/91054"/>
/// </remarks>
public interface IWeChatWorkMediaProvider
{
/// <summary>
/// 上传临时素材
/// </summary>
/// <remarks>
/// API: <see cref="https://developer.work.weixin.qq.com/document/path/90253"/>
/// </remarks>
/// <param name="agentId">应用标识</param>
/// <param name="type">媒体文件类型</param>
/// <param name="media">待上传文件</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<WeChatWorkMediaResponse> UploadAsync(
string agentId,
string type,
IRemoteStreamContent media,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取临时素材
/// </summary>
/// <remarks>
/// API: <see cref="https://developer.work.weixin.qq.com/document/path/90254"/>
/// </remarks>
/// <param name="agentId">应用标识</param>
/// <param name="mediaId">媒体文件id</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
///
Task<IRemoteStreamContent> GetAsync(
string agentId,
string mediaId,
CancellationToken cancellationToken = default);
/// <summary>
/// 上传图片
/// </summary>
/// <remarks>
/// API: <see cref="https://developer.work.weixin.qq.com/document/path/90256"/>
/// </remarks>
/// <param name="agentId">应用标识</param>
/// <param name="image">待上传图片</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<WeChatWorkImageResponse> UploadImageAsync(
string agentId,
IRemoteStreamContent image,
CancellationToken cancellationToken = default);
}

11
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Media/WeChatWorkImageResponse.cs

@ -0,0 +1,11 @@
using Newtonsoft.Json;
namespace LINGYUN.Abp.WeChat.Work.Media;
public class WeChatWorkImageResponse : WeChatWorkResponse
{
/// <summary>
/// 上传后得到的图片URL。永久有效
/// </summary>
[JsonProperty("url")]
public string Url { get; set; }
}

117
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Media/WeChatWorkMediaProvider.cs

@ -0,0 +1,117 @@
using LINGYUN.Abp.WeChat.Work.Token;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Content;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.WeChat.Work.Media;
public class WeChatWorkMediaProvider : IWeChatWorkMediaProvider, ISingletonDependency
{
protected IHttpClientFactory HttpClientFactory { get; }
protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
public WeChatWorkMediaProvider(
IHttpClientFactory httpClientFactory,
IWeChatWorkTokenProvider weChatWorkTokenProvider)
{
HttpClientFactory = httpClientFactory;
WeChatWorkTokenProvider = weChatWorkTokenProvider;
}
public async virtual Task<IRemoteStreamContent> GetAsync(
string agentId,
string mediaId,
CancellationToken cancellationToken = default)
{
var token = await WeChatWorkTokenProvider.GetTokenAsync(agentId, cancellationToken);
var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
using var response = await client.GetMediaAsync(
token.AccessToken,
mediaId,
cancellationToken);
if (!response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var errorResponse = JsonConvert.DeserializeObject<WeChatWorkResponse>(responseContent);
errorResponse.ThrowIfNotSuccess();
}
var mediaStream = await response.Content.ReadAsStreamAsync();
string fileName = null;
string contentType = null;
if (response.Headers.TryGetValues("Content-Disposition", out var contentDispositions))
{
var contentDisposition = contentDispositions.FirstOrDefault();
if (!contentDisposition.IsNullOrWhiteSpace())
{
var startIndex = contentDisposition.IndexOf("filename=", StringComparison.OrdinalIgnoreCase);
if (startIndex >= 0)
{
startIndex += "filename=".Length;
var endIndex = contentDisposition.IndexOf(";", startIndex);
if (endIndex < 0)
{
endIndex = contentDisposition.Length;
}
fileName = contentDisposition.Substring(startIndex, endIndex - startIndex).Trim('\"');
}
}
}
if (response.Headers.TryGetValues("Content-Type", out var contentTypes))
{
contentType = contentTypes.FirstOrDefault();
}
return new RemoteStreamContent(
mediaStream,
fileName: fileName,
contentType: contentType);
}
public async virtual Task<WeChatWorkMediaResponse> UploadAsync(
string agentId,
string type,
IRemoteStreamContent media,
CancellationToken cancellationToken = default)
{
var token = await WeChatWorkTokenProvider.GetTokenAsync(agentId, cancellationToken);
var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
var request = new WeChatWorkMediaRequest(
token.AccessToken,
media);
using var response = await client.UploadMediaAsync(type, request, cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync();
var mediaRespose = JsonConvert.DeserializeObject<WeChatWorkMediaResponse>(responseContent);
mediaRespose.ThrowIfNotSuccess();
return mediaRespose;
}
public async virtual Task<WeChatWorkImageResponse> UploadImageAsync(
string agentId,
IRemoteStreamContent image,
CancellationToken cancellationToken = default)
{
var token = await WeChatWorkTokenProvider.GetTokenAsync(agentId, cancellationToken);
var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
var request = new WeChatWorkMediaRequest(
token.AccessToken,
image);
using var response = await client.UploadImageAsync(request, cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync();
var mediaRespose = JsonConvert.DeserializeObject<WeChatWorkImageResponse>(responseContent);
mediaRespose.ThrowIfNotSuccess();
return mediaRespose;
}
}

15
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Media/WeChatWorkMediaRequest.cs

@ -0,0 +1,15 @@
using Volo.Abp.Content;
namespace LINGYUN.Abp.WeChat.Work.Media;
public class WeChatWorkMediaRequest
{
public string AccessToken { get; set; }
public IRemoteStreamContent Content { get; set; }
public WeChatWorkMediaRequest(
string accessToken,
IRemoteStreamContent content)
{
AccessToken = accessToken;
Content = content;
}
}

27
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Media/WeChatWorkMediaResponse.cs

@ -0,0 +1,27 @@
using Newtonsoft.Json;
namespace LINGYUN.Abp.WeChat.Work.Media;
public class WeChatWorkMediaResponse : WeChatWorkResponse
{
/// <summary>
/// 媒体文件类型
/// </summary>
/// <remarks>
/// 图片(image)
/// 语音(voice)
/// 视频(video)
/// 普通文件(file)
/// </remarks>
[JsonProperty("type")]
public string Type { get; set; }
/// <summary>
/// 媒体文件上传后获取的唯一标识,3天内有效
/// </summary>
[JsonProperty("media_id")]
public string MediaId { get; set; }
/// <summary>
/// 媒体文件上传时间戳
/// </summary>
[JsonProperty("created_at")]
public string CreatedAt { get; set; }
}

19
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/IWeChatWorkMessageSender.cs

@ -0,0 +1,19 @@
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.WeChat.Work.Message;
/// <summary>
/// 消息发送接口
/// </summary>
public interface IWeChatWorkMessageSender
{
/// <summary>
/// 发送消息
/// </summary>
/// <param name="message">继承自 <see cref="WeChatWorkMessage"/> 的企业微信消息载体</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<WeChatWorkMessageResponse> SendAsync(
WeChatWorkMessage message,
CancellationToken cancellationToken = default);
}

22
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/MarkdownMessage.cs

@ -0,0 +1,22 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message.Models;
/// <summary>
/// markdown消息
/// </summary>
public class MarkdownMessage
{
/// <summary>
/// markdown内容,最长不超过2048个字节,必须是utf8编码
/// </summary>
[NotNull]
[JsonProperty("content")]
[JsonPropertyName("content")]
public string Content { get; set; }
public MarkdownMessage(string content)
{
Content = content;
}
}

22
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/MediaMessage.cs

@ -0,0 +1,22 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message.Models;
/// <summary>
/// 媒体文件消息
/// </summary>
public class MediaMessage
{
/// <summary>
///媒体文件id,可以调用上传临时素材接口获取
/// </summary>
[NotNull]
[JsonProperty("media_id")]
[JsonPropertyName("media_id")]
public string MediaId { get; set; }
public MediaMessage(string mediaId)
{
MediaId = mediaId;
}
}

87
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/MpNewMessage.cs

@ -0,0 +1,87 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message.Models;
/// <summary>
/// 图文消息(mp)载体
/// </summary>
public class MpNewMessagePayload
{
/// <summary>
/// 图文消息(mp)列表
/// </summary>
[NotNull]
[JsonProperty("articles")]
[JsonPropertyName("articles")]
public List<MpNewMessage> Articles { get; set; }
public MpNewMessagePayload(List<MpNewMessage> articles)
{
Articles = articles;
}
}
/// <summary>
/// 图文消息(mp)
/// </summary>
public class MpNewMessage
{
public MpNewMessage(
string title,
string thumbMediaId,
string content,
string author = "",
string contentSourceUrl = "",
string description = "")
{
Title = title;
ThumbMediaId = thumbMediaId;
Author = author;
ContentSourceUrl = contentSourceUrl;
Content = content;
Description = description;
}
/// <summary>
/// 标题,不超过128个字节,超过会自动截断(支持id转译)
/// </summary>
[NotNull]
[JsonProperty("title")]
[JsonPropertyName("title")]
public string Title { get; set; }
/// <summary>
/// 图文消息缩略图的media_id, 可以通过素材管理接口获得。此处thumb_media_id即上传接口返回的media_id
/// </summary>
[NotNull]
[JsonProperty("thumb_media_id")]
[JsonPropertyName("thumb_media_id")]
public string ThumbMediaId { get; set; }
/// <summary>
/// 图文消息的作者,不超过64个字节
/// </summary>
[CanBeNull]
[JsonProperty("author")]
[JsonPropertyName("author")]
public string Author { get; set; }
/// <summary>
/// 图文消息点击“阅读原文”之后的页面链接
/// </summary>
[CanBeNull]
[JsonProperty("content_source_url")]
[JsonPropertyName("content_source_url")]
public string ContentSourceUrl { get; set; }
/// <summary>
/// 图文消息的内容,支持html标签,不超过666 K个字节(支持id转译)
/// </summary>
[NotNull]
[JsonProperty("content")]
[JsonPropertyName("content")]
public string Content { get; set; }
/// <summary>
/// 图文消息的描述,不超过512个字节,超过会自动截断(支持id转译)
/// </summary>
[CanBeNull]
[JsonProperty("digest")]
[JsonPropertyName("digest")]
public string Description { get; set; }
}

88
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/NewMessage.cs

@ -0,0 +1,88 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message.Models;
/// <summary>
/// 图文消息载体
/// </summary>
public class NewMessagePayload
{
/// <summary>
/// 图文消息列表
/// </summary>
[NotNull]
[JsonProperty("articles")]
[JsonPropertyName("articles")]
public List<NewMessage> Articles { get; set; }
public NewMessagePayload(List<NewMessage> articles)
{
Articles = articles;
}
}
/// <summary>
/// 图文消息
/// </summary>
public class NewMessage
{
public NewMessage(
string title,
string description = "",
string url = "",
string pictureUrl = "",
string appId = "",
string pagePath = "")
{
Title = title;
Description = description;
Url = url;
PictureUrl = pictureUrl;
AppId = appId;
PagePath = pagePath;
}
/// <summary>
/// 标题,不超过128个字节,超过会自动截断(支持id转译)
/// </summary>
[NotNull]
[JsonProperty("title")]
[JsonPropertyName("title")]
public string Title { get; set; }
/// <summary>
/// 描述,不超过512个字节,超过会自动截断(支持id转译)
/// </summary>
[CanBeNull]
[JsonProperty("description")]
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// 点击后跳转的链接。
/// 最长2048字节,请确保包含了协议头(http/https),小程序或者url必须填写一个
/// </summary>
[CanBeNull]
[JsonProperty("url")]
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// 图文消息的图片链接,最长2048字节,支持JPG、PNG格式,较好的效果为大图 1068*455,小图150*150。
/// </summary>
[CanBeNull]
[JsonProperty("picurl")]
[JsonPropertyName("picurl")]
public string PictureUrl { get; set; }
/// <summary>
/// 小程序appid,必须是与当前应用关联的小程序,appid和pagepath必须同时填写,填写后会忽略url字段
/// </summary>
[CanBeNull]
[JsonProperty("appid")]
[JsonPropertyName("appid")]
public string AppId { get; set; }
/// <summary>
/// 点击消息卡片后的小程序页面,最长128字节,仅限本小程序内的页面。appid和pagepath必须同时填写,填写后会忽略url字段
/// </summary>
[CanBeNull]
[JsonProperty("pagepath")]
[JsonPropertyName("pagepath")]
public string PagePath { get; set; }
}

51
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/TextCardMessage.cs

@ -0,0 +1,51 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message.Models;
/// <summary>
/// 文本卡片消息
/// </summary>
public class TextCardMessage
{
public TextCardMessage(
string title,
string description,
string url,
string buttonText = "")
{
Title = title;
Description = description;
Url = url;
ButtonText = buttonText;
}
/// <summary>
/// 标题,不超过128个字节,超过会自动截断(支持id转译)
/// </summary>
[NotNull]
[JsonProperty("title")]
[JsonPropertyName("title")]
public string Title { get; set; }
/// <summary>
/// 描述,不超过512个字节,超过会自动截断(支持id转译)
/// </summary>
[NotNull]
[JsonProperty("description")]
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// 点击后跳转的链接。最长2048字节,请确保包含了协议头(http/https)
/// </summary>
[NotNull]
[JsonProperty("url")]
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// 按钮文字。 默认为“详情”, 不超过4个文字,超过自动截断。
/// </summary>
[CanBeNull]
[JsonProperty("btntxt")]
[JsonPropertyName("btntxt")]
public string ButtonText { get; set; }
}

23
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/TextMessage.cs

@ -0,0 +1,23 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message.Models;
/// <summary>
/// 文本消息
/// </summary>
public class TextMessage
{
public TextMessage(string content)
{
Content = content;
}
/// <summary>
/// 消息内容,最长不超过2048个字节,超过将截断(支持id转译)
/// </summary>
[NotNull]
[JsonProperty("content")]
[JsonPropertyName("content")]
public string Content { get; set; }
}

42
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/Models/VideoMessage.cs

@ -0,0 +1,42 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message.Models;
/// <summary>
/// 视频消息
/// </summary>
public class VideoMessage
{
public VideoMessage(
string mediaId,
string title = "",
string description = "")
{
Title = title;
Description = description;
MediaId = mediaId;
}
/// <summary>
/// 视频消息的标题,不超过128个字节,超过会自动截断
/// </summary>
[CanBeNull]
[JsonProperty("title")]
[JsonPropertyName("title")]
public string Title { get; set; }
/// <summary>
/// 视频消息的描述,不超过512个字节,超过会自动截断
/// </summary>
[CanBeNull]
[JsonProperty("description")]
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// 视频媒体文件id,可以调用上传临时素材接口获取
/// </summary>
[NotNull]
[JsonProperty("media_id")]
[JsonPropertyName("media_id")]
public string MediaId { get; set; }
}

46
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkFileMessage.cs

@ -0,0 +1,46 @@
using JetBrains.Annotations;
using LINGYUN.Abp.WeChat.Work.Message.Models;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message;
/// <summary>
/// 企业微信文件消息
/// </summary>
public class WeChatWorkFileMessage : WeChatWorkMessage
{
public WeChatWorkFileMessage(
string agentId,
MediaMessage file) : base(agentId, "file")
{
File = file;
}
/// <summary>
/// 媒体文件
/// </summary>
[NotNull]
[JsonProperty("file")]
[JsonPropertyName("file")]
public MediaMessage File { get; set; }
/// <summary>
/// 表示是否是保密消息,
/// 0表示可对外分享,
/// 1表示不能分享且内容显示水印,
/// 默认为0
/// </summary>
[JsonProperty("safe")]
[JsonPropertyName("safe")]
public int Safe { get; set; }
/// <summary>
/// 表示是否开启重复消息检查,0表示否,1表示是,默认0
/// </summary>
[JsonProperty("enable_duplicate_check")]
[JsonPropertyName("enable_duplicate_check")]
public byte EnableDuplicateCheck { get; set; }
/// <summary>
/// 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时
/// </summary>
[JsonProperty("duplicate_check_interval")]
[JsonPropertyName("duplicate_check_interval")]
public int DuplicateCheckInterval { get; set; } = 1800;
}

54
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkImageMessage.cs

@ -0,0 +1,54 @@
using JetBrains.Annotations;
using LINGYUN.Abp.WeChat.Work.Message.Models;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message;
/// <summary>
/// 企业微信图片消息
/// </summary>
public class WeChatWorkImageMessage : WeChatWorkMessage
{
public WeChatWorkImageMessage(
string agentId,
MediaMessage image) : base(agentId, "image")
{
Image = image;
}
/// <summary>
/// 图片媒体文件
/// </summary>
[NotNull]
[JsonProperty("image")]
[JsonPropertyName("image")]
public MediaMessage Image { get; set; }
/// <summary>
/// 表示是否是保密消息,
/// 0表示可对外分享,
/// 1表示不能分享且内容显示水印,
/// 默认为0
/// </summary>
[JsonProperty("safe")]
[JsonPropertyName("safe")]
public int Safe { get; set; }
/// <summary>
/// 表示是否开启id转译,0表示否,1表示是,默认0。
/// 仅第三方应用需要用到
/// 企业自建应用可以忽略。
/// </summary>
[JsonProperty("enable_id_trans")]
[JsonPropertyName("enable_id_trans")]
public byte EnableIdTrans { get; set; }
/// <summary>
/// 表示是否开启重复消息检查,0表示否,1表示是,默认0
/// </summary>
[JsonProperty("enable_duplicate_check")]
[JsonPropertyName("enable_duplicate_check")]
public byte EnableDuplicateCheck { get; set; }
/// <summary>
/// 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时
/// </summary>
[JsonProperty("duplicate_check_interval")]
[JsonPropertyName("duplicate_check_interval")]
public int DuplicateCheckInterval { get; set; } = 1800;
}

37
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMarkdownMessage.cs

@ -0,0 +1,37 @@
using JetBrains.Annotations;
using LINGYUN.Abp.WeChat.Work.Message.Models;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message;
/// <summary>
/// 企业微信markdown消息
/// </summary>
public class WeChatWorkMarkdownMessage : WeChatWorkMessage
{
public WeChatWorkMarkdownMessage(
string agentId,
MarkdownMessage markdown) : base(agentId, "markdown")
{
Markdown = markdown;
}
/// <summary>
/// markdown消息
/// </summary>
[NotNull]
[JsonProperty("markdown")]
[JsonPropertyName("markdown")]
public MarkdownMessage Markdown { get; set; }
/// <summary>
/// 表示是否开启重复消息检查,0表示否,1表示是,默认0
/// </summary>
[JsonProperty("enable_duplicate_check")]
[JsonPropertyName("enable_duplicate_check")]
public byte EnableDuplicateCheck { get; set; }
/// <summary>
/// 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时
/// </summary>
[JsonProperty("duplicate_check_interval")]
[JsonPropertyName("duplicate_check_interval")]
public int DuplicateCheckInterval { get; set; } = 1800;
}

66
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessage.cs

@ -0,0 +1,66 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message;
/// <summary>
/// 企业微信消息
/// </summary>
public abstract class WeChatWorkMessage
{
/// <summary>
/// 指定接收消息的成员,成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。
/// 特殊情况:指定为"@all",则向该企业应用的全部成员发送
/// </summary>
[JsonProperty("touser")]
[JsonPropertyName("touser")]
public virtual string ToUser { get; set; }
/// <summary>
/// 指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。
/// 当touser为"@all"时忽略本参数
/// </summary>
[JsonProperty("toparty")]
[JsonPropertyName("toparty")]
public virtual string ToParty { get; set; }
/// <summary>
/// 指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。
/// 当touser为"@all"时忽略本参数
/// </summary>
[JsonProperty("totag")]
[JsonPropertyName("totag")]
public virtual string ToTag { get; set; }
/// <summary>
/// 消息类型
/// </summary>
[NotNull]
[JsonProperty("msgtype")]
[JsonPropertyName("msgtype")]
public virtual string MsgType { get; protected set; }
/// <summary>
/// 企业应用的id,整型。
/// 企业内部开发,可在应用的设置页面查看;
/// 第三方服务商,可通过接口 获取企业授权信息 获取该参数值
/// </summary>
[NotNull]
[JsonProperty("agentid")]
[JsonPropertyName("agentid")]
public virtual string AgentId { get; protected set; }
protected WeChatWorkMessage(
string agentId,
string msgType,
string toUser = "",
string toParty = "",
string toTag = "")
{
AgentId = agentId;
MsgType = msgType;
ToUser = toUser;
ToParty = toParty;
ToTag = toTag;
}
public virtual string SerializeToJson()
{
return JsonConvert.SerializeObject(this);
}
}

11
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageRequest.cs

@ -0,0 +1,11 @@
namespace LINGYUN.Abp.WeChat.Work.Message;
public class WeChatWorkMessageRequest
{
public string AccessToken { get; set; }
public WeChatWorkMessage Message { get; set; }
public WeChatWorkMessageRequest(string accessToken, WeChatWorkMessage message)
{
AccessToken = accessToken;
Message = message;
}
}

47
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageResponse.cs

@ -0,0 +1,47 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message;
/// <summary>
/// 企业微信发送消息响应
/// </summary>
public class WeChatWorkMessageResponse : WeChatWorkResponse
{
/// <summary>
/// 不合法的userid,不区分大小写,统一转为小写
/// </summary>
[JsonProperty("invaliduser")]
[JsonPropertyName("invaliduser")]
public string InvalidUser { get; set; }
/// <summary>
/// 不合法的partyid
/// </summary>
[JsonProperty("invalidparty")]
[JsonPropertyName("invalidparty")]
public string InvalidParty { get; set; }
/// <summary>
/// 不合法的标签id
/// </summary>
[JsonProperty("invalidtag")]
[JsonPropertyName("invalidtag")]
public string InvalidTag { get; set; }
/// <summary>
/// 没有基础接口许可(包含已过期)的userid
/// </summary>
[JsonProperty("unlicenseduser")]
[JsonPropertyName("unlicenseduser")]
public string UnLicensedUser { get; set; }
/// <summary>
/// 消息id,用于撤回应用消息
/// </summary>
[JsonProperty("msgid")]
[JsonPropertyName("msgid")]
public string MsgId { get; set; }
/// <summary>
/// 仅消息类型为“按钮交互型”,“投票选择型”和“多项选择型”的模板卡片消息返回,
/// 应用可使用response_code调用更新模版卡片消息接口,72小时内有效,且只能使用一次
/// </summary>
[JsonProperty("response_code")]
[JsonPropertyName("response_code")]
public string ResponseCode { get; set; }
}

59
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMessageSender.cs

@ -0,0 +1,59 @@
using LINGYUN.Abp.Features.LimitValidation;
using LINGYUN.Abp.WeChat.Work.Features;
using LINGYUN.Abp.WeChat.Work.Token;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
namespace LINGYUN.Abp.WeChat.Work.Message;
[RequiresFeature(WeChatWorkFeatureNames.Enable)]
public class WeChatWorkMessageSender : IWeChatWorkMessageSender, ISingletonDependency
{
public ILogger<WeChatWorkMessageSender> Logger { get; set; }
protected IHttpClientFactory HttpClientFactory { get; }
protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
public WeChatWorkMessageSender(
IHttpClientFactory httpClientFactory,
IWeChatWorkTokenProvider weChatWorkTokenProvider)
{
HttpClientFactory = httpClientFactory;
WeChatWorkTokenProvider = weChatWorkTokenProvider;
Logger = NullLogger<WeChatWorkMessageSender>.Instance;
}
[RequiresFeature(WeChatWorkFeatureNames.Message.Enable)]
[RequiresLimitFeature(
WeChatWorkFeatureNames.Message.SendLimit,
WeChatWorkFeatureNames.Message.SendLimitInterval,
LimitPolicy.Days)]
public async virtual Task<WeChatWorkMessageResponse> SendAsync(WeChatWorkMessage message, CancellationToken cancellationToken = default)
{
var token = await WeChatWorkTokenProvider.GetTokenAsync(message.AgentId, cancellationToken);
var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
var request = new WeChatWorkMessageRequest(
token.AccessToken,
message);
using var response = await client.SendMessageAsync(request, cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync();
var messageResponse = JsonConvert.DeserializeObject<WeChatWorkMessageResponse>(responseContent);
if (!messageResponse.IsSuccessed)
{
Logger.LogWarning("Send wechat work message failed");
Logger.LogWarning($"Error code: {messageResponse.ErrorCode}, message: {messageResponse.ErrorMessage}");
}
return messageResponse;
}
}

55
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkMpNewMessage.cs

@ -0,0 +1,55 @@
using JetBrains.Annotations;
using LINGYUN.Abp.WeChat.Work.Message.Models;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message;
/// <summary>
/// 企业微信文本图文消息
/// </summary>
public class WeChatWorkMpNewMessage : WeChatWorkMessage
{
public WeChatWorkMpNewMessage(
string agentId,
MpNewMessagePayload mpnews) : base(agentId, "mpnews")
{
News = mpnews;
}
/// <summary>
/// 图文消息(mp)
/// </summary>
[NotNull]
[JsonProperty("mpnews")]
[JsonPropertyName("mpnews")]
public MpNewMessagePayload News { get; set; }
/// <summary>
/// 表示是否是保密消息,
/// 0表示可对外分享,
/// 1表示不能分享且内容显示水印,
/// 2表示仅限在企业内分享,默认为0;
/// 注意仅mpnews类型的消息支持safe值为2,其他消息类型不支持
/// </summary>
[JsonProperty("safe")]
[JsonPropertyName("safe")]
public int Safe { get; set; }
/// <summary>
/// 表示是否开启id转译,0表示否,1表示是,默认0。
/// 仅第三方应用需要用到
/// 企业自建应用可以忽略。
/// </summary>
[JsonProperty("enable_id_trans")]
[JsonPropertyName("enable_id_trans")]
public byte EnableIdTrans { get; set; }
/// <summary>
/// 表示是否开启重复消息检查,0表示否,1表示是,默认0
/// </summary>
[JsonProperty("enable_duplicate_check")]
[JsonPropertyName("enable_duplicate_check")]
public byte EnableDuplicateCheck { get; set; }
/// <summary>
/// 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时
/// </summary>
[JsonProperty("duplicate_check_interval")]
[JsonPropertyName("duplicate_check_interval")]
public int DuplicateCheckInterval { get; set; } = 1800;
}

45
aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Message/WeChatWorkNewMessage.cs

@ -0,0 +1,45 @@
using JetBrains.Annotations;
using LINGYUN.Abp.WeChat.Work.Message.Models;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace LINGYUN.Abp.WeChat.Work.Message;
/// <summary>
/// 企业微信文本图文消息
/// </summary>
public class WeChatWorkNewMessage : WeChatWorkMessage
{
public WeChatWorkNewMessage(
string agentId,
NewMessagePayload news) : base(agentId, "news")
{
News = news;
}
/// <summary>
/// 图文消息
/// </summary>
[NotNull]
[JsonProperty("news")]
[JsonPropertyName("news")]
public NewMessagePayload News { get; set; }
/// <summary>
/// 表示是否开启id转译,0表示否,1表示是,默认0。
/// 仅第三方应用需要用到
/// 企业自建应用可以忽略。
/// </summary>
[JsonProperty("enable_id_trans")]
[JsonPropertyName("enable_id_trans")]
public byte EnableIdTrans { get; set; }
/// <summary>
/// 表示是否开启重复消息检查,0表示否,1表示是,默认0
/// </summary>
[JsonProperty("enable_duplicate_check")]
[JsonPropertyName("enable_duplicate_check")]
public byte EnableDuplicateCheck { get; set; }
/// <summary>
/// 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时
/// </summary>
[JsonProperty("duplicate_check_interval")]
[JsonPropertyName("duplicate_check_interval")]
public int DuplicateCheckInterval { get; set; } = 1800;
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save