一、微信公众平台消息管理接口介绍
要实现微信公众号的普通消息的接收和回复,我们需要先熟悉微信公众平台API中消息接口部分,点此进入,点击后将进入到【消息管理】部分,如下图所示:
对于普通消息的接收和回复我们只需要关注上图中的"接收消息——接收普通消息"和"发送消息——被动回复消息"
1.1、消息接收
先来说说接收消息, 当普通微信用户向公众账号发消息时,微信服务器会先接收到用户发送的消息,然后将用户消息按照指定的XML格式组装好数据,最后POST消息的XML数据包到开发者填写的URL上。
接收到的普通消息的消息类型目前有以下几种:
1 文本消息
2 图片消息
3 语音消息
4 视频消息
5 小视频消息
6 地理位置消息
7 链接消息
每一种消息类型都有其指定的XML数据格式,这7种消息的xml格式请到官方文档查看,有具体的格式定义和属性说明。格式很简单,基本共有属性包括ToUserName、FromUserName、CreateTime、MsgType、MsgId,并且每种类型有自己特殊的属性。
接收消息的过程其实就是获取post请求的这个xml,然后对这个xml进行分析的过程。post请求的入口还是之前提到的微信公众号接入的那个地址,整个公众号的所有请求都会走这个入口,只是接入时是get请求,其它情况下是post请求。
1.2、消息回复
微信服务器在将用户的消息发给公众号的开发者服务器地址后,会等待开发者服务器回复响应消息。微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。
假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:
1、(推荐方式)直接回复success 2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)
一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:
1、开发者在5秒内未回复任何内容 2、开发者回复了异常数据,比如JSON数据等
另外,请注意,回复图片等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。
消息回复目前支持回复文本、图片、图文、语音、视频、音乐,每一种类型的消息都有特定的XML数据格式。这几种回复消息的xml数据格式请参考官方文档,有具体的格式定义和属性说明。格式很简单,基本共有属性包括ToUserName、FromUserName、CreateTime、MsgType,并且每种类型有自己特殊的属性。
二、微信公众号的普通消息的接收和回复
2.1、接收消息
接收消息和被动回复消息这两个动作是不分家的,这本来就是一个交互场景,一般情况就是公众号通过分析接收到的消息,会给出对应的回复。
之前说过了,接收消息的过程其实就是获取微信服务器通过post请求的发送给我们公众号服务器的xml数据,然后我们的公众号服务器再对这个xml进行解析处理的过程。为了方便解析XML数据,我们借助于dom4j,dom4j是一个十分优秀的JavaXML API,具有性能优异、功能强大和极其易使用的特点,是用来读写XML文件的。针对微信服务器发来的xml请求数据,我们写一个parseXml方法来处理,parseXml方法的代码如下:
1 /** 2 * 解析微信发来的请求(XML) 3 * 4 * @param request 封装了请求信息的HttpServletRequest对象 5 * @return map 解析结果 6 * @throws Exception 7 */ 8 public static Map<String, String> parseXml(HttpServletRequest request) throws Exception { 9 // 将解析结果存储在HashMap中 10 Map<String, String> map = new HashMap<String, String>(); 11 // 从request中取得输入流 12 InputStream inputStream = request.getInputStream(); 13 // 读取输入流 14 SAXReader reader = new SAXReader(); 15 Document document = reader.read(inputStream); 16 // 得到xml根元素 17 Element root = document.getRootElement(); 18 // 得到根元素的所有子节点 19 List<Element> elementList = root.elements(); 20 21 // 遍历所有子节点 22 for (Element e : elementList) { 23 System.out.println(e.getName() + "|" + e.getText()); 24 map.put(e.getName(), e.getText()); 25 } 26 27 // 释放资源 28 inputStream.close(); 29 inputStream = null; 30 return map; 31 }
然后在处理微信请求的入口servlet的doPost方法中调用parseXml方法即可,调用代码如下:
1 /** 2 * 处理微信服务器发来的消息 3 */ 4 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 5 // TODO 接收、处理、响应由微信服务器转发的用户发送给公众帐号的消息 6 // 将请求、响应的编码均设置为UTF-8(防止中文乱码) 7 request.setCharacterEncoding("UTF-8"); 8 response.setCharacterEncoding("UTF-8"); 9 System.out.println("请求进入"); 10 String responseMessage; 11 try { 12 //解析微信发来的请求,将解析后的结果封装成Map返回 13 Map<String,String> map = MessageHandlerUtil.parseXml(request); 14 System.out.println("开始构造响应消息"); 15 responseMessage = MessageHandlerUtil.buildResponseMessage(map); 16 System.out.println(responseMessage); 17 if(responseMessage.equals("")){ 18 responseMessage ="未正确响应"; 19 } 20 } catch (Exception e) { 21 e.printStackTrace(); 22 System.out.println("发生异常:"+ e.getMessage()); 23 responseMessage ="未正确响应"; 24 } 25 //发送响应消息 26 response.getWriter().println(responseMessage); 27 }
这样我们就完成了消息的接收,消息接收之后,我们就要根据消息类型进行响应了,写一个根据消息类型构造返回消息的方法,代码如下:
1 /** 2 * 根据消息类型构造返回消息 3 * @param map 封装了解析结果的Map 4 * @return responseMessage(响应消息) 5 */ 6 public static String buildResponseMessage(Map map) { 7 //响应消息 8 String responseMessage = ""; 9 //得到消息类型 10 String msgType = map.get("MsgType").toString(); 11 System.out.println("MsgType:" + msgType); 12 //消息类型 13 MessageType messageEnumType = MessageType.valueOf(MessageType.class, msgType.toUpperCase()); 14 switch (messageEnumType) { 15 case TEXT: 16 //处理文本消息 17 responseMessage = handleTextMessage(map); 18 break; 19 case IMAGE: 20 //处理图片消息 21 responseMessage = handleImageMessage(map); 22 break; 23 case VOICE: 24 //处理语音消息 25 responseMessage = handleVoiceMessage(map); 26 break; 27 case VIDEO: 28 //处理视频消息 29 responseMessage = handleVideoMessage(map); 30 break; 31 case SHORTVIDEO: 32 //处理小视频消息 33 responseMessage = handleSmallVideoMessage(map); 34 break; 35 case LOCATION: 36 //处理位置消息 37 responseMessage = handleLocationMessage(map); 38 break; 39 case LINK: 40 //处理链接消息 41 responseMessage = handleLinkMessage(map); 42 break; 43 case EVENT: 44 //处理事件消息,用户在关注与取消关注公众号时,微信会向我们的公众号服务器发送事件消息,开发者接收到事件消息后就可以给用户下发欢迎消息 45 responseMessage = handleEventMessage(map); 46 default: 47 break; 48 } 49 //返回响应消息 50 return responseMessage; 51 }
这样我们就完成了根据消息类型进行响应了,在处理微信请求的入口servlet的doPost方法中调用buildResponseMessage方法即可,doPost方法的完整代码在上面已经贴出来了.buildResponseMessage方法中使用到了一个MessageType类,这是一个消息类型枚举类,MessageType类的代码如下:
1 /** 2 * 接收到的消息类型 3 */ 4 public enum MessageType { 5 TEXT,//文本消息 6 IMAGE,//图片消息 7 VOICE,//语音消息 8 VIDEO,//视频消息 9 SHORTVIDEO,//小视频消息 10 LOCATION,//地理位置消息 11 LINK,//链接消息 12 EVENT//事件消息 13 }
2.2、回复消息
下面我基于这样一个业务场景来演示构造回复的消息,接收到文本消息"文本",回复文本消息;接收到“图片”,回复图片消息;接收到“语音”,回复语音消息;接收到“视频”,回复视频消息;接收到“音乐”,回复音乐消息;接收到“图文”,回复图文消息。下面具体说明各种消息的构建,只贴出核心代码,一些辅助代码类请自行下载项目代码参考.
2.2.1、回复文本消息
接收的文本消息的XML数据格式如下:
1 <xml> 2 <ToUserName><![CDATA[toUser]]></ToUserName> 3 <FromUserName><![CDATA[fromUser]]></FromUserName> 4 <CreateTime>1348831860</CreateTime> 5 <MsgType><![CDATA[text]]></MsgType> 6 <Content><![CDATA[this is a test]]></Content> 7 <MsgId>1234567890123456</MsgId> 8 </xml>
回复的文本消息的XML数据格式如下:
1 <xml> 2 <ToUserName><![CDATA[发消息的人,即订阅者]]></ToUserName> 3 <FromUserName><![CDATA[微信公众号本身]]></FromUserName> 4 <CreateTime>消息创建时间(整形)</CreateTime> 5 <MsgType><![CDATA[text]]></MsgType> 6 <Content><![CDATA[消息内容]]></Content> 7 </xml>
其中接收消息格式中的ToUserName便是回复消息的FromUserName,接收消息格式中的FromUserName便是回复消息的ToUserName。CreateTime为消息发送的时间戳。MsgType为消息类型,文本为text。Content为消息内容。具体每一种类型消息的回复,就是构造此种类型的xml格式内容,格式大同小异,只是音乐、视频、语音、图文格式相对于文本消息构造的xml内容稍微复杂一点。
写一个构建文本消息的方法,代码如下:
1 /** 2 * 构造文本消息 3 * @param map 封装了解析结果的Map 4 * @param content 文本消息内容 5 * @return 文本消息XML字符串 6 */ 7 private static String buildTextMessage(Map<String, String> map, String content) { 8 //发送方帐号 9 String fromUserName = map.get("FromUserName"); 10 // 开发者微信号 11 String toUserName = map.get("ToUserName"); 12 /** 13 * 文本消息XML数据格式 14 * <xml> 15 <ToUserName><![CDATA[toUser]]></ToUserName> 16 <FromUserName><![CDATA[fromUser]]></FromUserName> 17 <CreateTime>1348831860</CreateTime> 18 <MsgType><![CDATA[text]]></MsgType> 19 <Content><![CDATA[this is a test]]></Content> 20 <MsgId>1234567890123456</MsgId> 21 </xml> 22 */ 23 return String.format( 24 "<xml>" + 25 "<ToUserName><![CDATA[%s]]></ToUserName>" + 26 "<FromUserName><![CDATA[%s]]></FromUserName>" + 27 "<CreateTime>%s</CreateTime>" + 28 "<MsgType><![CDATA[text]]></MsgType>" + 29 "<Content><![CDATA[%s]]></Content>" + 30 "</xml>", 31 fromUserName, toUserName, getMessageCreateTime(), content); 32 }
2.2.2、回复图片消息
写一个构建图片消息的方法,代码如下:
1 /** 2 * 构造图片消息 3 * @param map 封装了解析结果的Map 4 * @param mediaId 通过素材管理接口上传多媒体文件得到的id 5 * @return 图片消息XML字符串 6 */ 7 private static String buildImageMessage(Map<String, String> map, String mediaId) { 8 //发送方帐号 9 String fromUserName = map.get("FromUserName"); 10 // 开发者微信号 11 String toUserName = map.get("ToUserName"); 12 /** 13 * 图片消息XML数据格式 14 *<xml> 15 <ToUserName><![CDATA[toUser]]></ToUserName> 16 <FromUserName><![CDATA[fromUser]]></FromUserName> 17 <CreateTime>12345678</CreateTime> 18 <MsgType><![CDATA[image]]></MsgType> 19 <Image> 20 <MediaId><![CDATA[media_id]]></MediaId> 21 </Image> 22 </xml> 23 */ 24 return String.format( 25 "<xml>" + 26 "<ToUserName><![CDATA[%s]]></ToUserName>" + 27 "<FromUserName><![CDATA[%s]]></FromUserName>" + 28 "<CreateTime>%s</CreateTime>" + 29 "<MsgType><![CDATA[image]]></MsgType>" + 30 "<Image>" + 31 " <MediaId><![CDATA[%s]]></MediaId>" + 32 "</Image>" + 33 "</xml>", 34 fromUserName, toUserName, getMessageCreateTime(), mediaId); 35 }
2.2.3、回复音乐消息
写一个构建音乐消息的方法,代码如下:
1 /** 2 * 构造音乐消息 3 * @param map 封装了解析结果的Map 4 * @param music 封装好的音乐消息内容 5 * @return 音乐消息XML字符串 6 */ 7 private static String buildMusicMessage(Map<String, String> map, Music music) { 8 //发送方帐号 9 String fromUserName = map.get("FromUserName"); 10 // 开发者微信号 11 String toUserName = map.get("ToUserName"); 12 /** 13 * 音乐消息XML数据格式 14 *<xml> 15 <ToUserName><![CDATA[toUser]]></ToUserName> 16 <FromUserName><![CDATA[fromUser]]></FromUserName> 17 <CreateTime>12345678</CreateTime> 18 <MsgType><![CDATA[music]]></MsgType> 19 <Music> 20 <Title><![CDATA[TITLE]]></Title> 21 <Description><![CDATA[DESCRIPTION]]></Description> 22 <MusicUrl><![CDATA[MUSIC_Url]]></MusicUrl> 23 <HQMusicUrl><![CDATA[HQ_MUSIC_Url]]></HQMusicUrl> 24 <ThumbMediaId><![CDATA[media_id]]></ThumbMediaId> 25 </Music> 26 </xml> 27 */ 28 return String.format( 29 "<xml>" + 30 "<ToUserName><![CDATA[%s]]></ToUserName>" + 31 "<FromUserName><![CDATA[%s]]></FromUserName>" + 32 "<CreateTime>%s</CreateTime>" + 33 "<MsgType><![CDATA[music]]></MsgType>" + 34 "<Music>" + 35 " <Title><![CDATA[%s]]></Title>" + 36 " <Description><![CDATA[%s]]></Description>" + 37 " <MusicUrl><![CDATA[%s]]></MusicUrl>" + 38 " <HQMusicUrl><![CDATA[%s]]></HQMusicUrl>" + 39 "</Music>" + 40 "</xml>", 41 fromUserName, toUserName, getMessageCreateTime(), music.title, music.description, music.musicUrl, music.hqMusicUrl); 42 }
2.2.4、回复视频消息
写一个构建视频消息的方法,代码如下:
1 /** 2 * 构造视频消息 3 * @param map 封装了解析结果的Map 4 * @param video 封装好的视频消息内容 5 * @return 视频消息XML字符串 6 */ 7 private static String buildVideoMessage(Map<String, String> map, Video video) { 8 //发送方帐号 9 String fromUserName = map.get("FromUserName"); 10 // 开发者微信号 11 String toUserName = map.get("ToUserName"); 12 /** 13 * 音乐消息XML数据格式 14 *<xml> 15 <ToUserName><![CDATA[toUser]]></ToUserName> 16 <FromUserName><![CDATA[fromUser]]></FromUserName> 17 <CreateTime>12345678</CreateTime> 18 <MsgType><![CDATA[video]]></MsgType> 19 <Video> 20 <MediaId><![CDATA[media_id]]></MediaId> 21 <Title><![CDATA[title]]></Title> 22 <Description><![CDATA[description]]></Description> 23 </Video> 24 </xml> 25 */ 26 return String.format( 27 "<xml>" + 28 "<ToUserName><![CDATA[%s]]></ToUserName>" + 29 "<FromUserName><![CDATA[%s]]></FromUserName>" + 30 "<CreateTime>%s</CreateTime>" + 31 "<MsgType><![CDATA[video]]></MsgType>" + 32 "<Video>" + 33 " <MediaId><![CDATA[%s]]></MediaId>" + 34 " <Title><![CDATA[%s]]></Title>" + 35 " <Description><![CDATA[%s]]></Description>" + 36 "</Video>" + 37 "</xml>", 38 fromUserName, toUserName, getMessageCreateTime(), video.mediaId, video.title, video.description); 39 }
2.2.5、回复语音消息
写一个构建语音消息的方法,代码如下:
1 /** 2 * 构造语音消息 3 * @param map 封装了解析结果的Map 4 * @param mediaId 通过素材管理接口上传多媒体文件得到的id 5 * @return 语音消息XML字符串 6 */ 7 private static String buildVoiceMessage(Map<String, String> map, String mediaId) { 8 //发送方帐号 9 String fromUserName = map.get("FromUserName"); 10 // 开发者微信号 11 String toUserName = map.get("ToUserName"); 12 /** 13 * 语音消息XML数据格式 14 *<xml> 15 <ToUserName><![CDATA[toUser]]></ToUserName> 16 <FromUserName><![CDATA[fromUser]]></FromUserName> 17 <CreateTime>12345678</CreateTime> 18 <MsgType><![CDATA[voice]]></MsgType> 19 <Voice> 20 <MediaId><![CDATA[media_id]]></MediaId> 21 </Voice> 22 </xml> 23 */ 24 return String.format( 25 "<xml>" + 26 "<ToUserName><![CDATA[%s]]></ToUserName>" + 27 "<FromUserName><![CDATA[%s]]></FromUserName>" + 28 "<CreateTime>%s</CreateTime>" + 29 "<MsgType><![CDATA[voice]]></MsgType>" + 30 "<Voice>" + 31 " <MediaId><![CDATA[%s]]></MediaId>" + 32 "</Voice>" + 33 "</xml>", 34 fromUserName, toUserName, getMessageCreateTime(), mediaId); 35 }
2.2.6、回复图文消息
写一个构建图文消息的方法,代码如下:
1 /** 2 * 构造图文消息 3 * @param map 封装了解析结果的Map 4 * @return 图文消息XML字符串 5 */ 6 private static String buildNewsMessage(Map<String, String> map) { 7 String fromUserName = map.get("FromUserName"); 8 // 开发者微信号 9 String toUserName = map.get("ToUserName"); 10 NewsItem item = new NewsItem(); 11 item.Title = "微信开发学习总结(一)——微信开发环境搭建"; 12 item.Description = "工欲善其事,必先利其器。要做微信公众号开发,那么要先准备好两样必不可少的东西:\n" + 13 "\n" + 14 " 1、要有一个用来测试的公众号。\n" + 15 "\n" + 16 " 2、用来调式代码的开发环境"; 17 item.PicUrl = "http://images2015.cnblogs.com/blog/289233/201601/289233-20160121164317343-2145023644.png"; 18 item.Url = "http://www.cnblogs.com/xdp-gacl/p/5149171.html"; 19 String itemContent1 = buildSingleItem(item); 20 21 NewsItem item2 = new NewsItem(); 22 item2.Title = "微信开发学习总结(二)——微信开发入门"; 23 item2.Description = "微信服务器就相当于一个转发服务器,终端(手机、Pad等)发起请求至微信服务器,微信服务器然后将请求转发给我们的应用服务器。应用服务器处理完毕后,将响应数据回发给微信服务器,微信服务器再将具体响应信息回复到微信App终端。"; 24 item2.PicUrl = ""; 25 item2.Url = "http://www.cnblogs.com/xdp-gacl/p/5151857.html"; 26 String itemContent2 = buildSingleItem(item2); 27 28 29 String content = String.format("<xml>\n" + 30 "<ToUserName><![CDATA[%s]]></ToUserName>\n" + 31 "<FromUserName><![CDATA[%s]]></FromUserName>\n" + 32 "<CreateTime>%s</CreateTime>\n" + 33 "<MsgType><![CDATA[news]]></MsgType>\n" + 34 "<ArticleCount>%s</ArticleCount>\n" + 35 "<Articles>\n" + "%s" + 36 "</Articles>\n" + 37 "</xml> ", fromUserName, toUserName, getMessageCreateTime(), 2, itemContent1 + itemContent2); 38 return content; 39 40 } 41 42 /** 43 * 生成图文消息的一条记录 44 * 45 * @param item 46 * @return 47 */ 48 private static String buildSingleItem(NewsItem item) { 49 String itemContent = String.format("<item>\n" + 50 "<Title><![CDATA[%s]]></Title> \n" + 51 "<Description><![CDATA[%s]]></Description>\n" + 52 "<PicUrl><![CDATA[%s]]></PicUrl>\n" + 53 "<Url><![CDATA[%s]]></Url>\n" + 54 "</item>", item.Title, item.Description, item.PicUrl, item.Url); 55 return itemContent; 56 }
根据上述提到的消息回复业务场景,我们可以写一个handleTextMessage方法来作为构造各种回复消息的处理入口,代码如下:
1 /** 2 * 接收到文本消息后处理 3 * @param map 封装了解析结果的Map 4 * @return 5 */ 6 private static String handleTextMessage(Map<String, String> map) { 7 //响应消息 8 String responseMessage; 9 // 消息内容 10 String content = map.get("Content"); 11 switch (content) { 12 case "文本": 13 String msgText = "孤傲苍狼又要开始写博客总结了,欢迎朋友们访问我在博客园上面写的博客\n" + 14 "<a href=\"http://www.cnblogs.com/xdp-gacl\">孤傲苍狼的博客</a>"; 15 responseMessage = buildTextMessage(map, msgText); 16 break; 17 case "图片": 18 //通过素材管理接口上传图片时得到的media_id 19 String imgMediaId = "dSQCiEHYB-pgi7ib5KpeoFlqpg09J31H28rex6xKgwWrln3HY0BTsoxnRV-xC_SQ"; 20 responseMessage = buildImageMessage(map, imgMediaId); 21 break; 22 case "语音": 23 //通过素材管理接口上传语音文件时得到的media_id 24 String voiceMediaId = "h3ul0TnwaRPut6Tl1Xlf0kk_9aUqtQvfM5Oq21unoWqJrwks505pkMGMbHnCHBBZ"; 25 responseMessage = buildVoiceMessage(map,voiceMediaId); 26 break; 27 case "图文": 28 responseMessage = buildNewsMessage(map); 29 break; 30 case "音乐": 31 Music music = new Music(); 32 music.title = "赵丽颖、许志安 - 乱世俱灭"; 33 music.description = "电视剧《蜀山战纪》插曲"; 34 music.musicUrl = "http://gacl.ngrok.natapp.cn/music/music.mp3"; 35 music.hqMusicUrl = "http://gacl.ngrok.natapp.cn/music/music.mp3"; 36 responseMessage = buildMusicMessage(map, music); 37 break; 38 case "视频": 39 Video video = new Video(); 40 video.mediaId = "GqmIGpLu41rtwaY7WCVtJAL3ZbslzKiuLEXfWIKYDnHXGObH1CBH71xtgrGwyCa3"; 41 video.title = "小苹果"; 42 video.description = "小苹果搞笑视频"; 43 responseMessage = buildVideoMessage(map, video); 44 break; 45 default: 46 responseMessage = buildWelcomeTextMessage(map); 47 break; 48 49 } 50 //返回响应消息 51 return responseMessage; 52 }
到此,回复想消息的相关处理代码就编写完成了,将项目部署到Tomcat服务器进行测试,记得使用Ngrok将内网的服务器映射到外网,否则无法使用微信测试,如下:
关于Ngrok的使用方式之前的《微信开发学习总结(一)——微信开发环境搭建》博客中已经介绍过了,这里就不再重复讲解了,没了解过Ngrok的朋友可以去看看《微信开发学习总结(一)——微信开发环境搭建》这篇博客.
使用微信进行消息回复测试,测试效果如下:
可以看到,每一种消息都正常响应了.
这里需要说一下图片,语音,视频的回复消息构造,这三种消息构造时的都需要一个mediaId,而这个mediaId是通过素材管理接口上传多媒体文件得到的,为了构造图片,语音,视频的这几种回复消息,我事先准备好了测试素材,如下图所示:
然后通过微信公众号平台提供的素材管理接口将图片,语音,视频上传到微信服务器上,上传成功后,微信服务器会给我们返回一个mediaId,用于标识上传成功的多媒体素材,上传素材的工具类代码如下:
1 package me.gacl.wx.util; 2 3 import com.alibaba.fastjson.JSON; 4 import com.alibaba.fastjson.JSONException; 5 import com.alibaba.fastjson.JSONObject; 6 import org.apache.commons.httpclient.HttpClient; 7 import org.apache.commons.httpclient.HttpException; 8 import org.apache.commons.httpclient.methods.PostMethod; 9 import org.apache.commons.httpclient.methods.multipart.FilePart; 10 import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; 11 import org.apache.commons.httpclient.methods.multipart.Part; 12 import org.apache.commons.httpclient.methods.multipart.StringPart; 13 import org.apache.commons.httpclient.protocol.Protocol; 14 import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; 15 import org.apache.http.HttpStatus; 16 17 import javax.net.ssl.*; 18 import java.io.*; 19 import java.net.HttpURLConnection; 20 import java.net.URL; 21 import java.security.cert.CertificateException; 22 import java.security.cert.X509Certificate; 23 24 /** 25 * Created by allen on 2016/1/29. 26 */ 27 public class WeChatApiUtil { 28 // token 接口(GET) 29 private static final String ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; 30 // 素材上传(POST)https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE 31 private static final String UPLOAD_MEDIA = "https://api.weixin.qq.com/cgi-bin/media/upload"; 32 // 素材下载:不支持视频文件的下载(GET) 33 private static final String DOWNLOAD_MEDIA = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s"; 34 35 public static String getTokenUrl(String appId, String appSecret) { 36 return String.format(ACCESS_TOKEN, appId, appSecret); 37 } 38 39 public static String getDownloadUrl(String token, String mediaId) { 40 return String.format(DOWNLOAD_MEDIA, token, mediaId); 41 } 42 43 /** 44 * 通用接口获取Token凭证 45 * 46 * @param appId 47 * @param appSecret 48 * @return 49 */ 50 public static String getToken(String appId, String appSecret) { 51 if (appId == null || appSecret == null) { 52 return null; 53 } 54 55 String token = null; 56 String tockenUrl = WeChatApiUtil.getTokenUrl(appId, appSecret); 57 String response = httpsRequestToString(tockenUrl, "GET", null); 58 JSONObject jsonObject = JSON.parseObject(response); 59 if (null != jsonObject) { 60 try { 61 token = jsonObject.getString("access_token"); 62 } catch (JSONException e) { 63 token = null;// 获取token失败 64 } 65 } 66 return token; 67 } 68 69 /** 70 * 微信服务器素材上传 71 * 72 * @param file 表单名称media 73 * @param token access_token 74 * @param type type只支持四种类型素材(video/image/voice/thumb) 75 */ 76 public static JSONObject uploadMedia(File file, String token, String type) { 77 if (file == null || token == null || type == null) { 78 return null; 79 } 80 81 if (!file.exists()) { 82 System.out.println("上传文件不存在,请检查!"); 83 return null; 84 } 85 86 String url = UPLOAD_MEDIA; 87 JSONObject jsonObject = null; 88 PostMethod post = new PostMethod(url); 89 post.setRequestHeader("Connection", "Keep-Alive"); 90 post.setRequestHeader("Cache-Control", "no-cache"); 91 FilePart media; 92 HttpClient httpClient = new HttpClient(); 93 //信任任何类型的证书 94 Protocol myhttps = new Protocol("https", new SSLProtocolSocketFactory(), 443); 95 Protocol.registerProtocol("https", myhttps); 96 97 try { 98 media = new FilePart("media", file); 99 Part[] parts = new Part[]{new StringPart("access_token", token), 100 new StringPart("type", type), media}; 101 MultipartRequestEntity entity = new MultipartRequestEntity(parts, 102 post.getParams()); 103 post.setRequestEntity(entity); 104 int status = httpClient.executeMethod(post); 105 if (status == HttpStatus.SC_OK) { 106 String text = post.getResponseBodyAsString(); 107 jsonObject = JSONObject.parseObject(text); 108 } else { 109 System.out.println("upload Media failure status is:" + status); 110 } 111 } catch (FileNotFoundException e) { 112 e.printStackTrace(); 113 } catch (HttpException e) { 114 e.printStackTrace(); 115 } catch (IOException e) { 116 e.printStackTrace(); 117 } 118 return jsonObject; 119 } 120 121 /** 122 * 多媒体下载接口 123 * 124 * @param fileName 素材存储文件路径 125 * @param token 认证token 126 * @param mediaId 素材ID(对应上传后获取到的ID) 127 * @return 素材文件 128 * @comment 不支持视频文件的下载 129 */ 130 public static File downloadMedia(String fileName, String token, 131 String mediaId) { 132 String url = getDownloadUrl(token, mediaId); 133 return httpRequestToFile(fileName, url, "GET", null); 134 } 135 136 /** 137 * 多媒体下载接口 138 * 139 * @param fileName 素材存储文件路径 140 * @param mediaId 素材ID(对应上传后获取到的ID) 141 * @return 素材文件 142 * @comment 不支持视频文件的下载 143 */ 144 public static File downloadMedia(String fileName, String mediaId) { 145 String appId = "wxbe4d433e857e8bb1"; 146 String appSecret = "ccbc82d560876711027b3d43a6f2ebda"; 147 String token = WeChatApiUtil.getToken(appId, appSecret); 148 return downloadMedia(fileName,token,mediaId); 149 } 150 151 /** 152 * 以http方式发送请求,并将请求响应内容输出到文件 153 * 154 * @param path 请求路径 155 * @param method 请求方法 156 * @param body 请求数据 157 * @return 返回响应的存储到文件 158 */ 159 public static File httpRequestToFile(String fileName, String path, String method, String body) { 160 if (fileName == null || path == null || method == null) { 161 return null; 162 } 163 164 File file = null; 165 HttpURLConnection conn = null; 166 InputStream inputStream = null; 167 FileOutputStream fileOut = null; 168 try { 169 URL url = new URL(path); 170 conn = (HttpURLConnection) url.openConnection(); 171 conn.setDoOutput(true); 172 conn.setDoInput(true); 173 conn.setUseCaches(false); 174 conn.setRequestMethod(method); 175 if (null != body) { 176 OutputStream outputStream = conn.getOutputStream(); 177 outputStream.write(body.getBytes("UTF-8")); 178 outputStream.close(); 179 } 180 181 inputStream = conn.getInputStream(); 182 if (inputStream != null) { 183 file = new File(fileName); 184 } else { 185 return file; 186 } 187 188 //写入到文件 189 fileOut = new FileOutputStream(file); 190 if (fileOut != null) { 191 int c = inputStream.read(); 192 while (c != -1) { 193 fileOut.write(c); 194 c = inputStream.read(); 195 } 196 } 197 } catch (Exception e) { 198 } finally { 199 if (conn != null) { 200 conn.disconnect(); 201 } 202 203 /* 204 * 必须关闭文件流 205 * 否则JDK运行时,文件被占用其他进程无法访问 206 */ 207 try { 208 inputStream.close(); 209 fileOut.close(); 210 } catch (IOException execption) { 211 } 212 } 213 return file; 214 } 215 216 /** 217 * 上传素材 218 * @param filePath 媒体文件路径(绝对路径) 219 * @param type 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb) 220 * @return 221 */ 222 public static JSONObject uploadMedia(String filePath,String type){ 223 File f = new File(filePath); // 获取本地文件 224 String appId = "wxbe4d433e857e8bb1"; 225 String appSecret = "ccbc82d560876711027b3d43a6f2ebda"; 226 String token = WeChatApiUtil.getToken(appId, appSecret); 227 JSONObject jsonObject = uploadMedia(f, token, type); 228 return jsonObject; 229 } 230 231 /** 232 * 发送请求以https方式发送请求并将请求响应内容以String方式返回 233 * 234 * @param path 请求路径 235 * @param method 请求方法 236 * @param body 请求数据体 237 * @return 请求响应内容转换成字符串信息 238 */ 239 public static String httpsRequestToString(String path, String method, String body) { 240 if (path == null || method == null) { 241 return null; 242 } 243 244 String response = null; 245 InputStream inputStream = null; 246 InputStreamReader inputStreamReader = null; 247 BufferedReader bufferedReader = null; 248 HttpsURLConnection conn = null; 249 try { 250 TrustManager[] tm = {new JEEWeiXinX509TrustManager()}; 251 SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); 252 sslContext.init(null, tm, new java.security.SecureRandom()); 253 SSLSocketFactory ssf = sslContext.getSocketFactory(); 254 System.out.println(path); 255 URL url = new URL(path); 256 conn = (HttpsURLConnection) url.openConnection(); 257 conn.setSSLSocketFactory(ssf); 258 259 conn.setDoOutput(true); 260 conn.setDoInput(true); 261 conn.setUseCaches(false); 262 conn.setRequestMethod(method); 263 if (null != body) { 264 OutputStream outputStream = conn.getOutputStream(); 265 outputStream.write(body.getBytes("UTF-8")); 266 outputStream.close(); 267 } 268 269 inputStream = conn.getInputStream(); 270 inputStreamReader = new InputStreamReader(inputStream, "UTF-8"); 271 bufferedReader = new BufferedReader(inputStreamReader); 272 String str = null; 273 StringBuffer buffer = new StringBuffer(); 274 while ((str = bufferedReader.readLine()) != null) { 275 buffer.append(str); 276 } 277 278 response = buffer.toString(); 279 } catch (Exception e) { 280 281 } finally { 282 if (conn != null) { 283 conn.disconnect(); 284 } 285 try { 286 bufferedReader.close(); 287 inputStreamReader.close(); 288 inputStream.close(); 289 } catch (IOException execption) { 290 291 } 292 } 293 return response; 294 } 295 296 public static void main(String[] args) throws Exception{ 297 //媒体文件路径 298 String filePath = "D:/JavaSoftwareDevelopeFolder/IntelliJ IDEA_Workspace/WxStudy/web/media/image/我.jpg"; 299 //String filePath = "D:/JavaSoftwareDevelopeFolder/IntelliJ IDEA_Workspace/WxStudy/web/media/voice/voice.mp3"; 300 //String filePath = "D:\\JavaSoftwareDevelopeFolder\\IntelliJ IDEA_Workspace\\WxStudy\\web\\media\\video\\小苹果.mp4"; 301 //媒体文件类型 302 String type = "image"; 303 //String type = "voice"; 304 //String type = "video"; 305 JSONObject uploadResult = uploadMedia(filePath, type); 306 //{"media_id":"dSQCiEHYB-pgi7ib5KpeoFlqpg09J31H28rex6xKgwWrln3HY0BTsoxnRV-xC_SQ","created_at":1455520569,"type":"image"} 307 System.out.println(uploadResult.toString()); 308 309 //下载刚刚上传的图片以id命名 310 String media_id = uploadResult.getString("media_id"); 311 File file = downloadMedia("D:/" + media_id + ".png", media_id); 312 System.out.println(file.getName()); 313 314 } 315 } 316 317 class JEEWeiXinX509TrustManager implements X509TrustManager { 318 public void checkClientTrusted(X509Certificate[] chain, String authType) 319 throws CertificateException { 320 } 321 322 public void checkServerTrusted(X509Certificate[] chain, String authType) 323 throws CertificateException { 324 } 325 326 public X509Certificate[] getAcceptedIssuers() { 327 return null; 328 } 329 }
在工具类写一个main方法测试素材上传和下载,代码如下:
1 public static void main(String[] args) throws Exception{ 2 //媒体文件路径 3 String filePath = "D:/JavaSoftwareDevelopeFolder/IntelliJ IDEA_Workspace/WxStudy/web/media/image/我.jpg"; 4 //String filePath = "D:/JavaSoftwareDevelopeFolder/IntelliJ IDEA_Workspace/WxStudy/web/media/voice/voice.mp3"; 5 //String filePath = "D:\\JavaSoftwareDevelopeFolder\\IntelliJ IDEA_Workspace\\WxStudy\\web\\media\\video\\小苹果.mp4"; 6 //媒体文件类型 7 String type = "image"; 8 //String type = "voice"; 9 //String type = "video"; 10 JSONObject uploadResult = uploadMedia(filePath, type); 11 //{"media_id":"dSQCiEHYB-pgi7ib5KpeoFlqpg09J31H28rex6xKgwWrln3HY0BTsoxnRV-xC_SQ","created_at":1455520569,"type":"image"} 12 System.out.println(uploadResult.toString()); 13 14 //下载刚刚上传的图片以id命名 15 String media_id = uploadResult.getString("media_id"); 16 File file = downloadMedia("D:/" + media_id + ".png", media_id); 17 System.out.println(file.getName()); 18 19 }
运行结果如下:
可以看到,素材上传成功后,微信服务器就会返回一个media_id,用于标识上传后的文件.有了这个media_id后,我们就可以构建我们想要的图片,语音,视频回复消息了.
下面再说一下音乐的回复消息的构造,音乐素材也是我事先在服务器上准备了一个music.mp3音乐文件,并且保证可以正常使用外网访问,如下所示:
然后将MusicUrl和HQMusicUrl的地址指向了服务器上的music.mp3文件的访问地址,如下:
1 Music music = new Music(); 2 music.title = "赵丽颖、许志安 - 乱世俱灭"; 3 music.description = "电视剧《蜀山战纪》插曲"; 4 music.musicUrl = "http://gacl.ngrok.natapp.cn/media/music/music.mp3"; 5 music.hqMusicUrl = "http://gacl.ngrok.natapp.cn/media/music/music.mp3"; 6 responseMessage = buildMusicMessage(map, music);
这样就可以正常构造音乐回复消息了.
来源:oschina
链接:https://my.oschina.net/u/3851041/blog/3049202