八、快速上手处理微信请求:接收、解析、处理微信后台发送给你服务器的消息(一)
目录
很多人对于微信的请求处理一直弄不太明白,其实难,主要是微信返回的数据类型太多了,解析器了比较绕(微信文档全是坑就对了)
文章中的代码保证可以使用,提供完整的代码哦
一、官方内容解释~~~飞机票
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。详情请见“发送消息-被动回复消息”。
如果开发者需要对用户消息在5秒内立即做出回应,即使用“发送消息-被动回复消息”接口向用户被动回复消息时,可以在
关于重试的消息排重,推荐使用msgid排重。
PS:一定要保证及时响应微信哦,如果不能及时响应,一定要处理掉微信多次推送带来的消息重叠,不然用户会收到这种不友好的提示。
二、微信返回数据内容
PS:此内容只是示例,因返回数据类型不同参数会有一定出入,具体数据见每个接口的内容解释,其他内容不再赘述,详见微信官方文档解释。直奔主题吧
<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 | 消息类型,文本为text |
Content | 文本消息内容 |
MsgId | 消息id,64位整型 |
三 、接收、解析微信请求数据
1、controller层
接收请求的方法的参数只有两个
HttpServletRequest request
HttpServletResponse response
文中如果出现city 可以忽略,我得demo是多个公众号一起用的,city仅作为公众号的识别代码用的,sequence是一个雪花算法的随机数,作为唯一识别码,可以忽略或者写1L传递即可
为了方便理解,下文的代码我不提取单独的变量了,使用的地方我尽量直接拿,以防止看起来乱
boolean isGet = request.getMethod().toLowerCase().equals("get");
········PrintWriter out;
try {
out = response.getWriter();
if(isGet) {
// 微信加密签名
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echostr = request.getParameter("echostr");
// 通过校验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if(SignUtil.checkSignature(token, signature, timestamp, nonce)) {
out.print(echostr);
out.flush();
out.close();
out = null;
}
} else {
String respXml = messageService.processRequest(request, city);
logger.debug("WeChatServiceController.replyMessage:respXml = " + respXml + "请求序列:");
out.print(respXml);
out.flush();
out.close();
out = null;
}
} catch (Exception e1) {
logger.error("系统异常", e1);
}
验签方法 checkSignature(不太建议使用三目运算符,个人感觉数量级上来判断比较慢,写demo图省事)
public static boolean checkSignature(String token, String signature, String timestamp, String nonce) {
String[] arra = new String[]{token, timestamp, nonce};
//将token,timestamp,nonce组成数组进行字典排序
Arrays.sort(arra);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arra.length; i++) {
sb.append(arra[i]);
}
MessageDigest md = null;
String stnStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(sb.toString().getBytes());
stnStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
sb = null;
return stnStr != null ? stnStr.equals(signature.toUpperCase()) : false;
}
2、service层
回调事件处理接口
public String processRequest(HttpServletRequest request);
这个接口要做几件事
①、解析微信消息处理成map对象
第一步就是把微信返回的数据处理成方便我们解析的消息类型,我们这里解析成Map
Map<String,String> wechatMap = parseXml(request);//调用处 记得判空
public static Map<String,String> parseXml(HttpServletRequest request) throws Exception {
// 将解析结果存储在HashMap中
Map<String,String> map = new HashMap();
// 从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;
}
②、去除重复请求
消息重复内容拦截,利用缓存锁来拦截重复的消息内容,返回空内容在controller层以处理可以返回空字符串
if (StringUtils.isBlank(wechatMap.get("MsgId")) && wechatMap.containsKey("Ticket")) {
msgid = wechatMap.get("Ticket") + wechatMap.get("CreateTime");
}
if (StringUtils.isNotBlank(msgid) && !lockService.addLock(CODE_LOCK, fromUserName, msgid)) {
logger.warn("msgid + "重复请求已处理");
return "";
}
③、记录日志
根据业务需求的不同可以考虑记录微信请求的数据内容。
④、处理不同类型的消息请求
这应该是个比较大的交互模块,下文采取一种自定义注解的形式来处理,并只阐述大体内容,具体方法后文给出链接可供使用
首先 我们拿到两个重要的参数
//微信返回类型
String msgType = wechatMap.get("MsgType");
// 事件类型
String eventType = wechatMap.get("Event");
一种处理方式是常规的处理,将所有的返回类型和事件类型枚举出来,然后在不同的if结构体去做不同的操作,作者没有采取这种方式。
第二种是用的自定义注解的形式处理的,包括后文的扫码和文本处理都采用这种方式
//以返回类型+事件类型形成一个唯一的类型KEY
String messageType = msgType + msgType;
//直接传入自定义注解处理类,靠注解去寻找不同的接口。想使用spring的注解也可以。
MessageClassifyService merchantRegisterBean = MessageBeanContextUtil.getmessageTypeBean(messageType);
if (null == merchantRegisterBean) {
//TODO 异常处理
return null;
}
//wechatMap是上文解析的map参数
MessageDtoOut out = merchantRegisterBean.messageDispose(wechatMap);
MessageClassifyService.java接口
/**
* @version V1.0.0
* @Description 消息分类处理基类接口
* @Author
* @Package
* @CreateTime
*/
public interface MessageClassifyService {
/**
* 基础接口
*
* @param wechatMap 微信返回信息
* @return
*/
public MessageDtoOut messageDispose(Map<String,String> wechatMap,long sequence);
}
MessageBaseServiceImpl.java这是基础接口实现类,当找不到对应接口的时候可以跳转到这个进行返回,(实际用起来意义不大,强迫症容错用的)
public class MessageBaseServiceImpl implements MessageClassifyService {
private final static Logger logger = Logger.getLogger(MessageBaseServiceImpl.class);
/**
* 基础接口
*
* @param wechatMap 微信返回信息
* @param object 用户自定义数据
* @return
*/
@Override
public MessageDtoOut messageDispose(Map<String,String> wechatMap, long sequence) {
logger.warn("传入参数:wechatMap = [" + wechatMap + "],, sequence = [" + sequence + "]");
return new MessageDtoOut("您正在访问消息路径,请勿重复发送数据");
}
}
我们拿文本消息接口来举例(文本消息接口,用户在公众号发送文字,微信就会推送该事件到我们服务器)
MessTextServiceImpl.java文本消息处理实现类和图像处理类
MessageStaticType.MESSAGE_TEXT
该参数实际上就是一个静态常量,列出了所有微信的状态组合~~以返回类型+事件类型形成一个唯一的类型KEY这样注解就能通过前文组成的key直接找到对应的实现类,一个接口对应多个实现类,只需要改变实现类的getMessageType 值就可以自动跳到不同的实现类
@MessageSelector(getMessageType = MessageStaticType.MESSAGE_TEXT)
public class MessTextServiceImpl implements MessageClassifyService {
private final static Logger logger = Logger.getLogger(MessTextServiceImpl.class);
/**
* 基础接口
*
* @param wechatMap 微信返回信息
* @param object 用户自定义数据
* @return
*/
@Override
public MessageDtoOut messageDispose(Map<String,String> wechatMaplong sequence) {
//处理微信消息内容
//具体处理方法在消息处理讲解
}
}
@MessageSelector(getMessageType = MessageStaticType.MESSAGE_IMAGE)
public class MessImageServiceImpl implements MessageClassifyService {
private final static Logger logger = Logger.getLogger(MessImageServiceImpl.class);
/**
* 基础接口
*
* @param wechatMap 微信返回信息
* @param object 用户自定义数据
* @return
*/
@Override
public MessageDtoOut messageDispose(Map<String,String> wechatMap,long sequence) {
return new MessageDtoOut("测试-您发送的是图片,请勿重复发送数据");
}
}
⑤、自定义操作
四、重点的处理类demo
列举三个重点实现类的内容处理