diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/WeChatWorkInternalUserFinder.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/WeChatWorkUserClaimProvider.cs
similarity index 66%
rename from aspnet-core/framework/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/WeChatWorkInternalUserFinder.cs
rename to aspnet-core/framework/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/WeChatWorkUserClaimProvider.cs
index eb44979a6..fda85cb68 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/WeChatWorkInternalUserFinder.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/WeChatWorkUserClaimProvider.cs
@@ -8,16 +8,17 @@ using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
+using Volo.Abp.Uow;
namespace LINGYUN.Abp.Identity.WeChat.Work;
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
-[ExposeServices(typeof(IWeChatWorkInternalUserFinder))]
-public class WeChatWorkInternalUserFinder : IWeChatWorkInternalUserFinder
+[ExposeServices(typeof(IWeChatWorkUserClaimProvider))]
+public class WeChatWorkUserClaimProvider : IWeChatWorkUserClaimProvider
{
protected IdentityUserManager UserManager { get; }
- public WeChatWorkInternalUserFinder(
+ public WeChatWorkUserClaimProvider(
IdentityUserManager userManager)
{
UserManager = userManager;
@@ -55,4 +56,22 @@ public class WeChatWorkInternalUserFinder : IWeChatWorkInternalUserFinder
return userIdentifiers;
}
+
+ [UnitOfWork]
+ public async virtual Task BindUserAsync(
+ Guid userId,
+ string weChatUserId,
+ CancellationToken cancellationToken = default)
+ {
+ var user = await UserManager.GetByIdAsync(userId);
+ var existsWeChatUserId = GetUserOpenIdOrNull(user, AbpWeChatWorkGlobalConsts.ProviderName);
+ if (!existsWeChatUserId.IsNullOrWhiteSpace())
+ {
+ user.RemoveLogin(AbpWeChatWorkGlobalConsts.ProviderName, existsWeChatUserId);
+ }
+ user.AddLogin(new Microsoft.AspNetCore.Identity.UserLoginInfo(
+ AbpWeChatWorkGlobalConsts.ProviderName,
+ weChatUserId,
+ AbpWeChatWorkGlobalConsts.DisplayName));
+ }
}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeAppService.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeAppService.cs
index 06f8a7b06..d7369ae4b 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeAppService.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeAppService.cs
@@ -4,12 +4,24 @@ using Volo.Abp.Application.Services;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
public interface IWeChatWorkAuthorizeAppService : IApplicationService
{
+ ///
+ /// 生成授权链接
+ ///
+ /// 授权回调Url名称
+ /// 响应类型
+ /// 授权范围
+ ///
Task GenerateOAuth2AuthorizeAsync(
- string redirectUri,
+ string urlName,
string responseType = "code",
string scope = "snsapi_base");
-
+ ///
+ /// 生成登录链接
+ ///
+ /// 授权回调Url名称
+ /// 登录类型
+ ///
Task GenerateOAuth2LoginAsync(
- string redirectUri,
- string loginType = "ServiceApp");
+ string urlName,
+ string loginType = "CorpApp");
}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/Dtos/AgentConfigDto.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/Dtos/AgentConfigDto.cs
new file mode 100644
index 000000000..4ce84bffb
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/Dtos/AgentConfigDto.cs
@@ -0,0 +1,6 @@
+namespace LINGYUN.Abp.WeChat.Work.JsSdk.Dtos;
+public class AgentConfigDto
+{
+ public string AgentId { get; set; }
+ public string CorpId { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/Dtos/JsApiSignatureDto.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/Dtos/JsApiSignatureDto.cs
new file mode 100644
index 000000000..67d321223
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/Dtos/JsApiSignatureDto.cs
@@ -0,0 +1,17 @@
+namespace LINGYUN.Abp.WeChat.Work.JsSdk.Dtos;
+public class JsApiSignatureDto
+{
+ public string Nonce { get; set; }
+ public string Timestamp { get; set; }
+ public string Signature { get; set; }
+ public JsApiSignatureDto()
+ {
+
+ }
+ public JsApiSignatureDto(string nonce, string timestamp, string signature)
+ {
+ Nonce = nonce;
+ Timestamp = timestamp;
+ Signature = signature;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/IWeChatWorkJsSdkAppService.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/IWeChatWorkJsSdkAppService.cs
new file mode 100644
index 000000000..daccd6f7f
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/IWeChatWorkJsSdkAppService.cs
@@ -0,0 +1,13 @@
+using LINGYUN.Abp.WeChat.Work.JsSdk.Dtos;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Services;
+
+namespace LINGYUN.Abp.WeChat.Work.JsSdk;
+public interface IWeChatWorkJsSdkAppService : IApplicationService
+{
+ Task GetAgentConfigAsync();
+
+ Task GetSignatureAsync(string url);
+
+ Task GetAgentSignatureAsync(string url);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN.Abp.WeChat.Work.Application.csproj b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN.Abp.WeChat.Work.Application.csproj
index 283c07e8f..8ce77df14 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN.Abp.WeChat.Work.Application.csproj
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN.Abp.WeChat.Work.Application.csproj
@@ -15,6 +15,7 @@
+
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkApplicationModule.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkApplicationModule.cs
index 0eb380c9c..be9618245 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkApplicationModule.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkApplicationModule.cs
@@ -1,11 +1,13 @@
using Volo.Abp.Application;
using Volo.Abp.Modularity;
+using Volo.Abp.UI.Navigation;
namespace LINGYUN.Abp.WeChat.Work;
[DependsOn(
typeof(AbpWeChatWorkApplicationContractsModule),
typeof(AbpWeChatWorkModule),
+ typeof(AbpUiNavigationModule),
typeof(AbpDddApplicationModule))]
public class AbpWeChatWorkApplicationModule : AbpModule
{
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeAppService.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeAppService.cs
index cf3112900..13ab2f28a 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeAppService.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeAppService.cs
@@ -1,42 +1,49 @@
-using LINGYUN.Abp.WeChat.Work.Settings;
-using System;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.Security.Encryption;
+using Volo.Abp.UI.Navigation.Urls;
+using Volo.Abp.Users;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
[IntegrationService]
public class WeChatWorkAuthorizeAppService : ApplicationService, IWeChatWorkAuthorizeAppService
{
+ private readonly IAppUrlProvider _appUrlProvider;
private readonly IStringEncryptionService _encryptionService;
private readonly IWeChatWorkAuthorizeGenerator _authorizeGenerator;
public WeChatWorkAuthorizeAppService(
+ IAppUrlProvider appUrlProvider,
IStringEncryptionService encryptionService,
IWeChatWorkAuthorizeGenerator authorizeGenerator)
{
+ _appUrlProvider = appUrlProvider;
_encryptionService = encryptionService;
_authorizeGenerator = authorizeGenerator;
}
- public async virtual Task GenerateOAuth2AuthorizeAsync(string redirectUri, string responseType = "code", string scope = "snsapi_base")
+ public async virtual Task GenerateOAuth2AuthorizeAsync(
+ string urlName,
+ string responseType = "code",
+ string scope = "snsapi_base")
{
-
- var state = _encryptionService.Encrypt($"redirectUri={redirectUri}&responseType={responseType}&scope={scope}&random={Guid.NewGuid():D}").ToMd5();
+ var userId = CurrentUser.GetId().ToString("D");
+ var state = _encryptionService.Encrypt(userId);
+ var redirectUri = await _appUrlProvider.GetUrlAsync(AbpWeChatWorkGlobalConsts.ProviderName, urlName);
return await _authorizeGenerator.GenerateOAuth2AuthorizeAsync(redirectUri, state, responseType, scope);
}
- public async virtual Task GenerateOAuth2LoginAsync(string redirectUri, string loginType = "ServiceApp")
+ public async virtual Task GenerateOAuth2LoginAsync(
+ string urlName,
+ string loginType = "CorpApp")
{
- var state = _encryptionService.Encrypt($"redirectUri={redirectUri}&loginType={loginType}&random={Guid.NewGuid():D}").ToMd5();
-
- var corpId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.CorpId);
-
- Check.NotNullOrEmpty(corpId, nameof(corpId));
+ var userId = CurrentUser.GetId().ToString("D");
+ var state = _encryptionService.Encrypt(userId);
+ var redirectUri = await _appUrlProvider.GetUrlAsync(AbpWeChatWorkGlobalConsts.ProviderName, urlName);
- return await _authorizeGenerator.GenerateOAuth2LoginAsync(corpId, redirectUri, state, loginType);
+ return await _authorizeGenerator.GenerateOAuth2LoginAsync(redirectUri, state, loginType);
}
}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/JsSdk/WeChatWorkJsSdkAppService.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/JsSdk/WeChatWorkJsSdkAppService.cs
new file mode 100644
index 000000000..803c17395
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/JsSdk/WeChatWorkJsSdkAppService.cs
@@ -0,0 +1,50 @@
+using LINGYUN.Abp.WeChat.Work.Features;
+using LINGYUN.Abp.WeChat.Work.JsSdk.Dtos;
+using LINGYUN.Abp.WeChat.Work.Settings;
+using Microsoft.AspNetCore.Authorization;
+using System.Threading.Tasks;
+using System.Web;
+using Volo.Abp.Application.Services;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.JsSdk;
+
+[Authorize]
+[RequiresFeature(WeChatWorkFeatureNames.Enable)]
+public class WeChatWorkJsSdkAppService : ApplicationService, IWeChatWorkJsSdkAppService
+{
+ private readonly IJsApiTicketProvider _ticketProvider;
+
+ public WeChatWorkJsSdkAppService(IJsApiTicketProvider ticketProvider)
+ {
+ _ticketProvider = ticketProvider;
+ }
+
+ public async virtual Task GetAgentConfigAsync()
+ {
+ var corpId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.CorpId);
+ var agentId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.AgentId);
+
+ return new AgentConfigDto
+ {
+ CorpId = corpId,
+ AgentId = agentId,
+ };
+ }
+
+ public async virtual Task GetAgentSignatureAsync(string url)
+ {
+ var jsApiTicket = await _ticketProvider.GetAgentTicketInfoAsync();
+ var signatureData = _ticketProvider.GenerateSignature(jsApiTicket, HttpUtility.UrlDecode(url));
+
+ return new JsApiSignatureDto(signatureData.Nonce, signatureData.Timestamp, signatureData.Signature);
+ }
+
+ public async virtual Task GetSignatureAsync(string url)
+ {
+ var jsApiTicket = await _ticketProvider.GetTicketInfoAsync();
+ var signatureData = _ticketProvider.GenerateSignature(jsApiTicket, HttpUtility.UrlDecode(url));
+
+ return new JsApiSignatureDto(signatureData.Nonce, signatureData.Timestamp, signatureData.Signature);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeController.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeController.cs
index c32f652fe..9130cf8d5 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeController.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeController.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
@@ -34,19 +35,19 @@ public class WeChatWorkAuthorizeController : AbpControllerBase, IWeChatWorkAutho
///
/// 详情见企业微信文档:
///
- /// 企业内部应用标识
- /// 登录成功重定向url
+ /// 授权回调Url名称
/// oauth响应类型
/// oauth授权范围
///
[HttpGet]
- [Route("oauth2")]
+ [Authorize]
+ [Route("oauth2/generate")]
public virtual Task GenerateOAuth2AuthorizeAsync(
- [FromQuery(Name = "redirect_uri")] string redirectUri,
+ [FromQuery] string urlName,
[FromQuery(Name = "response_type")] string responseType = "code",
[FromQuery] string scope = "snsapi_base")
{
- return _service.GenerateOAuth2AuthorizeAsync(redirectUri, responseType, scope);
+ return _service.GenerateOAuth2AuthorizeAsync(responseType, scope);
}
///
@@ -55,19 +56,19 @@ public class WeChatWorkAuthorizeController : AbpControllerBase, IWeChatWorkAutho
///
/// 详情见企业微信文档:
///
- /// 企业内部应用标识
- /// 登录成功重定向url
+ /// 授权回调Url名称
/// oauth响应类型
/// oauth授权范围
///
[HttpGet]
- [Route("oauth2/authorize")]
+ [Authorize]
+ [Route("oauth2")]
public async virtual Task OAuth2AuthorizeAsync(
- [FromQuery(Name = "redirect_uri")] string redirectUri,
+ [FromQuery] string urlName,
[FromQuery(Name = "response_type")] string responseType = "code",
[FromQuery] string scope = "snsapi_base")
{
- var url = await _service.GenerateOAuth2AuthorizeAsync(redirectUri, responseType, scope);
+ var url = await _service.GenerateOAuth2AuthorizeAsync(urlName, responseType, scope);
return Redirect(url);
}
@@ -78,17 +79,17 @@ public class WeChatWorkAuthorizeController : AbpControllerBase, IWeChatWorkAutho
///
/// 详情见企业微信文档:
///
- /// 登录成功重定向url
+ /// 授权回调Url名称
/// 登录类型, ServiceApp:服务商登录;CorpApp:企业自建/代开发应用登录
- /// 企业自建应用/服务商代开发应用 AgentID,当login_type=CorpApp时填写
///
[HttpGet]
- [Route("oauth2/login")]
+ [Authorize]
+ [Route("oauth2/login/generate")]
public virtual Task GenerateOAuth2LoginAsync(
- [FromQuery(Name = "redirect_uri")] string redirectUri,
- [FromQuery(Name = "login_type")] string loginType = "ServiceApp")
+ string urlName,
+ string loginType = "CorpApp")
{
- return _service.GenerateOAuth2LoginAsync(redirectUri, loginType);
+ return _service.GenerateOAuth2LoginAsync(urlName, loginType);
}
///
@@ -97,17 +98,17 @@ public class WeChatWorkAuthorizeController : AbpControllerBase, IWeChatWorkAutho
///
/// 详情见企业微信文档:
///
- /// 登录成功重定向url
+ /// 授权回调Url名称
/// 登录类型, ServiceApp:服务商登录;CorpApp:企业自建/代开发应用登录
- /// 企业自建应用/服务商代开发应用 AgentID,当login_type=CorpApp时填写
///
[HttpGet]
- [Route("oauth2/login/sso")]
+ [Authorize]
+ [Route("oauth2/login")]
public async virtual Task OAuth2LoginAsync(
- [FromQuery(Name = "redirect_uri")] string redirectUri,
- [FromQuery(Name = "login_type")] string loginType = "ServiceApp")
+ string urlName,
+ string loginType = "CorpApp")
{
- var url = await _service.GenerateOAuth2LoginAsync(redirectUri, loginType);
+ var url = await _service.GenerateOAuth2LoginAsync(urlName, loginType);
return Redirect(url);
}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/JsSdk/WeChatWorkJsSdkController.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/JsSdk/WeChatWorkJsSdkController.cs
new file mode 100644
index 000000000..e7567ccfe
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/JsSdk/WeChatWorkJsSdkController.cs
@@ -0,0 +1,46 @@
+using LINGYUN.Abp.WeChat.Work.JsSdk.Dtos;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.Auditing;
+
+namespace LINGYUN.Abp.WeChat.Work.JsSdk;
+
+[Authorize]
+[Controller]
+[DisableAuditing]
+[Route("api/wechat/work/jssdk")]
+[Area(AbpWeChatWorkRemoteServiceConsts.ModuleName)]
+[RemoteService(Name = AbpWeChatWorkRemoteServiceConsts.RemoteServiceName)]
+public class WeChatWorkJsSdkController : AbpControllerBase, IWeChatWorkJsSdkAppService
+{
+ private readonly IWeChatWorkJsSdkAppService _service;
+
+ public WeChatWorkJsSdkController(IWeChatWorkJsSdkAppService service)
+ {
+ _service = service;
+ }
+
+ [HttpGet]
+ [Route("agent-config")]
+ public virtual Task GetAgentConfigAsync()
+ {
+ return _service.GetAgentConfigAsync();
+ }
+
+ [HttpGet]
+ [Route("agent-signature")]
+ public virtual Task GetAgentSignatureAsync(string url)
+ {
+ return _service.GetAgentSignatureAsync(url);
+ }
+
+ [HttpGet]
+ [Route("signature")]
+ public virtual Task GetSignatureAsync(string url)
+ {
+ return _service.GetSignatureAsync(url);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkGlobalConsts.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkGlobalConsts.cs
index d2722e389..21f8856b5 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkGlobalConsts.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkGlobalConsts.cs
@@ -5,7 +5,7 @@ public class AbpWeChatWorkGlobalConsts
///
/// 企业微信对应的Provider名称
///
- public static string ProviderName { get; set; } = "WeCom";
+ public static string ProviderName { get; set; } = "WorkWeixin";
///
/// 企业微信授权类型
///
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeGenerator.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeGenerator.cs
index 1b009c805..121915242 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeGenerator.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeGenerator.cs
@@ -25,13 +25,13 @@ public interface IWeChatWorkAuthorizeGenerator
///
///
///
- ///
+ ///
///
///
Task GenerateOAuth2LoginAsync(
string redirectUri,
string state,
string loginType = "ServiceApp",
- string agentid = "",
+ string agentId = "",
string lang = "zh");
}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkInternalUserFinder.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkUserClaimProvider.cs
similarity index 63%
rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkInternalUserFinder.cs
rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkUserClaimProvider.cs
index 42a0511b7..9a6b19960 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkInternalUserFinder.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkUserClaimProvider.cs
@@ -4,7 +4,10 @@ using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
-public interface IWeChatWorkInternalUserFinder
+///
+/// 企业微信用户身份提供者
+///
+public interface IWeChatWorkUserClaimProvider
{
///
/// 通过用户标识查询企业微信用户标识
@@ -24,4 +27,15 @@ public interface IWeChatWorkInternalUserFinder
Task> FindUserIdentifierListAsync(
IEnumerable userIdList,
CancellationToken cancellationToken = default);
+ ///
+ /// 绑定用户企业微信
+ ///
+ /// 用户Id
+ /// 企业微信用户Id
+ ///
+ ///
+ Task BindUserAsync(
+ Guid userId,
+ string weChatUserId,
+ CancellationToken cancellationToken = default);
}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/NullWeChatWorkInternalUserFinder.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/NullWeChatWorkUserClaimProvider.cs
similarity index 61%
rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/NullWeChatWorkInternalUserFinder.cs
rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/NullWeChatWorkUserClaimProvider.cs
index d9ac3748b..cc4921f84 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/NullWeChatWorkInternalUserFinder.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/NullWeChatWorkUserClaimProvider.cs
@@ -8,9 +8,9 @@ using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.WeChat.Work.Authorize;
[Dependency(ServiceLifetime.Singleton, TryRegister = true)]
-public class NullWeChatWorkInternalUserFinder : IWeChatWorkInternalUserFinder
+public class NullWeChatWorkUserClaimProvider : IWeChatWorkUserClaimProvider
{
- public readonly static IWeChatWorkInternalUserFinder Instance = new NullWeChatWorkInternalUserFinder();
+ public readonly static IWeChatWorkUserClaimProvider Instance = new NullWeChatWorkUserClaimProvider();
public Task FindUserIdentifierAsync(
Guid userId,
CancellationToken cancellationToken = default)
@@ -25,4 +25,11 @@ public class NullWeChatWorkInternalUserFinder : IWeChatWorkInternalUserFinder
{
return Task.FromResult(new List());
}
+ public Task BindUserAsync(
+ Guid userId,
+ string weChatUserId,
+ CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException("请使用 AbpIdentityWeChatWorkModule 模块实现企业微信用户绑定!");
+ }
}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeGenerator.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeGenerator.cs
index bf684ed89..319b3d2f4 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeGenerator.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeGenerator.cs
@@ -57,14 +57,19 @@ public class WeChatWorkAuthorizeGenerator : IWeChatWorkAuthorizeGenerator, ISing
}
public async virtual Task GenerateOAuth2LoginAsync(
- string appid,
string redirectUri,
string state,
string loginType = "ServiceApp",
+ string agentId = "",
string lang = "zh")
{
- var agentId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.AgentId);
+ if (agentId.IsNullOrWhiteSpace())
+ {
+ agentId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.AgentId);
+ }
+ var corpId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.CorpId);
+ Check.NotNullOrEmpty(corpId, nameof(corpId));
Check.NotNullOrEmpty(agentId, nameof(agentId));
var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.LoginClient);
@@ -75,7 +80,7 @@ public class WeChatWorkAuthorizeGenerator : IWeChatWorkAuthorizeGenerator, ISing
.Append(client.BaseAddress.AbsoluteUri.EnsureEndsWith('/'))
.Append("wwlogin/sso/login")
.AppendFormat("?login_type={0}", loginType)
- .AppendFormat("&appid={0}", appid)
+ .AppendFormat("&appid={0}", corpId)
.AppendFormat("&agentid={0}", agentId)
.AppendFormat("&redirect_uri={0}", HttpUtility.UrlEncode(redirectUri))
.AppendFormat("&state={0}", state)
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/IJsApiTicketProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/IJsApiTicketProvider.cs
new file mode 100644
index 000000000..59dde2512
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/IJsApiTicketProvider.cs
@@ -0,0 +1,32 @@
+using LINGYUN.Abp.WeChat.Work.JsSdk.Models;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.JsSdk;
+///
+/// JS-SDK临时票据提供者
+/// See: https://developer.work.weixin.qq.com/document/path/90506
+///
+public interface IJsApiTicketProvider
+{
+ ///
+ /// 获取企业 jsapi_ticket
+ ///
+ ///
+ ///
+ Task GetTicketInfoAsync(CancellationToken cancellationToken = default);
+ ///
+ /// 获取应用 jsapi_ticket
+ ///
+ ///
+ ///
+ Task GetAgentTicketInfoAsync(CancellationToken cancellationToken = default);
+ ///
+ /// 获取JS-SDK签名
+ ///
+ /// JS-SDK临时票据
+ /// 生成签名的url
+ ///
+ ///
+ JsApiSignatureData GenerateSignature(JsApiTicketInfo ticketInfo, string url, CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/JsApiTicketHelper.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/JsApiTicketHelper.cs
new file mode 100644
index 000000000..f600da4fc
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/JsApiTicketHelper.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace LINGYUN.Abp.WeChat.Work.JsSdk;
+public static class JsApiTicketHelper
+{
+ private static string[] _randomChars = new string[]
+ {
+ "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
+ "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"
+ };
+
+ public static string GenerateNonce()
+ {
+ var r = new Random();
+ var sb = new StringBuilder();
+ var length = _randomChars.Length;
+ for (var i = 0; i < 15; i++)
+ {
+ sb.Append(_randomChars[r.Next(length - 1)]);
+ }
+ return sb.ToString();
+ }
+
+ public static long GenerateTimestamp()
+ {
+ return (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000;
+ }
+
+ private static string ToSha1(string str)
+ {
+ using (var sha = SHA1.Create())
+ {
+ var data = sha.ComputeHash(Encoding.UTF8.GetBytes(str));
+
+ var sb = new StringBuilder();
+ foreach (var d in data)
+ {
+ sb.Append(d.ToString("x2"));
+ }
+ return sb.ToString();
+ }
+ }
+ ///
+ /// 生成JS-SDK签名
+ /// See: https://developer.work.weixin.qq.com/document/path/90506
+ ///
+ ///
+ ///
+ ///
+ public static string GenerateSignature(
+ string jsapiTicket,
+ string nonce,
+ string timestamp,
+ string url)
+ {
+ var sb = new StringBuilder();
+ sb.Append("jsapi_ticket=").Append(jsapiTicket).Append("&")
+ .Append("noncestr=").Append(nonce).Append("&")
+ .Append("timestamp=").Append(timestamp).Append("&")
+ .Append("url=").Append(url.IndexOf("#") >= 0 ? url.Substring(0, url.IndexOf("#")) : url);
+ return ToSha1(sb.ToString());
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/JsApiTicketProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/JsApiTicketProvider.cs
new file mode 100644
index 000000000..934e125e7
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/JsApiTicketProvider.cs
@@ -0,0 +1,105 @@
+using LINGYUN.Abp.WeChat.Work.JsSdk.Models;
+using LINGYUN.Abp.WeChat.Work.Token;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using System;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Caching;
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.WeChat.Work.JsSdk;
+public class JsApiTicketProvider : IJsApiTicketProvider, ISingletonDependency
+{
+ public ILogger Logger { get; set; }
+
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IDistributedCache Cache { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public JsApiTicketProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider,
+ IDistributedCache cache)
+ {
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ HttpClientFactory = httpClientFactory;
+ Cache = cache;
+
+ Logger = NullLogger.Instance;
+ }
+
+ public async virtual Task GetAgentTicketInfoAsync(CancellationToken cancellationToken = default)
+ {
+ var cacheKey = nameof(GetAgentTicketInfoAsync);
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var cackeItem = await GetCacheItemAsync(
+ cacheKey,
+ $"/cgi-bin/ticket/get?access_token={token.AccessToken}&type=agent_config",
+ cancellationToken);
+
+ return new JsApiTicketInfo(cackeItem.Ticket, cackeItem.ExpiresIn);
+ }
+
+ public async virtual Task GetTicketInfoAsync(CancellationToken cancellationToken = default)
+ {
+ var cacheKey = nameof(GetTicketInfoAsync);
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var cackeItem = await GetCacheItemAsync(
+ cacheKey,
+ $"/cgi-bin/get_jsapi_ticket?access_token={token.AccessToken}",
+ cancellationToken);
+
+ return new JsApiTicketInfo(cackeItem.Ticket, cackeItem.ExpiresIn);
+ }
+
+ public virtual JsApiSignatureData GenerateSignature(JsApiTicketInfo ticketInfo, string url, CancellationToken cancellationToken = default)
+ {
+ var nonce = JsApiTicketHelper.GenerateNonce();
+ var timestamp = JsApiTicketHelper.GenerateTimestamp().ToString();
+ var signature = JsApiTicketHelper.GenerateSignature(ticketInfo.Ticket, nonce, timestamp, url);
+
+ return new JsApiSignatureData(nonce, timestamp, signature);
+ }
+
+ protected async virtual Task GetCacheItemAsync(
+ string cacheKey,
+ string jsapiTicketUrl,
+ CancellationToken cancellationToken = default)
+ {
+ var cacheItem = await Cache.GetAsync(cacheKey, token: cancellationToken);
+
+ if (cacheItem != null)
+ {
+ Logger.LogDebug($"Found JsApiTicket in the cache: {cacheKey}");
+ return cacheItem;
+ }
+
+ Logger.LogDebug($"Not found JsApiTicket in the cache, getting from the httpClient: {cacheKey}");
+
+ var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
+
+ using var response = await client.GetAsync(
+ jsapiTicketUrl,
+ cancellationToken);
+ var ticketInfoResponse = await response.DeserializeObjectAsync();
+ var ticketInfo = ticketInfoResponse.ToJsApiTicket();
+ cacheItem = new JsApiTicketInfoCacheItem(ticketInfo.Ticket, ticketInfo.ExpiresIn);
+
+ Logger.LogDebug($"Setting the cache item: {cacheKey}");
+
+ var cacheOptions = new DistributedCacheEntryOptions
+ {
+ // 设置绝对过期时间为Token有效期剩余的二分钟
+ AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(ticketInfo.ExpiresIn - 100),
+ };
+
+ await Cache.SetAsync(cacheKey, cacheItem, cacheOptions, token: cancellationToken);
+
+ Logger.LogDebug($"Finished setting the cache item: {cacheKey}");
+
+ return cacheItem;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiSignatureData.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiSignatureData.cs
new file mode 100644
index 000000000..8d7c054c7
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiSignatureData.cs
@@ -0,0 +1,13 @@
+namespace LINGYUN.Abp.WeChat.Work.JsSdk.Models;
+public class JsApiSignatureData
+{
+ public string Nonce { get; }
+ public string Timestamp { get; }
+ public string Signature { get; }
+ public JsApiSignatureData(string nonce, string timestamp, string signature)
+ {
+ Nonce = nonce;
+ Timestamp = timestamp;
+ Signature = signature;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfo.cs
new file mode 100644
index 000000000..3b432736a
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfo.cs
@@ -0,0 +1,22 @@
+namespace LINGYUN.Abp.WeChat.Work.JsSdk.Models;
+public class JsApiTicketInfo
+{
+ ///
+ /// 生成签名所需的 jsapi_ticket,最长为512字节
+ ///
+ public string Ticket { get; set; }
+ ///
+ /// 凭证的有效时间(秒)
+ ///
+ public int ExpiresIn { get; set; }
+ public JsApiTicketInfo()
+ {
+
+ }
+
+ public JsApiTicketInfo(string ticket, int expiresIn)
+ {
+ Ticket = ticket;
+ ExpiresIn = expiresIn;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfoCacheItem.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfoCacheItem.cs
new file mode 100644
index 000000000..eb48d8133
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfoCacheItem.cs
@@ -0,0 +1,19 @@
+namespace LINGYUN.Abp.WeChat.Work.JsSdk.Models;
+
+public class JsApiTicketInfoCacheItem
+{
+ public string Ticket { get; set; }
+
+ public int ExpiresIn { get; set; }
+
+ public JsApiTicketInfoCacheItem()
+ {
+
+ }
+
+ public JsApiTicketInfoCacheItem(string ticket, int expiresIn)
+ {
+ Ticket = ticket;
+ ExpiresIn = expiresIn;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfoResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfoResponse.cs
new file mode 100644
index 000000000..e5c77dac3
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfoResponse.cs
@@ -0,0 +1,27 @@
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.JsSdk.Models;
+
+public class JsApiTicketInfoResponse : WeChatWorkResponse
+{
+ ///
+ /// 生成签名所需的 jsapi_ticket,最长为512字节
+ ///
+ [JsonProperty("ticket")]
+ [JsonPropertyName("ticket")]
+ public string Ticket { get; set; }
+ ///
+ /// 凭证的有效时间(秒)
+ ///
+ [JsonProperty("expires_in")]
+ [JsonPropertyName("expires_in")]
+ [System.Text.Json.Serialization.JsonConverter(typeof(NumberToStringConverter))]
+ public int ExpiresIn { get; set; }
+
+ public JsApiTicketInfo ToJsApiTicket()
+ {
+ ThrowIfNotSuccess();
+ return new JsApiTicketInfo(Ticket, ExpiresIn);
+ }
+}