AccessTokenContainer.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. #region Apache License Version 2.0
  2. /*----------------------------------------------------------------
  3. Copyright 2019 Jeffrey Su & Suzhou Senparc Network Technology Co.,Ltd.
  4. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
  5. except in compliance with the License. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software distributed under the
  8. License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  9. either express or implied. See the License for the specific language governing permissions
  10. and limitations under the License.
  11. Detail: https://github.com/JeffreySu/WeiXinMPSDK/blob/master/license.md
  12. ----------------------------------------------------------------*/
  13. #endregion Apache License Version 2.0
  14. /*----------------------------------------------------------------
  15. Copyright (C) 2019 Senparc
  16. 文件名:AccessTokenContainer.cs
  17. 文件功能描述:通用接口AccessToken容器,用于自动管理AccessToken,如果过期会重新获取
  18. 创建标识:Senparc - 20150211
  19. 修改标识:Senparc - 20150303
  20. 修改描述:整理接口
  21. 修改标识:Senparc - 20150702
  22. 修改描述:添加GetFirstOrDefaultAppId()方法
  23. 修改标识:Senparc - 20151004
  24. 修改描述:v13.3.0 将JsApiTicketContainer整合到AccessTokenContainer
  25. 修改标识:Senparc - 20160318
  26. 修改描述:v13.6.10 使用FlushCache.CreateInstance使注册过程立即生效
  27. 修改标识:Senparc - 20160717
  28. 修改描述:v13.8.11 添加注册过程中的Name参数
  29. 修改标识:Senparc - 20160721
  30. 修改描述:增加其接口的异步方法
  31. 修改标识:Senparc - 20160801
  32. 修改描述:v14.2.1 转移到Senparc.Weixin.MP.Containers命名空间下
  33. 修改标识:Senparc - 20160803
  34. 修改描述:v14.2.3 使用ApiUtility.GetExpireTime()方法处理过期
  35. 修改标识:Senparc - 20160808
  36. 修改描述:v14.3.0 删除 ItemCollection 属性,直接使用ContainerBag加入到缓存
  37. 修改标识:Senparc - 20160810
  38. 修改描述:v14.3.3 修复错误
  39. 修改标识:Senparc - 20160813
  40. 修改描述:v14.3.4 添加TryReRegister()方法,处理分布式缓存重启(丢失)的情况
  41. 修改标识:Senparc - 20160813
  42. 修改描述:v14.3.6 完善getNewToken参数传递
  43. 修改标识:Senparc - 20170702
  44. 修改描述:v14.5.0 为了配合新版本ApiHandlerWapper方法,GetAccessTokenResultAsync方法的返回值从Task<AccessTokenResult>改为Task<IAccessTokenResult>
  45. 修改标识:Senparc - 20170702
  46. 修改描述:v14.5.5 修改Container中的锁及异步调用方法
  47. 修改标识:Senparc - 20170702
  48. 修改描述:v14.6.2 回滚 v14.5.5中修改的方法(同步方法中调用异步方法)
  49. 修改标识:Senparc - 20180614
  50. 修改描述:CO2NET v0.1.0 ContainerBag 取消属性变动通知机制,使用手动更新缓存
  51. 修改标识:Senparc - 20180707
  52. 修改描述:v15.0.9 Container 的 Register() 的微信参数自动添加到 Config.SenparcWeixinSetting.Items 下
  53. 修改标识:Senparc - 20170522
  54. 修改描述:v16.6.2 修改 DateTime 为 DateTimeOffset
  55. ----------------------------------------------------------------*/
  56. using System;
  57. using System.Collections.Generic;
  58. using System.Linq;
  59. using System.Runtime.CompilerServices;
  60. using System.Threading.Tasks;
  61. using Senparc.Weixin.Cache;
  62. using Senparc.Weixin.Containers;
  63. using Senparc.Weixin.Exceptions;
  64. using Senparc.Weixin.MP.Entities;
  65. using Senparc.CO2NET.CacheUtility;
  66. using Senparc.Weixin.Entities;
  67. using Senparc.Weixin.MP.CommonAPIs;
  68. using Senparc.Weixin.Utilities.WeixinUtility;
  69. using Senparc.CO2NET.Extensions;
  70. namespace Senparc.Weixin.MP.Containers
  71. {
  72. /// <summary>
  73. /// AccessToken包
  74. /// </summary>
  75. [Serializable]
  76. public class AccessTokenBag : BaseContainerBag, IBaseContainerBag_AppId
  77. {
  78. public string AppId { get; set; }
  79. // {
  80. // get { return _appId; }
  81. //#if NET35 || NET40
  82. // set { this.SetContainerProperty(ref _appId, value, "AppId"); }
  83. //#else
  84. // set { this.SetContainerProperty(ref _appId, value); }
  85. //#endif
  86. // }
  87. public string AppSecret { get; set; }
  88. // {
  89. // get { return _appSecret; }
  90. //#if NET35 || NET40
  91. // set { this.SetContainerProperty(ref _appSecret, value, "AppSecret"); }
  92. //#else
  93. // set { this.SetContainerProperty(ref _appSecret, value); }
  94. //#endif
  95. // }
  96. public DateTimeOffset AccessTokenExpireTime { get; set; }
  97. // {
  98. // get { return _accessTokenExpireTime; }
  99. //#if NET35 || NET40
  100. // set { this.SetContainerProperty(ref _accessTokenExpireTime, value, "AccessTokenExpireTime"); }
  101. //#else
  102. // set { this.SetContainerProperty(ref _accessTokenExpireTime, value); }
  103. //#endif
  104. // }
  105. public AccessTokenResult AccessTokenResult { get; set; }
  106. // {
  107. // get { return _accessTokenResult; }
  108. //#if NET35 || NET40
  109. // set { this.SetContainerProperty(ref _accessTokenResult, value, "AccessTokenResult"); }
  110. //#else
  111. // set { this.SetContainerProperty(ref _accessTokenResult, value); }
  112. //#endif
  113. // }
  114. //private AccessTokenResult _accessTokenResult;
  115. //private DateTimeOffset _accessTokenExpireTime;
  116. //private string _appSecret;
  117. //private string _appId;
  118. }
  119. /// <summary>
  120. /// 通用接口AccessToken容器,用于自动管理AccessToken,如果过期会重新获取
  121. /// </summary>
  122. public class AccessTokenContainer : BaseContainer<AccessTokenBag>
  123. {
  124. const string LockResourceName = "MP.AccessTokenContainer";
  125. /// <summary>
  126. /// 注册应用凭证信息,此操作只是注册,不会马上获取Token,并将清空之前的Token
  127. /// </summary>
  128. /// <param name="appId">微信公众号后台的【开发】>【基本配置】中的“AppID(应用ID)”</param>
  129. /// <param name="appSecret">微信公众号后台的【开发】>【基本配置】中的“AppSecret(应用密钥)”</param>
  130. /// <param name="name">标记AccessToken名称(如微信公众号名称),帮助管理员识别。当 name 不为 null 和 空值时,本次注册内容将会被记录到 Senparc.Weixin.Config.SenparcWeixinSetting.Items[name] 中,方便取用。</param>
  131. public static void Register(string appId, string appSecret, string name = null)
  132. {
  133. //记录注册信息,RegisterFunc委托内的过程会在缓存丢失之后自动重试
  134. RegisterFunc = () =>
  135. {
  136. //using (FlushCache.CreateInstance())
  137. //{
  138. var bag = new AccessTokenBag()
  139. {
  140. //Key = appId,
  141. Name = name,
  142. AppId = appId,
  143. AppSecret = appSecret,
  144. AccessTokenExpireTime = DateTimeOffset.MinValue,
  145. AccessTokenResult = new AccessTokenResult()
  146. };
  147. Update(appId, bag, null);//第一次添加,此处已经立即更新
  148. return bag;
  149. //}
  150. };
  151. RegisterFunc();
  152. if (!name.IsNullOrEmpty())
  153. {
  154. Senparc.Weixin.Config.SenparcWeixinSetting.Items[name].WeixinAppId = appId;
  155. Senparc.Weixin.Config.SenparcWeixinSetting.Items[name].WeixinAppSecret = appSecret;
  156. }
  157. //为JsApiTicketContainer进行自动注册
  158. JsApiTicketContainer.Register(appId, appSecret, name);
  159. //OAuthAccessTokenContainer进行自动注册
  160. OAuthAccessTokenContainer.Register(appId, appSecret, name);
  161. }
  162. #region 同步方法
  163. #region AccessToken
  164. /// <summary>
  165. /// 使用完整的应用凭证获取Token,如果不存在将自动注册
  166. /// </summary>
  167. /// <param name="appId"></param>
  168. /// <param name="appSecret"></param>
  169. /// <param name="getNewToken"></param>
  170. /// <returns></returns>
  171. public static string TryGetAccessToken(string appId, string appSecret, bool getNewToken = false)
  172. {
  173. if (!CheckRegistered(appId) || getNewToken)
  174. {
  175. Register(appId, appSecret);
  176. }
  177. return GetAccessToken(appId, getNewToken);
  178. }
  179. /// <summary>
  180. /// 获取可用Token
  181. /// </summary>
  182. /// <param name="appId"></param>
  183. /// <param name="getNewToken">是否强制重新获取新的Token</param>
  184. /// <returns></returns>
  185. public static string GetAccessToken(string appId, bool getNewToken = false)
  186. {
  187. return GetAccessTokenResult(appId, getNewToken).access_token;
  188. }
  189. /// <summary>
  190. /// 获取可用AccessTokenResult对象
  191. /// </summary>
  192. /// <param name="appId"></param>
  193. /// <param name="getNewToken">是否强制重新获取新的Token</param>
  194. /// <returns></returns>
  195. public static AccessTokenResult GetAccessTokenResult(string appId, bool getNewToken = false)
  196. {
  197. if (!CheckRegistered(appId))
  198. {
  199. throw new UnRegisterAppIdException(appId, string.Format("此appId({0})尚未注册,请先使用AccessTokenContainer.Register完成注册(全局执行一次即可)!", appId));
  200. }
  201. var accessTokenBag = TryGetItem(appId);
  202. using (Cache.BeginCacheLock(LockResourceName, appId))//同步锁
  203. {
  204. if (getNewToken || accessTokenBag.AccessTokenExpireTime <= SystemTime.Now)
  205. {
  206. //已过期,重新获取
  207. accessTokenBag.AccessTokenResult = CommonApi.GetToken(accessTokenBag.AppId, accessTokenBag.AppSecret);
  208. accessTokenBag.AccessTokenExpireTime = ApiUtility.GetExpireTime(accessTokenBag.AccessTokenResult.expires_in);
  209. Update(accessTokenBag, null);//更新到缓存
  210. }
  211. }
  212. return accessTokenBag.AccessTokenResult;
  213. }
  214. #endregion
  215. #endregion
  216. #if !NET35 && !NET40
  217. #region 异步方法
  218. #region AccessToken
  219. /// <summary>
  220. /// 【异步方法】使用完整的应用凭证获取Token,如果不存在将自动注册
  221. /// </summary>
  222. /// <param name="appId"></param>
  223. /// <param name="appSecret"></param>
  224. /// <param name="getNewToken"></param>
  225. /// <returns></returns>
  226. public static async Task<string> TryGetAccessTokenAsync(string appId, string appSecret, bool getNewToken = false)
  227. {
  228. if (!CheckRegistered(appId) || getNewToken)
  229. {
  230. Register(appId, appSecret);
  231. }
  232. return await GetAccessTokenAsync(appId, getNewToken);
  233. }
  234. /// <summary>
  235. /// 【异步方法】获取可用Token
  236. /// </summary>
  237. /// <param name="appId"></param>
  238. /// <param name="getNewToken">是否强制重新获取新的Token</param>
  239. /// <returns></returns>
  240. public static async Task<string> GetAccessTokenAsync(string appId, bool getNewToken = false)
  241. {
  242. var result = await GetAccessTokenResultAsync(appId, getNewToken);
  243. return result.access_token;
  244. }
  245. /// <summary>
  246. /// 获取可用AccessTokenResult对象
  247. /// </summary>
  248. /// <param name="appId"></param>
  249. /// <param name="getNewToken">是否强制重新获取新的Token</param>
  250. /// <returns></returns>
  251. public static async Task<IAccessTokenResult> GetAccessTokenResultAsync(string appId, bool getNewToken = false)
  252. {
  253. if (!CheckRegistered(appId))
  254. {
  255. throw new UnRegisterAppIdException(appId, string.Format("此appId({0})尚未注册,请先使用AccessTokenContainer.Register完成注册(全局执行一次即可)!", appId));
  256. }
  257. var accessTokenBag = TryGetItem(appId);
  258. using (Cache.BeginCacheLock(LockResourceName, appId))//同步锁
  259. {
  260. if (getNewToken || accessTokenBag.AccessTokenExpireTime <= SystemTime.Now)
  261. {
  262. //已过期,重新获取
  263. var accessTokenResult = await CommonApi.GetTokenAsync(accessTokenBag.AppId, accessTokenBag.AppSecret);
  264. accessTokenBag.AccessTokenResult = accessTokenResult;
  265. accessTokenBag.AccessTokenExpireTime = ApiUtility.GetExpireTime(accessTokenBag.AccessTokenResult.expires_in);
  266. Update(accessTokenBag, null);//更新到缓存
  267. }
  268. }
  269. return accessTokenBag.AccessTokenResult;
  270. }
  271. #endregion
  272. #endregion
  273. #endif
  274. }
  275. }