天天看點

php/socket.io實作掃碼登入

首先先給大家道個歉,由于上次寫東西不認真,自己也沒有測試,不僅沒能幫到大家,還害的不少人走了彎路,是以原文章代碼我删掉了

掃碼登陸的原理  我上面的圖上已經說的很清楚  實際上就是手機端的token傳遞到web端的過程  關于token我不多說,這個大家應該都懂

修改過後的代碼可以在下面的連結中下載下傳到 代碼注釋非常清楚  可以直接運作 請先看reedme!

首先是編寫服務端

需要的node moudle如下

winston      日志子產品   可以用log4替代    做日志記錄用 需安裝    npm installwinston  下同

express      web伺服器兼架構  主要利用裡面一些現成的東西

socket.io    長連結的服務子產品

request       網絡請求  如果你的node server需要與後端web server進行通信 需要這個  選裝

定義日志記錄

var winston = require('winston');
var logger = new (winston.Logger)({
    transports: [
        new (winston.transports.Console)({level: "info", timestamp: true}),//設定日志級别
        new (winston.transports.File)({ filename: 'access.log',json: false})//設定日志記錄的檔案及各式
    ]
});
           

編寫http服務 

由于手機端沒有必要做長連結 一般是以短連結的形式 當然你使用長連結也可以  可以省掉這一段

var http = require('http');
//加載url子產品 解析get參數 
var url = require('url');
//加載query子產品 解析post參數
var query = require("querystring"); 

//開啟http服務
var server = http.createServer(function (request,response) {
    //擷取url通路參數
    var pathUrl = url.parse(request.url).pathname;
    //我這裡不實用rount子產品來實作路由 直接使用switch來做一個簡單的路由
    switch (pathUrl){

        //手機端連結成功
        case '/connection':
            var postdata = '';
            //當監測到post資料後 将參數追加到postdata中 如果資料量大 這裡可以使用buffer
            request.on("data",function(postchunk){
                postdata += postchunk;
            })

            //接受完post資料後
            request.on("end",function(){
                var data = query.parse(postdata.toString('utf-8'));
                if(!isNull(data.uuid)){
                    errorHead(response,'沒傳遞uuid參數');
                    response.end();
                }
                replayToDisplayer(data.uuid,{"status":0,"message":"手機端已連結成功"},'/appconnect')
                successHead(response,'連結成功');
            })
            break;


        //手機端确認登陸
        case '/confrim':
            var postdata = '';
            request.on("data",function(postchunk){
                postdata += postchunk;
            })
            request.on("end",function(){
                var data = query.parse(postdata.toString('utf-8'));
                if(!isNull(data.token)){
                    errorHead(response,'沒傳遞token參數');
                    response.end();
                }

                if(!isNull(data.uuid)){
                    errorHead(response,'沒傳遞uuid參數');
                    response.end();
                }

                replayToDisplayer(data.uuid,{"status":0,"data":{"token":data.token},"message":"手機端已确認登陸"},'/appconfirm')
                successHead(response,"手機端已确定登陸",data.token);
            })
            break;

        default:
            errorHead(response);
            break;
    }
}).listen(8889, "127.0.0.1");

//http服務狀态報告
server.once('listening', function() {  
    logger.info('tcp服務開啟 監聽端口 8889');  
});
           

然後是socket的服務

//定義一個list存放uuid與socket.id的對應關系
//同一時間會有多個用戶端連結在伺服器上  是以要知道手機端到底要将token給哪一個用戶端 
//當socket連結建立成功後 每一個連結都有一個獨一無二的socket.id  服務端會根據socket.id來決定要給哪個用戶端發送消息
//而用戶端連結的時候會提供一個唯一參數uuid 
//而手機端掃碼完成後 就可以解析道這個參數 進而得知到底要響應那一個用戶端
//我們要做的就是将uuid與socket.id進行綁定 這裡比較難懂一點 多看幾遍  
//可以了解成坐飛機 首先你得有張飛機票(socket.id) 而能上飛機的人都有飛機票  但你總得知道你坐在哪裡
//這時候你的座位号就有用了(uuid) 通過你的座位号你才知道 你究竟做哪 而根據座位号 可以反着計算出你的票是那張  這裡要實作的就是票和座位号的綁定關系

