恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

C#封装微信企业号主动推送:从Token获取到消息发送的完整实现

  • 首页
  • 资讯中心
  • /
  • C#封装微信企业号主动推送:从Token获取到消息发送的完整实现

相关资讯

Elasticsearch在即时通讯系统中的应用:从部署到消息检索优化 2026/10/21 21:59:31
从零手写最小LLM Agent:模型、工具、记忆与循环实战指南 2026/10/21 21:54:31
Linux驱动开发进阶:从GPIO到设备树与input子系统完整实战 2026/10/21 21:54:31

最新资讯

2核2G VPS装桌面实战:Xfce+xrdp+Chromium调优指南
AI Agent生产环境容错设计:SITS2026标准与三层熔断机制实战
如何构建个人技能体系:从目标拆解到刻意练习的完整指南
知识工作者的插件化工具箱:从网页抓取到检索增强的实战指南
知识工作插件盘点:从采集到输出,构建高效工具链
AI Agent核心模块拆解:从会聊天到能干活的工程实践

今日推荐

SAP 屏幕上的金额未必存在数据库表里,从 WB2R_SV 的 Net Amount 看懂运行时计算
具身智能中‘指哪打哪’的视觉语言动作联合建模
AI时代人工翻译技术博客的价值与实践指南

本周热门

基于GMM的语音识别系统原理与MATLAB实现详解
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

C#封装微信企业号主动推送:从Token获取到消息发送的完整实现

