以文本方式查看主题

-  Foxtable(狐表)  (http://foxtable.net/bbs/index.asp)
--  专家坐堂  (http://foxtable.net/bbs/list.asp?boardid=2)
----  二、Foxtable、微信公众平台与Asp.net开发实践之二:用Foxtable设计公众号的菜单  (http://foxtable.net/bbs/dispbbs.asp?boardid=2&id=77773)

--  作者:Hyphen
--  发布时间:2015/11/25 10:19:00
--  二、Foxtable、微信公众平台与Asp.net开发实践之二:用Foxtable设计公众号的菜单

如有任何错漏和好的建议可以在汇总帖回帖说明 ,不胜感谢。

 

如果有什么开发上的疑问,请另开一帖提问,本帖不回。

 

 示例在汇总贴

 

 

要设计菜单,首先要明白微信菜单的组成。


首先说明一下,在公众号管理页面左边功能区有一个自定义菜单的功能,点击后看到可能是这样:

 


图片点击可在新窗口打开查看此主题相关图片如下:1.jpg
图片点击可在新窗口打开查看

 

这是由于启用了上面第一章实践中我们保存的服务器配置,启用了配置后,微信默认大部分的功能都由开发者接口来提供,为了避免冲突,公众号管理页面一些功能会屏蔽不能在管理页面使用。我们这里主要做的就是开发实践,所以页面管理就不做介绍了。


 

打开微信开发文档,可以看到以下一段说明

 


图片点击可在新窗口打开查看此主题相关图片如下:2.jpg
图片点击可在新窗口打开查看

 

 

所以我们设计的菜单就要符合游戏制定者的规则。


 

微信菜单有10种类型的菜单,以click和view类型为主,其它的相对用的比较少。


 

微信菜单接口传输的一般都是JSON数据包,所以我们需要在程序中进行转换,代码直接使用了Newtonsoft.Json库。


 

下面我们就来一步步做一个可以使用的菜单:


 

1. 创建微信管理项目并设计数据表


 

打开Foxtable开发版,创建一个项目,项目名称“微信管理”,点击创建表,分别创建2个表【微信公众号菜单】和【菜单响应类型】:

 


图片点击可在新窗口打开查看此主题相关图片如下:3.jpg
图片点击可在新窗口打开查看

 


 


--  作者:Hyphen
--  发布时间:2015/11/25 10:19:00
--  继续

 
图片点击可在新窗口打开查看此主题相关图片如下:4.jpg
图片点击可在新窗口打开查看

 

把开发文档中的10种菜单类型导入表【菜单响应类型】。

 

2. 设计菜单管理窗口

 

打开窗口管理增加一个窗口“微信公众号菜单设置”,设计一个类似以下界面的窗口,可以增加、修改和删除微信菜单。

 


图片点击可在新窗口打开查看此主题相关图片如下:5.jpg
图片点击可在新窗口打开查看

 

 

窗口左边为一棵树,直观显示菜单的层次,设置一个函数ReflashTree,更改菜单后刷新树结构:

Dim canUse As Boolean = args(0)

Dim trvMenu As WinForm.TreeView = Forms("微信公众号菜单设置").Controls("trvMenu")

trvMenu.CreateTree("微信公众号菜单","路径","\\",IIF(canUse,"是否停用 = 0",""),"排序")

 

For Each n As WinForm.TreeNode In trvMenu.Nodes

    If n.Level = 0

        n.Ic

    Else

        n.Ic

    End If

Next

trvMenu.ExpandAll()

 

右边下侧是菜单表的副本,上侧是一些编辑框。

 

其中菜单响应类型下拉框内容来自“菜单响应类型”表,

 

这里的设计是点击新增和修改后,编辑完内容,需要点击保存,才保存到数据库中。

 

根据微信接口的一些要求和限制,保存逻辑中做了一些相应的校验,详细校验逻辑见附件示例。

 

3. 提交菜单到微信

 

3.1 微信接口的调用

 

开发文档中创建菜单的接口是:

 

http请求方式:POST(请使用https协议)

 https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

 

可以看得出来,微信要求使用POST的方式请求接口。

 

再看看接口参数,需要传递一个access_token,那么这个access_token又是怎么来的,到开发文档找找,文档是这么说的


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

 

获取access_token接口调用请求说明:

 

http请求方式: GET

access_token接口的请求方式是GET,参数有3个

参数

是否必须

说明

grant_type

获取access_token填写client_credential

appid

第三方用户唯一凭证

secret

第三方用户唯一凭证密钥,即appsecret

 

调用access_token接口的返回值是这样的

 

{"access_token":"ACCESS_TOKEN","expires_in":7200}


参数

说明

access_token

获取到的凭证

expires_in

凭证有效时间,单位:秒

 

grant_type是固定的内容,我们不用管;appid和secret就是微信管理平台开发者中心的开发者ID,都可以取的到,每个公众号的开发者ID都是唯一的。

 

到这里我们明白了几点:

 

a、所有的接口都需要使用access_token,所以每次调用接口都需要获取access_token

b、access_token并不是每次都重新需要获取,有一定的有效期,目前是2小时(7200秒),不然每天2000次的调用限制很快就会用完,所以需要做缓存处理

c、access_token依赖于开发者ID生成,所以请妥善保管好自己的开发者ID,避免被别人利用

d、微信的接口用的https协议,请求方式get和post都用到了,所以可以在代码中进行包装一下,进行代码重用

e、微信接口收发的都是Json数据包,所以设计上最好是适应这种特点

 

下面就根据这几点做一些接口调用前的准备工作

 

3.2 打开全局代码增加一些实体数据类

 

主要是一些实体类,方便在Json字符串和对象之间做转换,注意各类中定义的属性和Json中的名称要一致,而且大小写也要一致,比如微信接口的返回值是这样的:{"errcode":40013,"errmsg":"invalid appid"};

那么可以定义一个能够获取返回值的类如下

\'与微信接口交互的Json对象接口

Public Class JsonDatabase

\' 微信接口请求错误返回的错误码

Public errcode As ReturnCode = ReturnCode.请求成功

\' 微信接口请求错误返回的错误描述

Public errmsg As String

Public Overridable Function CheckError() As Boolean

Return errcode = ReturnCode.请求成功

End Function

End Class

 

其它的实体类的定义请自行查看示例的全局代码,就不一一贴出来了。


 

[此贴子已经被作者于2015/11/25 10:27:15编辑过]

--  作者:Hyphen
--  发布时间:2015/11/25 10:20:00
--  继续

 

3.3 打开内部函数,增加一些函数

 

a、定义GET函数:HttpGet

 

Dim url As String = args(0)

Dim encoding As System.Text.Encoding
If args.length > 2 Then encoding  = args(1)
If encoding Is Nothing Then encoding = System.Text.Encoding.UTF8

Dim wc As New System.Net.WebClient()

wc.Encoding = encoding

Return wc.DownloadString(url)

 

b、定义POST函数:HttpPost

 

\'定义POST函数

Dim url As String = Args(0) \'post的接口地址

Dim postStream As System.IO.Stream = Args(1) \'向接口POST的数据流

Dim encoding As System.Text.Encoding = System.Text.Encoding.GetEncoding("utf-8")\'编码格式

 

Dim sresult As String

Dim request As System.Net.HttpWebRequest = DirectCast(System.Net.WebRequest.Create(url), System.Net.HttpWebRequest)

request.Method = "POST"

request.Timeout = Vars("TimeOut")

\'设置标头

request.ContentType = "application/x-www-form-urlencoded"

request.ContentLength = IIf(Not postStream Is Nothing, postStream.Length, 0)

request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"

request.KeepAlive = True

request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36"

 

\'向接口POST数据

If (Not postStream Is Nothing) Then

    postStream.Position = 0

    Dim requestStream As System.IO.Stream = request.GetRequestStream

    Dim buffer4 As Byte() = New Byte(&H400 - 1) {}

    Dim num3 As Integer = postStream.Read(buffer4, 0, buffer4.Length)

    Do While (num3 <> 0)

        requestStream.Write(buffer4, 0, num3)

        num3 = postStream.Read(buffer4, 0, buffer4.Length)

    Loop

    postStream.Close()

End If

 

\'获取接口的返回值

Dim response As System.Net.HttpWebResponse = DirectCast(request.GetResponse, System.Net.HttpWebResponse)

Using stream3 As System.IO.Stream = response.GetResponseStream

Using reader As System.IO.StreamReader = New System.IO.StreamReader(stream3, encoding)

sresult = reader.ReadToEnd

End Using

End Using

Return sresult

 

 

c、定义获取AccessToken的函数:

 

在全局代码中定义一个AccessToken缓存对象

Public _accesstokencache As new AccessTokenCacheData

在项目事件AfterOpenProject中赋值开发者ID

\'定义开发者ID,实际应用时,可以加密字符串,放到配置文件中

_accesstokencache.AppId= "AppId" \'填写自己的AppId

_accesstokencache.AppSecret = "AppSecret" \'填写自己的AppSecret

 

创建一个函数,从微信接口获取AccessTokenGetNewAccessToken

 

Dim appid As String = _accesstokencache.AppId

Dim secret As String = _accesstokencache.AppSecret

Dim grant_type As String = "client_credential"

 

Dim url As String = String.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type={0}&appid={1}&secret={2}", grant_type, appid, secret)

Dim json As String = Functions.Execute("HttpGet",url)

Return ConvertHelper.FromJson(Of AccessTokenData)(json)

 

为了实现缓存,定义另外一个获取AccessToken的函数:GetAccessToken

 

\'如果没有过期

If Not _accesstokencache.IsExpire Then

    Functions.Execute("LogText","请求AccessToken:" & _accesstokencache.AccessTokenData.access_token)

    Return  _accesstokencache.AccessTokenData

End If

 

\'过期就获取新的AccessToken

Dim accesstoken = Functions.Execute("GetNewAccessToken")

\'第一次请求不成功,重复一次

If accesstoken.errcode <> ReturnCode.请求成功 Then

    accesstoken = Functions.Execute("GetNewAccessToken")

End If

If accesstoken.errcode = ReturnCode.请求成功 Then

    _accesstokencache.AccessTokenData = accesstoken

End If

Functions.Execute("LogText","请求AccessToken:" & accesstoken.access_token)

Return accesstoken

 

[此贴子已经被作者于2015/11/25 11:01:42编辑过]

--  作者:Hyphen
--  发布时间:2015/11/25 10:20:00
--  继续

d、定义创建微信接口菜单Json数据包函数:GetMenuJson

菜单并不复杂,先取一级菜单,判断有子菜单,则增加子菜单,并返回一个完整的菜单对象,然后序列化为Json字符串。当然各位看客有兴趣的话也可以自己写代码拼凑json字符串,就不用定义全局代码中那么多实体类了,。

 

Dim drs As List(Of DataRow)

drs = DataTables("微信公众号菜单").Sel ect("是否停用 = 0 and (所属一级菜单 is null or 所属一级菜单 = \'\')","排序")

If drs.Count > 0 Then

    Dim menu As new MmsgMenuGroup

    Dim button As object

    Dim submenudrs As List(Of DataRow)

    Dim subbutton As MmsgMenuBase

    For i As Integer = 0 To drs.Count -1

        submenudrs = DataTables("微信公众号菜单").Sel ect("是否停用 = 0 and 所属一级菜单 = \'" & drs(i)("name") & "\'","排序")

        If submenudrs.Count > 0 Then

            button = new MmsgSubMenuButton

            button.name = drs(i)("name")

            For j As Integer = 0 To submenudrs.Count -1

                subbutton = Functions.Execute("GetMmsgMenu",submenudrs(j)("type"))

                ConvertHelper.FromDataRow(submenudrs(j),subbutton )

                button.sub_button.Add(subbutton)

            Next

        Else

            button = MmsgMenuHelper.GetMmsgMenu(drs(i)("type"))

            ConvertHelper.FromDataRow(drs(i),button)

        End If

        menu.button.Add(button )

    Next

    Return ConvertHelper.ToJson(menu)

End If

Return Nothing

 

 

3.4 创建微信菜单

 

好了,万事具备,只欠东风。如果已经定义好了菜单,那么可以调用一下GetMenuJson函数,看Json数据是否正常。打开命令窗口,输入:Output.Show(Functions.Execute("GetMenuJson")),执行,返回值如下:

 

{"button":[{"sub_button":[{"url":"http://www.foxtable.com/","type":"view","name":"什么是Foxtable"},{"url":"https://shop115579086.taobao.com/index.htm","type":"view","name":"官方淘宝"},{"url":"http://www.foxtable.com/buy.html","type":"view","name":"报价"}],"name":"Foxtable"},{"sub_button":[{"url":"http://www.foxtable.com/help/topics/1508.htm","type":"view","name":"帮助"}],"name":"查询"},{"sub_button":[{"key":"cus_account","type":"click","name":"个人中心"},{"key":"online_call","type":"click","name":"在线客服"}],"name":"服务"}]}

 

这里只测试了clickview类型菜单,其它类型没有测试,等后面的篇章再做实践。

 

打开“微信公众号菜单设置窗口”,在按钮事件中编辑代码:

If MessageBox.Show("提交后会覆盖原有的菜单,确定要提交?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = DialogResult.Yes Then   

    Dim menujsonstring As String = Functions.Execute("GetMenuJson") \' 获取菜单Json

    Dim accesstoken = Functions.Execute("GetAccessToken") \'获取AccessToken

    \'定义接口url

    Dim url = String.Format("https://api.weixin.qq.com/cgi-bin/menu/create?access_token={0}", accesstoken.access_token)

    \'把菜单Json序列化到内存流

    Using ms As New System.IO.MemoryStream()

    Dim bytes = ConvertHelper.EncodingToBytes(menujsonstring, System.Text.Encoding.UTF8)

    ms.Write(bytes, 0, bytes.Length)

    ms.Seek(0, System.IO.SeekOrigin.Begin)

    Dim jsonString = Functions.Execute("HttpPost",url, ms) \'通过POST向接口传输菜单数据,并取得返回结果

    Dim mresult = ConvertHelper.FromJson(Of MmsgResult)(jsonString)

    If mresult.errcode = ReturnCode.请求成功 Then

        Functions.Execute("LogText","成功创建菜单!")

        msgbox("成功创建菜单!")

    Else

        Functions.Execute("LogText","创建菜单失败!错误代码: " & mresult.errcode & ";错误原因:" & mresult.errmsg)

        msgbox("创建菜单失败!错误代码: " & mresult.errcode & ";错误原因:" & mresult.errmsg)

    End If

End Using

End If

到这里,我们就完成实现了在Foxtable项目中通过微信接口创建公众号菜单的功能,关键点在于接口的调用和菜单Json的生成。

[此贴子已经被作者于2015/11/25 10:36:25编辑过]

--  作者:aduydgd
--  发布时间:2015/11/25 10:41:00
--  
好,不错,
--  作者:李睿涵
--  发布时间:2015/11/25 10:43:00
--  
好是好,看不懂。
--  作者:133198609
--  发布时间:2015/11/25 10:47:00
--  
这个,也太强大了吧
--  作者:blackzhu
--  发布时间:2015/11/25 10:49:00
--  
看不懂 太好了就不行了 为啥你创建表的界面跟我们不一样?
--  作者:狐狸爸爸
--  发布时间:2015/11/25 10:52:00
--  
以下是引用blackzhu在2015/11/25 10:49:00的发言:
看不懂 太好了就不行了 为啥你创建表的界面跟我们不一样?

 

最终我们会提供完整的方案和实例。

如果你有公众号,照着做就行了。

[此贴子已经被作者于2015/11/25 10:53:45编辑过]

--  作者:blackzhu
--  发布时间:2015/11/25 10:55:00
--  
等待中