文章目录
前言一、 微信公众号基本配置1、注册并登陆微信公众号2、服务器配置3、 创建微信连接检验工具包:CheckUtil4、创建controller层用来接收和返回参数二、消息的接收与处理1、接收普通消息文本消息图片消息语音消息视频消息小视频消息地理位置消息链接信息接受事件推送关注与取消关注自定义菜单栏事件2、消息接受处理XML格式处理消息接收消息处理建立消息实体消息实体基础类文本消息链接消息视频消息其他消息消息分类处理消息回复回复消息基类文本消息类图片消息类消息回复格式转换消息内容填充前言
微信公众平台是运营者通过公众号为微信用户提供资讯和服务的平台,而公众平台开发接口则是提供服务的基础,开发者在公众平台网站中创建公众号、获取接口权限后,可以通过阅读本文来帮助开发。
为了识别用户,每个用户针对每个公众号会产生一个安全的OpenID,如果需要在多公众号、移动应用之间做用户共通,则需前往微信开放平台,将这些公众号和应用绑定到一个开放平台账号下,绑定后,一个用户虽然对多个公众号和应用有多个不同的OpenID,但他对所有这些同一开放平台账号下的公众号和应用,只有一个UnionID
名词解释:
OpenID: 是一个以用户为中心的数字身份识别框架,它具有开放、分散性。OpenID 的创建基于这样一个概念:我们可以通过 URI (又叫 URL 或网站地址)来认证一个网站的唯一身份,同理,我们也可以通过这种方式来作为用户的身份认证。UnionID:如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过 UnionID 来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的 UnionID 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,UnionID是相同的。
一、 微信公众号基本配置
1、注册并登陆微信公众号
https://mp./
2、服务器配置
设置于开发——>基本配置——>服务器配置
填写配置URL 和 Token:自主设置用于验证开发者服务器。
3、 创建微信连接检验工具包:CheckUtil
提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
signature微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。timestamp时间戳nonce随机数echostr随机字符串
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1、将token、timestamp、nonce三个参数进行字典序排序 2、将三个参数字符串拼接成一个字符串进行sha1加密 3、开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
package com.vx.Util;import java.security.MessageDigest;import java.util.Arrays;public class CheckUtil {public static final String tooken = "WeChat"; //开发者自行定义Tookenpublic static boolean checkSignature(String signature,String timestamp,String nonce){//1.定义数组存放tooken,timestamp,nonceString[] arr = {tooken,timestamp,nonce};//2.对数组进行排序Arrays.sort(arr);//3.生成字符串StringBuffer sb = new StringBuffer();for(String s : arr){sb.append(s);}//4.sha1加密,网上均有现成代码String temp = getSha1(sb.toString());//5.将加密后的字符串,与微信传来的加密签名比较,返回结果return temp.equals(signature);}public static String getSha1(String str){if(str==null||str.length()==0){return null;}char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};try {MessageDigest mdTemp = MessageDigest.getInstance("SHA1");mdTemp.update(str.getBytes("UTF-8"));byte[] md = mdTemp.digest();int j = md.length;char buf[] = new char[j*2];int k = 0;for (int i = 0; i < j; i++) {byte byte0 = md[i];buf[k++] = hexDigits[byte0 >>> 4 & 0xf];buf[k++] = hexDigits[byte0 & 0xf];}return new String(buf);} catch (Exception e) {// TODO: handle exceptionreturn null;}}}
4、创建controller层用来接收和返回参数
package com.vx.controller;import com.vx.Util.CheckUtil;import org.apache.log4j.Logger;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;@Controller@RequestMapping("/wechat")public class WeChatController {private static Logger logger = Logger.getLogger(WeChatController. class );/*** 用于接受get请求参数,返回验证参数*/@RequestMapping (value = "security" , method = RequestMethod.GET)public void doGet(HttpServletRequest request, HttpServletResponse response,@RequestParam(value = "signature", required=true)String signature,@RequestParam (value = "timestamp" , required = true ) String timestamp,@RequestParam (value = "nonce" , required = true ) String nonce,@RequestParam (value = "echostr" , required = true ) String echostr) {if(CheckUtil.checkSignature(signature, timestamp, nonce)){try {PrintWriter out = response.getWriter();out.print(echostr);out.close();} catch (IOException e) {e.printStackTrace();}}}}
二、消息的接收与处理
1、接收普通消息
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
文本消息
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[fromUser]]></FromUserName><CreateTime>1348831860</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[this is a test]]></Content><MsgId>1234567890123456</MsgId></xml>
参数说明:
ToUserName开发者微信号FromUserName发送方帐号(一个OpenID)CreateTime消息创建时间 (整型)MsgType消息类型,文本为textContent文本消息内容MsgId消息id,64位整型
图片消息
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[fromUser]]></FromUserName><CreateTime>1348831860</CreateTime><MsgType><![CDATA[image]]></MsgType><PicUrl><![CDATA[this is a url]]></PicUrl><MediaId><![CDATA[media_id]]></MediaId><MsgId>1234567890123456</MsgId></xml>
ToUserName开发者微信号FromUserName发送方帐号(一个OpenID)CreateTime消息创建时间 (整型)MsgType消息类型,图片为imagePicUrl图片链接(由系统生成)MediaId图片消息媒体id,可以调用获取临时素材接口拉取数据。MsgId消息id,64位整型
语音消息
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[fromUser]]></FromUserName><CreateTime>1357290913</CreateTime><MsgType><![CDATA[voice]]></MsgType><MediaId><![CDATA[media_id]]></MediaId><Format><![CDATA[Format]]></Format><Recognition>< ![CDATA[腾讯微信团队] ]></Recognition><MsgId>1234567890123456</MsgId></xml>
ToUserName开发者微信号FromUserName发送方帐号(一个OpenID)CreateTime消息创建时间 (整型)MsgType语音为voiceMediaId语音消息媒体id,可以调用获取临时素材接口拉取数据。Format语音格式,如amr,speex等Recognition语音识别结果,UTF8编码MsgId消息id,64位整型
视频消息
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[fromUser]]></FromUserName><CreateTime>1357290913</CreateTime><MsgType><![CDATA[video]]></MsgType><MediaId><![CDATA[media_id]]></MediaId><ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId><MsgId>1234567890123456</MsgId></xml>
ToUserName开发者微信号FromUserName发送方帐号(一个OpenID)CreateTime消息创建时间 (整型)MsgType视频为videoMediaId视频消息媒体id,可以调用获取临时素材接口拉取数据。ThumbMediaId视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。MsgId消息id,64位整型
小视频消息
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[fromUser]]></FromUserName><CreateTime>1357290913</CreateTime><MsgType><![CDATA[shortvideo]]></MsgType><MediaId><![CDATA[media_id]]></MediaId><ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId><MsgId>1234567890123456</MsgId></xml>
ToUserName开发者微信号FromUserName发送方帐号(一个OpenID)CreateTime消息创建时间 (整型)MsgType小视频为shortvideoMediaId视频消息媒体id,可以调用获取临时素材接口拉取数据。ThumbMediaId视频消息缩略图的媒体id,可以调用获取临时素材接口拉取数据。MsgId消息id,64位整型
地理位置消息
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[fromUser]]></FromUserName><CreateTime>1351776360</CreateTime><MsgType><![CDATA[location]]></MsgType><Location_X>23.134521</Location_X><Location_Y>113.358803</Location_Y><Scale>20</Scale><Label><![CDATA[位置信息]]></Label><MsgId>1234567890123456</MsgId></xml>
ToUserName开发者微信号FromUserName发送方帐号(一个OpenID)CreateTime消息创建时间 (整型)MsgType消息类型,地理位置为locationLocation_X地理位置纬度Location_Y地理位置经度Scale地图缩放大小Label地理位置信息MsgId消息id,64位整型
链接信息
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[fromUser]]></FromUserName><CreateTime>1351776360</CreateTime><MsgType><![CDATA[link]]></MsgType><Title><![CDATA[公众平台官网链接]]></Title><Description><![CDATA[公众平台官网链接]]></Description><Url><![CDATA[url]]></Url><MsgId>1234567890123456</MsgId></xml>
ToUserName接收方微信号FromUserName发送方微信号,若为普通用户,则是一个OpenIDCreateTime消息创建时间MsgType消息类型,链接为linkTitle消息标题Description消息描述Url消息链接MsgId消息id,64位整型
接受事件推送
关注与取消关注
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[FromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[subscribe]]></Event></xml>
ToUserName开发者微信号FromUserName发送方帐号(一个OpenID)CreateTime消息创建时间 (整型)MsgType消息类型,eventEvent事件类型,subscribe(订阅)、unsubscribe(取消订阅)
自定义菜单栏事件
用户点击自定义菜单后,微信会把点击事件推送给开发者,请注意,点击菜单弹出子菜单,不会产生上报。
点击菜单拉取消息时的事件推送
在这里插入代码片<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[FromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[CLICK]]></Event><EventKey><![CDATA[EVENTKEY]]></EventKey></xml>
ToUserName开发者微信号FromUserName发送方帐号(一个OpenID)CreateTime消息创建时间 (整型)MsgType消息类型,eventEvent事件类型,CLICKEventKey事件KEY值,与自定义菜单接口中KEY值对应
点击菜单跳转链接时的事件推送
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[FromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[VIEW]]></Event><EventKey><![CDATA[]]></EventKey></xml>
ToUserName开发者微信号FromUserName发送方帐号(一个OpenID)CreateTime消息创建时间 (整型)MsgType消息类型,eventEvent事件类型,VIEWEventKey事件KEY值,设置的跳转URL
2、消息接受处理
XML格式处理
微信的消息体是采用xml格式,那么我在这里写了一个MessageUtil去做消息格式的处理,大致代码如下:
package com.vx.Util;import com.thoughtworks.xstream.XStream;import com.thoughtworks.xstream.core.util.QuickWriter;import com.thoughtworks.xstream.io.HierarchicalStreamWriter;import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;import com.thoughtworks.xstream.io.xml.XppDriver;import org.dom4j.Document;import org.dom4j.Element;import org.dom4j.io.SAXReader;import javax.servlet.http.HttpServletRequest;import java.io.InputStream;import java.io.Writer;import java.util.HashMap;import java.util.List;import java.util.Map;public class MessageUtil {/*** 返回消息类型:文本*/public static final String RESP_MESSAGE_TYPE_TEXT = "text" ;/*** 返回消息类型:音乐*/public static final String RESP_MESSAGE_TYPE_MUSIC = "music" ;/*** 返回消息类型:图文*/public static final String RESP_MESSAGE_TYPE_NEWS = "news" ;/*** 请求消息类型:文本*/public static final String REQ_MESSAGE_TYPE_TEXT = "text" ;/*** 请求消息类型:图片*/public static final String REQ_MESSAGE_TYPE_IMAGE = "image" ;/*** 请求消息类型:链接*/public static final String REQ_MESSAGE_TYPE_LINK = "link" ;/*** 请求消息类型:地理位置*/public static final String REQ_MESSAGE_TYPE_LOCATION = "location" ;/*** 请求消息类型:音频*/public static final String REQ_MESSAGE_TYPE_VOICE = "voice" ;/*** 请求消息类型:推送*/public static final String REQ_MESSAGE_TYPE_EVENT = "event" ;/*** 事件类型:subscribe(订阅)*/public static final String EVENT_TYPE_SUBSCRIBE = "subscribe" ;/*** 事件类型:unsubscribe(取消订阅)*/public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe" ;/*** 事件类型:CLICK(自定义菜单点击事件)*/public static final String EVENT_TYPE_CLICK = "CLICK" ;/*** 解析微信发来的请求(XML)*/@SuppressWarnings ( "unchecked" )public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {// 将解析结果存储在HashMap中Map<String, String> map = new HashMap<String, String>();// 从request中取得输入流InputStream inputStream = request.getInputStream();// 读取输入流SAXReader reader = new SAXReader();Document document = reader.read(inputStream);// 得到xml根元素Element root = document.getRootElement();// 得到根元素的所有子节点List<Element> elementList = root.elements();// 遍历所有子节点for (Element e : elementList)map.put(e.getName(), e.getText());// 释放资源inputStream.close();inputStream = null ;return map;}@SuppressWarnings ( "unused" )private static XStream xstream = new XStream(new XppDriver() {public HierarchicalStreamWriter createWriter(Writer out) {return new PrettyPrintWriter(out) {// 对所有xml节点的转换都增加CDATA标记boolean cdata = true ;@SuppressWarnings ( "rawtypes" )public void startNode(String name, Class clazz) {super .startNode(name, clazz);}protected void writeText(QuickWriter writer, String text) {if (cdata) {writer.write( "<![CDATA[" );writer.write(text);writer.write( "]]>" );} else {writer.write(text);}}};}});}
消息接收
WechatController下添加post方法用于做消息的接收和处理
@RequestMapping (value = "security" , method = RequestMethod.POST)// post方法用于接收微信服务端消息public void DoPost(HttpServletRequest request,HttpServletResponse response) {try {Map<String, String> map = MessageUtil.parseXml(request);} catch (Exception e) {logger.error(e,e);}}
消息处理
建立消息实体
建立消息实体以方便我们后面的使用
消息实体基础类
@Datapublic class BaseMessage {// 开发者微信号private String ToUserName;// 发送方帐号(一个OpenID)private String FromUserName;// 消息创建时间 (整型)private long CreateTime;// 消息类型(text/image/location/link/video/shortvideo)private String MsgType;// 消息id,64位整型private long MsgId;}
文本消息
public class TextMessage extends BaseMessage {//文本内容private String Content;}
链接消息
@Datapublic class LinkMessage extends BaseMessage{// 消息标题private String Title;// 消息描述private String Description;// 消息链接private String Url;}
视频消息
@Datapublic class VideoMessage {private String MediaId; // 视频消息媒体id,可以调用多媒体文件下载接口拉取数据private String ThumbMediaId; // 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据}
其他消息
其他消息类似根据前面不同消息不同内容进行添加属性处理
消息分类处理
按照上面收到想消息类别分别做不同的分发处理,这里我们建立了自己的业务分发器(EventDispatcher、MsgDispatcher),分别做普通消息处理和事件消息处理
EventDispatcher
public class EventDispatcher {public static String processEvent(Map<String, String> map) {if (map.get( "Event" ).equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {//关注事件System.out.println( "==============这是关注事件!" );}if (map.get( "Event" ).equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {//取消关注事件System.out.println( "==============这是取消关注事件!" );}if (map.get( "Event" ).equals(MessageUtil.EVENT_TYPE_CLICK)) {//自定义菜单点击事件System.out.println( "==============这是自定义菜单点击事件!" );}return "事件消息" ;}}
MsgDispatcher
public class MsgDispatcher {public static String processMessage(Map<String,String> map){if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)){System.out.println("==============这是文本消息");}if (map.get( "MsgType" ).equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {// 图片消息System.out.println( "==============这是图片消息!" );}if (map.get( "MsgType" ).equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) {// 链接消息System.out.println( "==============这是链接消息!" );}if (map.get( "MsgType" ).equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) {// 位置消息System.out.println( "==============这是位置消息!" );}if (map.get( "MsgType" ).equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) {// 视频消息System.out.println( "==============这是视频消息!" );}if (map.get( "MsgType" ).equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) {// 语音消息System.out.println( "==============这是语音消息!" );}return "普通事件";}}
这个时候我们需要把我们的消息入口【WechatController.java】中的post方法做些修改
public void DoPost(HttpServletRequest request,HttpServletResponse response) {try {Map<String, String> map = MessageUtil.parseXml(request);String msgType = map.get("MsgType");if (MessageUtil.REQ_MESSAGE_TYPE_EVENT.equals(msgType)){EventDispatcher.processEvent(map); //进入事件处理} else {MsgDispatcher.processMessage(map); //进入消息处理}} catch (Exception e) {logger.error(e,e);}}
消息回复
这里我们要讲述的是我们在给用户回复的消息类型,在这里也可以大致分为两类:文本消息和图文消息
回复消息基类
@Datapublic class BaseMessage {// 接收方帐号(收到的OpenID)private String ToUserName;// 开发者微信号private String FromUserName;// 消息创建时间 (整型)private long CreateTime;// 消息类型(text/music/news)private String MsgType;}
文本消息类
@Datapublic class TextMessage extends BaseMessage{// 回复的消息内容private String Content;}
图片消息类
@Datapublic class ImageMessage extends BaseMessage {// 通过素材管理中的接口上传多媒体文件,得到的id。private String MediaId;}
消息回复格式转换
MessageUtil 类中添加消息转换方法
/*** @Description: 文本消息对象转换成xml*/public static String textMessageToXml(TextMessage textMessage) {xstream.alias( "xml" , textMessage.getClass());return xstream.toXML(textMessage);}/*** @Description: 图文消息对象转换成xml*/public static String newsMessageToXml(ImageMessage imageMessage) {xstream.alias( "xml" , imageMessage.getClass());return xstream.toXML(imageMessage);}
消息内容填充
dispatcher消息中根据自己需求进行内容填充
String openid=map.get( "FromUserName" ); //用户openidString mpid=map.get( "ToUserName" ); //公众号原始IDif(map.get("Content").equals("文本")){TextMessage textMessage = new TextMessage();//文本回复类textMessage.setFromUserName(openid);textMessage.setToUserName(mpid);textMessage.setCreateTime(new Date().getTime());textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);textMessage.setContent("这是一个文本消息回复");return MessageUtil.textMessageToXml(textMessage);}if(map.get("Content").equals("图片")){ImageMessage imageMessage = new ImageMessage();//图片回复类imageMessage.setFromUserName(openid);imageMessage.setToUserName(mpid);imageMessage.setCreateTime(new Date().getTime());imageMessage.setMsgType(MessageUtil.REQ_MESSAGE_TYPE_IMAGE);//通过素材管理中的接口上传多媒体文件,得到的id。imageMessage.setMediaId("/chart_image/616f8c10e401fd16dd9090ea.png");return MessageUtil.newsMessageToXml(imageMessage);}