100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > .Net Core 3.1实现微信公众号发送模板消息 且跳转微信小程序

.Net Core 3.1实现微信公众号发送模板消息 且跳转微信小程序

时间:2022-07-12 14:04:52

相关推荐

.Net Core 3.1实现微信公众号发送模板消息 且跳转微信小程序

.Net Core 3.1开发微信相关

背景微信公众号和小程序关联微信小程序获取UnionId及OpenId微信公众号获取UnionId及OpenId

背景

公司需要将内部产品的审批放到微信小程序上,为广大客户提供更便捷和高效的单据审批体验。目前微信小程序功能及后台逻辑已经实现,需要把审批的通知信息发送到微信公众号(微信小程序无法发送消息给客户)

微信公众号和小程序关联

微信小程序审批之后,根据设置的审批流程,通知给下一个或多个审批人。需要知道同一个人的小程序和公众号的关联关系。微信小程序、公众号对应的每一个客户都分别对应唯一的openId。同一个人要关联,就需要UnionId。

UnionId:如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过unionid来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的unionid是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid是相同的。(特别注意加粗部分,特别重要)。需要到微信开放平台关联小程序及公众号,才能调用接口获取UnionId

微信小程序获取UnionId及OpenId

小程序要获取UnionId有两个方法

第一个是通过获取Code,通过Code换取openId和session_key(获取UnionId需要),此时关注了公众号的用户可以直接换取到UnionId。

//前端源码//小程序app.jsonLaunch() {var that = this;userLogin().then(res => {//获取到的code:res.code//用code换取openId和session_key及unionIdreturn getOpenid(res.code);}).then(res => {//openIdthat.globalData.openid = res.data.openid;//session_keythat.globalData.sessionkey = res.data.session_key;});}//获取codefunction userLogin() {return new Promise((resolve, reject) => {wx.login({success(res) {if (res.code) {resolve(res);}}});});}//获取openId、session_key、unionIdfunction getOpenid(code) {return new Promise((resolve, reject) => {wx.request({url: "https://你的服务器地址/api/Login/GetOpenId?js_code="+code,method: "GET",success: function(res) {//得到电话号码resolve(res);},fail(err) {reject(err);}});});}

//后端源码//appid:小程序appid//secret:小程序secret//js_code:上面获取到的code[HttpGet]public ActionResult GetOpenId(string js_code){string url = string.Format("https://api./sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code", WXConst.appid,WXConst.secret, js_code);string html = HttpRestlt.GetHttpResult(url);return Ok(html);}

第二个是通过调用接口wx.getUserInfo,从解密数据(encryptedData)中获取 UnionID(推荐使用)。无论如何,还是要获取到session_key,所以绕不过上面的方法。

//Login布局 [open-type]一定要这么写,不然不会跳出授权窗口,默认失败<button class="userbtn" open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo" >请登录</button>//单击事件bindGetUserInfo: function (e) {var that = this;if (e.detail.errMsg == "getUserInfo:ok") {wx.getUserInfo({success:function(res) {let url="https://你的服务器地址/api/Login/getUserUnoinID";let params={session_key: app.globalData.sessionkey,encryptedData: res.encryptedData,iv: res.iv};let method="POST";return request.getResult(url,params,method).then(res=>{if(res.data=="保存成功"){//......other code}});}})}}

//后端源码[HttpPost]public async Task<ActionResult> getUserUnoinID(PhoneNumManagement management){//解密获取unionIdUserInfoModel unionId = getUnionIdT<UserInfoModel>(management);//保存到后台bool result = await _login.SaveUnionId(unionId);if (!result)return BadRequest();elsereturn Ok("保存成功");}//因为需要对encryptedData做对称解密,以下是解密算法private T getUnionIdT<T>(PhoneNumManagement management){try{//非空验证if (!string.IsNullOrWhiteSpace(management.encryptedData) && !string.IsNullOrWhiteSpace(management.session_key) && !string.IsNullOrWhiteSpace(management.iv)){var decryptBytes = Convert.FromBase64String(management.encryptedData.Replace(' ', '+'));var keyBytes = Convert.FromBase64String(management.session_key.Replace(' ', '+'));var ivBytes = Convert.FromBase64String(management.iv.Replace(' ', '+'));var aes = new AesCryptoServiceProvider{Key = keyBytes,IV = ivBytes,Mode = CipherMode.CBC,Padding = PaddingMode.PKCS7};var outputBytes = aes.CreateDecryptor().TransformFinalBlock(decryptBytes, 0, decryptBytes.Length);var decryptResult = Encoding.UTF8.GetString(outputBytes);T decryptData = JsonConvert.DeserializeObject<T>(decryptResult);return decryptData;}else{return default;}}catch (Exception e){return default;}}

