java微信开发API第四步 微信自定义个性化菜单实现

时间:2022-06-17 10:15:02

微信如何实现自定义个性化菜单,下面为大家介绍

一、全局说明
详细说明请参考前两篇文章。

二、本文说明
本文分为五部分:
    * 工具类AccessTokenUtils的封装
    * 自定义菜单和个性化菜单文档的阅读解析
    * 菜单JSON的分析以及构建对应bean
    * 自定义菜单的实现
    * 个性化菜单的实现
微信自定义菜单所有类型菜单都给出演示
本文结束会给出包括本文前四篇文章的所有演示源码

工具类AccessTokenUtils的封装
在上文中关于AccessToken的获取和定时保存已经详细介绍过,此处直接给出处理过之后封装的AccessTokenUtils,实现原理以及文档阅读不再给出。
AccessTokenUtils.java

?
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
package com.gist.utils;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
 
import javax.net.ssl.HttpsURLConnection;
 
import com.gist.bean.Access_token;
import com.google.gson.Gson;
 
/**
 * @author 高远</n> 邮箱:wgyscsf@163.com</n> 博客 http://blog.csdn.net/wgyscsf</n>
 *  编写时期 2016-4-7 下午5:44:33
 */
public class AccessTokenUtils {
 private static final long MAX_TIME = 7200 * 1000;// 微信允许最长Access_token有效时间(ms)
 private static final String TAG = "WeixinApiTest";// TAG
 private static final String APPID = "wx889b020b3666b0b8";// APPID
 private static final String SECERT = "6da7676bf394f0a9f15fbf06027856bb";// 秘钥
 
 /*
 * 该方法实现获取Access_token、保存并且只保存2小时Access_token。如果超过两个小时重新获取;如果没有超过两个小时,直接获取。该方法依赖
 * :public static String getAccessToken();
 *
 * 思路:将获取到的Access_token和当前时间存储到file里,
 * 取出时判断当前时间和存储里面的记录的时间的时间差,如果大于MAX_TIME,重新获取,并且将获取到的存储到file替换原来的内容
 * ,如果小于MAX_TIME,直接获取。
 */
 // 为了调用不抛异常,这里全部捕捉异常,代码有点长
 public static String getSavedAccess_token() {
 Gson gson = new Gson();// 第三方jar,处理json和bean的转换
 String mAccess_token = null;// 需要获取的Access_token;
 FileOutputStream fos = null;// 输出流
 FileInputStream fis = null;// 输入流
 File file = new File("temp_access_token.temp");// Access_token保存的位置
 try {
  // 如果文件不存在,创建
  if (!file.exists()) {
  file.createNewFile();
  }
 } catch (Exception e1) {
  e1.printStackTrace();
 }
 // 如果文件大小等于0,说明第一次使用,存入Access_token
 if (file.length() == 0) {
  try {
  mAccess_token = getAccessToken();// 获取AccessToken
  Access_token at = new Access_token();
  at.setAccess_token(mAccess_token);
  at.setExpires_in(System.currentTimeMillis() + "");// 设置存入时间
  String json = gson.toJson(at);
  fos = new FileOutputStream(file, false);// 不允许追加
  fos.write((json).getBytes());// 将AccessToken和当前时间存入文件
  fos.close();
  return mAccess_token;
  } catch (Exception e) {
  e.printStackTrace();
  }
 } else {
  // 读取文件内容
  byte[] b = new byte[2048];
  int len = 0;
  try {
  fis = new FileInputStream(file);
  len = fis.read(b);
  } catch (IOException e1) {
  // TODO Auto-generated catch block
  e1.printStackTrace();
  }
  String mJsonAccess_token = new String(b, 0, len);// 读取到的文件内容
  Access_token access_token = gson.fromJson(mJsonAccess_token,
   new Access_token().getClass());
  if (access_token.getExpires_in() != null) {
  long saveTime = Long.parseLong(access_token.getExpires_in());
  long nowTime = System.currentTimeMillis();
  long remianTime = nowTime - saveTime;
  // System.out.println(TAG + "时间差:" + remianTime + "ms");
  if (remianTime < MAX_TIME) {
   Access_token at = gson.fromJson(mJsonAccess_token,
    new Access_token().getClass());
   mAccess_token = at.getAccess_token();
   return mAccess_token;
  } else {
   mAccess_token = getAccessToken();
   Access_token at = new Access_token();
   at.setAccess_token(mAccess_token);
   at.setExpires_in(System.currentTimeMillis() + "");
   String json = gson.toJson(at);
   try {
   fos = new FileOutputStream(file, false);// 不允许追加
   fos.write((json).getBytes());
   fos.close();
   } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   }
   return mAccess_token;
  }
 
  } else {
  return null;
  }
 }
 
