Skip to content

Repository files navigation

wecom-aibot-.net-sdk

企业微信智能机器人 .NET SDK —— 基于 WebSocket 长连接通道,提供消息收发、流式回复、模板卡片、事件回调、文件下载解密、媒体素材上传等核心能力。

本 SDK 从官方 @wecom/aibot-node-sdk(https://github.com/WecomTeam/aibot-node-sdk) 协议对齐的 .NET 实现,无需 Node 网关进程,API 设计保持一致。

特性

  • 🔗 WebSocket 长连接 —— 客户端主动外连,不需要公网 IP、域名或回调 URL
  • 🔐 自动认证 —— 连接建立后自动发送认证帧
  • 💓 心跳保活 —— 连续未收到回执即判定连接已死并重连
  • 🔄 断线重连 —— 指数退避(1s → 2s → 4s → … → 30s 上限);认证失败与网络断开使用独立计数器
  • 🚫 顶下线保护 —— 收到 disconnected_event 后不再重连,避免多实例互踢形成风暴
  • 🌊 流式回复 —— StreamSession 自动把增量累积成协议要求的全量帧,并按 ack 节流
  • 🃏 模板卡片 —— 5 种卡片类型、流式 + 卡片组合、卡片原地更新
  • 📤 主动推送 —— 向指定会话推送 Markdown / 卡片 / 媒体,不依赖回调帧
  • 串行回复队列 —— 同一 req_id 串行发送并等待回执
  • 🔒 文件下载解密 —— 内置 AES-256-CBC(32 字节块 PKCS#7)
  • 📎 媒体素材上传 —— 三步分片上传,自动分级并发与重试
  • 🧩 DI / 托管集成 —— AddWeComAiBot + IWeComAiBotHandler,每条消息独立作用域
  • 📦 双目标框架 —— net8.0 / net10.0,源生成序列化、零反射、AOT 友好

安装

dotnet add package WeCom.AiBot
dotnet add package WeCom.AiBot.Extensions.Hosting   # 需要 DI / IHostedService 时

快速开始

using Microsoft.Extensions.Hosting;
using WeCom.AiBot.Extensions.Hosting;
using WeCom.AiBot.Streaming;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddWeComAiBot(options =>
{
    options.BotId = Environment.GetEnvironmentVariable("WECOM_BOT_ID")!;
    options.Secret = Environment.GetEnvironmentVariable("WECOM_BOT_SECRET");
});
builder.Services.AddWeComAiBotHandler<MyBotHandler>();

await builder.Build().RunAsync();

// ------------------------------------------------------------

public sealed class MyBotHandler(IMyLlm llm) : IWeComAiBotHandler
{
    public async Task OnTextAsync(TextMessageContext ctx, CancellationToken ct)
    {
        await using StreamSession session = ctx.BeginStream();

        // 只喂增量,SDK 负责累积成全量帧、按 ack 节流、保证结束帧送达
        await foreach (string delta in llm.StreamAsync(ctx.Content, ct))
            await session.AppendAsync(delta, ct);

        await session.CompleteAsync(ct);
    }

    public Task OnEnterChatAsync(EventContext ctx, CancellationToken ct) =>
        ctx.ReplyWelcomeAsync("您好!有什么可以帮您的?", ct);   // 须在 5 秒内完成
}

IWeComAiBotHandler 的所有方法都有默认空实现,只覆写关心的部分即可。

两个必须知道的协议特性

1. 流式内容是「全量刷新」,不是增量追加

同一 stream.id 的后续帧会覆盖之前的内容。把大模型的 delta 逐个直接发出去, 用户会看到内容不断被替换成最后一小段。

StreamSession 内部累积完整文本再发,因此你只需要喂增量。若要直接用底层 API, ReplyStreamAsynccontent 必须是到目前为止的完整文本。

2. 同一 req_id 的回复必须串行等回执

逐个 delta 发送会在队列里积压,延迟越拉越大。StreamSession 在「上一帧未回执」 或「距上次刷帧不足最小间隔」时跳过中间帧,结束帧则一定送达

API 一览

连接

成员 说明
StartAsync 启动后台连接循环(不等待认证)
StopAsync 停止连接
WaitForAuthenticatedAsync 等待认证完成;主动推送前应先等待
IsConnected / IsAuthenticated / IsDisplaced 连接状态
ReadInboundAsync 以异步流读取收到的消息与事件

回复(被动,需透传 req_id

方法 说明
BeginStream 推荐。创建流式会话,自动处理全量刷新与节流
ReplyStreamAsync 发送一帧流式回复(content 须为完整内容)
ReplyStreamNonBlockingAsync 同上,但上一帧未回执时跳过
ReplyStreamWithCardAsync 流式 + 模板卡片组合
ReplyWelcomeTextAsync / ReplyWelcomeCardAsync 欢迎语,5 秒窗口
ReplyTemplateCardAsync 回复模板卡片
UpdateTemplateCardAsync 更新卡片,5 秒窗口task_id 须一致
ReplyMediaAsync 回复媒体消息

主动推送

方法 说明
SendMarkdownAsync 推送 Markdown
SendTemplateCardAsync 推送模板卡片
SendMediaAsync 推送媒体消息

媒体

方法 说明
UploadMediaAsync 三步分片上传,返回 media_id(3 天有效)
DownloadFileAsync 下载并用消息自带的 aeskey 解密

事件

事件 说明
Connected WebSocket 已连接(认证未完成)
Authenticated 认证成功
Disconnected 连接断开
Reconnecting 即将重连(含序号、延迟、是否由认证失败触发)
Error 发生错误
Displaced 被新连接顶下线,此后不再重连

配置

属性 默认值 说明
BotId 机器人 ID(必填)
Secret 机器人 Secret,与 SecretAlias 二选一
SecretAlias 别名如 alias:wecom-bot,运行时解析
WebSocketUrl wss://openws.work.weixin.qq.com 私有化部署时改为管理端提供的地址
ReconnectBaseDelay 1s 重连基础延迟
ReconnectMaxDelay 30s 重连延迟上限
MaxReconnectAttempts 10 断线重连上限,-1 无限
MaxAuthFailureAttempts 5 认证失败重试上限,-1 无限
HeartbeatInterval 30s 心跳间隔
MaxMissedHeartbeats 2 连续未回执次数达到即判死
ReplyAckTimeout 5s 等待回执超时
MaxReplyQueueLength 500 单个 req_id 排队上限
RemoteCertificateValidationCallback 自签证书校验(私有化部署)
ConfigureWebSocket 底层 ClientWebSocketOptions 逃生舱

Secret 别名

配置文件里只出现标识符,真实密钥留在进程环境:

options.SecretAlias = "alias:wecom-bot";

默认解析器读环境变量 WECOM_BOT_SECRET_WECOM_BOT(去掉 alias: 前缀、转大写、 非字母数字换成下划线)。可用 AddWeComSecretResolver<T>() 换成 KeyVault 等实现。

示例

示例 说明
samples/WeCom.AiBot.Sample.Echo 流式回显、欢迎语、卡片交互、文件下载解密
samples/WeCom.AiBot.Sample.WebConsole 浏览器控制台:实时看收到的消息 + 主动推送 + 人工接管
samples/WeCom.AiBot.Sample.EuCoreBridge 桥接 EU.Core.Agent:SSE deltaStreamSession

运行 Echo 示例

需要 .NET SDK 10.0.100+(见 global.json),以及在企业微信管理端创建好的机器人。

$env:WECOM_BOT_ID     = '你的机器人ID'
$env:WECOM_BOT_SECRET = '你的机器人Secret'

dotnet run --project samples/WeCom.AiBot.Sample.Echo

$env: 只在当前 PowerShell 会话内有效。凭据不要写进代码或提交进仓库。

启动后看到这几行就说明通了:

info: WeCom.AiBot.WeComAiBotClient[0] 正在连接 wss://openws.work.weixin.qq.com …
info: WeCom.AiBot.WeComAiBotClient[0] 连接已建立,发送认证帧。
info: WeCom.AiBot.WeComAiBotClient[0] 认证成功。

然后在企微里找这个机器人对话:

操作 预期
当天首次进入会话 5 秒内收到欢迎语
发任意文本 逐字流式回显,内容逐步刷新而非重复堆叠
发含「卡片」二字的文本 收到按钮交互卡片
点卡片按钮 卡片原地更新为「已确认 ✅ / 已取消 ❌」
发图片或文件 日志打出解密后字节数,并回一条确认消息
断网 30 秒再恢复 自动重连并恢复收发
另起一个同 botId 实例 旧实例收到 disconnected_event 后干净退出、不重连

⚠️ 同一个 botId 只允许一条活跃长连接。调试时别同时开两个实例 —— 它们会互相顶下线。

WECOM_BOT_SECRET 留空则回落到别名方式,示例内置别名 alias:echo-bot

$env:WECOM_BOT_SECRET          = ''
$env:WECOM_BOT_SECRET_ECHO_BOT = '你的机器人Secret'

运行 Web 控制台示例

调试时最缺的两件事——看见机器人收到了什么随手发一条测试消息——都在这个页面上。

$env:WECOM_BOT_ID     = '你的机器人ID'
$env:WECOM_BOT_SECRET = '你的机器人Secret'

dotnet run --project samples/WeCom.AiBot.Sample.WebConsole

启动后自动打开 http://127.0.0.1:5080(加 --no-browser 可关掉)。页面分两栏:

  • 左栏实时流水 —— 收到的消息、发出的回复、连接状态变化,经 SSE 实时推送。 点任意一条可把它的会话 ID 回填到右栏;图片与文件带「下载」按钮,取的是本机解密后的副本 (企微原始链接 5 分钟失效且内容加密,链不过去)。只留最近 20 个,进程重启即清空。
  • 右栏发送面板 —— Markdown、模板卡片(带「填入示例卡片」按钮)、媒体文件上传后推送。

右上角的人工接管开关决定收到消息后怎么办:

模式 行为
关(默认) 机器人自动流式回显,页面同时记录收发两侧
不自动回复,消息挂在页面上等你手动回

人工回复走的是主动推送而不是被动回复。被动回复必须透传 req_id,而人打字要几十秒 甚至几分钟,那时 req_id 早已过期。代价是回复在企微里不与原消息形成引用关系。

模板卡片在提交前会本地校验 card_typecard_action(文本通知型与图文展示型必填) 与 task_id 字符集,直接给中文提示,省得拿服务端错误码去翻文档。

⚠️ 这个页面能以机器人身份发消息,因此只绑回环地址 127.0.0.1 且不做鉴权。 需要给同事用就得自己加鉴权并改 WECOM_CONSOLE_URL——别直接把它暴露到公网。

别名方式为 alias:web-console,对应 WECOM_BOT_SECRET_WEB_CONSOLE

被顶下线时本示例不会退出进程StopHostWhenDisplaced = false),页面上会留一条醒目记录 说明为什么不再收消息——与 Echo 示例的默认行为刻意不同。

运行 EuCoreBridge 示例

$env:WECOM_BOT_ID     = '你的机器人ID'
$env:WECOM_BOT_SECRET = '你的机器人Secret'
$env:EUCORE_BASE_URL  = 'http://localhost:5000'
$env:EUCORE_AGENT_ID  = '已发布 Agent 的 GUID'

dotnet run --project samples/WeCom.AiBot.Sample.EuCoreBridge

EUCORE_RUN_PATH 可直接指定完整路径(优先于 EUCORE_AGENT_ID); EUCORE_SHOW_TOOL_TRACE=false 关闭「🔧 正在调用」回显。别名为 alias:bridge-bot

看原始协议帧

企微的实际下发结构偶尔与文档对不上,排查时原始帧是唯一可信的依据。 模板卡片事件的原始体 Echo 示例默认就会打出来;要看全部推送帧,把日志级别降到 Debug:

$env:Logging__LogLevel__Default = 'Debug'

排查

构建报 MSB3027 / MSB3021「文件被锁定」 —— 示例还在跑,bin 下的 DLL 被占用。 先 Ctrl+C 停掉,或:

Get-Process -Name 'WeCom.AiBot.Sample.Echo' -ErrorAction SilentlyContinue | Stop-Process

更新卡片被拒 errcode=42045card_action Missing or Invalid —— 文本通知型 (text_notice)与图文展示型(news_notice必填 card_action,且 type 只能取 1(跳转 URL)或 2(打开小程序),不填或取 0 都会被拒。

更新卡片被拒 errcode=40058 —— task_id 必须与回调收到的完全一致,取不到就发出去 该字段会被整个省略,服务端必拒。且更新有 5 秒窗口,收到事件后别做耗时操作再回。

文件下载报 aeskey 不是合法的 Base64 字符串 —— 确认 aeskey 取自同一条消息: 每个下载链接的密钥都不同,链接本身也只有 5 分钟有效期。

认证失败 —— 日志会打出 errcode / errmsg。认证失败与网络断开用独立的重试预算, 到上限后抛 WeComAuthFailureException 并停止重连 —— 这种情况重试无益,去核对 botIdsecret

欢迎语没收到 —— enter_chat 只在用户当天首次进入单聊会话时触发,当天再进不会重复触发。

与官方 Node SDK 的差异

方面 Node SDK 本 SDK
回复队列竞态 定时器回调 + 自增 seq 防过期超时误杀 req_id 一把互斥门 + 作用域超时,该竞态结构上不存在
流式全量刷新 调用方自行拼接完整内容 StreamSession 自动累积
分片上传失败 收集全部错误后汇总抛出 首个分片彻底失败即快速终止,不再白跑流量
日志 自定义 Logger 接口 标准 ILogger
chunk_index 类型注释写 1 基,实现是 0 基 与实现一致(0 基),并在注释中说明
模板卡片事件 声明 event_key / task_id 平铺在 event 线上实际嵌在 event.template_card_event 里,两种形态都能解析
aeskey 解码 Buffer.from(k,'base64') 恰好兼容 URL 安全字母表 显式宽松解码,兼容 - _ 与缺失填充
card_action 声明为可选,README 示例未带 注释标明文本通知型 / 图文展示型必填,否则 errcode=42045

构建与测试

dotnet build WeCom.AiBot.sln
dotnet test  WeCom.AiBot.sln     # net8.0 与 net10.0 各跑一遍

单元测试使用内存假传输,不需要真实机器人凭据即可覆盖认证、心跳判死、退避序列、 顶下线禁重连、队列串行与超时、流式累积与截断、加解密往返等行为。

作者

xiaochanghaihttps://github.com/xiaochanghai

仓库地址:https://github.com/xiaochanghai/wecom-aibot-dotnet-sdk,欢迎提 Issue 与 PR。

License

MIT © xiaochanghai

About

企业微信智能机器人 .net SDK

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages