C#開發(fā)微信門戶及應(yīng)用(39)--使用微信JSSDK實現(xiàn)簽到的功能

隨著微信開逐步開放更多JSSDK的接口,我們可以利用自定義網(wǎng)頁的方式來調(diào)用更多微信的接口,實現(xiàn)我們更加豐富的界面功能和效果,例如我們可以在頁面中調(diào)用各種手機的硬件來獲取信息,如攝像頭拍照,GPS信息、掃描二維碼等等,本篇介紹如何利用這些JSSDK接口實現(xiàn)簽到的功能,其中簽到需要報送地理坐標和地址,調(diào)用攝像頭實時拍照,以及獲取當前用戶的相關(guān)信息等等。

1、JSSDK的說明

微信JS-SDK是微信公眾平臺面向網(wǎng)頁開發(fā)者提供的基于微信內(nèi)的網(wǎng)頁開發(fā)工具包。通過使用微信JS-SDK,網(wǎng)頁開發(fā)者可借助微信高效地使用拍照、選圖、語音、位置等手機系統(tǒng)的能力,同時可以直接使用微信分享、掃一掃、卡券、支付等微信特有的能力,為微信用戶提供更優(yōu)質(zhì)的網(wǎng)頁體驗。
目前JSSDK支持的接口分類包括下面幾類:基礎(chǔ)接口、分享接口、圖像接口、音頻接口、智能接口、設(shè)備信息、地理位置、搖一搖周邊、界面操作、微信掃一掃、微信小店、微信卡券、微信支付,隨著微信功能的全部整合,估計更多的接口會陸續(xù)開放出來。
在微信的后臺進入【開發(fā)者文檔】模塊,我們可以看到對應(yīng)的JSSDK的功能分類和介紹,如下所示。


從右側(cè)我們可以詳細看到各個接口的使用說明,基本上JSSDK的使用方法都類似,因此調(diào)試通過并掌握其中一兩個,其他的也就依葫蘆畫瓢,照著做就可以了。
1)JSSDK使用步驟
步驟一:綁定域名
先登錄微信公眾平臺進入“公眾號設(shè)置”的“功能設(shè)置”里填寫“JS接口安全域名”。如下所示,在公眾平臺進行設(shè)置。

備注:登錄后可在“開發(fā)者中心”查看對應(yīng)的接口權(quán)限。

步驟二:引入JS文件
在需要調(diào)用JS接口的頁面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.0.0.js
如需使用搖一搖周邊功能,請引入 http://res.wx.qq.com/open/js/jweixin-1.1.0.js
備注:支持使用 AMD/CMD 標準模塊加載方法加載
當然,我們一般編輯頁面,為了方便實現(xiàn)更多的效果,可能還會引入其他JS,如JQuery的類庫等等。還有,我們還可以基于WeUI的jquery-weui類庫,實現(xiàn)更加豐富的功能,如下是我們案例代碼里面的JS引用。

<script src="~/Content/wechat/jquery-weui/lib/jquery-2.1.4.js"></script>
<script src="~/Content/wechat/jquery-weui/js/jquery-weui.js"></script>
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.1.0.js"></script>

步驟三:通過config接口注入權(quán)限驗證配置
所有需要使用JS-SDK的頁面必須先注入配置信息,否則將無法調(diào)用(同一個url僅需調(diào)用一次,對于變化url的SPA的web app可在每次url變化時進行調(diào)用,目前Android微信客戶端不支持pushState的H5新特性,所以使用pushState來實現(xiàn)web app的頁面會導(dǎo)致簽名失敗,此問題會在Android6.2中修復(fù))。

wx.config({
    debug: true, // 開啟調(diào)試模式,調(diào)用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數(shù),可以在pc端打開,參數(shù)信息會通過log打出,僅在pc端時才會打印。
    appId: '', // 必填,公眾號的唯一標識
    timestamp: , // 必填,生成簽名的時間戳
    nonceStr: '', // 必填,生成簽名的隨機串
    signature: '',// 必填,簽名,見附錄1
    jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表見附錄2

});