 return mAccess_token;
 }
 
 /*
 * 获取微信服务器AccessToken。该部分和getAccess_token() 一致,不再加注释
 */
 public static String getAccessToken() {
 String urlString = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
  + APPID + "&secret=" + SECERT;
 String reslut = null;
 try {
  URL reqURL = new URL(urlString);
  HttpsURLConnection httpsConn = (HttpsURLConnection) reqURL
   .openConnection();
  InputStreamReader isr = new InputStreamReader(
   httpsConn.getInputStream());
  char[] chars = new char[1024];
  reslut = "";
  int len;
  while ((len = isr.read(chars)) != -1) {
  reslut += new String(chars, 0, len);
  }
  isr.close();
 } catch (IOException e) {
 
  e.printStackTrace();
 }
 Gson gson = new Gson();
 Access_token access_token = gson.fromJson(reslut,
  new Access_token().getClass());
 if (access_token.getAccess_token() != null) {
  return access_token.getAccess_token();
 } else {
  return null;
 }
 }
}

自定义菜单和个性化菜单文档的阅读解析
•自定义菜单
◦自定义菜单创建接口
 ◦自定义菜单查询接口
 ◦自定义菜单删除接口
 ◦自定义菜单事件推送
 ◦个性化菜单接口
 ◦获取公众号的菜单配置

 •文档地址:http://mp.weixin.qq.com/wiki/10/0234e39a2025342c17a7d23595c6b40a.html
 •官网文档给出这样解释:
* 自定义菜单接口可实现多种类型按钮,如下:1、click:点击事件...;2、view:跳转事件...;3、...(关于自定义菜单)
* 接口调用请求说明 http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN(关于自定义菜单)
* click和view的请求示例 {"button":[...]}  (关于自定义菜单)
* 参数说明...(关于自定义菜单)
* 创建个性化菜单http请求方式:POST(请使用https协议)https://api.weixin.qq.com/cgi-bin/menu/addconditional?access_token=ACCESS_TOKEN(关于个性化菜单)
* 请求示例: {"button":[...],"matchrule":{...}}(关于个性化菜单)
* 参数说明...(关于个性化菜单)
* 开发者可以通过以下条件来设置用户看到的菜单(关于个性化菜单):
    1、用户分组(开发者的业务需求可以借助用户分组来完成)
    2、性别
    3、手机操作系统
    4、地区(用户在微信客户端设置的地区)
    5、语言(用户在微信客户端设置的语言)

 •理解:
◦又是熟悉的POST请求,但是,关于调用貌似说的含糊其辞,不太明白。只是知道我们需要使用“?access_token=ACCESS_TOKEN”这个参数,这个参数我们在上篇文章已经获取到了。假如我们将微信文档给的那个请求地址中“ACCESS_TOKEN”换成我们获取到的自己的ACCESS_TOKEN,访问该网址,会看到“{“errcode”:44002,”errmsg”:”empty post data hint: [Gdveda0984vr23]”}”。大概意思是,空的post请求数据。所以,我们要通过POST请求的形式传递参数给微信服务器,在文档下面还给出了参数的格式:{“button”:[…]},所以,我们要按照该格式给微信服务器进行传递参数。
 ◦关于参数说明,我们可以看到在自定义菜单创建中有七个参数。在个性化菜单接口中除去这七个参数之外,另外多个八个参数。简单查看此部分文档,我们可以了解到这个八个参数是为了个性化菜单做匹配筛选用的。
 ◦现在,我们需要按照微信文档的要求构造json通过post的请求向微信服务器发送这一串json数据,json里面就包括我们创建的各种类型的按钮事件。

菜单JSON的分析以及构建对应bean
 自定义菜单json分析(不包括个性化菜单)。下面这段代码是微信文档给的示例。
click和view的请求示例

?
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
{
"button":[
{
 "type":"click",
 "name":"今日歌曲",
 "key":"V1001_TODAY_MUSIC"
},
{
 "name":"菜单",
 "sub_button":[
 {
 "type":"view",
 "name":"搜索",
 "url":"http://www.soso.com/"
 },
 {
 "type":"view",
 "name":"视频",
 "url":"http://v.qq.com/"
 },
 {
 "type":"click",
 "name":"赞一下我们",
 "key":"V1001_GOOD"
 }]
}]
}

