ประยุกต์การล็อกอินด้วย qrcode สแกนเข้าสู่ระบบในอีกอุปกรณ์

บทความ เมื่อไม่กี่สัปดาห์ โดย Ninenik Narkdee
qrcode login scan qrcode qrcodejs qrcode scan

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ qrcode login scan qrcode qrcodejs qrcode scan

ดูแล้ว 613 ครั้ง


เนื้อหาในบทความตอนนี้ จะเป็นแนวทางการประยุกต์ การสร้างระบบ
การล็อกอินผ่าน qrcode ในอีกอุปกรณ์ ซึ่งหลักก็คือ จะใช้สำหรับ
ล็อกอินเข้าใช้งานผ่านเครื่อง desktop หรือ pc ในกรณีที่ผู้ใช้งานหรือ
สมาชิกได้ล็อกอินเว็บไซต์ผ่านอุปกรณ์มือถือค้างไว้อยู่ และต้องการจะมาใช้งาน
ในเครื่อง pc ก็สามารถทำการ สแกน qrcode ที่แสดงบนหน้าจอ เพื่อเข้าใช้งาน
โดยไม่จำเป็นต้องกรอกชื่อผู้ใช้หรือรหัสผ่าน
 

รูปแบบการทำงานการล็อกอินผ่าน QRCode

    การทำงานจะอยู่ในรูปแบบเงื่อนไขคือ ผู้ใช้งานหรือสมาชิกเว็บไซต์ของเรา ได้ทำการเข้าใช้งานเว็บไซต์
หรือแอปของเราบนมือถืออยู่ และสามารถไปยังหน้าที่เรียกใช้งานกล้องมือถือ เพื่อทำการ สแกน QRCode
โดยรหัส QRCode จะแสดงในอีกอุปกรณ์ในที่นี้ก็คือในเครื่อง pc จะมีเมนูสำหรับให้ผู้ใชัที่ยังไม่ได้ล็อกอิน
สามารถเลือกจะเลือกอินผ่านรหัส QRCode ได้ โดยเมื่อลิ้งค์ไปยังหน้าล็อกอินผ่าน QRCode ก็จะแสดง
QRCode ให้เราทำการสแกนเพื่อล็อกอินเข้าใช้งาน ภายในเวลาที่กำหนด 
 

แนวทางของโปรแกรมการทำงานการล็อกอินผ่าน QRCode

    แนวทางนี้สามารถนำไปปรับแต่งหรือประยุตก์เพิ่มเติมได้ตามต้องการ วิธ๊ก็คือ ในหน้าสร้าง QRCode 
เราจะให้โปรแกรมทำการสร้างรหัส Token วันที่เวลาสร้าง และวันที่เวลาหมดอายุ ในที่นี่กำหนดไว้แค่
2 นาที หลังจากได้ข้อมูลทั้งสามค่านี้แล้ว ก็ทำการสร้างเป็น QRCode เพื่อรอให้ผู้ใช้มาสแกน โดยในการ
สร้างนั้น เราจะกำหนดให้สร้างได้ทีละ 2 รอบ ทุกๆ 60 วินาที หรือ 1 นาทีก็ให้เริ่มใหม่ ใช้ค่าใหม่ หากผู้ใช้
ไม่ได้สแกน และถ้า QRCode ที่สร้างรอบทึ่ 2 ยังไม่ถูกสแกน ก็จะทำการหยุดการสร้าง QRCode ไว้ก่อน
หากผู้ใช้ต้องการใช้งาน ต้องทำการกดเพื่อโหลดหน้าใหม่อีกครั้ง แบบนี้เป็นต้น โดยในขณะที่แสดง QRCode
เราก็ทำคำสั่งส่งค่า token ไปตรวจสอบกับ server ทุกๆ 5 วินาที   สำหรับในฝั่งที่สแกน ผู้ใช้ที่ล็อกอินใน
มือถืออยู่แล้ว จะสามารถไปยังหน้าเปิดกล้องเพื่อสแกน QRCode เมื่อผู้ใช้ ทำการสแกนรหัสที่ยังไม่หมดอายุ
ก็จะทำการบันทึกลงในตารางฐานข้อมูล เพื่อนำค่า token วันที่สร้าง วันหมดอายุ และ session อาจจะเป็น
ค่า user_id ไปบันทึก และรอการทำงานในฝั่ง pc ที่จะค่ามาตรวจสอบทุกๆ 5 วินาทีว่า ถ้าค่ายังไม่หมดอายุ
และเป็นค่าที่ได้บันทึกลงฐานข้อมูลจากการสแกนแล้ว ก็นำค่านั้นไปตรวจสอบ ถ้าถูกต้องก็ทำการดึงข้อมูลสมาชิก
และสร้าง session ในฝั่งเครื่อง pc เพื่อเข้าใช้งาน พร้อมทั้งลิ้งค๋ไปยังหน้าสมาชิกอื่น และผู้ใช้ที่เข้าใช้งานแล้ว
จะไม่สามารถมายังหน้าแสดง QRCode นี้ได้ จนกว่าจะออกจากระบบ หรือไปใช้เครื่องอื่น แบบนี้เป็นต้น
 