发布时间:2026/10/21 21:59:31
C#封装微信企业号主动推送:从Token获取到消息发送的完整实现 简介这份rar压缩包提供了一套基于C#的微信企业号主动推送消息完整示例工程适合需要快速对接企业号API的.NET开发者尤其适用于企业向员工或客户推送通知、活动提醒等场景。压缩包体积约35KB共40个文件其中22个C#源代码文件构建了核心逻辑另有3个工程文件、2个资源文件及解决方案文件等目录结构清晰便于直接打开工程查看。已有874人学习下载。工程内包含微信企业号辅助类、消息模型定义和主动发送调用逻辑覆盖文本、图片、语音、视频、图文等消息类型的构造方式并给出发送接口的参数组织与调用示例同时附带WinForms演示界面可输入相关信息后直接运行验证推送效果方便二次开发与接入企业实际业务。学习这套代码可快速掌握企业号消息推送的基本流程减少摸索微信API的时间。1. 微信企业号主动推送企业内部通知的最后一公里企业内部系统的消息往往死在最后一百米审批通过、巡检告警、定时任务跑完这些节点都需要把结果推到员工手机的企业号会话里。如果靠后台手动群发既无法指定人也没法和业务系统联动如果从零调接口又要自己处理Token缓存、消息构造、错误解析这一堆细节。这个名为WChat的C#工程把微信企业号主动推送功能完整封装了一遍拿CorpID和Secret换access_token、按类型组装消息体、调用发送接口并解析回执封成类库后一行代码就能触发推送。适合要把通知能力嵌进OA审批流、运维监控、报表分发系统的C#开发者也适合第一次接触企业号API的新手按Demo跑通链路。2. 核心链路设计从CorpID到消息送达的完整路径写第一行代码之前先把企业号的调用模型理清楚。这部分不涉及具体语法但搞懂它后面看代码就不会绕弯。2.1 三个关键参数CorpID、Secret与AgentID企业号主动推送的三要素。CorpID是企业在企业微信体系里的唯一编号相当于企业身份证所有接口请求都要携带。Secret是自建应用的密钥创建应用时后台生成它的作用是配合CorpID去换取access_token也就是“用身份证明加钥匙换一张两小时有效的临时通行证”。AgentID是应用ID发送消息时必须指定——消息由哪个应用发出、能发给哪些人都受这个参数约束。新手最容易把Secret和AgentID搞混。Secret是一长串随机字符AgentID是一个整数两者在应用详情页的不同位置展示。我见过有人把AgentID填进Secret字段结果gettoken接口直接返回40001排查了半天才反应过来是配置串了。这三个配置项建议集中放到一个静态类里管理这个工程里的QyConfig就是这个作用namespace WeiXin.QiYe.Config { public static class QyConfig { /// summary企业ID在管理后台“我的企业”页面复制/summary public static string CorpId ww1234567890abcdef; /// summary应用密钥在应用详情页查看/summary public static string Secret 把这里换成你自己的Secret; /// summary应用ID发送消息时必填/summary public static int AgentId 1000002; } }这段代码的价值在于收敛配置。生产环境不要像这样明文写死Secret常见做法是放到环境变量或配置中心里代码里只保留非敏感字段测试阶段直接改这四个值就行。后面的所有类都会引用这个配置类保证一处修改、处处生效。2.2 一次主动推送的三步链路换Token、拼JSON、Post请求主动推送的完整链路只有三步和普通REST接口调用没有本质区别。第一步用CorpID加Secret请求gettoken接口换取access_tokenGET https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpidCORPIDcorpsecretSECRET接口返回的JSON包含access_token和expires_inexpires_in固定为7200秒也就是两小时。第二步按照接口文档组装消息体明确指定接收人、消息类型和应用ID{ touser: user01, msgtype: text, agentid: 1000002, text: { content: 这是一条来自C#工程的主动推送 }, safe: 0 }第三步把组装好的JSON作为请求体POST到message/send接口access_token放在URL查询参数里POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_tokenACCESS_TOKEN返回体同样是JSONerrcode为0表示企业号服务器受理了这次推送非0则说明参数或权限有问题。注意一个概念发送接口返回成功代表“受理成功”不代表“手机已收到”。真正的送达还受可见范围、客户端状态等因素影响这两者的差别第5章会详细说。2.3 工程目录结构类库加Demo的分层方式拆开压缩包解决方案分为核心类库和演示工程两层。类库封装所有企业号API细节Demo提供最小可运行示例这种结构方便直接引用类库到自己的项目中也方便先用Demo验证配置WeiXin.QiYe.sln ├── WeiXin.QiYe 核心类库 │ ├── Config │ │ └── QyConfig.cs 企业ID、Secret、AgentID配置 │ ├── Core │ │ ├── HttpHelper.cs 基于HttpClient的GET/POST封装 │ │ └── TokenManager.cs access_token获取、缓存与刷新 │ ├── Message │ │ ├── TextMessage.cs 文本消息构造 │ │ ├── TextCardMessage.cs 文本卡片消息构造 │ │ └── MarkdownMessage.cs Markdown消息构造 │ ├── Send │ │ ├── MessageSender.cs 消息发送统一入口 │ │ └── SendResult.cs 返回结果解析与状态判断 │ └── WeiXin.QiYe.csproj ├── WeiXin.QiYe.Demo 控制台演示工程 │ └── Program.cs 最小闭环发送一条文本消息 └── README.md 配置步骤与接口说明类库把“HTTP通信”“Token管理”“消息构造”“结果解析”四个职责拆成了独立文件。只想用文本消息时依赖Send和Core两层即可要扩展新消息类型在Message目录里新增构造类就行发送主逻辑不用动。Demo工程是整个接入过程的试验田——先在控制台把链路跑通确认配置无误再迁移到WebApi或Windows服务里会省掉很多联调噪音。2.4 消息类型选型文本、文本卡片与Markdown怎么选工程覆盖了三种最常用的消息类型。纯文本消息适合“构建成功”“定时任务完成”这类一句话通知文本卡片消息带标题、摘要和跳转按钮适合审批结果、工单状态这类需要点击查看详情的场景Markdown消息支持标题、引用、颜色标注等轻量排版适合运维告警、日报汇总。三种类型在代码层面的差别只是消息体字段不同但用户体验差异明显选型时优先考虑接收方“看完之后要不要做下一步动作”要动作就用卡片纯提醒用文本就行。3. 关键代码实现Token自动刷新、消息构造与发送封装这一章直接拆核心代码讲解每个关键类的设计意图和参数含义。完整源码在RAR包里这里只挑最值得讲的部分。3.1 TokenManager两小时有效期下的本地缓存与并发控制access_token的有效期只有两小时每次发送都重新获取一次不仅浪费网络开销还可能触发企业号的频率限制。工程的做法是把Token缓存在内存里过期前复用失效后自动重新获取using System; using System.Text.Json; using WeiXin.QiYe.Config; namespace WeiXin.QiYe.Core { public class TokenManager { private static readonly object Locker new object(); private static string _token string.Empty; private static DateTime _expireTime DateTime.MinValue; public static string GetAccessToken() { lock (Locker) { // 提前5分钟过期避免Token在边界失效 if (!string.IsNullOrEmpty(_token) _expireTime DateTime.Now.AddMinutes(5)) { return _token; } string url string.Format( https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid{0}corpsecret{1}, QyConfig.CorpId, QyConfig.Secret); string json HttpHelper.Get(url); using (JsonDocument doc JsonDocument.Parse(json)) { JsonElement root doc.RootElement; if (root.GetProperty(errcode).GetInt32() 0) { _token root.GetProperty(access_token).GetString(); int expiresIn root.GetProperty(expires_in).GetInt32(); _expireTime DateTime.Now.AddSeconds(expiresIn); } else { throw new Exception(获取access_token失败 root.GetProperty(errmsg).GetString()); } } return _token; } } } }三个设计点值得关注。第一过期判断留了5分钟余量_expireTime DateTime.Now.AddMinutes(5)宁可提前刷新也不在临界点用旧Token。第二整个方法用lock包住防止多线程同时请求gettoken——企业号接口单位时间内有调用次数限制锁住之后只有一个线程去换新Token其他线程直接复用缓存值。第三HttpHelper.Get是工程里对HttpClient的薄封装替换成RestSharp或原生HttpWebRequest都不影响上层逻辑。这套单机静态缓存的方案在绝大多数企业内部应用里够用。如果系统是多实例部署本地缓存会出现“各持一个Token”的情况常见做法是引入Redis缓存Token并设置过期时间或者用一个独立服务统一管理Token所有实例都从公共存储取。RAR包内是单机版本二次开发时可以直接按第6章的改造思路扩展。3.2 MessageSender消息发送的统一入口与touser参数细节发送模块是对外开放的核心接口设计上让调用方只关心“发给谁、发什么、用哪个应用”把JSON序列化、URL拼接、返回解析全部收敛在内部using System; using System.Text.Json; using WeiXin.QiYe.Core; namespace WeiXin.QiYe.Send { public class MessageSender { public static SendResult SendText(string toUser, int agentId, string content) { var request new { touser toUser, msgtype text, agentid agentId, text new { content content }, safe 0 }; string payload JsonSerializer.Serialize(request); string url https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token TokenManager.GetAccessToken(); string response HttpHelper.Post(url, payload); return SendResult.Parse(response); } } }touser字段支持三种写法传单个成员登录账号比如user01传多个成员用竖线分隔比如user01|user02如果要按部门发送用toparty字段传部门ID。touser和toparty都不传时消息会发送给应用可见范围内的全部成员——这个兜底行为容易造成误会第5章会展开讲。safe字段表示消息是否保密0为普通消息可转发可截屏1为保密消息客户端禁止转发和截屏。这个SendText方法看起来简单但已经把Token缓存、序列化、异常返回解析全部串起来了。调用方拿到SendResult后只要判断IsOk属性就能知道推送是否被企业号受理。3.3 SendResult返回码解析与invaliduser的二次处理企业号发送接口的返回体结构固定除errcode和errmsg之外还经常带invaliduser、invalidparty这类字段。把它们解析成强类型对象业务代码才能判断“这次推送是否全员成功”using System.Text.Json; namespace WeiXin.QiYe.Send { public class SendResult { public int ErrCode { get; set; } public string ErrMsg { get; set; } public string InvalidUser { get; set; } public bool IsOk ErrCode 0; public static SendResult Parse(string json) { using (JsonDocument doc JsonDocument.Parse(json)) { JsonElement root doc.RootElement; var result new SendResult(); if (root.TryGetProperty(errcode, out JsonElement code)) result.ErrCode code.GetInt32(); if (root.TryGetProperty(errmsg, out JsonElement msg)) result.ErrMsg msg.GetString(); if (root.TryGetProperty(invaliduser, out JsonElement user)) result.InvalidUser user.GetString(); return result; } } } }最容易被忽略的是invaliduser字段。errcode等于0只代表企业号受理了推送不代表所有目标都收到。返回体里出现invaliduser时说明部分用户ID不存在或不在可见范围内。正确做法是发送前先过滤目标用户发送后检查InvalidUser把失败的用户ID记录下来剔除或重发。企业号不像普通Web API那样有“全量送达确认”只能靠这种半结果字段做补偿处理。3.4 TextCard与Markdown消息构造从纯文本升级为可交互通知审批类场景适合用文本卡片点击卡片能直接跳转业务详情页比纯文本多一步交互。构造代码如下namespace WeiXin.QiYe.Message { public class TextCardMessage { public static object Build(string toUser, int agentId, string title, string description, string url) { return new { touser toUser, msgtype textcard, agentid agentId, textcard new { title title, description description, url url, btntxt 查看详情 } }; } } }这里唯一的坑是url字段。textcard的跳转地址必须使用企业微信后台配置过的可信域名否则员工在客户端点击时看到的是风险提示页面通知体验直接大打折扣。开发阶段建议先指向可信域名下的临时页面上线前再换真实业务地址。Markdown消息的构造更简单但内容语法有专门限制namespace WeiXin.QiYe.Message { public class MarkdownMessage { public static object Build(string toUser, int agentId, string content) { return new { touser toUser, msgtype markdown, agentid agentId, markdown new { content content } }; } } }Markdown内容支持企业微信专用语法比如font colorwarning给文字标色、引用块、##标题。这些语法只在企业微信手机客户端内完整渲染网页版和PC管理端不保证兼容。如果推送对象经常在网页版收消息建议优先用纯文本或文本卡片。4. 跑起来之前的必修课企业号后台配置与联调环境搭建代码逻辑清楚了还要让消息真正送达手机。这一章从环境准备开始按顺序走到最小闭环验证。4.1 环境准备目标框架、依赖与编译方式工程基于标准类库写的C#代码用的都是System.Net.Http、System.Text.Json这些BCL能力没引入第三方依赖。目标框架覆盖.NET Framework 4.5和.NET Core/.NET 5以上版本本机装的是.NET 6 SDK时直接编译dotnet build WeiXin.QiYe.sln编译通过后需要一个能访问公网的运行环境。跑控制台Demo不依赖任何服务只要本机网络能访问qyapi.weixin.qq.com就行接入WebApi时再考虑部署到内网服务器同时确保出口IP能被企业号后台的白名单识别。4.2 企业号后台配置三步走创建应用、复制参数、设置可见范围后台配置是整个联调最容易翻车的环节步骤不多但每个参数都要对应准确。第一步管理员登录企业微信管理后台在“应用管理”中创建自建应用填写应用名称和Logo后进入详情页页面上直接展示AgentID和Secret复制到QyConfig对应字段。第二步在应用详情页的“企业可信IP”配置中填入运行Demo的服务器公网IP。这一步经常被跳过结果一调接口就报60020错误详见第5章。第三步设置应用可见范围——新建应用默认可见范围是空的如果touser指向的成员不在范围内接口返回errcode0但消息并不会发出。三个参数和后台位置的对照关系直接看这张表参数值示例获取位置关键说明CorpIdww1234567890abcdef管理后台-我的企业企业唯一IDww开头Secret随机字符串应用详情-Secret换access_token的密钥AgentId1000002应用详情-基本信息发送消息必须指定可信IP203.0.113.10应用详情-企业可信IP不填会报60020表格里每一项都是联调时真正会用到的。CorpId和Secret配错是最低级的错误但也最容易发生复制前先看字符长度CorpId以ww开头Secret通常是一段较长的混合字符串格式不对基本就是复制错了。4.3 控制台Demo一个命令看到推送效果后台配置完成后先跑Demo工程。它的逻辑很简单取一次Token解析参数发送一条文本消息然后报告结果using System; using WeiXin.QiYe.Config; using WeiXin.QiYe.Send; namespace WeiXin.QiYe.Demo { class Program { static void Main(string[] args) { if (args.Length 0) { Console.WriteLine(用法: Demo 要发送的文本); return; } SendResult result MessageSender.SendText( user01, QyConfig.AgentId, args[0]); if (result.IsOk) { Console.WriteLine(推送成功请在手机企业微信中查看); } else { Console.WriteLine(推送失败errcode result.ErrCode , errmsg result.ErrMsg); } } } }这里的touser写死了user01测试前需要替换成你企业号后台可见范围内的成员登录账号。成员账号在管理后台“通讯录”里能看到不是手机号也不是姓名是账号ID那一列。跑起来后如果控制台输出“推送成功”立刻看手机企业微信里有没有这条消息没看到就跳去第5章查可见范围的问题。4.4 接入WebApi把推送能力封装成HTTP接口Demo验证通过后下一步是把推送能力嵌进业务系统。最常见的方式是包一个WebApi接口让其他子系统通过HTTP调用而不是直接引用类库namespace WeiXin.QiYe.WebApi.Controllers { [ApiController] [Route(api/[controller])] public class PushController : ControllerBase { [HttpPost(send)] public IActionResult Send([FromBody] PushRequest req) { SendResult result MessageSender.SendText( req.ToUser, QyConfig.AgentId, req.Content); if (result.IsOk) { return Ok(new { success true }); } return BadRequest(new { success false, error result.ErrMsg }); } } public class PushRequest { public string ToUser { get; set; } public string Content { get; set; } } }注意这里面有个性能隐患控制器里直接调用同步的MessageSender网络IO会占住线程池线程。建议改造时给MessageSender增加SendTextAsync异步版本或者把推送任务丢进后台队列接口先返回200再异步完成发送。生产环境我一般选后者——调用方不感知推送延迟企业号接口响应慢也不会拖垮业务接口的吞吐。5. 避坑笔记企业号推送联调中踩过的5个真实问题这一章是实际操作里反复试错攒下的记录。每条按现象、原因、解决三个步骤写遇到同类问题可以直接照方抓药。先给一个通用排查技巧遇到报错别急着改代码先用一条命令验证基础配置是否正确curl https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid你的CorpIdcorpsecret你的Secret把CorpID和Secret替换成后台真实值看返回的JSON里是access_token还是errcode。这一步能快速区分“配置错了”和“代码错了”节省大量排查时间。5.1 报错类errcode非0时的三种典型情况第一条gettoken接口返回40013。现象用配置好的CorpID和Secret调用gettoken返回errcode: 40013, errmsg: invalid corpid。原因CorpID填成了企业简称、企业邮箱或者其他自定义字符串而不是后台“我的企业”里的ww开头企业ID。解决点开管理后台“我的企业”复制真正的企业ID替换配置重启进程重新获取Token。第二条send接口返回40001或42001。现象Token能正常获取但发送消息时报invalid credential。原因access_token在缓存中过期或者多实例同时刷新Token导致其中一个被覆盖也有可能Secret在后台被重置但代码还是旧值。解决先检查是否有多实例各自缓存Token的情况统一走TokenManager的静态缓存再清掉本地缓存强制刷新一次排除Secret被改动的可能。如果常驻进程长时间运行建议加一个定时任务在Token过期前主动刷新。第三条接口返回60020。现象测试环境推送一切正常部署到生产服务器后调用message/send接口返回not allow to access from your ip。原因企业可信IP白名单里只配置了测试环境的地址生产服务器的出口IP没有加入。解决把当前运行环境的公网IP添加到应用详情页的“企业可信IP”中。这条我翻车过两次一次是测试机到生产机迁移时忘了同步白名单另一次是服务器换出口IP后白名单没更新排查了半个多小时才反应过来是网络侧的问题。5.2 送达类errcode为0但消息没到手机怎么办第四条接口返回成功手机却没收到。现象SendResult.IsOk为true但目标成员手机的企业微信里找不到这条消息。原因目标成员不在应用可见范围内企业号会静默丢弃这部分消息另一种可能是touser填错了比如传了成员的姓名而不是登录账号。解决回到应用详情页确认可见范围已包含该成员再核对通讯录里的账号ID。注意修改可见范围后需要等待一两分钟生效别刚改完就急着重测。第五条文本卡片跳转页面提示域名风险。现象员工收到textcard消息点击“查看详情”后浏览器弹出风险提示页面无法正常打开。原因textcard的url字段使用的是未配置可信域名的外网地址企业微信客户端无法确认页面安全性。解决先把业务页面的域名加入应用的“可信域名”配置按提示将校验文件放到域名根目录完成验证再把textcard的url替换成验证通过的地址。这条的经验是开发阶段的域名不要频繁变动提前把测试域名也配进白名单省得每次联调都在域名校验上耗时间。这5条是我在这个项目上实实在在交过的学费。第4章和第5章配合起来看先按规范配置再对照排查大多数问题半小时内能定位。6. 二次开发把纯发送模块改造成你的内部消息中心基础发送跑通之后这个模块的价值只发挥了一半。这一章给两个能直接落地的改造方向。6.1 用接口统一消息类型消除重复的发送方法现在的MessageSender对文本、文本卡片、Markdown各提供一个方法调用方不用关心细节但方法内部逻辑高度相似。继续加消息类型的话方法会越来越多。一个建议抽象出IMessage接口让每种消息自己负责JSON构造namespace WeiXin.QiYe.Message { public interface IMessage { string MsgType { get; } object BuildBody(); } public class TextMessage : IMessage { public string Content { get; set; } public string MsgType text; public object BuildBody() new { text new { content Content } }; } }改造后MessageSender只保留一个入口方法公共链路——取Token、拼URL、发请求、解析结果——收在模板方法里新增消息类型时只加新类。业务侧调用从“知道该调哪个方法”变成“传入一个消息对象”代码量明显下降也不容易出现复制粘贴改字段的错误。6.2 加一层重试与补偿队列从“能推送”升级为“可靠推送”企业号消息接口偶尔会因为网络抖动、频率限制返回失败对通知类场景不能接受“失败就丢了”。常见的做法是在MessageSender外层包一个带重试的策略namespace WeiXin.QiYe.Send { public class MessageRetry { public static SendResult SendWithRetry( string toUser, int agentId, string content, int maxRetry 3) { int retry 0; while (retry maxRetry) { SendResult result MessageSender.SendText(toUser, agentId, content); if (result.IsOk) { return result; } if (result.ErrCode 40014 || result.ErrCode 42001) { TokenManager.ForceRefresh(); } retry; Thread.Sleep(500 * retry); } return null; } } }重试3次仍失败后把消息落库稍后由定时任务补偿发送。配合一个简单的消息表和一个后台线程这个模块就从“调用接口发消息”升级成了“保证消息最终送达”的推送中心。生产环境里的通知链路拼到最后拼的不是发送速度而是重试和补偿的可靠性。我的习惯是每接一个新的企业号应用都先把后台配置、Token缓存、最小闭环发送这条路完整走一遍再写业务代码。RAR包里的类库和Demo正好按这个顺序组织后来者照着README改几个参数就能把推送接起来。这套标准流程已经帮我省了好多次排障希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号