微信支付之微信模板消息推送
今天我要跟大家分享的是“模板消息”的推送,这玩意呢,你说用途嘛,那还是真真的牛逼呐。原因在哪?就是因为它是依赖微信生存的呀,所以他能不牛逼吗?现在的社会,人多多少少都有或轻或重的“强迫症”。就是,看到有未读消息,都要去看一下。特别是现在的微信,大部分可以几个小时不看手机QQ有没有新消息来,但是这大部分人绝对做不到一个小时不看微信有没有消息来。现在的微信,真特么是神一样的存在,几乎人人手机上都会有微信。而且,如果你的公众号是服务号的话,这个推送消息会显示在首页列表中(如果有新的消息,会把公众号显示在最顶端,并提示有新消息),不像订阅好,会放在一个小盒子里面。成熟的例子也有许多,例如一些商业银行的刷卡消费提醒这些。好,这方面是他微信的优势,就是,you can‘t miss it.另外一个优势就是:只要你给他推送了,他的到达率是非常的高,几乎可以保证,100%送达(鉴于天朝的相关法规,这种100%的东西,是没人敢说的,所以,一般都和谐的说99.97%的样子)。所以,以这样的送达率,秒杀传统的短信通知。更有甚者,就是,他的推送,完全免费,完全免费,完全免费!而且,速度极快,速度极快,速度极快。。。好了,再说下去,估计等下你们以为我是腾讯的员工了。好了,这些我就不多说了,下面,我们进入主题吧。
首先,新建一个MVC的项目,如下图(在第二个页面里面,身份认证,改为不需要身份验证):
然后我们去把官方demo(demo地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1)中的business和lib拷贝到我们的项目中,并新建一个文件夹WxLib,放到里面去,如下图:
然后我们再“重新生成”以下项目,或者快捷键:ctrl+shift+b,这时候,会提下如下错误:
这时候,我们去添加引用,把WxLib/lib文件夹中的LitJson.dll 添加上即可,如下图:
到这里,我们就基本把官方的demo的环境给搭建好了,接下来,我们就要开始编写代码了。
首先,我们来看看官方文档,怎么介绍这个模板消息的,查看步骤(这个是需要登录公众平台才能看到的),如下图:
通过API文档,我们得知,我们推送模板消息,有三个必须要获取到的参数,他们分别是:ACCESS_TOKEN和OPENID,以及template_id,
ACCESS_TOKEN和OPENID,我们前面的额公众号支付已经演示过如何获取,如果忘了,可以回头看看(微信支付教程系列之公众号支付),
下面,我来讲讲怎么获取这个template_id。template_id是模板消息的ID,我们推送消息,不像我们发短信一样,可以随意编辑内容,想说啥就说啥,必须要符合模板消息的规则。例如,我们这次选择一个“帐户资金变动提醒”,添加操作如下:
点击”添加“之后,就会在”我的模板”里面出现,如下图:
这个“lypG1jYyOEfYsr*********jOK7-LhEwpPeVNHHxemSI”就是我们的模板ID了,也就是:template_id。
好,现在,我们知道ACCESS_TOKEN和OPENID以及template_id了,理论上,我们就有了可以发送推送消息的前提了。下面,我们来进入代码部分,如下:
首先,新建一个HomeController.cs,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace 微信支付之模板消息推送.Controllers { public class HomeController : Controller { // GET: Home public ActionResult Index() { return View(); } } }
然后添加一个View,代码如下:
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> </head> <body> <div> </div> </body> </html>
再说多几句,我们这次选的是一个“帐户资金变动提醒”,他的传参类型如下:
在此之前:由于我们这里面的文字,还可以设定颜色,我们从这里(模板里面看不到,但是可以从官方的demo)可以看出来:
但是,我们本次用的模板不是这个,是“帐户资金变动提醒”,但是道理相同,所以,我们也要新建一个类,文件名字就叫做:DataFontStyle.cs吧,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace 微信支付之模板消息推送.Models { public class DataFontStyle { public string value { get; set; } public string color { get; set; } } }
再分析他的参数,分别有:first,date,adCharge,type,cashBalance以及remark,所以,我们给他新建一个Model,到时候会用到的,新建的Model的文件名字就叫做:CashModel.cs,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace 微信支付之模板消息推送.Models { /// <summary> /// 帐户资金变动提醒Model /// </summary> public class CashModel { public DataFontStyle first { get; set; } public DataFontStyle date { get; set; } public DataFontStyle adCharge { get; set; } public DataFontStyle type { get; set; } public DataFontStyle cashBalance { get; set; } public DataFontStyle remark { get; set; } } }
此外,还有最后一个,那就是这个推送的Model,也要新建一个类,文件名,我们就叫做PushMess.cs吧,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace 微信支付之模板消息推送.Models { public class PushMessage { public string touser { get; set; } public string template_id { get; set; } public string url { get; set; } public string topcolor { get; set; } /// <summary> /// 注意,这里是CashModel,你如果要通用,有多个推送模板要用,那你就用object /// </summary> public CashModel data { get; set; } } }
漏了一个类,现在补回来,我们命名为:WxResult.cs,代码如下:public class WxResult { public int? errcode { get; set; } public string errmsg { get; set; } public int? msgid { get; set; } }
文件物理路径逻辑如下,检查一下,看看有无偏差,如下图:
说明一下:一些推送必须的代码,我就直接贴出(例如后去Access_token和推送代码),不再说明了,因为在前面几篇都有说明,如果看不懂,请翻阅前面的教程。
现在,我们来编辑一下前后端的代码,前端的代码的逻辑,还是跟往常一样,我代码尽量精简,本次,那个推送,我只传一个参数到后台,那就是first,其他的,都是在后台固定,实际项目中应用,到时候,你们再行修改即可,前台代码:
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>身份认证</title> <link href="~/Scripts/jquery-easyui-1.4.5/themes/bootstrap/easyui.css" rel="stylesheet" /> <link href="~/Scripts/jquery-easyui-1.4.5/themes/mobile.css" rel="stylesheet" /> <link href="~/Scripts/jquery-easyui-1.4.5/themes/icon.css" rel="stylesheet" /> <style type="text/css"> .line { width: 100%; float: left; height: auto; text-align: center; margin-top: 15pt; font-size: x-large; } .lineText { width: 100%; float: left; height: auto; text-indent: 5%; text-align: left; font-size: x-large; margin: 0; } a { text-decoration: none; color: white; } .input { height: 30pt; width: 90%; font-size: x-large; border-radius: 10px; margin: 0; padding: 0; } .btn { width: 90%; height: 35pt; font-size: x-large; background-color: green; color: white; border: none; border-radius: 10px; } </style> </head> <body> <div style="width: 100%; text-align: center;"> <form id="form1"> <div class="line"> <div class="lineText">*接收者openid:</div> </div> <div class="line"> <input type="text" id="openid" name="openid" class="input" /> </div> <div class="line"> <div class="lineText">*first.DATA:</div> </div> <div class="line"> <input type="text" id="first" name="first" class="input" /> </div> </form> </div> <div class="line"> <input type="button" id="btnSave" class="btn" value="推送" onclick="fPush()" /> </div> <script src="~/Scripts/jquery-easyui-1.4.5/jquery.min.js"></script> <script src="~/Scripts/jquery-easyui-1.4.5/jquery.easyui.min.js"></script> <script src="~/Scripts/jquery-easyui-1.4.5/jquery.easyui.mobile.js"></script> <script src="~/Scripts/jquery-easyui-1.4.5/easyloader.js"></script> <script type="text/javascript"> $(function () { var vCode = getQueryString("code"); if (vCode != "" && vCode != null) { $.ajax({ type: ‘post‘, data: { code: vCode }, url: ‘/Home/getWxInfo‘, success: function (sjson) { $.messager.show({ title: ‘提示‘, msg: ‘欢迎您的到来(看到这个提示,代表已经成功获取openid和access_token了)。‘ }); $("#openid").val(sjson.openid); } }) } else { $.ajax({ type: ‘post‘, url: ‘/Home/getCode‘, success: function (sjson) { location.href = sjson; } }) } }) //获取url的参数 function getQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); var r = window.location.search.substr(1).match(reg); if (r != null) return unescape(r[2]); return null; } //推送 function fPush() { var vTtile = $("#first").val(); $.ajax({ type: ‘post‘, data: { first: vTtile }, url: ‘/Home/Push‘, success: function (sjson) { if (sjson.result) { $.messager.alert("提示", sjson.msg, ‘info‘); } else { $.messager.alert("提示", sjson.msg, ‘warning‘); } } }) } </script> </body> </html>
后台代码:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Web; using System.Web.Mvc; using System.Web.Script.Serialization; using WxPayAPI; using 微信支付之模板消息推送.Models; namespace 微信支付之模板消息推送.Controllers { public class HomeController : Controller { JsApiPay jsApiPay = new JsApiPay(); JavaScriptSerializer JsonHelper = new JavaScriptSerializer(); /// <summary> /// 最后更新Access_token的时间 /// </summary> public static DateTime dtAccess_token; /// <summary> /// Access_token的值 /// </summary> public static string strAccess_token; // GET: Home public ActionResult Index() { if (Session["openid"] == null) { try { //调用【网页授权获取用户信息】接口获取用户的openid和access_token GetOpenidAndAccessToken(); } catch (Exception ex) { //Response.Write(ex.ToString()); //throw; } } return View(); } /** * * 网页授权获取用户基本信息的全部过程 * 详情请参看网页授权获取用户基本信息:http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html * 第一步:利用url跳转获取code * 第二步:利用code去获取openid和access_token * */ public void GetOpenidAndAccessToken() { if (Session["code"] != null) { //获取code码,以获取openid和access_token string code = Session["code"].ToString(); Log.Debug(this.GetType().ToString(), "Get code : " + code); jsApiPay.GetOpenidAndAccessTokenFromCode(code); } else { //构造网页授权获取code的URL string host = Request.Url.Host; string path = Request.Path; string redirect_uri = HttpUtility.UrlEncode("http://" + host + path); WxPayData data = new WxPayData(); data.SetValue("appid", WxPayConfig.APPID); data.SetValue("redirect_uri", redirect_uri); data.SetValue("response_type", "code"); data.SetValue("scope", "snsapi_base"); data.SetValue("state", "STATE" + "#wechat_redirect"); string url = "https://open.weixin.qq.com/connect/oauth2/authorize?" + data.ToUrl(); Log.Debug(this.GetType().ToString(), "Will Redirect to URL : " + url); Session["url"] = url; } } /// <summary> /// 获取code /// </summary> /// <returns></returns> [HttpPost] public ActionResult getCode() { object objResult = ""; if (Session["url"] != null) { objResult = Session["url"].ToString(); } else { objResult = "url为空。"; } return Json(objResult); } /// <summary> /// 通过code换取网页授权access_token和openid的返回数据 /// </summary> /// <returns></returns> [HttpPost] public ActionResult getWxInfo() { object objResult = ""; string strCode = Request.Form["code"]; string strAccess_Token = ""; string strOpenid = ""; if (Session["access_token"] == null || Session["openid"] == null) { jsApiPay.GetOpenidAndAccessTokenFromCode(strCode); strAccess_Token = Session["access_token"].ToString(); strOpenid = Session["openid"].ToString(); } else { strAccess_Token = Session["access_token"].ToString(); strOpenid = Session["openid"].ToString(); } objResult = new { openid = strOpenid, access_token = strAccess_Token }; return Json(objResult); } /// <summary> /// 推送 /// </summary> /// <returns></returns> [HttpPost] public ActionResult Push() { object objResult = ""; string strFirst = Request.Form["first"]; string strMsg = ""; bool bResult = false; //这个是推送消息的类 PushMessage aPushMessage = new PushMessage() { template_id = "lypG1jYyOEfY********OK7-LhEwpPeVNHHxemSI",//模板ID //data=,//暂时不赋值 topcolor = "#FF0000",//头部颜色 touser = Session["openid"].ToString(),//用户的Openid url = "http://www.baidu.com"//用途是当用户点击推送消息的时候,会进入这个页面,具体用途,自己拓展 }; //构造要推送的内容 CashModel aCachData = new CashModel() { adCharge = new DataFontStyle() { color = "#589E63", value = "对应变动金额" }, cashBalance = new DataFontStyle() { color = "#589E63", value = "对应帐户余额" }, date = new DataFontStyle() { color = "#589E63", value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }, first = new DataFontStyle() { color = "#589E63", value = strFirst }, remark = new DataFontStyle() { color = "#589E63", value = "对应:点击“查看详情“立即查阅您的帐户财务记录。" }, type = new DataFontStyle() { color = "#589E63", value = "对应“现金”" } }; //这时候,把要推送的内容,赋值给push,这样,我们要推送的内容就完成了。 aPushMessage.data = aCachData; string strUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + GetLatestAccess_token(); string strJsonData = JsonHelper.Serialize(aPushMessage); string strResult = HttpPost(strUrl, strJsonData); WxResult aResult = JsonHelper.Deserialize<WxResult>(strResult); if (aResult != null) { if (aResult.errcode == 0) { bResult = true; } else { bResult = false; strMsg = aResult.errmsg; } } else { bResult = false; strMsg = "通讯失败,请重试。"; } objResult = new { result = bResult, msg = strMsg }; return Json(objResult); } /// <summary> /// HttpPost /// </summary> /// <param name="Url"></param> /// <param name="postDataStr"></param> /// <returns></returns> public static string HttpPost(string Url, string postDataStr) { byte[] postData = Encoding.UTF8.GetBytes(postDataStr);//编码,尤其是汉字,事先要看下抓取网页的编码方式 WebClient webClient = new WebClient(); webClient.Headers.Add("Content-Type", "application/x-www-form-urlencoded");//采取POST方式必须加的header,如果改为GET方式的话就去掉这句话即可 byte[] responseData = webClient.UploadData(Url, "POST", postData);//得到返回字符流 string srcString = Encoding.UTF8.GetString(responseData);//解码 return srcString; } /// <summary> /// 返回最新的Access_token /// </summary> /// <returns></returns> public string GetLatestAccess_token() { if (dtAccess_token == null || dtAccess_token <= DateTime.Now.AddHours(-1) || string.IsNullOrWhiteSpace(strAccess_token)) { string strUrl = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", WxPayConfig.APPID, WxPayConfig.APPSECRET); string strAccess_tokenData = HttpGet(strUrl, ""); ModelForAccess_token aToken = JsonHelper.Deserialize<ModelForAccess_token>(strAccess_tokenData); dtAccess_token = DateTime.Now; strAccess_token = aToken.access_token; return strAccess_token; } else { return strAccess_token; } } /// <summary> /// WebGet /// </summary> /// <param name="Url"></param> /// <param name="postDataStr"></param> /// <returns></returns> public 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; } public class ModelForAccess_token { public string access_token { get; set; } public int? expires_in { get; set; } } } }
代码已完整提供,如果有缺漏的,请在评论中指出或者进群来告诉我。如果代码提供正常,应该就能推送到的了,目前这个代码,只能给自己推送。实际应用中,可以通过这个页面,然后绑定用户,这样,我们就可以通过用户名字,给他们的openid推送消息。下面贴出推送的截图
对了,忘记了一点,这个要发布到IIS上,然后你发布的域名,要在公众平台里面,那个获取用户权限(前面博文提及到)的网址里面,改成你发布的网址,例如:push.lmx.ren ,否则是获取不到用户信息的。