1. 程式人生 > 程式設計 >.NET CORE3.1實現微信小程式傳送訂閱訊息

.NET CORE3.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,int timeOut = 30,string> headers = null)
    {
      using HttpClient client = new HttpClient();
      client.Timeout = new TimeSpan(0,timeOut);

      if (headers != null)
      {
        foreach (var header in headers)
        client.DefaultRequestHeaders.Add(header.Key,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,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,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 nonce,string echostr)
    {
      bool result = await _context.CheckSignature(signature,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;
  }
}

到此這篇關於.NET CORE3.1實現微信小程式傳送訂閱訊息的文章就介紹到這了,更多相關.NET CORE 小程式傳送訂閱內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!