转自:http://blog.csdn.net/otangba/article/details/8273952

终于到了服务器端,第三篇的手机客户端如果已经下载了的话,没有服务器是不能正常运行的。

服务器端要做得事很多,虽然逻辑不是很复杂,但是我们必须要分析清楚我们要做哪些事,请看下图:

通过这张图,我们看出,服务器端的接口一共有6个,分别处理:

  1. 手机客户端登录
  2. 首页
  3. 二维码图片流
  4. long polling维持
  5. 接收手机客户端已扫描的通知
  6. 接收手机客户端已确认登录的通知
那么一个一个解决
 
首先是手机客户端登录,在上一篇我们介绍的手机客户端登录我们仅仅模拟一下,因此用户只需要提交一个用户名,服务器则通过SHA1对用户名加密,将密文返回作为token。为了将来验证这个密文是否OK,我们将用户名和密文保存在redis内供将来验证使。
需要引用的包:
  1. var http = require('http'), url = require('url'), fs = require('fs'), querystring = require('querystring'),qrcode = require('qrcode'), UUID = require('uuid-js'), sha1 = require('sha1'), redis = require('redis'), redisClient = redis
  2. .createClient('10087', '192.168.111.122'), redisKey = 'QRCODE_LOGIN_TOKEN';

redis 的客户端也一并创建了,并设置了key

web服务的基础结构如下:
  1. http.createServer(function(req, res) {
  2. // parse URL
  3. var url_parts = url.parse(req.url);
  4. var path = url_parts.pathname;
  5. var uuid4 = UUID.create();
  6. var _sessionID = uuid4.toString();
  7. if (path == '/') {
  8. //...
  9. } else if (path == '/poll') {
  10. // console.log('polling');
  11. } else if (path == '/qrcodeimage') {
  12. // 二维码的请求,参数为sessionID
  13. } else if (path == '/moblogin') {
  14. // 返回用户名对应的token,简单采用sha1加密
  15. } else if (path == '/scanned') {
  16. console.log('scanned');
  17. } else if (path == '/confirmed') {
  18. console.log('confirmed');
  19. } else {
  20. res.writeHead(200, {
  21. 'Content-Type' : 'text/html; charset=UTF-8'
  22. });
  23. res.end();
  24. }
  25. }).listen(9999, '192.168.111.109');
  26. console.log('服务器已运行在端口9999.');

通过分析,我们无非就是为这6个分支添加逻辑。

这次案例是一个试验,因此我们代码编写的也比较简单,如果使用类似express等框架的话,会更加方便一些。
 
先看看第一个接口,登录,返回sha1的token
  1. if (path == '/moblogin') {
  2. <span style="white-space:pre">      </span>// 返回用户名对应的token,简单采用sha1加密
  3. var userName = urlDecode(url_parts.query);
  4. var token = sha1(userName);
  5. // userHash.set(token, userName);
  6. // 保存token到redis
  7. redisClient.hset(redisKey, token, userName);
  8. res.writeHead(200, {
  9. 'Content-Type' : 'text/html; charset=UTF-8'
  10. });
  11. res.end(token);
  12. <span style="white-space:pre">  </span>}

下面是首页,如果用户敲击的url是一个不带参数的地址,事实上,用户初次访问肯定不带任何参数,而我们这个页面的目的是必须要有sessionID,因为首页内包含的2个子请求是必须具备sessionID参数的。因此我们要做url做一个分析和强制跳转:

  1. if (path == '/') {
  2. var sessionID = url_parts.query;
  3. if (typeof (sessionID) == "undefined" || sessionID == "") {
  4. // 访问首页没有参数,自动跳转
  5. res.writeHead(200, {
  6. 'Refresh' : '0; url=/?' + _sessionID,
  7. 'Content-Type' : 'text/html; charset=UTF-8'
  8. });
  9. res.end();
  10. } else {
  11. // 处理首页,刷新一条sessionID和二维码
  12. generateIndex(sessionID, req, res);
  13. }
  14. }

也就是说当直接访问/的时候,服务器强制将请求重定向并包含sessionID信息

  1. function generateIndex(sessionID, req, res) {
  2. fs.readFile('./index.html', 'UTF-8', function(err, data) {
  3. data = data.replace(/SESSIONID/g, sessionID);
  4. res.writeHead(200, {
  5. 'Content-Type' : 'text/html; charset=UTF-8'
  6. });
  7. res.end(data);
  8. });
  9. }

