說明:
1/因為賺麻煩這裡沒有使用資料庫或伺服器快取來儲存access_token和jsapi_ticket,為了方便這裡使用了本地的xml進行持久化這兩個值以及這兩個值的建立時間和有限期限。
2/每次請求先檢查有沒有存在並且在有效期內的access_token和jsapi_ticket,存在的話直接進行加密操作,不存在或過期重新請求wechat介面獲得再進行加密。
3/每個分享的頁面都需要將當頁的url傳送到伺服器進行簽名,且一定要encodeURIComponent,因為在微信中開啟會自動給當前連結加個各種引數,從而導致url不一致,導致invalid signature簽名錯誤。
4/分享的圖示url( imgUrl )必須是絕對路徑。
一 封裝的微信授權工具類
WechatJsSdk.cs
using SouthRuiHeH5.Models; using System; using System.IO; using System.Linq; using System.Net; using System.Security.Cryptography; using System.Text; using System.Web.Script.Serialization; using System.Xml.Linq; namespace SouthRuiHeH5.Provider { public class WechatJsSdk { /// <summary> /// 模擬get請求獲取AccessToken /// </summary> /// <param name="appID"></param> /// <param name="appSecret"></param> /// <returns></returns> public static string GetAccessToken(string appID, string appSecret) { string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appID, appSecret); JavaScriptSerializer js = new JavaScriptSerializer(); AccessTokenOutput output = js.Deserialize<AccessTokenOutput>(HttpGet(url)); SaveAccessTokenInXml(output); return output.access_token; } /// <summary> /// 將AccessToken儲存進xml /// </summary> /// <param name="input"></param> private static void SaveAccessTokenInXml(AccessTokenOutput input) { if (string.IsNullOrWhiteSpace(input?.access_token)) return; var mappedPath = System.Web.Hosting.HostingEnvironment.MapPath("~/"); string filePath = mappedPath + "Xml/AccessToken.xml"; XDocument inputDoc = XDocument.Load(filePath); inputDoc.Elements().First().Element("access_token").Value = input.access_token; inputDoc.Elements().First().Element("expires_in").Value = input.expires_in; inputDoc.Elements().First().Element("create_time").Value = DateTime.Now.ToString(); inputDoc.Save(filePath); } /// <summary> /// 模擬get請求獲取JsapiTicket /// </summary> /// <param name="accessToken"></param> /// <returns></returns> public static string GetJsapiTicket(string accessToken) { string url = string.Format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=jsapi", accessToken); JavaScriptSerializer js = new JavaScriptSerializer(); JsapiTicketOutput output = js.Deserialize<JsapiTicketOutput>(HttpGet(url)); SaveJsapiTicketInXml(output); return output.ticket; } /// <summary> /// 將JsapiTicket儲存進xml /// </summary> /// <param name="input"></param> private static void SaveJsapiTicketInXml(JsapiTicketOutput input) { if (string.IsNullOrWhiteSpace(input?.ticket)) return; var mappedPath = System.Web.Hosting.HostingEnvironment.MapPath("~/"); string filePath = mappedPath + "Xml/JsapiTicket.xml"; XDocument inputDoc = XDocument.Load(filePath); inputDoc.Elements().First().Element("ticket").Value = input.ticket; inputDoc.Elements().First().Element("expires_in").Value = input.expires_in; inputDoc.Elements().First().Element("create_time").Value = DateTime.Now.ToString(); inputDoc.Save(filePath); } /// <summary> /// get模擬請求 /// </summary> /// <param name="Url"></param> /// <param name="postDataStr"></param> /// <returns></returns> private static string HttpGet(string Url, string postDataStr = "") { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url + (postDataStr == "" ? "" : "?") + postDataStr); request.Method = "GET"; request.ContentType = "text/html;charset=UTF-8"; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream myResponseStream = response.GetResponseStream(); StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8")); string retString = myStreamReader.ReadToEnd(); myStreamReader.Close(); myResponseStream.Close(); return retString; } private static string[] strs = new string[] { "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", "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" }; /// <summary> /// 獲取隨機字串 /// </summary> /// <returns></returns> public static string CreatenNonce_str() { Random r = new Random(); var sb = new StringBuilder(); var length = strs.Length; for (int i = 0; i < 15; i++) { sb.Append(strs[r.Next(length - 1)]); } return sb.ToString(); } /// <summary> /// 獲取時間戳 /// </summary> /// <returns></returns> public static long CreatenTimestamp() { return (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000; } /// <summary> /// sha1加密string1 獲得Signature /// </summary> /// <param name="jsapi_ticket"></param> /// <param name="noncestr"></param> /// <param name="timestamp"></param> /// <param name="url"></param> /// <returns></returns> public static string GetSignature(string jsapi_ticket, string noncestr, long timestamp, string url) { string string1 = string.Format("jsapi_ticket={0}&noncestr={1}×tamp={2}&url={3}", jsapi_ticket, noncestr, timestamp, url); return Sha1(string1); } /// <summary> /// sha1 /// </summary> /// <param name="orgStr"></param> /// <param name="encode"></param> /// <returns></returns> private static string Sha1(string orgStr, string encode = "UTF-8") { var sha1 = new SHA1Managed(); var sha1bytes = System.Text.Encoding.GetEncoding(encode).GetBytes(orgStr); byte[] resultHash = sha1.ComputeHash(sha1bytes); string sha1String = BitConverter.ToString(resultHash).ToLower(); sha1String = sha1String.Replace("-", ""); return sha1String; } } }
二 webapi部分
ConfigController.cs
using SouthRuiHeH5.Models; using SouthRuiHeH5.Provider; using System; using System.Configuration; using System.Linq; using System.Web.Http; using System.Xml.Linq; namespace SouthRuiHeH5.Controllers { public class ConfigController : ApiController { public ConfigOutput Get([FromUri]string url) { string appId = ConfigurationManager.AppSettings.Get("AppId"); string appSecret = ConfigurationManager.AppSettings.Get("AppSecret"); if (string.IsNullOrWhiteSpace(appId)) throw new Exception("AppSeeting:AppId Missed"); if (string.IsNullOrWhiteSpace(appSecret)) throw new Exception("AppSeeting:AppSecret Missed"); string jsapiTicket = getJsapiTicketFromXml(); if (string.IsNullOrEmpty(jsapiTicket)) { string accessToken = GetAccessTokenFromXmlFirst(); if (string.IsNullOrEmpty(accessToken)) accessToken = WechatJsSdk.GetAccessToken(appId, appSecret); if (string.IsNullOrEmpty(accessToken)) throw new Exception("Get AccessToken Error"); jsapiTicket = WechatJsSdk.GetJsapiTicket(accessToken); if (string.IsNullOrEmpty(jsapiTicket)) throw new Exception("Get JsapiTicket Error"); } ConfigOutput output = new ConfigOutput { appId = appId, nonceStr = WechatJsSdk.CreatenNonce_str(), timestamp = WechatJsSdk.CreatenTimestamp() }; output.signature = WechatJsSdk.GetSignature(jsapiTicket, output.nonceStr, output.timestamp, url); return output; } /// <summary> /// 檢查xml是否有JsapiTicket,並且JsapiTicket在有效期內 /// </summary> /// <returns></returns> private string getJsapiTicketFromXml() { var mappedPath = System.Web.Hosting.HostingEnvironment.MapPath("~/"); string filePath = mappedPath + "Xml/JsapiTicket.xml"; XDocument inputDoc = XDocument.Load(filePath); string ticket = inputDoc.Elements().First().Element("ticket").Value; bool expiresInTry = int.TryParse(inputDoc.Elements().First().Element("expires_in").Value, out int expiresIn); bool createTimeTry = DateTime.TryParse(inputDoc.Elements().First().Element("create_time").Value, out DateTime createTime); if (!string.IsNullOrWhiteSpace(ticket) || !expiresInTry || !createTimeTry) { TimeSpan timeSpan = DateTime.Now.Subtract(createTime); if (timeSpan.TotalSeconds < expiresIn) return ticket; } return string.Empty; } /// <summary> /// 檢查xml是否有AccessToken,並且AccessToken在有效期內 /// </summary> /// <returns></returns> private string GetAccessTokenFromXmlFirst() { var mappedPath = System.Web.Hosting.HostingEnvironment.MapPath("~/"); string filePath = mappedPath + "Xml/AccessToken.xml"; XDocument inputDoc = XDocument.Load(filePath); string accessToken = inputDoc.Elements().First().Element("access_token").Value; bool expiresInTry = int.TryParse(inputDoc.Elements().First().Element("expires_in").Value, out int expiresIn); bool createTimeTry = DateTime.TryParse(inputDoc.Elements().First().Element("create_time").Value, out DateTime createTime); if (!string.IsNullOrWhiteSpace(accessToken) || !expiresInTry || !createTimeTry) { TimeSpan timeSpan = DateTime.Now.Subtract(createTime); if (timeSpan.TotalSeconds < expiresIn) return accessToken; } return string.Empty; } } }
三 JS部分
wechat.share.js
var url = window.location.href.split('#')[0]; $.get("/api/Config?url=" + encodeURIComponent(url), function (res) { if (!res) return; var input = res; //input.debug = true; input.jsApiList = ["onMenuShareTimeline", "onMenuShareAppMessage"]; wx.config(input); }); wx.ready(function () { onMenuShareTimeline(); onMenuShareAppMessage(); }); function onMenuShareTimeline() { wx.onMenuShareTimeline({ title: '為態度喝彩!', desc: '唯有創造價值,才能共享價值。南方瑞合三年定開基金(LOF)盛大發行中。', link: url, imgUrl: 'http://southruihe.huiz.cn/image/sharelogo.jpg', success: function () { } }); } function onMenuShareAppMessage() { wx.onMenuShareAppMessage({ title: '為態度喝彩!', desc: '唯有創造價值,才能共享價值。南方瑞合三年定開基金(LOF)盛大發行中。', link: url, imgUrl: 'http://southruihe.huiz.cn/image/sharelogo.jpg', success: function () { } }); }
四 其中使用的3個資料傳輸類
AccessTokenOutput.cs
namespace SouthRuiHeH5.Models { public class AccessTokenOutput { public string access_token { get; set; } public string expires_in { get; set; } public string errcode { get; set; } public string errmsg { get; set; } } }
ConfigOutput.cs
namespace SouthRuiHeH5.Models { public class ConfigOutput { /// <summary> /// 必填,公眾號的唯一標識 /// </summary> public string appId { get; set; } /// <summary> /// 必填,生成簽名的時間戳 /// </summary> public long timestamp { get; set; } /// <summary> /// 必填,生成簽名的隨機串 /// </summary> public string nonceStr { get; set; } /// <summary> /// 必填,簽名 /// </summary> public string signature { get; set; } } }
JsapiTicketOutput.cs
namespace SouthRuiHeH5.Models { public class JsapiTicketOutput { public string errcode { get; set; } public string errmsg { get; set; } public string ticket { get; set; } public string expires_in { get; set; } } }