JsApiTicketContainer.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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. 文件名:JsApiTicketContainer.cs
  17. 文件功能描述:通用接口JsApiTicket容器,用于自动管理JsApiTicket,如果过期会重新获取
  18. 创建标识:Senparc - 20160206
  19. 修改标识:Senparc - 20160206
  20. 修改描述:将public object Lock更改为internal object Lock
  21. 修改标识:Senparc - 20160318
  22. 修改描述:13.6.10 使用FlushCache.CreateInstance使注册过程立即生效
  23. 修改标识:Senparc - 20160717
  24. 修改描述:v13.8.11 添加注册过程中的Name参数
  25. 修改标识:Senparc - 20160801
  26. 修改描述:v14.2.1 转移到Senparc.Weixin.MP.Containers命名空间下
  27. 修改标识:Senparc - 20160803
  28. 修改描述:v14.2.3 使用ApiUtility.GetExpireTime()方法处理过期
  29. 修改标识:Senparc - 20160804
  30. 修改描述:v14.2.4 增加TryGetJsApiTicketAsync,GetJsApiTicketAsync,GetJsApiTicketResultAsync的异步方法
  31. 修改标识:Senparc - 20160808
  32. 修改描述:v14.3.0 删除 ItemCollection 属性,直接使用ContainerBag加入到缓存
  33. 修改标识:Senparc - 20160813
  34. 修改描述:v14.3.4 添加TryReRegister()方法,处理分布式缓存重启(丢失)的情况
  35. 修改标识:Senparc - 20160813
  36. 修改描述:v14.3.6 完善getNewToken参数传递
  37. 修改标识:Senparc - 20180614
  38. 修改描述:CO2NET v0.1.0 ContainerBag 取消属性变动通知机制,使用手动更新缓存
  39. 修改标识:Senparc - 20180707
  40. 修改描述:v15.0.9 Container 的 Register() 的微信参数自动添加到 Config.SenparcWeixinSetting.Items 下
  41. 修改标识:Senparc - 20181226
  42. 修改描述:v16.6.2 修改 DateTime 为 DateTimeOffset
  43. ----------------------------------------------------------------*/
  44. using System;
  45. using System.Collections.Generic;
  46. using System.Linq;
  47. using System.Threading.Tasks;
  48. using Senparc.Weixin.Cache;
  49. using Senparc.Weixin.Containers;
  50. using Senparc.Weixin.Exceptions;
  51. using Senparc.Weixin.MP.Entities;
  52. using Senparc.CO2NET.CacheUtility;
  53. using Senparc.Weixin.MP.CommonAPIs;
  54. using Senparc.Weixin.Utilities.WeixinUtility;
  55. using Senparc.CO2NET.Extensions;
  56. namespace Senparc.Weixin.MP.Containers
  57. {
  58. /// <summary>
  59. /// JsApiTicket包
  60. /// </summary>
  61. [Serializable]
  62. public class JsApiTicketBag : BaseContainerBag, IBaseContainerBag_AppId
  63. {
  64. public string AppId { get; set; }
  65. // {
  66. // get { return _appId; }
  67. //#if NET35 || NET40
  68. // set { this.SetContainerProperty(ref _appId, value, "AppId"); }
  69. //#else
  70. // set { this.SetContainerProperty(ref _appId, value); }
  71. //#endif
  72. // }
  73. public string AppSecret { get; set; }
  74. // {
  75. // get { return _appSecret; }
  76. //#if NET35 || NET40
  77. // set { this.SetContainerProperty(ref _appSecret, value, "AppSecret"); }
  78. //#else
  79. // set { this.SetContainerProperty(ref _appSecret, value); }
  80. //#endif
  81. // }
  82. public JsApiTicketResult JsApiTicketResult { get; set; }
  83. // {
  84. // get { return _jsApiTicketResult; }
  85. //#if NET35 || NET40
  86. // set { this.SetContainerProperty(ref _jsApiTicketResult, value, "JsApiTicketResult"); }
  87. //#else
  88. // set { this.SetContainerProperty(ref _jsApiTicketResult, value); }
  89. //#endif
  90. // }
  91. public DateTimeOffset JsApiTicketExpireTime { get; set; }
  92. // {
  93. // get { return _jsApiTicketExpireTime; }
  94. //#if NET35 || NET40
  95. // set { this.SetContainerProperty(ref _jsApiTicketExpireTime, value, "JsApiTicketExpireTime"); }
  96. //#else
  97. // set { this.SetContainerProperty(ref _jsApiTicketExpireTime, value); }
  98. //#endif
  99. // }
  100. /// <summary>
  101. /// 只针对这个AppId的锁
  102. /// </summary>
  103. internal object Lock = new object();
  104. //private DateTimeOffset _jsApiTicketExpireTime;
  105. //private JsApiTicketResult _jsApiTicketResult;
  106. //private string _appSecret;
  107. //private string _appId;
  108. }
  109. /// <summary>
  110. /// 通用接口JsApiTicket容器,用于自动管理JsApiTicket,如果过期会重新获取
  111. /// </summary>
  112. public class JsApiTicketContainer : BaseContainer<JsApiTicketBag>
  113. {
  114. const string LockResourceName = "MP.JsApiTicketContainer";
  115. #region 同步方法
  116. //static Dictionary<string, JsApiTicketBag> JsApiTicketCollection =
  117. // new Dictionary<string, JsApiTicketBag>(StringComparer.OrdinalIgnoreCase);
  118. /// <summary>
  119. /// 注册应用凭证信息,此操作只是注册,不会马上获取Ticket,并将清空之前的Ticket,
  120. /// </summary>
  121. /// <param name="appId"></param>
  122. /// <param name="appSecret"></param>
  123. /// <param name="name">标记JsApiTicket名称(如微信公众号名称),帮助管理员识别。当 name 不为 null 和 空值时,本次注册内容将会被记录到 Senparc.Weixin.Config.SenparcWeixinSetting.Items[name] 中,方便取用。</param>
  124. /*此接口不提供异步方法*/
  125. public static void Register(string appId, string appSecret, string name = null)
  126. {
  127. //记录注册信息,RegisterFunc委托内的过程会在缓存丢失之后自动重试
  128. RegisterFunc = () =>
  129. {
  130. //using (FlushCache.CreateInstance())
  131. //{
  132. var bag = new JsApiTicketBag()
  133. {
  134. Name = name,
  135. AppId = appId,
  136. AppSecret = appSecret,
  137. JsApiTicketExpireTime = DateTimeOffset.MinValue,
  138. JsApiTicketResult = new JsApiTicketResult()
  139. };
  140. Update(appId, bag, null);
  141. return bag;
  142. //}
  143. };
  144. RegisterFunc();
  145. if (!name.IsNullOrEmpty())
  146. {
  147. Senparc.Weixin.Config.SenparcWeixinSetting.Items[name].WeixinAppId = appId;
  148. Senparc.Weixin.Config.SenparcWeixinSetting.Items[name].WeixinAppSecret = appSecret;
  149. }
  150. }
  151. #region JsApiTicket
  152. /// <summary>
  153. /// 使用完整的应用凭证获取Ticket,如果不存在将自动注册
  154. /// </summary>
  155. /// <param name="appId"></param>
  156. /// <param name="appSecret"></param>
  157. /// <param name="getNewTicket"></param>
  158. /// <returns></returns>
  159. public static string TryGetJsApiTicket(string appId, string appSecret, bool getNewTicket = false)
  160. {
  161. if (!CheckRegistered(appId) || getNewTicket)
  162. {
  163. Register(appId, appSecret);
  164. }
  165. return GetJsApiTicket(appId);
  166. }
  167. /// <summary>
  168. /// 获取可用Ticket
  169. /// </summary>
  170. /// <param name="appId"></param>
  171. /// <param name="getNewTicket">是否强制重新获取新的Ticket</param>
  172. /// <returns></returns>
  173. public static string GetJsApiTicket(string appId, bool getNewTicket = false)
  174. {
  175. return GetJsApiTicketResult(appId, getNewTicket).ticket;
  176. }
  177. /// <summary>
  178. /// 获取可用Ticket
  179. /// </summary>
  180. /// <param name="appId"></param>
  181. /// <param name="getNewTicket">是否强制重新获取新的Ticket</param>
  182. /// <returns></returns>
  183. public static JsApiTicketResult GetJsApiTicketResult(string appId, bool getNewTicket = false)
  184. {
  185. if (!CheckRegistered(appId))
  186. {
  187. throw new UnRegisterAppIdException(null, "此appId尚未注册,请先使用JsApiTicketContainer.Register完成注册(全局执行一次即可)!");
  188. }
  189. var jsApiTicketBag = TryGetItem(appId);
  190. using (Cache.BeginCacheLock(LockResourceName, appId))//同步锁
  191. {
  192. if (getNewTicket || jsApiTicketBag.JsApiTicketExpireTime <= SystemTime.Now)
  193. {
  194. //已过期,重新获取
  195. jsApiTicketBag.JsApiTicketResult = CommonApi.GetTicket(jsApiTicketBag.AppId, jsApiTicketBag.AppSecret);
  196. jsApiTicketBag.JsApiTicketExpireTime = ApiUtility.GetExpireTime(jsApiTicketBag.JsApiTicketResult.expires_in);
  197. Update(jsApiTicketBag, null);
  198. }
  199. }
  200. return jsApiTicketBag.JsApiTicketResult;
  201. }
  202. #endregion
  203. #endregion
  204. #if !NET35 && !NET40
  205. #region 异步方法
  206. #region JsApiTicket
  207. /// <summary>
  208. /// 【异步方法】使用完整的应用凭证获取Ticket,如果不存在将自动注册
  209. /// </summary>
  210. /// <param name="appId"></param>
  211. /// <param name="appSecret"></param>
  212. /// <param name="getNewTicket"></param>
  213. /// <returns></returns>
  214. public static async Task<string> TryGetJsApiTicketAsync(string appId, string appSecret, bool getNewTicket = false)
  215. {
  216. if (!CheckRegistered(appId) || getNewTicket)
  217. {
  218. Register(appId, appSecret);
  219. }
  220. return await GetJsApiTicketAsync(appId, getNewTicket);
  221. }
  222. /// <summary>
  223. ///【异步方法】 获取可用Ticket
  224. /// </summary>
  225. /// <param name="appId"></param>
  226. /// <param name="getNewTicket">是否强制重新获取新的Ticket</param>
  227. /// <returns></returns>
  228. public static async Task<string> GetJsApiTicketAsync(string appId, bool getNewTicket = false)
  229. {
  230. var result = await GetJsApiTicketResultAsync(appId, getNewTicket);
  231. return result.ticket;
  232. }
  233. /// <summary>
  234. /// 【异步方法】获取可用Ticket
  235. /// </summary>
  236. /// <param name="appId"></param>
  237. /// <param name="getNewTicket">是否强制重新获取新的Ticket</param>
  238. /// <returns></returns>
  239. public static async Task<JsApiTicketResult> GetJsApiTicketResultAsync(string appId, bool getNewTicket = false)
  240. {
  241. if (!CheckRegistered(appId))
  242. {
  243. throw new UnRegisterAppIdException(null, "此appId尚未注册,请先使用JsApiTicketContainer.Register完成注册(全局执行一次即可)!");
  244. }
  245. var jsApiTicketBag = TryGetItem(appId);
  246. using (Cache.BeginCacheLock(LockResourceName, appId))//同步锁
  247. {
  248. if (getNewTicket || jsApiTicketBag.JsApiTicketExpireTime <= SystemTime.Now)
  249. {
  250. //已过期,重新获取
  251. var jsApiTicketResult = await CommonApi.GetTicketAsync(jsApiTicketBag.AppId, jsApiTicketBag.AppSecret);
  252. //jsApiTicketBag.JsApiTicketResult = CommonApi.GetTicket(jsApiTicketBag.AppId, jsApiTicketBag.AppSecret);
  253. jsApiTicketBag.JsApiTicketResult = jsApiTicketResult;
  254. jsApiTicketBag.JsApiTicketExpireTime = SystemTime.Now.AddSeconds(jsApiTicketBag.JsApiTicketResult.expires_in);
  255. Update(jsApiTicketBag, null);
  256. }
  257. }
  258. return jsApiTicketBag.JsApiTicketResult;
  259. }
  260. #endregion
  261. #endregion
  262. #endif
  263. }
  264. }