10万+IT人都在关注,史上最全面的微信开发实战教程:包含公众号,小程序,微信支付等开发案例
欢迎关注笔者个人博客:http://blogs.chenyunkeji.com/
首先,直接上图,看效果,如下,有三个根菜单,每个菜单上有不同类型的子菜单,点击子菜单可以实现用户和公众号实时交互
本案例技术栈:springboot,mysql,logback,mybatis
菜单创建请求接口:https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
首先是菜单项(按钮)的基类,所有一级菜单、二级菜单都有一个相同的属性,那就是name。菜单项基类的封装代码如下:
public class Button {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
接着是子菜单项的封装。这里对子菜单是这样定义的:底部根菜单的二级菜单。这类子菜单项一定会包含三个属性:type、name和key,封装的代码如下
public class CommandButton extends Button{
private String type;
private String key;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
接着封装父菜单项。对父菜单项的定义:包含有二级菜单项的一级菜单。这类菜单项包含有二个属性:name和sub_button,而sub_button是一个子菜单项数组。父菜单项的封装代码如下:
public class ComplexButton extends Button{
private Button[] sub_button;
public Button[] getSub_button() {
return sub_button;
}
public void setSub_button(Button[] sub_button) {
this.sub_button = sub_button;
}
}
最后是整个菜单对象的封装,菜单对象包含多个根菜单项(最多只能有3个),这些菜单项即可以是子菜单项(不含二级菜单的一级菜单),也可以是父菜单项(包含二级菜单的菜单项),如果能明白上面所讲的,再来看封装后的代码就很容易理解了:
public class Menu {
private Button[] button;
public Button[] getButton() {
return button;
}
public void setButton(Button[] button) {
this.button = button;
}
}
最后再封装一个链接类型的菜单,根据此处的名字也很好理解,不再赘述
public class ViewButton extends Button{
private String type;
private String url;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
关于菜单实体类以及菜单对象的封装就介绍完了,下面根据微信接口创建自定义菜单。
一、封装菜单创建工具类
public class WeiXinMenuUtil {
private static String menu_create_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN"; private static Logger log = LoggerFactory.getLogger(WeiXinMenuUtil.class);
public static int createMenu(Menu menu, String accessToken) {
int result = 0;
// 拼接创建菜单的url
String url = menu_create_url .replace("ACCESS_TOKEN", accessToken);
// 将菜单对象转换成json字符串
String jsonMenu = JSON.toJSONString(menu);
// 调用接口创建菜单
JSONObject jsonObject = WeiXinUtil.httpPost(url, "POST", jsonMenu);
if (null != jsonObject) {
if (0 != (Integer)jsonObject.get("errcode")) {
result = (Integer)jsonObject.get("errcode");
log.error("创建菜单失败 errcode:{} errmsg:{}", jsonObject.get("errcode"), jsonObject.getString("errmsg"));
}
}
return result;
}
public static Menu initMenu(){
//1.创建菜单
Menu menu = new Menu();
// 主菜单1
ComplexButton cb1 = new ComplexButton();
cb1.setName("技术干货");
// 主菜单2
ComplexButton cb2 = new ComplexButton();
cb2.setName("交流合作");
// 主菜单3
ComplexButton cb3 = new ComplexButton();
cb3.setName("演示功能");
// 主菜单1下面的子菜单1
ViewButton cb01 = new ViewButton();
cb01.setName("微服务教程连载");
cb01.setType("view");
cb01.setUrl("https://blog.csdn.net/guobinhui/article/category/8739270");
// 主菜单1下面的子菜单2
ViewButton cb02 = new ViewButton();
cb02.setName("公众号开发教程");
cb02.setType("view");
cb02.setUrl("https://blog.csdn.net/guobinhui/article/category/8534361");
ViewButton cb03 = new ViewButton();
cb03.setName("小程序开发教程");
cb03.setType("view");
cb03.setUrl("https://blog.csdn.net/guobinhui/article/category/7763266");
ViewButton cb04 = new ViewButton();
cb04.setName("JavaEE基础");
cb04.setType("view");
cb04.setUrl("https://blog.csdn.net/guobinhui");
ViewButton cb05 = new ViewButton();
cb05.setName("笔者博客");
cb05.setType("view");
cb05.setUrl("http://blogs.chenyunkeji.com/");
cb1.setSub_button(new ViewButton[]{cb01,cb02,cb03,cb04,cb05});
// 主菜单2下面的子菜单1
CommandButton cb11 = new CommandButton();
cb11.setType("click");
cb11.setKey("联系我");
cb11.setName("联系笔者");
CommandButton cb12 = new CommandButton();
cb12.setName("技术交流");
cb12.setType("click");
cb12.setKey("18629374628");
cb2.setSub_button(new CommandButton[]{cb11,cb12});
CommandButton cb21 = new CommandButton();
cb21.setType("scancode_waitmsg");
cb21.setKey("rselfmenu_0_0");
cb21.setName("扫码带提示");
CommandButton cb22 = new CommandButton();
cb22.setType("pic_sysphoto");
cb22.setKey("rselfmenu_1_0");
cb22.setName("系统拍照发图");
CommandButton cb23 = new CommandButton();
cb23.setType("pic_photo_or_album");
cb23.setKey("rselfmenu_1_1");
cb23.setName("拍照或者相册发图");
CommandButton cb24 = new CommandButton();
cb24.setType("pic_weixin");
cb24.setKey("rselfmenu_1_2");
cb24.setName("微信相册发图");
CommandButton cb25 = new CommandButton();
cb25.setType("location_select");
cb25.setKey("rselfmenu_2_0");
cb25.setName("发送地理位置");
cb3.setSub_button(new CommandButton[]{cb21, cb22,cb23, cb24,cb25});
menu.setButton(new Button[] { cb1,cb2, cb3});
return menu;
}
}
二、微信接口凭证access_token的获取以及缓存实现
access_token可以用各种缓存插件或者线程,定时任务,写入数据库等多种方式对access_token进行缓存7200秒,开发者可以依据自己的喜好选择其一,本案例策略采用IO流定时写入文件的方法缓存。首先获取access_token:
public static JSONObject getToken()throws IOException{
private String GET_ACCESS_TOKEN= "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
String url = GET_ACCESS_TOKEN.replace("APPID",WeixinConstant.APPID).replace("APPSECRET",WeixinConstant.APPSECRET);
JSONObject tokenObj = HttpGet(url);
return tokenObj;
}
接着缓存access_token,也就是每隔7200秒生成一次access_token,生成的缓存7200秒,这样其余用到access_token的从文件中读取的就是最新的,不会过期。
public static Map cacheToken() throws IOException {
Gson gson = new Gson();
Map map = new HashMap ();
String token = null;
JSONObject tokenObj = null; //需要获取的access_token对象;
String filePath = System.getProperty("user.dir")+"/src/main/resources/static/token.txt";
File file = new File(filePath);//Access_token保存的位置
if (!file.exists())
file.createNewFile();
// 如果文件大小等于0,说明第一次使用,存入Access_token
if (file.length() == 0) {
tokenObj = WeiXinUtil.getToken();
token = (String)tokenObj.get("access_token");
FileOutputStream fos = new FileOutputStream(filePath, false);// 不允许追加
tokenObj.put("expires_in",System.currentTimeMillis()/1000+"");
String json = gson.toJson(tokenObj);
fos.write(json.getBytes());
fos.close();
}else {
//读取文件内容
@SuppressWarnings("resource")
FileInputStream fis = new FileInputStream(file);
byte[] b = new byte[2048];
int len = fis.read(b);
String jsonAccess_token = new String(b, 0, len);// 读取到的文件内容
JSONObject access_token = gson.fromJson(jsonAccess_token,JSONObject.class);
if (access_token.get("expires_in") != null) {
String lastSaveTime = (String)access_token.get("expires_in");
long nowTime = System.currentTimeMillis()/1000;
long remianTime = nowTime - Long.valueOf(lastSaveTime);
if (remianTime < WeixinConstant.EXPIRESIN_TIME) {
JSONObject access = gson.fromJson(jsonAccess_token,JSONObject.class);
token = (String)access.get("access_token");
} else {
tokenObj = WeiXinUtil.getToken();
FileOutputStream fos = new FileOutputStream(file, false);// 不允许追加
tokenObj.put("expires_in",System.currentTimeMillis()/1000+"");
String json = gson.toJson(tokenObj);
fos.write((json).getBytes());
fos.close();
}
}
}
map.put("access_token",token);
return map;
}
最后在项目的启动入口文件的main方法调用菜单创建方法进行初始化,那么项目在启动的时候,公众号的菜单就进行了初始化,用户就能看到公众号的菜单。
@SpringBootApplication
@MapperScan(basePackages = "com.chenyun.cloud.dao")
public class HelloServiceApplication {
private final static Logger logger= LoggerFactory.getLogger(HelloServiceApplication.class);
public static void main(String[] args) {
SpringApplication.run(HelloServiceApplication.class, args);
Map map;
try {
map = WeiXinUtil.cacheToken();
String accessToken = (String)map.get("access_token");
logger.info("accessToken:"+accessToken);
Menu menu = WeiXinMenuUtil.initMenu();
System.out.println(JSON.toJSONString(menu));
int result = WeiXinMenuUtil.createMenu(menu,accessToken);
if (0 == result){
logger.info("菜单创建成功!");
}else{
logger.info("菜单创建失败,错误码:" + result);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
启动项目后,打印的JSON格式的日志打就显示了创建的菜单结构。
下节内容为大家分享公众号菜单栏的各种点击事件开发案例,更多JavaEE资料请关注下面公众号,欢迎广大开发者朋友一起交流。更多微信公众号功能演示请扫码体验,笔者电话(微信):18629374628