#region Apache License Version 2.0 /*---------------------------------------------------------------- Copyright 2019 Jeffrey Su & Suzhou Senparc Network Technology Co.,Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Detail: https://github.com/JeffreySu/WeiXinMPSDK/blob/master/license.md ----------------------------------------------------------------*/ #endregion Apache License Version 2.0 /*---------------------------------------------------------------- Copyright (C) 2019 Senparc 文件名:OAuthContainer.cs 文件功能描述:用户OAuth容器,用于自动管理OAuth的AccessToken,如果过期会重新获取 创建标识:Senparc - 20160801 修改标识:Senparc - 20160803 修改描述:v14.2.3 使用ApiUtility.GetExpireTime()方法处理过期 修改标识:Senparc - 20160804 修改描述:v14.2.4 增加TryGetOAuthAccessTokenAsync,GetOAuthAccessTokenAsync,GetOAuthAccessTokenResultAsync的异步方法 修改标识:Senparc - 20160808 修改描述:v14.3.0 删除 ItemCollection 属性,直接使用ContainerBag加入到缓存 修改标识:Senparc - 20160813 修改描述:v14.3.4 添加TryReRegister()方法,处理分布式缓存重启(丢失)的情况 修改标识:Senparc - 20160813 修改描述:v14.3.6 完善getNewToken参数传递 修改标识:Senparc - 20180614 修改描述:CO2NET v0.1.0 ContainerBag 取消属性变动通知机制,使用手动更新缓存 修改标识:Senparc - 20180707 修改描述:v15.0.9 Container 的 Register() 的微信参数自动添加到 Config.SenparcWeixinSetting.Items 下 修改标识:Senparc - 20181226 修改描述:v16.6.2 修改 DateTime 为 DateTimeOffset ----------------------------------------------------------------*/ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Senparc.Weixin.Cache; using Senparc.Weixin.Containers; using Senparc.Weixin.Exceptions; using Senparc.Weixin.MP.Entities; using Senparc.CO2NET.CacheUtility; using Senparc.Weixin.MP.AdvancedAPIs; using Senparc.Weixin.MP.AdvancedAPIs.OAuth; using Senparc.Weixin.MP.CommonAPIs; using Senparc.Weixin.Utilities.WeixinUtility; using Senparc.CO2NET.Extensions; namespace Senparc.Weixin.MP.Containers { /// /// OAuth包 /// [Serializable] public class OAuthAccessTokenBag : BaseContainerBag, IBaseContainerBag_AppId { public string AppId { get; set; } // { // get { return _appId; } //#if NET35 || NET40 // set { this.SetContainerProperty(ref _appId, value, "AppId"); } //#else // set { this.SetContainerProperty(ref _appId, value); } //#endif // } public string AppSecret { get; set; } // { // get { return _appSecret; } //#if NET35 || NET40 // set { this.SetContainerProperty(ref _appSecret, value, "AppSecret"); } //#else // set { this.SetContainerProperty(ref _appSecret, value); } //#endif // } public OAuthAccessTokenResult OAuthAccessTokenResult { get; set; } // { // get { return _oAuthAccessTokenResult; } //#if NET35 || NET40 // set { this.SetContainerProperty(ref _oAuthAccessTokenResult, value, "OAuthAccessTokenResult"); } //#else // set { this.SetContainerProperty(ref _oAuthAccessTokenResult, value); } //#endif // } public DateTimeOffset OAuthAccessTokenExpireTime { get; set; } // { // get { return _oAuthAccessTokenExpireTime; } //#if NET35 || NET40 // set { this.SetContainerProperty(ref _oAuthAccessTokenExpireTime, value, "OAuthAccessTokenExpireTime"); } //#else // set { this.SetContainerProperty(ref _oAuthAccessTokenExpireTime, value); } //#endif // } /// /// 只针对这个AppId的锁 /// internal object Lock = new object(); //private DateTimeOffset _oAuthAccessTokenExpireTime; //private OAuthAccessTokenResult _oAuthAccessTokenResult; //private string _appSecret; //private string _appId; } /// /// 用户OAuth容器,用于自动管理OAuth的AccessToken,如果过期会重新获取(测试中,暂时别用) /// public class OAuthAccessTokenContainer : BaseContainer { const string LockResourceName = "MP.OAuthAccessTokenContainer"; #region 同步方法 //static Dictionary JsApiTicketCollection = // new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// 注册应用凭证信息,此操作只是注册,不会马上获取Ticket,并将清空之前的Ticket, /// /// /// /// 标记JsApiTicket名称(如微信公众号名称),帮助管理员识别。当 name 不为 null 和 空值时,本次注册内容将会被记录到 Senparc.Weixin.Config.SenparcWeixinSetting.Items[name] 中,方便取用。 /// 此接口不提供异步方法 public static void Register(string appId, string appSecret, string name = null) { RegisterFunc = () => { //using (FlushCache.CreateInstance()) //{ var bag = new OAuthAccessTokenBag() { Name = name, AppId = appId, AppSecret = appSecret, OAuthAccessTokenExpireTime = DateTimeOffset.MinValue, OAuthAccessTokenResult = new OAuthAccessTokenResult() }; Update(appId, bag, null); return bag; //} }; RegisterFunc(); if (!name.IsNullOrEmpty()) { Senparc.Weixin.Config.SenparcWeixinSetting.Items[name].WeixinAppId = appId; Senparc.Weixin.Config.SenparcWeixinSetting.Items[name].WeixinAppSecret = appSecret; } } #region OAuthAccessToken /// /// 使用完整的应用凭证获取Ticket,如果不存在将自动注册 /// /// /// /// code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。 /// /// public static string TryGetOAuthAccessToken(string appId, string appSecret, string code, bool getNewToken = false) { if (!CheckRegistered(appId) || getNewToken) { Register(appId, appSecret); } return GetOAuthAccessToken(appId, code, getNewToken); } /// /// 获取可用Ticket /// /// /// code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。 /// 是否强制重新获取新的Ticket /// public static string GetOAuthAccessToken(string appId, string code, bool getNewToken = false) { return GetOAuthAccessTokenResult(appId, code, getNewToken).access_token; } /// /// 获取可用Ticket /// /// /// code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。 /// 是否强制重新获取新的Ticket /// public static OAuthAccessTokenResult GetOAuthAccessTokenResult(string appId, string code, bool getNewToken = false) { if (!CheckRegistered(appId)) { throw new UnRegisterAppIdException(null, "此appId尚未注册,请先使用OAuthAccessTokenContainer.Register完成注册(全局执行一次即可)!"); } var oAuthAccessTokenBag = TryGetItem(appId); using (Cache.BeginCacheLock(LockResourceName, appId))//同步锁 { if (getNewToken || oAuthAccessTokenBag.OAuthAccessTokenExpireTime <= SystemTime.Now) { //已过期,重新获取 oAuthAccessTokenBag.OAuthAccessTokenResult = OAuthApi.GetAccessToken(oAuthAccessTokenBag.AppId, oAuthAccessTokenBag.AppSecret, code); oAuthAccessTokenBag.OAuthAccessTokenExpireTime = ApiUtility.GetExpireTime(oAuthAccessTokenBag.OAuthAccessTokenResult.expires_in); Update(oAuthAccessTokenBag, null); } } return oAuthAccessTokenBag.OAuthAccessTokenResult; } #endregion #endregion #if !NET35 && !NET40 #region 异步方法 #region OAuthAccessToken /// /// 【异步方法】使用完整的应用凭证获取Ticket,如果不存在将自动注册 /// /// /// /// code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。 /// /// public static async Task TryGetOAuthAccessTokenAsync(string appId, string appSecret, string code, bool getNewToken = false) { if (!CheckRegistered(appId) || getNewToken) { Register(appId, appSecret); } return await GetOAuthAccessTokenAsync(appId, code, getNewToken); } /// /// 【异步方法】获取可用Ticket /// /// /// code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。 /// 是否强制重新获取新的Ticket /// public static async Task GetOAuthAccessTokenAsync(string appId, string code, bool getNewToken = false) { var result = await GetOAuthAccessTokenResultAsync(appId, code, getNewToken); return result.access_token; } /// /// 【异步方法】获取可用Ticket /// /// /// code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。 /// 是否强制重新获取新的Ticket /// public static async Task GetOAuthAccessTokenResultAsync(string appId, string code, bool getNewToken = false) { if (!CheckRegistered(appId)) { throw new UnRegisterAppIdException(null, "此appId尚未注册,请先使用OAuthAccessTokenContainer.Register完成注册(全局执行一次即可)!"); } var oAuthAccessTokenBag = TryGetItem(appId); using (Cache.BeginCacheLock(LockResourceName, appId))//同步锁 { if (getNewToken || oAuthAccessTokenBag.OAuthAccessTokenExpireTime <= SystemTime.Now) { //已过期,重新获取 var oAuthAccessTokenResult = await OAuthApi.GetAccessTokenAsync(oAuthAccessTokenBag.AppId, oAuthAccessTokenBag.AppSecret, code); oAuthAccessTokenBag.OAuthAccessTokenResult = oAuthAccessTokenResult; //oAuthAccessTokenBag.OAuthAccessTokenResult = OAuthApi.GetAccessToken(oAuthAccessTokenBag.AppId, oAuthAccessTokenBag.AppSecret, code); oAuthAccessTokenBag.OAuthAccessTokenExpireTime = ApiUtility.GetExpireTime(oAuthAccessTokenBag.OAuthAccessTokenResult.expires_in); Update(oAuthAccessTokenBag, null); } } return oAuthAccessTokenBag.OAuthAccessTokenResult; } #endregion #endregion #endif } }