如何在浏览器解析含MySQL导出.DAT文件的大型压缩包并调用API?
浏览器端处理MySQL导出压缩包(含TSV格式.DAT文件)并同步到BaaS方案
下面是一套完整的前端实现方案,涵盖流式处理、进度追踪和API提交,完全在浏览器中完成,无需后端中转:
一、所需工具库
- pako:用于在浏览器中流式解压GZIP/TAR.GZ压缩包,处理二进制数据高效可靠
- papaparse:支持流式解析TSV/CSV,适合处理大体积数据,避免内存溢出
可以通过CDN直接引入,或者打包到你的前端项目中:
<script src="https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js"></script>
二、分步实现指南
1. 文件选择与基础UI准备
先做一个简单的上传界面,包含文件输入框、进度条和状态提示:
<input type="file" id="zip-file-input" accept=".gz,.tar.gz"> <div id="progress-container"> <div id="progress-bar"></div> </div> <div id="status-text">请选择压缩文件</div> <div id="loading-spinner" class="loading-spinner" style="display:none;"></div>
2. 流式读取与解压压缩包
核心是用FileReader分块读取文件,配合pako.Inflate做流式解压,避免一次性加载大文件:
const fileInput = document.getElementById('zip-file-input'); const progressBar = document.getElementById('progress-bar'); const statusText = document.getElementById('status-text'); const spinner = document.getElementById('loading-spinner'); fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; spinner.style.display = 'block'; statusText.textContent = '开始读取文件'; const totalSize = file.size; let loadedBytes = 0; const chunkSize = 1024 * 1024; // 1MB分块,可根据文件大小调整 // 初始化pako解压实例 const inflate = new pako.Inflate({ chunkSize: chunkSize, onData: (uncompressedChunk) => { // 把解压后的二进制块交给tar解析逻辑 processTarChunk(uncompressedChunk); }, onEnd: () => { statusText.textContent = '解压完成,开始解析数据'; } }); // 分块读取文件 function readChunk(start) { const reader = new FileReader(); const end = Math.min(start + chunkSize, totalSize); const blob = file.slice(start, end); reader.onload = (e) => { loadedBytes += end - start; const progress = Math.round((loadedBytes / totalSize) * 100); progressBar.style.width = `${progress}%`; statusText.textContent = `解压中:${progress}%`; // 喂给解压实例 inflate.push(new Uint8Array(e.target.result), end >= totalSize); if (end < totalSize) { readChunk(end); } }; reader.readAsArrayBuffer(blob); } readChunk(0); });
3. 解析TAR结构,提取.DAT文件
压缩包一般是TAR.GZ格式,需要解析TAR的文件头,筛选出.dat后缀的文件:
let currentFile = null; let fileBuffer = []; const TAR_HEADER_SIZE = 512; function processTarChunk(chunk) { fileBuffer.push(...chunk); // 每次处理至少一个完整的TAR头 while (fileBuffer.length >= TAR_HEADER_SIZE) { const header = fileBuffer.slice(0, TAR_HEADER_SIZE); const fileName = new TextDecoder('utf-8').decode(header.slice(0, 100)).trim(); // 检查是否是.dat文件 if (fileName.endsWith('.dat')) { const fileSize = parseInt(new TextDecoder('utf-8').decode(header.slice(124, 136)), 8); currentFile = { name: fileName, size: fileSize, remaining: fileSize }; statusText.textContent = `开始解析文件:${fileName}`; // 跳过文件头 fileBuffer = fileBuffer.slice(TAR_HEADER_SIZE); // 初始化TSV解析器 initTSVParser(); } else if (currentFile) { // 处理当前.dat文件的内容 const take = Math.min(fileBuffer.length, currentFile.remaining); const dataChunk = fileBuffer.slice(0, take); feedTSVParser(dataChunk); currentFile.remaining -= take; fileBuffer = fileBuffer.slice(take); if (currentFile.remaining <= 0) { currentFile = null; parser.end(); // 结束当前文件的解析 } } else { // 跳过非.dat文件的内容 const fileSize = parseInt(new TextDecoder('utf-8').decode(header.slice(124, 136)), 8); const totalFileBytes = Math.ceil(fileSize / TAR_HEADER_SIZE) * TAR_HEADER_SIZE; fileBuffer = fileBuffer.slice(TAR_HEADER_SIZE + totalFileBytes); } } }
4. 流式解析TSV并批量提交到BaaS
用papaparse的流式解析功能,每积累一定数量的行就调用API提交,减少请求次数:
let parser = null; let batch = []; const BATCH_LIMIT = 150; // 批量提交的行数,可根据API限制调整 let totalProcessed = 0; function initTSVParser() { parser = Papa.parse(Papa.NODE_STREAM_INPUT, { delimiter: '\t', header: true, // 如果你的TSV有表头,设为true;否则设为false skipEmptyLines: true, step: (result) => { if (result.errors.length) { console.warn('解析错误:', result.errors); return; } batch.push(result.data); totalProcessed++; statusText.textContent = `已处理${totalProcessed}行,准备提交`; if (batch.length >= BATCH_LIMIT) { submitBatch(); } }, complete: () => { // 提交剩余的行 if (batch.length > 0) { submitBatch(); } statusText.textContent = `所有数据处理完成,共${totalProcessed}行`; spinner.style.display = 'none'; } }); } function feedTSVParser(chunk) { const text = new TextDecoder('utf-8').decode(chunk); parser.write(text); } async function submitBatch() { const batchToSubmit = [...batch]; batch = []; statusText.textContent = `正在提交${batchToSubmit.length}条数据`; try { // 替换为你的BaaS API地址和请求配置 const response = await fetch('https://your-baaas-api.com/import', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR_AUTH_TOKEN' // 如果需要认证 }, body: JSON.stringify(batchToSubmit) }); if (!response.ok) { throw new Error(`API返回错误:${response.status}`); } } catch (err) { console.error('提交失败:', err); statusText.textContent = '提交失败,重试中...'; // 重试逻辑,可限制重试次数 await new Promise(resolve => setTimeout(resolve, 2000)); submitBatch(batchToSubmit); } }
5. 进度条与加载动画样式
添加基础的CSS样式,让进度和加载状态更直观:
#progress-container { width: 100%; height: 24px; border-radius: 12px; background: #f0f0f0; margin: 16px 0; overflow: hidden; } #progress-bar { height: 100%; background: #2196F3; width: 0%; transition: width 0.2s ease; } .loading-spinner { border: 4px solid #f3f3f3; border-top: 4px solid #2196F3; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #status-text { font-size: 14px; color: #333; margin: 8px 0; }
三、关键注意事项
- 内存控制:严格使用流式处理,分块读取、解压、解析,绝对不要一次性加载整个大文件到内存,否则会导致浏览器崩溃
- 编码兼容:确认.DAT文件的编码(MySQL导出一般是UTF-8,但部分场景可能是GBK),如果是GBK,需要用
TextDecoder('gbk')解码 - 错误处理:添加文件格式校验(比如检查文件名后缀、TAR头合法性)、解析错误捕获、API重试机制(带指数退避)
- 浏览器兼容性:
FileReader、TextDecoder、fetch都是现代浏览器支持的API,如需兼容旧浏览器,需添加polyfill - CORS配置:如果BaaS API和前端域名不同,需要在BaaS端配置CORS规则,允许前端域名的请求
- 用户体验:处理过程中禁用文件输入框,添加取消按钮(需要实现中断流式处理的逻辑,比如终止
readChunk循环、关闭解析器)
内容的提问来源于stack exchange,提问作者north_celt
相关产品推荐
相关产品推荐

