啊Ran讲微信开发(.net) :公众号(订阅号)+自定义服务器(自定义菜单)

      经过前几次的探讨,我们大概知道了微信开发的流程.微信公众平台的消息交互机制需要微信个人用户手工输入消息才能得到消息回复.由于手机屏幕的限制,在手机上输入文字并不太方便,一般使用手机都是通过在手机屏幕上单击按钮来完成操作,为了个人用户在访问微信公众号时获取更佳的体验,微信开发团队为微信公众平台增加了自定义菜单的功能.

在进入自定义菜单怎么实现之前,我们先来了解一下微信公众号的几个类型:订阅号,服务号,企业号.

订阅号:

主要是为媒体和个人提供一种新的信息传播方式,构建与读者之间更好的沟通与管理的模式,订阅号的的侧重点事信息的传播和分享.在刚开始的时候,订阅号只有简单的消息接收和回复以及每天只有一次的群发功能.随着微信的发展,逐渐开放了很多接口,但前提是通过微信认证,如自定义菜单,群发接口,用户管理接口等.

服务号:

是给企业和组织提供更强大的业务服务与用户管理能力,帮助企业快速实现全新的公众号服务平台,打造意义上的"app".服务号的侧重点事用户管理和营销推广,可利用的接口比订阅号灵活多样.

企业号:

为企业或组织提供移动应用入口,帮助企业建立与员工,上下游供应链及企业应用间的连接.企业号可以理解为企业移动的OA管理系统.

了解了这些准备内容之后,开始进入自定义菜单的探讨.

目前自定义菜单最多包括3个一级菜单,每一个一级菜单最多包含5个二级菜单.一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以"...."代替.

1.自定义菜单数据结构

自定义菜单数据是以JSON格式进行传输的.

{
     "button":[
     {    
          "type":"click",
          "name":"作者简介",
          "key":"YZR_Info"
      },
      {
           "type":"click",
           "name":"相关随笔",
           "key":"YZR_Article"
      },
      {
           "name":"技术",
           "sub_button":[
           {    
               "type":"view",
               "name":"博客",
               "url":"http://www.cnblogs.com/Francis-YZR/"
            }]
       }]
 }

相信大家都对JSON很熟悉,在面向对象开发的时候我们可以拼凑出以上json格式的字符串以实现微信开发自定义菜单数据的传输.

从上述的json中,会发现type有两种值:click和view.

click:用户单击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者,并且带上按钮中开发者填写的key值,开发者可以通过定义的key值与用户进行交互.

view:用户单击view类型按钮后,微信客户端将会打开开发者在按钮中填写的url(网页链接),达到打开网页的目的.这样配合"微信授权"操作可以获取用户的基本信息.

2.自定义菜单接口的封装.

自定义菜单的操作开放了三个接口:

创建接口:(请求方式为POST)

string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/create?access_token={0}", WeixinContext.AccessToken);

查询接口:(请求接口为GET)

string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/get?access_token={0}", WeixinContext.AccessToken);

删除接口:(请求方式为POST)

string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={0}", WeixinContext.AccessToken);

 access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。

啊Ran讲微信开发(.net) :公众号(订阅号)+自定义服务器(自定义菜单)

啊Ran讲微信开发(.net) :公众号(订阅号)+自定义服务器(自定义菜单)

列如:

啊Ran讲微信开发(.net) :公众号(订阅号)+自定义服务器(自定义菜单)

更多相关信息,请查看微信官方开发者文档:

http://mp.weixin.qq.com/wiki/2/88b2bf1265a707c031e51f26ca5e6512.html

3.自定义菜单发送的xml格式

<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[CLICK]]></Event>
<EventKey><![CDATA[EVENTKEY]]></EventKey>
</xml>

 

4.代码的实现

