在java微信第三方平台开发(二)中写了授权事件的处理,并且第三方平台代公众号发起网页授权,获取用户信息和发红包等基本业务。接下来代公众号处理消息和事件。这时候就需要用到在开发者资料中填写的公众号消息与事件接受的URL了
一、接受处理微信消息和事件信息
URL地址
格式为:http://xxxx.com/b/weixin2/APPID/callback
当用户在发送文本信息,或者取消关注,上报地理位置等一些列操作时,微信会向该接口地址推送一段加密的xml文件。需要解密后才能获取详细消息类型和事件类型。(相关解密的操作可以查看:java微信第三方平台开发(二))
相关代码:
@RequestMapping(value="/{appid}/callback",method={RequestMethod.GET,RequestMethod.POST})
public void callBackEvent(@PathVariable String appid,
HttpServletResponse response,HttpServletRequest request){
try {
Log.logger.info(appid+"进入callback+++++++++++++++++++++++++++++++++");
weChatThridService.handleMessage(request,response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
<xml>
<ToUserName>ToUserName>
<FromUserName>FromUserName>
<CreateTime>1348831860CreateTime>
<MsgType>MsgType>
<Content>Content>
<MsgId>1234567890123456MsgId>
xml>
ToUserName:公众号的原始ID,接收方
FromUserName:用户的openID,发送方
CreateTime:发送的时间
MsgType:类型,文本时text,如果是图片消息是image
Content: 内容
关于普通消息更多的类型介绍可以参考微信开发文档:
https://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html
2、事件消息
关注取消关注、上报地理位置等等一系列事件消息。
解密后的xml文件:
<xml>
<ToUserName>ToUserName>
<FromUserName>FromUserName>
<CreateTime>1479953039CreateTime>
<MsgType>MsgType>
<Event>Event>
<Latitude>44.541485Latitude>
<Longitude>125.696594Longitude>
<Precision>40.000000Precision>
xml>
我们发现前3项是相同的,但是第四个虽然Element相同都是MsgType,但是内容却不相同,后续可以根据event和text的不同,做出不同的处理。
处理消息xml
既然是处理消息那当然少不了解密xml,然后写一个公用的方法,根据解密后的msgtype的不同来进一步实现不同的业务逻辑(中间可能包括耗时操作,比如用户上报的地理位置的经纬想存在数据库中,方便绘制用户的移动轨迹了(这是有点坏- 。-)),最后需要返回给微信响应。
说到给微信响应的这个就比较有复杂了,微信在推送xml后5s内收不到响应后,会断开连接,并且重新发起请求,总共重试三次,如果不能开发者不能确保自己服务器能响应的话可以直接回复空字符串。如果有业务要求,可以调用客服高级接口回复用户内容。内容可以自己定义。但是不回复的话,微信会推送重复的xml进来,这时就需要排重啦。一个static arraylist< string>(1000),当超过10000时清除key。
/**
* 接收消息 排重
* @param type 消息的类型: 1 文本 0 事件
* @param msgId 文本的内容
* @param FromUserName 消息的发送方,此处为openId
* @param CreateTime 消息创建的时间
* @return false 重复的 true 不重复
*/
public boolean messageExcludeRepeat(int type,String msgId,String FromUserName,String CreateTime ){
//当数量大于10000时,删除
if(excludeRepeatList.size()>=10000){
excludeRepeatList.clear();
}
if(type==1){
String key=msgId+FromUserName+CreateTime;
//判断该文本消息是否是重复的
if(excludeRepeatList.contains(key)){
return false;
}
excludeRepeatList.add(key);
}else{
String key=FromUserName+CreateTime;
//判断该事件消息是否是重复的
if(excludeRepeatList.contains(key)){
return false;
}
excludeRepeatList.add(key);
}
return true;
}
当我们有耗时操作时,不能在5s内回复微信的时候。解决办法有两种:
(1)、先回复微信空串,后续再24小时内利用客服api回复用户,这种办法需要公众号有高级接口权限。
(2)、利用selvet3.0的新特性AsyncContext来实现,微信第一个5s内如果没能完成耗时操作,直接return,但是连接不会断开,重新重request中解析xml。直到完成操作,在返回给微信响应。相关AsyncContext的用法,后续介绍。
下面就第一种方法结合全网发布的必要的规范流程详细写写,包括回复微信的响应内容。
二、全网发布详细流程
微信要求开发者在接到xml文件后,经过一系列处理后,返回给为微信服务器响应。响应的内容为xml格式的文件,并且是加密的xml文件。对于不同的消息类型有不同回复格式,接下来要做的就是,根据不同的msgtype一方面完成我们的业务,另一方面封装好不同的xml文件加密,发送给微信服务器。
根据msgtype来判断
/**
* 处理全网检测发布,回复微信xml
*
* @param request
* @param response
* @throws Exception
*/
public void handleMessage(HttpServletRequest request,
HttpServletResponse response) throws Exception {
String msgSignature = request.getParameter("msg_signature");
if(!StringUtils.isNotBlank(msgSignature)){
return;
}
String timestamp=request.getParameter("timestamp");
String encrypt_type=request.getParameter("encrypt_type");
String nonce=request.getParameter("nonce");
String msg_signature=request.getParameter("msg_signature");
Log.logger.info("timestamp:"+timestamp);
Log.logger.info("encrypt_type:"+encrypt_type);
Log.logger.info("nonce:"+nonce);
Log.logger.info("msg_signature:"+msg_signature);
//验证通过后
StringBuilder sb = new StringBuilder();
BufferedReader in = request.getReader();
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
}
String xml = sb.toString();
Log.logger.info("微信推送的原生:"+xml);
String encodingAesKey =WeChatContants.encodingAesKey;// 第三方平台组件加密密钥
String appId=WeChatContants.THRID_APPID;//从xml中解析
WXBizMsgCrypt pc = new WXBizMsgCrypt(WeChatContants.token, encodingAesKey,appId);
xml = pc.decryptMsg(msg_signature, timestamp, nonce, xml);
Log.logger.info("解密后的:"+xml);
Map<String, String> parseXml = WeChatUtils.parseXml(xml);
String msgType=parseXml.get("MsgType");
String toUserName=parseXml.get("ToUserName");
String fromUserName=parseXml.get("FromUserName");
if("event".equals(msgType)){
Log.logger.info("---------------事件消息--------");
//排重
boolean repeatFlag = messageExcludeRepeat(0, null, fromUserName,parseXml.get("CreateTime"));
if(repeatFlag){
//不重复的
String event = parseXml.get("Event");
replyEventMessage(request,response,event,toUserName,fromUserName);
}else{
//重复的,先回复空串,稍后调用客服接口回复
WeChatUtils.responseReplyMessage(response, "");
//调用客服
replyApiTextMessage(request,response,null,fromUserName,0);
}
}else if("text".equals(msgType)){
Log.logger.info("---------------文本消息--------");
//排重
boolean repeatFlag = messageExcludeRepeat(1, parseXml.get("MsgId"), fromUserName,parseXml.get("CreateTime"));
if(repeatFlag){
//不重复的
String content = parseXml.get("Content");
processTextMessage(request,response,content,toUserName,fromUserName);
}else{
//重复的,先回复空串,稍后调用客服接口回复
WeChatUtils.responseReplyMessage(response, "");
//调用客服
replyApiTextMessage(request,response,null,fromUserName,0);
}
}
}
sb.append("<xml>");
sb.append("<ToUserName>ToUserName>");
sb.append("<FromUserName>FromUserName>");
sb.append("<CreateTime>"+createTime+"CreateTime>");
sb.append("<MsgType>MsgType>");
sb.append("<Content>Content>");
sb.append("xml>");
微信对于事件回复格式的要求:xml中content格式为:文本消息的:event + “from_callback”(例如:LOCATIONfrom_callback)。
微信对于文本时要判断推送过的xml里的content的内容来回复不同的内容。
微信官方参考文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318611&token=5b3df85fa31c9a24daff2e2eac40b549d17a555e&lang=zh_CN
回复事件代码:
/**
* 微信全网接入 事件消息
* @param request
* @param response
* @param event
* @param toUserName
* @param fromUserName
* @throws Exception
*/
private void replyEventMessage(HttpServletRequest request,HttpServletResponse response, String event, String toUserName,String fromUserName) throws Exception {
String content = event + "from_callback";
Log.logger.info("--------------事件回复消息 content="+content + " toUserName="+toUserName+" fromUserName="+fromUserName);
replyTextMessage(request,response,content,toUserName,fromUserName);
}
回复文本消息:
/**
* 微信全网接入 文本消息
* @param request
* @param response
* @param event
* @param toUserName
* @param fromUserName
*/
private void processTextMessage(HttpServletRequest request, HttpServletResponse response,String content,String toUserName, String fromUserName) throws Exception{
if("TESTCOMPONENT_MSG_TYPE_TEXT".equals(content)){
String returnContent = content+"_callback";
replyTextMessage(request,response,returnContent,toUserName,fromUserName);
}else if(StringUtils.startsWithIgnoreCase(content, "QUERY_AUTH_CODE")){
//先回复空串
WeChatUtils.responseReplyMessage(response,"");
//接下来客服API再回复一次消息
replyApiTextMessage(request,response,content.split(":")[1],fromUserName,1);
}
}
/**
* 回复微信服务器"文本消息"
* @param request
* @param response
* @param content
* @param toUserName
* @param fromUserName
* @throws DocumentException
* @throws IOException
*/
public void replyTextMessage(HttpServletRequest request, HttpServletResponse response, String content, String toUserName, String fromUserName) throws Exception {
Long createTime = Calendar.getInstance().getTimeInMillis() / 1000;
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
sb.append("<ToUserName>ToUserName>");
sb.append("<FromUserName>FromUserName>");
sb.append("<CreateTime>"+createTime+"CreateTime>");
sb.append("<MsgType>MsgType>");
sb.append("<Content>Content>");
sb.append("xml>");
String replyMsg = sb.toString();
String returnvaleue = "";
try {
String encodingAesKey =WeChatContants.encodingAesKey;// 第三方平台组件加密密钥
String appId=WeChatContants.THRID_APPID;//从xml中解析
WXBizMsgCrypt pc = new WXBizMsgCrypt(WeChatContants.token,encodingAesKey,appId);
returnvaleue = pc.encryptMsg(replyMsg, createTime.toString(),request.getParameter("nonce"));
Log.logger.info("------------------加密后的返回内容 returnvaleue: "+returnvaleue);
} catch (AesException e) {
e.printStackTrace();
}
WeChatUtils.responseReplyMessage(response, returnvaleue);
}
/**
* 客服接口回复粉丝信息
* @param response
* @param auth_code 当type=1是才有值,type=0为null
* @param fromUserName
* @param type 1 表示全网发布时回复
* 0 表示普通消息回复
* @throws Exception
*/
public void replyApiTextMessage(HttpServletRequest request, HttpServletResponse response, String auth_code, String fromUserName,int type) throws Exception {
//从数据库中获取access_token
int companyId=Integer.parseInt(ConfigUtil.getString("resource/resource","BZ_ID"));
String access_token = getThridToken(companyId);
//模拟客户回复文本消息
Object[]objects={access_token};
String sendMessageTextUrl = MessageFormat.format(WeChatContants.THRID_KEFU_SENDMESSAGE_URL, objects);
//组装post数据
WeChatKeFuSendTextMessageVo textmessageVo=new WeChatKeFuSendTextMessageVo();
textmessageVo.setMsgtype("text");
textmessageVo.setTouser(fromUserName);
WeChatKeFuSendTextVo textContentVo=new WeChatKeFuSendTextVo();
String textContent = "";
if(type==1){
//全网发布回复的内容
textContent=auth_code+"_from_api";
}else{
//普通文本消息回复的内容
textContent="hello,ok!";
}
textContentVo.setContent(textContent);
textmessageVo.setText(textContentVo);
String result = HttpNetUtils.getInstance().httpByJson(sendMessageTextUrl,"POST",textmessageVo);
Log.logger.info("客服回复结果:"+result);
}
/**
* 统一回复微信服务器
* @param response
* @param content
* @throws IOException
*/
public static void responseReplyMessage(HttpServletResponse response,String content) throws IOException{
PrintWriter pw = response.getWriter();
pw.write(content);
pw.flush();
pw.close();
}
以上内容是最近几天断断续续的整理出来的,内容可能不是一气呵成,希望大家能多多指教,共同进步,谢谢。