hocvietcode.com
  • Trang chủ
  • Học lập trình
    • Lập trình C/C++
    • Cấu trúc dữ liệu và giải thuật
    • Lập trình HTML
    • Lập trình Javascript
      • Javascript cơ bản
      • ReactJS framework
      • AngularJS framework
      • Typescript cơ bản
      • Angular
    • Lập trình Mobile
      • Lập Trình Dart Cơ Bản
        • Dart Flutter Framework
    • Cơ sở dữ liệu
      • MySQL – MariaDB
      • Micrsoft SQL Server
      • Extensible Markup Language (XML)
      • JSON
    • Lập trình PHP
      • Lập trình PHP cơ bản
      • Laravel Framework
    • Lập trình Java
      • Java Cơ bản
    • Lập trình C#
      • Lập Trình C# Cơ Bản
      • ASP.NET Core MVC
    • Machine Learning
  • WORDPRESS
    • WordPress cơ bản
    • WordPress nâng cao
    • Chia sẻ WordPress
  • Kiến thức hệ thống
    • Microsoft Azure
    • Docker
    • Linux
  • Chia sẻ IT
    • Tin học văn phòng
      • Microsoft Word
      • Microsoft Excel
    • Marketing
      • Google Adwords
      • Facebook Ads
      • Kiến thức khác
    • Chia sẻ phần mềm
    • Review công nghệ
    • Công cụ – tiện ích
      • Kiểm tra bàn phím online
      • Kiểm tra webcam online
Đăng nhập
  • Đăng nhập / Đăng ký

Please enter key search to display results.

Home
  • Cyber Panel
Hướng dẫn tùy chỉnh phpmyadmin fix lỗi export Database 30MB Cyberpanel

Hướng dẫn tùy chỉnh phpmyadmin fix lỗi export Database 30MB Cyberpanel

  • 27-07-2025
  • Toanngo92
  • 0 Comments

Với tính năng của phpmyadmin trong cyberpanel, khi xuất file database thông qua nút export, sẽ gặp vấn đề khi kích thước tệp lớn hơn 30MB, tệp sẽ bị tự động cắt với lỗi trả ra là: The dynamic response body size is over the limit, the response will be truncated by the web server. The limit is set in the key ‘maxDynRespSize’ located in the tuning section of the server configuration, and labeled ‘max dynamic response body size. Mặc dù khi bạn tăng cấu hình này trong onelitespeed, lỗi vẫn không được khắc phục. Sau khi tìm hiểu trên cộng đồng cũng như hỏi AI, giải pháp cuối cùng mình lựa chọn là code thêm cho phpmyadmin để đáp ứng tính năng, chờ nhà phát hành vá lỗi.

Giải pháp tổng thể như sau:

  1. Tạo worker chạy liên tục để job dump sql ra thư mục backups
  2. Tùy chỉnh thêm 1 nút backup trong phpmyadmin và đẩy sự kiện cho worker thực thi
  3. Tạo tệp lắng nghe trạng thái worker
  4. Tạo tệp download cho phép tải CSDL đã backup từ worker.

Nội dung tệp backup_worker.sh:

