接收普通消息

当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。

请注意:

1、关于重试的消息排重,推荐使用msgid排重。

2、微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,

可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。详情请见“发送消息-被动回复消息”。

3、如果开发者需要对用户消息在5秒内立即做出回应,即使用“发送消息-被动回复消息”接口向用户被动回复消息时,可以在

公众平台官网的开发者中心处设置消息加密。开启加密后,用户发来的消息和开发者回复的消息都会被加密(但开发者通过客服

接口等API调用形式向用户发送消息,则不受影响)。关于消息加解密的详细说明,请见“发送消息-被动回复消息加解密说明”。

各消息类型的推送XML数据包结构如下:

文本消息

<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位整型

使用网页调试工具调试该接口

图片消息

<xml>

 <ToUserName><![CDATA[toUser]]></ToUserName>

 <FromUserName><![CDATA[fromUser]]></FromUserName>

 <CreateTime>1348831860</CreateTime>

 <MsgType><![CDATA[image]]></MsgType>

 <PicUrl><![CDATA[this is a url]]></PicUrl>

 <MediaId><![CDATA[media_id]]></MediaId>

 <MsgId>1234567890123456</MsgId>

 </xml>


参数 描述

ToUserName 开发者微信号

FromUserName 发送方帐号(一个OpenID)

CreateTime 消息创建时间 (整型)

MsgType image

PicUrl 图片链接(由系统生成)

MediaId 图片消息媒体id,可以调用多媒体文件下载接口拉取数据。

MsgId 消息id,64位整型

使用网页调试工具调试该接口

语音消息

<xml>

<ToUserName><![CDATA[toUser]]></ToUserName>

<FromUserName><![CDATA[fromUser]]></FromUserName>

<CreateTime>1357290913</CreateTime>

<MsgType><![CDATA[voice]]></MsgType>

<MediaId><![CDATA[media_id]]></MediaId>

<Format><![CDATA[Format]]></Format>

<MsgId>1234567890123456</MsgId>

</xml>


参数 描述

ToUserName 开发者微信号

FromUserName 发送方帐号(一个OpenID)

CreateTime 消息创建时间 (整型)

MsgType 语音为voice

MediaId 语音消息媒体id,可以调用多媒体文件下载接口拉取数据。

Format 语音格式,如amr,speex等

MsgID 消息id,64位整型

使用网页调试工具调试该接口


请注意,开通语音识别后,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,增加一个Recongnition字段(注:由于客户端缓存,开发者开启或者关闭语音识别功能,对新关注者立刻生效,对已关注用户需要24小时生效。开发者可以重新关注此帐号进行测试)。开启语音识别后的语音XML数据包如下:

<xml>

<ToUserName><![CDATA[toUser]]></ToUserName>

<FromUserName><![CDATA[fromUser]]></FromUserName>

<CreateTime>1357290913</CreateTime>

<MsgType><![CDATA[voice]]></MsgType>

<MediaId><![CDATA[media_id]]></MediaId>

<Format><![CDATA[Format]]></Format>

<Recognition><![CDATA[腾讯微信团队]]></Recognition>

<MsgId>1234567890123456</MsgId>

</xml>

参数说明:

参数 描述

ToUserName 开发者微信号

FromUserName 发送方帐号(一个OpenID)

CreateTime 消息创建时间 (整型)

MsgType 语音为voice

MediaID 语音消息媒体id,可以调用多媒体文件下载接口拉取该媒体

Format 语音格式:amr

Recognition 语音识别结果,UTF8编码

MsgID 消息id,64位整型

视频消息

<xml>

<ToUserName><![CDATA[toUser]]></ToUserName>

<FromUserName><![CDATA[fromUser]]></FromUserName>

<CreateTime>1357290913</CreateTime>

<MsgType><![CDATA[video]]></MsgType>

<MediaId><![CDATA[media_id]]></MediaId>

<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>

<MsgId>1234567890123456</MsgId>

</xml>


参数 描述

ToUserName 开发者微信号

FromUserName 发送方帐号(一个OpenID)

CreateTime 消息创建时间 (整型)

MsgType 视频为video

MediaId 视频消息媒体id,可以调用多媒体文件下载接口拉取数据。