<span style="color:#FF0000;">var UUIDMap = {}</span>;//後續請主要關注這個集合的變化

/**
 * 開啟socket.io服務 
 */
var request = require('request');
var io = require('socket.io').listen(8888);
logger.info('socket服務開啟,監聽端口:', 8888);

/**
 * socket.io事件  連接配接成功
 * @param {string} event名稱
 * @param {function} 連接配接成功的回調函數 
 */
io.sockets.on('connection', function (socket) {

	logger.info('web端連結成功,socket_id為:', socket.id);
    var UUID;

    //用戶端進行uuid與socket.id綁定  
    socket.on('/register', function(data){  
        UUID = data['uuid']; 
        UUIDMap[UUID] = socket.id;  
        logger.info('web端注冊,uuid為', UUID);  
    }); 

    //用戶端斷開連結  
    socket.on('/disconnect', function () {  
        if (UUID != null) {  
            logger.info('用戶端斷開連結,從連接配接池中删除', "uuid 為"+UUID+",socket.id為"+socket.id);  
            delete UUIDMap[UUID];  
        }  
    }); 
});
           

公共函數

/**
 * http請求成功應答 
 */
function successHead(response,notice,token){
    response.writeHead(200,{"Content-Type":"text/plain","Content-Type":"text/html; charset=utf-8"});
    var message = {"status":"0","data": isNull(token) ? token : "","message" :isNull(notice) ? notice : "請求成功"};
    response.write(JSON.stringify(message));
    response.end();
}

/**
 * http請求失敗應答 
 */
function errorHead(response,notice){
    response.writeHead(200,{"Content-Type":"text/plain","Content-Type":"text/html; charset=utf-8"});
    var message = {"status":"1","data":"","message":  isNull(notice) ? notice : "請求位址不存在"};
    response.write(JSON.stringify(message));
    response.end();
}

/**
 * 向指定web端發送資訊
 * 
 * @param {json} data 要傳回的資料
 * @param {string} event 要回調用戶端的監聽事件
 * @returns {undefined}
 */
function replayToDisplayer(uuid, data, event) {
    var submitUUID = uuid;
    var displayerSocket = findSocketByUUID(submitUUID);

    if (displayerSocket != null) {
        logger.info('根據uuid:'+uuid+"找到socket.id:");
        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('不能根據uuid找到socketid,uuid為', UUID);
        }
    }
    return null;
}

/** 
 * 判斷是否null 
 * @param {string} data
 * @return bool 
 */
function isNull(data){ 
    return (data == "" || data == undefined || data == null) ? false : true; 
}
           

php後端

php後端主要用來生成二維碼和校驗token  校驗部分請根據自身業務編寫  在node server中使用request子產品做一個網絡請求發送到php端來驗證token

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)

web端主要加載二維碼  然後即等待伺服器響應 代碼如下

<!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' οnclick="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: '../php/index.php',
            data: {},
            dataType: 'json',
            success: function(data){
                if(data.code === 1){
                    $('.qrcode').attr('src','../php/'+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}); 
        console.log("連結成功");


        //手機端掃碼成功
        socket.on('/appconnect',function(data){  
            console.log(data);  
            //後續操作   頁面顯示掃碼成功啦等等
        });


        //手機端确認登陸
        socket.on('/appconfirm',function(data){  
            //實際上就是要手機的token 掃碼登陸實際上就是把手機的token傳遞到web端上
            console.log(data);  
            //後續操作  比如跳轉頁面
        });
    } 

</script> 
</body>  
</html>
           

手機端(ios swift)

掃碼解析

import UIKit
import AVFoundation

class WKQrCodeViewController: UIViewController,AVCaptureMetadataOutputObjectsDelegate {
    
