阅读文档

自定义菜单窗口创建文档——传送门

注意:一级菜单不能超过3个。所以文档第二个例子要删掉几个一级菜单才可用。

  1. 自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
  2. 一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“…”代替。
  3. 创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。

按钮json格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"button":[
{
"type":"click",
"name":"今日歌曲",
"key":"V1001_TODAY_MUSIC"
},
{
"name":"菜单",
"sub_button":[
{
"type":"view",
"name":"搜索",
"url":"http://www.soso.com/"
},
{
"type":"miniprogram",
"name":"wxa",
"url":"http://mp.weixin.qq.com",
"appid":"wx286b93c14bbf93aa",
"pagepath":"pages/lunar/index"
},
{
"type":"click",
"name":"赞一下我们",
"key":"V1001_GOOD"
}]
}]
}

参数 是否必须 说明
button 一级菜单数组,个数应为1~3个
sub_button 二级菜单数组,个数应为1~5个
type 菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型
name 菜单标题,不超过16个字节,子菜单不超过60个字节
key click等点击类型必须 菜单KEY值,用于消息接口推送,不超过128字节
url view、miniprogram类型必须 网页链接,用户点击菜单可打开链接,不超过1024字节。type为miniprogram时,不支持小程序的老版本客户端将打开本url。
media_id media_id类型和view_limited类型必须 调用新增永久素材接口返回的合法media_id
appid miniprogram类型必须 小程序的appid(仅认证公众号可配置)
pagepath miniprogram类型必须 小程序的页面路径

type类型:

1、click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互。
2、view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。
3、scancode_push:扫码推事件用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。
4、scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。
5、pic_sysphoto:弹出系统拍照发图用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。
6、pic_photo_or_album:弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。
7、pic_weixin:弹出微信相册发图器用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。
8、location_select:弹出地理位置选择器用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。
9、media_id:下发消息(除文本消息)用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
10、view_limited:跳转图文消息URL用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。

代码实现

配置文件参照 消息加解密 —— 微信公众平台开发
代码:使用方法 先在微信公众平台添加本地ip的白名单,然后本地访问localhost/tp/index.php/Wx/Admin/createMenu方法即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?php
namespace Wx\Controller;
use Think\Controller;
class AdminController extends Controller {
private function getAccessToken()
{
$appid = C('APPID');
$appsecret = C('APPSECRET');
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$appsecret";

$output = $this->https_request($url);
$jsoninfo = json_decode($output, true);

return $jsoninfo["access_token"];
}

public function createMenu()
{
$access_token = $this->getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=".$access_token;
$jsonmenu = $this->getMenu();
$result = $this->https_request($url, $jsonmenu);
var_dump($url);
var_dump($result);
}

private function https_request($url,$data = null){
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
if (!empty($data)){
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($curl);
curl_close($curl);
return $output;
}


private function getMenu()
{
$jsonmenu = '{
"button":[
{
"type":"click",
"name":"今日歌曲",
"key":"V1001_TODAY_MUSIC"
},
{
"name":"菜单",
"sub_button":[
{
"type":"view",
"name":"搜索",
"url":"http://www.soso.com/"
},
{
"type":"miniprogram",
"name":"wxa",
"url":"http://mp.weixin.qq.com",
"appid":"wx286b93c14bbf93aa",
"pagepath":"pages/lunar/index"
},
{
"type":"click",
"name":"赞一下我们",
"key":"V1001_GOOD"
}]
}]
}';

return $jsonmenu;
}
}

服务器端处理按钮事件的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<?php
namespace Wx\Controller;
use Wx\Controller\AppBaseController;
use Think\Log;

class IndexController extends Controller {

public function index() {
$echostr = I("get.echostr");
if (empty($echostr)) {
$this->getWxCrypt(C('NEWENCODINGAESKEY'));
$this->responseMsg();
}else{
$this->valid();
}
}

//验证签名
private function valid()
{
$echoStr = I("get.echostr");
$signature = I("get.signature");
$timestamp = I("get.timestamp");
$nonce = I("get.nonce");
$tmpArr = array(C('TOKEN'), $timestamp, $nonce);
sort($tmpArr, SORT_STRING);
$tmpStr = implode($tmpArr);
$tmpStr = sha1($tmpStr);
if($tmpStr == $signature){
echo $echoStr;
exit;
}
}

//响应消息
private function responseMsg()
{
$postData = $GLOBALS["HTTP_RAW_POST_DATA"];
// $postData = file_get_contents("php://input");

// 第三方收到公众号平台发送的消息
$msg = '';
$errCode = $this->wxCrypt->decryptMsg(I("get.msg_signature"), I("get.timestamp"), I("get.nonce"), $postData, $msg);
if ($errCode != 0) {
//尝试之前的encodingAESKey解密
$this->getWxCrypt(C('OLDENCODINGAESKEY'));
$errCode = $this->wxCrypt->decryptMsg(I("get.msg_signature"), I("get.timestamp"), I("get.nonce"), $postData, $msg);
if ($errCode != 0) {
//解密失败
print("解密失败: ".$errCode . "\n");
exit();
}
}


if (!empty($msg)){
$postObj = simplexml_load_string($msg, 'SimpleXMLElement', LIBXML_NOCDATA);
$MsgType = trim($postObj->MsgType);


//消息类型分离
switch ($MsgType)
{
case "text":
$result = $this->receiveText($postObj);
break;
case "event":
$result = $this->receiveEvent($postObj);
break;
default:
$result = "unknown msg type: ".$MsgType;
break;
}
// $this->logger("T \r\n".$result);
echo $result;
}else {
echo "";
exit;
}
}


//接收文本消息
private function receiveText($object)
{
$keyword = trim($object->Content);


$content = '这是文本消息';
$result = $this->transmitText($object, $content);
return $result;
}

//接收事件消息
private function receiveEvent($object)
{
switch ($object->Event)
{
case "subscribe":
$content = "welcome~\n欢迎关注hojun微信公众号!";
break;
case "CLICK":
switch ($object->EventKey)
{
case "V1001_TODAY_MUSIC":
$content = "点击了今日歌曲";
break;
case "V1001_GOOD":
$content = "点击了赞一下我们";
break;
default:
$content = "点击菜单:".$object->EventKey;
break;
}
break;
default:
$content = "receive a new event: ".$object->Event;
break;
}

if (is_array($content)){
// $result = $this->transmitNews($object, $content);
}else{
$result = $this->transmitText($object, $content);
}
return $result;
}


//回复文本消息
private function transmitText($object, $content)
{
if (!isset($content) || empty($content)){
return "";
}

$xmlTpl = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[%s]]></Content></xml>";
$result = sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time(), $content);

$encryptMsg = '';
$errCode = $this->wxCrypt->encryptMsg($result, time(), $object->nonce, $encryptMsg);
if ($errCode != 0) {
print("加密后: " . $encryptMsg . "\n");
}

return $encryptMsg;
}

//获取WxCrypt对象
private function getWxCrypt($encodingAESKey) {
import('Vendor.Weixin.WXBizMsgCrypt');
$this->wxCrypt = new \WXBizMsgCrypt(C('TOKEN'), $encodingAESKey, C('APPID'));
}

}

希望这篇文章能给你带来知识和乐趣,喜欢博主的文章可以加博主好友哦

有好的文章也可以向博主投稿哦