以上的配置就是JSSDK的核心所在,里面需要配置好對應(yīng)的appid,還有timestamp,nonceStr這些都沒有特別之處,最值得注意的是signature的實現(xiàn)機制,這樣我們在后臺生成好對應(yīng)的值,賦給JS頁面就可以了,這樣也是最為安全的做法。

如下代碼是我們實際項目里面,在Asp.net的視圖頁面里面的HTML代碼,如下所示。

<script language="javascript">
var appid = '@ViewBag.appid';
var noncestr = '@ViewBag.noncestr';
var signature = '@ViewBag.signature';
var timestamp = '@ViewBag.timestamp';

    wx.config({
        debug: false,
        appId: appid, // 必填,公眾號的唯一標識
        timestamp: timestamp, // 必填,生成簽名的時間戳
        nonceStr: noncestr, // 必填,生成簽名的隨機串
        signature: signature, // 必填,簽名,見附錄1
        jsApiList: [
           'checkJsApi',
           'onMenuShareTimeline',
           'onMenuShareAppMessage',
           'onMenuShareQQ',
           'onMenuShareWeibo',
           'onMenuShareQZone',
           'hideMenuItems',
           'showMenuItems',
           'hideAllNonBaseMenuItem',
           'showAllNonBaseMenuItem',
           'translateVoice',
           'startRecord',
           'stopRecord',
           'onVoiceRecordEnd',
           'playVoice',
           'onVoicePlayEnd',
           'pauseVoice',
           'stopVoice',
           'uploadVoice',
           'downloadVoice',
           'chooseImage',
           'previewImage',
           'uploadImage',
           'downloadImage',
           'getNetworkType',
           'openLocation',
           'getLocation',
           'hideOptionMenu',
           'showOptionMenu',
           'closeWindow',
           'scanQRCode',
           'chooseWXPay',
           'openProductSpecificView',
           'addCard',
           'chooseCard',
           'openCard'
        ]
    });

步驟四:通過ready接口處理成功驗證

wx.ready(function(){
    // config信息驗證后會執(zhí)行ready方法,所有接口調(diào)用都必須在config接口獲得結(jié)果之后,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調(diào)用相關(guān)接口,
    //則須把相關(guān)接口放在ready函數(shù)中調(diào)用來確保正確執(zhí)行。對于用戶觸發(fā)時才調(diào)用的接口,則可以直接調(diào)用,不需要放在ready函數(shù)中。
});

這個ready的接口,也就是在頁面順利加載完畢后的處理內(nèi)容了,一般我們需要做很多操作,都是需要在頁面加載完畢后才能調(diào)用相關(guān)的對象進行賦值、處理等操作。

例如我們在頁面ready后,獲取對應(yīng)的GPS坐標等操作,可以用下面的JS代碼實現(xiàn)。

wx.ready(function () {
    wx.getLocation({
        type: 'wgs84', // 默認為wgs84的gps坐標,如果要返回直接給openLocation用的火星坐標,可傳入'gcj02'
        success: function (res) {
            var latitude = res.latitude; // 緯度,浮點數(shù),范圍為90 ~ -90
            var longitude = res.longitude; // 經(jīng)度,浮點數(shù),范圍為180 ~ -180。
            var speed = res.speed; // 速度,以米/每秒計
            var accuracy = res.accuracy; // 位置精度
            $("#lblLoacation").text(latitude + "," + longitude);
        }
    });
});

步驟五:通過error接口處理失敗驗證

wx.error(function(res){
    // config信息驗證失敗會執(zhí)行error函數(shù),如簽名過期導(dǎo)致驗證失敗,具體錯誤信息可以打開config的debug模式查看,
    // 也可以在返回的res參數(shù)中查看,對于SPA可以在這里更新簽名。
});

這個error接口也就是用來處理異常信息的,一般情況下可以在這里提示用戶出現(xiàn)的錯誤。

