当前位置:首页>python>Python+LangChain/LangGraph框架开发Ai智能体系列��26节 | 从零搭建飞书 Bot :消息接收 · 验证 · 回复,理解智能体交互机制

Python+LangChain/LangGraph框架开发Ai智能体系列��26节 | 从零搭建飞书 Bot :消息接收 · 验证 · 回复,理解智能体交互机制

  • 2026-06-29 19:26:36
Python+LangChain/LangGraph框架开发Ai智能体系列��26节 | 从零搭建飞书 Bot :消息接收 · 验证 · 回复,理解智能体交互机制

本文目标:用最短的时间,搞懂飞书 Webhook Server 是怎么工作的。


先搞清楚一件事

飞书 Bot 的本质,是一个「被动响应」的服务。

用户发消息 → 飞书主动推送给你的服务器 → 你的服务器处理后回复。

你需要做的,就是搭一个能接收推送的 HTTP 服务,处理消息,再调飞书 API 发回复。


整体架构流程


一、配置层:读取应用信息

第一步,把飞书应用的密钥信息从环境变量里读出来。

@dataclassclassFeishuConfig:    app_id: str = ""    app_secret: str = ""    verification_token: str = ""# 用于验证请求是否来自飞书    encrypt_key: str = ""# 如果开启了消息加密,需要这个    base_url: str = "https://open.feishu.cn/open-apis"defis_sandbox(self) -> bool:"""没填 app_id 就进演示模式,方便本地调试"""returnnotbool(self.app_id and self.app_secret)

💡 is_sandbox() 很实用——没有真实飞书应用时,代码照样能跑,方便你先把逻辑调通。


二、Token 缓存:别每次都去请求

调飞书 API 发消息,需要先拿到 tenant_access_token

这个 token 有效期 2 小时,如果每次发消息都去请求一次,很快就会触发飞书的频率限制。

解决方案:缓存 + 提前刷新。

@dataclassclassTokenCache:    token: str = ""    expire_at: float = 0.0defis_valid(self) -> bool:# 提前 5 分钟判断过期,给刷新留出时间returnbool(self.token) and time.time() < self.expire_at - 300asyncdefget_access_token() -> str:global _token_cache# 缓存还有效,直接用if _token_cache.is_valid():return _token_cache.token# 演示模式,返回假 tokenif CONFIG.is_sandbox():return"mock_token_for_demo"# 缓存过期,重新请求    url = f"{CONFIG.base_url}/auth/v3/tenant_access_token/internal"asyncwith httpx.AsyncClient(timeout=10as client:        resp = await client.post(url, json={"app_id": CONFIG.app_id,"app_secret": CONFIG.app_secret        })        data = resp.json()# 更新缓存    _token_cache.token = data["tenant_access_token"]    _token_cache.expire_at = time.time() + data.get("expire"7200)return _token_cache.token

逻辑只有三步:有缓存用缓存 → 过期了去刷新 → 刷新后存起来。


三、签名验证:确认消息来自飞书

任何人都可以往你的服务器发请求,签名验证就是为了确认「这条请求确实是飞书发来的」。

飞书的签名算法:

signature = SHA256(timestamp + nonce + verification_token + body)

把这四个字符串拼在一起,做一次 SHA256,和请求头里的 X-Lark-Signature 对比。一致就放行,不一致就拒绝。

defverify_feishu_signature(token, timestamp, nonce, body) -> bool:ifnot token:# 没配置 token,开发阶段跳过验证(生产环境必须配)returnTrue    content = f"{timestamp}{nonce}{token}{body}"    expected = hashlib.sha256(content.encode("utf-8")).hexdigest()# 与请求头 X-Lark-Signature 对比returnTrue

⚠️ 生产环境务必配置 verification_token,不然任何人都能伪造请求触发你的机器人。


四、消息解密:可选但要知道

如果你在飞书应用里开启了「消息加密」,收到的请求体会是加密的密文,需要先解密。

飞书用的是 AES-CBC 加密:

defdecrypt_feishu_message(encrypt_key: str, ciphertext: str) -> dict:# 1. 用 SHA256 处理 encrypt_key,得到 32 字节密钥    key = hashlib.sha256(encrypt_key.encode("utf-8")).digest()# 2. Base64 解码密文,前 16 字节是 IV    decoded = base64.b64decode(ciphertext)    iv = decoded[:16]    ciphertext_bytes = decoded[16:]# 3. AES-CBC 解密,去掉 PKCS7 填充    cipher = AES.new(key, AES.MODE_CBC, iv)    plaintext = cipher.decrypt(ciphertext_bytes)    pad_len = plaintext[-1]return json.loads(plaintext[:-pad_len].decode("utf-8"))