    fileprivate let sWidth = UIScreen.main.bounds.size.width
    fileprivate let sHeight = UIScreen.main.bounds.size.height
    fileprivate let maskViewColor = UIColor.black
    fileprivate let maskViewAlpha : CGFloat = 0.3
    
    deinit {
        print("二維碼界面被銷毀了")
    }
    
    
    
    //比例
    let scaleWidth : CGFloat = 0.6
    var session:AVCaptureSession?
    var lineView:UIImageView? = UIImageView.init(imageName: "qrscan_line")
    var timer = Timer()
    
    fileprivate var isSent: Bool = false
    
    override func viewWillAppear(_ animated: Bool) {
        
        //即将進入時對狀态條進行隐藏
        UIApplication.shared.setStatusBarHidden(true, with: UIStatusBarAnimation.none)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        self.timer.invalidate()
    }
  
    override func viewDidLoad() {
        
        super.viewDidLoad()
 
        //二維碼框上的動畫計時器
        self.timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(configLine), userInfo: nil, repeats: true)
        //擷取攝像裝置,注意是Video而不是Audio
        let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
        //初始化AV Session來協調和處理AV的輸入和輸出流
        let session = AVCaptureSession()
        
        //建立輸入流
        let input:AVCaptureDeviceInput? = try! AVCaptureDeviceInput(device: device)
        
        if session.canAddInput(input){
            session.addInput(input)
        }
        
        //建立輸出流
        let output:AVCaptureMetadataOutput = AVCaptureMetadataOutput()
        if session.canAddOutput(output){
            session.addOutput(output)
            //設定輸出流代理,從接收端收到的所有中繼資料都會被傳送到delegate方法,所有delegate方法均在queue中執行
            output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            //設定中繼資料的類型,這裡是二維碼QRCode
            output.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
            
//            //固定寬度
//            let gdWidth : CGFloat = 200
//            let scaleWidth : CGFloat = 200 / sWidth
            
            //比例
//            let scaleWidth : CGFloat = 0.6
            /*!
             這個是手機橫着的時候的 x,y,w,h
             */
            
            output.rectOfInterest = CGRect(x : (1 - scaleWidth * sHeight / sWidth) / 2, y : (1 - scaleWidth) / 2,width : scaleWidth * sHeight / sWidth, height :  scaleWidth)
            print(output.rectOfInterest)
        }
        
        //建立視訊裝置拍攝視訊區域
        let layer:AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer.init(session: session)
        
        layer.videoGravity = AVLayerVideoGravityResizeAspectFill
        
        layer.frame = CGRect(x : 0, y : 0, width : UIScreen.main.bounds.size.height,height : UIScreen.main.bounds.size.width);
        
        self.view.layer.addSublayer(layer)
    
        //上
        let topView = UIView()
        print((sHeight - sWidth * scaleWidth) / 2)
        print(sHeight)//320
        print(sWidth)//640
        topView.frame = CGRect(x:0, y:0, width:sHeight, height:(sWidth -  sHeight * scaleWidth) / 2)
        topView.backgroundColor = maskViewColor
        topView.alpha = maskViewAlpha
        
        //下
        let downView = UIView()
        downView.frame = CGRect(x:0, y:sWidth - topView.frame.size.height, width:topView.frame.size.width, height:topView.frame.size.height)
        downView.backgroundColor = maskViewColor
        downView.alpha = maskViewAlpha
        
        //左
        let leftView = UIView()
        leftView.frame = CGRect(x:0,y:topView.frame.size.height,width:(sHeight - (sWidth - 2*topView.frame.size.height)) / 2, height:sWidth - 2*topView.frame.size.height)
        leftView.backgroundColor = maskViewColor
        leftView.alpha = maskViewAlpha

        //右
        let rightView = UIView()
        rightView.frame = CGRect(x:sWidth - 2*topView.frame.size.height + leftView.frame.size.width, y:topView.frame.size.height, width:(sHeight - (sWidth - 2*topView.frame.size.height)) / 2, height:sWidth - 2*topView.frame.size.height)
        rightView.backgroundColor = maskViewColor
        rightView.alpha = maskViewAlpha
        
