29 changed files with 350 additions and 127 deletions
@ -0,0 +1,28 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.Identity; |
|||
|
|||
namespace LINGYUN.Abp.Account |
|||
{ |
|||
public class WeChatRegisterDto |
|||
{ |
|||
[Required] |
|||
public string Code { get; set; } |
|||
|
|||
[DisableAuditing] |
|||
[DataType(DataType.Password)] |
|||
[Required] |
|||
[StringLength(IdentityUserConsts.MaxPasswordLength)] |
|||
public string Password { get; set; } |
|||
|
|||
[StringLength(IdentityUserConsts.MaxNameLength)] |
|||
public string Name { get; set; } |
|||
|
|||
[StringLength(IdentityUserConsts.MaxUserNameLength)] |
|||
public string UserName { get; set; } |
|||
|
|||
[EmailAddress] |
|||
[StringLength(IdentityUserConsts.MaxEmailLength)] |
|||
public string EmailAddress { get; set; } |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
using IdentityModel.Client; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace System.Net.Http |
|||
{ |
|||
public static class HttpClientTokenRequestExtensions |
|||
{ |
|||
public static async Task<WeChatOpenIdResponse> RequestWeChatCodeTokenAsync(this HttpMessageInvoker client, WeChatOpenIdRequest request, CancellationToken cancellationToken = default) |
|||
{ |
|||
var getResuestUrlBuiilder = new StringBuilder(); |
|||
getResuestUrlBuiilder.Append(request.BaseUrl); |
|||
getResuestUrlBuiilder.AppendFormat("?appid={0}", request.AppId); |
|||
getResuestUrlBuiilder.AppendFormat("&secret={0}", request.Secret); |
|||
getResuestUrlBuiilder.AppendFormat("&js_code={0}", request.Code); |
|||
getResuestUrlBuiilder.Append("&grant_type=authorization_code"); |
|||
|
|||
var getRequest = new HttpRequestMessage(HttpMethod.Get, getResuestUrlBuiilder.ToString()); |
|||
HttpResponseMessage httpResponse; |
|||
try |
|||
{ |
|||
httpResponse = await client.SendAsync(getRequest, cancellationToken).ConfigureAwait(false); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
return ProtocolResponse.FromException<WeChatOpenIdResponse>(ex); |
|||
} |
|||
return await ProtocolResponse.FromHttpResponseAsync<WeChatOpenIdResponse>(httpResponse).ConfigureAwait(false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,48 +0,0 @@ |
|||
using IdentityModel.Client; |
|||
|
|||
namespace System.Net.Http |
|||
{ |
|||
public class WeChatOpenIdResponse : ProtocolResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 用户唯一标识
|
|||
/// </summary>
|
|||
public string OpenId => TryGet("openid"); |
|||
/// <summary>
|
|||
/// 会话密钥
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 仅仅只是要一个openid,这个没多大用吧
|
|||
/// </remarks>
|
|||
public string SessionKey => TryGet("session_key"); |
|||
/// <summary>
|
|||
/// 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回
|
|||
/// </summary>
|
|||
public string UnionId => TryGet("unionid"); |
|||
/// <summary>
|
|||
/// 微信认证成功没有errorcode或者errorcode为0
|
|||
/// </summary>
|
|||
public new bool IsError => !ErrorCode.IsNullOrWhiteSpace() && !"0".Equals(ErrorCode); |
|||
/// <summary>
|
|||
/// 错误码
|
|||
/// </summary>
|
|||
public string ErrorCode => TryGet("errcode"); |
|||
/// <summary>
|
|||
/// 错误信息
|
|||
/// </summary>
|
|||
public new string ErrorMessage |
|||
{ |
|||
get |
|||
{ |
|||
return ErrorCode switch |
|||
{ |
|||
"-1" => "系统繁忙,此时请开发者稍候再试", |
|||
"0" => string.Empty, |
|||
"40029" => "code 无效", |
|||
"45011" => "频率限制,每个用户每分钟100次", |
|||
_ => "未知的异常", |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
public interface IWeChatOpenIdFinder |
|||
{ |
|||
Task<WeChatOpenId> FindAsync(string code); |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
public class WeChatOpenId |
|||
{ |
|||
/// <summary>
|
|||
/// 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回
|
|||
/// </summary>
|
|||
public string UnionId { get; set; } |
|||
/// <summary>
|
|||
/// 用户唯一标识
|
|||
/// </summary>
|
|||
public string OpenId { get; set; } |
|||
/// <summary>
|
|||
/// 会话密钥
|
|||
/// </summary>
|
|||
public string SessionKey { get; set; } |
|||
|
|||
public WeChatOpenId() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public WeChatOpenId(string openId, string sessionKey, string unionId = null) |
|||
{ |
|||
OpenId = openId; |
|||
SessionKey = sessionKey; |
|||
UnionId = unionId; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
public class WeChatOpenIdCacheItem |
|||
{ |
|||
public string Code { get; set; } |
|||
|
|||
public WeChatOpenId WeChatOpenId { get; set; } |
|||
public WeChatOpenIdCacheItem() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public WeChatOpenIdCacheItem(string code, WeChatOpenId weChatOpenId) |
|||
{ |
|||
Code = code; |
|||
WeChatOpenId = weChatOpenId; |
|||
} |
|||
|
|||
public static string CalculateCacheKey(string code, Guid? tenantId = null) |
|||
{ |
|||
string tenant = tenantId != null ? tenantId.Value.ToString("D") : "host"; |
|||
|
|||
return "t:" + tenant + ",c:" + code; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
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; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
[Dependency(ServiceLifetime.Singleton, ReplaceServices = true)] |
|||
[ExposeServices(typeof(IWeChatOpenIdFinder))] |
|||
public class WeChatOpenIdFinder : IWeChatOpenIdFinder |
|||
{ |
|||
public ILogger<WeChatOpenIdFinder> Logger { get; set; } |
|||
protected AbpWeChatOptions Options { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IHttpClientFactory HttpClientFactory { get; } |
|||
protected IJsonSerializer JsonSerializer { get; } |
|||
protected IDistributedCache<WeChatOpenIdCacheItem> Cache { get; } |
|||
public WeChatOpenIdFinder( |
|||
ICurrentTenant currentTenant, |
|||
IJsonSerializer jsonSerializer, |
|||
IHttpClientFactory httpClientFactory, |
|||
IOptions<AbpWeChatOptions> options, |
|||
IDistributedCache<WeChatOpenIdCacheItem> cache) |
|||
{ |
|||
CurrentTenant = currentTenant; |
|||
JsonSerializer = jsonSerializer; |
|||
HttpClientFactory = httpClientFactory; |
|||
|
|||
Cache = cache; |
|||
Options = options.Value; |
|||
|
|||
Logger = NullLogger<WeChatOpenIdFinder>.Instance; |
|||
} |
|||
public virtual async Task<WeChatOpenId> FindAsync(string code) |
|||
{ |
|||
return (await GetCacheItemAsync(code, CurrentTenant.Id)).WeChatOpenId; |
|||
} |
|||
|
|||
protected virtual async Task<WeChatOpenIdCacheItem> GetCacheItemAsync(string code, Guid? tenantId = null) |
|||
{ |
|||
var cacheKey = WeChatOpenIdCacheItem.CalculateCacheKey(code, tenantId); |
|||
|
|||
Logger.LogDebug($"WeChatOpenIdFinder.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("WeChatRequestClient"); |
|||
|
|||
var request = new WeChatOpenIdRequest |
|||
{ |
|||
BaseUrl = client.BaseAddress.AbsoluteUri, |
|||
AppId = Options.AppId, |
|||
Secret = Options.AppSecret, |
|||
Code = code |
|||
}; |
|||
|
|||
var response = await client.RequestWeChatOpenIdAsync(request); |
|||
var responseContent = await response.Content.ReadAsStringAsync(); |
|||
var weChatOpenIdResponse = JsonSerializer.Deserialize<WeChatOpenIdResponse>(responseContent); |
|||
var weChatOpenId = weChatOpenIdResponse.ToWeChatOpenId(); |
|||
cacheItem = new WeChatOpenIdCacheItem(code, weChatOpenId); |
|||
|
|||
Logger.LogDebug($"Setting the cache item: {cacheKey}"); |
|||
|
|||
var cacheOptions = new DistributedCacheEntryOptions |
|||
{ |
|||
// 微信官方文档表示 session_key的有效期是3天
|
|||
// https://developers.weixin.qq.com/community/develop/doc/000c2424654c40bd9c960e71e5b009
|
|||
AbsoluteExpiration = DateTimeOffset.Now.AddDays(3) |
|||
// SlidingExpiration = TimeSpan.FromDays(3),
|
|||
}; |
|||
|
|||
|
|||
await Cache.SetAsync(cacheKey, cacheItem, cacheOptions); |
|||
|
|||
Logger.LogDebug($"Finished setting the cache item: {cacheKey}"); |
|||
|
|||
return cacheItem; |
|||
} |
|||
} |
|||
} |
|||
@ -1,4 +1,4 @@ |
|||
namespace System.Net.Http |
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
public class WeChatOpenIdRequest |
|||
{ |
|||
@ -0,0 +1,63 @@ |
|||
using Newtonsoft.Json; |
|||
using System; |
|||
using Volo.Abp; |
|||
|
|||
namespace LINGYUN.Abp.WeChat.Authorization |
|||
{ |
|||
/// <summary>
|
|||
/// 微信OpenId返回对象
|
|||
/// </summary>
|
|||
public class WeChatOpenIdResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 错误码
|
|||
/// </summary>
|
|||
[JsonProperty("errcode")] |
|||
public string ErrorCode { get; set; } |
|||
/// <summary>
|
|||
/// 会话密钥
|
|||
/// </summary>
|
|||
[JsonProperty("session_key")] |
|||
public string SessionKey { get; set; } |
|||
/// <summary>
|
|||
/// 用户唯一标识
|
|||
/// </summary>
|
|||
[JsonProperty("openid")] |
|||
public string OpenId { get; set; } |
|||
/// <summary>
|
|||
/// 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回
|
|||
/// </summary>
|
|||
[JsonProperty("unionid")] |
|||
public string UnionId { get; set; } |
|||
/// <summary>
|
|||
/// 错误消息
|
|||
/// </summary>
|
|||
public string ErrorMessage |
|||
{ |
|||
get |
|||
{ |
|||
switch (ErrorCode) |
|||
{ |
|||
case "-1": return "系统繁忙,此时请开发者稍候再试"; |
|||
case "0": return string.Empty; |
|||
case "40029": return "code 无效"; |
|||
case "45011": return "频率限制,每个用户每分钟100次"; |
|||
default: return "未知的异常,请重试"; |
|||
}; |
|||
} |
|||
} |
|||
/// <summary>
|
|||
/// 微信认证成功没有errorcode或者errorcode为0
|
|||
/// </summary>
|
|||
public bool IsError => !ErrorCode.IsNullOrWhiteSpace() && !"0".Equals(ErrorCode); |
|||
|
|||
public WeChatOpenId ToWeChatOpenId() |
|||
{ |
|||
if(IsError) |
|||
{ |
|||
throw new AbpException(ErrorMessage); |
|||
} |
|||
return new WeChatOpenId(OpenId, SessionKey, UnionId); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue