# 💾 คู่มือการจัดเก็บข้อมูลลงฐานข้อมูล
## Face Recognition Database Integration Guide
เอกสารนี้อธิบายวิธีการปรับปรุงระบบ Face Recognition ให้สามารถจัดเก็บข้อมูลใบหน้าลงฐานข้อมูลเพื่อใช้ในการเปรียบเทียบแทนการใช้รูปภาพต้นฉบับ
**ผู้พัฒนา:** Goragod Wiriya
**วันที่อัปเดต:** 15 กรกฎาคม 2568
---
## 🗃️ โครงสร้างฐานข้อมูล
### ตาราง users (ข้อมูลบุคคล)
```sql
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE,
phone VARCHAR(20),
department VARCHAR(100),
position VARCHAR(100),
status ENUM('active', 'inactive') DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
### ตาราง face_descriptors (ข้อมูล Face Descriptor)
```sql
CREATE TABLE face_descriptors (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
descriptor_data TEXT NOT NULL, -- JSON array ของ face descriptor (128 dimensions)
image_path VARCHAR(500),
confidence FLOAT DEFAULT 0,
quality_score FLOAT DEFAULT 0,
is_primary BOOLEAN DEFAULT FALSE, -- กำหนดว่าเป็น descriptor หลักหรือไม่
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
```
### ตาราง face_logs (บันทึกการเปรียบเทียบ)
```sql
CREATE TABLE face_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
similarity_score FLOAT,
match_result BOOLEAN,
detection_time FLOAT, -- เวลาที่ใช้ในการตรวจจับ (วินาที)
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45),
user_agent TEXT,
location VARCHAR(100),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);
```
### ตาราง access_logs (บันทึกการเข้าใช้งาน)
```sql
CREATE TABLE access_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
access_type ENUM('login', 'logout', 'register', 'update') NOT NULL,
success BOOLEAN DEFAULT TRUE,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45),
notes TEXT,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);
```
### การสร้าง Index เพื่อเพิ่มประสิทธิภาพ
```sql
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_status ON users(status);
CREATE INDEX idx_face_user_id ON face_descriptors(user_id);
CREATE INDEX idx_face_primary ON face_descriptors(is_primary);
CREATE INDEX idx_logs_timestamp ON face_logs(timestamp);
CREATE INDEX idx_logs_user_id ON face_logs(user_id);
CREATE INDEX idx_access_timestamp ON access_logs(timestamp);
```
---
## 🔧 Backend API (PHP)
สร้างไฟล์ `api.php`:
```php
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
// จัดการ CORS preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
// การเชื่อมต่อฐานข้อมูล
class DatabaseConnection {
private $host = 'localhost';
private $dbname = 'face_recognition';
private $username = 'root';
private $password = '';
private $pdo;
public function __construct() {
try {
$this->pdo = new PDO(
"mysql:host={$this->host};dbname={$this->dbname};charset=utf8mb4",
$this->username,
$this->password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch(PDOException $e) {
$this->sendError('Database connection failed: ' . $e->getMessage());
}
}
public function getConnection() {
return $this->pdo;
}
private function sendError($message) {
http_response_code(500);
echo json_encode(['error' => $message]);
exit();
}
}
// คลาสหลักสำหรับจัดการ Face Recognition
class FaceRecognitionAPI {
private $db;
private $uploadDir = 'uploads/faces/';
private $maxFileSize = 5 * 1024 * 1024; // 5MB
private $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif'];
public function __construct() {
$this->db = (new DatabaseConnection())->getConnection();
$this->createUploadDirectory();
}
private function createUploadDirectory() {
if (!is_dir($this->uploadDir)) {
mkdir($this->uploadDir, 0777, true);
}
}
public function handleRequest() {
$action = $_POST['action'] ?? $_GET['action'] ?? '';
switch($action) {
case 'register_face':
$this->registerFace();
break;
case 'find_match':
$this->findMatchingFace();
break;
case 'get_all_users':
$this->getAllUsers();
break;
case 'get_user_faces':
$this->getUserFaces();
break;
case 'update_user':
$this->updateUser();
break;
case 'delete_user':
$this->deleteUser();
break;
case 'log_access':
$this->logAccess();
break;
case 'get_statistics':
$this->getStatistics();
break;
default:
$this->sendError('Invalid action', 400);
}
}
// ลงทะเบียนใบหน้าใหม่
public function registerFace() {
try {
$name = $this->validateInput($_POST['name'] ?? '');
$email = $this->validateEmail($_POST['email'] ?? '');
$phone = $_POST['phone'] ?? '';
$department = $_POST['department'] ?? '';
$position = $_POST['position'] ?? '';
$descriptorData = $_POST['descriptor'] ?? '';
if (empty($name) || empty($email) || empty($descriptorData)) {
$this->sendError('Missing required fields', 400);
}
// ตรวจสอบ descriptor format
$descriptor = json_decode($descriptorData, true);
if (!$descriptor || count($descriptor) !== 128) {
$this->sendError('Invalid face descriptor format', 400);
}
// ตรวจสอบ email ซ้ำ
$existingUser = $this->getUserByEmail($email);
if ($existingUser) {
$this->sendError('Email already exists', 409);
}
// อัปโหลดรูปภาพ
$imagePath = $this->uploadImage();
// เริ่ม transaction
$this->db->beginTransaction();
try {
// เพิ่มข้อมูลผู้ใช้
$stmt = $this->db->prepare("
INSERT INTO users (name, email, phone, department, position)
VALUES (?, ?, ?, ?, ?)
");
$stmt->execute([$name, $email, $phone, $department, $position]);
$userId = $this->db->lastInsertId();
// คำนวณคุณภาพของ descriptor
$qualityScore = $this->calculateDescriptorQuality($descriptor);
// บันทึก face descriptor
$stmt = $this->db->prepare("
INSERT INTO face_descriptors
(user_id, descriptor_data, image_path, quality_score, is_primary)
VALUES (?, ?, ?, ?, TRUE)
");
$stmt->execute([
$userId,
$descriptorData,
$imagePath,
$qualityScore
]);
// บันทึก access log
$this->logUserAccess($userId, 'register', true);
$this->db->commit();
$this->sendSuccess([
'user_id' => $userId,
'message' => 'Face registered successfully',
'quality_score' => $qualityScore
]);
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
} catch (Exception $e) {
$this->sendError($e->getMessage());
}
}
// ค้นหาใบหน้าที่ตรงกัน
public function findMatchingFace() {
try {
$inputDescriptor = json_decode($_POST['descriptor'] ?? '', true);
$threshold = floatval($_POST['threshold'] ?? 0.6);
if (!$inputDescriptor || count($inputDescriptor) !== 128) {
$this->sendError('Invalid face descriptor', 400);
}
$startTime = microtime(true);
// ดึงข้อมูลใบหน้าทั้งหมดที่ active
$stmt = $this->db->prepare("
SELECT u.id, u.name, u.email, u.department, u.position,
f.descriptor_data, f.quality_score, f.image_path
FROM users u
JOIN face_descriptors f ON u.id = f.user_id
WHERE u.status = 'active' AND f.is_primary = TRUE
");
$stmt->execute();
$faces = $stmt->fetchAll();
$bestMatch = null;
$bestDistance = PHP_FLOAT_MAX;
foreach($faces as $face) {
$storedDescriptor = json_decode($face['descriptor_data'], true);
$distance = $this->calculateEuclideanDistance($inputDescriptor, $storedDescriptor);
if($distance < $bestDistance) {
$bestDistance = $distance;
$bestMatch = $face;
}
}
$detectionTime = microtime(true) - $startTime;
$similarity = $bestDistance < 1 ? (1 - $bestDistance) * 100 : 0;
$isMatch = $bestDistance < $threshold;
// บันทึกผลการเปรียบเทียบ
$this->logComparison(
$isMatch ? $bestMatch['id'] : null,
$similarity,
$isMatch,
$detectionTime
);
if ($isMatch && $bestMatch) {
$this->sendSuccess([
'match' => true,
'user' => [
'id' => $bestMatch['id'],
'name' => $bestMatch['name'],
'email' => $bestMatch['email'],
'department' => $bestMatch['department'],
'position' => $bestMatch['position'],
'image_path' => $bestMatch['image_path']
],
'similarity' => round($similarity, 2),
'distance' => round($bestDistance, 4),
'quality_score' => $bestMatch['quality_score'],
'detection_time' => round($detectionTime, 4)
]);
} else {
$this->sendSuccess([
'match' => false,
'similarity' => round($similarity, 2),
'detection_time' => round($detectionTime, 4)
]);
}
} catch (Exception $e) {
$this->sendError($e->getMessage());
}
}
// ดึงข้อมูลผู้ใช้ทั้งหมด
public function getAllUsers() {
try {
$page = intval($_GET['page'] ?? 1);
$limit = intval($_GET['limit'] ?? 20);
$search = $_GET['search'] ?? '';
$offset = ($page - 1) * $limit;
$whereClause = "WHERE u.status = 'active'";
$params = [];
if (!empty($search)) {
$whereClause .= " AND (u.name LIKE ? OR u.email LIKE ? OR u.department LIKE ?)";
$searchParam = "%{$search}%";
$params = [$searchParam, $searchParam, $searchParam];
}
// นับจำนวนทั้งหมด
$countStmt = $this->db->prepare("SELECT COUNT(*) as total FROM users u {$whereClause}");
$countStmt->execute($params);
$total = $countStmt->fetch()['total'];
// ดึงข้อมูล
$stmt = $this->db->prepare("
SELECT u.*,
COUNT(f.id) as face_count,
MAX(f.quality_score) as best_quality
FROM users u
LEFT JOIN face_descriptors f ON u.id = f.user_id
{$whereClause}
GROUP BY u.id
ORDER BY u.created_at DESC
LIMIT {$limit} OFFSET {$offset}
");
$stmt->execute($params);
$users = $stmt->fetchAll();
$this->sendSuccess([
'users' => $users,
'pagination' => [
'page' => $page,
'limit' => $limit,
'total' => $total,
'pages' => ceil($total / $limit)
]
]);
} catch (Exception $e) {
$this->sendError($e->getMessage());
}
}
// ดึงข้อมูลสถิติ
public function getStatistics() {
try {
$period = $_GET['period'] ?? '30'; // days
// สถิติการใช้งาน
$stmt = $this->db->prepare("
SELECT
DATE(timestamp) as date,
COUNT(*) as total_checks,
COUNT(CASE WHEN match_result = 1 THEN 1 END) as successful_matches,
AVG(similarity_score) as avg_similarity,
AVG(detection_time) as avg_detection_time
FROM face_logs
WHERE timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY)
GROUP BY DATE(timestamp)
ORDER BY date DESC
");
$stmt->execute([$period]);
$dailyStats = $stmt->fetchAll();
// สถิติผู้ใช้
$stmt = $this->db->query("
SELECT
COUNT(*) as total_users,
COUNT(CASE WHEN status = 'active' THEN 1 END) as active_users
FROM users
");
$userStats = $stmt->fetch();
// ผู้ใช้ที่เข้าใช้งานล่าสุด
$stmt = $this->db->prepare("
SELECT u.name, u.department, l.timestamp, l.similarity_score
FROM face_logs l
JOIN users u ON l.user_id = u.id
WHERE l.match_result = 1
ORDER BY l.timestamp DESC
LIMIT 10
");
$stmt->execute();
$recentMatches = $stmt->fetchAll();
$this->sendSuccess([
'daily_statistics' => $dailyStats,
'user_statistics' => $userStats,
'recent_matches' => $recentMatches
]);
} catch (Exception $e) {
$this->sendError($e->getMessage());
}
}
// ฟังก์ชันช่วยเหลือ
private function validateInput($input) {
return trim(htmlspecialchars($input, ENT_QUOTES, 'UTF-8'));
}
private function validateEmail($email) {
$email = filter_var($email, FILTER_VALIDATE_EMAIL);
if (!$email) {
throw new Exception('Invalid email format');
}
return $email;
}
private function getUserByEmail($email) {
$stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$email]);
return $stmt->fetch();
}
private function uploadImage() {
if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
return null;
}
$file = $_FILES['image'];
// ตรวจสอบขนาดไฟล์
if ($file['size'] > $this->maxFileSize) {
throw new Exception('File size too large');
}
// ตรวจสอบนามสกุลไฟล์
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowedExtensions)) {
throw new Exception('Invalid file type');
}
// สร้างชื่อไฟล์ใหม่
$fileName = uniqid() . '_' . date('Y-m-d') . '.' . $extension;
$uploadPath = $this->uploadDir . $fileName;
if (move_uploaded_file($file['tmp_name'], $uploadPath)) {
return $uploadPath;
}
throw new Exception('Failed to upload image');
}
private function calculateEuclideanDistance($desc1, $desc2) {
if (count($desc1) !== count($desc2)) {
return PHP_FLOAT_MAX;
}
$sum = 0;
for ($i = 0; $i < count($desc1); $i++) {
$sum += pow($desc1[$i] - $desc2[$i], 2);
}
return sqrt($sum);
}
private function calculateDescriptorQuality($descriptor) {
// คำนวณคุณภาพจากความแปรปรวนของ descriptor
$mean = array_sum($descriptor) / count($descriptor);
$variance = 0;
foreach ($descriptor as $value) {
$variance += pow($value - $mean, 2);
}
$variance /= count($descriptor);
// แปลงเป็นคะแนน 0-100
return min(100, $variance * 1000);
}
private function logComparison($userId, $similarity, $matchResult, $detectionTime) {
$stmt = $this->db->prepare("
INSERT INTO face_logs
(user_id, similarity_score, match_result, detection_time, ip_address, user_agent)
VALUES (?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$userId,
$similarity,
$matchResult,
$detectionTime,
$_SERVER['REMOTE_ADDR'] ?? '',
$_SERVER['HTTP_USER_AGENT'] ?? ''
]);
}
private function logUserAccess($userId, $accessType, $success = true) {
$stmt = $this->db->prepare("
INSERT INTO access_logs
(user_id, access_type, success, ip_address)
VALUES (?, ?, ?, ?)
");
$stmt->execute([
$userId,
$accessType,
$success,
$_SERVER['REMOTE_ADDR'] ?? ''
]);
}
private function sendSuccess($data) {
echo json_encode(['success' => true, 'data' => $data]);
exit();
}
private function sendError($message, $code = 500) {
http_response_code($code);
echo json_encode(['error' => $message]);
exit();
}
}
// เริ่มต้นการทำงาน
try {
$api = new FaceRecognitionAPI();
$api->handleRequest();
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Internal server error']);
}
?>
```
---
## 🌐 Frontend JavaScript Integration
สร้างไฟล์ `database.js`:
```javascript
// คลาสสำหรับจัดการการติดต่อกับ Database API
class FaceDatabase {
constructor(apiUrl = 'api.php') {
this.apiUrl = apiUrl;
this.cache = new Map();
this.cacheTimeout = 5 * 60 * 1000; // 5 minutes
}
// ลงทะเบียนใบหน้าใหม่
async registerFace(userData, descriptor, imageFile) {
const formData = new FormData();
formData.append('action', 'register_face');
formData.append('name', userData.name);
formData.append('email', userData.email);
formData.append('phone', userData.phone || '');
formData.append('department', userData.department || '');
formData.append('position', userData.position || '');
formData.append('descriptor', JSON.stringify(Array.from(descriptor)));
if (imageFile) {
formData.append('image', imageFile);
}
try {
const response = await fetch(this.apiUrl, {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
this.clearCache();
return result.data;
} else {
throw new Error(result.error);
}
} catch (error) {
console.error('Registration error:', error);
throw error;
}
}
// ค้นหาใบหน้าที่ตรงกัน
async findMatch(descriptor, threshold = 0.6) {
try {
const response = await fetch(this.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'find_match',
descriptor: JSON.stringify(Array.from(descriptor)),
threshold: threshold.toString()
})
});
const result = await response.json();
if (result.success) {
return result.data;
} else {
throw new Error(result.error);
}
} catch (error) {
console.error('Match finding error:', error);
throw error;
}
}
// ดึงรายชื่อผู้ใช้ทั้งหมด
async getAllUsers(page = 1, limit = 20, search = '') {
const cacheKey = `users_${page}_${limit}_${search}`;
// ตรวจสอบ cache
if (this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.data;
}
}
try {
const params = new URLSearchParams({
action: 'get_all_users',
page: page.toString(),
limit: limit.toString(),
search: search
});
const response = await fetch(`${this.apiUrl}?${params}`);
const result = await response.json();
if (result.success) {
// บันทึกใน cache
this.cache.set(cacheKey, {
data: result.data,
timestamp: Date.now()
});
return result.data;
} else {
throw new Error(result.error);
}
} catch (error) {
console.error('Error fetching users:', error);
throw error;
}
}
// ดึงสถิติการใช้งาน
async getStatistics(period = 30) {
try {
const params = new URLSearchParams({
action: 'get_statistics',
period: period.toString()
});
const response = await fetch(`${this.apiUrl}?${params}`);
const result = await response.json();
if (result.success) {
return result.data;
} else {
throw new Error(result.error);
}
} catch (error) {
console.error('Error fetching statistics:', error);
throw error;
}
}
// ล้าง cache
clearCache() {
this.cache.clear();
}
// ตรวจสอบสถานะการเชื่อมต่อ
async checkConnection() {
try {
const response = await fetch(this.apiUrl + '?action=ping');
return response.ok;
} catch (error) {
return false;
}
}
}
// ปรับปรุง Face Recognition App ให้ใช้งาน Database
class DatabaseFaceRecognition extends FaceRecognitionApp {
constructor() {
super();
this.database = new FaceDatabase();
this.isDbMode = true;
this.setupDatabaseUI();
}
setupDatabaseUI() {
// เพิ่ม UI สำหรับลงทะเบียน
this.createRegistrationForm();
this.createUserList();
this.createStatisticsPanel();
}
createRegistrationForm() {
const registrationHTML = `
<div class="panel" id="registration-panel" style="display: none;">
<h2>👤 ลงทะเบียนใบหน้าใหม่</h2>
<form id="registration-form">
<input type="text" id="reg-name" placeholder="ชื่อ-นามสกุล" required>
<input type="email" id="reg-email" placeholder="อีเมล" required>
<input type="tel" id="reg-phone" placeholder="เบอร์โทรศัพท์">
<input type="text" id="reg-department" placeholder="แผนก">
<input type="text" id="reg-position" placeholder="ตำแหน่ง">
<button type="button" onclick="this.registerCurrentFace()">บันทึกข้อมูล</button>
</form>
</div>
`;
document.querySelector('.container').insertAdjacentHTML('beforeend', registrationHTML);
}
async registerCurrentFace() {
if (!this.referenceDescriptor) {
this.showError('กรุณาอัปโหลดรูปภาพก่อนลงทะเบียน');
return;
}
const formData = {
name: document.getElementById('reg-name').value,
email: document.getElementById('reg-email').value,
phone: document.getElementById('reg-phone').value,
department: document.getElementById('reg-department').value,
position: document.getElementById('reg-position').value
};
if (!formData.name || !formData.email) {
this.showError('กรุณากรอกข้อมูลที่จำเป็น');
return;
}
try {
this.showLoading('กำลังบันทึกข้อมูล...');
const result = await this.database.registerFace(
formData,
this.referenceDescriptor,
this.currentImageFile
);
this.showResult(`✅ ลงทะเบียนสำเร็จ! คุณภาพ: ${result.quality_score.toFixed(1)}%`);
document.getElementById('registration-form').reset();
} catch (error) {
this.showError(`ไม่สามารถลงทะเบียนได้: ${error.message}`);
}
}
// ปรับฟังก์ชันการเปรียบเทียบให้ใช้ Database
async performDatabaseComparison() {
if (!this.isModelLoaded || !this.video) return;
try {
const detection = await faceapi.detectSingleFace(
this.video,
new faceapi.TinyFaceDetectorOptions()
).withFaceLandmarks().withFaceDescriptor();
if (detection) {
// ค้นหาในฐานข้อมูล
const matchResult = await this.database.findMatch(detection.descriptor, 0.6);
if (matchResult.match) {
const user = matchResult.user;
this.displayMatchResult({
match: true,
user: user,
similarity: matchResult.similarity,
detectionTime: matchResult.detection_time,
qualityScore: matchResult.quality_score
});
} else {
this.displayMatchResult({
match: false,
similarity: matchResult.similarity,
detectionTime: matchResult.detection_time
});
}
// วาดกรอบ
this.drawBoxOnVideo(detection);
} else {
this.clearMatchResult();
this.clearVideoCanvas();
}
} catch (error) {
console.error('Database comparison error:', error);
this.showError('เกิดข้อผิดพลาดในการเปรียบเทียบ');
}
}
displayMatchResult(result) {
if (result.match) {
this.resultsContainer.innerHTML = `
<div class="result-item match-success">
<div class="user-info">
<h3>🟢 พบข้อมูลในระบบ</h3>
<p><strong>ชื่อ:</strong> ${result.user.name}</p>
<p><strong>แผนก:</strong> ${result.user.department || 'ไม่ระบุ'}</p>
<p><strong>ตำแหน่ง:</strong> ${result.user.position || 'ไม่ระบุ'}</p>
<p><strong>อีเมล:</strong> ${result.user.email}</p>
</div>
<div class="match-stats">
<p><strong>ความแม่นยำ:</strong> ${result.similarity.toFixed(1)}%</p>
<p><strong>เวลาตรวจสอบ:</strong> ${(result.detectionTime * 1000).toFixed(0)}ms</p>
<p><strong>คุณภาพ:</strong> ${result.qualityScore?.toFixed(1) || 'N/A'}%</p>
</div>
</div>
`;
} else {
this.resultsContainer.innerHTML = `
<div class="result-item match-fail">
<h3>🔴 ไม่พบข้อมูลในระบบ</h3>
<p><strong>ความคล้ายคลึงสูงสุด:</strong> ${result.similarity.toFixed(1)}%</p>
<p><strong>เวลาตรวจสอบ:</strong> ${(result.detectionTime * 1000).toFixed(0)}ms</p>
<button onclick="app.showRegistrationForm()">ลงทะเบียนใบหน้านี้</button>
</div>
`;
}
}
showRegistrationForm() {
document.getElementById('registration-panel').style.display = 'block';
document.getElementById('reg-name').focus();
}
}
// เริ่มต้นใช้งาน Database Mode
document.addEventListener('DOMContentLoaded', () => {
if (typeof faceapi !== 'undefined') {
window.app = new DatabaseFaceRecognition();
} else {
setTimeout(() => {
window.app = new DatabaseFaceRecognition();
}, 1000);
}
});
```
---
## 📱 Admin Dashboard
สร้างไฟล์ `admin.html`:
```html
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Face Recognition - Admin Dashboard</title>
<style>
/* Admin-specific styles */
.dashboard {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 2rem;
margin: 2rem 0;
}
.stat-card {
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
text-align: center;
}
.stat-number {
font-size: 3rem;
font-weight: bold;
color: #667eea;
}
.user-table {
width: 100%;
border-collapse: collapse;
margin: 2rem 0;
}
.user-table th,
.user-table td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #eee;
}
.user-table th {
background: #f8f9fa;
font-weight: 600;
}
.chart-container {
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
margin: 2rem 0;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="container">
<h1>🎛️ Face Recognition Admin Dashboard</h1>
<!-- Statistics Cards -->
<div class="dashboard">
<div class="stat-card">
<div class="stat-number" id="total-users">0</div>
<div>จำนวนผู้ใช้ทั้งหมด</div>
</div>
<div class="stat-card">
<div class="stat-number" id="today-checks">0</div>
<div>การตรวจสอบวันนี้</div>
</div>
<div class="stat-card">
<div class="stat-number" id="success-rate">0%</div>
<div>อัตราความสำเร็จ</div>
</div>
</div>
<!-- Charts -->
<div class="chart-container">
<h2>📊 สถิติการใช้งานรายวัน</h2>
<canvas id="usageChart"></canvas>
</div>
<!-- User Management -->
<div class="panel">
<h2>👥 จัดการผู้ใช้</h2>
<div class="search-controls">
<input type="text" id="user-search" placeholder="ค้นหาผู้ใช้...">
<button onclick="loadUsers()">ค้นหา</button>
</div>
<table class="user-table" id="users-table">
<thead>
<tr>
<th>ชื่อ</th>
<th>อีเมล</th>
<th>แผนก</th>
<th>ตำแหน่ง</th>
<th>วันที่ลงทะเบียน</th>
<th>การจัดการ</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<script src="database.js"></script>
<script>
// Admin Dashboard Logic
class AdminDashboard {
constructor() {
this.database = new FaceDatabase();
this.init();
}
async init() {
await this.loadStatistics();
await this.loadUsers();
this.setupChart();
}
async loadStatistics() {
try {
const stats = await this.database.getStatistics(30);
document.getElementById('total-users').textContent =
stats.user_statistics.total_users;
const todayStats = stats.daily_statistics[0];
if (todayStats) {
document.getElementById('today-checks').textContent =
todayStats.total_checks;
document.getElementById('success-rate').textContent =
`${((todayStats.successful_matches / todayStats.total_checks) * 100).toFixed(1)}%`;
}
this.updateChart(stats.daily_statistics);
} catch (error) {
console.error('Error loading statistics:', error);
}
}
async loadUsers() {
try {
const search = document.getElementById('user-search').value;
const result = await this.database.getAllUsers(1, 50, search);
const tbody = document.querySelector('#users-table tbody');
tbody.innerHTML = '';
result.users.forEach(user => {
const row = tbody.insertRow();
row.innerHTML = `
<td>${user.name}</td>
<td>${user.email}</td>
<td>${user.department || '-'}</td>
<td>${user.position || '-'}</td>
<td>${new Date(user.created_at).toLocaleDateString('th-TH')}</td>
<td>
<button onclick="editUser(${user.id})">แก้ไข</button>
<button onclick="deleteUser(${user.id})">ลบ</button>
</td>
`;
});
} catch (error) {
console.error('Error loading users:', error);
}
}
setupChart() {
const ctx = document.getElementById('usageChart').getContext('2d');
this.chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'การตรวจสอบ',
data: [],
borderColor: '#667eea',
backgroundColor: 'rgba(102, 126, 234, 0.1)',
tension: 0.4
}, {
label: 'ตรงกัน',
data: [],
borderColor: '#06d6a0',
backgroundColor: 'rgba(6, 214, 160, 0.1)',
tension: 0.4
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
updateChart(data) {
const labels = data.map(d => new Date(d.date).toLocaleDateString('th-TH'));
const checks = data.map(d => d.total_checks);
const matches = data.map(d => d.successful_matches);
this.chart.data.labels = labels.reverse();
this.chart.data.datasets[0].data = checks.reverse();
this.chart.data.datasets[1].data = matches.reverse();
this.chart.update();
}
}
// เริ่มต้น Admin Dashboard
document.addEventListener('DOMContentLoaded', () => {
window.admin = new AdminDashboard();
});
// ฟังก์ชันจัดการผู้ใช้
function editUser(userId) {
// TODO: Implement user editing
alert(`แก้ไขผู้ใช้ ID: ${userId}`);
}
function deleteUser(userId) {
if (confirm('คุณแน่ใจหรือไม่ที่จะลบผู้ใช้นี้?')) {
// TODO: Implement user deletion
alert(`ลบผู้ใช้ ID: ${userId}`);
}
}
function loadUsers() {
admin.loadUsers();
}
</script>
</body>
</html>
```
---
## 🔒 ข้อควรพิจารณาด้านความปลอดภัย
### 1. การเข้ารหัสข้อมูล
```php
// เข้ารหัส Face Descriptor
function encryptDescriptor($data, $key) {
$cipher = "AES-256-CBC";
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$encrypted = openssl_encrypt($data, $cipher, $key, 0, $iv);
return base64_encode($encrypted . '::' . $iv);
}
function decryptDescriptor($data, $key) {
$cipher = "AES-256-CBC";
list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
return openssl_decrypt($encrypted_data, $cipher, $key, 0, $iv);
}
```
### 2. การตรวจสอบสิทธิ์
```php
// JWT Token Validation
function validateToken($token) {
try {
$decoded = JWT::decode($token, $secret_key, ['HS256']);
return $decoded;
} catch (Exception $e) {
return false;
}
}
```
### 3. Rate Limiting
```php
// Simple rate limiting
function checkRateLimit($ip, $action, $limit = 10, $window = 60) {
// Implementation for rate limiting
// Store in Redis or database
}
```
---
## 📈 การปรับปรุงประสิทธิภาพ
### 1. Database Indexing
```sql
-- Composite indexes for faster searches
CREATE INDEX idx_face_user_quality ON face_descriptors(user_id, quality_score DESC);
CREATE INDEX idx_logs_date_result ON face_logs(DATE(timestamp), match_result);
```
### 2. Query Optimization
```sql
-- Use EXPLAIN to analyze query performance
EXPLAIN SELECT u.name, f.descriptor_data
FROM users u
JOIN face_descriptors f ON u.id = f.user_id
WHERE u.status = 'active';
```
### 3. Caching Strategy
```javascript
// Client-side caching with expiration
class CacheManager {
constructor(defaultTTL = 300000) { // 5 minutes
this.cache = new Map();
this.defaultTTL = defaultTTL;
}
set(key, value, ttl = this.defaultTTL) {
this.cache.set(key, {
value,
expires: Date.now() + ttl
});
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expires) {
this.cache.delete(key);
return null;
}
return item.value;
}
}
```
---
## 🚀 การ Deploy Production
### 1. ข้อกำหนด Server
- PHP 7.4+ with MySQLi/PDO
- MySQL 5.7+ หรือ MariaDB 10.3+
- SSL Certificate (HTTPS)
- Sufficient storage for face images
### 2. Configuration
```php
// config.php
return [
'database' => [
'host' => $_ENV['DB_HOST'] ?? 'localhost',
'name' => $_ENV['DB_NAME'] ?? 'face_recognition',
'user' => $_ENV['DB_USER'] ?? 'root',
'pass' => $_ENV['DB_PASS'] ?? '',
],
'upload' => [
'max_size' => 5 * 1024 * 1024, // 5MB
'allowed_types' => ['jpg', 'jpeg', 'png'],
'path' => '/var/www/uploads/faces/',
],
'security' => [
'jwt_secret' => $_ENV['JWT_SECRET'] ?? 'your-secret-key',
'encrypt_key' => $_ENV['ENCRYPT_KEY'] ?? 'your-encrypt-key',
]
];
```
### 3. Backup Strategy
```bash
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
mysqldump -u$DB_USER -p$DB_PASS face_recognition > backup_$DATE.sql
tar -czf images_backup_$DATE.tar.gz uploads/faces/
```
---
## 📞 การสนับสนุน
สำหรับคำถามเกี่ยวกับการติดตั้งและใช้งาน:
- 📧 Email: admin@goragod.com
- 🌐 Website: https://goragod.com
---
**© 2568 Goragod Wiriya - Face Recognition Database Integration Guide**