ThumbMediaId 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。

MsgId 消息id,64位整型

使用网页调试工具调试该接口

小视频消息

<xml>

<ToUserName><![CDATA[toUser]]></ToUserName>

<FromUserName><![CDATA[fromUser]]></FromUserName>

<CreateTime>1357290913</CreateTime>

<MsgType><![CDATA[shortvideo]]></MsgType>

<MediaId><![CDATA[media_id]]></MediaId>

<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>

<MsgId>1234567890123456</MsgId>

</xml>


参数 描述

ToUserName 开发者微信号

FromUserName 发送方帐号(一个OpenID)

CreateTime 消息创建时间 (整型)

MsgType 小视频为shortvideo

MediaId 视频消息媒体id,可以调用多媒体文件下载接口拉取数据。

ThumbMediaId 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。

MsgId 消息id,64位整型

使用网页调试工具调试该接口

地理位置消息

<xml>

<ToUserName><![CDATA[toUser]]></ToUserName>

<FromUserName><![CDATA[fromUser]]></FromUserName>

<CreateTime>1351776360</CreateTime>

<MsgType><![CDATA[location]]></MsgType>

<Location_X>23.134521</Location_X>

<Location_Y>113.358803</Location_Y>

<Scale>20</Scale>

<Label><![CDATA[位置信息]]></Label>

<MsgId>1234567890123456</MsgId>

</xml>


参数 描述

ToUserName 开发者微信号

FromUserName 发送方帐号(一个OpenID)

CreateTime 消息创建时间 (整型)

MsgType location

Location_X 地理位置维度

Location_Y 地理位置经度

Scale 地图缩放大小

Label 地理位置信息

MsgId 消息id,64位整型

使用网页调试工具调试该接口

链接消息

<xml>

<ToUserName><![CDATA[toUser]]></ToUserName>

<FromUserName><![CDATA[fromUser]]></FromUserName>

<CreateTime>1351776360</CreateTime>

<MsgType><![CDATA[link]]></MsgType>

<Title><![CDATA[公众平台官网链接]]></Title>

<Description><![CDATA[公众平台官网链接]]></Description>

<Url><![CDATA[url]]></Url>

<MsgId>1234567890123456</MsgId>

</xml>


参数 描述

ToUserName 接收方微信号

FromUserName 发送方微信号,若为普通用户,则是一个OpenID

CreateTime 消息创建时间

MsgType 消息类型,link

Title 消息标题

Description 消息描述

Url 消息链接

MsgId 消息id,64位整型



<?php

/**

  * wechat php test

  */


//define your token

define("TOKEN", "weixin");

$wechatObj = new wechatCallbackapiTest();

// $wechatObj->valid();

$wechatObj->index();


class wechatCallbackapiTest

{


    public function index(){

        if(isset($_GET['echostr'])) {

            $this->valid();

        } else {

            $this->responseMsg();

        }

    }


    public function valid()

    {

        $echoStr = $_GET["echostr"];


        //valid signature , option

        if($this->checkSignature()){

            echo $echoStr;

            exit;

        }

    }


    public function responseMsg()

    {

        //get post data, May be due to the different environments

        $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];


        //extract post data

        if (!empty($postStr)){

                /* libxml_disable_entity_loader is to prevent XML eXternal Entity Injection,

                   the best way is to check the validity of xml by yourself */

                libxml_disable_entity_loader(true);

                $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);

                $fromUsername = $postObj->FromUserName;

                $toUsername = $postObj->ToUserName;

                $keyword = trim($postObj->Content);

                $type = $postObj->MsgType;

                $time = time();

                $textTpl = "<xml>

                            <ToUserName><![CDATA[%s]]></ToUserName>

                            <FromUserName><![CDATA[%s]]></FromUserName>

                            <CreateTime>%s</CreateTime>

                            <MsgType><![CDATA[%s]]></MsgType>

                            <Content><![CDATA[%s]]></Content>

                            <FuncFlag>0</FuncFlag>

                            </xml>";  


                $imgTpl = "<xml>

                            <ToUserName><![CDATA[%s]]></ToUserName>

                            <FromUserName><![CDATA[%s]]></FromUserName>

