diff --git a/src/Rabbit.WeiXin/Handlers/HandlerMiddleware.cs b/src/Rabbit.WeiXin/Handlers/HandlerMiddleware.cs index ba56a5b..ee3b6ba 100644 --- a/src/Rabbit.WeiXin/Handlers/HandlerMiddleware.cs +++ b/src/Rabbit.WeiXin/Handlers/HandlerMiddleware.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Rabbit.WeiXin.Handlers { @@ -13,14 +14,14 @@ public abstract class HandlerMiddleware /// 下一个处理中间件。 protected HandlerMiddleware(HandlerMiddleware next) { - Next = next; + Next = next; } /// /// 下一个处理中间件。 /// protected HandlerMiddleware Next { get; private set; } - + /// /// 调用。 /// diff --git a/src/Rabbit.WeiXin/MP/Api/AccountModel.cs b/src/Rabbit.WeiXin/MP/Api/AccountModel.cs index c96ea81..f4bb53b 100644 --- a/src/Rabbit.WeiXin/MP/Api/AccountModel.cs +++ b/src/Rabbit.WeiXin/MP/Api/AccountModel.cs @@ -21,5 +21,10 @@ public class AccountModel /// 获取调用接口凭证。 /// public Func GetAccessToken { get; set; } + + /// + /// 获取Js调用接口凭证。 + /// + public Func GetJsApiTicket { get; set; } } } \ No newline at end of file diff --git a/src/Rabbit.WeiXin/MP/Api/CommonService.cs b/src/Rabbit.WeiXin/MP/Api/CommonService.cs index 839e028..1cbf17b 100644 --- a/src/Rabbit.WeiXin/MP/Api/CommonService.cs +++ b/src/Rabbit.WeiXin/MP/Api/CommonService.cs @@ -19,6 +19,13 @@ public interface ICommonService /// 第三方用户唯一凭证密钥,即appsecret。 AccessTokenModel GetAccessToken(bool ignoreCached = false); + /// + /// 获取JsApi的全局唯一票据。 + /// + /// 是否忽略缓存。 + /// 第三方用户唯一凭证密钥。 + JsApiTicketModel GetJsApiTicket(bool ignoreCached = false); + /// /// 将一个url地址转换成一个短地址。 /// @@ -44,6 +51,9 @@ public sealed class CommonService : ICommonService private static readonly ConcurrentDictionary Dictionary = new ConcurrentDictionary(); private readonly Lazy _accessTokenLazy; + private static readonly ConcurrentDictionary JsDictionary = new ConcurrentDictionary(); + private readonly Lazy _jsApiTicketLazy; + #endregion Field #region Constructor @@ -56,6 +66,7 @@ public CommonService(AccountModel accountModel) { _accountModel = accountModel; _accessTokenLazy = new Lazy(InternalGetAccessToken); + _jsApiTicketLazy = new Lazy(InternalGetJsApiTicket); } #endregion Constructor @@ -79,6 +90,23 @@ public AccessTokenModel GetAccessToken(bool ignoreCached = false) }); } + /// + /// 获取JsApi的全局唯一票据。 + /// + /// 是否忽略缓存。 + /// 第三方用户唯一凭证密钥。 + public JsApiTicketModel GetJsApiTicket(bool ignoreCached = false) + { + var appId = _accountModel.AppId; + return JsDictionary.AddOrUpdate(appId, key => _jsApiTicketLazy.Value, (k, model) => + { + //无效、过期、忽略缓存则重新获取。 + if (model == null || model.IsExpired() || ignoreCached) + return InternalGetJsApiTicket(); + return model; + }); + } + /// /// 将一个url地址转换成一个短地址。 /// @@ -127,6 +155,13 @@ private AccessTokenModel InternalGetAccessToken() return WeiXinHttpHelper.GetResultByJson(url); } + private JsApiTicketModel InternalGetJsApiTicket() + { + var accessToken = _accountModel.GetAccessToken(); + var url = string.Format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=jsapi", accessToken); + return WeiXinHttpHelper.GetResultByJson(url); + } + #endregion Private Method } @@ -177,5 +212,50 @@ public bool IsExpired() } } + /// + /// 获取JsApi全局唯一票据结果模型 + /// + public sealed class JsApiTicketModel + { + #region Constructor + + /// + /// 初始化一个新的JsApi的全局唯一票据。 + /// + public JsApiTicketModel() + { + CreateTime = DateTime.Now; + } + + #endregion Constructor + + /// + /// 全局唯一票据 + /// + [JsonProperty("ticket")] + public string Ticket { get; set; } + + /// + /// 凭证有效时间(秒)。 + /// + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + /// + /// 创建时间。 + /// + [JsonIgnore] + public DateTime CreateTime { get; private set; } + + /// + /// 是否过期。 + /// + /// 如果过期返回true,否则返回false。 + public bool IsExpired() + { + return CreateTime.AddSeconds(ExpiresIn - 20/*不采用最后的期限作为判断,防止在很少的时间内到期导致后续的逻辑无法执行*/) <= DateTime.Now; + } + } + #endregion Help Class } \ No newline at end of file diff --git a/src/Rabbit.WeiXin/MP/Api/JsSdk/JsConfigService.cs b/src/Rabbit.WeiXin/MP/Api/JsSdk/JsConfigService.cs new file mode 100644 index 0000000..636da82 --- /dev/null +++ b/src/Rabbit.WeiXin/MP/Api/JsSdk/JsConfigService.cs @@ -0,0 +1,84 @@ +using Newtonsoft.Json; +using Rabbit.WeiXin.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Rabbit.WeiXin.Utility.Extensions; + +namespace Rabbit.WeiXin.MP.Api.Js +{ + + /// + /// Js配置生成生成服务 + /// + public interface IJsConfigService + { + /// + /// 生成微信JsSDK配置信息 + /// + /// + /// + JsSdkConfigModel Create(string currentUrl); + } + + + /// + /// Js配置生成生成服务 + /// + public class JsConfigService : IJsConfigService + { + private readonly AccountModel _accountModel; + + /// + /// ctor + /// + /// + public JsConfigService(AccountModel accountModel) + { + _accountModel = accountModel; + } + + public JsSdkConfigModel Create(string currentUrl) + { + JsSdkConfigModel result = new JsSdkConfigModel(); + result.Timestamp = DateTimeHelper.GetTimeStampByTime(DateTime.Now).ToString(); + result.AppId = _accountModel.AppId; + result.NonceStr = StringHelper.GetRandomString(32); + + + return result; + } + } + + + /// + /// + /// + public class JsSdkConfigModel + { + /// + /// AppId + /// + [JsonProperty("appId")] + public string AppId { get; set; } + + /// + /// 时间戳 + /// + [JsonProperty("timestamp")] + public string Timestamp { get; set; } + + /// + /// 随机字符串 + /// + [JsonProperty("nonceStr")] + public string NonceStr { get; set; } + + /// + /// Signature + /// + [JsonProperty("signature")] + public string Signature { get; set; } + } +} diff --git a/src/Rabbit.WeiXin/Utility/Extensions/SerializeExtensions.cs b/src/Rabbit.WeiXin/Utility/Extensions/SerializeExtensions.cs new file mode 100644 index 0000000..c74843f --- /dev/null +++ b/src/Rabbit.WeiXin/Utility/Extensions/SerializeExtensions.cs @@ -0,0 +1,27 @@ +using Rabbit.WeiXin.MP.Messages; +using Rabbit.WeiXin.MP.Messages.Response; +using Rabbit.WeiXin.MP.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Rabbit.WeiXin.Utility.Extensions +{ + /// + /// 序列化对象 + /// + public static class SerializeExtensions + { + /// + /// 序列化Response消息 + /// + /// Response消息 + /// + public static string Serialize(this IResponseMessage responseMessage) + { + var facotry = new ResponseMessageFactory(new MessageFormatterFactory()); + return facotry.GetXmlByReponseMessage(responseMessage); + } + } +} diff --git a/src/Rabbit.WeiXin/Utility/StringHelper.cs b/src/Rabbit.WeiXin/Utility/StringHelper.cs new file mode 100644 index 0000000..11aa23c --- /dev/null +++ b/src/Rabbit.WeiXin/Utility/StringHelper.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Rabbit.WeiXin.Utility +{ + + /// + /// 字符串帮助类 + /// + public class StringHelper + { + static Random random = new Random(); + static string[] arr = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }; + + + /// + /// 获得随机长度字符串 + /// + /// 字符串长度 + /// + public static string GetRandomString(int length) + { + var result = new StringBuilder(); + for (var i = 0; i < length; i++) + { + result.Append(arr[random.Next(37)]); + } + return result.ToString(); + } + } +} diff --git a/test/Rabbit.WeiXin.Tests/ApiTestBase.cs b/test/Rabbit.WeiXin.Tests/ApiTestBase.cs index 48a7a13..b0045a6 100644 --- a/test/Rabbit.WeiXin.Tests/ApiTestBase.cs +++ b/test/Rabbit.WeiXin.Tests/ApiTestBase.cs @@ -29,7 +29,8 @@ public ApiTestBase() { AppId = AppId, AppSecret = AppSecret, - GetAccessToken = GetAccessToken + GetAccessToken = GetAccessToken, + GetJsApiTicket = GetJsApiTicket, }; CommonService = new CommonService(AccountModel); } @@ -43,6 +44,11 @@ protected string GetAccessToken() return CommonService.GetAccessToken().AccessToken; } + protected string GetJsApiTicket() + { + return CommonService.GetJsApiTicket().Ticket; + } + #endregion Protected Method } } \ No newline at end of file diff --git a/test/Rabbit.WeiXin.Tests/CommonServiceTest.cs b/test/Rabbit.WeiXin.Tests/CommonServiceTest.cs index a44556f..9be3ba8 100644 --- a/test/Rabbit.WeiXin.Tests/CommonServiceTest.cs +++ b/test/Rabbit.WeiXin.Tests/CommonServiceTest.cs @@ -31,6 +31,23 @@ public void GetAccessTokenTest() } [Fact] + public void GetJsApiTicketTest() + { + Func get = + ignoreCached => CommonService.GetJsApiTicket(ignoreCached); + + var model = get(false); + + Assert.NotNull(model.Ticket); + + Assert.False(model.IsExpired()); + + var model2 = get(false); + Assert.Equal(model.Ticket, model2.Ticket); + Assert.False(model2.IsExpired()); + } + + //[Fact] public void GenerateShotAddressTest() { const string url = "http://cn.bing.com/search?q=windows10&go=%E6%8F%90%E4%BA%A4&qs=n&form=QBLH&pq=windows10&sc=8-9&sp=-1&sk=&ghc=1&cvid=6d333afcfbec4834bbf3ce592b699f66";