33 changed files with 746 additions and 56 deletions
@ -1,6 +1,6 @@ |
|||
namespace System.Net.Http |
|||
{ |
|||
public class WeChatTokenRequest |
|||
public class WeChatOpenIdRequest |
|||
{ |
|||
public string BaseUrl { get; set; } |
|||
public string AppId { get; set; } |
|||
@ -0,0 +1,30 @@ |
|||
using LINGYUN.Abp.WeChat.Authorization; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Polly; |
|||
using System; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpWeChatAuthorizationModule), |
|||
typeof(AbpNotificationModule))] |
|||
public class AbpNotificationsWeChatWeAppModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Services.GetConfiguration(); |
|||
Configure<AbpWeChatWeAppNotificationOptions>(configuration.GetSection("Notifications:WeChat:WeApp")); |
|||
|
|||
// TODO:是否有必要启用重试机制?
|
|||
context.Services.AddHttpClient(WeChatWeAppNotificationSender.SendNotificationClientName) |
|||
.AddTransientHttpErrorPolicy(builder => |
|||
builder.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)))); |
|||
|
|||
Configure<AbpNotificationOptions>(options => |
|||
{ |
|||
options.PublishProviders.Add<WeChatWeAppNotificationPublishProvider>(); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
|||
{ |
|||
public class AbpWeChatWeAppNotificationOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 默认小程序模板
|
|||
/// </summary>
|
|||
public string DefaultTemplateId { get; set; } |
|||
/// <summary>
|
|||
/// 默认跳转小程序类型
|
|||
/// </summary>
|
|||
public string DefaultWeAppState { get; set; } = "developer"; |
|||
/// <summary>
|
|||
/// 默认小程序语言
|
|||
/// </summary>
|
|||
public string DefaultWeAppLanguage { get; set; } = "zh_CN"; |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
|||
{ |
|||
public interface IWeChatWeAppNotificationSender |
|||
{ |
|||
Task SendAsync(WeChatWeAppSendNotificationData notificationData); |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
|||
{ |
|||
/// <summary>
|
|||
/// 微信小程序消息推送提供者
|
|||
/// </summary>
|
|||
public class WeChatWeAppNotificationPublishProvider : NotificationPublishProvider |
|||
{ |
|||
public override string Name => "WeChat.WeApp"; |
|||
|
|||
protected IWeChatWeAppNotificationSender NotificationSender { get; } |
|||
protected AbpWeChatWeAppNotificationOptions Options { get; } |
|||
public WeChatWeAppNotificationPublishProvider( |
|||
IServiceProvider serviceProvider, |
|||
IWeChatWeAppNotificationSender notificationSender, |
|||
IOptions<AbpWeChatWeAppNotificationOptions> options) |
|||
: base(serviceProvider) |
|||
{ |
|||
Options = options.Value; |
|||
NotificationSender = notificationSender; |
|||
} |
|||
|
|||
public override async Task PublishAsync(NotificationInfo notification, IEnumerable<UserIdentifier> identifiers) |
|||
{ |
|||
// step1 默认微信openid绑定的就是username,
|
|||
// 如果不是,需要自行处理openid获取逻辑
|
|||
|
|||
// step2 调用微信消息推送接口
|
|||
foreach (var identifier in identifiers) |
|||
{ |
|||
await SendWeChatTemplateMessagAsync(notification, identifier); |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task SendWeChatTemplateMessagAsync(NotificationInfo notification, UserIdentifier identifier) |
|||
{ |
|||
var templateId = GetOrDefaultTemplateId(notification.Data); |
|||
Logger.LogDebug($"Get wechat weapp template id: {templateId}"); |
|||
|
|||
var redirect = GetOrDefault(notification.Data, "RedirectPage", ""); |
|||
Logger.LogDebug($"Get wechat weapp redirect page: {redirect}"); |
|||
|
|||
var weAppState = GetOrDefault(notification.Data, "WeAppState", Options.DefaultWeAppState); |
|||
Logger.LogDebug($"Get wechat weapp state: {weAppState}"); |
|||
|
|||
var weAppLang = GetOrDefault(notification.Data, "WeAppLanguage", Options.DefaultWeAppLanguage); |
|||
Logger.LogDebug($"Get wechat weapp language: {weAppLang}"); |
|||
|
|||
var weChatWeAppNotificationData = new WeChatWeAppSendNotificationData(identifier.UserName, |
|||
templateId, redirect, weAppState, weAppLang); |
|||
|
|||
Logger.LogDebug($"Sending wechat weapp notification: {notification.Name}"); |
|||
// 发送小程序订阅消息
|
|||
await NotificationSender.SendAsync(weChatWeAppNotificationData); |
|||
} |
|||
|
|||
protected string GetOrDefaultTemplateId(NotificationData data) |
|||
{ |
|||
return GetOrDefault(data, "TemplateId", Options.DefaultTemplateId); |
|||
} |
|||
|
|||
protected string GetOrDefault(NotificationData data, string key, string defaultValue) |
|||
{ |
|||
if (data.Properties.TryGetValue(key, out object value)) |
|||
{ |
|||
return value.ToString(); |
|||
} |
|||
return defaultValue; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
using LINGYUN.Abp.WeChat.Authorization; |
|||
using Newtonsoft.Json; |
|||
using System.Collections.Generic; |
|||
using System.Net.Http; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
|||
{ |
|||
public class WeChatWeAppNotificationSender : IWeChatWeAppNotificationSender, ITransientDependency |
|||
{ |
|||
public const string SendNotificationClientName = "WeChatWeAppSendNotificationClient"; |
|||
protected IHttpClientFactory HttpClientFactory { get; } |
|||
protected IJsonSerializer JsonSerializer { get; } |
|||
protected IWeChatTokenProvider WeChatTokenProvider { get; } |
|||
public WeChatWeAppNotificationSender( |
|||
IJsonSerializer jsonSerializer, |
|||
IHttpClientFactory httpClientFactory, |
|||
IWeChatTokenProvider weChatTokenProvider) |
|||
{ |
|||
JsonSerializer = jsonSerializer; |
|||
HttpClientFactory = httpClientFactory; |
|||
WeChatTokenProvider = weChatTokenProvider; |
|||
} |
|||
|
|||
public virtual async Task SendAsync(WeChatWeAppSendNotificationData notificationData) |
|||
{ |
|||
var weChatToken = await WeChatTokenProvider.GetTokenAsync(); |
|||
var requestParamters = new Dictionary<string, string> |
|||
{ |
|||
{ "access_token", weChatToken.AccessToken } |
|||
}; |
|||
var weChatSendNotificationUrl = "https://api.weixin.qq.com"; |
|||
var weChatSendNotificationPath = "/cgi-bin/message/subscribe/send"; |
|||
var requestUrl = BuildRequestUrl(weChatSendNotificationUrl, weChatSendNotificationPath, requestParamters); |
|||
var responseContent = await MakeRequestAndGetResultAsync(requestUrl, notificationData); |
|||
var weChatSenNotificationResponse = JsonSerializer.Deserialize<WeChatSendNotificationResponse>(responseContent); |
|||
weChatSenNotificationResponse.ThrowIfNotSuccess(); |
|||
} |
|||
protected virtual async Task<string> MakeRequestAndGetResultAsync(string url, WeChatWeAppSendNotificationData notificationData) |
|||
{ |
|||
var client = HttpClientFactory.CreateClient(SendNotificationClientName); |
|||
var requestContent = new StringContent(JsonSerializer.Serialize(notificationData)); |
|||
var requestMessage = new HttpRequestMessage(HttpMethod.Post, url) |
|||
{ |
|||
Content = requestContent |
|||
}; |
|||
|
|||
var response = await client.SendAsync(requestMessage); |
|||
if (!response.IsSuccessStatusCode) |
|||
{ |
|||
throw new AbpException($"Baidu http request service returns error! HttpStatusCode: {response.StatusCode}, ReasonPhrase: {response.ReasonPhrase}"); |
|||
} |
|||
var resultContent = await response.Content.ReadAsStringAsync(); |
|||
|
|||
return resultContent; |
|||
} |
|||
|
|||
protected virtual string BuildRequestUrl(string uri, string path, IDictionary<string, string> paramters) |
|||
{ |
|||
var requestUrlBuilder = new StringBuilder(128); |
|||
requestUrlBuilder.Append(uri); |
|||
requestUrlBuilder.Append(path).Append("?"); |
|||
foreach (var paramter in paramters) |
|||
{ |
|||
requestUrlBuilder.AppendFormat("{0}={1}", paramter.Key, paramter.Value); |
|||
requestUrlBuilder.Append("&"); |
|||
} |
|||
requestUrlBuilder.Remove(requestUrlBuilder.Length - 1, 1); |
|||
return requestUrlBuilder.ToString(); |
|||
} |
|||
} |
|||
|
|||
public class WeChatSendNotificationResponse |
|||
{ |
|||
[JsonProperty("errcode")] |
|||
public int ErrorCode { get; set; } |
|||
|
|||
[JsonProperty("errmsg")] |
|||
public string ErrorMessage { get; set; } |
|||
|
|||
public void ThrowIfNotSuccess() |
|||
{ |
|||
if (ErrorCode != 0) |
|||
{ |
|||
throw new AbpException($"Send wechat weapp notification error:{ErrorMessage}"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
#pragma warning disable IDE1006 // 禁止编译器提示
|
|||
namespace LINGYUN.Abp.Notifications.WeChat.WeApp |
|||
{ |
|||
public class WeChatWeAppSendNotificationData |
|||
{ |
|||
/// <summary>
|
|||
/// 接收者(用户)的 openid
|
|||
/// </summary>
|
|||
public string touser { get; set; } |
|||
/// <summary>
|
|||
/// 所需下发的订阅模板id
|
|||
/// </summary>
|
|||
public string template_id { get; set; } |
|||
/// <summary>
|
|||
/// 点击模板卡片后的跳转页面,仅限本小程序内的页面。
|
|||
/// 支持带参数,(示例index?foo=bar)。
|
|||
/// 该字段不填则模板无跳转
|
|||
/// </summary>
|
|||
public string page { get; set; } |
|||
/// <summary>
|
|||
/// 跳转小程序类型:
|
|||
/// developer为开发版;trial为体验版;formal为正式版;
|
|||
/// 默认为正式版
|
|||
/// </summary>
|
|||
public string miniprogram_state { get; set; } |
|||
/// <summary>
|
|||
/// 进入小程序查看”的语言类型,
|
|||
/// 支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),
|
|||
/// 默认为zh_CN
|
|||
/// </summary>
|
|||
public string lang { get; set; } |
|||
|
|||
public WeChatWeAppSendNotificationData() { } |
|||
public WeChatWeAppSendNotificationData(string openId, string templateId, string redirectPage = "", |
|||
string state = "formal", string miniLang = "zh_CN") |
|||
{ |
|||
touser = openId; |
|||
template_id = templateId; |
|||
page = redirectPage; |
|||
miniprogram_state = state; |
|||
lang = miniLang; |
|||
} |
|||
} |
|||
} |
|||
#pragma warning restore IDE1006
|
|||
@ -1,25 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.WeChat |
|||
{ |
|||
public class WeChatNotificationPublishProvider : NotificationPublishProvider |
|||
{ |
|||
public override string Name => "WeChat"; |
|||
|
|||
public WeChatNotificationPublishProvider( |
|||
IServiceProvider serviceProvider) |
|||
: base(serviceProvider) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public override async Task PublishAsync(NotificationInfo notification, IEnumerable<UserIdentifier> identifiers) |
|||
{ |
|||
// step1 默认微信openid绑定的就是username,如果不是,那就根据userid去获取
|
|||
|
|||
// step2 调用微信消息推送接口
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Notifications |
|||
{ |
|||
/// <summary>
|
|||
/// 通知订阅管理器
|
|||
/// </summary>
|
|||
public interface INotificationSubscriptionManager |
|||
{ |
|||
/// <summary>
|
|||
/// 是否已订阅
|
|||
/// </summary>
|
|||
/// <param name="tenantId">租户</param>
|
|||
/// <param name="userId">用户标识</param>
|
|||
/// <param name="notificationName">通知名称</param>
|
|||
/// <returns></returns>
|
|||
Task<bool> IsSubscribedAsync(Guid? tenantId, Guid userId, string notificationName); |
|||
/// <summary>
|
|||
/// 订阅通知
|
|||
/// </summary>
|
|||
/// <param name="tenantId">租户</param>
|
|||
/// <param name="identifier">用户标识</param>
|
|||
/// <param name="notificationName">通知名称</param>
|
|||
/// <returns></returns>
|
|||
Task SubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName); |
|||
/// <summary>
|
|||
/// 订阅通知
|
|||
/// </summary>
|
|||
/// <param name="tenantId">租户</param>
|
|||
/// <param name="identifiers">用户标识列表</param>
|
|||
/// <param name="notificationName">通知名称</param>
|
|||
/// <returns></returns>
|
|||
Task SubscribeAsync(Guid? tenantId, IEnumerable<UserIdentifier> identifiers, string notificationName); |
|||
/// <summary>
|
|||
/// 取消订阅
|
|||
/// </summary>
|
|||
/// <param name="tenantId">租户</param>
|
|||
/// <param name="identifier">用户标识</param>
|
|||
/// <param name="notificationName">通知名称</param>
|
|||
/// <returns></returns>
|
|||
Task UnsubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName); |
|||
/// <summary>
|
|||
/// 获取通知被订阅用户列表
|
|||
/// </summary>
|
|||
/// <param name="tenantId">租户</param>
|
|||
/// <param name="notificationName">通知名称</param>
|
|||
/// <returns></returns>
|
|||
Task<List<NotificationSubscriptionInfo>> GetSubscriptionsAsync(Guid? tenantId, string notificationName); |
|||
/// <summary>
|
|||
/// 获取用户订阅列表
|
|||
/// </summary>
|
|||
/// <param name="tenantId">租户</param>
|
|||
/// <param name="userId">用户标识</param>
|
|||
/// <returns></returns>
|
|||
Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync(Guid? tenantId, Guid userId); |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.Internal |
|||
{ |
|||
internal class NotificationSubscriptionManager : INotificationSubscriptionManager, ITransientDependency |
|||
{ |
|||
private readonly INotificationStore _store; |
|||
|
|||
public NotificationSubscriptionManager( |
|||
INotificationStore store) |
|||
{ |
|||
_store = store; |
|||
} |
|||
|
|||
public virtual async Task<List<NotificationSubscriptionInfo>> GetSubscriptionsAsync(Guid? tenantId, string notificationName) |
|||
{ |
|||
return await _store.GetSubscriptionsAsync(tenantId, notificationName); |
|||
} |
|||
|
|||
public virtual async Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync(Guid? tenantId, Guid userId) |
|||
{ |
|||
return await _store.GetUserSubscriptionsAsync(tenantId, userId); |
|||
} |
|||
|
|||
public virtual async Task<bool> IsSubscribedAsync(Guid? tenantId, Guid userId, string notificationName) |
|||
{ |
|||
return await _store.IsSubscribedAsync(tenantId, userId, notificationName); |
|||
} |
|||
|
|||
public virtual async Task SubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName) |
|||
{ |
|||
if (await IsSubscribedAsync(tenantId, identifier.UserId, notificationName)) |
|||
{ |
|||
return; |
|||
} |
|||
await _store.InsertUserSubscriptionAsync(tenantId, identifier, notificationName); |
|||
} |
|||
|
|||
public virtual async Task SubscribeAsync(Guid? tenantId, IEnumerable<UserIdentifier> identifiers, string notificationName) |
|||
{ |
|||
foreach(var identifier in identifiers) |
|||
{ |
|||
await SubscribeAsync(tenantId, identifier, notificationName); |
|||
} |
|||
} |
|||
|
|||
public virtual async Task UnsubscribeAsync(Guid? tenantId, UserIdentifier identifier, string notificationName) |
|||
{ |
|||
await _store.DeleteUserSubscriptionAsync(tenantId, identifier.UserId, notificationName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="3.1.2" /> |
|||
<PackageReference Include="Volo.Abp.Caching" Version="2.9.0" /> |
|||
<PackageReference Include="Volo.Abp.Json" Version="2.9.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,25 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Polly; |
|||
using System; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
[DependsOn(typeof(AbpJsonModule), typeof(AbpCachingModule))] |
|||
public class AbpWeChatAuthorizationModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Services.GetConfiguration(); |
|||
Configure<AbpWeChatOptions>(configuration.GetSection("WeChat:Auth")); |
|||
|
|||
context.Services.AddHttpClient("WeChatTokenProviderClient", options => |
|||
{ |
|||
options.BaseAddress = new Uri("https://api.weixin.qq.com/cgi-bin/token"); |
|||
}).AddTransientHttpErrorPolicy(builder => |
|||
builder.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)))); |
|||
} |
|||
} |
|||
} |
|||
@ -1,6 +1,6 @@ |
|||
namespace LINGYUN.Abp.IdentityServer |
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
public class AbpWeChatValidatorOptions |
|||
public class AbpWeChatOptions |
|||
{ |
|||
public string AppId { get; set; } |
|||
public string AppSecret { get; set; } |
|||
@ -0,0 +1,9 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
public interface IWeChatTokenProvider |
|||
{ |
|||
Task<WeChatToken> GetTokenAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
/// <summary>
|
|||
/// 微信令牌
|
|||
/// </summary>
|
|||
public class WeChatToken |
|||
{ |
|||
/// <summary>
|
|||
/// 访问令牌
|
|||
/// </summary>
|
|||
public string AccessToken { get; set; } |
|||
/// <summary>
|
|||
/// 过期时间,单位(s)
|
|||
/// </summary>
|
|||
public int ExpiresIn { get; set; } |
|||
public WeChatToken() |
|||
{ |
|||
|
|||
} |
|||
public WeChatToken(string token, int expiresIn) |
|||
{ |
|||
AccessToken = token; |
|||
ExpiresIn = expiresIn; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
public class WeChatTokenCacheItem |
|||
{ |
|||
public string AppId { get; set; } |
|||
|
|||
public WeChatToken WeChatToken { get; set; } |
|||
public WeChatTokenCacheItem() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public WeChatTokenCacheItem(string appId, WeChatToken weChatToken) |
|||
{ |
|||
AppId = appId; |
|||
WeChatToken = weChatToken; |
|||
} |
|||
|
|||
public static string CalculateCacheKey(string provider, string appId) |
|||
{ |
|||
return "p:" + provider + ",o:" + appId; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
public class WeChatTokenProvider : IWeChatTokenProvider, ISingletonDependency |
|||
{ |
|||
public ILogger<WeChatTokenProvider> Logger { get; set; } |
|||
protected IHttpClientFactory HttpClientFactory { get; } |
|||
protected IJsonSerializer JsonSerializer { get; } |
|||
protected IDistributedCache<WeChatTokenCacheItem> Cache { get; } |
|||
protected AbpWeChatOptions Options { get; } |
|||
public WeChatTokenProvider( |
|||
IJsonSerializer jsonSerializer, |
|||
IHttpClientFactory httpClientFactory, |
|||
IOptions<AbpWeChatOptions> options, |
|||
IDistributedCache<WeChatTokenCacheItem> cache) |
|||
{ |
|||
JsonSerializer = jsonSerializer; |
|||
HttpClientFactory = httpClientFactory; |
|||
|
|||
Cache = cache; |
|||
Options = options.Value; |
|||
|
|||
Logger = NullLogger<WeChatTokenProvider>.Instance; |
|||
} |
|||
|
|||
public virtual async Task<WeChatToken> GetTokenAsync() |
|||
{ |
|||
return (await GetCacheItemAsync("WeChatToken", Options.AppId)).WeChatToken; |
|||
} |
|||
|
|||
protected virtual async Task<WeChatTokenCacheItem> GetCacheItemAsync(string provider, string appId) |
|||
{ |
|||
var cacheKey = WeChatTokenCacheItem.CalculateCacheKey(provider, appId); |
|||
|
|||
Logger.LogDebug($"WeChatTokenProvider.GetCacheItemAsync: {cacheKey}"); |
|||
|
|||
var cacheItem = await Cache.GetAsync(cacheKey); |
|||
|
|||
if (cacheItem != null) |
|||
{ |
|||
Logger.LogDebug($"Found in the cache: {cacheKey}"); |
|||
return cacheItem; |
|||
} |
|||
|
|||
Logger.LogDebug($"Not found in the cache, getting from the httpClient: {cacheKey}"); |
|||
|
|||
var client = HttpClientFactory.CreateClient("WeChatTokenProviderClient"); |
|||
|
|||
var request = new WeChatTokenRequest |
|||
{ |
|||
BaseUrl = client.BaseAddress.AbsoluteUri, |
|||
AppSecret = Options.AppSecret, |
|||
AppId = Options.AppId, |
|||
GrantType = "client_credential" |
|||
}; |
|||
|
|||
var response = await client.RequestWeChatCodeTokenAsync(request); |
|||
var responseContent = await response.Content.ReadAsStringAsync(); |
|||
var weChatTokenResponse = JsonSerializer.Deserialize<WeChatTokenResponse>(responseContent); |
|||
var weChatToken = weChatTokenResponse.ToWeChatToken(); |
|||
cacheItem = new WeChatTokenCacheItem(appId, weChatToken); |
|||
|
|||
Logger.LogDebug($"Setting the cache item: {cacheKey}"); |
|||
|
|||
var cacheOptions = new DistributedCacheEntryOptions |
|||
{ |
|||
// 设置绝对过期时间为Token有效期剩余的二分钟
|
|||
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(weChatToken.ExpiresIn - 120) |
|||
}; |
|||
|
|||
await Cache.SetAsync(cacheKey, cacheItem, cacheOptions); |
|||
|
|||
Logger.LogDebug($"Finished setting the cache item: {cacheKey}"); |
|||
|
|||
return cacheItem; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
public class WeChatTokenRequest |
|||
{ |
|||
public string BaseUrl { get; set; } |
|||
public string GrantType { get; set; } |
|||
public string AppId { get; set; } |
|||
public string AppSecret { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using Newtonsoft.Json; |
|||
using Volo.Abp; |
|||
|
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
/// <summary>
|
|||
/// 微信访问令牌返回对象
|
|||
/// </summary>
|
|||
public class WeChatTokenResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 错误码
|
|||
/// </summary>
|
|||
[JsonProperty("errcode")] |
|||
public int ErrorCode { get; set; } |
|||
/// <summary>
|
|||
/// 错误消息
|
|||
/// </summary>
|
|||
[JsonProperty("errmsg")] |
|||
public string ErrorMessage { get; set; } |
|||
/// <summary>
|
|||
/// 访问令牌
|
|||
/// </summary>
|
|||
[JsonProperty("access_token")] |
|||
public string AccessToken { get; set; } |
|||
/// <summary>
|
|||
/// 过期时间,单位(s)
|
|||
/// </summary>
|
|||
[JsonProperty("expires_in")] |
|||
public int ExpiresIn { get; set; } |
|||
|
|||
public WeChatToken ToWeChatToken() |
|||
{ |
|||
if(ErrorCode != 0) |
|||
{ |
|||
throw new AbpException(ErrorMessage); |
|||
} |
|||
return new WeChatToken(AccessToken, ExpiresIn); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using LINGYUN.Abp.WeChat.Authorization; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace System.Net.Http |
|||
{ |
|||
public static class HttpClientWeChatTokenRequestExtensions |
|||
{ |
|||
public static async Task<HttpResponseMessage> RequestWeChatCodeTokenAsync(this HttpMessageInvoker client, WeChatTokenRequest request, CancellationToken cancellationToken = default) |
|||
{ |
|||
var getResuestUrlBuilder = new StringBuilder(); |
|||
getResuestUrlBuilder.Append(request.BaseUrl); |
|||
getResuestUrlBuilder.Append("?grant_type=client_credential"); |
|||
getResuestUrlBuilder.AppendFormat("&appid={0}", request.AppId); |
|||
getResuestUrlBuilder.AppendFormat("&secret={0}", request.AppSecret); |
|||
|
|||
var getRequest = new HttpRequestMessage(HttpMethod.Get, getResuestUrlBuilder.ToString()); |
|||
HttpResponseMessage httpResponse; |
|||
|
|||
httpResponse = await client.SendAsync(getRequest, cancellationToken).ConfigureAwait(false); |
|||
|
|||
return httpResponse; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue