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


    参考了《微信公众账号开发教程(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("
    access_token:" + access_token);
            log.info("
    expires_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;
        }
    }
    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>
    ");  
                        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("
    "+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:
    " + 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("你好,你发送的内容是:
    " + 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("你好,欢迎关注[程序员的生活]公众号![愉快]/呲牙/玫瑰
    目前可以回复文本消息");  
                        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("你好,你点击了回复文本菜单:
    " );  
                              
                            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:
    " + 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("
    "+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>
    ");  
                            return MessageUtil.textMessageToXml(tm);  
                        }  
                    }  
                }  
            } catch (Exception e) {  
                e.printStackTrace();  
                log.error("处理微信请求时发生异常:");  
            }  
              
            return str;  
        }  
          
    }
    SouthEast
  • 相关阅读:
    关于springMVC+Mybatis jar包详解
    关于struts2的过滤器和mybatis的插件的分析
    C# Zip压缩、解压
    JS 字符串转字节截取
    JS 时间差计算 XX秒前、XX小时前、XX天前
    IIS配置web.config 将带www域名转为不带www域名
    JavaScript获取当前url路径
    SQL 查询今天、昨天、7天内、30天的数据
    SQL求解两个时间差
    SqlServer获取当前日期
  • 原文地址:https://www.cnblogs.com/luckystar2010/p/4123084.html
Copyright © 2020-2023  润新知