下面是一个完整的解决方案,使用 jQuery 在浏览器端解压 GZ 压缩的 SQL 文件,然后分块导入到 MySQL 数据库,并显示进度。

前端部分 (HTML + jQuery)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GZ压缩SQL文件导入工具</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <!-- 引入pako用于GZ解压 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.0.4/pako.min.js"></script>
    <style>
        .container {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            font-family: Arial, sans-serif;
        }
        .progress-container {
            margin: 20px 0;
            background-color: #f5f5f5;
            border-radius: 4px;
            height: 30px;
        }
        .progress-bar {
            height: 100%;
            border-radius: 4px;
            background-color: #4CAF50;
            width: 0%;
            transition: width 0.3s;
            text-align: center;
            color: white;
            line-height: 30px;
        }
        .status {
            margin: 10px 0;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            min-height: 60px;
        }
        .btn {
            background-color: #4CAF50;
            color: white;
            padding: 10px 15px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
        }
        .btn:hover {
            background-color: #45a049;
        }
        .btn:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }
        #fileInfo {
            margin-top: 10px;
            padding: 10px;
            background-color: #f9f9f9;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>GZ压缩SQL文件导入工具</h1>
        
        <div>
            <input type="file" id="gzFile" accept=".gz,.sql.gz">
            <button id="importBtn" class="btn">解压并导入SQL文件</button>
        </div>
        
        <div id="fileInfo" style="display: none;">
            <strong>文件信息:</strong>
            <div>名称: <span id="fileName"></span></div>
            <div>大小: <span id="fileSize"></span></div>
            <div>类型: <span id="fileType"></span></div>
        </div>
        
        <div class="progress-container">
            <div id="progressBar" class="progress-bar">0%</div>
        </div>
        
        <div id="status" class="status">请选择GZ压缩的SQL文件(.gz或.sql.gz)并点击导入按钮</div>
        
        <div id="error" style="color: red;"></div>
    </div>

    <script>
    $(document).ready(function() {
        // 显示文件信息
        $('#gzFile').change(function(e) {
            const file = e.target.files[0];
            if (!file) return;
            
            $('#fileInfo').show();
            $('#fileName').text(file.name);
            $('#fileSize').text((file.size / 1024 / 1024).toFixed(2) + ' MB');
            $('#fileType').text(file.type || 'application/gzip');
        });
        
        $('#importBtn').click(function() {
            const fileInput = $('#gzFile')[0];
            if (fileInput.files.length === 0) {
                $('#error').text('请先选择GZ压缩文件');
                return;
            }
            
            const file = fileInput.files[0];
            
            // 验证文件类型
            if (!file.name.match(/\.(gz|sql\.gz)$/i)) {
                $('#error').text('请选择.gz或.sql.gz格式的文件');
                return;
            }
            
            $('#importBtn').prop('disabled', true);
            $('#status').html('开始处理文件...');
            $('#error').text('');
            $('#progressBar').css('width', '0%').text('0%');
            
            const reader = new FileReader();
            
            reader.onload = function(e) {
                try {
                    // 使用pako解压GZ文件
                    $('#status').html('解压文件中...');
                    
                    const compressedData = new Uint8Array(e.target.result);
                    const decompressedData = pako.inflate(compressedData);
                    const sqlContent = new TextDecoder('utf-8').decode(decompressedData);
                    
                    $('#status').html('文件解压成功,准备导入数据...');
                    
                    // 分割SQL内容为行
                    const lines = sqlContent.split('\n');
                    const totalLines = lines.length;
                    let currentLine = 0;
                    
                    // 分批处理SQL文件(每批50行)
                    const batchSize = 50;
                    let batch = [];
                    let batchNumber = 0;
                    
                    function processBatch() {
                        const startLine = batchNumber * batchSize;
                        const endLine = Math.min(startLine + batchSize, totalLines);
                        batch = lines.slice(startLine, endLine);
                        
                        if (batch.length === 0) {
                            $('#status').append('<br>导入完成!');
                            $('#importBtn').prop('disabled', false);
                            return;
                        }
                        
                        // 计算进度
                        currentLine = endLine;
                        const progress = Math.round((currentLine / totalLines) * 100);
                        $('#progressBar').css('width', progress + '%').text(progress + '%');
                        $('#status').html(`导入进度: ${progress}%<br>已处理 ${currentLine} 行,共 ${totalLines} 行`);
                        
                        // 发送当前批次到服务器
                        $.ajax({
                            url: 'import_sql_chunk.php',
                            type: 'POST',
                            data: {
                                chunk: batch.join('\n'),
                                currentLine: currentLine,
                                totalLines: totalLines,
                                isLastBatch: (endLine >= totalLines)
                            },
                            success: function(response) {
                                try {
                                    const data = JSON.parse(response);
                                    if (data.success) {
                                        batchNumber++;
                                        // 处理下一批
                                        setTimeout(processBatch, 100);
                                    } else {
                                        $('#error').text('导入错误: ' + data.message);
                                        if (data.errors) {
                                            $('#error').append('<br>错误示例: ' + data.errors.join(', '));
                                        }
                                        $('#importBtn').prop('disabled', false);
                                    }
                                } catch (e) {
                                    $('#error').text('解析响应时出错: ' + e);
                                    $('#importBtn').prop('disabled', false);
                                }
                            },
                            error: function(xhr, status, error) {
                                $('#error').text('请求失败: ' + error);
                                $('#importBtn').prop('disabled', false);
                            }
                        });
                    }
                    
                    // 开始处理第一批
                    processBatch();
                    
                } catch (e) {
                    $('#error').text('处理文件时出错: ' + e.message);
                    $('#importBtn').prop('disabled', false);
                    console.error(e);
                }
            };
            
            reader.onerror = function() {
                $('#error').text('读取文件时出错');
                $('#importBtn').prop('disabled', false);
            };
            
            // 以ArrayBuffer形式读取文件,便于pako处理
            reader.readAsArrayBuffer(file);
        });
    });
    </script>