//补充HttpRestlt发送消息类using AutoUpdate;using System;using System.IO;using ;using System.Text;namespace MobileApp.Utils{public static class HttpRestlt{/// <summary>/// 获取消息/// </summary>/// <param name="url"></param>/// <returns></returns>public static string GetHttpResult(string url){HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);request.Method = "GET";request.ContentType = "application/x-www-form-urlencoded;charset=utf-8";HttpWebResponse response = (HttpWebResponse)request.GetResponse();Stream myResponseStream = response.GetResponseStream();StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.UTF8);string retString = myStreamReader.ReadToEnd();myStreamReader.Close();myResponseStream.Close();return retString;}/// <summary>/// 发送消息,并获取结果/// </summary>/// <param name="url"></param>/// <param name="senddata"></param>/// <returns></returns>public static string GetHttpSendResult(string url, string senddata){string result = null;byte[] byteData = Encoding.GetEncoding("UTF-8").GetBytes(senddata);try{HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);request.ContentType = "application/x-www-form-urlencoded";request.Referer = url;request.Accept = "*/*";request.Timeout = 30 * 1000;request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)";request.Method = "POST";request.ContentLength = byteData.Length;Stream stream = request.GetRequestStream();stream.Write(byteData, 0, byteData.Length);stream.Flush();stream.Close();HttpWebResponse response = (HttpWebResponse)request.GetResponse();Stream backStream = response.GetResponseStream();StreamReader sr = new StreamReader(backStream, Encoding.GetEncoding("UTF-8"));result = sr.ReadToEnd();sr.Close();backStream.Close();response.Close();request.Abort();}catch(Exception ex){LogHelper.WriteException(ex);LogHelper.WriteLog("发送微信模板消息:" + ex.Message);LogHelper.WriteLog(result);return string.Empty;}return result;}}}

微信公众号获取UnionId及OpenId

微信公众号须是服务号微信公众号需要绑定小程序微信公众号需要开通开发者权限,且认证为开发者微信公众号验证开发者,且接收微信服务器推送的消息,获取openId及UnionId

