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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qincidong/article/details/40858017
参考了《微信公众账号开发教程(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{

	@Override
	public void checkClientTrusted(X509Certificate[] arg0, String arg1)
			throws CertificateException {
		
	}

	@Override
	public void checkServerTrusted(X509Certificate[] arg0, String arg1)
			throws CertificateException {
		
	}

	@Override
	public 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_token
		AccessToken accessToken = WeixinUtil.getAccessToken(WeixinUtil.APPID, WeixinUtil.APP_SECRET);
		// 2).创建菜单
		Menu menu = new Menu();
		
		// 菜单1
		ComplexButton 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});
		
		// 菜单2
		ComplexButton 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});
		
		// 菜单3
		ComplexButton 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来处理。
如下:

@Service
public 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;
	}
	
}


猜你喜欢

转载自blog.csdn.net/qincidong/article/details/40858017