zoukankan      html  css  js  c++  java
  • C# aspnetcore 3.1 微信小程序发送订阅消息封装

    一、appsettings.json定义小程序配置信息

    "WX": {
      "AppId": "wx88822730803edd44",
      "AppSecret": "75b269042e8b5026e6ed14aa24ba9353",
      "Templates": {
      "Audit": {
        "TemplateId": "aBaIjTsPBluYtj2tzotzpowsDDBGLhXQkwrScupnQsM",
        "PageUrl": "/pages/index/formAudit?formId={0}&tableId={1}",
        "MiniprogramState": "developer",
        "Lang": "zh_TW",
        "Data": {
            "Title": "thing6",
            "Content": "thing19",
            "Date": "date9"
          }
        }
      },
      "SignatureToken": "aaaaaa",
      "MessageSendUrl": "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={0}",
      "AccessTokenUrl": "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}"
    }


    二、编写通用类加载配置

    using System;
    using System.Text;
    using System.Security.Cryptography;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Configuration.Json;
    
    namespace WXERP.Services
    {
      /// <summary>
      /// 项目公有静态类
      /// </summary>
      public class Common
      {
        /// <summary>
        /// 獲取根目錄
        /// </summary>
        public static string AppRoot => Environment.CurrentDirectory;// AppContext.BaseDirectory;
        /// <summary>
        /// 獲取項目配置
        /// </summary>
        public static IConfiguration Configuration { get; set; }
        /// <summary>
        /// 加載項目配置
        /// </summary>
        static Common()
        {
          Configuration = new ConfigurationBuilder()
          .Add(new JsonConfigurationSource
          {
            Path = "appsettings.json",
            ReloadOnChange = true //当appsettings.json被修改时重新加载 
          })
          .Build();
        }
    
        /// <summary>
        /// SHA1加密
        /// </summary>
        /// <param name="content">需要加密的字符串</param>
        /// <returns>返回40位大寫字符串</returns>
        public static string SHA1(string content)
        {
          try
          {
            SHA1 sha1 = new SHA1CryptoServiceProvider();
            byte[] bytes_in = Encoding.UTF8.GetBytes(content);
            byte[] bytes_out = sha1.ComputeHash(bytes_in);
            sha1.Dispose();
            string result = BitConverter.ToString(bytes_out);
            result = result.Replace("-", "");
            return result;
          }
          catch (Exception ex)
          {
            throw new Exception("Error in SHA1: " + ex.Message);
          }
        }
    
      }
    }

    三、编写HttpHelper请求类

    using System;
    using System.Text;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    using System.Collections.Generic;
    
    namespace WXERP.Services
    {
      /// <summary>
      /// HTTP請求輔助類
      /// </summary>
      public class HttpHelper
      {
        /// <summary>
        /// post同步請求
        /// </summary>
        /// <param name="url">地址</param>
        /// <param name="postData">數據</param>
        /// <param name="contentType">application/xml、application/json、application/text、application/x-www-form-urlencoded</param>
        /// <param name="headers">請求頭</param> 
        /// <returns></returns>
        public static string HttpPost(string url, string postData = null, string contentType = null, Dictionary<string, string> headers = null)
        {
          using HttpClient client = new HttpClient();
    
          if (headers != null)
          {
            foreach (var header in headers)
            client.DefaultRequestHeaders.Add(header.Key, header.Value);
          }
    
          postData ??= "";
          using HttpContent httpContent = new StringContent(postData, Encoding.UTF8);
          if (contentType != null)
          httpContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
    
          HttpResponseMessage response = client.PostAsync(url, httpContent).Result;
          return response.Content.ReadAsStringAsync().Result;
        }
    
        /// <summary>
        /// post異步請求
        /// </summary>
        /// <param name="url">地址</param>
        /// <param name="postData">數據</param>
        /// <param name="contentType">application/xml、application/json、application/text、application/x-www-form-urlencoded</param>
        /// <param name="timeOut">請求超時時間</param> 
        /// <param name="headers">請求頭</param> 
        /// <returns></returns>
        public static async Task<string> HttpPostAsync(string url, string postData = null, string contentType = null, int timeOut = 30, Dictionary<string, string> headers = null)
        {
          using HttpClient client = new HttpClient();
          client.Timeout = new TimeSpan(0, 0, timeOut);
    
          if (headers != null)
          {
            foreach (var header in headers)
            client.DefaultRequestHeaders.Add(header.Key, header.Value);
          }
    
          postData ??= "";
          using HttpContent httpContent = new StringContent(postData, Encoding.UTF8);
          if (contentType != null)
            httpContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
    
          HttpResponseMessage response = await client.PostAsync(url, httpContent);
          return await response.Content.ReadAsStringAsync();
        }
    
        /// <summary>
        /// get同步請求
        /// </summary>
        /// <param name="url">地址</param>
        /// <param name="headers">請求頭</param>
        /// <returns></returns>
        public static string HttpGet(string url, Dictionary<string, string> headers = null)
        {
          using HttpClient client = new HttpClient();
    
          if (headers != null)
          {
            foreach (var header in headers)
            client.DefaultRequestHeaders.Add(header.Key, header.Value);
          }
    
          HttpResponseMessage response = client.GetAsync(url).Result;
          return response.Content.ReadAsStringAsync().Result;
        }
    
        /// <summary>
        /// get異步請求
        /// </summary>
        /// <param name="url"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static async Task<string> HttpGetAsync(string url, Dictionary<string, string> headers = null)
        {
          using HttpClient client = new HttpClient();
    
          if (headers != null)
          {
            foreach (var header in headers)
            client.DefaultRequestHeaders.Add(header.Key, header.Value);
          }
    
          HttpResponseMessage response = await client.GetAsync(url);
          return await response.Content.ReadAsStringAsync();
        }
    
      }
    }

    四、在sqlserver下存储并获取openid,这个主要是因为提交消息并不是在微信小程序端,如果是在微信小程序上发起订阅消息,可以忽略这个步骤

    // 创建数据库表
    
    create table TBSF_Conmmunicate_WXUser
    (
      ID int identity(1,1) primary key,
      Staff_ID varchar(10),
      OpenId varchar(50),
      SessionKey varchar(50),
      UnionId varchar(50),
      IsValid bit,
    )
    
    // SqlHelper数据库辅助类来自于CommunicationOperateDBUtility,可以自己编写
    
    using System.Data;
    using System.Text;
    using CommunicationOperateDBUtility;
    
    namespace WXERP.Services.CommunicationOperateDAL
    {
      /// <summary>
      /// 微信信息
      /// </summary>
      public class WXInforDeal
      {
        private SqlHelper sqlHelper = null;
        /// <summary>
        /// 初始化數據庫輔助對象
        /// </summary>
        /// <param name="con"></param>
        public WXInforDeal(object con)
        {
          sqlHelper = new SqlHelper(con);
        }
        /// <summary>
        /// 獲取微信登陸用戶信息
        /// </summary>
        /// <param name="staffIdList">工號</param>
        /// <returns></returns>
        public DataSet GetLoginUserInfo(string staffIdList)
        {
          DataSet ds = new DataSet();
          StringBuilder stringBuilder = new StringBuilder();
          stringBuilder.Append(" SELECT distinct OpenId FROM ");
          stringBuilder.Append(" TBSF_Conmmunicate_WXUser WHERE Staff_ID IN (");
          stringBuilder.Append(staffIdList);
          stringBuilder.Append(")");
          string strSql = stringBuilder.ToString();
          sqlHelper.DBRunSql(strSql, ref ds);
          return ds;
        }
      }
    }

    五、编写订阅消息基类模型

    using System;
    using System.Data;
    using Newtonsoft.Json;
    using System.Collections.Generic;
    using WXERP.Services.CommunicationOperateDAL;
    
    namespace WXERP.Models
    {
      /// <summary>
      /// 訂閲消息請求模型
      /// </summary>
      public class SubscribeMessageModel
      {
        /// <summary>
        /// 初始化審核訂閲消息
        /// </summary>
        /// <param name="dbTransOrCnn">數據庫事務</param>
        /// <param name="nextAuditStaffId">下一個審核通知用戶工號</param>
        public SubscribeMessageModel(object dbTransOrCnn, string nextAuditStaffId)
        {
          WXInforDeal wxInfoDeal = new WXInforDeal(dbTransOrCnn);
          DataSet wxUserInfo = wxInfoDeal.GetLoginUserInfo(nextAuditStaffId);
          if (wxUserInfo != null && wxUserInfo.Tables.Count > 0 && wxUserInfo.Tables[0].Rows.Count > 0)
          {
            Touser = wxUserInfo.Tables[0].Rows[0]["OpenId"].ToString();
          }
        }
        /// <summary>
        /// 消息接收者的openid
        /// </summary>
        [JsonProperty("touser")]
        public string Touser { get; set; }
        /// <summary>
        /// 消息模板ID
        /// </summary>
        [JsonProperty("template_id")]
        public string TemplateId { get; set; }
        /// <summary>
        /// 點擊模板卡片后的跳轉頁面,僅限本小程序内的頁面,支持帶參數(示例index?foo=bar),該字段不填則不跳轉
        /// </summary>
        [JsonProperty("page")]
        public string Page { get; set; }
        /// <summary>
        /// 跳轉小程序類型:developer開發版、trial體驗版、formal正式版,默认为正式版
        /// </summary>
        [JsonProperty("miniprogram_state")]
        public string MiniprogramState { get; set; }
        /// <summary>
        /// 進入小程序查看的語言類型,支持zh_CN(簡體中文)、en_US(英文)、zh_HK(繁體中文)、zh_TW(繁體中文),默認為zh_CN
        /// </summary>
        [JsonProperty("lang")]
        public string Lang { get; set; }
        /// <summary>
        /// 模板内容
        /// </summary>
        [JsonProperty("data")]
        public Dictionary<string, DataValue> Data { get; set; }
      }
      /// <summary>
      /// 模板内容關鍵字
      /// </summary>
      public class DataValue
      {
        /// <summary>
        /// 訂閲消息參數值
        /// </summary>
        [JsonProperty("value")]
        public string Value { get; set; }
      }
    
      /// <summary>
      /// 小程序訂閲消息響應模型
      /// </summary>
      public class SubscribeMsgResponseModel
      {
        /// <summary>
        /// 錯誤代碼
        /// </summary>
        public int Errcode { get; set; }
        /// <summary>
        /// 錯誤信息
        /// </summary>
        public string Errmsg { get; set; }
      }
    
      /// <summary>
      /// 小程序獲取token響應模型
      /// </summary>
      public class AccessTokenResponseModel
      {
        /// <summary>
        /// 小程序訪問token
        /// </summary>
        public string Access_token { get; set; }
        /// <summary>
        /// Token過期時間,單位秒
        /// </summary>
        public int Expires_id { get; set; }
        /// <summary>
        /// Token創建時間
        /// </summary>
        public DateTime Create_time { get; set; }
        /// <summary>
        /// 刷新以後的Token
        /// </summary>
        public string Refresh_token { get; set; }
        /// <summary>
        /// 小程序用戶唯一標識,如果用戶未關注公衆號,訪問公衆號網頁也會產生
          /// </summary>
        public string Openid { get; set; }
        /// <summary>
        /// 用戶授權的作用域,使用逗號分隔
        /// </summary>
        public string Scope { get; set; }
      }
    
    }


    六、实现消息订阅基类,下面的SetTemplateData方法根据自己的情况设置需要推送消息的内容,如果以后有其他订阅消息模板,新增一个类实现SubscribeMessageModel

    using System;
    using System.Collections.Generic;
    using Newtonsoft.Json;
    using BestSoft.Common.Resources;
    using BSFWorkFlow.Common.GeneralUtility;
    using WXERP.Models;
    
    namespace WXERP.Services.SubscribeMessage
    {
      /// <summary>
      /// 審核訂閲消息
      /// </summary>
      public class AuditSubscribeMessage : SubscribeMessageModel
      {
        private string page;
        private string lang;
        private Dictionary<string, DataValue> data;
        /// <summary>
        /// 設置小程序OpenId
        /// </summary>
        /// <param name="dbTransOrCnn">數據庫事務</param>
        /// <param name="nextAuditStaffId">下一個審核通知用戶工號</param>
        public AuditSubscribeMessage(object dbTransOrCnn, string nextAuditStaffId)
        : base(dbTransOrCnn, nextAuditStaffId)
        {
    
        }
        /// <summary>
        /// 消息模板ID
        /// </summary>
        [JsonProperty("template_id")]
        public new string TemplateId => Common.Configuration["WX:Templates:Audit:TemplateId"];
    
        /// <summary>
        /// 設置小程序訂閲消息跳轉頁面
        /// </summary>
        /// <param name="formId"></param>
        /// <param name="tableId"></param>
        public void SetPageUrl(string formId, string tableId)
        {
          Page = string.Format(Common.Configuration["WX:Templates:Audit:PageUrl"],
          formId, tableId);
        }
        /// <summary>
        /// 點擊模板卡片后的跳轉頁面
        /// </summary>
        [JsonProperty("page")]
        public new string Page
        {
          get
          {
            return page;
          }
          set
          {
            page = value;
            return;
          }
        }
        /// <summary>
        /// 跳轉小程序類型
        /// </summary>
        [JsonProperty("miniprogram_state")]
        public new string MiniprogramState => Common.Configuration["WX:Templates:Audit:MiniprogramState"];
        /// <summary>
        /// 進入小程序查看的語言類型,支持zh_CN(簡體中文)、en_US(英文)、zh_HK(繁體中文)、zh_TW(繁體中文),默認為zh_CN
        /// </summary>
        [JsonProperty("lang")]
        public new string Lang
        {
          get
          {
            lang = Common.Configuration["WX:Templates:Audit:Lang"];
            if (!string.IsNullOrEmpty(MyHttpContext.Current.Request.Headers["bsLanKind"]))
            lang = MyHttpContext.Current.Request.Headers["bsLanKind"];
    
            return lang;
          }
          set
          {
            lang = value;
            return;
          }
        }
        /// <summary>
        /// 設置審核訂閲消息數據
        /// </summary>
        /// <param name="operation">審核動作:通過、否決、作廢、退回</param>
        /// <param name="itemAuditStatus">審核狀態:1代表審核完畢</param>
        /// <param name="currentWorkflowName">審核標題</param>
        public void SetTemplateData(WFAuditOperation operation, WFAuditItemStatus itemAuditStatus, string currentWorkflowName)
        {
          string tip_msg = "";
          switch (operation)
          {
            case WFAuditOperation.AuditPassAndAgree:
              if (itemAuditStatus == WFAuditItemStatus.SuccessfulToFinishAllAudits)
                tip_msg = GeneralFunction.ReplaceNullOrEmptyStr(SourcesWarehouse.GetStringSources("WFEngine_FinishAuditTip"), "您的單據已審核完成!");
              else
                tip_msg = GeneralFunction.ReplaceNullOrEmptyStr(SourcesWarehouse.GetStringSources("WFEngine_AuditAgreeTip"), "您有一筆新單據待審核!");
            break;
            case WFAuditOperation.AuditPassButDegree:
              tip_msg = GeneralFunction.ReplaceNullOrEmptyStr(SourcesWarehouse.GetStringSources("WFEngine_AuditDegreeTip"), "您提交的單據等待異議!");
            break;
            case WFAuditOperation.AuditAbort:
              tip_msg = GeneralFunction.ReplaceNullOrEmptyStr(SourcesWarehouse.GetStringSources("WFEngine_AuditAbortTip"), "您提交的單據已被作廢!");
            break;
            case WFAuditOperation.AuditBack:
              tip_msg = GeneralFunction.ReplaceNullOrEmptyStr(SourcesWarehouse.GetStringSources("WFEngine_AuditBackTip"), "您提交的單據已被退回修正!");
            break;
          }
    
          string title = Common.Configuration["WX:Templates:Audit:Data:Title"];
          string content = Common.Configuration["WX:Templates:Audit:Data:Content"];
          string date = Common.Configuration["WX:Templates:Audit:Data:Date"];
          Dictionary<string, DataValue> data = new Dictionary<string, DataValue>()
          {
            {title, new DataValue{ Value= currentWorkflowName }},
            {content, new DataValue{ Value= tip_msg }},
            {date, new DataValue{ Value= DateTime.Now.ToShortDateString() }}
          };
    
          Data = data;
        }
        /// <summary>
        /// 審核訂閲消息數據
        /// </summary>
        [JsonProperty("data")]
        public new Dictionary<string, DataValue> Data
        {
          get
          {
            return data;
          }
          set
          {
            data = value;
            return;
          }
        }
    
      }
    }

    七、编写发送订阅消息,消息推送配置签名认证

    using System;
    using System.Threading.Tasks;
    using System.Collections.Generic;
    using Newtonsoft.Json;
    using WXERP.Models;
    
    namespace WXERP.Services
    {
      /// <summary>
      /// 系統消息上下文
      /// </summary>
      public class MessageContext
      {
        /// <summary>
        /// 獲取AccessToken的全局鎖
        /// </summary>
        private readonly static object SyncLock = new object();
    
        private static Dictionary<string, AccessTokenResponseModel> tokenCache = new Dictionary<string, AccessTokenResponseModel>();
    
        /// <summary>
        /// 發送訂閲消息
        /// </summary>
        /// <param name="msg">消息内容</param>
        /// <param name="errMsg">可能由於獲取的token錯誤</param>
        /// <returns></returns>
        public static bool SendSubscribeMsg(SubscribeMessageModel msg, out string errMsg)
        {
          errMsg = "";
          try
          {
            string token = GetAccessToken();
            if (token.Length < 20)
            {
              errMsg = "Failed to send subscription message, Access token error!";
              return false;
            }
            string url = string.Format(Common.Configuration["WX:MessageSendUrl"], token);
            string requestJson = JsonConvert.SerializeObject(msg);
            string responseJson = HttpHelper.HttpPost(url, requestJson, "application/json", null);
    
            var msgResponse = JsonConvert.DeserializeObject<SubscribeMsgResponseModel>(responseJson);
            if (msgResponse.Errcode != 0)
            {
              errMsg = string.Format("Failed to send subscription message, {0}", msgResponse.Errmsg);
              return false;
            }
          }
          catch (Exception exp)
          {
            throw new Exception("SendSubscribeMsg: " + exp.Message);
          }
          return true;
        }
    
        /// <summary>
        /// 獲取小程序訪問token
        /// </summary>
        /// <returns></returns>
        private static string GetAccessToken()
        {
          lock (SyncLock)
          {
            string appid = Common.Configuration["WX:AppId"];
            string appsecret = Common.Configuration["WX:AppSecret"];
            string accessTokenUrl = string.Format(Common.Configuration["WX:AccessTokenUrl"], appid, appsecret);
    
            AccessTokenResponseModel result = null;
            if (tokenCache.ContainsKey(appid))
              result = tokenCache[appid];
    
            if (result == null)
            {
              string responseJson = HttpHelper.HttpGet(accessTokenUrl, null);
              result = JsonConvert.DeserializeObject<AccessTokenResponseModel>(responseJson);
              result.Create_time = DateTime.Now;
              tokenCache.Add(appid, result);
            }
            else if (DateTime.Compare(result.Create_time.AddSeconds(result.Expires_id), DateTime.Now) < 1)
            {
              string responseJson = HttpHelper.HttpGet(accessTokenUrl, null);
              result = JsonConvert.DeserializeObject<AccessTokenResponseModel>(responseJson);
              result.Create_time = DateTime.Now;
              tokenCache[appid] = result;
            }
            return result.Access_token;
          }
        }
    
        /// <summary>
        /// 驗證消息來自於微信服務器
        /// </summary>
        /// <param name="signature">微信加密簽名,signature結合了開發者填寫的token、timestamp、nonce</param>
        /// <param name="timestamp">時間戳</param>
        /// <param name="nonce">隨機數</param>
        /// <returns></returns>
        public async Task<bool> CheckSignature(string signature, string timestamp, string nonce)
        {
          string token = Common.Configuration["WX:SignatureToken"];
          string[] tmpArr = { token, timestamp, nonce };
          Array.Sort(tmpArr);
          string tmpStr = string.Join("", tmpArr);
          tmpStr = Common.SHA1(tmpStr);
    
          if (!tmpStr.Equals(signature, StringComparison.OrdinalIgnoreCase))
            return false;
    
          await Task.CompletedTask;
          return true;
        }
    
      }
    }

    八、编写消息推送配置签名认证控制器

    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using WXERP.Services;
    
    namespace WXERP.Controllers
    {
      /// <summary>
      /// 消息控制器
      /// </summary>
      [Route("api/[controller]")]
      [ApiController]
      public class MessageController : ControllerBase
      {
        private readonly MessageContext _context;
        /// <summary>
        /// 初始化消息
        /// </summary>
        public MessageController()
        {
          _context = new MessageContext();
        }
    
        /// <summary>微信消息</summary>
        /// <remarks>驗證消息來自於微信服務器</remarks>
        /// <param name="signature">微信加密簽名,signature結合了開發者填寫的token、timestamp、nonce</param>
        /// <param name="timestamp">時間戳</param>
        /// <param name="nonce">隨機數</param>
        /// <param name="echostr">隨機字符串</param>
        /// <returns></returns>
        [HttpGet("checkSignature")]
        [AllowAnonymous]
        public async void CheckSignature(string signature,string timestamp,string nonce,string echostr)
        {
          bool result = await _context.CheckSignature(signature, timestamp, nonce);
          if (result)
          {
            HttpContext.Response.ContentType = "text/plain; charset=utf-8";
            await HttpContext.Response.WriteAsync(echostr);
          }
          else
          {
            HttpContext.Response.StatusCode = 409;
            HttpContext.Response.ContentType = "text/plain; charset=utf-8";
            await HttpContext.Response.WriteAsync("error");
          }
        }
    
      }
    }


    九、调用小程序订阅消息,需要自己实现其他逻辑

    //@iFormSaveDAL.GetTran 数据库链接事务,如果发送消息失败,应该回滚提交的表单数据
    //@wFControl.NextAuditNotifyStaffIDStr 下一个审核用户的工号
    //@auditPageData.FormID 表单编号
    //@auditPageData.MainRecordID 表单数据ID
    //@operationByCode 一个枚举类型,前端传递的:审核通过、作废、退回等
    //@wFControl.ItemAuditStatus 一个枚举类型,如果全部审核完毕为1,否则为0
    //@wFControl.CurrentWorkflowName 当前流程的名称,例如:请假单审核
    //@SaveAfterInfo 全局字符变量,用于保存结果信息
    
    AuditSubscribeMessage auditMsg = new AuditSubscribeMessage(iFormSaveDAL.GetTran, wFControl.NextAuditNotifyStaffIDStr);
    auditMsg.SetPageUrl(auditPageData.FormID, auditPageData.MainRecordID);
    auditMsg.SetTemplateData(operationByCode, wFControl.ItemAuditStatus, wFControl.CurrentWorkflowName);
    if (!string.IsNullOrEmpty(auditMsg.Touser))
    {
      if (!MessageContext.SendSubscribeMsg(auditMsg, out messageStr))
      {
        SaveAfterInfo = messageStr;
        return false;
      }
    }

    有不懂或需要改正的欢迎留言!

  • 相关阅读:
    Tomcat的SessionID引起的Session Fixation和Session Hijacking问题
    别把项目成功当目标!——项目经理的误区(1)(转)
    技术,项目经理的命?——项目经理的误区(3)(转)
    项目管理中,最难管的是什么?(转)
    项目经理的超越(三)人际优先,做事上的超越(转)
    大丈夫不可一日无权啊!——项目经理的误区(2)(转)
    舌尖上的职场(二)一起去吃饭吧!(转)
    舌尖上的职场(一)你最近还好吗?(转)
    【项目经理之修炼(10)】《初级篇》人际关系问题也是可以避免的(转)
    gcc与g++区别
  • 原文地址:https://www.cnblogs.com/fanyang1/p/13720355.html
Copyright © 2011-2022 走看看