一、首先了解本文要解决的问题:
公司前一段开发了移动网站,老板喜欢通过微信看,然后把看到的东西通过右上角的按钮分享出来,但老板发现分享出来的东西,没有指定的图片,没有描述;所以我就得老老实实干活了。。。
如下图所示:
点击发送给朋友,或者分享到朋友圈时,需要带上老板指定的图片、描述和标题。
二、尝试解决问题
刚开始,通过网上查阅,发现微信会自动抓取页面的title属性作为标题,和页面中第一张尺寸大于等于300*300的图片,作为分享出来的图片,于是带着偷懒的想法先这样尝试了一下,会有一下几个问题:
①不能自定义描述信息
②当页面内没有超过300*300的图片时,就不能显示图片了,于是我放了一张300*300的公司的logo在每个页面内,首先老板并不喜欢,其次会影响加载速度,同时当用户在隐藏图片还没加载出来的时候就进行分享动作,那么图片还是识别不出来。。。
改完第一次,用了一段时间,老板不满意,要求继续优化,于是第二版开始更新。。
既然不能走野路子,只能通过微信给出的开发文档进行更改了。。请参考官网文档:http://mp.weixin.qq.com/wiki/7/1c97470084b73f8e224fe6d9bab1625b.html
微信发布了《微信JS-SDK说明文档》供开发者使用,并且给出了示例,包含了php、java、nodejs、python示例,然而并没有什么卵用!因为我是一个.net开发!!!
只能自己动手,丰衣足食了。
三、说干就干
代码分两个部分,js和后端;后端我们目前使用的是webservice,其中用到了Cache,下面贴主要代码了
①接口中代码,需要解决js跨域问题
1 [WebMethod(Description = "微信分享请求参数接口")] 2 [ScriptMethod(UseHttpGet = false)] 3 public string GetWXSharedParam(string url) 4 { 5 string timestamp = Common.APPApplyClass.WxSharedClass.ConvertDateTimeInt(DateTime.Now).ToString(); 6 string nonceStr = Guid.NewGuid().ToString(); 7 string ticket = string.Empty; 8 string appId = ConfigurationManager.AppSettings["appid"]; 9 10 //获取jsapi_ticket 11 if (HttpRuntime.Cache["JsApiTicket"] == null) 12 { 13 Common.APPApplyClass.WxSharedClass.GetJsApiTicket(); 14 } 15 ticket = HttpRuntime.Cache["JsApiTicket"] as string; 16 if (string.IsNullOrEmpty(ticket)) 17 { 18 return JsonConvert.SerializeObject(new { result = false }); 19 } 20 21 SortedList<string, string> SLString = new SortedList<string, string>(); 22 SLString.Add("noncestr", nonceStr); 23 SLString.Add("url", url); 24 SLString.Add("timestamp", timestamp); 25 SLString.Add("jsapi_ticket", ticket); 26 27 StringBuilder sb = new StringBuilder(); 28 foreach (KeyValuePair<string, string> des in SLString) //返回的是KeyValuePair,在学习的时候尽量少用var,起码要知道返回的是什么 29 { 30 sb.Append(des.Key + "=" + des.Value + "&"); 31 } 32 string signature = sb.ToString().Substring(0, sb.ToString().Length - 1); 33 signature = FormsAuthentication.HashPasswordForStoringInConfigFile(signature, "SHA1").ToLower(); 34 35 return JsonConvert.SerializeObject(new { result = true, timestamp, nonceStr, signature, appId }); 36 }
②新建了一个类,提供了上个方法中需要调用的方法和实体
1 public static class WxSharedClass 2 { 3 static System.Web.Caching.Cache objCache = HttpRuntime.Cache; 4 5 /// <summary> 6 /// 获取jsapi_ticket 7 /// 有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket 8 /// </summary> 9 /// <returns></returns> 10 public static void GetJsApiTicket() 11 { 12 string accessToken = string.Empty; 13 if (objCache["AccessToken"] == null) 14 { 15 GetAccessToken(); 16 } 17 accessToken = objCache["AccessToken"] as string; 18 if (!string.IsNullOrEmpty(accessToken)) 19 { 20 accessToken = objCache["AccessToken"] as string; 21 string url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accessToken + "&type=jsapi"; 22 string resStr = HttpGet(url); 23 TicketModel model = JsonConvert.DeserializeObject<TicketModel>(resStr); 24 if (!string.IsNullOrEmpty(model.ticket)) 25 { 26 //请求成功了 27 DateTime dt = DateTime.Now.AddSeconds(Convert.ToInt32(model.expires_in)); 28 objCache.Insert("JsApiTicket", model.ticket, null, dt, System.Web.Caching.Cache.NoSlidingExpiration); 29 } 30 } 31 } 32 33 /// <summary> 34 /// DateTime时间格式转换为Unix时间戳格式 35 /// </summary> 36 /// <param name="time"> DateTime时间格式</param> 37 /// <returns>Unix时间戳格式</returns> 38 public static int ConvertDateTimeInt(System.DateTime time) 39 { 40 System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)); 41 return (int)(time - startTime).TotalSeconds; 42 } 43 44 /// <summary> 45 /// 获取access_token 46 /// 有效期7200秒,开发者必须在自己的服务全局缓存access_token 47 /// </summary> 48 /// <returns></returns> 49 private static void GetAccessToken() 50 { 51 string appId = ConfigurationManager.AppSettings["appid"];//订阅号应用id 52 string secret = ConfigurationManager.AppSettings["secret"];//订阅号应用密钥 53 string url = 54 "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + secret; 55 string resStr = HttpGet(url); 56 if (!resStr.Contains("errcode"))//string.IsNullOrEmpty(model.errcode) 57 { 58 //请求成功了 59 AccessTokenModel model = JsonConvert.DeserializeObject<AccessTokenModel>(resStr); 60 DateTime dt = DateTime.Now.AddSeconds(Convert.ToInt32(model.expires_in)); 61 objCache.Insert("AccessToken", model.access_token, null, dt, System.Web.Caching.Cache.NoSlidingExpiration); 62 } 63 } 64 65 66 /// <summary> 67 /// HttpGet请求 68 /// </summary> 69 /// <param name="url"></param> 70 /// <returns></returns> 71 private static string HttpGet(string url) 72 { 73 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); 74 request.Method = "GET"; 75 request.ContentType = "text/html;charset=UTF-8"; 76 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 77 Stream myResponseStream = response.GetResponseStream(); 78 StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8")); 79 string retString = myStreamReader.ReadToEnd(); 80 myStreamReader.Close(); 81 myResponseStream.Close(); 82 return retString; 83 } 84 } 85 86 public class AccessTokenModel 87 { 88 public string access_token; 89 90 public string expires_in; 91 } 92 93 public class TicketModel 94 { 95 public string errcode; 96 97 public string errmsg; 98 99 public string ticket; 100 101 public string expires_in; 102 }
③js代码
1 //引用jquery 2 <script type="text/javascript" src="js/jquery.js"></script> 3 <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> 4 <script type="text/javascript"> 5 var title = '测试标题', 6 imgUrl = '图片地址', 7 desc = '测试描述'; 8 9 $(function () { 10 /* 11 wx.checkJsApi({ 12 jsApiList: [ 13 'onMenuShareTimeline', 14 'onMenuShareAppMessage' 15 ], // 需要检测的JS接口列表,所有JS接口列表见附录2, 16 success: function (res) { 17 allPrpos(res); 18 // 以键值对的形式返回,可用的api值true,不可用为false 19 // 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"} 20 } 21 }); 22 */ 23 24 var param = { 25 url: location.href.split('#')[0] 26 }; 27 //从后端请求参数 28 AjaxPostData("url", param, function (json) { 29 if (json.result === true) { 30 31 wx.config({ 32 debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 33 appId: json.appId, // 必填,公众号的唯一标识 34 timestamp: json.timestamp, // 必填,生成签名的时间戳 35 nonceStr: json.nonceStr, // 必填,生成签名的随机串 36 signature: json.signature,// 必填,签名,见附录1 37 menuItem: 'addContact', 38 jsApiList: [ 39 'onMenuShareTimeline', 40 'onMenuShareAppMessage' 41 ] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 42 }); 43 } 44 }); 45 }); 46 47 wx.ready(function () { 48 // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。 49 50 51 //获取“分享到朋友圈”按钮点击状态及自定义分享内容接口 52 wx.onMenuShareTimeline({ 53 title: title, // 分享标题 54 link: window.location.href, // 分享链接 55 imgUrl: imgUrl, // 分享图标 56 trigger: function (res) { 57 //监听Menu中的按钮点击时触发的方法,该方法仅支持Menu中的相关接口。 58 //不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回。 59 }, 60 success: function (res) { 61 //接口调用成功时执行的回调函数。 62 }, 63 cancel: function (res) { 64 //用户点击取消时的回调函数,仅部分有用户取消操作的api才会用到。 65 }, 66 fail: function (res) { 67 //接口调用失败时执行的回调函数。 68 }, 69 complete: function (res) { 70 //接口调用完成时执行的回调函数,无论成功或失败都会执行。 71 } 72 //以上几个函数都带有一个参数,类型为对象,其中除了每个接口本身返回的数据之外,还有一个通用属性errMsg,其值格式如下: 73 /*调用成功时:"xxx:ok" ,其中xxx为调用的接口名 74 用户取消时:"xxx:cancel",其中xxx为调用的接口名 75 调用失败时:其值为具体错误信息 76 */ 77 }); 78 79 80 //获取“分享给朋友”按钮点击状态及自定义分享内容接口 81 wx.onMenuShareAppMessage({ 82 title: title, // 分享标题 83 desc: desc, // 分享描述 84 link: window.location.href, // 分享链接 85 imgUrl: imgUrl, // 分享图标 86 type: 'link', // 分享类型,music、video或link,不填默认为link 87 dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空 88 success: function () { 89 // 用户确认分享后执行的回调函数 90 }, 91 cancel: function () { 92 // 用户取消分享后执行的回调函数 93 } 94 }); 95 }); 96 97 wx.error(function (res) { 98 //allPrpos(res); 99 // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。 100 101 }); 102 103 //这是我抄的我们前端开发小组的,不知道他们抄的谁的。。。 104 var AjaxPostData = function(url, data, success) { 105 /// <summary> 106 /// Ajax的POST方法 107 /// </summary> 108 /// <param name="url" type="String"> 109 /// 网址/参数 110 /// </param> 111 /// <param name="data" type="JSON类型"> 112 /// 参数 113 /// </param> 114 /// <param name="success" type="Function"> 115 /// 回调函数,不用这个的时候为同步,用的时候为异步(有一个返回数据参数(数据)) 116 /// </param> 117 /// <returns type="string">同步时返回数据,异步时在回调函数中取</returns> 118 119 var val = ""; 120 121 var param = { 122 url: encodeURI(url), 123 type: "POST", 124 timeout: 50000, 125 async: false, 126 data: data, 127 dataType: "text", 128 success: function(tempData) {}, 129 error: function(XMLHttpRequest, textStatus, errorThrown) { 130 console.log(arguments); 131 } 132 }; 133 134 if (!success) { 135 param.async = false; 136 param.success = function(tempData) { 137 val = tempData; 138 }; 139 } else { 140 param.async = true; 141 param.success = function(r) { 142 if (success) { 143 // var result = $(r).find('string').html(); 144 // result = result.StringToJsonObject(); 145 146 var len = r.length; 147 var result = JSON.parse(r.substring(76, len - 9)); 148 149 success(result); 150 } 151 }; 152 } 153 154 $.ajax(param); 155 156 return val; 157 } 158 </script>