DATABASE_GUIDE.md

43.21 KB
15/07/2025 03:25
MD
DATABASE_GUIDE.md
# 💾 คู่มือการจัดเก็บข้อมูลลงฐานข้อมูล
## 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**