1.搭建回调服务器
可参考:https://www.cnblogs.com/zspwf/p/16381643.html进行搭建
2.编写代码
2.1接口定义
应用可以发送模板卡片消息,发送之后可再通过接口更新可回调的用户任务卡片消息的替换文案信息(仅原卡片为 按钮交互型、投票选择型、多项选择型的卡片以及填写了action_menu字段的文本通知型、图文展示型可以调用本接口更新)。
请注意,当应用调用发送模版卡片消息后,接口会返回一个response_code,通过response_code用户可以调用本接口一次。后续如果有用户点击任务卡片,回调接口也会带上response_code,开发者通过该code也可以调用本接口一次,注意response_code的有效期是24小时,超过24小时后将无法使用。
请求方式:POST(HTTPS)
请求地址: https://qyapi.weixin.qq.com/cgi-bin/message/update_template_card?access_token=ACCESS_TOKEN
参数说明:
2.2 appsettings配置
根据实际情况填写、
corpid 企业ID
corpsecret 应用密钥,
CallBackToken 企业微信后台,开发者设置的Token,
EncodingAESKey企业微信后台,开发者设置的EncodingAESKey。
"Wx": { "Baseurl": "https://qyapi.weixin.qq.com/cgi-bin/", "PushUrl": "message/send?access_token={0}", "PushCardUrl": "message/update_template_card?access_token={0}", "PushTokenUrl": "gettoken?corpid=&corpsecret=", "CallBackToken": "", "EncodingAESKey": "", "corpid": "" }
2.3 Startup.cs
public void ConfigureServices(IServiceCollection services) { services.AddHttpClient("WxClient", config => { config.BaseAddress = new Uri(Configuration["Wx:baseurl"]); config.DefaultRequestHeaders.Add("Accept", "application/json"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { GlobalContext.httpClientFactory = app.ApplicationServices.GetService<IHttpClientFactory>(); }
2.4 GlobalContext.cs
提供了Token,推送等方法。
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Net.Http; using System.Text; namespace Wx { public class GlobalContext { public static IHttpClientFactory httpClientFactory { get; set; } /// <summary> /// Wx 过期时间 /// </summary> public static DateTime TimeOutDate { get; set; } /// <summary> /// Wx Token /// </summary> public static string Token { get; set; } /// <summary> /// 获取Token /// </summary> /// <returns>Item1 Token;Item2 是否成功</returns> public static Tuple<string, bool> GetPushToken() { //判断Token是否存在 以及Token是否在有效期内 if (string.IsNullOrEmpty(Token) || TimeOutDate > DateTime.Now) { //构造请求链接 var requestBuild = AppSetting.Configuration["Wx:PushTokenUrl"]; using (var wxClient = httpClientFactory.CreateClient("WxClient")) { var httpResponse = wxClient.GetAsync(requestBuild).Result; var dynamic = JsonConvert.DeserializeObject<GetTokenResult>( httpResponse.Content.ReadAsStringAsync().Result ); if (dynamic.errcode == 0) { Token = dynamic.access_token; //过期5分钟前刷新Token var expires_in = Convert.ToDouble(dynamic.expires_in - 5 * 60); TimeOutDate = DateTime.Now.AddSeconds(expires_in); return Tuple.Create(Token, true); } else { return Tuple.Create($"获取Token失败,错误:{ dynamic.errmsg}", false); } } } else { return Tuple.Create(Token, true); } } /// <summary> /// 推送MES /// </summary> /// <returns>Item1 Token;Item2 是否成功</returns> public static string WxPush(string content) { //构造请求链接 var requestBuild = AppSetting.Configuration["Wx:PushUrl"]; var (token, issuccess) = GetPushToken(); if (!issuccess) throw new Exception(token); requestBuild = string.Format(requestBuild, token); //建立HttpClient using (var wxClient = httpClientFactory.CreateClient("WxClient")) { byte[] data = Encoding.UTF8.GetBytes(content); var bytearray = new ByteArrayContent(data); var httpResponse = wxClient.PostAsync(requestBuild, bytearray).Result; var dynamic = JsonConvert.DeserializeObject<dynamic>( httpResponse.Content.ReadAsStringAsync().Result ); bytearray.Dispose(); if (dynamic.errcode == 0) return "推送成功!"; if (dynamic.errcode == 82001) throw new Exception("推送失败,原因:未配置员工手机号或者员工手机号不在应用可见范围!"); else throw new Exception($"推送失败,原因:{JsonConvert.SerializeObject(dynamic) }"); } } /// <summary> /// 获取发送内容 /// </summary> /// <param name="userId"></param> /// <param name="Msg"></param> /// <returns></returns> public static string GetTextContent(string userId, string msg, int agentid) { var objText = new { content = msg }; string text = JsonConvert.SerializeObject(objText); var obj = new { touser = userId, toparty = "", totag = "", msgtype = "text", agentid = agentid, text = objText, safe = 0, enable_id_trans = 0, enable_duplicate_check = 0, duplicate_check_interval = 1800 }; string strJson = JsonConvert.SerializeObject(obj); return strJson; } /// <summary> /// 更新微信推送消息内容 /// </summary> /// <param name="userId"></param> /// <param name="responsecode"></param> /// <param name="replacename"></param> /// <param name="agentid"></param> /// <returns></returns> public static string UpdateTextCardContent(string[] userId, string responsecode, string replacename, int agentid) { var obj = new { userids = userId, atall = 0, agentid = agentid, response_code = responsecode, button = new { replace_name = replacename } }; string strJson = JsonConvert.SerializeObject(obj); return strJson; } /// <summary> /// 更新卡片消息 /// </summary> /// <returns>Item1 Token;Item2 是否成功</returns> public static string UpdateCard(string content) { //构造请求链接 var requestBuild = AppSetting.Configuration["Wx:PushCardUrl"]; var (token, issuccess) = GetPushToken(); if (!issuccess) throw new Exception(token); requestBuild = string.Format(requestBuild, token); //建立HttpClient using (var wxClient = httpClientFactory.CreateClient("WxClient")) { byte[] data = Encoding.UTF8.GetBytes(content); var bytearray = new ByteArrayContent(data); var httpResponse = wxClient.PostAsync(requestBuild, bytearray).Result; var dynamic = JsonConvert.DeserializeObject<dynamic>( httpResponse.Content.ReadAsStringAsync().Result ); bytearray.Dispose(); if (dynamic.errcode == 0) return "推送成功!"; if (dynamic.errcode == 82001) throw new Exception("推送失败,原因:未配置员工手机号或者员工手机号不在应用可见范围!"); else throw new Exception($"推送失败,原因:{JsonConvert.SerializeObject(dynamic) }"); } } } }
2.5 回调中编写内容
下图中是按钮交互性的按钮,点击确认会触发回调服务器的方法,在回调服务中根据返回的FromUserName和ResponseCode调用更新模板方法,把按钮改为已推送。
下面代码为回调服务中的Post方法,在1中搭建回调服务器中的方法。业务逻辑,失主请求获取联系方式,拾取人点击确认,推送联系方式至失主企业微信。将确认更新成已发送。可根据自己的实际业务替换内容,其中UpdateCard方法为更新模板已推送方法。
[HttpPost, Route("callback/interAspect")] public ContentResult AcceptMessage(string msg_signature,string timestamp,string nonce) { //获取被动响应包 string encrypt = ""; using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8)) { encrypt = sr.ReadToEndAsync().Result; } //验证 WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(AppSetting.Configuration["Wx:CallBackToken"] , AppSetting.Configuration["Wx:EncodingAESKey"] , AppSetting.Configuration["Wx:corpid"]); string sMsg = ""; // 解析之后的明文 int ret = wxcpt.DecryptMsg(msg_signature, timestamp, nonce, encrypt, ref sMsg); if (ret != 0) { throw new Exception(); } // ret==0表示解密成功,sMsg表示解密之后的明文xml串 XmlDocument doc = new XmlDocument(); doc.LoadXml(sMsg); XmlNode root = doc.FirstChild; string userName = root["FromUserName"].InnerText; string eventKey = root["EventKey"].InnerText; string responseCode = root["ResponseCode"].InnerText; //业务逻辑 eventKey是我保存的请求人推送UserID。 var content = GlobalContext.GetTextContent(eventKey, $"我的联系方式:" + userName, 1); var message = GlobalContext.WxPush(content); if (message == "推送成功!") { try { var responseContent = GlobalContext.UpdateTextCardContent(new string[] { userName }, responseCode, "已推送", 1); var updateMessage = GlobalContext.UpdateCard(responseContent); if (updateMessage == "推送成功!") { return Content("成功"); } else {throw new Exception(); } } catch(Exception ex) {throw new Exception(); } } else { throw new Exception(); } }
3.测试
4.链接
更新模板卡片:https://developer.work.weixin.qq.com/document/path/94888