<1>封装获取AccessToken的步骤

    /// <summary>
    /// 获得AccsessToken上下文
    /// </summary>
    class WeixinContext
    {
        private static DateTime GetAccessToken_Time;
        /// <summary>
        /// 过期时间为7200秒
        /// </summary>
        private static int Expires_Period = 7200;
        /// <summary>
        /// 
        /// </summary>
        private static string mAccessToken;
        /// <summary>
        /// 
        /// </summary>
        public static string AccessToken
        {
            get
            {
                //如果为空,或者过期,需要重新获取
                if (string.IsNullOrEmpty(mAccessToken) || HasExpired())
                {
                    //获取
                    mAccessToken = GetAccessToken(AppID, AppSecret);
                }

                return mAccessToken;
            }
        }
        /// <summary>
        /// 获取AccessToken
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="appSecret"></param>
        /// <returns></returns>
        private 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);
            string result = HttpUtility.GetData(url);

            XDocument doc = XmlUtility.ParseJson(result, "root");
            XElement root = doc.Root;
            if (root != null)
            {
                XElement access_token = root.Element("access_token");
                if (access_token != null)
                {
                    GetAccessToken_Time = DateTime.Now;
                    if (root.Element("expires_in") != null)
                    {
                        Expires_Period = int.Parse(root.Element("expires_in").Value);
                    }
                    return access_token.Value;
                }
                else
                {
                    GetAccessToken_Time = DateTime.MinValue;
                }
            }

            return null;
        }
        /// <summary>
        /// 判断Access_token是否过期
        /// </summary>
        /// <returns>bool</returns>
        private static bool HasExpired()
        {
            if (GetAccessToken_Time != null)
            {
                //过期时间,允许有一定的误差,一分钟。获取时间消耗
                if (DateTime.Now > GetAccessToken_Time.AddSeconds(Expires_Period).AddSeconds(-60))
                {
                    return true;
                }
            }
            return false;
        }
    }

<2>封装Http协议的get,post请求

    /// <summary>
    /// Http帮助类
    /// </summary>
    class HttpUtility
    {
        /// <summary>
        /// 发送POST请求,得到response数据
        /// </summary>
        /// <param name="url">Url地址</param>
        /// <param name="data">参数数据</param>
        public static string SendHttpRequest(string url, string data)
        {
            return SendPostHttpRequest(url, "application/x-www-form-urlencoded", data);
        }
        /// <summary>
        /// 发出get请求,得到response
        /// </summary>
        /// <param name="url">url地址</param>
        /// <returns></returns>
        public static string GetData(string url) 
        {
            return SendGetHttpRequest(url, "application/x-www-form-urlencoded");
        }

        /// <summary>
        /// 发出POST请求,得到response数据
        /// </summary>
        /// <param name="url">Url地址</param>
        /// <param name="contentType">类型</param>
        /// <param name="requestData">参数数据</param>
        /// <returns>得到返回数据</returns>
        public static string SendPostHttpRequest(string url, string contentType, string requestData)
        {
            WebRequest request = (WebRequest)HttpWebRequest.Create(url);
            request.Method = "POST";
            byte[] postBytes = null;
            request.ContentType = contentType;
            postBytes = Encoding.UTF8.GetBytes(requestData);
            request.ContentLength = postBytes.Length;
            using (Stream outstream = request.GetRequestStream())
            {
                outstream.Write(postBytes, 0, postBytes.Length);
            }
            string result = string.Empty;
            using (WebResponse response = request.GetResponse())
            {
                if (response != null)
                {
                    using (Stream stream = response.GetResponseStream())
                    {
                        using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                        {
                            result = reader.ReadToEnd();
                        }
                    }

                }
            }
            return result;
        }

       /// <summary>
        /// 发出GET请求,得到response数据
       /// </summary>
       /// <param name="url">URL地址</param>
       /// <param name="contentType">类型</param>
       /// <returns>得到返回数据</returns>
        public static string SendGetHttpRequest(string url, string contentType)
        {
            WebRequest request = (WebRequest)HttpWebRequest.Create(url);
            request.Method = "GET";
            request.ContentType = contentType;
            string result = string.Empty;
            using (WebResponse response = request.GetResponse())
            {
                if (response != null)
                {
                    using (Stream stream = response.GetResponseStream())
                    {
                        using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                        {
                            result = reader.ReadToEnd();
                        }
                    }
                }
            }
            return result;
        }
    }