        //溫馨提示(上)
        var tmpview = UIView()
        tmpview = tmpview.configOnPrompt(center: rightView.center)
        view.addSubview(tmpview)
        //溫馨提示(下)
        var lab = UILabel()
        lab = lab.configDownPrompt(frame: leftView.frame)
        view.addSubview(lab)
        
        self.view.layer.addSublayer(topView.layer)
        self.view.layer.addSublayer(downView.layer)
        self.view.layer.addSublayer(leftView.layer)
        self.view.layer.addSublayer(rightView.layer)
  
        //線
        configLine()
        
        //框
        configborder()
        
        //取消
        configBack()
        
        //開始采集視訊資料
        session.startRunning()
 
    }
    
    func configBack() -> Void {
        
        let backButton = UIButton()
        backButton.setTitle("取消", for: .normal)
        backButton.sizeToFit()
        backButton.frame = CGRect(x:sHeight - 37.5, y:15, width:40, height:20)
        backButton.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2));
        
        backButton.backgroundColor = UIColor.clear
        backButton.addTarget(self, action: #selector(backEvent), for: UIControlEvents.touchUpInside)

        view.addSubview(backButton)
 
    }
    
    func backEvent() -> Void {
   
        print("二維碼界面的傳回被點選")
        guard (self.presentingViewController? .isKind(of: WKQrConfirmViewController.classForCoder()))! else {
            
            self.presentingViewController?.dismiss(animated: true, completion: nil)
            return
        }
        
        self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
     
    }
    
     //線(存在問題是圖檔不能夠放在UIImageView上)
    func configLine() -> Void {
        
        /*
         imageView.contentScaleFactor = [[UIScreen mainScreen] scale];
         5
         imageView.contentMode = UIViewContentModeScaleAspectFill;
         6
         imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
         7
         imageView.clipsToBounds = YES;
         */
//        lineView?.contentScaleFactor = UIScreen.main.scale
//        lineView?.autoresizingMask = .flexibleHeight
//        lineView?.contentMode = .scaleAspectFill
        

        lineView!.frame = CGRect(x: (sHeight - (sWidth - 2*(sWidth -  sHeight * scaleWidth) / 2)) / 2 + self.sWidth - 2*(self.sWidth - self.sHeight * self.scaleWidth) / 2,y:  (sWidth -  sHeight * scaleWidth) / 2, width: 2, height: (sWidth -  sHeight * scaleWidth) / 2 + 2)

        UIView.animate(withDuration: 2) {
            
            self.lineView!.frame = CGRect(x: (self.sHeight - (self.sWidth - 2*(self.sWidth -  self.sHeight * self.scaleWidth) / 2)) / 2,y: (self.sWidth -  self.sHeight * self.scaleWidth) / 2,width: 2, height: (self.sWidth -  self.sHeight * self.scaleWidth) / 2 + 2)
            self.view.addSubview(self.lineView!)
            
            
        }
        
    }

    func configborder() -> Void {
        
        let qrCodeFrameView = UIImageView(image: UIImage(named: "qrscan_frame"))
        qrCodeFrameView.frame = CGRect(x:(sHeight - (sWidth - (sWidth -  sHeight * scaleWidth))) / 2, y:(sWidth -  sHeight * scaleWidth) / 2, width:sWidth - (sWidth -  sHeight * scaleWidth), height:sWidth - (sWidth -  sHeight * scaleWidth))
        view.addSubview(qrCodeFrameView)
        self.view.layer.addSublayer(qrCodeFrameView.layer);
    }
    
    //實作AVCaptureMetadataOutputObjectsDelegate的成員方法來處理二維碼資訊
    @objc(captureOutput:didOutputMetadataObjects:fromConnection:) func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from: AVCaptureConnection!) {
        session?.stopRunning()
    
        //擷取二維碼資訊中繼資料
        guard let metadataObject = metadataObjects.first else {
            return
        }
        
        //讓掃描隻執行一次
        if isSent == true {
            return
        }
        isSent = true
        
        let readableObject = metadataObject as! AVMetadataMachineReadableCodeObject
        
        //添加震動
        AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
    
// MARK: - 拿到加密串base64 解碼 && 反序列化
        let decodedData = NSData(base64Encoded
            : readableObject.stringValue!, options:.ignoreUnknownCharacters )
        
        let decodedString = String(data: decodedData! as Data, encoding: String.Encoding.utf8)
        
        let UTF8Data = decodedString?.data(using: String.Encoding.utf8)

        let oResult = try! JSONSerialization.jsonObject(with: UTF8Data!, options: [JSONSerialization.ReadingOptions.mutableContainers, JSONSerialization.ReadingOptions.mutableLeaves])
        print(oResult)
        guard let result = oResult as? [String: AnyObject] else {
            print("result沒解析出來!!")
            return
        }

        let host = result["host"] as! String
        let port = String(describing: result["port"])
        let uuid = result["uuid"] as! String
        
        let viewModel = WKQrCodeViewModel()
        
        viewModel.qrCodeUpData(host: host, port : port, UUID: uuid, success: {
            
                let userToken = UserAccountViewModel.sharedUserAccount
                let para = ["uuid" : uuid, "token" : userToken.accessToken!] as [String : Any]
                print(para)
            

                viewModel.qrCodeUpDataAgain(para: para as! Dictionary<String, String>, success: {
              
                    //第二次網絡請求成功,觸發去除二維碼界面,傳回跳進重新整理界面
                    //完成跳轉後對二維碼界面進行銷毀
                    self.dismiss(animated: false, completion: nil)
                    let confirmVC = WKQrConfirmViewController()
                    
                    self.presentingViewController?.present(confirmVC, animated: true, completion: {
                      
                    })
                    
                }, failure: { (errMsg) in
                    print("列印第二次失敗資訊:\(errMsg)")
                    
                    let alert = UIAlertController(title: "溫馨提示", message: "伺服器故障,請取消掃碼", preferredStyle: .alert)
                    alert.addAction(UIAlertAction(title: "知道了", style: .default){(action)->() in
                        
                        alert.view.isHidden = true
                    })
                    
                    alert.view.isHidden = true
                    self.present(alert, animated: true, completion: {() -> Void in
                        
                        alert.view.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2))
                        alert.view.isHidden = false
                    })
                })

            
            }) { (errMsg) in
                print("列印失敗資訊\(errMsg)");

                let alert = UIAlertController(title: "溫馨提示", message: "伺服器故障,請取消掃碼", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "知道了", style: .default){(action)->() in
                    
                    alert.view.isHidden = true
                })
                
                alert.view.isHidden = true
                self.present(alert, animated: true, completion: {() -> Void in
                    
                    alert.view.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2))
                    alert.view.isHidden = false
                })
        }
    }
    
    override var shouldAutorotate : Bool {
        return false
    }

    override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
        return UIInterfaceOrientationMask.portrait
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
  
}