经过分析我们可以看到这串json数据分为三层:“”button”:[{…},{…}]”、“[{…},{{“name”:菜单,”sub_button”:[{},{}]}]”、“{“type”:”view”,”name:”:”视频”,”url”:”…”},{},{}”,可能看起来比较晕。
但是,如果我们能够联想起来现实中看到的微信菜单,就会好理解一点:一级:菜单(一个菜单),下包括一到三个父按钮;二级:父按钮(1~3个父按钮),下包括一到五个子按钮;三级:子按钮(1~5个子按钮)。
 现在,我们可以看到json和我们理解的“菜单”可以一一对应起来了。现在重点是如何确认每一级的“级名”,在java中也就是对应的javabean对象。
 同时,因为一级菜单下会有多个父按钮,所以是一个List<父菜单>的形式。父按钮下可能有多个子菜单,也是一个 List<子菜单>;但是,父按钮也有可能也是一个单独的可以响应的按钮。是一个单独的父按钮对象。子按钮就是一个单独的子按钮对象。
 查看关于自定义菜单的参数说明,我们可以看到按钮分为一级按钮(“button”)和二级按钮(“sub_button”)。还有一些公用的数据类型,例如:菜单响应类型(“type”)、菜单标题(“name”)、click类型的参数(“key”)、view类型的参数(“url”)、media_id类型和view_limited类型的参数(“media_id”)。
 •数据抽象(没有写setter,getter):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//按钮基类
public class BaseButton {
 private String type;
 private String name;
 private String key;
 private String url;
 private String media_id;
}
//子按钮
public class SonButton extends BaseButton {
 private String sub_button;
}
//父按钮
public class FatherButton extends BaseButton {
private String button;//可能直接一个父按钮做响应
@SerializedName("sub_button")//为了保证Gson解析后子按钮的名字是“sub_button”,具体用法请搜索
private List<SonButton> sonButtons;//可能有多个子按钮
}
 
public class Menu {
@SerializedName("button")
private List<FatherButton> fatherButtons;
}

 以上是完整的自定义菜单的分析以及对应javabean的构建。

 对于个性化菜单,如果查看该部分的文档,会发现和自定义菜单大致相同,只是多个一个“配置”的json,格式是这样的:{“button”:[…],”matchrule”:{…}}。
 我们发现,“匹配”这段json和“button”是同级的,分析和实现和上面基本等同,直接给出实现的javabean。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//匹配的json对应的json
public class MatchRule {
private String group_id;
private String sex;
private String client_platform_type;
private String country;
private String province;
private String city;
private String language;
}
 
//修改Menu.java
public class Menu {
@SerializedName("button")
private List<FatherButton> fatherButtons;
private MatchRule matchrule;
}

 自定义菜单的实现
 任务,我们实现所有微信按钮响应类型:
 任务(注释:“m-0”表示父按钮;“m-n”表示第m个父按钮,第n个子按钮(m,n≠0)):1-0:名字:click,响应点击事件:点击推事件 。2-0:名字:父按钮2。2-1:名字:view,响应事件:跳转网页;2-2:名字:scancode_push,响应事件:扫码推事件;2-3:名字:scancode_waitmsg,响应事件:扫码推事件且弹出“消息接收中”提示框;2-4:名字:pic_sysphoto,响应事件
 :弹出系统拍照发图。2-5:名字:pic_photo_or_album,响应事件:弹出拍照或者相册发图。3-0:名字:父按钮3。3-1:名字
 :pic_weixin,响应事件:弹出微信相册发图器;3-2:名字:location_select,响应事件:弹出地理位置选择器;3-3:名字:media_id,响应事件:下发消息(除文本消息);3-4:名字:view_limited,响应事件:跳转图文消息url。

实现源码(引用的AccessTokenUtils.java在第一部分:工具类AccessTokenUtils的封装)

?
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
/*
* 创建自定义菜单。
*/
@Test
public void createCommMenu() {
String ACCESS_TOKEN = AccessTokenUtils.getAccessToken();// 获取AccessToken,AccessTokenUtils是封装好的类
// 拼接api要求的httpsurl链接
String urlString = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token="
 + ACCESS_TOKEN;
try {
 // 创建一个url
 URL reqURL = new URL(urlString);
 // 拿取链接
 HttpsURLConnection httpsConn = (HttpsURLConnection) reqURL
  .openConnection();
 httpsConn.setDoOutput(true);
 // 取得该连接的输出流,以读取响应内容
 OutputStreamWriter osr = new OutputStreamWriter(
  httpsConn.getOutputStream());
 osr.write(getMenuJson());// 使用本类外部方法getMenuJson()
 osr.close();
 
 // 返回结果
 InputStreamReader isr = new InputStreamReader(
  httpsConn.getInputStream());
 // 读取服务器的响应内容并显示
 char[] chars = new char[1024];
 String reslut = "";
 int len;
 while ((len = isr.read(chars)) != -1) {
 reslut += new String(chars, 0, len);
 }
 System.out.println("返回结果:" + reslut);
 isr.close();
} catch (IOException e) {
 e.printStackTrace();
}
}
 
public String getMenuJson() {
Gson gson = new Gson();// json处理工具
 
Menu menu = new Menu();// 菜单类
List<FatherButton> fatherButtons = new ArrayList<FatherButton>();// 菜单中的父按钮集合
// -----------
// 父按钮1
FatherButton fb1 = new FatherButton();
fb1.setName("click");
fb1.setType("click");
fb1.setKey("10");
// -------------
// 父按钮2
FatherButton fb2 = new FatherButton();
fb2.setName("父按钮2");
List<SonButton> sonButtons2 = new ArrayList<SonButton>();// 子按钮的集合
 
// 子按钮2-1
SonButton sb21 = new SonButton();
sb21.setName("view");
sb21.setUrl("http://www.baidu.com");
sb21.setType("view");
// 子按钮2-2
SonButton sb22 = new SonButton();
sb22.setName("scancode_push");
sb22.setType("scancode_push");
sb22.setKey("22");
// 子按钮2-3
SonButton sb23 = new SonButton();
sb23.setName("scancode_waitmsg");
sb23.setType("scancode_waitmsg");
sb23.setKey("23");
// 子按钮2-4
SonButton sb24 = new SonButton();
sb24.setName("pic_sysphoto");
sb24.setType("pic_sysphoto");
sb24.setKey("24");
// 子按钮2-5
SonButton sb25 = new SonButton();
sb25.setName("pic_photo_or_album");
sb25.setType("pic_photo_or_album");
sb25.setKey("25");
 
// 添加子按钮到子按钮集合
sonButtons2.add(sb21);
sonButtons2.add(sb22);
sonButtons2.add(sb23);
sonButtons2.add(sb24);
sonButtons2.add(sb25);
 
// 将子按钮放到2-0父按钮集合
fb2.setSonButtons(sonButtons2);
 
// ------------------
// 父按钮3
FatherButton fb3 = new FatherButton();
fb3.setName("父按钮3");
List<SonButton> sonButtons3 = new ArrayList<SonButton>();
 
// 子按钮3-1
SonButton sb31 = new SonButton();
sb31.setName("pic_weixin");
sb31.setType("pic_weixin");
sb31.setKey("31");
// 子按钮3-2
SonButton sb32 = new SonButton();
sb32.setName("locatselect");
sb32.setType("location_select");
sb32.setKey("32");
// // 子按钮3-3-->测试不了,因为要media_id。这需要调用素材id.
// SonButton sb33 = new SonButton();
// sb33.setName("media_id");
// sb33.setType("media_id");
// sb33.setMedia_id("???");
// // 子按钮3-4-->测试不了,因为要media_id。这需要调用素材id.
// SonButton sb34 = new SonButton();
// sb34.setName("view_limited");
// sb34.setType("view_limited");
// sb34.setMedia_id("???");
 
// 添加子按钮到子按钮队列
sonButtons3.add(sb31);
sonButtons3.add(sb32);
// sonButtons3.add(sb33);
// sonButtons3.add(sb34);
 
// 将子按钮放到3-0父按钮队列
fb3.setSonButtons(sonButtons3);
// ---------------------
 
// 将父按钮加入到父按钮集合
fatherButtons.add(fb1);
fatherButtons.add(fb2);
fatherButtons.add(fb3);
 
// 将父按钮队列加入到菜单栏
menu.setFatherButtons(fatherButtons);
String json = gson.toJson(menu);
System.out.println(json);// 测试输出
return json;
 
}

 个性化菜单的实现
 •任务:根据性别展示不同的按钮显示(可以根据性别、地区、分组手机操作系统等)
 •修改代码一,因为是不同的微信后台实现,所以接口也不一样,不过还是POST请求,代码不用改,只要替换原来urlString即可。

?
1
2
3
// 拼接api要求的httpsurl链接
String urlString = "https://api.weixin.qq.com/cgi-bin/menu/addconditional?access_token="
  + ACCESS_TOKEN;

 •修改代码二,只要创建一个MatchRule,设置匹配规则,然后将matchrule加入到menu便可以完成匹配规则。

?
1
2
3
4
5
6
// -----
// 从此处开始设置个性菜单
MatchRule matchrule = new MatchRule();
matchrule.setSex("2");// 男生
menu.setMatchrule(matchrule);
// ----

源码下载:WeixinApi.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:http://blog.csdn.net/wgyscsf/article/details/51104855