接收与回复消息:文本、图片、语音、视频、地理位置
好,咱们接着往下聊。上一章我们把服务器验证和基础消息收发搞定了。这一章,我带你看看公众号里最常见的五种消息类型:文本、图片、语音、视频、地理位置。
说实话,这五种类型覆盖了90%以上的用户交互场景。我自己做过的几十个公众号项目里,基本就靠它们撑场面。你想想看,用户发个图片、说句话、分享个位置,你都能自动回复——这不就是智能客服的雏形吗?
消息的通用结构
在动手写代码之前,咱们先搞清楚一件事:所有消息都长一个样。嗯,我是说结构上。
每条用户发来的消息,XML里都包含这些公共字段:
| 字段名 | 说明 |
|---|---|
| ToUserName | 开发者微信号(也就是你的公众号) |
| FromUserName | 发送方账号(用户的OpenID) |
| CreateTime | 消息创建时间(整型时间戳) |
| MsgType | 消息类型,比如 text、image、voice 等 |
| MsgId | 消息ID,64位整型,用于去重 |
我刚开始做的时候,没注意MsgId的去重作用。结果有一次用户连续发了两次同样的消息,我回复了两遍,用户直接投诉了。后来我才知道,MsgId是全局唯一的,可以用来判断是不是重复消息。
文本消息
文本消息是最简单的,也是我们最先要处理的。
用户发来的文本消息XML长这样:
<xml>
<ToUserName><![CDATA[gh_xxx]]></ToUserName>
<FromUserName><![CDATA[o_xxx]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
注意看,Content字段就是用户输入的文字。我们回复的时候,也是返回一个类似的XML,只不过ToUserName和FromUserName要互换。
回复文本消息的XML格式:
<xml>
<ToUserName><![CDATA[o_xxx]]></ToUserName>
<FromUserName><![CDATA[gh_xxx]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[收到,你好呀!]]></Content>
</xml>
图片消息
图片消息稍微复杂一点。用户发来图片时,XML里会带一个PicUrl字段,这是图片的临时链接。注意,这个链接有效期只有3天。
用户发来的图片消息:
<xml>
<ToUserName><![CDATA[gh_xxx]]></ToUserName>
<FromUserName><![CDATA[o_xxx]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<PicUrl><![CDATA[http://mmbiz.qpic.cn/xxx]]></PicUrl>
<MediaId><![CDATA[media_id_xxx]]></MediaId>
<MsgId>1234567890123456</MsgId>
</xml>
回复图片消息时,你不能直接发图片链接。微信要求你通过MediaId来回复。这个MediaId怎么来的?你得先通过素材管理接口上传图片,拿到MediaId,然后才能用。
回复图片消息的XML:
<xml>
<ToUserName><![CDATA[o_xxx]]></ToUserName>
<FromUserName><![CDATA[gh_xxx]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[media_id_xxx]]></MediaId>
</Image>
</xml>
语音消息
语音消息和图片类似,也是通过MediaId来传递。不过语音多了一个Format字段,告诉你语音的格式,通常是amr或speex。
用户发来的语音消息:
<xml>
<ToUserName><![CDATA[gh_xxx]]></ToUserName>
<FromUserName><![CDATA[o_xxx]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<MediaId><![CDATA[media_id_xxx]]></MediaId>
<Format><![CDATA[amr]]></Format>
<MsgId>1234567890123456</MsgId>
</xml>
回复语音消息时,同样需要MediaId。格式和图片类似,只是把Image标签换成Voice:
<xml>
<ToUserName><![CDATA[o_xxx]]></ToUserName>
<FromUserName><![CDATA[gh_xxx]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<Voice>
<MediaId><![CDATA[media_id_xxx]]></MediaId>
</Voice>
</xml>
个人经验:我曾经做过一个语音点歌的公众号。用户发语音说歌名,我通过语音识别接口转成文字,然后匹配歌曲。这里有个坑——语音识别接口返回的文字可能不准,尤其是方言。所以后来我加了一个确认环节,让用户说「是」或「不是」来确认。
视频消息
视频消息和图片、语音类似,但多了一个ThumbMediaId字段,这是视频封面的MediaId。
用户发来的视频消息:
<xml>
<ToUserName><![CDATA[gh_xxx]]></ToUserName>
<FromUserName><![CDATA[o_xxx]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[video]]></MsgType>
<MediaId><![CDATA[media_id_xxx]]></MediaId>
<ThumbMediaId><![CDATA[thumb_media_id_xxx]]></ThumbMediaId>
<MsgId>1234567890123456</MsgId>
</xml>
回复视频消息时,你需要提供MediaId、Title和Description:
<xml>
<ToUserName><![CDATA[o_xxx]]></ToUserName>
<FromUserName><![CDATA[gh_xxx]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[video]]></MsgType>
<Video>
<MediaId><![CDATA[media_id_xxx]]></MediaId>
<Title><![CDATA[视频标题]]></Title>
<Description><![CDATA[视频描述]]></Description>
</Video>
</xml>
地理位置消息
地理位置消息比较特殊。用户发来位置时,XML里会带经纬度、缩放比例和具体地址。
用户发来的地理位置消息:
<xml>
<ToUserName><![CDATA[gh_xxx]]></ToUserName>
<FromUserName><![CDATA[o_xxx]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[location]]></MsgType>
<Location_X>23.137466</Location_X>
<Location_Y>113.352425</Location_Y>
<Scale>20</Scale>
<Label><![CDATA[广州市天河区xxx路]]></Label>
<MsgId>1234567890123456</MsgId>
</xml>
回复地理位置消息时,你可以回复文本,也可以回复图文。我个人习惯回复文本,告诉用户「已收到您的位置,我们正在为您查找附近的服务」。然后后台根据经纬度去查数据库,再通过客服消息接口推送给用户。
避坑指南:我曾经做过一个附近门店查询的功能。用户发位置,我回复附近的门店列表。一开始我直接用Location_X和Location_Y去查数据库,结果发现经纬度是反的!微信的Location_X是纬度,Location_Y是经度。很多新手都会搞混,你一定要注意。
实战:统一消息处理入口
好了,五种消息类型都讲完了。在实际项目中,你不会为每种消息单独写一个接口。而是写一个统一的消息处理入口,根据MsgType分发到不同的处理函数。
我一般这样写:
// 伪代码示例
function handleMessage($xmlData) {
$msgType = $xmlData->MsgType;
switch ($msgType) {
case 'text':
return handleText($xmlData);
case 'image':
return handleImage($xmlData);
case 'voice':
return handleVoice($xmlData);
case 'video':
return handleVideo($xmlData);
case 'location':
return handleLocation($xmlData);
default:
return handleDefault($xmlData);
}
}
这样写的好处是,以后新增消息类型,只需要加一个case分支,不会影响已有的逻辑。我在好几个项目里都用这个模式,维护起来特别省心。
嗯,这一章的内容就到这。五种消息类型,说白了就是XML格式不同,回复方式不同。你只要把每种类型的XML结构记清楚,写代码就是体力活了。下一章,咱们聊聊图文消息和客服消息,那才是真正展示技术的地方。