微信公众平台开发(4)-自定义菜单

来源:互联网 发布:linux vi 最后一页 编辑:程序博客网 时间:2024/04/28 10:18
参考了《微信公众账号开发教程(java).doc》
文档:http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口
其实就是以POST方式调用 https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN,将菜单数据以微信公众平台定义的格式写到输出流中。
链接中的access_token从哪儿来?
参考http://mp.weixin.qq.com/wiki/index.php?title=获取access_token


不论是获取access_token,还是创建菜单,都需要使用https方式调用提供的URL。
将获取access_token,创建菜单等动作封装到一个工具类中。

如下:

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.net.MalformedURLException;import java.net.URL;import java.security.KeyManagementException;import java.security.NoSuchAlgorithmException;import java.security.NoSuchProviderException;import java.security.SecureRandom;import java.util.Calendar;import java.util.Date;import java.util.HashMap;import java.util.Map;import javax.net.ssl.HttpsURLConnection;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLSocketFactory;import javax.net.ssl.TrustManager;import net.sf.json.JSONObject;import org.apache.commons.lang.StringUtils;import org.apache.log4j.Logger;import com.company.project.model.menu.AccessToken;import com.company.project.model.menu.Menu;public class WeixinUtil {private static Logger log = Logger.getLogger(WeixinUtil.class);public final static String APPID = "****************";public final static String APP_SECRET = "************************";// 获取access_token的接口地址(GET) 限200(次/天)public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";// 创建菜单public final static String create_menu_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";// 存放:1.token,2:获取token的时间,3.过期时间public final static Map<String,Object> accessTokenMap = new HashMap<String,Object>();/** * 发起https请求并获取结果 *  * @param requestUrl 请求地址 * @param requestMethod 请求方式(GET、POST) * @param outputStr 提交的数据 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值) */public static JSONObject handleRequest(String requestUrl,String requestMethod,String outputStr) {JSONObject jsonObject = null;try {URL url = new URL(requestUrl);HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();SSLContext ctx = SSLContext.getInstance("SSL", "SunJSSE");TrustManager[] tm = {new MyX509TrustManager()};ctx.init(null, tm, new SecureRandom());SSLSocketFactory sf = ctx.getSocketFactory();conn.setSSLSocketFactory(sf);conn.setDoInput(true);conn.setDoOutput(true);conn.setRequestMethod(requestMethod);conn.setUseCaches(false);if ("GET".equalsIgnoreCase(requestMethod)) {conn.connect();}if (StringUtils.isNotEmpty(outputStr)) {OutputStream out = conn.getOutputStream();out.write(outputStr.getBytes("utf-8"));out.close();}InputStream in = conn.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(in,"utf-8"));StringBuffer buffer = new StringBuffer();String line = null;while ((line = br.readLine()) != null) {buffer.append(line);}in.close();conn.disconnect();jsonObject = JSONObject.fromObject(buffer.toString());} catch (MalformedURLException e) {e.printStackTrace();log.error("URL错误!");} catch (IOException e) {e.printStackTrace();} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (NoSuchProviderException e) {e.printStackTrace();} catch (KeyManagementException e) {e.printStackTrace();}return jsonObject;}/** * 获取access_token * * @author qincd * @date Nov 6, 2014 9:56:43 AM */public static AccessToken getAccessToken(String appid,String appSecret) {AccessToken at = new AccessToken();// 每次获取access_token时,先从accessTokenMap获取,如果过期了就重新从微信获取// 没有过期直接返回// 从微信获取的token的有效期为2个小时if (!accessTokenMap.isEmpty()) {Date getTokenTime = (Date) accessTokenMap.get("getTokenTime");Calendar c = Calendar.getInstance();c.setTime(getTokenTime);c.add(Calendar.HOUR_OF_DAY, 2);getTokenTime = c.getTime();if (getTokenTime.after(new Date())) {log.info("缓存中发现token未过期,直接从缓存中获取access_token");// token未过期,直接从缓存获取返回String token = (String) accessTokenMap.get("token");Integer expire = (Integer) accessTokenMap.get("expire");at.setToken(token);at.setExpiresIn(expire);return at;}}String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appSecret);JSONObject object = handleRequest(requestUrl, "GET", null);String access_token = object.getString("access_token");int expires_in = object.getInt("expires_in");log.info("\naccess_token:" + access_token);log.info("\nexpires_in:" + expires_in);at.setToken(access_token);at.setExpiresIn(expires_in);// 每次获取access_token后,存入accessTokenMap// 下次获取时,如果没有过期直接从accessTokenMap取。accessTokenMap.put("getTokenTime", new Date());accessTokenMap.put("token", access_token);accessTokenMap.put("expire", expires_in);return at;}/** * 创建菜单 * * @author qincd * @date Nov 6, 2014 9:56:36 AM */public static boolean createMenu(Menu menu,String accessToken) {String requestUrl = create_menu_url.replace("ACCESS_TOKEN", accessToken);String menuJsonString = JSONObject.fromObject(menu).toString();JSONObject jsonObject = handleRequest(requestUrl, "POST", menuJsonString);String errorCode = jsonObject.getString("errcode");if (!"0".equals(errorCode)) {log.error(String.format("菜单创建失败!errorCode:%d,errorMsg:%s",jsonObject.getInt("errcode"),jsonObject.getString("errmsg")));return false;}log.info("菜单创建成功!");return true;}}public class AccessToken {// 获取到的凭证private String token;// 凭证有效时间,单位:秒private int expiresIn;public String getToken() {return token;}public void setToken(String token) {this.token = token;}public int getExpiresIn() {return expiresIn;}public void setExpiresIn(int expiresIn) {this.expiresIn = expiresIn;}}
<pre name="code" class="java">import java.security.cert.CertificateException;import java.security.cert.X509Certificate;import javax.net.ssl.X509TrustManager;public class MyX509TrustManager implements X509TrustManager{@Overridepublic void checkClientTrusted(X509Certificate[] arg0, String arg1)throws CertificateException {}@Overridepublic void checkServerTrusted(X509Certificate[] arg0, String arg1)throws CertificateException {}@Overridepublic X509Certificate[] getAcceptedIssuers() {return null;}}






菜单包含一级菜单和二级菜单,菜单类型又包括点击菜单直接跳转到一个链接,或者是点击菜单触发一个事件,在事件处理中自定义逻辑。
不论是哪种菜单,都包含菜单名字,封装到Button类中。
对于点击后直接跳转到某个链接的菜单定义为ViewButton。
对于点击后触发一个事件的菜单定义为CommandButton
一级菜单可以包含二级菜单,定义为ComplexButton
对于整个菜单来讲,可以包含多个一级菜单,定义为Menu。


各个类的结构如下:

public class Button {private String name;/** * @return the name */public String getName() {return name;}/** * @param name the name to set */public void setName(String name) {this.name = name;}}public class CommandButton extends Button{private String type;private String key;/** * @return the type */public String getType() {return type;}/** * @param type the type to set */public void setType(String type) {this.type = type;}/** * @return the key */public String getKey() {return key;}/** * @param key the key to set */public void setKey(String key) {this.key = key;}}public class ViewButton extends Button {private String type;private String url;/** * @return the type */public String getType() {return type;}/** * @param type the type to set */public void setType(String type) {this.type = type;}/** * @return the url */public String getUrl() {return url;}/** * @param url the url to set */public void setUrl(String url) {this.url = url;}}public class ComplexButton extends Button{private Button[] sub_button;/** * @return the sub_button */public Button[] getSub_button() {return sub_button;}/** * @param sub_button the sub_button to set */public void setSub_button(Button[] sub_button) {this.sub_button = sub_button;}}public class Menu {private Button[] button;/** * @return the button */public Button[] getButton() {return button;}/** * @param button the button to set */public void setButton(Button[] button) {this.button = button;}}

菜单创建测试代码:

public class WeixinUtilTest {/** * * @author qincd * @date Nov 6, 2014 9:57:54 AM */public static void main(String[] args) {// 1).获取access_tokenAccessToken accessToken = WeixinUtil.getAccessToken(WeixinUtil.APPID, WeixinUtil.APP_SECRET);// 2).创建菜单Menu menu = new Menu();// 菜单1ComplexButton cb0 = new ComplexButton();cb0.setName("超值预定");ViewButton cb01 = new ViewButton();cb01.setName("团购订单");cb01.setType("view");cb01.setUrl("http://www.meituan.com");ViewButton cb02 = new ViewButton();cb02.setName("微信团购");cb02.setType("view");cb02.setUrl("http://www.weixin.com");cb0.setSub_button(new ViewButton[]{cb01,cb02});// 菜单2ComplexButton cb1 = new ComplexButton();cb1.setName("我的服务");ViewButton cb11 = new ViewButton();cb11.setName("办登机牌");cb11.setType("view");cb11.setUrl("http://www.meituan.com");ViewButton cb12 = new ViewButton();cb12.setName("航班动态");cb12.setType("view");cb12.setUrl("http://www.meituan.com");ViewButton cb13 = new ViewButton();cb13.setName("里程查询");cb13.setType("view");cb13.setUrl("http://www.meituan.com");cb1.setSub_button(new ViewButton[]{cb11,cb12,cb13});// 菜单3ComplexButton cb2 = new ComplexButton();cb2.setName("我的测试");CommandButton cb21 = new CommandButton();cb21.setName("回复文字");cb21.setType("click");cb21.setKey("reply_words");CommandButton cb22 = new CommandButton();cb22.setName("回复音乐");cb22.setType("click");cb22.setKey("reply_music");CommandButton cb23 = new CommandButton();cb23.setName("回复图文");cb23.setType("click");cb23.setKey("reply_news");CommandButton cb24 = new CommandButton();cb24.setName("回复链接");cb24.setType("click");cb24.setKey("reply_link");cb2.setSub_button(new CommandButton[]{cb21,cb22,cb23,cb24});menu.setButton(new ComplexButton[]{cb0,cb1,cb2});String menuJsonString = JSONObject.fromObject(menu).toString();System.out.println(menuJsonString);WeixinUtil.createMenu(menu, accessToken.getToken());}}

上面的代码创建了3个一级菜单,每个一级菜单都包含有二级菜单。
前2个一级菜单都是点击菜单后直接跳转到一个连接,第3个一级菜单点击后触发click事件
在后来根据菜单的key来处理。
如下:

@Servicepublic class WeixinService {public static Logger log = Logger.getLogger(WeixinService.class);public String processRequest(HttpServletRequest req) {// 解析微信传递的参数String str = null;try {Map<String,String> xmlMap = MessageUtil.parseXml(req);str = "请求处理异常,请稍后再试!";String ToUserName = xmlMap.get("ToUserName");String FromUserName = xmlMap.get("FromUserName");String MsgType = xmlMap.get("MsgType");if (MsgType.equals(MessageUtil.MESSAGG_TYPE_TEXT)) {// 用户发送的文本消息String content = xmlMap.get("Content");log.info("用户:[" + FromUserName + "]发送的文本消息:" + content);// 链接if (content.contains("csdn")) {TextMessage tm = new TextMessage();tm.setToUserName(FromUserName);tm.setFromUserName(ToUserName);tm.setMsgType(MessageUtil.MESSAGG_TYPE_TEXT);tm.setCreateTime(System.currentTimeMillis());tm.setContent("我的CSDN博客:<a href=\"http://my.csdn.net/qincidong\">我的CSDN博客</a>\n");return MessageUtil.textMessageToXml(tm);}if (content.contains("图文")) {NewsMessage nm = new NewsMessage();nm.setFromUserName(ToUserName);nm.setToUserName(FromUserName);nm.setCreateTime(System.currentTimeMillis());nm.setMsgType(MessageUtil.MESSAGG_TYPE_NEWS);List<Articles> articles = new ArrayList<Articles>();Articles e1 = new Articles();e1.setTitle("马云接受外媒专访:中国的五大银行想杀了“我”");e1.setDescription("阿里巴巴集团上市大获成功,《华尔街日报》日前就阿里巴巴集团、支付宝等话题采访了马云,马云也谈到了与苹果Apple Pay建立电子支付联盟的可能性。本文摘编自《华尔街日报》,原文标题:马云谈阿里巴巴将如何帮助美国出口商,虎嗅略有删节。");e1.setPicUrl("http://img1.gtimg.com/finance/pics/hv1/29/53/1739/113092019.jpg");e1.setUrl("http://finance.qq.com/a/20141105/010616.htm?pgv_ref=aio2012&ptlang=2052");Articles e2 = new Articles();e2.setTitle("史上最牛登机牌:姓名竟是微博名 涉事航空公司公开致歉");e2.setDescription("世上最遥远的距离是飞机在等你登机,你却过不了安检。");e2.setPicUrl("http://p9.qhimg.com/dmfd/328_164_100/t011946ff676981792d.png");e2.setUrl("http://www.techweb.com.cn/column/2014-11-05/2093128.shtml");articles.add(e1);articles.add(e2);nm.setArticles(articles);nm.setArticleCount(articles.size());String newsXml = MessageUtil.NewsMessageToXml(nm);log.info("\n"+newsXml);return newsXml;}if (content.contains("音乐")) {MusicMessage mm =  new MusicMessage();mm.setFromUserName(ToUserName);mm.setToUserName(FromUserName);mm.setMsgType(MessageUtil.MESSAGG_TYPE_MUSIC);mm.setCreateTime(System.currentTimeMillis());Music music = new Music();music.setTitle("Maid with the Flaxen Hair");music.setDescription("测试音乐");music.setMusicUrl("http://yinyueshiting.baidu.com/data2/music/123297915/1201250291415073661128.mp3?xcode=e2edf18bbe9e452655284217cdb920a7a6a03c85c06f4409");music.setHQMusicUrl("http://yinyueshiting.baidu.com/data2/music/123297915/1201250291415073661128.mp3?xcode=e2edf18bbe9e452655284217cdb920a7a6a03c85c06f4409");mm.setMusic(music);String musicXml = MessageUtil.MusicMessageToXml(mm);log.info("musicXml:\n" + musicXml);return musicXml;}// 响应TextMessage tm = new TextMessage();tm.setToUserName(FromUserName);tm.setFromUserName(ToUserName);tm.setMsgType(MessageUtil.MESSAGG_TYPE_TEXT);tm.setCreateTime(System.currentTimeMillis());tm.setContent("你好,你发送的内容是:\n" + content);String xml = MessageUtil.textMessageToXml(tm);log.info("xml:" + xml);return xml;}else if (MsgType.equals(MessageUtil.MESSAGG_TYPE_EVENT)) {String event = xmlMap.get("Event");if (event.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {// 订阅TextMessage tm = new TextMessage();tm.setToUserName(FromUserName);tm.setFromUserName(ToUserName);tm.setMsgType(MessageUtil.MESSAGG_TYPE_TEXT);tm.setCreateTime(System.currentTimeMillis());tm.setContent("你好,欢迎关注[程序员的生活]公众号![愉快]/呲牙/玫瑰\n目前可以回复文本消息");return MessageUtil.textMessageToXml(tm);}else if (event.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {// 取消订阅log.info("用户【" + FromUserName + "]取消关注了。");}else if (event.equals(MessageUtil.EVENT_TYPE_CLICK)) {String eventKey = xmlMap.get("EventKey");if (eventKey.equals("reply_words")) { // 点击了回复文字菜单TextMessage tm = new TextMessage();tm.setToUserName(FromUserName);tm.setFromUserName(ToUserName);tm.setMsgType(MessageUtil.MESSAGG_TYPE_TEXT);tm.setCreateTime(System.currentTimeMillis());tm.setContent("你好,你点击了回复文本菜单:\n" );String xml = MessageUtil.textMessageToXml(tm);log.info("xml:" + xml);return xml;}else if (eventKey.equals("reply_music")) { // 点击了回复音乐MusicMessage mm =  new MusicMessage();mm.setFromUserName(ToUserName);mm.setToUserName(FromUserName);mm.setMsgType(MessageUtil.MESSAGG_TYPE_MUSIC);mm.setCreateTime(System.currentTimeMillis());Music music = new Music();music.setTitle("Maid with the Flaxen Hair");music.setDescription("测试音乐");music.setMusicUrl("http://yinyueshiting.baidu.com/data2/music/123297915/1201250291415073661128.mp3?xcode=e2edf18bbe9e452655284217cdb920a7a6a03c85c06f4409");music.setHQMusicUrl("http://yinyueshiting.baidu.com/data2/music/123297915/1201250291415073661128.mp3?xcode=e2edf18bbe9e452655284217cdb920a7a6a03c85c06f4409");mm.setMusic(music);String musicXml = MessageUtil.MusicMessageToXml(mm);log.info("musicXml:\n" + musicXml);return musicXml;}else if (eventKey.equals("reply_news")) { // 点击了回复图文NewsMessage nm = new NewsMessage();nm.setFromUserName(ToUserName);nm.setToUserName(FromUserName);nm.setCreateTime(System.currentTimeMillis());nm.setMsgType(MessageUtil.MESSAGG_TYPE_NEWS);List<Articles> articles = new ArrayList<Articles>();Articles e1 = new Articles();e1.setTitle("马云接受外媒专访:中国的五大银行想杀了“我”");e1.setDescription("阿里巴巴集团上市大获成功,《华尔街日报》日前就阿里巴巴集团、支付宝等话题采访了马云,马云也谈到了与苹果Apple Pay建立电子支付联盟的可能性。本文摘编自《华尔街日报》,原文标题:马云谈阿里巴巴将如何帮助美国出口商,虎嗅略有删节。");e1.setPicUrl("http://img1.gtimg.com/finance/pics/hv1/29/53/1739/113092019.jpg");e1.setUrl("http://finance.qq.com/a/20141105/010616.htm?pgv_ref=aio2012&ptlang=2052");Articles e2 = new Articles();e2.setTitle("史上最牛登机牌:姓名竟是微博名 涉事航空公司公开致歉");e2.setDescription("世上最遥远的距离是飞机在等你登机,你却过不了安检。");e2.setPicUrl("http://p9.qhimg.com/dmfd/328_164_100/t011946ff676981792d.png");e2.setUrl("http://www.techweb.com.cn/column/2014-11-05/2093128.shtml");articles.add(e1);articles.add(e2);nm.setArticles(articles);nm.setArticleCount(articles.size());String newsXml = MessageUtil.NewsMessageToXml(nm);log.info("\n"+newsXml);return newsXml;}else if (eventKey.equals("reply_link")) {TextMessage tm = new TextMessage();tm.setToUserName(FromUserName);tm.setFromUserName(ToUserName);tm.setMsgType(MessageUtil.MESSAGG_TYPE_TEXT);tm.setCreateTime(System.currentTimeMillis());tm.setContent("我的CSDN博客:<a href=\"http://my.csdn.net/qincidong\">我的CSDN博客</a>\n");return MessageUtil.textMessageToXml(tm);}}}} catch (Exception e) {e.printStackTrace();log.error("处理微信请求时发生异常:");}return str;}}


0 0
原创粉丝点击