当访问的地址符合/?sessionID的时候,服务器读取一个html页面,并将其中的二维码和long polling需要的参数替换为sessionID

  1. <html>
  2. <head>
  3. <script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
  4. <script>
  5. var poll = function() {
  6. $.getJSON('/poll?SESSIONID', function(response) {
  7. var cmd = response.cmd;
  8. if (cmd == 'scanned') {
  9. scanned();
  10. } else if (cmd == 'pclogin') {
  11. var username=response.username;
  12. pclogin(username);
  13. }
  14. poll();
  15. });
  16. }
  17. var pclogin = function(username) {
  18. $('#output').text('欢迎您:' + username + ',您已成功登录');
  19. }
  20. var scanned = function() {
  21. $('#output').text('已成功扫描,等待手机确认登录');
  22. }
  23. poll();
  24. </script>
  25. </head>
  26. <body>
  27. <p align="center"><img src="/qrcodeimage?SESSIONID">
  28. </p>
  29. </body>
  30. </html>

二维码的请求在我们第二篇就已经介绍过,这里不再重复。

那么维持long polling的接口
  1. if (path == '/poll') {
  2. // console.log('polling');
  3. var sessionID = url_parts.query;
  4. var sessionObj = {
  5. 'sessionID' : sessionID,
  6. 'res' : res
  7. };
  8. clients.push(sessionObj);
  9. console.log('client added' + sessionObj);
  10. }

在处理接收客户端完成扫描和确认登录的时候,逻辑比较类似,都是先验证用户的token是否存在,商用的话可能还要有些更安全的考虑

然后根据sessionID找到维持long polling的客户端对象,并且返回相关的操作指令
  1. function handleScanned(res, token, sessionID) {
  2. // console.log(">>>" + token + "," + sessionID);
  3. var success = false;
  4. if (typeof (token) != "undefined") {
  5. // 验证是否包含用户信息已确认是登录的用户
  6. var userName;
  7. redisClient.hget(redisKey, token, function(err, reply) {
  8. userName = reply;
  9. // console.log("username=" + userName);
  10. if (typeof (userName) != "undefined") {
  11. // 用户存在
  12. for ( var int = 0; int < clients.length; int++) {
  13. var clientobj = clients[int];
  14. var savedSession = clientobj.sessionID;
  15. var client = clientobj.res;
  16. if (savedSession == sessionID) {
  17. // 页面存在
  18. client.end(JSON.stringify({
  19. cmd : 'scanned'
  20. }));
  21. clients.splice(int, 1);
  22. success = true;
  23. break;
  24. }
  25. }
  26. }
  27. res.writeHead(200, {
  28. 'Content-Type' : 'text/html; charset=UTF-8'
  29. });
  30. if (success) {
  31. res.end("scanned");
  32. } else {
  33. res.end("error");
  34. }
  35. });
  36. }
  37. }

至此,我们的完整的二维码扫描登录的流程就已经走完了。

放在服务器上运行一下,完全OK,如果想作为daemon的话可以使用forever包。
 
经过这几篇的介绍,我们不难发现其实这个效果的实现并不是很复杂,关键在于你要把整个逻辑理顺和想清楚。
同时由于这个案例涉及的技术也较多,技术不全面的话也很难形成完成的解决方案。
 
思考:
这个案例中还存在哪些问题
  1. 微信27秒是事出有因的,考虑到http请求有可能在客户端因为长时间无响应而被终止,因此27秒自动刷新long polling可以有效的防治连接断掉,而在我们这个案例里,并没有去实现这个功能。首先我觉得实现起来没有问题,不难,另外,这些点应该由你们自己去实现,我更加关注的是分析业务。
  2. 关于页面session的内涵,应该可以附加一些加密的信息,对于客户端只是传递这些信息,因此不涉及解密操作,而服务器端就可以验证sessionID的合法性,目前如果你访问/?的时候自己宿便敲sessionID也是可以的,服务器没有做任何验证和限制。
  3. 关于long polling客户端的response对象的维持和清理,在本例中我们直接采用了js的数组进行存储,因此每次都是遍历。如果商用,必然用采用哈希的方式来存储,同时可能还必须存储在数据库内。
  4. 本例只是在客户端确认登录之后在页面上显示确认登录,并没有跳转到某页面,但是实际应用的时候,可能会携带某个服务器生成的钥匙去redirect到某个url,只有目标地址确认这个钥匙是登录确认信息之后才会以某用户方式登录,这个还是希望大家能实现,逻辑很简单,只是本例略掉。
 
后续的文章,就不再就这个话题讨论了,我们会回到XMPP上,但是可能会把XMPP和我们这个案例做些结合。
例如客户端登录的不是web系统,而是XMPP
客户端确认登录以及提交扫描成功不是通过http,而是通过XMPP
web页面要实现BOSH连XMPP
这些话题我们会在后面的内容里不断收入的研究。