using AutoUpdate;using Microsoft.AspNetCore.Http;using Microsoft.AspNetCore.Mvc;using MobileApp.BLL.Utils;using MobileApp.Entities;using MobileApp.IBLL;using MobileApp.Models;using MobileApp.Utils;using Newtonsoft.Json;using System;using System.IO;using System.Linq;using System.Text;namespace MobileApp.Controllers{/// <summary>/// 微信小程序端,根据点击的ID,获取送审单据内容/// </summary>[ApiController][Route("api/[controller]/[action]")]//[Authorize]public class SendMessageController : ControllerBase{private readonly ILogin<UseEntity> _login;public SendMessageController(ILogin<UseEntity> login){this._login = login;}//注:此方法没有写是什么请求。是因为验证为开发者的时候是Get请求。//发送其它消息的时候又是Post请求。所以此处不填请求方式。public void ProcessRequest(){//此处需要用到context,但是.net fw和.net core的api有所不同,下面会给出源码Microsoft.AspNetCore.Http.HttpContext context = MyHttpContext.Current;string postString = null;try{//验证的时候调用下面的方法,验证通过后,后面就不用了,可以注释掉//ValidDev(context);//此处开始为接收微信服务器推送的消息if (context.Request.Method.ToUpper() == "POST"){context.Request.EnableBuffering();context.Request.Body.Seek(0, 0);context.Request.Body.Position = 0;using (var ms = new MemoryStream()){context.Request.Body.CopyTo(ms);var b = ms.ToArray();postString = Encoding.UTF8.GetString(b);Handle(postString);}}}catch (Exception ex){LogHelper.WriteLog("关注公众号:" + ex.Message);}}/// <summary>/// 验证成为微信服务号开发者 第一次验证的时候被调用即可/// </summary>[HttpGet]public void ValidDev(Microsoft.AspNetCore.Http.HttpContext context){const string token = "hzrgwkjyxgs";string echoString = null;//string signature = null;//string timestamp = null;//string nonce = null;try{echoString = context.Request.Query["echoStr"].FirstOrDefault();//signature = context.Request.Query["signature"].FirstOrDefault();//timestamp = context.Request.Query["timestamp"].FirstOrDefault();//nonce = context.Request.Query["nonce"].FirstOrDefault();if (!string.IsNullOrEmpty(echoString)){context.Response.WriteAsync(echoString);}}catch (Exception ex){LogHelper.WriteLog("验证开发者:" + ex.Message);}}/// <summary>/// 处理信息得到openId/// </summary>/// <param name="postStr"></param>private void Handle(string postStr){//XmlHelper.GetT将微信发送的xml解析为自定义类,下面会提供源码WeChartAcceptMessage message = XmlHelper.GetT<WeChartAcceptMessage>(postStr);if (message == null)return;string openId = message.FromUserName;//获取access_tokenstring access_token = GetAccessToken.GetAccess_Token();GetUnionid(openId, access_token);}/// <summary>/// 获取用户唯一Unionid/// </summary>/// <param name="openId"></param>private async void GetUnionid(string openId,string accessToken){string url = string.Format("https://api./cgi-bin/user/info?access_token={0}&openid={1}&lang=zh_CN ", accessToken, openId);string html = HttpRestlt.GetHttpResult(url);UseWeChartInfoModel useWeChartInfoModel = JsonConvert.DeserializeObject<UseWeChartInfoModel>(html);if (useWeChartInfoModel == null)return;string unionId = useWeChartInfoModel.unionid;UserInfoModel userInfoModel = new UserInfoModel{openId = openId,unionid = unionId};bool result = await _login.EditAppId(userInfoModel);if (!result)LogHelper.WriteLog("微信公众号获取UnionId保存失败");}}}

context在.net core中的用法解析,可参考文章

public void ConfigureServices(IServiceCollection services ){//3.0中默认禁用了AllowSynchronousIO,同步读取body的方式需要ConfigureServices中配置允许同步读取IO流,否则可能会抛出异常 Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.services.Configure<KestrelServerOptions>(x => x.AllowSynchronousIO = true).Configure<IISServerOptions>(x => x.AllowSynchronousIO = true);//利用 Core的依赖注入容器系统,通过请求获取IHttpContextAccessor接口,我们拥有模拟使用HttpContext.Current这样API的可能性。但是因为IHttpContextAccessor接口默认不是由依赖注入进行实例管理的。我们先要将它注册到ServiceCollection中services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();MyHttpContext.ServiceProvider = services.BuildServiceProvider();}//自定义实现contextusing System;namespace MobileApp{public static class MyHttpContext{public static IServiceProvider ServiceProvider;public static Microsoft.AspNetCore.Http.HttpContext Current{get{object factory = ServiceProvider.GetService(typeof(Microsoft.AspNetCore.Http.IHttpContextAccessor));Microsoft.AspNetCore.Http.HttpContext context = ((Microsoft.AspNetCore.Http.HttpContextAccessor)factory).HttpContext;return context;}}}}

xml反序列化得到实体,如有错误可参考

using AutoUpdate;using System;using System.IO;using System.Xml.Serialization;namespace MobileApp.Utils{public class XmlHelper{/// <summary>/// xml反序列化/// </summary>/// <typeparam name="T"></typeparam>/// <param name="xml"></param>/// <returns></returns>public static T GetT<T>(string xml){try{//此处若报错,可参考文章 /lionetchen/p/3926846.htmlXmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute("xml"));StringReader reader = new StringReader(xml.Replace(" ", ""));T entity = (T)serializer.Deserialize(reader);reader.Close();reader.Dispose();return entity;}catch (Exception ex){LogHelper.WriteLog("xml序列化为类:" + ex.Message);return default(T);}}}}

获取Access_Token

using AutoUpdate;using Microsoft.Extensions.Caching.Memory;using MobileApp.Models;using MobileApp.Utils;using Newtonsoft.Json;using System;using System.Collections.Generic;using System.Linq;namespace MobileApp.BLL.Utils{public static class GetAccessToken{public static IMemoryCache _memoryCache = new MemoryCache(new MemoryCacheOptions());public static string GetAccess_Token(){string access_token = null;Dictionary<string, DateTime> accesstoken = new Dictionary<string, DateTime>();var item = _memoryCache.Get("accesstoken");if (item == null){access_token = Get_Access();if (access_token == string.Empty)return string.Empty;accesstoken.Add(access_token, DateTime.Now);_memoryCache.Set("accesstoken", accesstoken);return access_token;}else{accesstoken = (Dictionary<string, DateTime>)item;DateTime timePre = accesstoken.FirstOrDefault().Value;//若过期,重新申请if ((timePre - DateTime.Now).Hours < -2){access_token = Get_Access();if (access_token == string.Empty)return string.Empty;accesstoken.Add(access_token, DateTime.Now);_memoryCache.Remove("accesstoken");_memoryCache.Set("accesstoken", accesstoken);return access_token;}elsereturn accesstoken.First().Key;}}public static string Get_Access(){//此处的WXConst.appid_fwh WXConst.secret_fwh是公众号对应的内容,不是小程序对应的内容,不要混淆string url = string.Format("https://api./cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", WXConst.appid_fwh, WXConst.secret_fwh);string result = HttpRestlt.GetHttpResult(url);try{AccessToeknModel toeknModel = JsonConvert.DeserializeObject<AccessToeknModel>(result);return toeknModel.access_token;}catch (Exception ex){LogHelper.WriteException(ex);LogHelper.WriteLog("获取accesstoken:" + ex.Message);LogHelper.WriteLog(result);return string.Empty;}}}}

谢谢打赏!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。