#!/bin/bash
while true; do
  for file in /var/backups/queue/*.txt; do
    [ -f "$file" ] || continue
    DB=$(cat "$file")
    
    # Validate database name (security check)
    if [[ ! "$DB" =~ ^[a-zA-Z0-9_]+$ ]]; then
      echo "{\"error\":\"Invalid database name: $DB\",\"failed_at\":\"$(date '+%Y-%m-%d %H:%M:%S')\"}" > "$file.fail"
      rm "$file"
      continue
    fi
    
    OUT="/usr/local/CyberCP/public/phpmyadmin/backups/${DB}_$(date '+%Y-%m-%d_%H-%M-%S').sql"
    OUT_ZIP="/usr/local/CyberCP/public/phpmyadmin/backups/${DB}_$(date '+%Y-%m-%d_%H-%M-%S').sql.zip"
    # Thực hiện backup và kiểm tra kết quả
    if mysqldump --defaults-extra-file=/root/.my.cnf "$DB" > "$OUT" && \
zip "${OUT}.zip" "$OUT" && rm "$OUT"; then
      # Kiểm tra file backup đã được tạo và có kích thước > 0
      if [ -f "$OUT_ZIP" ] && [ -s "$OUT_ZIP" ]; then
        # Backup thành công: tạo file .done với thông tin đường dẫn file
        cat > "$file.done" << EOF
{"file":"$OUT_ZIP","database":"$DB","completed_at":"$(date '+%Y-%m-%d %H:%M:%S')","size":$(stat -f%z "$OUT" 2>/dev/null || stat -c%s "$OUT" 2>/dev/null || echo "0")}
EOF
      else
        # File backup rỗng hoặc không tồn tại
        echo "{\"error\":\"Backup file is empty or not created for database: $DB\",\"failed_at\":\"$(date '+%Y-%m-%d %H:%M:%S')\"}" > "$file.fail"
        [ -f "$OUT_ZIP" ] && rm "$OUT_ZIP"
      fi
    else
      # Backup thất bại: tạo file .fail với thông tin lỗi
      echo "{\"error\":\"mysqldump failed for database: $DB\",\"failed_at\":\"$(date '+%Y-%m-%d %H:%M:%S')\"}" > "$file.fail"
      # Xóa file backup không hoàn chỉnh (nếu có)
      [ -f "$OUT" ] && rm "$OUT"
    fi
    
    # Xóa file job gốc
    rm "$file"
  done
  sleep 10
done

Thêm tệp config.header.inc.php tại thư mục gốc phpmyadmin:

<?php
if (!defined('PHPMYADMIN')) exit;
?>
<script>
    document.addEventListener('DOMContentLoaded', function() {

        /** ================= helpers ================= **/
        function getDbName() {
            // Ưu tiên API nội bộ của phpMyAdmin nếu có
            if (typeof PMA_commonParams !== 'undefined' && PMA_commonParams.get) {
                return PMA_commonParams.get('db') || '';
            }
            const params = new URLSearchParams(location.search);
            return params.get('db') || '';
        }

        const API = {
            start: (db) => 'custom/quick-backup.php?db=' + encodeURIComponent(db),
            status: (jobId) => 'custom/backup-status.php?job_id=' + encodeURIComponent(jobId)
        };

        function createBtn(dbName) {
            const btn = document.createElement('button');
            btn.type = 'button';
            btn.id = 'quick-backup-btn';
            btn.dataset.db = dbName;
            btn.textContent = 'Quick Backup';
            btn.className = 'btn btn-success';
            btn.style.marginLeft = '10px';
            return btn;
        }

        function downloadViaIframe(url) {
            const iframe = document.createElement('iframe');
            iframe.style.display = 'none';
            iframe.src = url;
            document.body.appendChild(iframe);
            // có thể remove sau vài giây nếu muốn
            setTimeout(() => iframe.remove(), 60000);
        }

        function injectQuickBackupButton() {
            if (!location.href.includes('db=')) return;

            const dbName = getDbName();

            // Nếu đã có, chỉ cập nhật lại db
            let btn = document.getElementById('quick-backup-btn');
            if (btn) {
                btn.dataset.db = dbName;
                return;
            }

            btn = createBtn(dbName);

            const exportBtn = document.querySelector('form[name="dump"] input[type=submit]');
            if (exportBtn) exportBtn.insertAdjacentElement('afterend', btn);

            btn.addEventListener('click', async function() {
                const db = btn.dataset.db || getDbName();
                if (!db) {
                    alert('Không lấy được tên database.');
                    return;
                }

                btn.disabled = true;
                const oldText = btn.textContent;
                btn.textContent = 'Starting backup…';

                try {
                    // 1) Gửi yêu cầu tạo job
                    const startRes = await fetch(API.start(db), {
                        method: 'GET'
                    });
                    const startJson = await startRes.json();

                    if (!startJson.success) {
                        throw new Error(startJson.message || startJson.error || 'Cannot start job');
                    }

                    const jobId = startJson.job_id;
                    btn.textContent = 'Backing up… (Job: ' + jobId + ')';

                    // 2) Poll status cho tới khi done/failed
                    await pollUntilDone(jobId, function onDone(downloadUrl) {
                        // get current path
                        const currentPath = location.pathname;
                        const host = location.host;
                        console.log('Current path:', currentPath);
                        console.log('Host:', host);
                        let download = new URL(currentPath, 'https://' + host);
                        download.href = download.href.replace('/index.php', downloadUrl);
                        // window.open(download.href, '_blank');
                        downloadViaIframe(download.href);
                    });

                    btn.textContent = 'Done! Downloading…';
                } catch (e) {
                    console.error(e);
                    alert('Backup failed: ' + e.message);
                    btn.textContent = oldText;
                } finally {
                    btn.disabled = false;
                    setTimeout(() => (btn.textContent = oldText), 3000);
                }
            });
        }

        async function pollUntilDone(jobId, onDone) {
            const maxAttempts = 90; // ~3 phút nếu interval = 2000ms
            const intervalMs = 2000;

            for (let i = 0; i < maxAttempts; i++) {
                const stRes = await fetch(API.status(jobId));
                const stJson = await stRes.json();

                if (stJson.status === 'done' && stJson.download_url) {
                    onDone(stJson.download_url);
                    return;
                }

                if (stJson.status === 'failed') {
                    throw new Error(stJson.message || 'Worker failed');
                }

                // pending
                await sleep(intervalMs);
            }

            throw new Error('Timeout: backup still pending.');
        }

        function sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }

        /** ================= boot ================= **/
        injectQuickBackupButton();

        // Lắng nghe thay đổi form / render lại bởi AJAX
        document.addEventListener('change', function(e) {
            if (
                e.target.matches('select[name="db"]') ||
                e.target.closest('form[name="dump"]')
            ) {
                injectQuickBackupButton();
            }
        });

        const observer = new MutationObserver(() => {
            if (location.href.includes('db=')) injectQuickBackupButton();
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
</script>

Tạo thư mục custom, bên trong chứa 2 tệp:

  • backup-status.php
  • download.php

Nội dung tệp backup-status.php:

<?php
// custom/backup-status.php
header('Content-Type: application/json; charset=utf-8');

$jobId = $_GET['job_id'] ?? '';
if (!$jobId) {
    http_response_code(400);
    echo json_encode(['success' => false, 'message' => 'Missing job_id']);
    exit;
}

// Validate job_id format to prevent directory traversal
if (!preg_match('/^job_[a-zA-Z0-9_.]+$/', $jobId)) {
    http_response_code(400);
    echo json_encode(['success' => false, 'message' => 'Invalid job_id format']);
    exit;
}

$queueDir = '/var/backups/queue';
$queueFile = "$queueDir/$jobId.txt";
$done = "$queueDir/$jobId.txt.done";
$fail = "$queueDir/$jobId.txt.fail";

// Check if job completed successfully
if (file_exists($done)) {
    $jsonContent = file_get_contents($done);
    if ($jsonContent === false) {
        echo json_encode(['success' => false, 'status' => 'error', 'message' => 'Cannot read done file']);
        exit;
    }
    
    // Trim whitespace and check if it's just a database name (old format)
    $trimmedContent = trim($jsonContent);
    if (!str_starts_with($trimmedContent, '{')) {
        // Old format: file contains just database name
        // Try to find the backup file by database name pattern
        $dbName = $trimmedContent;
        $backupDir = '/var/backups/mysql';
        $pattern = $backupDir . '/' . $dbName . '_*.sql.zip';
        $files = glob($pattern);
        
        if (!empty($files)) {
            // Get the most recent backup file
            $latestFile = end($files);
            $downloadUrl = '/custom/download.php?f=' . urlencode(basename($latestFile));
            echo json_encode([
                'success' => true, 
                'status' => 'done', 
                'download_url' => $downloadUrl,
                'database' => $dbName,
                'completed_at' => date('Y-m-d H:i:s', filemtime($latestFile)),
                'file_size' => filesize($latestFile),
                'note' => 'Legacy format detected'
            ]);
            exit;
        } else {
            echo json_encode([
                'success' => false, 
                'status' => 'error', 
                'message' => 'Done file is in old format but no backup found for database: ' . $dbName
            ]);
            exit;
        }
    }
    
    $data = json_decode($jsonContent, true);
    if ($data === null) {
        echo json_encode([
            'success' => false, 
            'status' => 'error', 
            'message' => 'Invalid JSON in done file', 
            'json_error' => json_last_error_msg(),
            'raw_content' => substr($jsonContent, 0, 200) // First 200 chars for debugging
        ]);
        exit;
    }
    
    $file = $data['file'] ?? '';
    if (!$file) {
        echo json_encode(['success' => false, 'status' => 'error', 'message' => 'No file path in done file']);
        exit;
    }
    
    if (!file_exists($file)) {
        echo json_encode(['success' => false, 'status' => 'error', 'message' => 'Backup file not found: ' . $file]);
        exit;
    }
        // $downloadUrl = '/custom/' . urlencode(basename($file));
    $downloadUrl = '/custom/download.php?f=' . urlencode(basename($file));
    echo json_encode([
        'success' => true, 
        'status' => 'done', 
        'download_url' => $downloadUrl,
        'database' => $data['database'] ?? '',
        'completed_at' => $data['completed_at'] ?? '',
        'file_size' => $data['size'] ?? (file_exists($file) ? filesize($file) : 0)
    ]);
    exit;
}

// Check if job failed
if (file_exists($fail)) {
    $jsonContent = file_get_contents($fail);
    $data = json_decode($jsonContent, true);
    
    if ($data === null) {
        echo json_encode([
            'success' => false, 
            'status' => 'failed', 
            'message' => 'Backup failed (invalid error format)',
            'raw_error' => substr($jsonContent, 0, 200)
        ]);
        exit;
    }
    
    echo json_encode([
        'success' => false, 
        'status' => 'failed', 
        'message' => $data['error'] ?? 'Unknown error',
        'failed_at' => $data['failed_at'] ?? ''
    ]);
    exit;
}

// Check if job still in queue (pending)
if (file_exists($queueFile)) {
    echo json_encode(['success' => true, 'status' => 'pending']);
    exit;
}

// Job not found
echo json_encode(['success' => false, 'status' => 'not_found', 'message' => 'Job not found']);

Nội dung tệp download.php:

<?php
// custom/download.php
// $backupDir = '/var/backups/mysql';
// $f = $_GET['f'] ?? '';
// $path = realpath($backupDir . '/' . $f);

// if (!$f || !$path || strpos($path, realpath($backupDir)) !== 0 || !is_file($path)) {
//     http_response_code(404);
//     exit('File not found');
// }

// // header('Content-Type: application/gzip');
// header('Content-Type: application/octet-stream');
// header('Content-Disposition: attachment; filename="' . basename($path) . '"');
// header('Content-Length: ' . filesize($path));
// readfile($path);
// exit;

// $backupDir = '/var/backups/mysql';
$backupDir = '/usr/local/CyberCP/public/phpmyadmin/backups';
$f   = $_GET['f'] ?? '';
$path = realpath($backupDir . '/' . $f);

if (!$f || !$path || strpos($path, realpath($backupDir)) !== 0 || !is_file($path)) {
    http_response_code(404);
    header('Content-Type: text/plain; charset=utf-8');
    echo "File not found: " . htmlspecialchars($f);
    exit;
}

set_time_limit(0);
header('Cache-Control: no-cache, must-revalidate');
header('Expires: 0');
header('X-Content-Type-Options: nosniff');
header('Content-Type: application/zip'); // hoặc octet-stream nếu không chắc loại
header('Content-Disposition: attachment; filename="' . basename($path) . '"');
header('Content-Length: ' . filesize($path));
readfile($path);
exit;

Tạo thư mục backups trong thư mục gốc phpmyadmin, cùng cấp với thư mục custom để lưu trữ database đã dump. Chúc bạn thành công !

Bài viết liên quan:

Hướng dẫn tự cài đặt n8n comunity trên CyberPanel, trỏ tên miền
Hướng dẫn cài đặt Ubuntu Server cho máy tính cá nhân, laptop và setup NAT làm Home Server host website
Hướng dẫn cài đặt và sử dụng Cyber Panel cho Linux Ubuntu

THÊM BÌNH LUẬN Cancel reply

Dịch vụ thiết kế Wesbite

NỘI DUNG MỚI CẬP NHẬT

Hướng dẫn tùy chỉnh phpmyadmin fix lỗi export Database 30MB Cyberpanel

Làm việc với dữ liệu và các kiểu dữ liệu trong JSON

Giới thiệu về JSON

Truy Vấn Dữ Liệu Với SELECT Trong MySQL

Các Lệnh DML Cơ Bản Trong MySQL: INSERT, UPDATE, DELETE

Giới thiệu

hocvietcode.com là website chia sẻ và cập nhật tin tức công nghệ, chia sẻ kiến thức, kỹ năng. Chúng tôi rất cảm ơn và mong muốn nhận được nhiều phản hồi để có thể phục vụ quý bạn đọc tốt hơn !

Liên hệ quảng cáo: [email protected]

Kết nối với HỌC VIẾT CODE

© hocvietcode.com - Tech888 Co .Ltd since 2019

Đăng nhập

Trở thành một phần của cộng đồng của chúng tôi!
Registration complete. Please check your email.
Đăng nhập bằng google
Đăng kýBạn quên mật khẩu?

Create an account

Welcome! Register for an account
The user name or email address is not correct.
Registration confirmation will be emailed to you.
Log in Lost your password?

Reset password

Recover your password
Password reset email has been sent.
The email could not be sent. Possible reason: your host may have disabled the mail function.
A password will be e-mailed to you.
Log in Register
×