首先来先看张流程图,了解下原理
后端web服务器: 80端口 php 负责生成二维码 生成uuid(这个很重要,后边会提到) 解析token等
web端: javascript 实现链接建立,信息发送
app端: javascript 实现登录确认(惭愧 ios学了好久就会加载个webview,网络编程更是一塌糊涂,只能拿webview代替原生了)
socket服务端:8888端口 node.js 负责管理socket链接 交换数据
首先是socket服务器
为什么决定用node来实现呢?其实刚开始准备用php的socket来实现,结果发现php socket网上几乎找不到什么资料,好不容易找到几个,一看日期,好家伙,2006年的,果断不敢用,随后发现了一个php的socket框架workerman,框架封装的很好,几乎不需要考虑套接字,协议也可以自己定义,但是我只想做一个demo,正巧最近被逼着搞node,于是就决定用node来做后端.
搭建socket服务器
node服务器的搭建非常简单,在我另一篇博文中有详细介绍,这里不多介绍,详见,http://blog.csdn.net/zhangsheng_1992/article/details/51322707 整个socket服务需要三个扩展:
socket.io 一看就是提供服务的
winston 这个是记录日志的,方便调试
express 这个是干啥的我也不知道,不过不装的话老报错,node二把刀水平,有知道的大神还望告知,
安装包很简单
首先建立个package.json文件,这个文件你可以理解成apache的httpd.conf
内容如下:
{ "name": "IoService", "version": "0.0.1", "description": "Socket.io Service", "dependencies": { "express": "^4.13.4", "socket.io": "^1.4.6", "winston": "^2.2.0" } }
npm install --save express npm install --save socket.io npm install --save winston
{ "port": 8888, "log_level_logger": "info" }
//加载配置文件 var fs = require('fs'); var file = __dirname + "/" + 'conf.json'; var config = JSON.parse(fs.readFileSync(file, 'utf8')); var port = config['port']; var loggerLevel = config['log_level_logger']; //初始化日志记录 var winston = require('winston'); var logger = new (winston.Logger)({ transports: [ new (winston.transports.Console)({level: loggerLevel, timestamp: true}), new (winston.transports.File)({ filename: 'access.log',json: false}) ] }) //加载socket.io模块 监听指定端口 var http= require('http'); var io = require('socket.io').listen(port); logger.info('service start'); //定义一个list存放uuid 每一个uuid对应一个socket id var UUIDMap = {}; io.sockets.on('connection', function (socket) { logger.info('connection:', socket.id); var UUID; //客户端进行uuid与socket.id绑定 socket.on('register', function(data){ var regUUID = data['uuid']; //如果存在 解决刷新二维码造成的uuid不一致问题 if (UUID != null) { delete UUIDMap[UUID]; } UUID = regUUID; UUIDMap[UUID] = socket.id; logger.info('save in UUIDMap', UUID); }); //手机端确认登陆 socket.on('confirm',function(data){ //提示web端 手机端扫码成功 并将token令牌返回 replayToDisplayer(data, 'result'); //同时提示手机端 验证成功 socket.emit('success', { result: 'success' }) logger.info('mobile comfrim submit'); }); //客户端断开链接 socket.on('disconnect', function () { logger.info('disconnect', socket.id); if (UUID != null) { logger.info('delete ', UUID); delete UUIDMap[UUID]; } }); }); /** * 向指定web端发送信息 * * @param {json} data 要返回的数据 * @param {string} event 要回调客户端的监听事件 * @returns {undefined} */ function replayToDisplayer(data, event) { var submitUUID = data['uuid']; var displayerSocket = findSocketByUUID(submitUUID); if (displayerSocket != null) { logger.info('find socket and emit back ', submitUUID); displayerSocket.emit(event, data); } } /** * 通过uuid查找socket connection id * * @param {uuid} data 要返回的数据 */ function findSocketByUUID(UUID) { var targetSocketID = UUIDMap[UUID]; if (targetSocketID != null) { var targetSocket = io.sockets.connected[targetSocketID]; if (targetSocket != null) { return targetSocket; } else { logger.info('cant find target socket by uuid: ', UUID); } } else { logger.error('cant find target socket id by uuid: ', UUID); } return null; }
第二步是建立后端服务器用于生成二维码,生成uuid,验证token
这个很简单 如何生成二维码可以用谷歌的api 也可以用php的Qrcode包,网上有,而且很简单,不啰嗦了,实现代码如下:
<?php include 'qrcode/phpqrcode.php'; //uuid 唯一的标示符 用于指定客户端收发信息 $uuid = 'abc123'; //生成二维码文件 $filename = 'qrcode'.time().mt_rand(1000,9999).'.png'; //二维码中包含的数据 $data = [ "ip"=>'127.0.0.1', "port"=>'8888', 'exprise'=>time()+60, 'uuid'=>$uuid ]; try{ QRcode::png('$data','temp/'.$filename,'L',15); echo json_encode(['code'=>1,'message'=>'temp/'.$filename,'uuid'=>$uuid]); }catch(\Exception $e) { echo json_encode(['code'=>0,'message'=>$e->getMessage()]); }
第三步编写web端脚本index.html如下:
<span style="font-size:12px;"><!Doctype html> <html> <head> <title>扫码登录demo</title> <meta charset="utf-8"></meta> </head> <body> <div style='margin:100px auto;width:80%;text-align:center'> <img src="" class="qrcode" style="display:none;margin:0 auto;"/><br /> <p></p><br /> <button class='button' onclick="getQrcode()" style='font-size:16px'>获取二维码登陆</button> </div> <script type="text/javascript" src="js/socket.io.js"></script> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript"> var timeLimit = 60; var uuid; /** * 获取二维码 */ function getQrcode(){ $.ajax({ type: 'POST', url: 'index.php', data: {}, dataType: 'json', success: function(data){ if(data.code === 1){ $('.qrcode').attr('src',data.message); $('.qrcode').show(); $('.button').attr('disabled',true); uuid = data.uuid; countDown(); init(data.uuid); console.log('生成二维码成功,正在建立链接...'); }else{ console.log(data.message); } } }); } /** * 刷新计时器 */ function countDown(){ var id = setInterval(function (){ var str = '二维码有效期剩余:'+timeLimit+'秒'; $('.button').html(str); if(timeLimit >0){ timeLimit--; }else{ timeLimit = 60; clearInterval(id); $('.button').html('获取二维码登陆'); $('.button').attr('disabled',false); } },1000); } /* * 初始化链接 */ function init(uuid) { var socket = io.connect('http://127.0.0.1:8888'); //向服务器发送uuid绑定socket.id socket.emit('register',{uuid:uuid}); //手机端扫码确认登陆后 socket.on('result',function(data){ //这里可以拿到data.token 拿到后就可以进行登陆了 console.log("登陆成功"+'token='+data.token); //后续操作 }); } </script> </body> </html></span>
socket.io为客户端socket的实现脚本,你可以在cdnjs上下载到,我将它下载到本地了
第四部分为app端的模拟代码如下
<span style="font-size:12px;"><!Doctype html> <html> <head> <title>模拟app扫码验证demo</title> <meta charset="utf-8"></meta> </head> <body> <div style='margin:100px auto;width:80%;text-align:center'> <p>注:现在模拟扫码的结果</p> <p>url:http://127.0.0.1</p> <p>port:8888</p> <p>uuid:abc123</p> <p>token:token</p> <div style='margin:0 auto;text-align:center' class="result"> <p class="notice">扫码验证完毕!</p> <button onclick="confrimYes()">确认登陆</button> </div> </div> <script type="text/javascript" src="js/socket.io.js"></script> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript"> //这部分应该是扫码解析出来的 现在只做模拟 var url = 'http://127.0.0.1'; var port = 8888; var uuid = 'abc123'; var token = 'token'; serviceUrl = url + ':' +port; var socket = io.connect(serviceUrl); socket.on('success',function(data){ console.log("登陆成功"+'message'+data.result); }); //确认登陆 function confrimYes(){ socket.emit('confirm',{uuid:uuid,token:token}); } </script> </body> </html></span>
1.开启服务node sokcet.io服务如下
2.通过浏览器访问index.html获取二维码 链接node service
service端收到请求 建立连接
3.通过浏览器访问app.html模拟手机端授权
然后点击确认登陆 控制台输出登陆成功
4.完成扫码登录web端跳转
我们来看一下日志可以得到整个流程
第一行 service start 表示服务启动
第二行 connection 是web端扫码后进行链接
第三行表示收到web端发送的uuid 并保存
第四行是app链接
第五行输出了app 确认登陆 登陆成功
后边是链接断开信息
以上就是扫码登录的思路及实现demo 实质上也可以通过轮询来实现 轮询前后端反而更容易编写写
可以参考我的另一篇博文
http://blog.csdn.net/zhangsheng_1992/article/details/51291497
所有代码可以在这里找到
https://code.csdn.net/zhangsheng_1992/socket/tree/master