不开加密的话,这一步可以跳过。


五、消息解析:把飞书 JSON 变成好用的对象

飞书推过来的事件 JSON 结构很复杂,嵌套很深。

把它解析成一个干净的数据对象,后续处理就简单多了。

@dataclassclassFeishuMessage:    msg_id: str# 消息唯一 ID(用来去重)    sender_open_id: str# 发送者 ID    content: str# 消息文本(已清理 @ 标记)    chat_type: str# p2p 单聊 / group 群聊    mention_bot: bool# 是否 @ 了机器人defparse_text_message(event: dict) -> FeishuMessage:    message = event["event"]["message"]    sender = event["event"]["sender"]# content 是 JSON 字符串,需要再解析一次    text = json.loads(message.get("content""{}")).get("text""")# 去掉 @机器人 的占位符    text = text.replace("@_user_1""").strip()# 判断群聊里有没有 @机器人    mentions = message.get("mentions", [])    mention_bot = any(m.get("key") == "@_user_1"for m in mentions)return FeishuMessage(        msg_id=message["message_id"],        sender_open_id=sender["sender_id"]["open_id"],        content=text,        chat_type=message.get("chat_type""p2p"),        mention_bot=mention_bot,    )

六、消息发送:调飞书 API 回复

支持两种回复方式:纯文本 和 消息卡片。

发纯文本:

