说明:
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.Seconds < 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.Seconds < 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; } } }
H5+.Net Webapi集成微信分享前后端代码 微信JS-SDK wx.onMenuShareTimeline wx.onMenuShareAppMessage