下面是一个完整的解决方案,能够自动判断上传的文件是GZ压缩文件还是普通SQL文件,并相应地进行处理后再导入MySQL数据库。
<!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
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
]);
}
?>
自动检测文件类型:
根据文件扩展名自动判断是GZ压缩文件还是普通SQL文件
支持 .sql
, .gz
和 .sql.gz
扩展名
自动解压处理:
对于GZ文件,使用pako.js在浏览器端解压
对于普通SQL文件,直接读取内容
增强功能:
详细的文件信息显示
文件处理状态跟踪
更完善的错误处理
服务器端日志记录
进度显示:
解压进度(对于GZ文件)
导入行数进度
百分比进度条
安全改进:
文件名验证和过滤
错误信息限制
操作日志记录
使用方法:
用户只需选择文件,系统会自动判断文件类型并处理
无需手动选择文件类型
整个过程可视化,状态清晰
这个解决方案提供了完整的自动化处理流程,用户只需上传文件,系统会自动完成所有判断和处理工作,大大简化了用户操作。