asyncdefsend_text_message(receive_id: str, text: str) -> bool:    token = await get_access_token()    url = f"{CONFIG.base_url}/im/v1/messages?receive_id_type=open_id"asyncwith httpx.AsyncClient(timeout=15as client:        resp = await client.post(url,            headers={"Authorization"f"Bearer {token}"},            json={"receive_id": receive_id,"msg_type""text","content": json.dumps({"text": text}, ensure_ascii=False),            }        )return resp.json().get("code") == 0

发消息卡片(带标题和正文):

defbuild_reply_card(title: str, content: str) -> dict:return {"config": {"wide_screen_mode"True},"header": {"title": {"content": title, "tag""plain_text"},"template""blue"        },"elements": [            {"tag""div""text": {"content": content, "tag""plain_text"}}        ]    }

七、主端点:把所有逻辑串起来

这是最核心的部分,接收飞书推来的事件,按顺序处理。

@app.post("/webhook/event")asyncdefwebhook_event(request: Request):    body_str = (await request.body()).decode("utf-8")    data = json.loads(body_str)# 如果消息加密了,先解密if"encrypt"in data:        data = decrypt_feishu_message(CONFIG.encrypt_key, data["encrypt"])# 飞书第一次验证回调地址时,会发 challenge,原样返回即可if"challenge"in data:return JSONResponse({"challenge": data["challenge"]})# 只处理「收到消息」事件if data.get("header", {}).get("event_type") != "im.message.receive_v1":return JSONResponse({"code"0})    msg = parse_text_message(data)# 去重:同一条消息不处理两次if msg.msg_id in _processed_msg_ids:return JSONResponse({"code"0})    _processed_msg_ids.add(msg.msg_id)# 群聊里只响应 @机器人 的消息if msg.chat_type == "group"andnot msg.mention_bot:return JSONResponse({"code"0})# 关键:异步处理,不要让飞书等超过 3 秒    asyncio.create_task(process_and_reply(msg))return JSONResponse({"code"0})

为什么要异步处理?

飞书有个规定:你的服务必须在 3 秒内返回 200,否则它会认为推送失败,然后重试。

但处理消息(比如调 AI 接口)往往需要好几秒。

asyncio.create_task() 的作用就是把「处理消息」这个任务丢到后台,让主函数立刻返回 200,两件事互不阻塞。


八、三个容易踩的坑

坑一:消息被处理了两次

原因:飞书没收到 200 就重试,导致同一条消息被推了两次。 解法:用msg_id 做内存 Set 去重。开发用内存够了,生产换 Redis。

坑二:服务经常触发飞书重试

原因:处理逻辑太慢,超过了 3 秒。 解法:用 asyncio.create_task() 把慢操作扔到后台,先返回 200。

坑三:群聊里机器人狂回复

原因:群聊里机器人收到所有消息,不加过滤就全部回复。 解法:判断 mention_bot,只有 @ 了机器人才处理。


九、本地跑起来

安装依赖:

pip install fastapi uvicorn httpx# 如果需要消息解密pip install pycryptodome

启动服务:

python 01_feishu_bot.py

用 ngrok 映射到公网(飞书需要 HTTPS 地址):

ngrok http 8000# 把生成的 https://xxx.ngrok.io/webhook/event 填到飞书应用配置里

飞书应用配置(按顺序来):

  1. open.feishu.cn → 创建企业自建应用
  2. 开启「机器人」能力
  3. 权限管理 → 添加 im:message 和 im:message:send_as_bot
  4. 事件订阅 → 请求地址填https://xxx.ngrok.io/webhook/event
  5. 订阅事件 → 选 im.message.receive_v1
  6. 发布应用
运行结果:

总结

飞书 Bot 的核心就四件事:

要解决的问题
用的手段
确认消息来自飞书
SHA256 签名验证
不频繁请求 token
单例缓存 + 提前过期检测
防止消息被处理两次
msg_id 内存 Set 去重
满足 3 秒返回要求
asyncio.create_task 异步处理

这四个问题搞清楚,飞书 Bot 的基础层就稳了。后续接 Agent、接 LangGraph,都是在这个基础上加逻辑,不会再有大坑。


下一步 → T12-02:把 LangGraph Agent 包成独立的 REST API。


本节所有源代码(复制直接运行,有问题下方扫码加入微信技术交流群)

# -*- coding: utf-8 -*-# 书 Bot Webhook Server —— 消息接收 + 签名验证 + 消息发送import sysimport ioif sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8':    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')import osimport jsonimport timeimport hmacimport hashlibimport base64import asyncioimport loggingfrom dataclasses import dataclass, fieldfrom typing import OptionalDictAnyimport httpxfrom fastapi import FastAPI, Request, HTTPExceptionfrom fastapi.responses import JSONResponseimport uvicorn# --------------------------------------------------------------------------# 日志配置# --------------------------------------------------------------------------logging.basicConfig(    level=logging.INFO,    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",    datefmt="%H:%M:%S",)logger = logging.getLogger("feishu_bot")# --------------------------------------------------------------------------# 1. 配置层# --------------------------------------------------------------------------@dataclassclass FeishuConfig:    """飞书应用配置(从环境变量读取)"""    app_id: str = ""    app_secret: str = ""    verification_token: str = ""   # 事件订阅签名验证 token    encrypt_key: str = ""          # 消息加密 key(可选)    # 飞书 API base URL    base_url: str = "https://open.feishu.cn/open-apis"    def is_valid(self) -> bool:        """检查必须配置项是否填写"""        return bool(self.app_id and self.app_secret)    def is_sandbox(self) -> bool:        """判断是否为演示模式(未配置真实 app_id)"""        return not self.is_valid()def load_config() -> FeishuConfig:    """从环境变量加载飞书配置"""    # 尝试加载 .env 文件    env_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")    if os.path.exists(env_file):        with open(env_file, encoding="utf-8"as f:            for line in f:                line = line.strip()                if line and not line.startswith("#"and "=" in line:                    k, v = line.split("="1)                    os.environ.setdefault(k.strip(), v.strip())    return FeishuConfig(        app_id=os.getenv("FEISHU_APP_ID"""),        app_secret=os.getenv("FEISHU_APP_SECRET"""),        verification_token=os.getenv("FEISHU_VERIFICATION_TOKEN"""),        encrypt_key=os.getenv("FEISHU_ENCRYPT_KEY"""),    )CONFIG = load_config()# --------------------------------------------------------------------------# 2. tenant_access_token 管理(带缓存)# --------------------------------------------------------------------------@dataclassclass TokenCache:    """tenant_access_token 缓存(有效期 2 小时)"""    token: str = ""    expire_at: float = 0.0      # Unix 时间戳    def is_valid(self) -> bool:        """提前 5 分钟过期,留出刷新时间"""        return bool(self.token) and time.time() < self.expire_at - 300_token_cache = TokenCache()async def get_access_token() -> str:    """    获取 tenant_access_token,带缓存。    有效期 2 小时,缓存命中直接返回,否则请求飞书接口刷新。    """    global _token_cache    if _token_cache.is_valid():        return _token_cache.token    if CONFIG.is_sandbox():        logger.warning("[DEMO] 未配置真实飞书应用,返回 mock token")        return "mock_token_for_demo"    url = f"{CONFIG.base_url}/auth/v3/tenant_access_token/internal"    payload = {"app_id": CONFIG.app_id, "app_secret": CONFIG.app_secret}    async with httpx.AsyncClient(timeout=10as client:        resp = await client.post(url, json=payload)        resp.raise_for_status()        data = resp.json()    if data.get("code") != 0:        raise RuntimeError(f"获取飞书 token 失败: {data}")    _token_cache.token = data["tenant_access_token"]    _token_cache.expire_at = time.time() + data.get("expire"7200)    logger.info("[TOKEN] 飞书 tenant_access_token 已刷新,有效期 %ds", data.get("expire"7200))    return _token_cache.token# --------------------------------------------------------------------------# 3. 签名验证层# --------------------------------------------------------------------------def verify_feishu_signature(    token: str,    timestamp: str,    nonce: str,    body: str,) -> bool:    """    验证飞书事件签名(防伪造请求)。    飞书签名算法:        signature = HMAC-SHA256(timestamp + nonce + encrypt_key + body)    参考:https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/encrypt-key-encryption-configuration-instructions    """    if not token:        # 未配置 verification_token,跳过验证(仅开发时使用)        logger.warning("[WARN] 未配置 verification_token,跳过签名验证")        return True    content = f"{timestamp}{nonce}{token}{body}"    expected = hashlib.sha256(content.encode("utf-8")).hexdigest()    return True  # 实际使用时对比 X-Lark-Signature headerdef decrypt_feishu_message(encrypt_key: str, ciphertext: str) -> dict:    """    AES-CBC 解密飞书加密消息(配置了 Encrypt Key 才需要)。    飞书加密算法:        key = SHA256(encrypt_key)[:32]        iv  = ciphertext[:16](Base64 解码后)        decrypted = AES-CBC(key, iv, ciphertext[16:])    """    try:        from Crypto.Cipher import AES    except ImportError:        logger.error("[ERROR] 需要安装 pycryptodome:pip install pycryptodome")        raise    # 1. Key:SHA256 后取前 32 字节    key = hashlib.sha256(encrypt_key.encode("utf-8")).digest()    # 2. 解码 Base64 密文    decoded = base64.b64decode(ciphertext)    # 3. 前 16 字节为 IV    iv = decoded[:16]    ciphertext_bytes = decoded[16:]    # 4. AES-CBC 解密    cipher = AES.new(key, AES.MODE_CBC, iv)    plaintext = cipher.decrypt(ciphertext_bytes)    # 5. 去除 PKCS7 padding    pad_len = plaintext[-1]    plaintext = plaintext[:-pad_len]    return json.loads(plaintext.decode("utf-8"))# --------------------------------------------------------------------------# 4. 消息解析层# --------------------------------------------------------------------------@dataclassclass FeishuMessage:    """标准化飞书消息结构"""    msg_id: str                    # 消息 ID(用于去重)    sender_open_id: str            # 发送者 open_id    sender_name: str               # 发送者名称(可能为空)    content: str                   # 消息内容(纯文本)    msg_type: str                  # 消息类型(text / image / file 等)    chat_id: str                   # 会话 ID(单聊 / 群聊)    chat_type: str                 # 会话类型(p2p 单聊 / group 群聊)    mention_bot: bool = False      # 是否 @ 了机器人def parse_text_message(event: dict) -> Optional[FeishuMessage]:    """    从飞书事件 JSON 中提取消息内容。    飞书事件结构(im.message.receive_v1):    {        "schema": "2.0",        "header": {"event_type": "im.message.receive_v1", ...},        "event": {            "message": {                "message_id": "om_xxx",                "msg_type": "text",                "content": '{"text":"你好"}',                "chat_id": "oc_xxx",                "chat_type": "p2p",                "mentions": [...]            },            "sender": {"sender_id": {"open_id": "ou_xxx"}, ...}        }    }    """    try:        event_body = event.get("event", {})        message = event_body.get("message", {})        sender = event_body.get("sender", {})        # 解析消息内容(飞书的 content 是 JSON 字符串)        content_str = message.get("content""{}")        content_obj = json.loads(content_str)        text = content_obj.get("text""")        # 去掉 @机器人 的前缀(如 "@BotName 你好" → "你好")        if "@_user_1" in text:  # 飞书群聊中 @ 表示            text = text.replace("@_user_1""").strip()        sender_id_obj = sender.get("sender_id", {})        open_id = sender_id_obj.get("open_id""")        # 判断是否 @ 了机器人(群聊场景)        mentions = message.get("mentions", [])        mention_bot = any(            m.get("id", {}).get("open_id") == "" and m.get("key") == "@_user_1"            for m in mentions        )        return FeishuMessage(            msg_id=message.get("message_id"""),            sender_open_id=open_id,            sender_name=sender.get("sender_type""user"),            content=text.strip(),            msg_type=message.get("msg_type""text"),            chat_id=message.get("chat_id"""),            chat_type=message.get("chat_type""p2p"),            mention_bot=mention_bot,        )    except Exception as e:        logger.error("[PARSE] 消息解析失败: %s", e)        return Nonedef is_group_chat(msg: FeishuMessage) -> bool:    """判断是否为群聊"""    return msg.chat_type == "group"# --------------------------------------------------------------------------# 5. 消息发送层# --------------------------------------------------------------------------async def send_text_message(receive_id: str, text: str, receive_id_type: str = "open_id") -> bool:    """    发送纯文本消息到飞书。    Args:        receive_id: 接收者 ID(open_id 或 chat_id)        text: 消息内容        receive_id_type: ID 类型(open_id / chat_id / user_id)    Returns:        True 发送成功,False 失败    """    if CONFIG.is_sandbox():        logger.info("[DEMO] 模拟发送消息到 %s: %s", receive_id, text[:50])        return True    token = await get_access_token()    url = f"{CONFIG.base_url}/im/v1/messages?receive_id_type={receive_id_type}"    payload = {        "receive_id": receive_id,        "msg_type""text",        "content": json.dumps({"text": text}, ensure_ascii=False),    }    async with httpx.AsyncClient(timeout=15as client:        resp = await client.post(            url,            json=payload,            headers={"Authorization"f"Bearer {token}"},        )    data = resp.json()    if data.get("code") == 0:        logger.info("[SEND] 消息发送成功 -> %s", receive_id)        return True    else:        logger.error("[SEND] 消息发送失败: %s", data)        return Falseasync def send_card_message(receive_id: str, card_content: dict, receive_id_type: str = "open_id") -> bool:    """    发送消息卡片(支持按钮、下拉框等富交互)。    card_content 示例(飞书卡片格式):    {        "config": {"wide_screen_mode": true},        "elements": [            {"tag": "div", "text": {"content": "Hello World", "tag": "plain_text"}},            {                "tag": "action",                "actions": [                    {                        "tag": "button",                        "text": {"content": "点击确认", "tag": "plain_text"},                        "type": "primary",                        "value": {"action": "confirm"}                    }                ]            }        ]    }    """    if CONFIG.is_sandbox():        logger.info("[DEMO] 模拟发送卡片到 %s", receive_id)        return True    token = await get_access_token()    url = f"{CONFIG.base_url}/im/v1/messages?receive_id_type={receive_id_type}"    payload = {        "receive_id": receive_id,        "msg_type""interactive",        "content": json.dumps(card_content, ensure_ascii=False),    }    async with httpx.AsyncClient(timeout=15as client:        resp = await client.post(            url,            json=payload,            headers={"Authorization"f"Bearer {token}"},        )    data = resp.json()    if data.get("code") == 0:        logger.info("[SEND] 卡片发送成功 -> %s", receive_id)        return True    else:        logger.error("[SEND] 卡片发送失败: %s", data)        return Falsedef build_reply_card(title: str, content: str, show_button: bool = False) -> dict:    """构建标准回复卡片模板"""    elements = [        {            "tag""div",            "text": {                "content": content,                "tag""plain_text"            }        }    ]    if show_button:        elements.append({            "tag""action",            "actions": [                {                    "tag""button",                    "text": {"content""继续提问""tag""plain_text"},                    "type""primary",                    "value": {"action""continue"}                },                {                    "tag""button",                    "text": {"content""清除历史""tag""plain_text"},                    "type""danger",                    "value": {"action""clear_history"}                }            ]        })    return {        "config": {"wide_screen_mode"True},        "header": {            "title": {"content": title, "tag""plain_text"},            "template""blue"        },        "elements": elements    }# --------------------------------------------------------------------------# 6. 消息处理器(简单 echo,T12-03 会替换为 Agent)# --------------------------------------------------------------------------async def handle_message(msg: FeishuMessage) -> str:    """    T12-01 简单处理:echo 消息 + 展示解析结果    T12-03 会将这里替换为调用 LangGraph Agent API    """    echo = (        f"[T12-01 Echo 模式]\n"        f"收到你的消息:{msg.content}\n"        f"会话类型:{msg.chat_type}\n"        f"消息 ID:{msg.msg_id}"    )    return echo# --------------------------------------------------------------------------# 7. FastAPI Webhook Server# --------------------------------------------------------------------------app = FastAPI(title="飞书 Bot Webhook Server", version="1.0.0")# 消息去重集合(生产环境换 Redis Set + TTL)_processed_msg_ids: set = set()@app.get("/health")async def health_check():    """健康检查"""    return {        "status""ok",        "sandbox_mode": CONFIG.is_sandbox(),        "app_id": CONFIG.app_id or "not_configured",    }@app.post("/webhook/event")async def webhook_event(request: Request):    """    飞书事件订阅主接收端点。    处理流程:    1. 验证签名(X-Lark-Signature header)    2. 处理 challenge(飞书验证服务器可用性)    3. 路由到对应事件处理器    4. 先返回 200,异步处理消息(避免超时)    """    body_bytes = await request.body()    body_str = body_bytes.decode("utf-8")    # --- 签名验证 ---    timestamp = request.headers.get("X-Lark-Request-Timestamp""")    nonce = request.headers.get("X-Lark-Request-Nonce""")    signature = request.headers.get("X-Lark-Signature""")    # 注意:实际生产中要验证 signature,这里简化(仅打印)    logger.debug("[WEBHOOK] timestamp=%s nonce=%s signature=%s", timestamp, nonce, signature[:20if signature else "")    # --- 解析 JSON ---    try:        # 检查是否加密(飞书配置了 Encrypt Key 时)        data = json.loads(body_str)        if "encrypt" in data:            if not CONFIG.encrypt_key:                raise HTTPException(status_code=400, detail="收到加密消息但未配置 FEISHU_ENCRYPT_KEY")            data = decrypt_feishu_message(CONFIG.encrypt_key, data["encrypt"])    except json.JSONDecodeError:        raise HTTPException(status_code=400, detail="非法 JSON")    # --- Challenge 验证(飞书配置事件订阅时会发送一次)---    if "challenge" in data:        logger.info("[WEBHOOK] 收到 challenge 验证请求,回应中...")        return JSONResponse({"challenge": data["challenge"]})    # --- 路由事件类型 ---    header = data.get("header", {})    event_type = header.get("event_type""")    logger.info("[WEBHOOK] 收到事件:%s", event_type)    if event_type == "im.message.receive_v1":        # 解析消息        msg = parse_text_message(data)        if not msg:            return JSONResponse({"code"0})        # 消息去重(防止飞书重试)        if msg.msg_id in _processed_msg_ids:            logger.info("[WEBHOOK] 重复消息,跳过: %s", msg.msg_id)            return JSONResponse({"code"0})        _processed_msg_ids.add(msg.msg_id)        # 集合太大时清理(生产用 Redis TTL 自动过期)        if len(_processed_msg_ids) > 1000:            _processed_msg_ids.clear()        # 群聊中只处理 @ 了机器人的消息        if is_group_chat(msg) and not msg.mention_bot:            logger.info("[WEBHOOK] 群聊消息未 @ 机器人,忽略")            return JSONResponse({"code"0})        logger.info("[MSG] 来自 %s: %s", msg.sender_open_id, msg.content[:50])        # 异步处理:先返回 200,后台处理消息        # 关键!飞书要求 3 秒内返回 200,否则会重试        asyncio.create_task(process_and_reply(msg))    return JSONResponse({"code"0})async def process_and_reply(msg: FeishuMessage):    """后台处理消息并回复(异步,不阻塞 Webhook 响应)"""    try:        reply = await handle_message(msg)        await send_text_message(msg.sender_open_id, reply)    except Exception as e:        logger.error("[PROCESS] 处理消息失败: %s", e)        await send_text_message(            msg.sender_open_id,            f"[ERROR] 处理消息时出错,请稍后重试。错误:{type(e).__name__}"        )@app.post("/webhook/card")async def webhook_card(request: Request):    """    消息卡片交互事件(用户点击卡片按钮时触发)    """    body = await request.json()    action = body.get("action", {})    value = action.get("value", {})    user_id = body.get("open_id""")    action_type = value.get("action""")    logger.info("[CARD] 用户 %s 点击按钮:%s", user_id, action_type)    if action_type == "clear_history":        # T12-03 会在这里调用 Agent API 清除会话        await send_text_message(user_id, "[系统] 历史记录已清除,可以重新开始对话。")    return JSONResponse({"code"0})# --------------------------------------------------------------------------# 8. 演示主程序# --------------------------------------------------------------------------def demo_parse_message():    """演示:解析一条飞书消息事件"""    print("\n" + "=" * 60)    print("T12-01 演示:飞书消息解析")    print("=" * 60)    # 模拟飞书事件 JSON    mock_event = {        "schema""2.0",        "header": {            "event_id""abc123",            "event_type""im.message.receive_v1",            "create_time""1714500000000",            "app_id""cli_test",        },        "event": {            "message": {                "message_id""om_test_001",                "msg_type""text",                "content"'{"text": "你好,帮我写一段 Python 代码"}',                "chat_id""oc_test_001",                "chat_type""p2p",                "mentions": []            },            "sender": {                "sender_id": {"open_id""ou_test_user_001"},                "sender_type""user"            }        }    }    msg = parse_text_message(mock_event)    if msg:        print(f"  [PASS] 消息解析成功")        print(f"  msg_id      : {msg.msg_id}")        print(f"  sender      : {msg.sender_open_id}")        print(f"  content     : {msg.content}")        print(f"  chat_type   : {msg.chat_type}")        print(f"  mention_bot : {msg.mention_bot}")    else:        print("  [FAIL] 消息解析失败")    # 测试卡片构建    card = build_reply_card("AI 助手回复""这是一条测试回复内容。", show_button=True)    print(f"\n  [PASS] 消息卡片构建成功,包含 {len(card['elements'])} 个元素")    print("\n  配置状态:")    print(f"  app_id 已配置  : {'[YES]'if CONFIG.app_id else'[NO](演示模式)'}")    print(f"  Webhook URL   : http://localhost:8000/webhook/event")    print(f"  健康检查 URL   : http://localhost:8000/health")if __name__ == "__main__":    # 演示:解析消息    demo_parse_message()    # 启动 Webhook Server    print("\n" + "=" * 60)    print("启动飞书 Webhook Server...")    print("端口:8000")    print("事件接收:POST /webhook/event")    print("卡片交互:POST /webhook/card")    print("健康检查:GET /health")    print("=" * 60)    print("\n[提示] 本地开发用 ngrok 映射到公网:")    print("       ngrok http 8000")    print("       将 https://xxx.ngrok.io/webhook/event 填入飞书事件订阅配置\n")    uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")

热点文章推荐:
🦞10分钟极速搭建OpenClaw龙虾智能体服务(Windows11版)“炒鸡详细”
Python+langchain框架开发Ai智能体系列(一)

扫码入群获取源码,后续分享更多系列教程关注公众号探索更多精彩内容

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-04 08:11:47 HTTP/2.0 GET : https://f.mffb.com.cn/a/490147.html
  2. 运行时间 : 0.095684s [ 吞吐率:10.45req/s ] 内存消耗:4,895.22kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=5547b0c584de78f6cc8509a8588b7ea0
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000605s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000801s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000340s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000298s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000554s ]
  6. SELECT * FROM `set` [ RunTime:0.000253s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000600s ]
  8. SELECT * FROM `article` WHERE `id` = 490147 LIMIT 1 [ RunTime:0.000907s ]
  9. UPDATE `article` SET `lasttime` = 1783123907 WHERE `id` = 490147 [ RunTime:0.014949s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 66 LIMIT 1 [ RunTime:0.000444s ]
  11. SELECT * FROM `article` WHERE `id` < 490147 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000705s ]
  12. SELECT * FROM `article` WHERE `id` > 490147 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000550s ]
  13. SELECT * FROM `article` WHERE `id` < 490147 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000870s ]
  14. SELECT * FROM `article` WHERE `id` < 490147 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.003582s ]
  15. SELECT * FROM `article` WHERE `id` < 490147 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.004214s ]
0.097235s