- 后端
- 即时通讯
- 金融科技
【免费下载链接】WeiXinMPSDK
微信全平台 .NET SDK, Senparc.Weixin for C#,支持 .NET Framework 及 .NET Core、.NET 10.0。已支持微信公众号、小程序、小游戏、微信支付、企业微信/企业号、开放平台、JSSDK、微信周边等全平台。 WeChat SDK for C#.
公众号自定义菜单是用户与公众号交互的第一入口,也是公众号界面中最重要的导航元素之一。本文基于 WeiXinMPSDK(Senparc.Weixin for C#)仓库中的 公众号菜单设置文档,系统讲解菜单的设置与使用两大环节:从可视化编辑器(No Code)到代码方式创建菜单,再到自定义 MessageHandler 中接收并处理菜单点击事件,并深入 SDK 源码剖析菜单实体模型与底层 API 调用链。读完本文,你将掌握用 Senparc.Weixin.MP 完整落地「创建菜单 → 点击响应」全流程的能力。
一、菜单的两大环节:设置与使用
公众号菜单从生命周期上可以拆分为两个相互独立又紧密衔接的环节:
- 设置(Create):把菜单结构(一级菜单、二级菜单、按钮类型、跳转地址、事件 Key 等)上传到微信服务器,使其在公众号界面上生效;
- 使用(Use):用户点击菜单后,微信服务器把点击事件以 XML 消息的形式推送到消息 URL,由我们的 MessageHandler 接收并作出响应。
其中“设置”是一次性动作,通常只在菜单结构变更时才需要重新执行,因此官方文档建议把创建菜单的代码放在管理员后台手动运行,而不是放在每次请求都会执行的业务逻辑中;而“使用”则是持续性的在线处理逻辑,每一次用户点击都会触发一次事件推送。
二、设置菜单的三种方式
方式一:微信公众号后台(非开发模式)
直接登录微信公众号后台,在「自定义菜单」页面可视化编辑。此方式不涉及开发模式,适合菜单结构简单、无需与业务系统联动的场景。原文档对此略过,本文也不再展开。
方式二(推荐):可视化编辑器(No Code)
SDK 官方提供在线可视化菜单编辑器,地址为 https://sdk.weixin.senparc.com/Menu(原文档中给出的链接)。该编辑器支持零代码拖拽配置菜单,配置完成后即可生成可直接用于 SDK 的菜单 JSON 或调用代码,适合不熟悉实体模型、希望快速上手的开发者。原文档将其列为推荐方式。
方式三:使用代码设置(推荐用于生产环境)
使用 Senparc.Weixin.MP 提供的ButtonGroup实体模型在代码中构建菜单结构,再调用CommonApi.CreateMenuAsync(appId, bg)一次性提交。原文档给出的完整示例:
public async Task CreateMenuAsync() { ButtonGroup bg = new ButtonGroup(); //定义一级菜单 var subButton = new SubButton() { name = "一级菜单" }; bg.button.Add(subButton); //下属二级菜单 subButton.sub_button.Add(new SingleViewButton() { url = "https://book.weixin.senparc.com/book/link?code=SenparcRobotMenu", name = "《微信开发深度解析》" }); subButton.sub_button.Add(new SingleClickButton() { key = "OneClick", name = "单击测试" }); subButton.sub_button.Add(new SingleViewButton() { url = "https://weixin.senparc.com/", name = "Url跳转" }); //最多可添加 3 个一级自定义菜单,每个菜单下最多 5 个子菜单 var result = await CommonApi.CreateMenuAsync(appId, bg); }示例中构建了一个一级菜单「一级菜单」,其下挂载三个二级菜单项:一个SingleViewButton(点击跳转到外部链接,如《微信开发深度解析》书籍页面)、一个SingleClickButton(点击触发事件推送,key为OneClick)、以及另一个SingleViewButton(跳转到官网)。这段代码需要基于「完整继承 + 深入讲解」的原则进行展开,下面进入源码层。
三、菜单实体模型源码剖析
菜单结构在 SDK 中被设计为一套完整的实体类继承体系,位于 src/Senparc.Weixin.MP/Senparc.Weixin.MP/Entities/Menu 目录下:
Entities/Menu/ ├── ButtonGroupBase.cs // 所有菜单组的基类(含 button 列表) ├── IButtonGroupBase.cs // 菜单组接口 ├── Custom/ │ └── ButtonGroup.cs // 普通自定义菜单的整个按钮设置 ├── Conditional/ │ ├── ConditionalButtonGroup.cs // 个性化(条件)菜单 │ └── MenuMatchRule.cs // 个性化菜单匹配规则 └── Buttons/ ├── BaseButton.cs // 所有按钮基类(name) ├── SingleButton.cs // 所有单击按钮基类(type) ├── SubButton.cs // 子菜单(sub_button) ├── SingleClickButton.cs // click 按钮(key) ├── SingleViewButton.cs // view 按钮(url) ├── SingleMiniProgramButton.cs// 小程序按钮 ├── SingleScancodePushButton.cs ├── SingleScancodeWaitmsgButton.cs ├── SinglePicSysphotoButton.cs ├── SinglePicPhotoOrAlbumButton.cs ├── SinglePicWeixinButton.cs ├── SingleLocationSelectButton.cs ├── SingleMediaIdButton.cs ├── SingleViewLimitedButton.cs ├── SingleArticleIdButton.cs └── SingleArticleViewLimitedButton.cs3.1 菜单组:ButtonGroup 与 ButtonGroupBase
Custom/ButtonGroup.cs 是(普通自定义菜单)整个按钮设置的入口,类注释明确指出“可以直接用 ButtonGroup 实例返回 JSON 对象”——也就是说,构建好的实体对象会由底层序列化为微信 API 所需的 JSON 请求体。
其基类 ButtonGroupBase.cs 定义了核心属性:
public abstract class ButtonGroupBase : IButtonGroupBase { /// <summary> /// 按钮数组,按钮个数应为1-3个 /// </summary> public List<BaseButton> button { get; set; } public ButtonGroupBase() { button = new List<BaseButton>(); } }button:一级菜单数组,数量限制为 1~3 个(与微信官方规则一致);- 构造函数自动初始化列表,因此示例中可以直接
bg.button.Add(...)而无需手动 new。
3.2 按钮基类:BaseButton 与 SingleButton
Buttons/BaseButton.cs 是所有按钮的基类,定义了唯一的通用属性:
public class BaseButton : IBaseButton { /// <summary> /// 按钮描述,既按钮名字,不超过16个字节,子菜单不超过40个字节 /// </summary> public string name { get; set; } }name即按钮显示名称,注意微信对长度有字节限制:普通按钮不超过 16 字节,子菜单不超过 40 字节(一个汉字在 UTF-8 下占 3 字节,中文名需格外留意)。
Buttons/SingleButton.cs 是所有“可点击”按钮的抽象基类,新增了type属性(按钮类型,如 click / view),构造函数要求传入类型字符串:
public abstract class SingleButton : BaseButton, IBaseButton { /// <summary> /// 按钮类型(click或view) /// </summary> public string type { get; set; } public SingleButton(string theType) { type = theType; } }3.3 子菜单:SubButton
Buttons/SubButton.cs 表示带二级菜单的一级菜单项:
public class SubButton : BaseButton, IBaseButton { /// <summary> /// 子按钮数组,按钮个数应为2~5个 /// </summary> public List<SingleButton> sub_button { get; set; } public SubButton() { sub_button = new List<SingleButton>(); } public SubButton(string name) : this() { base.name = name; } }sub_button:二级菜单数组,数量限制为 2~5 个;- 提供
SubButton(string name)便捷构造函数,可直接以菜单名初始化。
3.4 常用按钮类型:SingleClickButton 与 SingleViewButton
示例中用到的两个最典型按钮:
SingleClickButton.cs(click 型,点击推送事件):
public class SingleClickButton : SingleButton { /// <summary> /// 类型为click时必须。 /// 按钮KEY值,用于消息接口(event类型)推送,不超过128字节 /// </summary> public string key { get; set; } public SingleClickButton() : base(MenuButtonType.click.ToString()) { } }- 构造函数自动把
type设为click; key是按钮的事件标识,用户点击后该值会通过EventKey字段随事件消息推送过来,不超过 128 字节,用于在服务端区分具体点了哪个按钮。
SingleViewButton(view 型,点击直接跳转网页)则携带url属性,对应示例中的跳转链接。仓库中 Buttons 目录还提供了SingleMiniProgramButton(跳小程序)、SingleScancodePushButton(扫码推事件)、SingleLocationSelectButton(弹出地理位置选择器)等更多按钮类型,可按需选用。
四、菜单创建的底层 API 调用链
原文档调用的是CommonApi.CreateMenuAsync(appId, bg),其同步底层实现在 CommonAPIs/Menu/CommonApi.Menu.Custom.cs 中:
public static WxJsonResult CreateMenu(string accessTokenOrAppId, ButtonGroup buttonData, int timeOut = Config.TIME_OUT) { return ApiHandlerWapper.TryCommonApi(accessToken => { var urlFormat = Config.ApiMpHost + "/cgi-bin/menu/create?access_token={0}"; //对特殊符号进行URL转义 ... return CommonJsonSend.Send<WxJsonResult>(accessToken, urlFormat, buttonData, timeOut: timeOut); }); }从源码可以确认以下实现事实:
- 请求接口:最终调用微信官方接口
POST /cgi-bin/menu/create?access_token={0}; - AccessToken 自动管理:方法签名是
accessTokenOrAppId,即既可直接传 AccessToken,也可传 AppId——当传 AppId 且 AccessToken 失效时,SDK 会通过TryCommonApi自动获取/刷新一次 AccessToken;传null时则使用当前注册的第一个 AppId; - 参数透传:
ButtonGroup实体直接作为请求体序列化提交,返回WxJsonResult判断是否创建成功; - 超时控制:
timeOut默认取Config.TIME_OUT,可自定义; - 异步版本
CreateMenuAsync是对上述同步方法的异步封装(Task 包装),因此原文档示例中可以直接await。
五、使用菜单:在 MessageHandler 中接收点击事件
5.1 事件推送机制
菜单设置完成后,当用户在微信客户端点击菜单按钮时:
- 若点击的是view 型按钮,微信直接跳转
url,不会推送事件到服务器; - 若点击的是click 型按钮(或其他会触发事件的按钮类型),微信服务器会把
event类型消息(Event = click)自动推送到消息 URL,即进入已经配置好的 MessageHandler。
因此,服务端只需在自定义 MessageHandler 中重写(override)对应的事件处理方法即可完成响应。
5.2 重写 OnEvent_ClickRequestAsync
针对原文档“方法三”中创建的菜单,当用户点击【单击测试】按钮(key = "OneClick")时,原文档给出如下处理代码:
public override async Task OnEvent_ClickRequestAsync(RequestMessageEvent_Click requestMessage) { var reponseMessage = CreateResponseMessage(); if (requestMessage.EventKey == "OneClick") { reponseMessage.Content = "您点击了【单击测试】按钮"; } else { reponseMessage.Content = "您点击了其他事件按钮"; } return reponseMessage; }要点说明:
RequestMessageEvent_Click是点击事件请求消息实体,EventKey字段即携带按钮的key值(对应SingleClickButton.key);- 通过
requestMessage.EventKey == "OneClick"即可精确区分点击的是哪个按钮; - 使用
CreateResponseMessage()创建响应消息并设置Content文本,即可实现点击后的自动回复;该方法是 MessageHandler 内置的便捷方法(可指定泛型如CreateResponseMessage<ResponseMessageText>()); - 事件处理返回的响应消息会由框架自动回复给用户。
5.3 仓库中的真实参考实现
原文档末尾指向的参考文件位于 Samples/MP/Senparc.Weixin.Sample.MP/MessageHandlers/CustomMessageHandler_Events.cs。仓库中该文件确实实现了同名方法(第 114 行起):
/// <summary> /// 点击事件 /// </summary> /// <param name="requestMessage">请求消息</param> /// <returns></returns> public override async Task<IResponseMessageBase> OnEvent_ClickRequestAsync(RequestMessageEvent_Click requestMessage) { var reponseMessage = CreateResponseMessage<ResponseMessageText>(); if (requestMessage.EventKey == "OneClick") { reponseMessage.Content = "您点击了【单击测试】按钮"; } else { reponseMessage.Content = "您点击了其他事件按钮"; } return reponseMessage; }这段真实示例与原文档代码几乎一致,且与 CustomMessageHandler.cs 配合,通过partial class方式组织事件处理逻辑,验证了文档所述方案的可行性与标准写法。SDK 的 MP 示例项目 Senparc.Weixin.Sample.MP 中还有完整的项目配置(Program.cs、appsettings.json),可作为可直接运行的最小复现工程参考。
六、完整实战:从创建菜单到点击响应
将上述两部分串起来,一个完整的公众号菜单实战流程为:
- 构建菜单:用
ButtonGroup+SubButton+SingleClickButton/SingleViewButton等实体构建菜单树(注意一级菜单 1~3 个、二级菜单每个 2~5 个、按钮名长度限制); - 发布菜单:在管理员后台(仅需执行一次)调用
await CommonApi.CreateMenuAsync(appId, bg)上传菜单,并检查返回的WxJsonResult(errcode == 0表示成功); - 接收事件:确保消息 URL(服务器配置中的回调地址)正确指向 MessageHandler;
- 处理点击:在
CustomMessageHandler中重写OnEvent_ClickRequestAsync,根据requestMessage.EventKey分发业务逻辑(如回复文本、调用客服接口、跳转页面等); - 测试验收:在微信客户端点击各菜单项,验证跳转与事件回复是否符合预期。
七、小结
- 公众号菜单分为「设置」与「使用」两个环节:设置是一次性上传动作(推荐代码方式 + 后台手动触发),使用是持续的事件处理逻辑;
- Senparc.Weixin.MP 通过 Entities/Menu 下完整的实体继承体系(
ButtonGroup → SubButton → SingleButton → 具体按钮类型)屏蔽了微信菜单 JSON 的复杂度,实体即请求体; - 底层由 CommonApi.Menu.Custom.cs 中的
CreateMenu/CreateMenuAsync完成cgi-bin/menu/create接口调用,并内置 AccessToken 自动管理; - 点击事件(click 型)通过
OnEvent_ClickRequestAsync在自定义 MessageHandler 中处理,以EventKey区分按钮,参考实现见 CustomMessageHandler_Events.cs。
掌握本文内容后,你可以在任意基于 WeiXinMPSDK 的公众号项目中,独立完成菜单的构建、发布与点击交互的全链路开发。
- 后端
- 即时通讯
- 金融科技
【免费下载链接】WeiXinMPSDK
微信全平台 .NET SDK, Senparc.Weixin for C#,支持 .NET Framework 及 .NET Core、.NET 10.0。已支持微信公众号、小程序、小游戏、微信支付、企业微信/企业号、开放平台、JSSDK、微信周边等全平台。 WeChat SDK for C#.
相关推荐
WeiXinMPSDK 微信公众号自定义菜单开发指南:从代码设置到事件响应全流程
WeiXinMPSDK 微信公众号自定义菜单开发指南:从代码设置到事件响应全流程 本文围绕 menu.md https://link.gitcode.com/i
后端即时通讯金融科技Ant Design Dropdown 菜单项点击事件处理:基于 key 分发操作的技术指南
Ant Design Dropdown 菜单项点击事件处理:基于 key 分发操作的技术指南 导读 Dropdown(下拉菜单)是 Ant Design 组件库
前端UI组件设计系统x64dbg 插件菜单点击回调 PLUG_CB_MENUENTRY 完全指南:从菜单注册到事件分发
x64dbg 插件菜单点击回调 PLUG_CB_MENUENTRY 完全指南:从菜单注册到事件分发 导读 PLUG_CB_MENUENTRY 是 x64dbg
逆向工程调试器开发工具应用安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考