Browse Source

Merge pull request #1316 from colinin/wecom-jssdk

feat(wecom): add jssdk interface integration
pull/1327/head
yx lin 5 months ago
committed by GitHub
parent
commit
05f4077e73
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 25
      aspnet-core/framework/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/WeChatWorkUserClaimProvider.cs
  2. 20
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeAppService.cs
  3. 6
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/Dtos/AgentConfigDto.cs
  4. 17
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/Dtos/JsApiSignatureDto.cs
  5. 13
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application.Contracts/LINGYUN/Abp/WeChat/Work/JsSdk/IWeChatWorkJsSdkAppService.cs
  6. 1
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN.Abp.WeChat.Work.Application.csproj
  7. 2
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkApplicationModule.cs
  8. 33
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeAppService.cs
  9. 50
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN/Abp/WeChat/Work/JsSdk/WeChatWorkJsSdkAppService.cs
  10. 47
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeController.cs
  11. 46
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.HttpApi/LINGYUN/Abp/WeChat/Work/JsSdk/WeChatWorkJsSdkController.cs
  12. 2
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkGlobalConsts.cs
  13. 4
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeGenerator.cs
  14. 16
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkUserClaimProvider.cs
  15. 11
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/NullWeChatWorkUserClaimProvider.cs
  16. 11
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/WeChatWorkAuthorizeGenerator.cs
  17. 32
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/IJsApiTicketProvider.cs
  18. 65
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/JsApiTicketHelper.cs
  19. 105
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/JsApiTicketProvider.cs
  20. 13
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiSignatureData.cs
  21. 22
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfo.cs
  22. 19
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfoCacheItem.cs
  23. 27
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/JsSdk/Models/JsApiTicketInfoResponse.cs

25
aspnet-core/framework/wechat/LINGYUN.Abp.Identity.WeChat.Work/LINGYUN/Abp/Identity/WeChat/Work/WeChatWorkInternalUserFinder.cs → 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));
}
}

20
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
{
/// <summary>
/// 生成授权链接
/// </summary>
/// <param name="urlName">授权回调Url名称</param>
/// <param name="responseType">响应类型</param>
/// <param name="scope">授权范围</param>
/// <returns></returns>
Task<string> GenerateOAuth2AuthorizeAsync(
string redirectUri,
string urlName,
string responseType = "code",
string scope = "snsapi_base");
/// <summary>
/// 生成登录链接
/// </summary>
/// <param name="urlName">授权回调Url名称</param>
/// <param name="loginType">登录类型</param>
/// <returns></returns>
Task<string> GenerateOAuth2LoginAsync(
string redirectUri,
string loginType = "ServiceApp");
string urlName,
string loginType = "CorpApp");
}

6
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; }
}

17
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;
}
}

13
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<AgentConfigDto> GetAgentConfigAsync();
Task<JsApiSignatureDto> GetSignatureAsync(string url);
Task<JsApiSignatureDto> GetAgentSignatureAsync(string url);
}

1
aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Application/LINGYUN.Abp.WeChat.Work.Application.csproj

@ -15,6 +15,7 @@
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Application" />
<PackageReference Include="Volo.Abp.UI.Navigation" />
</ItemGroup>
<ItemGroup>

2
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
{

33
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<string> GenerateOAuth2AuthorizeAsync(string redirectUri, string responseType = "code", string scope = "snsapi_base")
public async virtual Task<string> 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<string> GenerateOAuth2LoginAsync(string redirectUri, string loginType = "ServiceApp")
public async virtual Task<string> 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);
}
}

50
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<AgentConfigDto> 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<JsApiSignatureDto> 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<JsApiSignatureDto> 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);
}
}

