.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.js
onLaunch() {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;});
}
//获取code
function userLogin() {return new Promise((resolve, reject) => {wx.login({success(res) {if (res.code) {resolve(res);}}});});
}
//获取openId、session_key、unionId
function 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.weixin.qq.com/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)
{//解密获取unionId
UserInfoModel 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 System.Net;
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.weixin.qq.com/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);//利用ASP.NET Core的依赖注入容器系统,通过请求获取IHttpContextAccessor接口,我们拥有模拟使用HttpContext.Current这样API的可能性。但是因为IHttpContextAccessor接口默认不是由依赖注入进行实例管理的。我们先要将它注册到ServiceCollection中services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();MyHttpContext.ServiceProvider = services.BuildServiceProvider();}//自定义实现context
using 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{//此处若报错,可参考文章 https://www.cnblogs.com/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.weixin.qq.com/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;}}}
}

谢谢打赏!

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

  1. springboot微信公众号发送模板消息

    springboot微信公众号发送模板消息 1.准备工作 申请你所需要模板 配置ip白名单(你所需要部署的服务器ip) 2.编写模板消息的请求参数封装类 import java.util.HashMa ...

  2. php 微信模板消息url,【求助】php 微信公众号 发送模板消息改变不了颜色

    php 微信公众号 发送模板消息改变不了颜色 不知道为什么 1.模板消息内容: 2.发送的模板消息效果: 序列化的模板消息内容如下: 大家可以测试下,touser需要另外添加下 a:4:{s:11:& ...

  3. 微信公众号 发送模板消息和获取关注公众号人数

    微信公众号发送模板消息 1.创建模板,拿到模板ID 2.创建发送消息工具类 import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JS ...

  4. php之微信公众号发送模板消息

    讲一下开发项目中微信公众号发送模板消息的实现过程(我用的还是Thinkphp5.0).先看一下效果,如图: 就是类似于这样的,下面讲一下实现过程: 第一步:微信公众号申请模板消息权限: 立即申请: 申 ...

  5. tp5微信公众号发送模板消息

    ``话不多说直接贴代码(公众号设置模板id看官方文档 ) <?php namespace app\admin\controller; use think\Controller; use thin ...

  6. 使用微信公众号发送模板消息

    使用微信公众号 API 本文所有内容均使用微信公众号测试号平台来演示 打开公众平台的测试号管理页面后我们可以在页面中看到测试号的信息 图中的 appId 和 appSecret 就是我们需要用到的 图 ...

  7. 微信公众号 java发送消息_微信公众号发送模板消息 Java实现。

    本博文是测试公众号调用模板接口测试.请不要完全复制我的代码.里面的测试代码中有本人测试号的微信模板id.麻烦替换成自己的可以吗? 第一步:创建模板信息 第二步:准备模板代码实体类用到的属性自行加入就行 ...

  8. 微信开放平台(第三方平台)代替微信公众号发送模板消息(基于lavarel框架开发,EasyWeChat)

    1.公众号必须得把模板消息授权到第三方平台. 2.我用的是 EasyWeChat 3.通过接口修改账号所属行业 (实质上就是开通模板消息) //修改账号所属行业public function set_ ...

  9. (Java)微信公众号发送模板消息

    模板消息仅用于公众号向用户发送重要的服务通知,只能用于符合其要求的服务场景中,如信用卡刷卡通知,商品购买成功通知等.不支持广告等营销类消息以及其它所有可能对用户造成骚扰的消息. 1.模板消息调用时主要 ...

最新文章

  1. man、info、help
  2. B09_NumPy迭代数组(控制遍历顺序,修改数组中元素的值,使用外部循环,广播迭代)
  3. 微信重大更新!这特么是为上班摸鱼开发的吧.....(附内测地址)
  4. 我要自学网python视频教程_人生苦短,请用Python!学习Python的四大理由
  5. [图神经网络] 图节点Node表示(DeepWalk与Node2Vec)
  6. leetcode1 两数之和
  7. 三大数据库数据库端口号及连接jdbc驱动下载
  8. Webpack实战(八):教你搞懂webpack如果实现代码分片(code splitting)
  9. java canvas 动态画图_canvas前端动图如何实现
  10. 2018年1月问答系统综述
  11. Pytorch基础(三)数据集加载及预处理
  12. 设计一函数,求整数区间[a,b]和[c,d]的交集
  13. java集合框架之LinkedList
  14. linux NVMe驱动总结
  15. 恋物志(二):独居者的智能生活指南
  16. python flask web框架_Python_WEB框架之Flask
  17. 那村的人儿(村长)第十期数码照片后期处理全套培训教程
  18. 苹果越狱后怎么还原_TestFlight/苹果TF上架到期后怎么办?
  19. [iOS][转]iOS 架构模式 - 简述 MVC, MVP, MVVM 和 VIPER (译)
  20. 微信浏览器调起来扫一扫和问题总结

热门文章

  1. Hive Archive合并文件归档,减少小文件数量(推荐)
  2. 拾人牙慧 在C#中如何解决SQL注入攻击
  3. 【华为机试真题 Python实现】路灯问题【2022 Q1 Q2 |200分】
  4. 在IE网页右键中添加“创建到桌面快捷方式”
  5. C语言从零开始——学这一篇文章就够了
  6. 微软不能关闭服务器,微软正在解决部分Surface Laptop用户遭遇的无法退出S模式的故障...
  7. 文本检索系统-7:软件和数据
  8. vue一键截图并上传至后台
  9. jquery validation engine ajax,validationEngine ajax验证 java
  10. 首个MaaS正式上线,元界即服务将会是未来吗?