</body>
</html>

后端部分 (PHP)

import_sql_chunk.php

<?php
header('Content-Type: application/json');

// 数据库配置
$dbHost = 'localhost';
$dbUser = 'username';
$dbPass = 'password';
$dbName = 'database_name';

// 获取POST数据
$chunk = isset($_POST['chunk']) ? $_POST['chunk'] : '';
$currentLine = isset($_POST['currentLine']) ? intval($_POST['currentLine']) : 0;
$totalLines = isset($_POST['totalLines']) ? intval($_POST['totalLines']) : 0;
$isLastBatch = isset($_POST['isLastBatch']) ? $_POST['isLastBatch'] === 'true' : false;

if (empty($chunk)) {
    echo json_encode(['success' => false, 'message' => '没有接收到SQL数据']);
    exit;
}

// 连接数据库
$conn = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
if ($conn->connect_error) {
    echo json_encode(['success' => false, 'message' => '数据库连接失败: ' . $conn->connect_error]);
    exit;
}

// 处理SQL块
$queries = explode(';', $chunk);
$successCount = 0;
$errorCount = 0;
$errors = [];

foreach ($queries as $query) {
    $query = trim($query);
    
    // 跳过空查询和注释
    if (empty($query) || substr($query, 0, 2) == '--' || substr($query, 0, 1) == '#') {
        continue;
    }
    
    // 执行SQL
    if ($conn->query($query . ';') === false) {
        $errorCount++;
        $errors[] = $conn->error;
    } else {
        $successCount++;
    }
}

// 关闭连接
$conn->close();

// 返回结果
if ($errorCount > 0) {
    echo json_encode([
        'success' => false,
        'message' => "本批次处理完成 (成功: $successCount, 失败: $errorCount)",
        'errors' => array_slice($errors, 0, 5) // 只返回前5个错误
    ]);
} else {
    echo json_encode([
        'success' => true,
        'message' => "本批次处理完成 (成功: $successCount)",
        'progress' => $currentLine / $totalLines,
        'isLastBatch' => $isLastBatch
    ]);
}
?>

实现说明

  1. 前端处理流程:

    • 用户选择GZ压缩的SQL文件

    • 使用FileReader API读取文件为ArrayBuffer

    • 使用pako.js库在浏览器端解压GZ文件

    • 将解压后的SQL内容分割成多个批次

    • 通过AJAX将每个批次发送到服务器

    • 实时更新进度条和状态信息

  2. 后端处理流程:

    • 接收SQL数据块

    • 分割成单独的SQL语句

    • 执行每条SQL语句

    • 返回处理结果

  3. 关键技术点:

    • 使用pako.js进行浏览器端GZ解压,减少服务器负担

    • 分块处理大文件,避免内存问题

    • 详细的进度反馈

    • 错误处理和报告

  4. 优化建议:

    • 对于超大文件,可以增加更细粒度的进度报告

    • 添加暂停/继续功能

    • 实现断点续传功能

    • 增加导入前的SQL语法快速检查

  5. 安全考虑:

    • 文件类型验证

    • 限制最大文件大小

    • SQL错误信息过滤

    • 使用数据库预处理语句(在实际应用中应添加)

这个解决方案完全在浏览器端处理GZ解压,然后分块发送SQL到服务器执行,既高效又能提供良好的用户体验。