Nuxt3项目中PDF转CSV转换器生成Blob/ObjectURL失败,输出无效JSON文件问题求助
Nuxt3项目中PDF转CSV转换器生成Blob/ObjectURL失败,输出无效JSON文件问题求助
我最近在开发一个PDF转CSV的转换器,现在卡在最后一步了——代码能正常执行,但生成下载链接时,本该生成Blob对应的ObjectURL,结果却输出了一个包含原始数据的无效JSON文件,完全没法正常使用。我自己折腾了好长时间,甚至找AI帮忙排查,都没解决问题。AI提示问题可能出在Composable/useConvert.ts文件里,我也觉得是这个文件的锅,因为它负责把API返回的转换结果处理成正确格式,再生成供客户端下载的链接。有没有大佬能帮我看看啊?
类型声明文件
types/new-parsers.d.ts
declare module 'pdf-parse/lib/pdf-parse.js' { interface PDFParse { (dataBuffer: Buffer, options?: any): Promise<{ numpages: number; numrender: number; info: any; metadata: any; version: string; text: string; }>; } const pdf: PDFParse; export default pdf; } declare module 'papaparse' { // 这里可以根据需求补充更详细的类型,不过目前用any足够处理我们的动态数据 export function parse(csvString: string, config: any): { data: any[], errors: any[], meta: any }; export function unparse(data: any[] | object, config?: any): string; }
CSV转换核心代码
server/utils/csvConverter.ts
import * as path from 'path'; import * as os from 'os'; import * as fs from 'fs/promises'; import libre from 'libreoffice-convert'; import { promisify } from 'util'; import { load } from 'cheerio'; // 命名导入 import pdf from 'pdf-parse/lib/pdf-parse.js'; import Papa from 'papaparse'; import { XMLParser, XMLBuilder } from 'fast-xml-parser'; const libreConvertAsync = promisify(libre.convert); interface MultiPartData { data: Buffer; filename?: string; } // --- 新的健壮辅助函数 --- async function convertWithLibreOffice(inputBuffer: Buffer, outputExtension: string, tempDir: string): Promise<Buffer> { try { const inputPath = path.join(tempDir, `inputfile${path.extname(tempDir)}`); // 给文件加个扩展名 await fs.writeFile(inputPath, inputBuffer); return await libreConvertAsync(inputBuffer, `.${outputExtension}`, undefined); } catch (err: any) { console.error("LibreOffice转换失败,请确认LibreOffice已安装并添加到系统PATH中?", err); // 抛出更友好的错误信息给用户 throw new Error("文件转换失败,请确保服务器已安装LibreOffice。"); } } export async function handleCsvConversion(file: MultiPartData, targetFormat: string): Promise<{ buffer: Buffer; filename: string; contentType: string }> { const extension = path.extname(file.filename!).slice(1).toLowerCase(); const newFilename = file.filename!.replace(/\.[^/.]+$/, `.${targetFormat}`); const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'nuxt-csv-')); try { let outputBuffer: Buffer; let contentType = 'text/csv'; if (targetFormat === 'csv') { if (['xlsx', 'ods'].includes(extension)) { outputBuffer = await convertWithLibreOffice(file.data, 'csv', tempDir); } else if (extension === 'pdf') { const data = await pdf(file.data); const csvContent = `"${data.text.replace(/"/g, '""')}"`; outputBuffer = Buffer.from(csvContent, 'utf-8'); } else if (extension === 'html') { const $ = load(file.data.toString()); const rows: string[][] = []; $('table tr').each((i, row) => { const rowData: string[] = []; $(row).find('th, td').each((j, cell) => { rowData.push(`"${$(cell).text().trim().replace(/"/g, '""')}"`); }); rows.push(rowData); }); outputBuffer = Buffer.from(rows.map(row => row.join(',')).join('\n')); } else if (extension === 'json' || extension === 'bson') { const jsonData = JSON.parse(file.data.toString()); const csvString = Papa.unparse(jsonData); outputBuffer = Buffer.from(csvString); } else if (extension === 'xml') { const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "" }); const jsonObj = parser.parse(file.data); const arrayToConvert = Object.values(jsonObj[Object.keys(jsonObj)[0]]).find(Array.isArray); if (!arrayToConvert) throw new Error('无法在XML中找到清晰的数组结构用于转换为CSV。'); const csvString = Papa.unparse(arrayToConvert); outputBuffer = Buffer.from(csvString); } else if (extension === 'srt') { const srtContent = file.data.toString(); const entries = srtContent.split(/\r?\n\r?\n/); const csvRows = [["index", "start", "end", "text"]]; for (const entry of entries) { if (entry.trim() === '') continue; const lines = entry.split(/\r?\n/); csvRows.push([ `"${lines[0]}"`, `"${lines[1].split(' --> ')[0]}"`, `"${lines[1].split(' --> ')[1]}"`, `"${lines.slice(2).join(' ').replace(/"/g, '""')}"` ]); } outputBuffer = Buffer.from(csvRows.map(row => row.join(',')).join('\n')); } } return { buffer: outputBuffer, filename: newFilename, contentType }; } finally { // 清理临时目录 await fs.rm(tempDir, { recursive: true, force: true }); } }
目前我怀疑问题出在前端的useConvert.ts组合式函数里,它负责接收后端返回的转换结果并生成Blob URL供用户下载。但现在后端返回的buffer数据,前端可能没有正确处理成Blob,反而直接把原始数据序列化成了JSON,导致下载的文件不对。有没有大佬能帮我排查下,到底是后端转换逻辑有问题,还是前端处理Blob的代码写错了?感激不尽!
内容来源于stack exchange
相关产品推荐
相关产品推荐

