Files
iAs/wechat
2026-06-25 17:23:11 +08:00
..
2026-06-25 17:23:11 +08:00
2026-06-25 17:23:11 +08:00
2026-06-25 17:23:11 +08:00

wechat 模块使用文档

wechat 是一个封装微信 iLink Bot API 的 Rust crate,负责微信 Bot 的扫码登录、监听器生命周期管理、长轮询收消息和文本消息发送。

当前模块只提供库能力,不会自动启动后台任务,也不会自动持久化登录态。业务服务需要主动创建 WeChatClient、完成登录或恢复登录态,然后调用收发消息接口。

模块结构

wechat/
  src/
    lib.rs       # 模块导出
    client.rs    # WeChatClient,封装所有 HTTP API 调用
    types.rs     # iLink Bot API 请求、响应和消息结构

公开模块:

pub mod client;
pub mod types;

常用类型:

use wechat::client::WeChatClient;
use wechat::types::{GetUpdatesResp, LoginResult, MessageItem, WeixinMessage};

Cargo 引用

本仓库根目录已经把 wechat 加入 workspace

[workspace]
members = ["service", "ai", "common", "wechat"]

如果其他 workspace crate 需要使用它,在对应 Cargo.toml 里加入:

[dependencies]
wechat = { path = "../wechat" }

环境变量

变量 必填 默认值 说明
WEIXIN_BASE_URL https://ilinkai.weixin.qq.com iLink Bot API 地址。测试、代理或私有部署时可覆盖。

示例:

export WEIXIN_BASE_URL="https://ilinkai.weixin.qq.com"

基本生命周期

推荐调用顺序:

  1. 创建客户端:WeChatClient::new(None)
  2. 扫码登录:login(...)
  3. 持久化登录结果:保存 tokenaccount_idbase_url
  4. 注册监听器:notify_start()
  5. 循环拉取消息:receive_messages()
  6. 处理用户消息并回复:send_text(...)
  7. 服务退出前注销监听器:notify_stop()

快速开始

下面示例会扫码登录,注册监听器,然后持续接收用户文本消息并原样回复。

use std::time::Duration;

use wechat::client::WeChatClient;
use wechat::types::WeixinMessage;

#[tokio::main]
async fn main() -> Result<(), String> {
    let client = WeChatClient::new(None);

    let login = client
        .login(
            |qrcode_url| {
                println!("二维码地址: {qrcode_url}");
            },
            120,
        )
        .await?;

    println!("登录成功: account_id={}", login.account_id);

    client.notify_start().await?;

    loop {
        let updates = client.receive_messages().await?;

        for msg in updates.msgs {
            if msg.msg_type != Some(WeixinMessage::TYPE_USER) {
                continue;
            }

            let Some(from_user_id) = msg.from_user_id.as_deref() else {
                continue;
            };

            let Some(text) = msg.text_content() else {
                continue;
            };

            client
                .send_text(from_user_id, &format!("收到: {text}"), msg.context_token.as_deref())
                .await?;
        }

        tokio::time::sleep(Duration::from_secs(1)).await;
    }
}

登录

完整登录接口:

pub async fn login(
    &self,
    on_qrcode: impl Fn(&str),
    timeout_secs: u64,
) -> Result<LoginResult, String>

参数说明:

参数 说明
on_qrcode 获取二维码内容后的回调。当前实现会传入 qrcode_img_content,通常是可打开或可展示为二维码的内容。
timeout_secs 扫码确认总超时时间,单位秒。

返回值:

pub struct LoginResult {
    pub token: String,
    pub account_id: String,
    pub base_url: String,
    pub user_id: String,
}

登录内部流程:

  1. get_qrcode() 调用 /ilink/bot/get_bot_qrcode 获取二维码。
  2. 终端渲染二维码,同时打印二维码链接。
  3. poll_qr_status() 轮询 /ilink/bot/get_qrcode_status
  4. 用户扫码并确认后,把 bot_tokenilink_bot_id 写入客户端内存。
  5. 返回 LoginResult,供业务层持久化。

扫码状态值:

状态 含义 当前处理
wait 等待扫码 继续轮询
scaned 已扫码,等待微信确认 提示用户确认
scaned_but_redirect 需要切换 IDC 使用 redirect_host 更新轮询地址
confirmed 登录成功 保存 token/account_id 并返回
expired 二维码过期 返回错误

