H5+.Net Webapi整合微信分享前後端程式碼 微信JS-SDK wx.onMenuShareTimeline wx.onMenuShareAppMessage

eedc發表於2018-08-23

說明:

1/因為賺麻煩這裡沒有使用資料庫或伺服器快取來儲存access_token和jsapi_ticket,為了方便這裡使用了本地的xml進行持久化這兩個值以及這兩個值的建立時間和有限期限。

2/每次請求先檢查有沒有存在並且在有效期內的access_token和jsapi_ticket,存在的話直接進行加密操作,不存在或過期重新請求wechat介面獲得再進行加密。

3/每個分享的頁面都需要將當頁的url傳送到伺服器進行簽名,且一定要encodeURIComponent,因為在微信中開啟會自動給當前連結加個各種引數,從而導致url不一致,導致invalid signature簽名錯誤。

4/分享的圖示url( imgUrl )必須是絕對路徑。

 

一 封裝的微信授權工具類

WechatJsSdk.cs

using SouthRuiHeH5.Models;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web.Script.Serialization;
using System.Xml.Linq;

namespace SouthRuiHeH5.Provider
{
    public class WechatJsSdk
    {
        /// <summary>
        /// 模擬get請求獲取AccessToken
        /// </summary>
        /// <param name="appID"></param>
        /// <param name="appSecret"></param>
        /// <returns></returns>
        public static string GetAccessToken(string appID, string appSecret)
        {
            string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appID, appSecret);
            JavaScriptSerializer js = new JavaScriptSerializer();
            AccessTokenOutput output = js.Deserialize<AccessTokenOutput>(HttpGet(url));
            SaveAccessTokenInXml(output);
            return output.access_token;
        }

        /// <summary>
        /// 將AccessToken儲存進xml
        /// </summary>
        /// <param name="input"></param>
        private static void SaveAccessTokenInXml(AccessTokenOutput input)
        {
            if (string.IsNullOrWhiteSpace(input?.access_token)) return;
            var mappedPath = System.Web.Hosting.HostingEnvironment.MapPath("~/");
            string filePath = mappedPath + "Xml/AccessToken.xml";
            XDocument inputDoc = XDocument.Load(filePath);
            inputDoc.Elements().First().Element("access_token").Value = input.access_token;
            inputDoc.Elements().First().Element("expires_in").Value = input.expires_in;
            inputDoc.Elements().First().Element("create_time").Value = DateTime.Now.ToString();
            inputDoc.Save(filePath);
        }

        /// <summary>
        /// 模擬get請求獲取JsapiTicket
        /// </summary>
        /// <param name="accessToken"></param>
        /// <returns></returns>
        public static string GetJsapiTicket(string accessToken)
        {
            string url = string.Format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=jsapi", accessToken);
            JavaScriptSerializer js = new JavaScriptSerializer();
            JsapiTicketOutput output = js.Deserialize<JsapiTicketOutput>(HttpGet(url));
            SaveJsapiTicketInXml(output);
            return output.ticket;
        }

        /// <summary>
        /// 將JsapiTicket儲存進xml
        /// </summary>
        /// <param name="input"></param>
        private static void SaveJsapiTicketInXml(JsapiTicketOutput input)
        {
            if (string.IsNullOrWhiteSpace(input?.ticket)) return;
            var mappedPath = System.Web.Hosting.HostingEnvironment.MapPath("~/");
            string filePath = mappedPath + "Xml/JsapiTicket.xml";
            XDocument inputDoc = XDocument.Load(filePath);
            inputDoc.Elements().First().Element("ticket").Value = input.ticket;
            inputDoc.Elements().First().Element("expires_in").Value = input.expires_in;
            inputDoc.Elements().First().Element("create_time").Value = DateTime.Now.ToString();
            inputDoc.Save(filePath);
        }