//MARK: 二維碼界面提示
extension UIView {
    
    func configOnPrompt(center:CGPoint) -> UIView {
        
        let promptLab1 = UILabel()
        let promptLab2 = UILabel()
        let backboard = UIView()
        
        backboard.backgroundColor = UIColor.clear
        backboard.bounds = CGRect(x: 0,y: 0,width: 300,height: 40)
        backboard.center = center
        
        promptLab1.text = "請使用電腦登陸"
        promptLab1.textColor = UIColor.white
        promptLab1.font = UIFont.boldSystemFont(ofSize: 15)
        promptLab1.textAlignment = .center
        promptLab1.backgroundColor = UIColor.clear
        promptLab1.numberOfLines = 1
        promptLab1.frame = CGRect(x: 0,y: 0,width: 300,height: 15)
        promptLab2.text = "www.yiqiweikeshangchuan.com"
        promptLab2.textColor = UIColor.white
        promptLab2.font = UIFont.boldSystemFont(ofSize: 14)
        promptLab2.textAlignment = .center
        promptLab2.backgroundColor = UIColor.clear
        promptLab2.numberOfLines = 1
        promptLab2.frame = CGRect(x: 0,y: 20,width: 300,height: 15)
        
        backboard.addSubview(promptLab1)
        backboard.addSubview(promptLab2)
        
        backboard.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2))
        return backboard
    }
}