实现手机扫描二维码页面登录,类似web微信-第四篇,服务器端的更多相关文章

  1. 实现手机扫描二维码页面登录,类似web微信-第一篇,业务分析

    转自:http://www.cnblogs.com/fengyun99/p/3541249.html 关于XMPP组件的文章,先休息两天,好歹已经完整的写了一份. 这两天,先实现一套关于web微信扫描 ...

  2. 实现手机扫描二维码页面登录,类似web微信-第二篇,关于二维码的自动生成

    转自:http://www.cnblogs.com/fengyun99/p/3541251.html 接上一章,我们已经基本把业务逻辑分析清楚了 下面我们第一步,实现二维码的web动态生成. 页面的二 ...

  3. 实现手机扫描二维码页面登录,类似web微信-第三篇,手机客户端

    转自:http://www.cnblogs.com/fengyun99/p/3541254.html 上一篇,介绍了二维码生成的机制,紧接着,我们就要开发手机客户端来识别这个二维码. 二维码,实际上是 ...

  4. 手机扫描二维码下载APP,根据操作系统不同自动下载

    Android和IOS手机扫描二维码下载APP,根据OS不同,自动处理相应下载操作.IOS自动跳转至AppStore应用下载页,Android自动下载应用的apk包. <script type= ...

  5. Android扫描二维码 实现 登录网页

    工程代码:ScanQRcode.zip ------------------------------------------------------------------ 1. 扫描二维码登录的实现 ...

  6. uni-app开发经验分享十三:实现手机扫描二维码并跳转全过程

    最近使用 uni-app 开发 app ,需要实现一个调起手机摄像头扫描二维码功能,官网API文档给出了这样一个demo: // 允许从相机和相册扫码 uni.scanCode({ success: ...

  7. asp:手机扫描二维码跳转手机版

    如果想手机扫描用pc版网站生成的二维码跳转到对应的手机版的话,请在pc端的首页的<head></head>标签里面加入下面内容:   <script src=" ...

  8. 大众点评试水O2O新模式:实体店试穿,扫描二维码付款 现场取货

    在餐饮美食行业取得不错的成绩之后,大众点评将触角延伸到了线下的传统商铺,开始涉足线下商品的 O2O 团购.和传统的线上下单,线下消费的 O2O 模式不同.大众点评的 O2O 团购用户,可在店内试穿后通 ...

  9. 扫描二维码自动识别手机系统(Android/IOS)

    移动互联网发展迅速,各种APP的开发都会推出多个版本(多终端),比如:iPhone版.iPad版.Android版.有些APP还会考虑覆盖到多个国家(国际化),比如:中文版.英文版.日文版.韩文版等. ...

随机推荐

  1. Qt信号与槽应用实例一

    ..... connect(m_pGlobelWidget,signal(globeControlClick(object,object)),this,slot(globeControlClick(o ...

  2. PoE以太网远程供电

    每个以太网口向下挂设备提供的最大功率为 15.4W 通过3/5 类双绞线的信号线(1.3.2.6)同时传递数据和电流

  3. mysql 允许远程登陆

    参考:http://blog.chinaunix.net/uid-23215128-id-2951624.html 1.以root账户登录 2.grant all PRIVILEGES on disc ...

  4. SSI-Server Side Inclued

    SSI是指将内容发送到浏览器之前,可以使用“服务器端包含 (SSI)”指令将文本.图形或应用程序信息包含到网页中. IIS.Apache等主流web服务器都支持,cassini不支持.它并不经过asp ...

  5. arraylist与linkedlist的区别与性能测试

    /** *arraylist和linkedlist的适用场合. **/ import java.util.List; import java.util.ArrayList; import java.u ...

  6. Web Performance Test: 如果使用Plugin过滤Dependent Request

    前言 由于Visual Studio的Web Performance Test是基于XML脚本的,留给用户修改测试行为的自由度并不高.因此,Plugin机制就对于实现很多客户化的配置显得很重要. 问题 ...

  7. spring cache

    spring-ehcache.xml文件内容如下: <?xml version="1.0" encoding="UTF-8"?> <beans ...

  8. Struts2之Action

    Struts2之Action MVC模式中需要有一个控制器来负责浏览器与服务器之间的通信,实现用户与服务器的交互.在Struts2框架中实现这一功能的是Action,它是整个框架最核心的部分.Acti ...

  9. Hibernate <查询缓存>

    查询缓存: 定义:查询缓存它是基于二级缓存的,可以保存普通属性查询的结果,查询对象实体时,他会保存id作为键,查询结果作为值,下个对象访问时,可以直接查到 查询缓存查询实体对象时,显著的特点是,会执行 ...

  10. WebSocket实战之————Workerman服务器的安装启动

    安装php apt-get install php5-cli root@iZ23b64pe35Z:/home/www# php -v PHP 5.5.9-1ubuntu4.20 (cli) (buil ...