2)、簽名算法
簽名生成規(guī)則如下:參與簽名的字段包括noncestr(隨機字符串), 有效的jsapi_ticket, timestamp(時間戳), url(當前網(wǎng)頁的URL,不包含#及其后面部分) 。對所有待簽名參數(shù)按照字段名的ASCII 碼從小到大排序(字典序)后,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1。這里需要注意的是所有參數(shù)名均為小寫字符。對string1作sha1加密,字段名和字段值都采用原始值,不進行URL 轉(zhuǎn)義。
即signature=sha1(string1)。 示例:

noncestr=Wm3WZYTPz0wzccnW

jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg

timestamp=1414587457

url=http://mp.weixin.qq.com?params=value

步驟1. 對所有待簽名參數(shù)按照字段名的ASCII 碼從小到大排序(字典序)后,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1:

jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value

步驟2. 對string1進行sha1簽名,得到signature:

0f9de62fce790f9a083d5c99e95740ceb90c27ed

注意事項
1.簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。
2.簽名用的url必須是調(diào)用JS接口頁面的完整URL。
3.出于安全考慮,開發(fā)者必須在服務(wù)器端實現(xiàn)簽名的邏輯。
如出現(xiàn)invalid signature 等錯誤詳見附錄5常見錯誤及解決辦法。
以上就是JSSDK總體的使用流程,雖然看起來比較抽象,但是基本上也就是這些步驟了。

上面的過程是具體的參數(shù)處理邏輯,我們要對應(yīng)到C#代碼的簽名實現(xiàn),需要對幾個變量進行處理,下面是對應(yīng)的生成noncestr、timestamp、以及簽名等操作的代碼。

/// <summary>
/// 生成時間戳,標準北京時間,時區(qū)為東八區(qū),自1970年1月1日 0點0分0秒以來的秒數(shù)
/// </summary>
/// <returns>時間戳</returns>
private static string GetTimeStamp()
{
    TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
    return Convert.ToInt64(ts.TotalSeconds).ToString();
}

/// <summary>
/// 生成隨機串,隨機串包含字母或數(shù)字
/// </summary>
/// <returns>隨機串</returns>
private static string GetNonceStr()
{
    return Guid.NewGuid().ToString().Replace("-", "");
}

還有我們要實現(xiàn)JSSDK簽名的處理,必須先根據(jù)幾個變量,構(gòu)建好URL字符串,具體的處理過程,我們可以把它們逐一放在一個Hashtable里面,如下代碼所示。

/// <summary>
/// 獲取JSSDK所需要的參數(shù)信息,返回Hashtable結(jié)合
/// </summary>
/// <param name="appId">微信AppID</param>
/// <param name="jsTicket">根據(jù)Token獲取到的JSSDK ticket</param>
/// <param name="url">頁面URL</param>
/// <returns></returns>
public static Hashtable GetParameters(string appId, string jsTicket, string url)
{
    string timestamp = GetTimeStamp();
    string nonceStr = GetNonceStr();

    // 這里參數(shù)的順序要按照 key 值 ASCII 碼升序排序  
    string rawstring = "jsapi_ticket=" + jsTicket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url + "";

    string signature = GetSignature(rawstring);
    Hashtable signPackage = new Hashtable();
    signPackage.Add("appid", appId);
    signPackage.Add("noncestr", nonceStr);
    signPackage.Add("timestamp", timestamp);
    signPackage.Add("url", url);
    signPackage.Add("signature", signature);
    signPackage.Add("jsapi_ticket", jsTicket);
    signPackage.Add("rawstring", rawstring);

    return signPackage;
}

我們注意到URL參數(shù)的字符串組合:

string rawstring = "jsapi_ticket=" + jsTicket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url + "";

這里我們拼接好URL參數(shù)后,就需要使用簽名的規(guī)則來實現(xiàn)簽名的處理了,簽名的代碼如下所示,注釋代碼和上面代碼等同。

/// <summary>
/// 使用SHA1哈希加密算法生成簽名
/// </summary>
/// <param name="rawstring">待處理的字符串</param>
/// <returns></returns>
private static string GetSignature(string rawstring)
{
    return FormsAuthentication.HashPasswordForStoringInConfigFile(rawstring, "SHA1").ToLower();

    ////下面和上面代碼等價
    //SHA1 sha1 = new SHA1CryptoServiceProvider();
    //byte[] bytes_sha1_in = System.Text.UTF8Encoding.Default.GetBytes(rawstring);
    //byte[] bytes_sha1_out = sha1.ComputeHash(bytes_sha1_in);
    //string signature = BitConverter.ToString(bytes_sha1_out);
    //signature = signature.Replace("-", "").ToLower();
    //return signature;
}

這樣我們有了對應(yīng)的值后,我們就可以把它們的參數(shù)全部放在集合里面了供使用。

/// <summary>
/// 獲取用于JS-SDK的相關(guān)參數(shù)列表(該方法對accessToken和JSTicket都進行了指定時間的緩存處理,多次調(diào)用不會重復(fù)生成)
/// 集合里面包括jsapi_ticket、noncestr、timestamp、url、signature、appid、rawstring
/// </summary>
/// <param name="appid">應(yīng)用ID</param>
/// <param name="appSecret">開發(fā)者憑據(jù)</param>
/// <param name="url">頁面URL</param>
/// <returns></returns>
public Hashtable GetJSAPI_Parameters(string appid, string appSecret, string url)
{
    string accessToken = GetAccessToken(appid, appSecret);
    string jsTicket = GetJSAPI_Ticket(accessToken);

    return JSSDKHelper.GetParameters(appid, jsTicket, url);
}

下面我們通過具體的代碼案例來介紹使用的過程。

2、簽到功能的實現(xiàn)處理

其實簽到,都可以在微信公眾號和企業(yè)號實現(xiàn),微信的企業(yè)號可能實現(xiàn)更佳一些,不過他們使用JSSDK的接口操作是一樣的,我們可以拓展過去就可以了。這里介紹微信公眾號JSSDK實現(xiàn)簽到的功能處理。
簽到的功能,我們希望記錄用戶的GPS位置信息,還有就是利用拍照功能,拍一個照片同時上傳到服務(wù)器,這樣我們就可以實現(xiàn)整個業(yè)務(wù)效果了。
首先我們來設(shè)計簽到的界面,代碼及效果如下所示。



界面預(yù)覽效果如下所示:



我們來看看微信JSSDK里面對于【獲取地理位置接口】的說明:
wx.getLocation({
    type: 'wgs84', // 默認為wgs84的gps坐標,如果要返回直接給openLocation用的火星坐標,可傳入'gcj02'
    success: function (res) {
        var latitude = res.latitude; // 緯度,浮點數(shù),范圍為90 ~ -90
        var longitude = res.longitude; // 經(jīng)度,浮點數(shù),范圍為180 ~ -180。
        var speed = res.speed; // 速度,以米/每秒計
        var accuracy = res.accuracy; // 位置精度
    }
});

以及圖形接口里面【拍照或從手機相冊中選圖接口】的說明:

wx.chooseImage({
    count: 1, // 默認9
    sizeType: ['original', 'compressed'], // 可以指定是原圖還是壓縮圖,默認二者都有
    sourceType: ['album', 'camera'], // 可以指定來源是相冊還是相機,默認二者都有
    success: function (res) {
        var localIds = res.localIds; // 返回選定照片的本地ID列表,localId可以作為img標簽的src屬性顯示圖片
    }
});

上傳圖片到微信服務(wù)器接口如下所示。

wx.uploadImage({
    localId: '', // 需要上傳的圖片的本地ID,由chooseImage接口獲得
    isShowProgressTips: 1, // 默認為1,顯示進度提示
    success: function (res) {
        var serverId = res.serverId; // 返回圖片的服務(wù)器端ID
    }
});

備注:上傳圖片有效期3天,可用微信多媒體接口下載圖片到自己的服務(wù)器,此處獲得的 serverId 即 media_id。

根據(jù)這幾個接口,我們來對它們進行包裝,以實現(xiàn)我們的業(yè)務(wù)需求。根據(jù)我們的需要,我們對JSSDK接口進行了調(diào)用,如下所示。

<script language="javascript">
var appid = '@ViewBag.appid';
var noncestr = '@ViewBag.noncestr';
var signature = '@ViewBag.signature';
var timestamp = '@ViewBag.timestamp';

    wx.config({
        debug: false,
        appId: appid, // 必填,公眾號的唯一標識
        timestamp: timestamp, // 必填,生成簽名的時間戳
        nonceStr: noncestr, // 必填,生成簽名的隨機串
        signature: signature, // 必填,簽名,見附錄1
        jsApiList: [
           'checkJsApi',
           'chooseImage',
           'previewImage',
           'uploadImage',
           'downloadImage',
           'getNetworkType',
           'openLocation',
           'getLocation'
        ]
    });

    wx.ready(function () {
        wx.getLocation({
            type: 'wgs84', // 默認為wgs84的gps坐標,如果要返回直接給openLocation用的火星坐標,可傳入'gcj02'
            success: function (res) {
                var latitude = res.latitude; // 緯度,浮點數(shù),范圍為90 ~ -90
                var longitude = res.longitude; // 經(jīng)度,浮點數(shù),范圍為180 ~ -180。
                var speed = res.speed; // 速度,以米/每秒計
                var accuracy = res.accuracy; // 位置精度
                $("#lblLoacation").text(latitude + "," + longitude);

                //解析坐標地址
                var location = latitude + "," + longitude;
                $.ajax({
                    type: 'GET',
                    url: '/JSSDKTest/GetAddress?location=' + location,
                    //async: false, //同步
                    //dataType: 'json',
                    success: function (json) {
                        $("#lblAddress").text(json);
                    },
                    error: function (xhr, status, error) {
                        $.messager.alert("提示", "操作失敗" + xhr.responseText); //xhr.responseText
                    }
                });
            }
        });
        wx.getNetworkType({
            success: function (res) {
                var networkType = res.networkType; // 返回網(wǎng)絡(luò)類型2g,3g,4g,wifi
                $("#lblNetwork").text(networkType);
            }
        });
        
        chooseImage();
    });
</script>

其中的chooseImage()是我們在頁面開始的時候,讓用戶拍照的操作,具體JS代碼如下所示。

//拍照顯示
var localIds;
function chooseImage() {
    wx.chooseImage({
        count: 1, // 默認9
        sizeType: ['original', 'compressed'], // 可以指定是原圖還是壓縮圖,默認二者都有
        sourceType: ['camera'], // 可以指定來源是相冊還是相機,默認二者都有
        success: function (res) {
            localIds = res.localIds; // 返回選定照片的本地ID列表,localId可以作為img標簽的src屬性顯示圖片
            $("#imgUpload").attr("src", localIds);
        }
    });
}

但用戶使用攝像頭拍照后,就會返回一個res.localIds集合,因為我們拍照一個,那么可以把它直接賦值給圖片對象,讓它顯示當前拍照的圖片。

拍照完成,我們單擊【簽到】應(yīng)該把圖片和相關(guān)的坐標等信息上傳到服務(wù)器的,圖片首先是保存在微信服務(wù)器的,上傳圖片有效期3天,可用微信多媒體接口下載圖片到自己的服務(wù)器,此處獲得的 serverId 即 media_id。

為了實現(xiàn)我們自己的業(yè)務(wù)數(shù)據(jù),我們需要把圖片集相關(guān)信息存儲在自己的服務(wù)器,這樣才可以實現(xiàn)信息的保存,最后提示【簽到操作成功】,具體過程如下所示。

//上傳圖片
var serverId;
function upload() {
    wx.uploadImage({
        localId: localIds[0],
        success: function (res) {
            serverId = res.serverId;

            //提交數(shù)據(jù)到服務(wù)器

            //提示信息
            $.toast("簽到操作成功");
        },
        fail: function (res) {
            alert(JSON.stringify(res));
        }
    });
}

另外,我們?yōu)榱藢崿F(xiàn)單擊圖片控件,實現(xiàn)重新拍照的操作,以及簽到的事件處理,我們對控件的單擊處理進行了綁定,如下代碼所示。

document.querySelector('#imgUpload').onclick = function () {
    chooseImage();
};

$(document).on("click", "#btnSignIn", function () {
    if (localIds == undefined || localIds== null) {
        $.toast('請先拍照', "forbidden");
        return;
    }
    //調(diào)用上傳圖片獲得媒體ID
    upload();
});

如果對這個《C#開發(fā)微信門戶及應(yīng)用》系列感興趣,可以關(guān)注我的其他文章.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容