extension UILabel {

    //溫馨提示lable(下)
    func configDownPrompt(frame:CGRect) -> UILabel {
        
        let promptLab = UILabel()
        promptLab.text = "掃碼登陸後進行上傳"
        promptLab.textColor = UIColor.white
        promptLab.font = UIFont.boldSystemFont(ofSize: 15)
        promptLab.textAlignment = .center
        promptLab.backgroundColor = UIColor.clear
        promptLab.numberOfLines = 1
        promptLab.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2))
        promptLab.frame = frame
        return promptLab
    }
}


           

網絡請求

import Foundation

class WKQrCodeViewModel {
    
    var url = "" //不含路徑
    private let netTool = NetworkTools.sharedTools;
    
    func qrCodeUpData(host : String, port : String, UUID : String, success :  @escaping ()->(), failure : @escaping (_ errMsg : String) -> ()) {
        
        //協定
        let url_protocol = "http://"
        //路徑
        let url_host = host;
        //端口号(暫用8889)
        let url_port = ":8889"
        //字首
        let path = "/connection"
        
        url = url_protocol + url_host + url_port
        
        print("第一次目前的url是\(url)")
        //url
        let urlStr: String = url + path
        
        //參數
        let param = ["uuid":UUID]

        print("拼接後的網址是\(urlStr),parameterDic是\(param)")
        
        //請求
        netTool.request(.POST, URLString: urlStr, parameters: param as [String : AnyObject]?) { (result, error) in
            
            if error == nil {
                guard let result = result as? [String: AnyObject] else {
                    return
                }
                
                guard let status = result["status"] as? String else {
                    return
                }
                print(status,result)
                
                if Int(status) == 0 {
                    success()
                }else {
                    
                    guard let message = result["message"] as? String else {
                        return
                    }
                    failure(message)
                }
            } else {
     
                
                failure("網絡異常")
            }
        }
    }
    
    //第二次請求
    func qrCodeUpDataAgain(para : Dictionary<String, String>, success:@escaping ()->(), failure:@escaping (_ errMsg : String)->()) -> Void {
        
        print(url)
        netTool.request(.POST, URLString: url+"/confrim", parameters: para as [String : AnyObject]?) { (result, error) in
            
                if error == nil {
                guard let result = result as? [String: AnyObject] else {
                    return
                }
            
                guard let status = result["status"] as? String else {
                    return
                }
                print(status,result)
                
                if Int(status) == 0 {
                    
                    success()
                }else {
                    guard let message = result["message"] as? String else {
                        
                        return
                    }
                    //伺服器傳回失敗消息
                    failure(message)
                }
            }else {

                failure("網絡異常")
            }
        }
    }
}

           

流程圖

php/socket.io實作掃碼登入

運作結果

用戶端

php/socket.io實作掃碼登入

模拟手機端的請求

php/socket.io實作掃碼登入
php/socket.io實作掃碼登入

手機端一般是2次請求 

第一次需要告訴web端自己已經成功掃描二維碼并解析 

第二次是登陸确認

解析的步驟由手機端實作   這裡我隻是模拟 是以可以看到我的uuid和token是随便寫的

node伺服器端收到的資訊

php/socket.io實作掃碼登入

node伺服器的搭建非常簡單  http://blog.csdn.net/zhangsheng_1992/article/details/51322707

所有代碼可以在這裡找到 https://code.csdn.net/zhangsheng_1992/socket-io/tree/master