                            <CreateTime>%s</CreateTime>

                            <MsgType><![CDATA[%s]]></MsgType>

                            <Image>

                            <MediaId><![CDATA[%s]]></MediaId>

                            </Image>

                            </xml>"; 


                $voiceTpl = "<xml>

                                <ToUserName><![CDATA[%s]]></ToUserName>

                                <FromUserName><![CDATA[%s]]></FromUserName>

                                <CreateTime>%s</CreateTime>

                                <MsgType><![CDATA[%s]]></MsgType>

                                <Voice>

                                <MediaId><![CDATA[%s]]></MediaId>

                                </Voice>

                                </xml>";

                $videoTpl = "<xml>

                            <ToUserName><![CDATA[%s]]></ToUserName>

                            <FromUserName><![CDATA[%s]]></FromUserName>

                            <CreateTime>%s</CreateTime>

                            <MsgType><![CDATA[%s]]></MsgType>

                            <Video>

                            <MediaId><![CDATA[%s]]></MediaId>

                            <Title><![CDATA[%s]]></Title>

                            <Description><![CDATA[%s]]></Description>

                            </Video> 

                            </xml>

                            ";

            $musicTpl = "<xml>

                        <ToUserName><![CDATA[%s]]></ToUserName>

                        <FromUserName><![CDATA[%s]]></FromUserName>

                        <CreateTime>%s</CreateTime>

                        <MsgType><![CDATA[%s]]></MsgType>

                        <Music>

                        <Title><![CDATA[%s]]></Title>

                        <Description><![CDATA[%s]]></Description>

                        <MusicUrl><![CDATA[%s]]></MusicUrl>

                        <HQMusicUrl><![CDATA[%s]]></HQMusicUrl>

                        <ThumbMediaId><![CDATA[%s]]></ThumbMediaId>

                        </Music>

                        </xml>

                        ";


                $newsTpl = "<xml>

                        <ToUserName><![CDATA[%s]]></ToUserName>

                        <FromUserName><![CDATA[%s]]></FromUserName>

                        <CreateTime>%s</CreateTime>

                        <MsgType><![CDATA[%s]]></MsgType>

                        <ArticleCount>2</ArticleCount>

                        <Articles>

                        <item>

                        <Title><![CDATA[%s]]></Title> 

                        <Description><![CDATA[%s]]></Description>

                        <PicUrl><![CDATA[%s]]></PicUrl>

                        <Url><![CDATA[%s]]></Url>

                        </item>

                        <item>

                        <Title><![CDATA[%s]]></Title>

                        <Description><![CDATA[%s]]></Description>

                        <PicUrl><![CDATA[%s]]></PicUrl>

                        <Url><![CDATA[%s]]></Url>

                        </item>

                        </Articles>

                        </xml>";


