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