恢复登录态

wechat 模块不负责把登录态写入数据库或文件。业务层应在登录成功后保存:

  • LoginResult.token
  • LoginResult.account_id
  • LoginResult.base_url
  • get_updates_buf() 返回值,建议在每次 receive_messages() 成功后保存

下次启动时恢复:

let mut client = WeChatClient::new(None);

client
    .set_auth(
        "持久化保存的 token",
        "持久化保存的 account_id",
        "持久化保存的 base_url",
    )
    .await;

client
    .set_updates_buf("持久化保存的 get_updates_buf")
    .await;

client.notify_start().await?;

注意:set_auth 需要 &mut self,因此恢复登录态时变量要声明为 mut

注册和注销监听器

注册监听器:

client.notify_start().await?;

注销监听器:

client.notify_stop().await?;

这两个接口分别调用:

  • POST /ilink/bot/msg/notifystart
  • POST /ilink/bot/msg/notifystop

建议:

  • 登录或恢复登录态后先调用 notify_start(),再开始拉消息。
  • 进程收到退出信号时调用 notify_stop()
  • 如果进程异常退出,下一次启动后仍应重新调用 notify_start()

接收消息

接口:

pub async fn receive_messages(&self) -> Result<GetUpdatesResp, String>

响应结构:

pub struct GetUpdatesResp {
    pub ret: i32,
    pub errcode: Option<i32>,
    pub errmsg: Option<String>,
    pub msgs: Vec<WeixinMessage>,
    pub get_updates_buf: String,
    pub longpolling_timeout_ms: Option<i64>,
}

说明:

  • 该接口调用 /ilink/bot/getupdates
  • 客户端内部维护 updates_buf,并在响应里的 get_updates_buf 非空时自动更新。
  • 如果请求超时,当前实现返回一个空消息列表,不视为致命错误。
  • 如果 ret != 0errcode != 0,当前实现会写 warn 日志,但仍把响应返回给调用方。

文本消息提取:

for msg in updates.msgs {
    if msg.msg_type == Some(WeixinMessage::TYPE_USER) {
        if let Some(text) = msg.text_content() {
            println!("收到文本: {text}");
        }
    }
}

消息关键字段:

字段 说明
msg_type 1 表示用户消息,2 表示 Bot 消息。
from_user_id 发送者 ID。回复私聊消息时通常作为 send_textto
to_user_id 接收者 ID。
group_id 群消息 ID,群聊场景可能非空。
item_list 消息内容列表,可包含文本、图片、语音、文件、视频。
context_token 微信上下文令牌。回复时透传可保持多轮上下文。

发送文本消息

接口:

pub async fn send_text(
    &self,
    to: &str,
    text: &str,
    context_token: Option<&str>,
) -> Result<String, String>

参数说明:

参数 说明
to 接收者用户 ID。通常来自收到消息的 from_user_id
text 要发送的文本内容。
context_token 可选上下文令牌,建议透传收到消息里的 context_token

返回值是本地生成的 client_id,可用于日志追踪。

示例:

let client_id = client
    .send_text(
        from_user_id,
        "你好,我是 Bot",
        msg.context_token.as_deref(),
    )
    .await?;

println!("消息已提交: client_id={client_id}");

当前发送实现固定构造:

  • message_type = 2,表示 Bot 消息
  • message_state = 2,表示完成态
  • item_list = [MessageItem::text(text)]

非文本消息

协议类型已经定义了以下消息 item

  • TextItem
  • ImageItem
  • VoiceItem
  • FileItem
  • VideoItem

当前便捷发送接口只实现了 send_text()。如果需要发送图片、文件或视频,需要新增对应的发送方法,构造 WeixinMessage.item_list 中的 MessageItem

接收侧可以通过 MessageItem.item_type 判断类型:

for item in msg.item_list.unwrap_or_default() {
    match item.item_type {
        Some(MessageItem::TYPE_TEXT) => {
            let text = item.text_item.and_then(|v| v.text);
            println!("文本: {:?}", text);
        }
        Some(MessageItem::TYPE_IMAGE) => {
            println!("图片: {:?}", item.image_item);
        }
        Some(MessageItem::TYPE_VOICE) => {
            println!("语音: {:?}", item.voice_item);
        }
        Some(MessageItem::TYPE_FILE) => {
            println!("文件: {:?}", item.file_item);
        }
        Some(MessageItem::TYPE_VIDEO) => {
            println!("视频: {:?}", item.video_item);
        }
        _ => {}
    }
}