        /// <summary>
        /// get模擬請求
        /// </summary>
        /// <param name="Url"></param>
        /// <param name="postDataStr"></param>
        /// <returns></returns>
        private static string HttpGet(string Url, string postDataStr = "")
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url + (postDataStr == "" ? "" : "?") + postDataStr);
            request.Method = "GET";
            request.ContentType = "text/html;charset=UTF-8";

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            Stream myResponseStream = response.GetResponseStream();
            StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8"));
            string retString = myStreamReader.ReadToEnd();
            myStreamReader.Close();
            myResponseStream.Close();
            return retString;
        }

        private static string[] strs = new string[]
                                {
                                  "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
                                  "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"
                                };

        /// <summary>
        /// 獲取隨機字串
        /// </summary>
        /// <returns></returns>
        public static string CreatenNonce_str()
        {
            Random r = new Random();
            var sb = new StringBuilder();
            var length = strs.Length;
            for (int i = 0; i < 15; i++)
            {
                sb.Append(strs[r.Next(length - 1)]);
            }
            return sb.ToString();
        }

        /// <summary>
        /// 獲取時間戳
        /// </summary>
        /// <returns></returns>
        public static long CreatenTimestamp()
        {
            return (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000;
        }

        /// <summary>
        /// sha1加密string1 獲得Signature
        /// </summary>
        /// <param name="jsapi_ticket"></param>
        /// <param name="noncestr"></param>
        /// <param name="timestamp"></param>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string GetSignature(string jsapi_ticket, string noncestr, long timestamp, string url)
        {
            string string1 = string.Format("jsapi_ticket={0}&noncestr={1}&timestamp={2}&url={3}", jsapi_ticket, noncestr, timestamp, url);
            return Sha1(string1);
        }

        /// <summary>
        /// sha1
        /// </summary>
        /// <param name="orgStr"></param>
        /// <param name="encode"></param>
        /// <returns></returns>
        private static string Sha1(string orgStr, string encode = "UTF-8")
        {
            var sha1 = new SHA1Managed();
            var sha1bytes = System.Text.Encoding.GetEncoding(encode).GetBytes(orgStr);
            byte[] resultHash = sha1.ComputeHash(sha1bytes);
            string sha1String = BitConverter.ToString(resultHash).ToLower();
            sha1String = sha1String.Replace("-", "");
            return sha1String;
        }

    }

}

 

二  webapi部分

ConfigController.cs

using SouthRuiHeH5.Models;
using SouthRuiHeH5.Provider;
using System;
using System.Configuration;
using System.Linq;
using System.Web.Http;
using System.Xml.Linq;

namespace SouthRuiHeH5.Controllers
{
    public class ConfigController : ApiController
    {
        public ConfigOutput Get([FromUri]string url)
        {
            string appId = ConfigurationManager.AppSettings.Get("AppId");
            string appSecret = ConfigurationManager.AppSettings.Get("AppSecret");

            if (string.IsNullOrWhiteSpace(appId)) throw new Exception("AppSeeting:AppId Missed");
            if (string.IsNullOrWhiteSpace(appSecret)) throw new Exception("AppSeeting:AppSecret Missed");


            string jsapiTicket = getJsapiTicketFromXml();
            if (string.IsNullOrEmpty(jsapiTicket))
            {
                string accessToken = GetAccessTokenFromXmlFirst();
                if (string.IsNullOrEmpty(accessToken)) accessToken = WechatJsSdk.GetAccessToken(appId, appSecret);
                if (string.IsNullOrEmpty(accessToken)) throw new Exception("Get AccessToken Error");
                jsapiTicket = WechatJsSdk.GetJsapiTicket(accessToken);
                if (string.IsNullOrEmpty(jsapiTicket)) throw new Exception("Get JsapiTicket Error");
            }

            ConfigOutput output = new ConfigOutput
            {
                appId = appId,
                nonceStr = WechatJsSdk.CreatenNonce_str(),
                timestamp = WechatJsSdk.CreatenTimestamp()
            };
            output.signature = WechatJsSdk.GetSignature(jsapiTicket, output.nonceStr, output.timestamp, url);
            return output;
        }

