下面是一个完整的解决方案,能够自动判断上传的文件是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>SQL文件导入工具(自动解压)</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <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>SQL文件导入工具(自动解压)</h1>
        
        <div>
            <input type="file" id="sqlFile" accept=".sql,.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>状态: <span id="fileStatus">等待处理</span></div>
        </div>
        
        <div class="progress-container">
            <div id="progressBar" class="progress-bar">0%</div>
        </div>
        
        <div id="status" class="status">请选择SQL文件(.sql)或GZ压缩的SQL文件(.gz/.sql.gz)</div>
        
        <div id="error" style="color: red;"></div>
    </div>

    <script>
    $(document).ready(function() {
        // 显示文件信息
        $('#sqlFile').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 || getFileType(file.name));
            $('#fileStatus').text('等待处理').css('color', 'black');
        });
        
        // 根据文件名获取文件类型
        function getFileType(filename) {
            if (filename.match(/\.sql\.gz$/i)) return 'GZ压缩的SQL文件';
            if (filename.match(/\.gz$/i)) return 'GZ压缩文件';
            if (filename.match(/\.sql$/i)) return 'SQL文件';
            return '未知文件类型';
        }
        
        // 检查是否是GZ文件
        function isGzFile(filename) {
            return filename.match(/\.(gz|sql\.gz)$/i);
        }
        
        $('#importBtn').click(function() {
            const fileInput = $('#sqlFile')[0];
            if (fileInput.files.length === 0) {
                $('#error').text('请先选择文件');
                return;
            }
            
            const file = fileInput.files[0];
            const isGz = isGzFile(file.name);
            
            // 验证文件类型
            if (!file.name.match(/\.(sql|gz|sql\.gz)$/i)) {
                $('#error').text('请选择.sql、.gz或.sql.gz格式的文件');
                return;
            }
            
            $('#importBtn').prop('disabled', true);
            $('#status').html('开始处理文件...');
            $('#error').text('');
            $('#progressBar').css('width', '0%').text('0%');
            $('#fileStatus').text('处理中...').css('color', 'blue');
            
            const reader = new FileReader();
            
            reader.onload = function(e) {
                try {
                    if (isGz) {
                        $('#status').html('检测到GZ压缩文件,正在解压...');
                        $('#fileStatus').text('解压中...');
                        
                        // 使用pako解压GZ文件
                        const compressedData = new Uint8Array(e.target.result);
                        let decompressedData;
                        
                        try {
                            decompressedData = pako.inflate(compressedData);
                        } catch (e) {
                            throw new Error('GZ解压失败: 文件可能已损坏或不是有效的GZ格式');
                        }
                        
                        const sqlContent = new TextDecoder('utf-8').decode(decompressedData);
                        $('#fileStatus').text('解压完成').css('color', 'green');
                        processSqlContent(sqlContent, file.name);
                    } else {
                        $('#status').html('检测到普通SQL文件,准备导入...');
                        $('#fileStatus').text('处理中...');
                        const sqlContent = e.target.result;
                        processSqlContent(sqlContent, file.name);
                    }
                } catch (e) {
                    $('#error').text('处理文件时出错: ' + e.message);
                    $('#importBtn').prop('disabled', false);
                    $('#fileStatus').text('处理失败').css('color', 'red');
                    console.error(e);
                }
            };
            
            reader.onerror = function() {
                $('#error').text('读取文件时出错');
                $('#importBtn').prop('disabled', false);
                $('#fileStatus').text('读取失败').css('color', 'red');
            };
            
            // 根据文件类型选择读取方式
            if (isGz) {
                reader.readAsArrayBuffer(file);
            } else {
                reader.readAsText(file);
            }
        });
        
        // 处理SQL内容
        function processSqlContent(sqlContent, filename) {
            $('#status').html('文件处理成功,准备导入数据...');
            $('#fileStatus').text('准备导入').css('color', 'blue');
            
            // 分割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);
                    $('#fileStatus').text('导入完成').css('color', 'green');
                    return;
                }
                
                // 计算进度
                currentLine = endLine;
                const progress = Math.round((currentLine / totalLines) * 100);
                $('#progressBar').css('width', progress + '%').text(progress + '%');
                $('#status').html(`导入进度: ${progress}%<br>已处理 ${currentLine} 行,共 ${totalLines} 行`);
                $('#fileStatus').text(`导入中 (${progress}%)`).css('color', 'blue');
                
                // 发送当前批次到服务器
                $.ajax({
                    url: 'import_sql_chunk.php',
                    type: 'POST',
                    data: {
                        chunk: batch.join('\n'),
                        currentLine: currentLine,
                        totalLines: totalLines,
                        isLastBatch: (endLine >= totalLines),
                        filename: filename
                    },
                    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);
                                $('#fileStatus').text('导入失败').css('color', 'red');
                            }
                        } catch (e) {
                            $('#error').text('解析响应时出错: ' + e);
                            $('#importBtn').prop('disabled', false);
                            $('#fileStatus').text('导入失败').css('color', 'red');
                        }
                    },
                    error: function(xhr, status, error) {
                        $('#error').text('请求失败: ' + error);
                        $('#importBtn').prop('disabled', false);
                        $('#fileStatus').text('导入失败').css('color', 'red');
                    }
                });
            }
            
            // 开始处理第一批
            processBatch();
        }
    });
    </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;
$filename = isset($_POST['filename']) ? basename($_POST['filename']) : 'unknown';

// 记录日志
function logImport($message) {
    $logFile = __DIR__ . '/import_log.txt';
    $timestamp = date('Y-m-d H:i:s');
    file_put_contents($logFile, "[$timestamp] $message\n", FILE_APPEND);
}

logImport("开始处理文件: $filename, 当前行: $currentLine/$totalLines");

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

// 连接数据库
$conn = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
if ($conn->connect_error) {
    logImport("数据库连接失败: " . $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++;
        $errorMsg = $conn->error;
        $errors[] = $errorMsg;
        logImport("SQL执行错误: $errorMsg \n查询: " . substr($query, 0, 100) . "...");
    } else {
        $successCount++;
    }
}

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

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

实现说明

  1. 自动检测文件类型:

    • 根据文件扩展名自动判断是GZ压缩文件还是普通SQL文件

    • 支持 .sql.gz 和 .sql.gz 扩展名

  2. 自动解压处理:

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

    • 对于普通SQL文件,直接读取内容

  3. 增强功能:

    • 详细的文件信息显示

    • 文件处理状态跟踪

    • 更完善的错误处理

    • 服务器端日志记录

  4. 进度显示:

    • 解压进度(对于GZ文件)

    • 导入行数进度

    • 百分比进度条

  5. 安全改进:

    • 文件名验证和过滤

    • 错误信息限制

    • 操作日志记录

  6. 使用方法:

    • 用户只需选择文件,系统会自动判断文件类型并处理

    • 无需手动选择文件类型

    • 整个过程可视化,状态清晰

这个解决方案提供了完整的自动化处理流程,用户只需上传文件,系统会自动完成所有判断和处理工作,大大简化了用户操作。