微信公众号开发
1.首先有一个公众号(服务号);
2.登陆微信公众平台设置Url(注意这个url是入口地址,每次请求都要从这个地址进)以及Token
3.公众号的基本设置就到此结束,其他的设置用到时再来设置。接下来就是写代码:
3.1自定义公众号菜单:
先获取Access_Token
using Common; using System.Timers; namespace Common { /// <summary> /// 获取Access_Token /// </summary> public static class AccessTokenUtil { private static readonly string FILENAME = "AccessTokenUtil.cs"; // AccessToken private static string AccessToken = ""; // 超时时间 private static int Expires_in = 7200; private static Timer timer = null; public static string GetAccessToken() { if (string.IsNullOrEmpty(AccessToken)) { // 获取Token并定时刷新 string httpcontent = GetTokenHttp(); bool result = FormatResult(httpcontent); if (result == true) { // 设置定时器 if (timer != null) { timer.Dispose(); } timer = new Timer(); timer.Elapsed += delegate { string httpcontent2 = GetTokenHttp(); bool result2 = FormatResult(httpcontent); if (!result2) { LogHelper.FileNameError(FILENAME, string.Format("Timer获取Token失败")); } }; timer.Interval = Expires_in * 1000 / 2; } else { LogHelper.FileNameError(FILENAME, string.Format("获取Token失败")); } } return AccessToken; } /// <summary> /// 发送请求获取AccessToken /// </summary> /// <returns></returns> private static string GetTokenHttp() { string url = "https://api.weixin.qq.com/cgi-bin/token"; string appid = System.Configuration.ConfigurationManager.AppSettings["appid"]; string appsecret = System.Configuration.ConfigurationManager.AppSettings["appsecret"]; string param = string.Format("grant_type=client_credential&appid={0}&secret={1}", appid, appsecret); string result = HttpUtils.HttpGet(url, param); LogHelper.FileNameInfo(FILENAME, string.Format("access_token接收到的数据:{0}", result)); return result; } private static bool FormatResult(string result) { AccessTokenResult resultObj = fastJSON.JSON.ToObject<AccessTokenResult>(result); if (!string.IsNullOrEmpty(resultObj.access_token)) { AccessToken = resultObj.access_token; Expires_in = resultObj.expires_in; return true; } else { LogHelper.FileNameError(FILENAME, string.Format("获取失败,返回码:{0}, 错误信息:{1}", resultObj.errcode, resultObj.errmsg)); return false; } } public class AccessTokenResult { public string access_token { get; set; } public int expires_in { get; set; } public string errcode { get; set; } public string errmsg { get; set; } } } }
menu.txt
{ "button":[ { "type":"view", "name":"订水", "url":"http://wx." }, { "type":"view", "name":"商城", "sub_button":[ { "type":"view", "name":"我的商城", "url":"http://wx" }, { "type":"view", "name":"我的订单", "url":"http://wx." }, { "type":"view", "name":"我的地址", "url":"http://wx.sil" }] }, { "type":"view", "name":"关于", "sub_button":[ { "type":"view", "name":"锶的作用", "url":"https://mp.weixin-KQ3Q" }, { "type":"view", "name":"关于锶享家", "url":"https://mp.weixin" }, { "type":"view", "name":"联系我们", "url":"http://wx" }] }] }
请求微信接口创建菜单
using Common; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace weixin_api { public partial class createMenu : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { FileStream fs1 = new FileStream(Server.MapPath(".") + "\\menu.txt", FileMode.Open); StreamReader sr = new StreamReader(fs1, Encoding.GetEncoding("GBK")); string menu = sr.ReadToEnd(); sr.Close(); fs1.Close(); string token = AccessTokenUtil.GetAccessToken(); GetPage("https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + token, menu); } public string GetPage(string posturl, string postData) { Stream outstream = null; Stream instream = null; StreamReader sr = null; HttpWebResponse response = null; HttpWebRequest request = null; Encoding encoding = Encoding.UTF8; byte[] data = encoding.GetBytes(postData); // 准备请求... try { // 设置参数 request = WebRequest.Create(posturl) as HttpWebRequest; CookieContainer cookieContainer = new CookieContainer(); request.CookieContainer = cookieContainer; request.AllowAutoRedirect = true; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = data.Length; outstream = request.GetRequestStream(); outstream.Write(data, 0, data.Length); outstream.Close(); //发送请求并获取相应回应数据 response = request.GetResponse() as HttpWebResponse; //直到request.GetResponse()程序才开始向目标网页发送Post请求 instream = response.GetResponseStream(); sr = new StreamReader(instream, encoding); //返回结果网页(html)代码 string content = sr.ReadToEnd(); string err = string.Empty; Response.Write(content); return content; } catch (Exception ex) { string err = ex.Message; return string.Empty; } } } }
调接口之前还要在微信平台里面添加白名单即将本地的ip地址加进去,不然请求出错。接口调用成功后取消关注重新关注公众号就能看到创建的菜单。
3.2 菜单创建完成后接下来就是拿到用户信息存在数据库里面,在我们关注或取消关注的时候微信会给我们上面的填写的Url发送请求,所以在这个url(我这个是wxapi.aspx)对应的页面里面就可以拿到用户信息以及用户操作类型(关注事件还是取消事件等)
using Common; using DAL; using fastJSON; using Model; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Xml; using System.Xml.Linq; using weixin_api.ViewModels; namespace weixin_api { public partial class wxapi : System.Web.UI.Page { private readonly string FILENAME = "wxapi.aspx"; private static List<BaseMsg> _queue = null; protected void Page_Load(object sender, EventArgs e) { LogHelper.FileNameInfo(FILENAME, "收到服务器消息"); // 取参数 signature、timestamp、nonce、encrypt_type、msg_signature string echoString = Request.QueryString["echoStr"]; string signature = Request.QueryString["signature"]; string timestamp = Request.QueryString["timestamp"]; string nonce = Request.QueryString["nonce"]; string encrypt_type = Request.QueryString["encrypt_type"]; string msg_signature = Request.QueryString["msg_signature"]; LogHelper.FileNameInfo(FILENAME, string.Format("参数分别是, echoString={0}, signature={1}, timestamp={2}, nonce={3},encrypt_type={4},msg_signature={5}", echoString, signature, timestamp, nonce, encrypt_type, msg_signature)); if (!string.IsNullOrEmpty(echoString)) { Response.Write(echoString); Response.End(); return; } // 验证签名 string token = System.Configuration.ConfigurationManager.AppSettings["Token"]; if (!ParamCheck(signature, timestamp, nonce, echoString, token)) { LogHelper.FileNameError(FILENAME, string.Format("服务器验证失败")); Response.End(); return; } // 接收消息 if (Request.HttpMethod.ToUpper() == "POST") { string str = ConvertUtils.StreamToString(Request.InputStream); LogHelper.FileNameInfo(FILENAME, "接收到的事件数据:" + str); //解密 //公众平台上开发者设置的token, appID, EncodingAESKey string sToken = System.Configuration.ConfigurationManager.AppSettings["Token"]; string sAppID = System.Configuration.ConfigurationManager.AppSettings["appid"]; string sEncodingAESKey = System.Configuration.ConfigurationManager.AppSettings["EncodingAESKey"]; WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sAppID); string sMsg = ""; //解析之后的明文 int ret = 0; ret = wxcpt.DecryptMsg(msg_signature, timestamp, nonce, str, ref sMsg); if (ret != 0) { LogHelper.FileNameInfo(FILENAME, string.Format("解密失败:" + ret)); Response.End(); return; } // 判断队列是否超额 if (_queue == null) { _queue = new List<BaseMsg>(); } else if (_queue.Count >= 50) { // 保留20秒内未响应的消息 _queue = _queue.Where(q => { return q.CreateTime.AddSeconds(20) > DateTime.Now; }).ToList(); } //这是解密出来的结果 //<xml><ToUserName><![CDATA[gh_9fca63d34597]]></ToUserName> //<FromUserName><![CDATA[orhx-5o9]]></FromUserName> //<CreateTime>1557123110</CreateTime> //<MsgType><![CDATA[event]]></MsgType> //<Event><![CDATA[VIEW]]></Event> //<EventKey><![CDATA[http://wx.s/gzh_Ping.html]]></EventKey> //<MenuId>471589802</MenuId> //</xml> XElement xdoc = XElement.Parse(sMsg); var FromUserName = GetXmlElement(xdoc, "FromUserName"); var CreateTime = GetXmlElement(xdoc, "CreateTime"); var msgtype = GetXmlElement(xdoc, "MsgType").ToUpper(); var MsgId = FromUserName + CreateTime; string openid = GetXmlElement(xdoc, "FromUserName"); if (string.IsNullOrEmpty(openid)) { LogHelper.FileNameInfo(FILENAME, string.Format("没有获取到当前用户的openid")); Response.Write(""); Response.End(); return; } GlobalInfo.openid = openid; MsgType type = (MsgType)Enum.Parse(typeof(MsgType), msgtype); if (type == MsgType.EVENT) { try { // 事件消息 if (_queue.FirstOrDefault(m => { return m.MsgFlag == MsgId; }) == null) { _queue.Add(new BaseMsg { CreateTime = DateTime.Now, FromUser = FromUserName, MsgFlag = MsgId }); } else { Response.Write(""); Response.End(); return; } EventMsg(new EventModel { ToUserName = GetXmlElement(xdoc, "ToUserName"), FromUserName = GetXmlElement(xdoc, "FromUserName"), CreateTime = GetXmlElement(xdoc, "CreateTime"), MsgType = GetXmlElement(xdoc, "MsgType"), Event = GetXmlElement(xdoc, "Event"), EventKey = GetXmlElement(xdoc, "EventKey"), MenuId = GetXmlElement(xdoc, "MenuId") }); return; } catch (Exception ex) { LogHelper.FileNameInfo(FILENAME, "事件消息处理出现异常" + ex.Message); } } else { Response.Write(""); Response.End(); return; } } LogHelper.FileNameInfo(FILENAME, "未知消息"); Response.End(); return; } /// <summary> /// 验证参数 /// </summary> /// <returns></returns> private bool ParamCheck(string signature, string timestamp, string nonce, string echostr, string token) { List<string> list = new List<string>(); list.Add(token); list.Add(timestamp); list.Add(nonce); list.Sort(); string str = string.Join("", list); string shaStr = SHA1Common.SHA1(str); LogHelper.FileNameInfo(FILENAME, string.Format("sha结果:{0}, signature={1}", shaStr, signature)); if (!string.IsNullOrEmpty(signature) && !string.IsNullOrEmpty(shaStr) && signature.ToUpper().Equals(shaStr.ToUpper())) { return true; } return false; } private void EventMsg(EventModel model) { LogHelper.FileNameInfo(FILENAME, "接收到事件:" + model.Event); switch (model.Event) { case "subscribe": SubscribeEvent(model); break; case "unsubscribe": UnSubscribeEvent(model); break; default: break; } } /// <summary> /// 订阅事件 /// </summary> /// <param name="model"></param> private void SubscribeEvent(EventModel model) { try { //发请求获取用户信息 string url = "https://api.weixin.qq.com/cgi-bin/user/info"; string access_token = AccessTokenUtil.GetAccessToken(); string param = string.Format("access_token={0}&openid={1}&lang=zh_CN", access_token, model.FromUserName); string result = Common.HttpUtils.HttpGet(url, param); wx_user user = JSON.ToObject<wx_user>(result); wx_userDal wxUserDal = new wx_userDal(); if (wxUserDal.Exists(user.openid)) { // 已存在,更新关注属性为1 wxUserDal.UpdateSubscribe(user.openid, 1, (long)user.subscribe_time); } else { // 添加到数据库 user.balance = 0; int n = wxUserDal.Add(user); if (n != 1) { LogHelper.FileNameError(FILENAME, string.Format("用户订阅失败, openid:{0}", user.openid)); } } string r = sendTextMessage(model, System.Configuration.ConfigurationManager.AppSettings["DefaultWxMsg"]); HttpContext.Current.Response.Write(r); HttpContext.Current.Response.End(); } catch (Exception e) { LogHelper.FileNameError(FILENAME, e.Message); } } /// <summary> /// 取消订阅事件 /// </summary> /// <param name="model"></param> private void UnSubscribeEvent(EventModel model) { // 把数据库是否订阅字段改为0 try { wx_userDal wxUserDal = new wx_userDal(); wxUserDal.UpdateSubscribe(model.FromUserName, 0, Common.CommonUtils.GetTimeStamp()); } catch (Exception e) { LogHelper.FileNameError(FILENAME, e.Message); } } private string GetXmlElement(XElement element, string nodeName) { if (element == null || string.IsNullOrEmpty(nodeName)) { return string.Empty; } if (element.Element(nodeName) != null) { return element.Element(nodeName).Value; } return string.Empty; } #region 关注后自动回复 /// <summary> /// 发送文字消息 /// </summary> /// <param name="wx" />获取的收发者信息 /// <param name="content" />内容 /// <returns></returns> private string sendTextMessage(EventModel wx, string content) { string res = string.Format(Message_Text, wx.FromUserName, wx.ToUserName, DateTime.Now.Ticks, content); return res; } /// <summary> /// 普通文本消息 /// </summary> private static string Message_Text { get { return @"<xml> <ToUserName><![CDATA[{0}]]></ToUserName> <FromUserName><![CDATA[{1}]]></FromUserName> <CreateTime>{2}</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[{3}]]></Content> </xml>"; } } #endregion #region 模型 public class BaseMsg { /// <summary> /// 发送者标识 /// </summary> public string FromUser { get; set; } /// <summary> /// 消息表示。普通消息时,为msgid,事件消息时,为事件的创建时间 /// </summary> public string MsgFlag { get; set; } /// <summary> /// 添加到队列的时间 /// </summary> public DateTime CreateTime { get; set; } } public enum MsgType { /// <summary> ///文本类型 /// </summary> TEXT, /// <summary> /// 图片类型 /// </summary> IMAGE, /// <summary> /// 语音类型 /// </summary> VOICE, /// <summary> /// 视频类型 /// </summary> VIDEO, /// <summary> /// 地理位置类型 /// </summary> LOCATION, /// <summary> /// 链接类型 /// </summary> LINK, /// <summary> /// 事件类型 /// </summary> EVENT } public enum Event { /// <summary> /// 非事件类型 /// </summary> NOEVENT, /// <summary> /// 订阅 /// </summary> SUBSCRIBE, /// <summary> /// 取消订阅 /// </summary> UNSUBSCRIBE, /// <summary> /// 扫描带参数的二维码 /// </summary> SCAN, /// <summary> /// 地理位置 /// </summary> LOCATION, /// <summary> /// 单击按钮 /// </summary> CLICK, /// <summary> /// 链接按钮 /// </summary> VIEW, /// <summary> /// 扫码推事件 /// </summary> SCANCODE_PUSH, /// <summary> /// 扫码推事件且弹出“消息接收中”提示框 /// </summary> SCANCODE_WAITMSG, /// <summary> /// 弹出系统拍照发图 /// </summary> PIC_SYSPHOTO, /// <summary> /// 弹出拍照或者相册发图 /// </summary> PIC_PHOTO_OR_ALBUM, /// <summary> /// 弹出微信相册发图器 /// </summary> PIC_WEIXIN, /// <summary> /// 弹出地理位置选择器 /// </summary> LOCATION_SELECT, /// <summary> /// 模板消息推送 /// </summary> TEMPLATESENDJOBFINISH } #endregion } }
3.3 菜单完了、用户信息存了,接下来就是 具体的功能实现新建页面,在网上找一个公众号开发的框架比如我用的是weui,去官网下载。
比如一个简单的购买水的页面
代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0" /> <link href="static/weui-master/css/weui.css" rel="stylesheet" /> <link href="static/weui-master/css/weuix.css" rel="stylesheet" /> <script src="static/weui-master/js/zepto.min.js"></script> <script src="static/weui-master/js/zepto.weui.js"></script> <script src="static/weui-master/js/axios.min.js"></script> <script src="static/weui-master/js/zepto.weui.js"></script> <script src="static/weui-master/js/zepto.min.js"></script> <title>产品列表</title> <style> .lab { font-size: 15px; color: gray; } .price { color: red; } .page-bg { /*background-color:orange;*/ } .weui-cells { margin-top: 0.5em; } .goumai { position: relative; top: 20px; } .weui-count { position: relative; top: 9px; } #goodslist { margin-bottom: 50px; } .weui_media_title { font-size: 13px; } .car { font-size: 8px; -o-border-radius: 100%; -ms-border-radius: 100%; -moz-border-radius: 100%; -webkit-border-radius: 100%; } .weui-cell__ft { text-align: center; } .weui-btn_mini { line-height: 2.1; } * { touch-action: pan-y; } </style> <script> $(function () { $('.weui-tab').tab({ defaultIndex: 0, activeClass: 'weui-bar__item_on', onToggle: function (index) { if (index > 0) { if (index == 1) { var spanCar = parseInt($('#spanCar').text()); //购物车里面数量 if (spanCar <= 0) { $.toast("购物车太空啦。", "forbidden"); } else { window.location.href = "gzh_ManageCar.html"; } } } } }) }) </script> </head> <body ontouchstart class="page-bg"> <div class="weui-pull-to-refresh__layer"> <div class='weui-pull-to-refresh__arrow'></div> <div class='weui-pull-to-refresh__preloader'></div> <div class="down">下拉刷新</div> <div class="up">释放刷新</div> <div class="refresh">正在刷新</div> </div> <div class="weui-cells" id="goodslist"> </div> <div class="weui-tab tab-bottom " style="height:44px;z-index:100;"> <div class="weui-tabbar"> <a href="javascript:;" class="weui-tabbar__item"> <span style="display: inline-block;position: relative;"> <i class="icon icon-67 f27"></i> <span class="weui-badge" style="position: absolute;top: -2px;right: -13px;" id="ResPrice">¥0.0</span> </span> <p class="weui-tabbar__label">本次消费</p> </a> <a href="javascript:;" class="weui-tabbar__item"> <span style="display: inline-block;position: relative;"> <i class="icon icon-24 f27 weui-icon-warn"></i> <span class="weui-badge" style="position: absolute;top: -2px;right: -13px;" id="spanCar">0</span> </span> <p class="weui-tabbar__label">购物车</p> </a> <!--<a href="javascript:;" class="weui-tabbar__item weui-icon-warn"> <i class="icon icon-43 f27 weui-icon-waiting"></i> <p class="weui-tabbar__label">去结算</p> </a>--> <!--<a href="javascript:;" class="weui-btn bg-orange">快去结算</a>--> </div> </div> <script type="text/javascript"> //全局变量 var load = false; // load为判断接口是否请求完成,防止多次触摸、多次点击导致接口的多次请求出错 var page = 1;//默认第一页 var pageSize = 20; //默认一页显示6条数据 $(function () { $(document.body).pullToRefresh({ distance: 10, onRefresh: function () { getData(page, "api/GetTicketListJson.ashx"); //初始化数据从第一页数据开始请求 getCar(); $(document.body).pullToRefreshDone(); } }); getData(page, "api/GetTicketListJson.ashx"); //初始化数据从第一页数据开始请求 getCar(); var MAX = 99, MIN = 0; $('.weui-count__decrease').click(function (e) { var id = $(this).attr("tag"); var $input = $(e.currentTarget).parent().find('.weui-count__number'); var number = parseInt($input.val() || "0") - 1 if (number == 0) { $('#des' + id).hide(); } if (number < MIN) { number = MIN; return; } $input.val(number) }) $('.weui-count__increase').click(function (e) { var id = $(this).attr("tag");var $input = $(e.currentTarget).parent().find('.weui-count__number'); var number = parseInt($input.val() || "0") + 1 if (number > MAX) number = MAX; $input.val(number) }) }); //请求后台数据 function getData(page, url) { //请求接口的方法,方法带page,url两个参数。 $('#more').text('正在加载中...'); $.ajax({ url: url, //请求接口的url type: 'get',//请求方式(post或get)默认为get async: false, //默认情况下是true表示所有请求为异步请求,如果要为同步则用false dataType: "json", data: { 'page': page, 'limit': pageSize, 'name': '' //$("#searchInput").val() }, success: function (data) { //请求成功调用的回调函数 if (data != null && data.code == 0) { $("#pageNum").val(parseInt(data.currentPage) + 1); showList(data); if (data.currentPage >= data.totalPage) { //这里判断接口数据是否到底部 load = true; $("#more").html('已经到底了'); } else if (data.currentPage < data.totalPage) { load = false; $("#more").html('查看更多'); } } }, error: function (error) { //请求失败调用的回调函数 console.log(error); } }); } //查询当前用户的购物车信息 function getCar() { axios.post('api/AddCar.ashx?goodId=0&numType=0') .then(function (rs) { if (rs.data.result) { $('#spanCar').text(rs.data.code); } }) .catch(function (e) { }); } //显示数据 function showList(data) { //显示列表的html内容 $("#goodslist").html(""); for (var i = 0; i < data.list.length; i++) { var html = "<div class='weui-cell weui-cell_swiped'><div class='weui-cell__bd'><div class='weui-cell'><div class='weui-cell__hd'>" html += "<img src='" + data.list[i].imgurl + "' alt='' style='width:60px;margin-right:5px;display:block'>"; html += "</div>"; //img外面的div结束 html += "<div class='weui-cell__bd'><span class='weui_media_title product-buttom'>" + data.list[i].title + "</span><br><span class='lab'>规格:" + data.list[i].regulation + "</span><span id='spanPrice" + data.list[i].id + "' class='lab price'>¥" + data.list[i].price + "</span>"; html += "</div>"; //标题 单价 规格 外面的div结束 html += "<div class='weui-cell__ft'><div class='weui-count'><a class='weui-count__btn weui-count__decrease' style='display:none;' id='des" + data.list[i].id + "' tag=" + data.list[i].id + "></a><input class='weui-count__number' style='display:none;' tag=" + data.list[i].id + " id='txtCount" + data.list[i].id + "' type='number' value='0' /><a class='weui-count__btn weui-count__increase' tag=" + data.list[i].id + "></a>"; html += "</div>"; //计数器外面的div结束 html += "</div>"; //class="weui-cell__ft" div结束 html += "</div></div></div>"; $("#goodslist").append(html); } } //加减按钮事件 function ChangeCount(v, op) { $.showLoading("加载中..."); var id = v; if (parseInt(id) > 0 && id != "") { axios.post('api/AddCar.ashx?goodId=' + id + "&numType=" + op) .then(function (rs) { $.hideLoading(); if (rs.data.result) { JsPrice(); $('#spanCar').text(rs.data.code); } else { $.toast("添加失败" + rs.data.msg, "cancel"); } }) .catch(function (e) { $.hideLoading(); $.toast("请求失败", 3000); }); } } //计算本次消费金额 function JsPrice() { var priceSum = 0; var chks = $('#goodslist input[type=number]'); if (chks.length > 0) { for (var i = 0; i < chks.length; i++) { var num = parseInt($(chks[i]).val()); var id = parseInt($(chks[i]).attr("tag")); if (num > 0) { //单价 var price = $('#spanPrice' + id).text().replace(/¥/, ''); //计算总价 priceSum += num * price; } } } $('#ResPrice').text("¥" + parseFloat(parseFloat(priceSum)).toFixed(2)); } //==============核心代码============= //鼠标向下滚动加载下一页数据,或者移动端向下滑动加载下一页数据 var winH = $(window).height(); //页面可视区域高度 var scrollHandler = function () { var pageH = $(document.body).height(); var scrollT = $(window).scrollTop(); //滚动条top var aa = (pageH - winH - scrollT) / winH; if (aa < 0.02) {//0.02是个参数 if (load == false) { load = true; page = $("#pageNum").val(); getData(page, "/api/GetTicketListJson.ashx"); } } } //定义鼠标滚动事件 $(window).scroll(scrollHandler); </script> </body> </html>
具体功能画面根据自己需求去写,这里简单举例而已。到这里就已经基本完成了。希望可以帮助更多的刚开始接触公众号开发的伙伴们,后面有空再写公众号授权 微信支付等这些
人生的成功不在于拿到一副好牌,而是怎样将坏牌打好。