Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make callback endpoints support multi-tenant and multi-app #35

Merged
merged 6 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,48 +1,11 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity;
using EasyAbp.Abp.WeChat.Official.Infrastructure.OptionsResolve;
using Volo.Abp.Threading;

namespace EasyAbp.Abp.WeChat.Official.HttpApi
{
[DependsOn(typeof(AbpWeChatOfficialModule),
typeof(AbpAspNetCoreMvcModule))]
public class AbpWeChatOfficialHttpApiModule : AbpModule
{
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
VerifyOptions(context);
}

private void VerifyOptions(ApplicationInitializationContext context)
{
AsyncHelper.RunSync(async () =>
{
var options = await context.ServiceProvider
.GetRequiredService<IWeChatOfficialOptionsResolver>()
.ResolveAsync();

if (string.IsNullOrEmpty(options.Token))
{
throw new ArgumentNullException(nameof(options.Token),
"该参数是微信公众平台的必填参数,不能够为空。");
}

if (string.IsNullOrEmpty(options.AppId))
{
throw new ArgumentNullException(nameof(options.AppId),
"该参数是微信公众平台的必填参数,不能够为空。");
}

if (string.IsNullOrEmpty(options.AppSecret))
{
throw new ArgumentNullException(nameof(options.AppSecret),
"该参数是微信公众平台的必填参数,不能够为空。");
}
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Net.Http;
using System;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Web;
Expand All @@ -12,7 +12,9 @@
using EasyAbp.Abp.WeChat.Official.HttpApi.Models;
using EasyAbp.Abp.WeChat.Official.Infrastructure;
using EasyAbp.Abp.WeChat.Official.Infrastructure.OptionsResolve;
using EasyAbp.Abp.WeChat.Official.Infrastructure.OptionsResolve.Contributors;
using EasyAbp.Abp.WeChat.Official.Services.Login;
using JetBrains.Annotations;

namespace EasyAbp.Abp.WeChat.Official.HttpApi.Controllers
{
Expand All @@ -22,36 +24,41 @@ namespace EasyAbp.Abp.WeChat.Official.HttpApi.Controllers
[Route("/wechat")]
public class WeChatController : AbpControllerBase
{
private readonly IWeChatOfficialOptionsResolver _optionsResolver;
private readonly IHttpClientFactory _httpClientFactory;

private readonly SignatureChecker _signatureChecker;
private readonly ISignatureGenerator _signatureGenerator;

private readonly IJsTicketAccessor _jsTicketAccessor;

private readonly ISignatureGenerator _signatureGenerator;
private readonly LoginService _loginService;
private readonly IWeChatOfficialAsyncLocal _weChatOfficialAsyncLocal;
private readonly IWeChatOfficialOptionsResolver _optionsResolver;

public WeChatController(SignatureChecker signatureChecker,
IHttpClientFactory httpClientFactory,
IJsTicketAccessor jsTicketAccessor,
ISignatureGenerator signatureGenerator,
LoginService loginService,
IWeChatOfficialAsyncLocal weChatOfficialAsyncLocal,
IWeChatOfficialOptionsResolver optionsResolver)
{
_signatureChecker = signatureChecker;
_httpClientFactory = httpClientFactory;
_jsTicketAccessor = jsTicketAccessor;
_signatureGenerator = signatureGenerator;
_optionsResolver = optionsResolver;
_loginService = loginService;
_weChatOfficialAsyncLocal = weChatOfficialAsyncLocal;
}

[HttpGet]
[Route("verify")]
public virtual async Task<string> Verify(VerifyRequestDto input)
[Route("verify/tenant-id/{tenantId}")]
[Route("verify/app-id/{appId}")]
[Route("verify/tenant-id/{tenantId}/app-id/{appId}")]
public virtual async Task<string> Verify(
VerifyRequestDto input, [CanBeNull] string tenantId, [CanBeNull] string appId)
{
var options = await _optionsResolver.ResolveAsync();
using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId));

// 如果指定了 appId,请务必实现 IHttpApiWeChatOfficialOptionsProvider
var options = await ResolveOptionsAsync(appId);

if (_signatureChecker.Validate(options.Token, input.Timestamp, input.Nonce, input.Signature))
{
return input.EchoStr;
Expand All @@ -62,28 +69,58 @@ public virtual async Task<string> Verify(VerifyRequestDto input)

[HttpGet]
[Route("redirect-url")]
public virtual async Task<ActionResult> RedirectUrl(RedirectUrlRequest input)
[Route("redirect-url/tenant-id/{tenantId}")]
[Route("redirect-url/app-id/{appId}")]
[Route("redirect-url/tenant-id/{tenantId}/app-id/{appId}")]
public virtual async Task<ActionResult> RedirectUrl(RedirectUrlRequest input, [CanBeNull] string tenantId,
[CanBeNull] string appId)
{
if (input == null) return BadRequest();
if (string.IsNullOrEmpty(input.Code)) return BadRequest();

var options = await _optionsResolver.ResolveAsync();
using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId));

// 如果指定了 appId,请务必实现 IHttpApiWeChatOfficialOptionsProvider
var options = await ResolveOptionsAsync(appId);

return Redirect($"{options.OAuthRedirectUrl.TrimEnd('/')}?code={input.Code}");
}

[HttpGet]
[Route("access-token-by-code")]
public virtual async Task<Code2AccessTokenResponse> GetAccessTokenByCode([FromQuery] string code)
[Route("access-token-by-code/tenant-id/{tenantId}")]
[Route("access-token-by-code/app-id/{appId}")]
[Route("access-token-by-code/tenant-id/{tenantId}/app-id/{appId}")]
public virtual async Task<Code2AccessTokenResponse> GetAccessTokenByCode([FromQuery] string code,
[CanBeNull] string tenantId, [CanBeNull] string appId)
{
using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId));

// 如果指定了 appId,请务必实现 IHttpApiWeChatOfficialOptionsProvider
var options = await ResolveOptionsAsync(appId);

using var changeOptions = _weChatOfficialAsyncLocal.Change(options);

return await _loginService.Code2AccessTokenAsync(code);
}

[HttpGet]
[Route("js-sdk-config-parameters")]
public virtual async Task<ActionResult> GetJsSdkConfigParameters([FromQuery] string jsUrl)
[Route("js-sdk-config-parameters/tenant-id/{tenantId}")]
[Route("js-sdk-config-parameters/app-id/{appId}")]
[Route("js-sdk-config-parameters/tenant-id/{tenantId}/app-id/{appId}")]
public virtual async Task<ActionResult> GetJsSdkConfigParameters([FromQuery] string jsUrl,
[CanBeNull] string tenantId, [CanBeNull] string appId)
{
if (string.IsNullOrEmpty(jsUrl)) throw new UserFriendlyException("需要计算的 JS URL 参数不能够为空。");

using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId));

// 如果指定了 appId,请务必实现 IHttpApiWeChatOfficialOptionsProvider
var options = await ResolveOptionsAsync(appId);

using var changeOptions = _weChatOfficialAsyncLocal.Change(options);

var nonceStr = RandomStringHelper.GetRandomString();
var timeStamp = DateTimeHelper.GetNowTimeStamp();
var ticket = await _jsTicketAccessor.GetTicketAsync();
Expand All @@ -98,12 +135,18 @@ public virtual async Task<ActionResult> GetJsSdkConfigParameters([FromQuery] str

return new JsonResult(new
{
appid = (await _optionsResolver.ResolveAsync()).AppId,
appid = options.AppId,
noncestr = nonceStr,
timestamp = timeStamp,
signature = signStr,
jsapi_ticket = ticket
});
}

protected virtual async Task<IWeChatOfficialOptions> ResolveOptionsAsync(string appId)
{
var provider = LazyServiceProvider.LazyGetRequiredService<IHttpApiWeChatOfficialOptionsProvider>();
return appId.IsNullOrWhiteSpace() ? await _optionsResolver.ResolveAsync() : await provider.GetAsync(appId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using EasyAbp.Abp.WeChat.Official.Infrastructure.OptionsResolve;
using JetBrains.Annotations;

namespace EasyAbp.Abp.WeChat.Official.HttpApi;

public interface IHttpApiWeChatOfficialOptionsProvider
{
Task<IWeChatOfficialOptions> GetAsync([CanBeNull] string appId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,36 @@
using Newtonsoft.Json.Linq;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using EasyAbp.Abp.WeChat.Common;
using EasyAbp.Abp.WeChat.Official.Infrastructure.OptionsResolve;

namespace EasyAbp.Abp.WeChat.Official.Infrastructure
{
public class DefaultJsTicketAccessor : IJsTicketAccessor, ISingletonDependency
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IAccessTokenAccessor _accessTokenAccessor;
private readonly IWeChatOfficialOptionsResolver _weChatOfficialOptionsResolver;
private readonly IDistributedCache<string> _distributedCache;

public DefaultJsTicketAccessor(IHttpClientFactory httpClientFactory,
public DefaultJsTicketAccessor(
IHttpClientFactory httpClientFactory,
IAccessTokenAccessor accessTokenAccessor,
IWeChatOfficialOptionsResolver weChatOfficialOptionsResolver,
IDistributedCache<string> distributedCache)
{
_httpClientFactory = httpClientFactory;
_accessTokenAccessor = accessTokenAccessor;
_weChatOfficialOptionsResolver = weChatOfficialOptionsResolver;
_distributedCache = distributedCache;
}

public virtual async Task<string> GetTicketJsonAsync()
{
var options = await _weChatOfficialOptionsResolver.ResolveAsync();

var accessToken = await _accessTokenAccessor.GetAccessTokenAsync();

return await _distributedCache.GetOrAddAsync("CurrentJsTicket",
return await _distributedCache.GetOrAddAsync(await GetJsTicketAsync(options),
async () =>
{
var client = _httpClientFactory.CreateClient();
Expand All @@ -43,6 +49,11 @@ public virtual async Task<string> GetTicketJsonAsync()
});
}

protected virtual async Task<string> GetJsTicketAsync(IWeChatOfficialOptions options)
{
return $"WeChatJsTicket:{options.AppId}";
}

public virtual async Task<string> GetTicketAsync()
{
var json = await GetTicketJsonAsync();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand All @@ -8,6 +9,8 @@
using EasyAbp.Abp.WeChat.OpenPlatform.Infrastructure.Models.ThirdPartyPlatform;
using EasyAbp.Abp.WeChat.OpenPlatform.Infrastructure.ThirdPartyPlatform;
using EasyAbp.Abp.WeChat.OpenPlatform.Infrastructure.ThirdPartyPlatform.OptionsResolve;
using EasyAbp.Abp.WeChat.OpenPlatform.Infrastructure.ThirdPartyPlatform.OptionsResolve.Contributors;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
Expand All @@ -21,13 +24,16 @@ namespace EasyAbp.Abp.WeChat.OpenPlatform.Controllers;
[Route("/wechat/third-party-platform")]
public class WeChatThirdPartyPlatformController : AbpControllerBase
{
private readonly IWeChatThirdPartyPlatformAsyncLocal _weChatThirdPartyPlatformAsyncLocal;
private readonly IWeChatNotificationEncryptor _weChatNotificationEncryptor;
private readonly IWeChatThirdPartyPlatformOptionsResolver _optionsResolver;

public WeChatThirdPartyPlatformController(
IWeChatThirdPartyPlatformAsyncLocal weChatThirdPartyPlatformAsyncLocal,
IWeChatNotificationEncryptor weChatNotificationEncryptor,
IWeChatThirdPartyPlatformOptionsResolver optionsResolver)
{
_weChatThirdPartyPlatformAsyncLocal = weChatThirdPartyPlatformAsyncLocal;
_weChatNotificationEncryptor = weChatNotificationEncryptor;
_optionsResolver = optionsResolver;
}
Expand All @@ -39,8 +45,11 @@ public class WeChatThirdPartyPlatformController : AbpControllerBase
/// </summary>
[HttpPost]
[Route("notify/auth")]
public virtual async Task<ActionResult> NotifyAuthAsync()
[Route("notify/auth/tenant-id/{tenantId}")]
public virtual async Task<ActionResult> NotifyAuthAsync([CanBeNull] string tenantId)
{
using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId));

var handlers = LazyServiceProvider.LazyGetService<IEnumerable<IWeChatThirdPartyPlatformAuthEventHandler>>();

Request.EnableBuffering();
Expand All @@ -52,6 +61,10 @@ public virtual async Task<ActionResult> NotifyAuthAsync()
Model = model
};

// 如果你拥有不止一个第三方平台,请务必实现 IHttpApiWeChatThirdPartyOptionsProvider
var options = await ResolveOptionsAsync(model.AppId);
using var changeOptions = _weChatThirdPartyPlatformAsyncLocal.Change(options);

foreach (var handler in handlers.Where(x => x.InfoType == model.InfoType))
{
await handler.HandleAsync(context);
Expand All @@ -73,9 +86,19 @@ public virtual async Task<ActionResult> NotifyAuthAsync()
/// https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/operation/thirdparty/prepare.html
/// </summary>
[HttpPost]
[Route("notify/app")]
public virtual async Task<ActionResult> NotifyAppAsync()
[Route("notify/app/component-app-id/{componentAppId}/app-id/{appId}")]
[Route("notify/app/tenant-id/{tenantId}/app-id/{appId}")]
[Route("notify/app/tenant-id/{tenantId}/component-app-id/{componentAppId}/app-id/{appId}")]
public virtual async Task<ActionResult> NotifyAppAsync(
[CanBeNull] string tenantId, [CanBeNull] string componentAppId, [NotNull] string appId)
{
using var changeTenant = CurrentTenant.Change(tenantId.IsNullOrWhiteSpace() ? null : Guid.Parse(tenantId));

// 如果你拥有不止一个第三方平台,请务必实现 IHttpApiWeChatThirdPartyOptionsProvider
var options = await ResolveOptionsAsync(componentAppId);

using var changeOptions = _weChatThirdPartyPlatformAsyncLocal.Change(options);

var handlers = LazyServiceProvider.LazyGetService<IEnumerable<IWeChatThirdPartyPlatformAppEventHandler>>();

Request.EnableBuffering();
Expand All @@ -84,6 +107,8 @@ public virtual async Task<ActionResult> NotifyAppAsync()
var model = await DecryptMsgAsync<WeChatAppNotificationModel>(await streamReader.ReadToEndAsync());
var context = new WeChatThirdPartyPlatformAppEventHandlerContext
{
ComponentAppId = componentAppId,
AuthorizerAppId = appId,
Model = model
};

Expand Down Expand Up @@ -116,4 +141,18 @@ protected virtual async Task<T> DecryptMsgAsync<T>(string postData) where T : Ex
HttpContext.Request.Query["nonce"].FirstOrDefault(),
postData);
}

protected virtual async Task<IWeChatThirdPartyPlatformOptions> ResolveOptionsAsync(string componentAppId)
{
var options = await _optionsResolver.ResolveAsync();

if (componentAppId.IsNullOrWhiteSpace() || componentAppId == options.AppId)
{
return options;
}

var provider = LazyServiceProvider.LazyGetRequiredService<IHttpApiWeChatThirdPartyPlatformOptionsProvider>();

return await provider.GetAsync(componentAppId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using EasyAbp.Abp.WeChat.OpenPlatform.Infrastructure.ThirdPartyPlatform.OptionsResolve;
using JetBrains.Annotations;

namespace EasyAbp.Abp.WeChat.OpenPlatform;

public interface IHttpApiWeChatThirdPartyPlatformOptionsProvider
{
Task<IWeChatThirdPartyPlatformOptions> GetAsync([CanBeNull] string appId);
}