钉钉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) { ///其中"/Api/DDUser/GetUserWithCode"是MVC下的DDUserController,其他的语言可以采取相似的方式实现,也可用一般处理程序来实现,我们的目的就是传入code,然后换取用户信息(该信息中包含钉钉用户的id),具体的可以参见官方的前面或者前面的【钉钉开发系列】文章。为了避免同一用户每次扫描时都去换取用户信息,可以将用户信息暂存起来,比如放到localstorage中,这样登录之后只需要判断是否已存在,若已存在则直接跳过不再发起请求。配置微信的JSSDK /// 要请求的api列表,以['biz.chat.chooseConversationByCorpId','biz.chat.toConversation']的形式 /// dd.ready调用的函数 /// dd.error调用的函数 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)); } );
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还没有写入就先返回,以腾出资源给其他请求。
实际的效果如下图
欢迎打描左侧二维码打赏。
转载请注明出处。