C#实现微信小程序订阅消息推送
一、背景介绍
微信小程序的订阅消息是服务端向用户推送通知的重要能力,广泛应用于订单状态变更、预约提醒、物流进度等场景。
重要提示:微信官方已于2023年10月1日起全面下线了旧的「模板消息」接口,现在只能使用「订阅消息」(subscribeMessage.send)能力,请勿沿用旧接口文档。
订阅消息的核心机制是:用户主动授权一次,开发者可发送一条消息,授权次数与发送次数一一对应。
本文将详细介绍如何使用C#(.NET Core)实现微信小程序订阅消息的推送。
二、准备工作
1. 获取小程序配置信息
登录微信公众平台,在「开发-开发管理」中获取:
AppId:小程序唯一标识
AppSecret:小程序密钥(妥善保管)
2. 配置订阅消息模板
进入「功能-订阅消息」,从公共模板库选择或申请新模板,审核通过后记录模板ID。模板中的参数(如thing1、date2等)需与代码中的data字段对应。
3. 配置服务器域名
在「开发-开发设置-服务器域名」中配置request合法域名,用于后端服务调用微信API。
三、核心流程
小程序端用户点击按钮
↓
wx.requestSubscribeMessage 弹出授权窗
↓
用户同意授权(一次授权=一条消息额度)
↓
服务端调用 subscribeMessage.send
↓
微信服务器向用户推送订阅消息
四、代码实现
1. 配置文件(appsettings.json)
{ "Wechat": { "AppId": "你的小程序AppId", "AppSecret": "你的小程序AppSecret", "TemplateId": "你的订阅消息模板ID", "AccessTokenUrl": "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", "SendMessageUrl": "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={0}" } }2. 获取AccessToken
AccessToken有效期7200秒(2小时),必须缓存并定时刷新,避免频繁请求。
using System.Net.Http; using System.Text.Json; using Microsoft.Extensions.Options; public class WechatTokenService { private readonly HttpClient _httpClient; private readonly IOptions<WechatOptions> _options; private string _cachedToken; private DateTime _tokenExpireTime; public WechatTokenService(HttpClient httpClient, IOptions<WechatOptions> options) { _httpClient = httpClient; _options = options; } public async Task<string> GetAccessTokenAsync() { // 检查缓存是否有效(提前5分钟刷新) if (!string.IsNullOrEmpty(_cachedToken) && DateTime.Now < _tokenExpireTime) { return _cachedToken; } string url = string.Format(_options.Value.AccessTokenUrl, _options.Value.AppId, _options.Value.AppSecret); var response = await _httpClient.GetAsync(url); var json = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(json); _cachedToken = doc.RootElement.GetProperty("access_token").GetString(); int expiresIn = doc.RootElement.GetProperty("expires_in").GetInt32(); _tokenExpireTime = DateTime.Now.AddSeconds(expiresIn - 300); // 提前5分钟刷新 return _cachedToken; } }3. 发送订阅消息
public class SubscribeMessageService { private readonly HttpClient _httpClient; private readonly WechatTokenService _tokenService; private readonly IOptions<WechatOptions> _options; public SubscribeMessageService(HttpClient httpClient, WechatTokenService tokenService, IOptions<WechatOptions> options) { _httpClient = httpClient; _tokenService = tokenService; _options = options; } /// <summary> /// 发送订阅消息 /// </summary> /// <param name="openId">用户的OpenId</param> /// <param name="page">点击消息跳转的页面路径</param> /// <param name="data">模板数据,key为模板字段名</param> public async Task<SendResult> SendSubscribeMessageAsync( string openId, string page, Dictionary<string, string> data) { var token = await _tokenService.GetAccessTokenAsync(); var url = string.Format(_options.Value.SendMessageUrl, token); // 构造模板数据 var msgData = new Dictionary<string, object>(); foreach (var item in data) { msgData[item.Key] = new { value = item.Value }; } var requestBody = new { touser = openId, template_id = _options.Value.TemplateId, page = page, data = msgData, miniprogram_state = "formal" // 正式版 }; var json = JsonSerializer.Serialize(requestBody); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync(url, content); var resultJson = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(resultJson); var errcode = doc.RootElement.GetProperty("errcode").GetInt32(); var errmsg = doc.RootElement.GetProperty("errmsg").GetString(); return new SendResult { Success = errcode == 0, ErrCode = errcode, ErrMsg = errmsg }; } } public class SendResult { public bool Success { get; set; } public int ErrCode { get; set; } public string ErrMsg { get; set; } } public class WechatOptions { public string AppId { get; set; } public string AppSecret { get; set; } public string TemplateId { get; set; } public string AccessTokenUrl { get; set; } public string SendMessageUrl { get; set; } }4. 服务注册(Program.cs / Startup.cs)
builder.Services.Configure<WechatOptions>(builder.Configuration.GetSection("Wechat")); builder.Services.AddHttpClient(); builder.Services.AddScoped<WechatTokenService>(); builder.Services.AddScoped<SubscribeMessageService>();5. 调用示例(Controller)
[ApiController] [Route("api/[controller]")] public class MessageController : ControllerBase { private readonly SubscribeMessageService _msgService; public MessageController(SubscribeMessageService msgService) { _msgService = msgService; } [HttpPost("send")] public async Task<IActionResult> Send([FromBody] SendRequest request) { var data = new Dictionary<string, string> { ["thing1"] = "您的订单已发货", // 对应模板中的thing1 ["time2"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm"), // 对应time2 ["thing3"] = "请注意查收" // 对应thing3 }; var result = await _msgService.SendSubscribeMessageAsync( request.OpenId, "pages/order/detail?id=123", data ); if (result.Success) { return Ok(new { code = 0, msg = "发送成功" }); } // 43101表示用户未授权或拒绝授权 if (result.ErrCode == 43101) { return BadRequest(new { code = 43101, msg = "用户未授权订阅" }); } return BadRequest(result); } } public class SendRequest { public string OpenId { get; set; } }五、小程序端代码
前端需要在用户的点击事件中调用
wx.requestSubscribeMessage拉起授权弹窗:
// 页面js Page({ onSubscribe() { wx.requestSubscribeMessage({ tmplIds: ['你的模板ID'], // 最多3个 success(res) { // res[templateId] 可能为 'accept'、'reject'、'ban'、'filter' if (res['你的模板ID'] === 'accept') { // 用户同意,可调用后端接口发送消息 wx.request({ url: 'https://你的域名/api/message/send', method: 'POST', data: { openId: '用户的openid' } }); } }, fail(err) { console.error('订阅失败', err); } }); } });注意:wx.requestSubscribeMessage必须在用户的点击事件(如bindtap)中调用,不能放在onLoad或onShow中。
六、常见错误码
| 错误码 | 含义 | 解决方案 |
|---|---|---|
| 0 | 成功 | - |
| 40001 | access_token无效 | 重新获取token |
| 42001 | access_token过期 | 刷新token后重试 |
| 43101 | 用户未授权订阅 | 引导用户重新订阅 |
| 20037 | 模板参数不合法 | 检查字段类型和字数限制 |
七、注意事项
一次性订阅 vs 长期订阅:大部分小程序使用一次性订阅,用户每次授权发一条;长期订阅仅对特定类目(医疗、民生、交通等)开放。
用户勾选"总是保持选择"后,弹窗不再出现,但订阅状态会保持,代码中直接调用发送接口即可。
发送上限:开通支付能力的小程序每日3千万条,未开通的1千万条。
推荐使用SDK:社区有成熟的C# SDK,如Senparc.Weixin.WxOpen、DH.SKIT.FlurlHttpClient.Wechat.Api,封装了订阅消息等微信API,可减少重复造轮子。
八、总结
微信小程序订阅消息的核心是用户主动授权 + 服务端按授权发送。本文通过配置管理、Token缓存、消息发送、前端授权等环节,完整展示了C#服务端的实现方案。关键在于:
模板ID必须与微信公众平台配置一致
AccessToken必须缓存刷新
用户未授权(43101)需引导重新订阅
接入过程中建议优先使用成熟的微信SDK,可大幅提升开发效率。