长轮询循环建议

推荐业务层把接收循环放到独立 tokio task:

let client_for_loop = client.clone();

tokio::spawn(async move {
    loop {
        match client_for_loop.receive_messages().await {
            Ok(updates) => {
                for msg in updates.msgs {
                    // 处理消息
                }
            }
            Err(err) => {
                tracing::warn!("接收微信消息失败: {err}");
                tokio::time::sleep(std::time::Duration::from_secs(3)).await;
            }
        }
    }
});

注意:

  • WeChatClient 实现了 Clone,内部 token、account_id 和 updates_buf 使用 Arc<Mutex<_>> 共享。
  • receive_messages() 已经处理普通超时,业务层不需要把空消息视为错误。
  • 处理消息时建议过滤 WeixinMessage::TYPE_BOT,避免重复处理 Bot 自己发出的消息。
  • 每次成功拉取后可以调用 get_updates_buf() 并持久化,降低重启后重复收消息的概率。

与 AI 回复集成示例

示例逻辑:收到用户文本后调用业务自己的 AI 函数,再把结果发回微信。

async fn handle_wechat_message(client: &WeChatClient, msg: WeixinMessage) -> Result<(), String> {
    if msg.msg_type != Some(WeixinMessage::TYPE_USER) {
        return Ok(());
    }

    let Some(from_user_id) = msg.from_user_id.as_deref() else {
        return Ok(());
    };

    let Some(user_text) = msg.text_content() else {
        return Ok(());
    };

    let answer = call_ai(user_text).await?;

    client
        .send_text(from_user_id, &answer, msg.context_token.as_deref())
        .await?;

    Ok(())
}

async fn call_ai(input: &str) -> Result<String, String> {
    Ok(format!("AI 回复: {input}"))
}

API 端点

WeChatClient 当前使用以下 iLink Bot API

方法 路径 用途
GET /ilink/bot/get_bot_qrcode 获取扫码登录二维码
GET /ilink/bot/get_qrcode_status 查询扫码状态
POST /ilink/bot/msg/notifystart 注册监听器
POST /ilink/bot/msg/notifystop 注销监听器
POST /ilink/bot/getupdates 长轮询拉取消息
POST /ilink/bot/sendmessage 发送消息

请求头由模块内部生成:

  • Content-Type: application/json
  • Content-Length
  • iLink-App-ClientVersion
  • X-WECHAT-UIN
  • 登录后请求额外带:
    • Authorization: Bearer <token>
    • AuthorizationType: ilink_bot_token

日志

模块使用 tracing 输出日志,主要 target 为:

ias::auth

业务程序可初始化日志:

tracing_subscriber::fmt()
    .with_env_filter("info,ias::auth=debug")
    .init();

错误处理

所有公开异步方法当前返回 Result<_, String>

常见错误:

场景 典型错误 处理建议
二维码过期 二维码已过期 重新调用 login()
登录超时 登录超时 增大 timeout_secs 或重新登录。
token 无效 HTTP 401/403 或接口返回错误 清理持久化登录态,重新扫码登录。
长轮询超时 返回空 msgs 正常情况,继续下一轮。
网络错误 请求失败 延迟重试,必要时检查 WEIXIN_BASE_URL

当前限制

  • ILINK_APP_ID 当前是空字符串。如接入方要求 app id,需要在 client.rs 中配置或改造成环境变量。
  • 登录态只保存在内存中,重启恢复需要业务层调用 set_auth()set_updates_buf()
  • 只提供文本发送便捷方法 send_text()
  • 群聊回复策略需要业务层结合 group_id 和实际 iLink 协议进一步确认。
  • send_text() 当前不解析服务端响应体,只在 HTTP 层确认请求成功后返回本地 client_id

排障清单

  1. 确认能访问 WEIXIN_BASE_URL
  2. 确认扫码后在微信侧完成确认。
  3. 登录后先调用 notify_start(),再调用 receive_messages()
  4. 如果收不到消息,检查 get_updates_buf 是否恢复了过旧状态;必要时清空后重试。
  5. 如果发送失败,检查 token 是否过期,以及 to 是否使用了正确的 from_user_id
  6. 打开 ias::auth=debug 日志观察 API 请求和状态变化。