47
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
/// <remarks>
/// 详情见企业微信文档: <see cref="https://developer.work.weixin.qq.com/document/path/91022"/>
/// </remarks>
/// <param name="agentid">企业内部应用标识</param>
/// <param name="redirectUri">登录成功重定向url</param>
/// <param name="urlName">授权回调Url名称</param>
/// <param name="responseType">oauth响应类型</param>
/// <param name="scope">oauth授权范围</param>
/// <returns></returns>
[HttpGet]
[Route("oauth2")]
[Authorize]
[Route("oauth2/generate")]
public virtual Task<string> 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);
}
/// <summary>
@ -55,19 +56,19 @@ public class WeChatWorkAuthorizeController : AbpControllerBase, IWeChatWorkAutho
/// <remarks>
/// 详情见企业微信文档: <see cref="https://developer.work.weixin.qq.com/document/path/91022"/>
/// </remarks>
/// <param name="agentid">企业内部应用标识</param>
/// <param name="redirectUri">登录成功重定向url</param>
/// <param name="urlName">授权回调Url名称</param>
/// <param name="responseType">oauth响应类型</param>
/// <param name="scope">oauth授权范围</param>
/// <returns></returns>
[HttpGet]
[Route("oauth2/authorize")]
[Authorize]
[Route("oauth2")]
public async virtual Task<IActionResult> 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
/// <remarks>
/// 详情见企业微信文档: <see cref="https://developer.work.weixin.qq.com/document/path/98152#api%E6%8E%A5%E5%8F%A3"/>
/// </remarks>
/// <param name="redirectUri">登录成功重定向url</param>
/// <param name="urlName">授权回调Url名称</param>
/// <param name="loginType">登录类型, ServiceApp:服务商登录;CorpApp:企业自建/代开发应用登录</param>
/// <param name="agentid">企业自建应用/服务商代开发应用 AgentID,当login_type=CorpApp时填写</param>
/// <returns></returns>
[HttpGet]
[Route("oauth2/login")]
[Authorize]
[Route("oauth2/login/generate")]
public virtual Task<string> 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);
}
/// <summary>
@ -97,17 +98,17 @@ public class WeChatWorkAuthorizeController : AbpControllerBase, IWeChatWorkAutho
/// <remarks>
/// 详情见企业微信文档: <see cref="https://developer.work.weixin.qq.com/document/path/98152#api%E6%8E%A5%E5%8F%A3"/>
/// </remarks>
/// <param name="redirectUri">登录成功重定向url</param>
/// <param name="urlName">授权回调Url名称</param>
/// <param name="loginType">登录类型, ServiceApp:服务商登录;CorpApp:企业自建/代开发应用登录</param>
/// <param name="agentid">企业自建应用/服务商代开发应用 AgentID,当login_type=CorpApp时填写</param>
/// <returns></returns>
[HttpGet]
[Route("oauth2/login/sso")]
[Authorize]
[Route("oauth2/login")]
public async virtual Task<IActionResult> 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);
}

46
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<AgentConfigDto> GetAgentConfigAsync()
{
return _service.GetAgentConfigAsync();
}
[HttpGet]
[Route("agent-signature")]
public virtual Task<JsApiSignatureDto> GetAgentSignatureAsync(string url)
{
return _service.GetAgentSignatureAsync(url);
}
[HttpGet]
[Route("signature")]
public virtual Task<JsApiSignatureDto> GetSignatureAsync(string url)
{
return _service.GetSignatureAsync(url);
}
}

2
aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkGlobalConsts.cs

@ -5,7 +5,7 @@ public class AbpWeChatWorkGlobalConsts
/// <summary>
/// 企业微信对应的Provider名称
/// </summary>
public static string ProviderName { get; set; } = "WeCom";
public static string ProviderName { get; set; } = "WorkWeixin";
/// <summary>
/// 企业微信授权类型
/// </summary>

4
aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkAuthorizeGenerator.cs

@ -25,13 +25,13 @@ public interface IWeChatWorkAuthorizeGenerator
/// <param name="redirectUri"></param>
/// <param name="state"></param>
/// <param name="loginType"></param>
/// <param name="agentid"></param>
/// <param name="agentId"></param>
/// <param name="lang"></param>
/// <returns></returns>
Task<string> GenerateOAuth2LoginAsync(
string redirectUri,
string state,
string loginType = "ServiceApp",
string agentid = "",
string agentId = "",
string lang = "zh");
}