                if($type == 'text') {

                        $picurl = 'http://i3.mifile.cn/a4/T1UQAgB7_v1RXrhCrK.jpg';


                       $echo  = sprintf($newsTpl,$fromUsername,$toUsername,$time,'news','title1','description1',$picurl,'http://baidu.com','title2','description2',$picurl,'http://taikr.com');




                    // if ($keyword == 1) {

                        // $musicUrl = 'http://sc.111ttt.com/up/mp3/161940/AAA1CD69EBEED018C3AC9CE6CCB8C3B3.mp3';

                        // $media_id = '7Wn1YzkjjNMJ-ZFIUbbQjItbMi6zVzlKoCdvRpkZYSg8yew5yV8yCe8QRx4X4NAR';

                        //     $echo = sprintf($musicTpl,$fromUsername,$toUsername,$time,'music','音乐标题','音乐描述',$musicUrl,$musicUrl,$media_id);

                        // } else {

                        //     $str = '你发送的是文本消息' . $keyword;

                        //      $MsgType = 'text';

                        //      $echo  = sprintf($textTpl,$fromUsername,$toUsername,$time,$MsgType,$str);

                        // }



                  

                } else if ($type == 'image'){

                    $MsgType = 'image';


                    $media_id = '7Wn1YzkjjNMJ-ZFIUbbQjItbMi6zVzlKoCdvRpkZYSg8yew5yV8yCe8QRx4X4NAR';

                    $media_id = $postObj->MediaId;

                    $echo = sprintf($imgTpl,$fromUsername,$toUsername,$time,$MsgType,$media_id);

                    

                } else if ($type == 'voice') {

                

                    $str = $postObj->Recognition;

                     $str = '你说话的语音识别结果是' . $postObj->Recognition;

                    $MsgType = 'text';

                     $echo  = sprintf($textTpl,$fromUsername,$toUsername,$time,$MsgType,$str);


                } else if ($type == 'video') {

                    $str = '你发送的是视频消息,Id是' . $postObj->MediaId;

                    $MsgType = 'text';

                     $echo  = sprintf($textTpl,$fromUsername,$toUsername,$time,$MsgType,$str);

                   // $MsgType = 'video';

                   // $title = '视频标题';

                   // $desc = '视频描述';

                   // $echo = sprintf($videoTpl,$fromUsername,$toUsername,$time,$MsgType,$postObj->MediaId,$title,$desc);

                } else if ($type == 'location') {

                    $str = '你发送的是地理位置消息,纬度是' . $postObj->Location_X. " 经度是" . $postObj->Location_Y . "名称" . $postObj->Label . "精度是" . $postObj->Scale;

                    $MsgType = 'text';

                     $echo  = sprintf($textTpl,$fromUsername,$toUsername,$time,$MsgType,$str);

                } else if ($type == 'link') {

                     $str = '你发送的是链接消息'  . $postObj->title  . $postObj->Description . $postObj->Url;

                    $MsgType = 'text';

                     $echo  = sprintf($textTpl,$fromUsername,$toUsername,$time,$MsgType,$str);

                } else if ($type == 'event') {

                    if ($postObj->EventKey == 'V1001_TODAY_MUSIC') {

                          $musicUrl = 'http://sc.111ttt.com/up/mp3/161940/AAA1CD69EBEED018C3AC9CE6CCB8C3B3.mp3';

                         $media_id = '7Wn1YzkjjNMJ-ZFIUbbQjItbMi6zVzlKoCdvRpkZYSg8yew5yV8yCe8QRx4X4NAR';

                         $echo = sprintf($musicTpl,$fromUsername,$toUsername,$time,'music','音乐标题','音乐描述',$musicUrl,$musicUrl,$media_id);

                    } else if ($postObj->EventKey == 'V1001_GOOD') {

                        $str = 'thank you for your zan';

                       $MsgType = 'text';

                       $echo  = sprintf($textTpl,$fromUsername,$toUsername,$time,$MsgType,$str);

                    }

                }



               

                echo $echo;


        }else {

            echo "";

            exit;

        }

    }

        

    private function checkSignature()

    {

        // you must define TOKEN by yourself

        if (!defined("TOKEN")) {

            throw new Exception('TOKEN is not defined!');

        }

        

        $signature = $_GET["signature"];

        $timestamp = $_GET["timestamp"];

        $nonce = $_GET["nonce"];

                

        $token = TOKEN;

        $tmpArr = array($token, $timestamp, $nonce);

        // use SORT_STRING rule

        sort($tmpArr, SORT_STRING);

        $tmpStr = implode( $tmpArr );

        $tmpStr = sha1( $tmpStr );

        

        if( $tmpStr == $signature ){

            return true;

        }else{

            return false;

        }

    }

}


?>


