上一篇写了如何通过微信开发者认证,今天来讲下如何接收用户的消息,我们以接收用户的订阅消息为例。
微信用户消息格式
在开发者文档的接收事件推送文档中,说明了用户订阅消息的请求实体,内容如下:
1 2 3 4 5 6 7
| <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: 消息类型,事件消息为event
- Event: 事件类型,订阅消息为subscribe
消息真实性验证
每次开发者接收用户消息的时候,微信也都会带上前面三个参数(signature、timestamp、nonce)访问开发者设置的URL,开发者依然通过对签名的效验判断此条消息的真实性。效验方式与首次提交验证申请一致。
所以每个订阅消息的http请求都会带有(signature、timestamp、nonce)这3个参数和上面的xml请求实体,服务端可以选择是否校验消息的真实性,建议校验,这样会比较安全。
接收消息后的响应内容
了解了消息请求的入参后,还需要知道我们处理请求后,需要返回什么样的内容给用户,这个在开发者文档里面好像没有提及,参考各方资料后知道需要返回一段xml内容,格式如下:
1 2 3 4 5 6 7 8
| <xml> <Content>感谢您关注我的公众账号[愉快]</Content> <CreateTime>1423022113</CreateTime> <FromUserName>zzm</FromUserName> <FuncFlag>0</FuncFlag> <MsgType>text</MsgType> <ToUserName>zzm</ToUserName> </xml>
|
- ToUserName:
用户微信账号的OpenID
- FromUserName:
开发者微信号
- CreateTime: 消息发送时间,秒数
- FuncFlag: 这个暂时不知道是什么,默认值为0
- MsgType: 消息类型,文档消息可以为text和其他,这里我们以最简单的text文本消息为例
- Content: 返回给订阅用户的消息内容,可以加表情
PS: ToUserName和FromUserName这2个参数和请求的xml实体要相反,这个也比较好理解,用户发了条消息过来,你要发个消息回去,ToUserName就变成了用户,FromUserName变成了你自己的公众账号了。
服务端开发
- 了解了http请求的入参和出参,我们可以来开发我们的API了,
talk is cheap, show me code
。
MainController.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @RequestMapping(value = "/index", method = RequestMethod.POST) public @ResponseBody ResponseEntity<String> receive(@RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce, @RequestBody String body) throws Exception { log.info("receive message start"); log.info(String.format("signature:%s, timestamp:%s, nonce:%s", signature, timestamp, nonce));
if (!wechatAuth(signature, timestamp, nonce)) { log.info("wechat auth failed"); return new ResponseEntity<String>("wechat auth failed.", HttpStatus.BAD_REQUEST); }
log.info(String.format("body:%s", body)); TextMessage requestMessage = XmlUtil.toTextMessage(body); log.info(String.format("requestMessage:%s", requestMessage));
TextMessage textMessage = null; String msgType = requestMessage.getMsgType(); String toUserName = requestMessage.getToUserName(); String fromUserName = requestMessage.getFromUserName(); if (MessageType.event.name().equals(msgType)) { if (EventType.subscribe.name().equals(requestMessage.getEvent())) { String message = "感谢您关注我的公众账号[愉快]"; textMessage = new TextMessage(toUserName, fromUserName, MessageType.text.name(), message, TimeUtil.currentSeconds()); } }
String responseMessage = XmlUtil.toXml(textMessage); HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.add("Content-Type", "text/html; charset=utf-8"); log.info(String.format("response message: %s", responseMessage)); log.info("receive message finish"); return new ResponseEntity<String>(responseMessage, responseHeaders, HttpStatus.OK); }
|
XmlUtil.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import com.zzm.wechat.model.TextMessage; import org.apache.commons.io.IOUtils;
import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import java.io.StringReader; import java.io.StringWriter;
public class XmlUtil {
public static String toXml(TextMessage textMessage) throws Exception { if (textMessage == null) return "";
JAXBContext context = JAXBContext.newInstance(TextMessage.class); Marshaller m = context.createMarshaller(); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); m.setProperty(Marshaller.JAXB_FRAGMENT, true);
StringWriter sw = new StringWriter(); m.marshal(textMessage, sw); return sw.toString(); }
public static TextMessage toTextMessage(String xml) throws Exception { JAXBContext jaxbContext = JAXBContext.newInstance(TextMessage.class); Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); StringReader reader = new StringReader(xml); TextMessage textMessage = (TextMessage) jaxbUnmarshaller.unmarshal(reader); IOUtils.closeQuietly(reader); return textMessage; } }
|
- 定义消息的model类,这里需要用到xml的一些annotation。
XmlUtil.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "xml") public class TextMessage { private String fromUserName; private String toUserName; private String msgType; private int funcFlag = 0; private String content; private String event; private long createTime;
public TextMessage() { }
public TextMessage(String fromUserName, String toUserName, String msgType, String content, long createTime) { this.fromUserName = fromUserName; this.toUserName = toUserName; this.msgType = msgType; this.content = content; this.createTime = createTime; }
public String getToUserName() { return toUserName; }
@XmlElement(name = "ToUserName") public void setToUserName(String toUserName) { this.toUserName = toUserName; }
@Override public String toString() { } }
|
我的公众账号是赵芝明的公账号
,有兴趣的也可以加一下,以后这个公共账号的功能肯定会慢慢丰富的。