16
aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/IWeChatWorkInternalUserFinder.cs → 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
/// <summary>
/// 企业微信用户身份提供者
/// </summary>
public interface IWeChatWorkUserClaimProvider
{
/// <summary>
/// 通过用户标识查询企业微信用户标识
@ -24,4 +27,15 @@ public interface IWeChatWorkInternalUserFinder
Task<List<string>> FindUserIdentifierListAsync(
IEnumerable<Guid> userIdList,
CancellationToken cancellationToken = default);
/// <summary>
/// 绑定用户企业微信
/// </summary>
/// <param name="userId">用户Id</param>
/// <param name="weChatUserId">企业微信用户Id</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task BindUserAsync(
Guid userId,
string weChatUserId,
CancellationToken cancellationToken = default);
}

11
aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Authorize/NullWeChatWorkInternalUserFinder.cs → 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<string> FindUserIdentifierAsync(
Guid userId,
CancellationToken cancellationToken = default)
@ -25,4 +25,11 @@ public class NullWeChatWorkInternalUserFinder : IWeChatWorkInternalUserFinder
{
return Task.FromResult(new List<string>());
}
public Task BindUserAsync(
Guid userId,
string weChatUserId,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException("请使用 AbpIdentityWeChatWorkModule 模块实现企业微信用户绑定!");
}
}

11
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<string> 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)

32
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;
/// <summary>
/// JS-SDK临时票据提供者
/// See: https://developer.work.weixin.qq.com/document/path/90506
/// </summary>
public interface IJsApiTicketProvider
{
/// <summary>
/// 获取企业 jsapi_ticket
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<JsApiTicketInfo> GetTicketInfoAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 获取应用 jsapi_ticket
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<JsApiTicketInfo> GetAgentTicketInfoAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 获取JS-SDK签名
/// </summary>
/// <param name="ticketInfo">JS-SDK临时票据</param>
/// <param name="url">生成签名的url</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
JsApiSignatureData GenerateSignature(JsApiTicketInfo ticketInfo, string url, CancellationToken cancellationToken = default);
}

65
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();
}
}
/// <summary>
/// 生成JS-SDK签名
/// See: https://developer.work.weixin.qq.com/document/path/90506
/// </summary>
/// <param name="jsapiTicket"></param>
/// <param name="url"></param>
/// <returns></returns>
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());
}
}

105
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<JsApiTicketProvider> Logger { get; set; }
protected IHttpClientFactory HttpClientFactory { get; }
protected IDistributedCache<JsApiTicketInfoCacheItem> Cache { get; }
protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
public JsApiTicketProvider(
IHttpClientFactory httpClientFactory,
IWeChatWorkTokenProvider weChatWorkTokenProvider,
IDistributedCache<JsApiTicketInfoCacheItem> cache)
{
WeChatWorkTokenProvider = weChatWorkTokenProvider;
HttpClientFactory = httpClientFactory;
Cache = cache;
Logger = NullLogger<JsApiTicketProvider>.Instance;
}
public async virtual Task<JsApiTicketInfo> 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<JsApiTicketInfo> 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<JsApiTicketInfoCacheItem> 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<JsApiTicketInfoResponse>();
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;
}
}

13
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;
}
}

22
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
{
/// <summary>
/// 生成签名所需的 jsapi_ticket,最长为512字节
/// </summary>
public string Ticket { get; set; }
/// <summary>
/// 凭证的有效时间(秒)
/// </summary>
public int ExpiresIn { get; set; }
public JsApiTicketInfo()
{
}
public JsApiTicketInfo(string ticket, int expiresIn)
{
Ticket = ticket;
ExpiresIn = expiresIn;
}
}

19
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;
}
}

27
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
{
/// <summary>
/// 生成签名所需的 jsapi_ticket,最长为512字节
/// </summary>
[JsonProperty("ticket")]
[JsonPropertyName("ticket")]
public string Ticket { get; set; }
/// <summary>
/// 凭证的有效时间(秒)
/// </summary>
[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);
}
}
Loading…
Cancel
Save