<think> 嗯,用户想了解飞书机器人接收消息的实现方法和机制。用户当前的问题主要围绕飞书机器人的消息接收机制和实现方法展开。 结合用户之前提供的引用内容,引用[1]提到了飞书机器人的反向代理和内网穿透技术,引用[2]介绍了如何将Dify智能体接入飞书等平台。这些资料显示飞书机器人的消息接收涉及到网络配置和平台对接。 飞书机器人的消息接收机制本质上是通过HTTPS协议实现的。开发者需要先创建自定义机器人应用,配置请求验证(包括Encrypt Key和Verification Token),并在服务器部署接收消息的API接口。当用户与机器人交互,飞书服务器会将消息内容以JSON格式POST到开发者配置的Webhook地址上。 考虑到用户可能是开发者,我需要在回复中详细说明技术实现步骤。首先应该讲解核心机制中的请求验证流程,包括签名验证方法(X-Lark-Signature头部字段)。然后重点说明两种消息接收模式:普通加密模式和事件订阅模式的区别。最后需要给出一个完整的消息处理流程图,展示从用户发送消息到机器人响应的完整过程。 另外用户提供的引用[2]提到LangBot工具可以简化对接流程,这点也值得在回答中提及。我会在代码示例部分展示一个Python实现的签名验证基本框架,这样用户可以快速上手开发。 最后需要为用户准备一些相关问题,比如消息加密方式的选择、消息去重机制、多租户支持等进阶话题。这些问题应该能帮助用户深入了解飞书机器人的高级功能。 </think> 以下是飞书机器人接收消息的实现方法和机制详解,结合技术文档与实际开发经验整理: --- ### **一、核心实现机制** 1. **HTTPS双向验证** - 开发者需在飞书开放平台配置 `Verification Token` 和 `Encrypt Key` - 机器人服务器须实现签名验证:对收到的请求计算 `HMAC-SHA256` 签名,与请求头 `X-Lark-Signature` 比对[^1] ```python # Python签名验证示例 import hashlib, hmac def verify_signature(timestamp, nonce, body, signature): key = f"{timestamp}\n{nonce}\n{body}".encode('utf-8') sign = hmac.new(ENCRYPT_KEY, key, hashlib.sha256).hexdigest() return sign == signature ``` 2. **消息接收模式** | **模式** | **特点** | **适用场景** | |------------------|--------------------------------------------------------------------------|---------------------| | **普通加密模式** | 需解密 `encrypt` 字段获取原始消息,响应需返回加密数据 | 高安全性要求场景 | | **事件订阅模式** | 直接解析明文JSON,需响应 `challenge` 值完成订阅验证[^2] | 快速开发/调试环境 | --- ### **二、消息处理流程** ```mermaid graph TD A[用户发送消息] --> B(飞书服务器) B --> C{消息路由} C -->|匹配机器人| D[POST到开发者配置的URL] D --> E[开发者服务器] E --> F{签名验证?} F -->|成功| G[解析消息体] F -->|失败| H[丢弃请求] G --> I[业务逻辑处理] I --> J[构造响应] J --> K[返回飞书服务器] ``` 1. **消息类型解析** - **文本消息**:`event.message.content` 含JSON结构(如 `{"text":"@机器人 你好"}`) - **富文本/卡片**:通过 `content` 中的 `post` `card` 字段解析 - **事件通知**:用户加群、撤回消息等通过 `header.event_type` 区分(如 `im.message.receive_v1`) 2. **关键字段说明** ```json { "schema": "2.0", "header": { "event_id": "xxxx", // 事件唯一ID "event_type": "im.message.receive_v1", "token": "VERIFICATION_TOKEN" // 需校验 }, "event": { "message": { "chat_id": "oc_xxx", // 群聊ID "content": "{\"text\":\"@机器人 help\"}" } } } ``` --- ### **三、开发实践要点** 1. **防重放攻击** - 校验请求头 `X-Lark-Request-Timestamp`:拒绝超过5分钟的请求[^1] - 使用 `nonce` 随机数防重放 2. **多租户支持** - 通过 `header.app_id` 识别不同企业实例 - 按租户隔离消息处理逻辑 3. **消息去重机制** - 基于 `event_id` + `timestamp` 建立缓存,避免重复处理 4. **异步处理优化** ```python # 示例:异步响应框架(FastAPI) @app.post("/webhook") async def handle_event(request: Request): # 1. 验证签名 # 2. 立即返回空响应(HTTP 200) # 3. 异步处理业务逻辑 background_tasks.add_task(process_message, request.json()) return "" ``` --- ### **四、调试工具推荐** 1. **飞书开发者工具**:模拟消息发送与事件触发 2. **Ngrok/内网穿透**:本地开发调试必备(如 `ngrok http 8000`)[^1] 3. **开放平台事件追踪**:实查看消息投递状态 > 提示:飞书要求所有消息接口在 **1秒内响应**,超会导致消息重试(最多3次),复杂业务需异步处理[^2]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值