        /// <summary>
        /// 檢查xml是否有JsapiTicket,並且JsapiTicket在有效期內
        /// </summary>
        /// <returns></returns>
        private string getJsapiTicketFromXml()
        {
            var mappedPath = System.Web.Hosting.HostingEnvironment.MapPath("~/");
            string filePath = mappedPath + "Xml/JsapiTicket.xml";
            XDocument inputDoc = XDocument.Load(filePath);

            string ticket = inputDoc.Elements().First().Element("ticket").Value;
            bool expiresInTry = int.TryParse(inputDoc.Elements().First().Element("expires_in").Value, out int expiresIn);
            bool createTimeTry = DateTime.TryParse(inputDoc.Elements().First().Element("create_time").Value, out DateTime createTime);

            if (!string.IsNullOrWhiteSpace(ticket) || !expiresInTry || !createTimeTry)
            {
                TimeSpan timeSpan = DateTime.Now.Subtract(createTime);
                if (timeSpan.TotalSeconds < expiresIn)
                    return ticket;
            }
            return string.Empty;
        }

        /// <summary>
        /// 檢查xml是否有AccessToken,並且AccessToken在有效期內
        /// </summary>
        /// <returns></returns>
        private string GetAccessTokenFromXmlFirst()
        {
            var mappedPath = System.Web.Hosting.HostingEnvironment.MapPath("~/");
            string filePath = mappedPath + "Xml/AccessToken.xml";
            XDocument inputDoc = XDocument.Load(filePath);

            string accessToken = inputDoc.Elements().First().Element("access_token").Value;
            bool expiresInTry = int.TryParse(inputDoc.Elements().First().Element("expires_in").Value, out int expiresIn);
            bool createTimeTry = DateTime.TryParse(inputDoc.Elements().First().Element("create_time").Value, out DateTime createTime);

            if (!string.IsNullOrWhiteSpace(accessToken) || !expiresInTry || !createTimeTry)
            {
                TimeSpan timeSpan = DateTime.Now.Subtract(createTime);
                if (timeSpan.TotalSeconds < expiresIn)
                    return accessToken;
            }
            return string.Empty;

        }
    }
}

 

三 JS部分

wechat.share.js

var url = window.location.href.split('#')[0];

$.get("/api/Config?url=" + encodeURIComponent(url), function (res) {
    if (!res) return;
    var input = res;
    //input.debug = true;
    input.jsApiList = ["onMenuShareTimeline", "onMenuShareAppMessage"];
    wx.config(input);
});

wx.ready(function () {
    onMenuShareTimeline();
    onMenuShareAppMessage();
});

function onMenuShareTimeline() {
    wx.onMenuShareTimeline({
        title: '為態度喝彩!',
        desc: '唯有創造價值,才能共享價值。南方瑞合三年定開基金(LOF)盛大發行中。',
        link: url,
        imgUrl: 'http://southruihe.huiz.cn/image/sharelogo.jpg',
        success: function () { }
    });
}

function onMenuShareAppMessage() {
    wx.onMenuShareAppMessage({
        title: '為態度喝彩!',
        desc: '唯有創造價值,才能共享價值。南方瑞合三年定開基金(LOF)盛大發行中。',
        link: url,
        imgUrl: 'http://southruihe.huiz.cn/image/sharelogo.jpg',
        success: function () { }
    });
}

 

四 其中使用的3個資料傳輸類

AccessTokenOutput.cs

namespace SouthRuiHeH5.Models
{
    public class AccessTokenOutput
    {
        public string access_token { get; set; }
        public string expires_in { get; set; }
        public string errcode { get; set; }
        public string errmsg { get; set; }
    }
}

ConfigOutput.cs

namespace SouthRuiHeH5.Models
{
    public class ConfigOutput
    {
        /// <summary>
        /// 必填,公眾號的唯一標識
        /// </summary>
        public string appId { get; set; }
        /// <summary>
        /// 必填,生成簽名的時間戳
        /// </summary>
        public long timestamp { get; set; }
        /// <summary>
        /// 必填,生成簽名的隨機串
        /// </summary>
        public string nonceStr { get; set; }
        /// <summary>
        /// 必填,簽名
        /// </summary>
        public string signature { get; set; }
    }
}

JsapiTicketOutput.cs

namespace SouthRuiHeH5.Models
{
    public class JsapiTicketOutput
    {
        public string errcode { get; set; }
        public string errmsg { get; set; }
        public string ticket { get; set; }
        public string expires_in { get; set; }
    }
}

 

相關文章