ไฟล์ JavaScript และไฟล์อื่นที่ต้องใช้ประกอบทั้งหมด

    สามารถดาวน์โหลดไฟล์ที่ต้องใช้ได้ที่ qrlogin.rar
 

qrlogin/
------lib/
--------- crypto-js.min.js  // ใช้สำหรับสร้าง token
--------- jsQR.js  // ใช้สำหรับอ่านข้อมูลจากรูป QRCode
--------- qrcode.min.js // ใช้สำหรับสร้างรูป QRCode
------images/
--------- pass-fail.png  // สร้างรูปไอคอน
------sound/
--------- scanner-beeps-barcode.mp3  // เพิ่มเสียงเมื่อทำการสแกน
 
 

เตรียมตารางฐานข้อมูล

    เราจะสร้างตารางชื่อ qr_token ตามรูปแบบคำสั่งดังนี้
 
CREATE TABLE `qr_token` (
  `qr_token_id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `qr_token_val` varchar(255) NOT NULL,
  `qr_token_addDate` datetime NOT NULL,
  `qr_token_expiredDate` datetime NOT NULL,
  `qr_token_loginDate` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `qr_token`
  ADD PRIMARY KEY (`qr_token_id`);

ALTER TABLE `qr_token`
  MODIFY `qr_token_id` int(11) NOT NULL AUTO_INCREMENT;
 
ค่า user_id คือค่าที่เราจะเก็บ id ของผู้ใช้ที่ตารางหลักของสมาชิก สมมติว่า ตารางสมาชิกชื่อว่า
member อาจจะมีข้อมูล user_id,name,email,pass แบบนี้เป็นต้น 

 

ไฟล์ตรวจสอบการล็อกอิน และการสแกนข้อมูล

    ไฟล์นี้สามารถแยกเป็นสองไฟล์ หรือจะใช้เป้นไฟล์เดียวกันก็ได้ ในที่นี้จะใช้เป็นไฟล์เดียวกัน ดังนี้
สมมติใช้เป็นชื่อว่า checkqrlogin.php  เราให้ค่าตัวแปร session ตัวอย่างเราใช้เป็น 
$_SESSION['mem_user_id']
 

    ไฟล์ checkqrlogin.php

<?php
/** Error reporting */
/*error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);*/
session_start();  
// โค้ดไฟล์ dbconnect.php ดูได้ที่ http://niik.in/que_2398_5642
require_once("dbconnect.php");  // ส่วนเชื่อมต่อฐานข้อมูล
// ส่วนของสมาชิก ที่ทำการสแกน QRCode แล้วส่งค่าเพื่อนำไปบันทึกลงฐานข้อมูล
// มีการส่งข้อมูลแบบ POST เข้ามา และต้องเข้าสู่ระบบในมือถืออยู่ก่อนหน้าแล้ว
if (
	isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST'
	&&  isset($_SESSION['mem_user_id']) && $_SESSION['mem_user_id'] != ''
) {
	// ดึงข้อมูล JSON payload ที่ส่งเข้ามาในส่วน request body
	$jsonPayload = file_get_contents('php://input');

	// ถ้าไม่ใช่ข้อมูลว่าง หรือถ้ามีข้อมูล
	if (!empty($jsonPayload)) {
		// มีข้อมูลส่งมา จัดให้อยู่ในรูปแบบตัวแปร อาเรย์
		$data = json_decode($jsonPayload, true);

		// ตรวจสอบข้อมูลอาเรย์ ว่ามีค่า token หรือไม่
		if (isset($data['token'])) {
			$token = $data['token']; // เก็บค่า token ในตัวแปรไว้ใช้งาน
			// จัดรูปแบบวันที่เดิมที่ส่งมาในแบบ 20240408123030 ก็จัดให้อยู่ในรูปแบบวันที่
			// เป็น 2024-04-08 12:30:30 เพือจะนำไปบันทึกวันที่และเวลาลงฐานข้อมูล
			$date_generated = DateTime::createFromFormat('YmdHis', $data['generated']);
			$generated = $date_generated->format('Y-m-d H:i:s');
			$date_expired = DateTime::createFromFormat('YmdHis', $data['expired']);
			$expired = $date_expired->format('Y-m-d H:i:s');
			// ตรวจสอบ QRCode ที่ส่งมายังไม่หมดอายุ
			if (time() <= strtotime($expired)) {
				// ทำคำสั่งบันทึกลงฐานข้อมูลตาราง qr_token 
				$sql = "
			INSERT INTO qr_token  
				(
					user_id, 
					qr_token_val, 
					qr_token_addDate, 
					qr_token_expiredDate
				) 
			VALUES 
				(
					'" . $_SESSION['mem_user_id'] . "',
					'" . $token . "',
					'" . $generated . "',
					'" . $expired . "'
				)
			";
				$mysqli->query($sql);
			} else { // token หมดอายุแล้ว
				$error_msg = "Token was expired";
			}
		} else {
			// ไม่มีข้อมูล token ส่งมา
			$error_msg = "Token not found in the payload";
		}
	} else {
		// ไม่มีข้อมูลส่งมา
		$error_msg = "No payload data received";
	}
	// เมื่อมี token และทำการสแกนบันทึกลงฐานข้อมูลเรียบร้อย ไม่มี error ใดๆ
	if (isset($token) && !isset($error_msg)) {
		// บันทึกข้อมูลสำเร็จ รอกดตรวจสอบ การล็อกอินจากฝั่งผู้ใช้ใน pc
		$response = array(
			'login' => true,
			'generated' => $generated,
			'expired' => $expired,
			'token' => $token
		);
	} else {
		// กรณีอื่นๆ ส่งกลับข้อมูลการบันทึกข้อมูลไม่สำเร็จ
		$response = array(
			'login' => false,
			'error' => $error_msg
		);
	}
	// คืนค่าข้อมูลเป็น json เพื่อไปให้ ckeditor จัดการ
	header('Content-Type: application/json');
	echo json_encode($response);
	exit;
}
// ส่วนของการตรวจสอบการเข้าสู่ระบบ
// มีการส่งข้อมูลแบบ POST เข้ามา และยังไม่มีการเข้าสู่ระบบหรือยังไม่มีการสร้าง session ข้อมูล ของผู้ใช้งาน
if (
	isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST'
	&&  !isset($_SESSION['mem_user_id'])
) {
	// ดึงข้อมูล JSON payload ที่ส่งเข้ามาในส่วน request body
	$jsonPayload = file_get_contents('php://input');

	// ถ้าไม่ใช่ข้อมูลว่าง หรือถ้ามีข้อมูล
	if (!empty($jsonPayload)) {
		// มีข้อมูลส่งมา จัดให้อยู่ในรูปแบบตัวแปร อาเรย์
		$data = json_decode($jsonPayload, true);

		// ตรวจสอบข้อมูลอาเรย์ ว่ามีค่า token หรือไม่
		if (isset($data['token'])) { // ถ้ามี
			$token = $data['token'];  // เก็บค่า token ในตัวแปรไว้ใช้งาน
			// ลบข้อมูล token ที่หมดอายุแล้วออกจากฐานข้อมูลถ้ามี
			$mysqli->query("DELETE FROM qr_token WHERE qr_token_expiredDate < NOW()");
			// คำสั่ง sql ดึงข้อมูล user_id และ qr_token_id ในตาราง ที่มีค่า token ตรงกัน และยังไม่หมดอายุหรือไม่
			$sql = "
			SELECT user_id,qr_token_id FROM qr_token WHERE 
			qr_token_val='" . $token . "' AND NOW() <= qr_token_expiredDate 
			";
			$result = $mysqli->query($sql);
			if($result && $result->num_rows>0){  // มีข้อมูล ยังไม่หมดอายุ
				$row = $result->fetch_assoc(); // ดึงข้อมูลจาก ตาราง qr_token
				$user_id = $row['user_id'];
				$qr_token_id = $row['qr_token_id'];
				// ดึงข้อมูลสมาชิก เพื่อสร้าง session เพื่อล็อกอิน
				$sql = "SELECT * FROM member WHERE user_id='".$user_id."'  LIMIT 1 ";
				$result_user = $mysqli->query($sql);
				if($result_user && $result_user->num_rows>0){  // มีข้อมูล
					$row_user = $result->fetch_assoc(); // ดึงข้อมูลจาก ตาราง member
					// สร้าง session ของผู้ใช้งาน สร้างตามต้องการว่ามีอะไรบ้าง
					$_SESSION['mem_user_id'] = $row_user['user_id'];						
				
					// อัปเดทตาราง qr_token ว่าได้ทำการล็อกอินแล้ว ที่เวลาเท่าไหร่
					$logindate = date("Y-m-d H:i:s");
					$mysqli->query("
						UPDATE qr_token SET 
						qr_token_loginDate='" . $logindate . "'
						WHERE qr_token_id='" . $qr_token_id . "'			
					");					
				} else { // กรณีไม่มีผู้ใช้นี้ หรืออื่นๆ ไม่อนุญาตให้เข้่าใช้งาน
					$error_msg = "Access denied!";  
				}
			} else { // token หมดอายุแล้ว
				$error_msg = "Token was invalid or expired";
			}
		} else {
			// ไม่มีข้อมูล token ส่งมา
			$error_msg = "Token not found in the payload";
		}
	} else {
		// ไม่มีข้อมูลส่งมา
		$error_msg = "No payload data received";
	}
	// เมื่อทำการล็อกอิน มีการส่งค่า token มา และไม่มี error เกิดขึ้น
	if (isset($token) && !isset($error_msg)) {
		// ส่งกลับข้อมูลการล็อกอินสำเร็จ
		$response = array(
			'login' => true,
			'logindate'=>$logindate,
			'token' => $token
		);
	} else {
		// กรณีอื่นๆ ส่งกลับข้อมูลการล็อกอินไม่สำเร็จ
		$response = array(
			'login' => false,
			'error' => $error_msg
		);
	}
	// คืนค่าข้อมูลเป็น json 
	header('Content-Type: application/json');
	echo json_encode($response);
	exit;
}
 
 
ต่อไปเป็นส่วนของไฟล์ที่แสดง qrcode ในที่นี้จะใช้ชื่อเป็น qrcode_login.php
เงื่อนไขของหน้านี้ จะต้องไม่มี session ของสมาชิก หรือจะต้องยังไม่ล็อกอิน ถึงจะมาหน้านี้ได้
ถ้ามี session อยู่แล้ว ให้ redirect หรือลิ้งค์ไปหน้าสมาชิก ไม่อนุญาตให้เข้าหน้านี้ได้
 

    ไฟล์ qrcode_login.php

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>QRCode Login</title>
    <script src="./qrlogin/lib/crypto-js.min.js"></script>
    <script src="./qrlogin/lib/qrcode.min.js"></script>
</head>

<body>
    <style type="text/css">
        .wrap-login-qrcode {
            margin: auto;
            margin-top: 10px;
            text-align: center;
        }

        #login-qrcode {
            width: 128px;
            height: 128px;
            margin: auto;
        }

        #countdown-expired {
            text-align: center;
            margin-top: 10px;
        }

        .container-qrcode {
            position: relative;
            width: 128px;
            height: 128px;
            margin: auto;
        }

        .container-qrcode.success {
            border: 5px dashed #6ead3d;
        }

        .container-qrcode.fail {
            border: 5px dashed #ce2c2f;
        }

        .overlay-qrcode {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: #8bc34a7a;
            display: none;
            justify-content: center;
            align-items: center;
        }

        .overlay-qrcode.show {
            display: flex;
        }

        .overlay-qrcode.fail {
            display: flex;
            background-color: #f4433657 !important;

        }

        .checkmark {
            width: 51px;
            height: 51px;
            background-image: url('./qrlogin/images/pass-fail.png');
            background-size: 109.9px 109.9px;
            background-position: 0 0;
            background-repeat: no-repeat;
            opacity: 0;
        }

        .overlay-qrcode.show .checkmark {
            opacity: 0.8;
        }

        .overlay-qrcode.fail .checkmark {
            opacity: 0.8;
            background-position: 0px -59px !important;
        }

        #try-limit {
            margin-top: 15px;
        }
    </style>
    <div class="wrap-login-qrcode">
        <!-- แสดงคิวอาร์โค้ด -->
        <div class="container-qrcode">
            <div id="login-qrcode"></div>
            <div class="overlay-qrcode">
                <div class="checkmark"></div>
            </div>
        </div>
        <!-- แสดงเวลานับถอยหลัง -->
        <div id="countdown-expired"></div>
        <div id="wrap-try-limit" hidden>
            <button id="try-limit" type="button">ลองใหม่อีกครััง</button>
        </div>
    </div>
    <!-- Script to generate QR code and start timer -->
    <script>
        // กำหนด dom object ที่เรียกใช้แต่ละรายการ
        const loginQrcode = document.querySelector("#login-qrcode");
        const countDownExpired = document.querySelector("#countdown-expired");
        const trylimitDiv = document.querySelector("#wrap-try-limit");
        const trylimitBtn = document.querySelector("#try-limit");
        var postToken; // ตัวแปรเก็บข้อมูล token ที่จะส่งไปใช้งาน
        var allow_time = 2; // ให้หยุดการทำงานทุก 2 รอบถ้าไม่มีการสแกน
        var try_time = 1; // ตัวแปรเริ่มการนับครั้งที่สร้าง qrcode ใหม่่ 
        // กำหนดเวลา ให้เพิ่มอีก 1000 หรือ 1 วินาทีให้กับเวลาที่ต้องการ
        // สมมติให้นับ 60 วินาที ก็จะใช้เป็น 61000
        var qrcodecountdownexpired = 61000; // ค่า 1 วินาทีใช้ 10000 

        // สร้าง token ระดับที่หนึ่งแบบง่าย
        function generateToken() {
            const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            let token = "";
            for (let i = 0; i < 10; i++) {
                token += charset.charAt(Math.floor(Math.random() * charset.length));
            }
            return token;
        }

        // สร้างรูปแบบวันที่และเวลาที่สรา้าง qrcode ไว้แนบไปกับ token ค่าจะเป็น 20240408183030
        function generateDateTime() {
            const date = new Date();
            const year = date.getFullYear();
            const month = String(date.getMonth() + 1).padStart(2, '0');
            const day = String(date.getDate()).padStart(2, '0');
            const hours = String(date.getHours()).padStart(2, '0');
            const minutes = String(date.getMinutes()).padStart(2, '0');
            const seconds = String(date.getSeconds()).padStart(2, '0');
            return `${year}${month}${day}${hours}${minutes}${seconds}`;
        }
        // สร้างรูปแบบวันที่และเวลาที่หมดอายุ qrcode ไว้แนบไปกับ ไม่ควรนาน ที่นี้กำหนด 2 นาที 
        // ค่าจะเป็น 20240408183230
        function generateExpiredDateTime(minutes_expired) {
            const date = new Date();
            date.setMinutes(date.getMinutes() + minutes_expired);
            const year = date.getFullYear();
            const month = String(date.getMonth() + 1).padStart(2, '0');
            const day = String(date.getDate()).padStart(2, '0');
            const hours = String(date.getHours()).padStart(2, '0');
            const minutes = String(date.getMinutes()).padStart(2, '0');
            const seconds = String(date.getSeconds()).padStart(2, '0');
            return `${year}${month}${day}${hours}${minutes}${seconds}`;
        }

        // สร้าง token ระดับที่สองด้วย CryptoJS
        function generateTokenCryptoJS() {
            let token = CryptoJS.SHA256(generateToken()).toString(CryptoJS.enc.Base64);
            return token;
        }

        // ฟังก์ชั่นสร้าง qrcode พร้อมแนบข้อมูลวันที่เพิ่มกับวันหมดอายุเข้าไปด้วย
        function generateQRCode() {
            // สร้างข้อมูลเวลา
            const datetime = generateDateTime();
            const expired_datetime = generateExpiredDateTime(2); // หมดอายุใน 2 นาที

            // สร้างข้อมูล token
            const token = generateTokenCryptoJS();
            postToken = token; // เก็บไว้ในตัวแปรไว้ส่งไปใช้งาน

            // จัดรูปแบบข้อมูลที่จะสร้าง qrcode รูปแบบจะเป็น
            // 20240408183030:ข้อมูลtoken:20240408183230
            const text = datetime + ":" + token + ":" + expired_datetime;

            // สร้าง qrcode จากข้อความที่ได้และแสดง
            var qrcode = new QRCode(loginQrcode, {
                text: text,
                width: 128,
                height: 128
            });
        }

        // ฟังก์ชั่นนับเวลาหมดอายุ
        function updateCountdown(timeRemaining) {
            let remain_time = Math.ceil(timeRemaining / 1000);
            if (remain_time <= 0) { // หมดอายุ ให้ลองให่อีกครั้ง
                countDownExpired.innerText = "ลองใหม่อีกครั้ง";
            } else {
                countDownExpired.innerText = "เหลือเวลา: " + remain_time + " วินาที";
            }
        }

        // ฟังก์ชั่นทำงาน เมื่อรอบสร้าง qrcode หมดอายุ
        function handleExpiration() {
            // รีเซ็ตหรือหยุดการทำงานตัวนับเวลาถอยหลัง
            clearInterval(countdownInterval);

            // รีเซ็ตหรือหยุดการทำงานการส่งข้อมูลไปตรวจสอบการล็อกอิน
            clearInterval(checkAccessInterval);

            // ล้างข้อมูล qrcode ก่อนหน้าเป็นค่าว่าง ให้พร้อมสำหรับการสร้างใหม่
            loginQrcode.innerHTML = "";

            try_time++; // นับจำนวนครั้งที่มีการสร้าง qrcode
            generateQRCode(); // เรียกฟังก์ชั่นสร้าง qrcode
            if (try_time > allow_time) { // มากกว่าจำนวนครั้งที่อนุญาต
                // รีเซ็ตหรือหยุดการทำงานตัวนับเวลาถอยหลัง
                clearInterval(countdownInterval);
                // รีเซ็ตหรือหยุดการทำงานการส่งข้อมูลไปตรวจสอบการล็อกอิน
                clearInterval(checkAccessInterval);
                handleLoginFail();
                countDownExpired.innerText = "";
                trylimitDiv.hidden = false;
            } else { // ยังไม่เลยจำนวนครั้งที่อนุญาต ทำงานต่อได้
                startExpirationTimer();
            }
        }

        // ฟังก์ชั่นนับเวลาถอยหลัง
        function startExpirationTimer() {
            // กำหนดเวลาหมดอายุของ qrcode ที่่แสดง
            const expirationTime = qrcodecountdownexpired; 

            // กำหนดารทำงานนับถอยหลักทุกๆ 1 วินาที
            countdownInterval = setInterval(function() {
                // คำนวนเวลาที่เหลือเหลือกี่วินาที
                var currentTime = new Date().getTime();
                var timeRemaining = expirationTime - (currentTime - startTime);

                // แสดงเวลาที่เหลืออยู่
                updateCountdown(timeRemaining);

                // ถ้าหมดเวลาแล้ว
                if (timeRemaining <= 0) {
                    handleExpiration();
                }
            }, 1000);

            // กำหนดการส่งข้อมูลไปล็อกอินทุกๆ 5 วินาที
            checkAccessInterval = setInterval(function() {
                //   countDownExpired.innerText = "Authorizing.....";		
                postData = {
                    token: postToken
                }
                // กำหนด url สำหรับตรวจสอบการล็อกอิน
                qrcheckaccess('checkqrlogin.php', postData)
                    .then(data => {
                        if (data.login) {
                            clearInterval(countdownInterval);
                            clearInterval(checkAccessInterval);
                            handleLoginSuccess();
                            countDownExpired.innerText = "เข้าสู่ระบบสำเร็จ...กรุณารอสักครู่";
                            setTimeout(function(){
                            //    window.location = ''; ลิ้งค์ไปยังหน้าสมาชิก
                            },3000);
                        }
                    })
                    .catch(error => {
                        console.error('Error:', error);
                    });

            }, 5000);

            // เก็บวันเวลาเริ่มต้นสำหรับตัวนับเวลาถอยหลัง
            var startTime = new Date().getTime();
        }

        // เริ่มสร้าง qrcode และ กำหนดกาารทำงานของตัวกำหนดวันหมดอายุ 
        generateQRCode();
        startExpirationTimer();

        // ฟังก์ชั่นสำหรับปุ่มเริ่่มใหม่อีกครั้ง
        trylimitBtn.addEventListener('click', function() {
            window.location.reload();
        });
        async function qrcheckaccess(url = '', data = {}) {
            // Default options are marked with *
            const response = await fetch(url, {
                method: 'POST', // *GET, POST, PUT, DELETE, etc.
                headers: {
                    'Content-Type': 'application/json'
                    // 'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: JSON.stringify(data) // body data type must match "Content-Type" header
            });
            return response.json(); // parses JSON response into native JavaScript objects
        }

        function handleLoginSuccess() {
            const qrcodeDiv = document.querySelector(".container-qrcode");
            const overlay = document.querySelector('.overlay-qrcode');
            overlay.classList.add('show');
            qrcodeDiv.classList.add('success');

        }

        function handleLoginFail() {
            const qrcodeDiv = document.querySelector(".container-qrcode");
            const overlay = document.querySelector('.overlay-qrcode');
            overlay.classList.add('fail');
            qrcodeDiv.classList.add('fail');
        }
    </script>
</body>
</html>
 
 
และไฟล์สุดท้าย ส่วนของไฟล์สำหรับสมาชิก ที่ใช้งานบนมือถืออยู่ และยังล็อกอินไว้ ดังนั้นหน้านี้จะต้อง
เข้าถึงได้เฉพาะสมาชิกที่เข้าสู่ระบบแล้วเท่านั้น หากยังไม่ได้เข้าสู่ระบบ ให้ลิ้งค์ไปหน้าอื่น ไม่อนุญาต
ให้เข้าใข้งานได้ ในที่นี้ใช้ชื่อเป็น qrcode_scan.php
 

    ไฟล์ qrcode_scan.php

<!DOCTYPE html>
<html lang='en'>

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Scan QRCode</title>
	<script src="./qrlogin/lib/jsQR.js"></script>
	<script src="./qrlogin/lib/qrcode.min.js"></script>
	<style>
		h1 {
			margin: 10px 0;
			font-size: 22px;
		}

		.wrap-qrcode-scanner {
			max-width: 640px;
			margin: 0 auto;
			position: relative;
			height: 650px;
			overflow: hidden;
		}

		#loadingMessage {
			text-align: center;
			padding: 40px;
			background-color: #eee;
		}

		#canvas,
		#qrcode-result {
			width: 100%;
			height: 100%;
			border-radius: 10px;
		}

		#output {
			margin-top: 20px;
			background: #eee;
			padding: 10px;
			padding-bottom: 0;
		}

		#output div {
			padding-bottom: 10px;
			word-wrap: break-word;
		}

		#beepsound {
			width: 0px;
			height: 1px;
		}

		.wrap-video {
			margin: auto;
			width: 300px;
			height: 300px;
			padding: 15px;
			border-radius: 10px;
			border: 3px dashed #000000;
		}
	</style>
</head>

<body>


	<div class="wrap-qrcode-scanner">
		<h1>QRCode Scanner</h1>
		<div id="loadingMessage">&#x1F3A5; Unable to access video stream (please make sure you have a webcam enabled)</div>
		<div class="wrap-video">
			<canvas id="canvas" hidden></canvas>
			<div id="qrcode-result" hidden></div>
		</div>
		<div id="output" hidden>
			<div id="outputMessage">No QR code detected.</div>
			<div hidden> <span id="outputData"></span>
				<button class="btn-cancel" type="button" onclick="resumeAnimation();" hidden>
					ทำรายการใหม่
				</button>
			</div>
		</div>
		<audio id="beepsound" controls>
			<source src="./qrlogin/sound/scanner-beeps-barcode.mp3" type="audio/mpeg">
			Your browser does not support the audio tag.
		</audio>
		<div id="outputqrcode"></div>
	</div>
	<script>
		// กำหนดตัวแปร DOM object และตัวแปรต่างๆ
		var video = document.createElement("video");
		var finalQrcode = document.getElementById("qrcode-result");
		var canvasElement = document.getElementById("canvas");
		var canvas = canvasElement.getContext("2d");
		var loadingMessage = document.getElementById("loadingMessage");
		var outputContainer = document.getElementById("output");
		var outputMessage = document.getElementById("outputMessage");
		var outputData = document.getElementById("outputData");
		var cancelBtn = document.querySelector(".btn-cancel");
		var beepsound = document.getElementById("beepsound");
		var TLR, TRR, BRL, BLL;
		var code;
		var waiting;

		// ฟังก์ชั่นสำหรับสร้างเส้นกรอบของ qrcode ที่สแกน
		function drawLine(begin, end, color) {
			canvas.beginPath();
			canvas.moveTo(begin.x, begin.y);
			canvas.lineTo(end.x, end.y);
			canvas.lineWidth = 4;
			canvas.strokeStyle = color;
			canvas.stroke();
			return true;
		}

		// ขอนุญาตเข้าถึงกล้องในมือถือ เพื่อใช้งานการสแกน
		navigator.mediaDevices.getUserMedia({
			video: {
				facingMode: "environment",
				advanced: [
					/*	{ zoom: 2 },*/
				]
			}
		}).then(function(stream) {
			video.srcObject = stream; // นำข้อมูลจากกล้องที่เป็นแบบ  stream ไปแสดงในวิดีโอ
			video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
			video.play();
			requestAnimationFrame(tick); // ทำคำสั่งจับภาพทุกการเคลื่อนไหวหน้าจอ
		});

		let animationRunning = true; // เริ่มต้นแสดงทีเมื่อกล้องพร้อม

		// ฟังก์ชั่นที่จะถูกเรียกให้ทำงาน ทุกการเคลื่อนไหวของหน้าจอในค่าที่เหมาะสม 
		function tick() {
			if (!animationRunning) { // เมื่อไม่มีการจับภาพการเคลือนไหว ให้หยุดการทำงานของฟังก์ชั่น
				return;
			}

			// แสดงสถานะ ขณะกล้องยังไม่พร้อมใช้งาน
			loadingMessage.innerText = "&#x23F3; Loading video..."
			// เมื่อกล้องพร้อมใช้งานและสแงดวิดีโอจากล้อง
			if (video.readyState === video.HAVE_ENOUGH_DATA) {
				loadingMessage.hidden = true; // ซ่อนข้อความโหลดวิดีโอ
				canvasElement.hidden = false;
				outputContainer.hidden = false;
				finalQrcode.hidden = true;
				finalQrcode.innerHTML = '';

				canvasElement.height = video.videoHeight;
				canvasElement.width = video.videoWidth;
				canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
				if (!video.paused) {
					var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
					code = jsQR(imageData.data, imageData.width, imageData.height, {
						inversionAttempts: "dontInvert",
					});
				}
				if (code) {
					TLR = drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58");
					TRR = drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58");
					BRL = drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58");
					BLL = drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58");
					outputMessage.hidden = true;
					outputData.parentElement.hidden = false;
					outputData.innerText = code.data;
					cancelBtn.hidden = false;
					if (code.data != "" && !waiting && TLR == true && TRR == true && BRL == true && BLL == true) {
						debug_log(code.data);
						// สามารถส่งค่า code.data ไปทำงานอย่างอื่นๆ ผ่าน ajax ได้
						video.pause();
						beepsound.play();
						beepsound.onended = function() {
							finalQrcode.hidden = false;
							// สร้าง qrcode มาแทนกล้อง เมื่อเข้าสู่ระบบสำเร็จ ปรับขนาดให้เหมาะสม
							new QRCode(finalQrcode, {
								text: code.data,
								width: 265,
								height: 265
							});
							canvasElement.hidden = true;

							beepsound.muted = true;
							pauseAnimation();

							const tokenData = code.data.split(':');
							postData = {
								generated: tokenData[0],
								expired: tokenData[2],
								token: tokenData[1]
							}
							// ส่งข้อมูล token ไปบันทึกลงฐานข้อมูล รอฝั่ง pc เข้ามาล็อกอินผ่าน qrcode				 
							grandedaccess('checkqrlogin.php', postData)
								.then(data => {
									// เพิ่มข้อมูลการล็อกอินสำเร็จ รอทาง pc ทำการเข้าสู่ระบบ
									if (data.login) {
										alert('กำลังดำเนินการ...รอสักครู่');
									}
									if (data.error !== undefined) { // เพิ่มข้อมูลไม่สำเร็จ
										alert("เกิดข้อผิดพลาด: " + data.error + " \r\nลองใหม่อีกครั้ง");
									}
									debug_log(data.login + " | " + data.token + " | " + data.generated + 
									" | " + data.expired + " | " + data.error);
								})
								.catch(error => {
									debug_log('Error:' + error);
									console.error('Error:', error);
								});

						};
						// ให้เริ่มเล่นวิดีโอก่อนล็กน้อย เพื่อล้างค่ารูป qrcod ล่าสุด เป็นการใช้รูปจากกล้องแทน
						setTimeout(function() {
							video.play();
						}, 4500);
						// ให้รอ 5 วินาทีสำหรับการ สแกนในครั้งจ่อไป
						waiting = setTimeout(function() {
							TLR,
							TRR,
							BRL,
							BLL = null;
							beepsound.muted = false;
							if (waiting) {
								clearTimeout(waiting);
								waiting = null;
							}
						}, 5000);
					}
				} else {
					outputMessage.hidden = false;
					outputData.parentElement.hidden = true;
				}
			}
			requestAnimationFrame(tick);
		}
		// ฟังก์ชั่นสำหรับหยุดการตรวจจับการเคลื่อนไหว หรือหยุดกล้องชั่วคราว
		function pauseAnimation() {
			animationRunning = false;
		}

		// ฟังก์ชั่นกลับไปใช้งานกล้องอีกครั้ง
		function resumeAnimation() {
			animationRunning = true;
			tick(); // เริ่มขั้นตอน animation loop
		}
		async function grandedaccess(url = '', data = {}) {
			// Default options are marked with *
			const response = await fetch(url, {
				method: 'POST', // *GET, POST, PUT, DELETE, etc.
				headers: {
					'Content-Type': 'application/json'
					// 'Content-Type': 'application/x-www-form-urlencoded',
				},
				body: JSON.stringify(data) // body data type must match "Content-Type" header
			});
			return response.json(); // parses JSON response into native JavaScript objects
		}
		// สำหรับทดสอบ นำออกได้ถ้าไม่ได้ใช้งาน
		function debug_log(data) {
			const debugDiv = document.querySelector("#debug");
			debugDiv.innerHTML = data;
		}
	</script>

	<div id="debug">

	</div>

</body>
</html>
 
 
เนื้อหาเกี่ยวกับการใช้กล้องสแกน qrcode ดูเพิ่มเติมในบทความด้านล่าง
 
ประยุกต์ scan qrcode ผ่าน webcam โดยใช้ jsQR Library http://niik.in/981 
 
เท่านี้ก็สามารถเป็นแนวทางนำไปประยุกต์ใช้งาน หรือปรับแต่งเพิ่มเติมตามต้องการได้ และเว็บไซต์นี้
ก็มีการนำระบบดังกล่าวมาใช้งาน สามารถทดสอบการใช้งานได้ ที่ เมนู | ตั้งค่า ในมือถือ
 
หวังว่าบทความนี้จะเป็นแนวทางที่มีประโยชน์ และสามารถนำไปประยุกต์ใช้งานต่อไป ไม่มากก็น้อย


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ







เนื้อหาที่เกี่ยวข้อง






เนื้อหาพิเศษ เฉพาะสำหรับสมาชิก

กรุณาล็อกอิน เพื่ออ่านเนื้อหาบทความ

ยังไม่เป็นสมาชิก

สมาชิกล็อกอิน



( หรือ เข้าใช้งานผ่าน Social Login )




URL สำหรับอ้างอิง





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