zoukankan      html  css  js  c++  java
  • 钉钉开发系列(八)二维码扫描登录的实现

    钉钉PC版在登录的时候,需要使用手机版的来扫描,扫描之后会弹出一个确定的页面,确定之后PC端就会登录进去。基于此,我们也想实现相似的功能,为此我们需要先探讨其中的原理。

    查看钉钉PC版的二维码会发现是一个含有一个KEY的URL,钉钉扫描后会直接进入该URL的页面,在页面中确定之后,会向钉钉服务器发起一个请求,应该是写入了一串值,同时钉钉PC版检测到该值,匹配之后就登录成功了。下面是一个流程简图。


    由此,我们只需要实现同样的流程则可达到二维码登录的目的。

    1.建立服务器

    我们可以在数据库中建一个qrlogin的表来保存key,比如我们建这样的一个表


    其中qrl_guid就是登录的key,登录后的标识flag就是qrl_dingDingUserId(钉钉用户的ID)。

    2.生成二维码

    二维码的生成可以使用相关的库,比如.net的可以用thoughtworks.qrcode.dll,生成的字符串是htttp://www.xxx.com/qrlogin.html?key=生成的GUID,代码如下

     string enCodeString = "http://www.xxx.com/qrlogin.html?key=" + key;
                        QRCodeEncoder qrCodeEncoder = new QRCodeEncoder();
                        Bitmap qrBmp = qrCodeEncoder.Encode(enCodeString, Encoding.UTF8);
    其中key需要先将其插入到数据库表TQRLogin中。

    3.qrlogin.html页面

    在使用钉钉扫一扫进入到qrlogin.html页面后,我们不只需要得到url中的key,还需要得到该用户的信息,为此需要jsapi的票据,然后配置要调用的jsapi,比如dd.runtime.permission.requestAuthCode,再通过该函数得到用户的信息,示例代码如下

    var g={};
    g.configJsSdk=function (jsApiList, fnReady, fnError) {
                /// <summary>配置微信的JSSDK</summary>
                /// <param name="jsApiList" type="function">要请求的api列表,以['biz.chat.chooseConversationByCorpId','biz.chat.toConversation']的形式</param>
                /// <param name="fnReady" type="function">dd.ready调用的函数</param>
                /// <param name="fnError" type="function">dd.error调用的函数</param>  
                var url = window.location.href;
                $.getJSON("/Api/DDAuth/GetSignPackage", "url=" + window.location.href, function (response, textStatus, jqXHR) {
                    try {
                        if (response.Status != global.StatusCode.OK) {
                            alert('GetSignPackage error.' + response.Message);
                            return;
                        }
                        var signPackage = response.Data;
                        global.corpId = signPackage.corpId;
                        dd.config(
                               {
                                   agentId: signPackage.agentId,
                                   corpId: signPackage.corpId,
                                   timeStamp: signPackage.timeStamp,
                                   nonceStr: signPackage.nonceStr,
                                   signature: signPackage.signature,
                                   jsApiList: jsApiList
                               });
                        dd.ready(function () {
                            if (fnReady != null) {
                                fnReady();
                            }
                        });
    
                        dd.error(function (err) {
                            if (err == null) {
                                alert('dd error: ' + JSON.stringify(err));
                            }
                            else {
                                fnError();
                            }
                        });
                    }
                    catch (e) {
                        alert("getJSON error:" + JSON.stringify(e))
                    }
                });
            }
    
    g.configJsSdk(
                ['dd.runtime.permission.requestAuthCode'],
                function () {
                    dd.runtime.permission.requestAuthCode({
                        corpId: g.corpId,
                        onSuccess: function (result) {
                            result = eval(result);
                            $.getJSON("/Api/DDUser/GetUserWithCode", "code=" + result.code, function (response, textStatus, jqXHR) {
                                try {
    
                                    if (response.Status != g.StatusCode.OK) {
                                        alert("GetUserWithCode:" + response.Message);
                                        return;
                                    }
                                    var userInfo = {
                                        userid: response.Data.userid,
                                        name: response.Data.name
                                    };
                                    localStorage.userInfo = JSON.stringify(userInfo);
                                } catch (e) {
                                    alert(JSON.stringify(e));
                                }
                            });
                        },
                        onFail: function (err) {
                            alert('error' + JSON.stringify(err));
                        }
                    })
                },
                function (err) {
                    alert('error' + JSON.stringify(err));
                }
            );
    其中"/Api/DDUser/GetUserWithCode"是MVC下的DDUserController,其他的语言可以采取相似的方式实现,也可用一般处理程序来实现,我们的目的就是传入code,然后换取用户信息(该信息中包含钉钉用户的id),具体的可以参见官方的前面或者前面的【钉钉开发系列】文章。为了避免同一用户每次扫描时都去换取用户信息,可以将用户信息暂存起来,比如放到localstorage中,这样登录之后只需要判断是否已存在,若已存在则直接跳过不再发起请求。

    4.qrlogin.html页面中确定并写入flag

    在qrlogin.html页面中放一个确定按钮,点击后发起ajax请求,将key和钉钉用户的id一起传到服务器中,示例代码如下

     $('#btn_qrcode_login_ok').live('click', function () {
                try {
                    var userInfo = g.userInfo();
                    var key = $.getUrlParam('KEY');
                    g.toast.show('登录中...');
                    try {
                        $.getJSON("api/DDQRLogin/Confirm", 'key=' + key + '&dingDingUserId=' + userInfo.userid, function (response, textStatus, jqXHR) {
                            if (response.Status != g.StatusCode.OK) {
                                g.toast.hide();
                                alert('api/DDQRLogin/Confirm error.' + response.Message);
                                return;
                            }
                            var param = {};
                            param.title = '登录成功';
                            $('#btn_qrcode_login_ok').hide();
                            g.msg(param)
                            g.toast.hide();
                        });
                    } catch (e) {
                        alert("getjson exception:" + JSON.stringify(e.message));
                        g.toast.hide();
                    }
                } catch (e) {
                    alert(e.message);
                }
            });
    在后端收到key和dingDingUserId后,依据key将dingDingUserId写入到数据库。

    5.二维码端以key向服务器查询flag

    二维码端内部可以开起一个线程(thread或者task)向服务端查询是否已经写入了dingDingUserId,如果写入则说明已经扫描登录,这样UI就可以进行相应的跳转。
    为了持续的查询dingDingUserId,一般有两种方式,第一种就是二维码端每隔一段时间就查询一次,也就是轮询。第二种就是二维码端向服务器查询后,服务器不立即返回,而是等待dingDingUserId写入到数据库中(这里可以采用以较小间隔查询数据库的方式来实现),一旦写入就立即返回给二维码端。
    这两种方式各有利弊,
    第一种,由于每次都是由二维码端发起请求,所以请求次数会明显增多,而且由于是查了之后再返回,所以响应会相对慢一些,但由于是请求就等待再请求,所以会相对的减小服务器的压力。
    第二种,由于服务器端需要挂起等待dingDingUserId的写入,所以会一直占用服务器的资源,但拿到数据后会立即返回结果,响应会快一些。
    具体应用也可考虑两者相结合的方式,比如二维码端隔一段合适的时间就发起请求,服务端挂起一段合适的时间,如果dingDingUserId还没有写入就先返回,以腾出资源给其他请求。
    实际的效果如下图

    欢迎打描左侧二维码打赏。

    转载请注明出处。


  • 相关阅读:
    方法重载与方法重写的概念和区别(转载)
    sql exist 和not exist(转载)
    SET ANSI_NULLS ON SET QUOTED_IDENTIFIER ON 什么意思 sql server 2005 2008
    sql中的isnull
    sql中unique和distinct
    SQLServer中的Merge使用
    SQL Server系统表sysobjects介绍与使用
    sql select as
    存储过程SET XACT_ABORT ON
    SQL Server之存储过程基础知识
  • 原文地址:https://www.cnblogs.com/sparkleDai/p/7604923.html
Copyright © 2011-2022 走看看