对用户的消息及事件作出处理
首先在公众号后台设置开发者后需要是指一个消息的返回地址
我这里设置的接口地址是: /wxpublic/verify_wx_token
如下图:
bp_wx_wxmanage是我的项目名称,我这里热部署在tomcat上的,所以配置完成以后微信就会发送消息到/wxpublic/verify_wx_token接口,在此Controller里处理数据即可。
首先需要做一次token的接口验证
这里微信需要一个可外界访问的接口,本地测试的话你可以下载一个花生壳操作一哈,配置上微信的白名单就可以进行测试了。
做token验证时也是访问的上诉接口,是以get方式请求的,而发送事件消息是post消息,因此可以进行区分:
@RequestMapping("/wxpublic/verify_wx_token")
@ResponseBody
public void verifyWXToken(HttpServletRequest request, HttpServletResponse response)
throws AesException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
boolean isGet = request.getMethod().toLowerCase().equals("get");
PrintWriter out = response.getWriter();
try {
if (isGet) {
String msgSignature = request.getParameter("signature");
String msgTimestamp = request.getParameter("timestamp");
String msgNonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
if (WXPublicUtils.verifyUrl(msgSignature, msgTimestamp, msgNonce)) {
logger.info("验证完毕返回");
try {
response.getWriter().write(echostr);
} catch (IOException e) {
logger.error("验证失败= {}", e.getMessage());
}
}
} else {
String respMessage = "异常消息!";
try {
respMessage = weiXinPostService.weixinPost(request);
if (respMessage != null)
out.print(respMessage);
logger.info("The request completed successfully");
logger.info("to weixin server " + respMessage);
} catch (Exception e) {
logger.error("Failed to convert the message from weixin! {}", e.getMessage());
}
}
} catch (Exception e) {
logger.error("Connect the weixin server is error. e.errorMsg={}", e.getMessage());
} finally {
out.close();
out = null;
}
}
在这里我区分了get和post,get用来验证
这里自定义了一个异常处理:AesException
public class AesException extends Exception{
public final static int OK = 0;
public final static int ValidateSignatureError = -40001;
public final static int ParseXmlError = -40002;
public final static int ComputeSignatureError = -40003;
public final static int IllegalAesKey = -40004;
public final static int ValidateAppidError = -40005;
public final static int EncryptAESError = -40006;
public final static int DecryptAESError = -40007;
public final static int IllegalBuffer = -40008;
public final static int EncodeBase64Error = -40009;
public final static int DecodeBase64Error = -40010;
public final static int GenReturnXmlError = -40011;
private int code;
private static String getMessage(int code) {
switch (code) {
case ValidateSignatureError:
return "签名验证错误";
case ParseXmlError:
return "xml解析失败";
case ComputeSignatureError:
return "sha加密生成签名失败";
case IllegalAesKey:
return "SymmetricKey非法";
case ValidateAppidError:
return "appid校验失败";
case EncryptAESError:
return "aes加密失败";
case DecryptAESError:
return "aes解密失败";
case IllegalBuffer:
return "解密后得到的buffer非法";
case EncodeBase64Error:
return "base64加密错误";
case DecodeBase64Error:
return "base64解密错误";
case GenReturnXmlError:
return "xml生成失败";
default:
return null;
}
}
public int getCode() {
return code;
}
public AesException(int code) {
super(getMessage(code));
this.code = code;
}
}
WXPublicUtils.verifyUrl返回了验证结果
WXPublicUtils工具类代码如下:
public class WXPublicUtils {
/**
* 验证Token
*
* @param msgSignature
* 签名串,对应URL参数的signature
* @param timeStamp
* 时间戳,对应URL参数的timestamp
* @param nonce
* 随机串,对应URL参数的nonce
*
* @return 是否为安全签名
* @throws AesException
* 执行失败,请查看该异常的错误码和具体的错误信息
*/
public static boolean verifyUrl(String msgSignature, String timeStamp, String nonce) throws AesException {
// 这里的 WXPublicConstants.TOKEN 填写你自己设置的Token就可以了
String signature = SHA1.getSHA1("wxtoken", timeStamp, nonce);
if (!signature.equals(msgSignature)) {
throw new AesException(AesException.ValidateSignatureError);
}
return true;
}
}
SHA1.getSHA1(“wxtoken”, timeStamp, nonce);中的wxtoken就是在微信后台的配置自己设置的
使用SHA1算法验证Token:
public class SHA1 {
/**
* 用SHA1算法验证Token
*
* @param token
* 票据
* @param timestamp
* 时间戳
* @param nonce
* 随机字符串
* @return 安全签名
* @throws AesException
*/
public static String getSHA1(String token, String timestamp, String nonce) throws AesException {
try {
String[] array = new String[] { token, timestamp, nonce };
StringBuffer sb = new StringBuffer();
// 字符串排序
Arrays.sort(array);
for (int i = 0; i < 3; i++) {
sb.append(array[i]);
}
String str = sb.toString();
// SHA1签名生成
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuffer hexstr = new StringBuffer();
String shaHex = "";
for (int i = 0; i < digest.length; i++) {
shaHex = Integer.toHexString(digest[i] & 0xFF);
if (shaHex.length() < 2) {
hexstr.append(0);
}
hexstr.append(shaHex);
}
return hexstr.toString();
} catch (Exception e) {
e.printStackTrace();
throw new AesException(AesException.ComputeSignatureError);
}
}
}
接下来对用户的时间进行处理Post消息接收
上诉的weiXinPostService.weixinPost(request);用于处理请求
这里需要使用xml处理一下请求
Xml的工具类提供一哈,顺便规定了一哈处理的事件类型
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_VIDEO = "video";
/**
* 请求消息类型:推送
*/
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转换为map
*
* @param request
* @return
* @throws IOException
*/
public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException {
Map<String, String> map = new HashMap<String, String>();
SAXReader reader = new SAXReader();
InputStream ins = null;
try {
ins = request.getInputStream();
Document doc = reader.read(ins);
Element root = doc.getRootElement();
@SuppressWarnings("unchecked")
List<Element> list = root.elements();
for (Element e : list) {
map.put(e.getName(), e.getText());
}
return map;
} catch (DocumentException e1) {
e1.printStackTrace();
} finally {
ins.close();
}
return null;
}
/**
* 文本消息对象转换成xml
*
* @param textMessage
* 文本消息对象
* @return xml
*/
public static String textMessageToXml(Object textMessage) {
XStream xstream = new XStream();
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
// 图文消息转化为XML
public static String newsMessageToXml(Object newsMessage) {
XStream xstream = new XStream();
xstream.alias("xml", newsMessage.getClass());
xstream.alias("item", new News().getClass());
return xstream.toXML(newsMessage);
}
public static void main(String[] args) {
VoiceMessage voiceMessage=new VoiceMessage();
voiceMessage.setToUserName("aaaa");
voiceMessage.setFromUserName("bbbb");
voiceMessage.setCreateTime(new Date().getTime());
voiceMessage.setMsgType("voice");
Voice voice=new Voice();
voice.setMediaId("llllllllllllllllll");
voiceMessage.setVoice(voice);
String textMessageToXml = textMessageToXml(voiceMessage);
System.out.println(textMessageToXml);
}
}
接下来开始处理事件
/**
* 处理微信发来的请求
*
* @param request
* @return
*/
public String weixinPost(HttpServletRequest request) {
String respMessage = null;
try {
// xml请求解析
Map<String, String> requestMap = MessageUtil.xmlToMap(request);
LOGGER.info(requestMap.toString());
// 发送方帐号(open_id)
String fromUserName = requestMap.get("FromUserName");
// 公众帐号
String toUserName = requestMap.get("ToUserName");
// 消息类型
String msgType = requestMap.get("MsgType");
// 消息内容
String content = requestMap.get("Content");
LOGGER.info("FromUserName is:" + fromUserName + ", ToUserName is:" + toUserName + ", MsgType is:" + msgType);
TextMessage text = new TextMessage();//这里我规定给用户反馈的文本
// 文本消息
if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
}
/*
* else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {//
* 事件推送 String eventType = requestMap.get("Event");// 事件类型
*
* if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {// 订阅
* respContent = "欢迎关注xxx公众号!"; return
* MessageResponse.getTextMessage(fromUserName , toUserName ,
* respContent); } else if
* (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {// 自定义菜单点击事件
* String eventKey = requestMap.get("EventKey");//
* 事件KEY值,与创建自定义菜单时指定的KEY值对应 logger.info("eventKey is:" +eventKey);
* return xxx; } } //开启微信声音识别测试 2015-3-30 else
* if(msgType.equals("voice")) { String recvMessage =
* requestMap.get("Recognition"); //respContent =
* "收到的语音解析结果:"+recvMessage; if(recvMessage!=null){ respContent =
* TulingApiProcess.getTulingResult(recvMessage); }else{ respContent
* = "您说的太模糊了,能不能重新说下呢?"; } return
* MessageResponse.getTextMessage(fromUserName , toUserName ,
* respContent); } //拍照功能 else if(msgType.equals("pic_sysphoto")) {
*
* } else { return MessageResponse.getTextMessage(fromUserName ,
* toUserName , "返回为空"); }
*/
// 事件推送
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
String eventType = requestMap.get("Event");// 事件类型
if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {// 订阅
else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {// 取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息
}
else if (eventType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) {// 获取地址
}
// 自定义菜单点击事件
else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {// 点击类型为Click的按
respMessage = MessageUtil.textMessageToXml(text);
}
/*其他事件继续else if添加就好了*/
}
} catch (Exception e) {
LOGGER.error("error......{}", e.getMessage());
}
return respMessage;
}
规定一个给用户反馈的对象的基础类
public class BaseMessage {
// 开发者微信号
private String ToUserName;
// 发送方帐号(一个OpenID)
private String FromUserName;
// 消息创建时间 (整型)
private Long CreateTime;
// 消息类型(text/image/location/link)
private String MsgType;
// 消息id,64位整型
private Long MsgId;
/**
* 位0x0001被标志时,星标刚收到的消息
*/
private Integer FuncFlag;
public String getToUserName() {
return ToUserName;
}
public void setToUserName(String toUserName) {
ToUserName = toUserName;
}
public String getFromUserName() {
return FromUserName;
}
public void setFromUserName(String fromUserName) {
FromUserName = fromUserName;
}
public Long getCreateTime() {
return CreateTime;
}
public void setCreateTime(Long createTime) {
CreateTime = createTime;
}
public String getMsgType() {
return MsgType;
}
public void setMsgType(String msgType) {
MsgType = msgType;
}
public Long getMsgId() {
return MsgId;
}
public void setMsgId(Long msgId) {
MsgId = msgId;
}
public Integer getFuncFlag() {
return FuncFlag;
}
public void setFuncFlag(Integer funcFlag) {
FuncFlag = funcFlag;
}
}
我这里给用户返回的text为文本,最后都需要转成Xml,使用MessageUtil.textMessageToXml(text);
public class TextMessage extends BaseMessage {
// 消息内容
private String Content;
public String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
}
微信公众号的事件处理业务到这里差不多结束了,其他的可自行补充。