创建自定义菜单首先要满足公众号获得创建自定义菜单的权限才可以。微信公众号开发本质还是提交一组菜单数据给微信公众号服务器,服务器拿到数据后保存到他们的服务器上,我们的公众号客户端进入公众号,公众号就会自动拉取微信公众号服务器上的菜单数据并解析成相应的菜单,基本原理和思路非常简单
一、先看我们的Controller,这里是需要向微信公众号服务器提交我们的数据,这里我们用了一个MenuController中的createMenu方法创建我们的自定义菜单,当然除了创建自定义菜单还可以查询自定义菜单等(详细请参考微信公众号开发文档)
这个方法啥也没做,就是调用了menuUtil中的一个方法
package com.xu.wemall.controller.wechat;
import com.alibaba.fastjson.JSONObject;
import com.xu.wemall.commons.utils.MenuUtil;
import com.xu.wemall.pojo.menu.Menu;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* 类名称: LoginController
* 类描述: 与微信对接登陆验证
*
* @author yuanjun
* 创建时间:2017年12月5日上午10:52:13
*/
@Slf4j
@RestController
@Api(tags = "微信菜单接口")
@RequestMapping(value = "/menu")
public class MenuController {
@Autowired
private MenuUtil menuUtil;
@ApiOperation(value = "创建自定义菜单")
@RequestMapping(value = "/createMenu", method = RequestMethod.POST)
public Object createMenu() {
JSONObject jsonObject = menuUtil.creatMenu();
return jsonObject;
}
@ApiOperation(value = "查询自定义菜单")
@RequestMapping(value = "/getMenu", method = RequestMethod.GET)
public Object getMenu() {
JSONObject jsonObject = menuUtil.getWXMenu();
return jsonObject;
}
}
二、自定义菜单MenuUtil类
这个类主要是做了两个操作,一个是组装我们的菜单结构,一个是调用接口请求微信公众号服务器
package com.xu.wemall.commons.utils;
import com.alibaba.fastjson.JSONObject;
import com.xu.wemall.commons.constants.URIConstant;
import com.xu.wemall.components.accessToken.AccessTokenUtil;
import com.xu.wemall.pojo.menu.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Slf4j
@Component
public class MenuUtil {
@Autowired
private RestTemplate restTemplate;
@Autowired
private AccessTokenUtil accessTokenUtil;
public Menu createMenuString(){
ViewButton btn11 = new ViewButton();
btn11.setName("百度一下");
btn11.setType("view");
btn11.setUrl("https://www.baidu.com/");
ViewButton btn12 = new ViewButton();
btn12.setName("小米官网");
btn12.setType("view");
btn12.setUrl("https://www.mi.com/");
ViewButton btn13 = new ViewButton();
btn13.setName("聊天室");
btn13.setType("view");
btn13.setUrl("http://r7fkc2.natappfree.cc/template/chat_room");
ViewButton btn14 = new ViewButton();
btn14.setName("今日头条");
btn14.setType("view");
btn14.setUrl("https://www.toutiao.com/");
ClickButton btn21 = new ClickButton();
btn21.setName("歌曲点播");
btn21.setType("click");
btn21.setKey("21");
ClickButton btn22 = new ClickButton();
btn22.setName("经典游戏");
btn22.setType("click");
btn22.setKey("22");
ClickButton btn23 = new ClickButton();
btn23.setName("美女电台");
btn23.setType("click");
btn23.setKey("23");
ClickButton btn24 = new ClickButton();
btn24.setName("人脸识别");
btn24.setType("click");
btn24.setKey("24");
ClickButton btn25 = new ClickButton();
btn25.setName("聊天唠嗑");
btn25.setType("click");
btn25.setKey("25");
ClickButton btn31 = new ClickButton();
btn31.setName("Q友圈");
btn31.setType("click");
btn31.setKey("31");
ClickButton btn32 = new ClickButton();
btn32.setName("电影排行榜");
btn32.setType("click");
btn32.setKey("32");
ClickButton btn33 = new ClickButton();
btn33.setName("幽默笑话");
btn33.setType("click");
btn33.setKey("33");
ComplexButton mainBtn1 = new ComplexButton();
mainBtn1.setName("门户官网");
mainBtn1.setSub_button(new Button[] { btn11, btn12, btn13, btn14 });
ComplexButton mainBtn2 = new ComplexButton();
mainBtn2.setName("休闲驿站");
mainBtn2.setSub_button(new Button[] { btn21, btn22, btn23, btn24, btn25 });
ComplexButton mainBtn3 = new ComplexButton();
mainBtn3.setName("更多体验");
mainBtn3.setSub_button(new Button[] { btn31, btn32, btn33 });
Menu menu = new Menu();
menu.setButton(new Button[] { mainBtn1, mainBtn2, mainBtn3 });
return menu;
}
public JSONObject creatMenu(){
Menu menu = this.createMenuString();
String accessToken = accessTokenUtil.getAccessToken();
if(accessToken != null){
log.info("URL{}",URIConstant.MENU_CREATE_URL);
String url = URIConstant.MENU_CREATE_URL.replace("ACCESS_TOKEN", accessToken);
log.info("URL_ACCESS_TOKEN:{}",url);
//将菜单对象转换成JSON字符串
String jsonMenu = JSONObject.toJSONString(menu);
log.info("JSONMENU:{}",jsonMenu);
//发起POST请求创建菜单
JSONObject jsonObject = restTemplate.postForObject(url, jsonMenu, JSONObject.class);
return jsonObject;
}
return null;
}
/**
*
* @Description: 查询菜单
* @Parameters:
* @Return:
* @Create Date: 2018年3月13日下午2:24:02
* @Version: V1.00
* @author: 来日可期
*/
public JSONObject getWXMenu(){
String accessToken = accessTokenUtil.getAccessToken();
String requestUrl = URIConstant.MENU_GET_URL.replace("ACCESS_TOKEN", accessToken);
//发起GET请求查询菜单
JSONObject jsonObject = restTemplate.getForObject(requestUrl, JSONObject.class);
return jsonObject;
}
/**
*
* @Description: 删除菜单
* @Parameters:
* @Return:
* @Create Date: 2018年3月13日下午2:31:15
* @Version: V1.00
* @author: 来日可期
*/
public JSONObject deleteMenu(String accessToken){
String requestUrl = URIConstant.MENU_DELETE_URL.replace("ACCESS_TOKEN", accessToken);
//发起GET请求删除菜单
JSONObject jsonObject = restTemplate.getForObject(requestUrl, JSONObject.class);
// if (null != jsonObject) {
// int errorCode = jsonObject.getIntValue("errcode");
// String errorMsg = jsonObject.getString("errmsg");
// if (0== errorCode) {
// result = true;
// } else {
// result = false;
// log.error("创建菜单失败 errcode:{} errmsg:{} ",errorCode,errorMsg);
// }
// }
return jsonObject;
}
}
package com.xu.wemall.pojo.menu;
import lombok.*;
/**
* @Description: 按钮的基类(每个按钮对象都有一个共同的name属性,
* 因此需要定义一个按钮对象的基类,所有按钮对象都需要继承该类)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Button {
private String name;
}
package com.xu.wemall.pojo.menu;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class ClickButton extends Button{
private String type;
private String key;
}
package com.xu.wemall.pojo.menu;
import lombok.*;
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ViewButton extends Button{
private String type;
private String url;
}
package com.xu.wemall.pojo.menu;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Menu {
private Button[] button;
}
三、跟之前一样,在请求接口之前我们需要获得我们的token,这个接口需要附带token值才能有效
请求创建自定义菜单的接口是https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
,ACCESS_TOKEN替换成你自己获得的token即可
/**
* 自定义菜单的创建接口
*/
public static final String MENU_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
我们这里用到了redis用来缓存token,因为微信公众号的token每天有访问次数限制和失效期,我们把把token获取到之后缓存到
redis中(设置一个接近失效期的时间),如果获取不到就重新请求获取access_token接口重新获取
package com.xu.wemall.components.accessToken;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.xu.wemall.commons.constants.AcountConstant;
import com.xu.wemall.commons.constants.TestConstant;
import com.xu.wemall.commons.constants.URIConstant;
import com.xu.wemall.commons.constants.XueConstant;
import com.xu.wemall.components.redis.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
/**
* 获取access_token,注意他的有效期只有7200秒(2小时),而且获取access_token次数有限
*/
@Slf4j
@Component
public class AccessTokenUtil {
@Autowired
private RestTemplate restTemplate;
@Autowired
private RedisUtil redisUtil;
private final String TOKEN_KEY = "access_token";
private final String TOKEN_TIME = "token_time";
public String getAccessToken() {
//先从redis中获取access_token
String accessToken = (String) redisUtil.get(TOKEN_KEY);
if (accessToken != null) {
log.info("原来的access_token还有效存在!,有效时间是:{}秒, 产生的时间是:{}"
,redisUtil.getExpire(TOKEN_KEY)
,redisUtil.get(TOKEN_TIME)
);
return accessToken;
} else {
//否则就重新开始获取access_token
try {
String url = URIConstant.ACCESS_TOKEN_URL;
Map<String, String> params = new HashMap<>();
//这里设置开发者appid和secret!!!
params.put("appid", XueConstant.APPID);
params.put("secret", XueConstant.APPSECRET);
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime time = LocalDateTime.now();
String localTime = df.format(time);
JSONObject jsonObject = restTemplate.getForObject(url, JSONObject.class, params);
if (null != jsonObject) {
log.info("调用接口获取新的access_token!,时间是:{}",localTime);
String access_token = jsonObject.getString(TOKEN_KEY);
int expires_in = jsonObject.getIntValue("expires_in");
if (access_token != null) {
log.error("获得access_token成功:{} ", jsonObject.toJSONString());
redisUtil.set(TOKEN_KEY, access_token, 7199);//保存2小时(官方是2小时失效)
redisUtil.set(TOKEN_TIME, localTime);//保存时间
return access_token;
} else {
int errorCode = jsonObject.getIntValue("errcode");
String errorMsg = jsonObject.getString("errmsg");
log.error("获得access_token失败:{} ", jsonObject.toJSONString());
}
}
} catch (RestClientException e) {
log.error("请根据提示信息设置接口的白名单IP:", e.getMessage());
} catch (JSONException e) {
log.error("字符串转换异常:", e.getMessage());
}
return null;
}
}
}
四、在swagger中请求我们的创建自定义菜单请求,即可生成我们自己的自定义菜单,上面是我的代码,生成的菜单截图如下:
说明:自定义菜单如果是多次请求生成新的菜单,新的菜单需要用户重新退出公众号再次进入后才能自动刷新,如果还是没有刷新可以取消关注公众号重新关注该公众号。