<3>封装自定义菜单接口

class MenuManager
    {
        
        /// <summary>
        /// 依据accesstoken发出get请求获取菜单response
        /// </summary>
        public static string GetMenu()
        {
            string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/get?access_token={0}", WeixinContext.AccessToken);

            return HttpUtility.GetData(url);
        }
        /// <summary>
        /// 创建菜单
        /// </summary>
        public static void CreateMenu(string menu)
        {
            string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/create?access_token={0}", WeixinContext.AccessToken);
            HttpUtility.SendHttpRequest(url, menu);
        }
        /// <summary>
        /// 删除菜单
        /// </summary>
        public static void DeleteMenu()
        {
            string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={0}", WeixinContext.AccessToken);
            HttpUtility.GetData(url);
        }

<4>自定义菜单的处理程序

    class EventHandler : IHandler
    {
        /// <summary>
        /// 请求的xml
        /// </summary>
        private string RequestXml { get; set; }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="requestXml"></param>
        public EventHandler(string requestXml)
        {
            this.RequestXml = requestXml;
        }
        /// <summary>
        /// 处理请求
        /// </summary>
        /// <returns></returns>
        public string HandleRequest()
        {
            string response = string.Empty;
            EventMessage em = EventMessage.LoadFromXml(RequestXml);
            if (em != null)
            {
                switch (em.Event.ToLower())
                {
                    case "click":
                        response = ClickEventHandler(em);
                        break;
                }
            }
            return response;
        }
      
        /// <summary>
        /// 处理点击事件
        /// </summary>
        /// <param name="em"></param>
        /// <returns></returns>
        private string ClickEventHandler(EventMessage em)
        {
            string result = string.Empty;
            if (em != null && em.EventKey != null)
            {
                switch (em.EventKey)
                {
                    case"YZR_Info":
                        result = btnInfoClick(em);
                        break;
                    case "YZR_Article":
                        result = btnArticleClick(em);
                        break;
                 
                }
            }

            return result;
        }
        /// <summary>
        /// 简介
        /// </summary>
        /// <param name="em"></param>
        /// <returns></returns>
        private string btnInfoClick(EventMessage em)
        {
            //回复消息
            TextMessage tm = new TextMessage();
            tm.ToUserName = em.FromUserName;
            tm.FromUserName = em.ToUserName;
            tm.CreateTime = Common.GetNowTime();
            tm.Content = @"I am YZR,that‘s all!";
            return tm.GenerateContent();
        }
        /// <summary>
        /// 随笔文章
        /// </summary>
        /// <param name="em"></param>
        /// <returns></returns>
        private string btnArticleClick(EventMessage em)
        {
            //回复消息
            TextMessage tm = new TextMessage();
            tm.ToUserName = em.FromUserName;
            tm.FromUserName = em.ToUserName;
            tm.CreateTime = Common.GetNowTime();
            tm.Content = @"请到我的博客查看";
            return tm.GenerateContent();
        }
      

    }

 

<4>调用

程序通过面向对象编程(连接数据库得到相应的菜单数据信息),拼凑出上文提到的满足格式的json字符串,带入CreateMeun(string jsonMenu)方法中.在自定义服务器上的处理程序中通过URL接入之后一开始就调用,也可以在全局应用文件上调用.

 

通过对自定义菜单的探讨之后,我们大概知道了对于Menu下的click的处理流程,然而我们还会发现还有一种type为view的,带有url参数的点击事件,下次我们会结合这个url一起来探讨一下"微信授权登录".

啊Ran讲微信开发(.net) :公众号(订阅号)+自定义服务器(自定义菜单)

上一篇:啊Ran讲微信开发(.net) 目录结构


下一篇:关于南通大学教务管理系统微信公众号的不足