/** * File Parser Service * * 解析上传文件内容,支持文本、PDF、DOCX 等格式。 * 返回 { text, mimeType, truncated } */ const fs = require('fs'); const path = require('path'); const MAX_TEXT_LENGTH = 50000; // 最大解析字符数 // 文本类扩展名 const TEXT_EXTENSIONS = new Set([ '.txt', '.md', '.log', '.csv', '.json', '.yaml', '.yml', '.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.go', '.rs', '.html', '.css', '.scss', '.xml', '.sql', '.sh', '.bat', '.env', '.ini', '.toml', '.conf', ]); // 图片类扩展名 const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg']); /** * 解析文件内容 * @param {string} filePath - 文件绝对路径 * @returns {Promise<{text: string, mimeType: string, truncated: boolean}>} */ async function parseFileContent(filePath) { const ext = path.extname(filePath).toLowerCase(); const fileName = path.basename(filePath); // 1. 文本类文件 if (TEXT_EXTENSIONS.has(ext)) { const raw = fs.readFileSync(filePath, 'utf-8'); const truncated = raw.length > MAX_TEXT_LENGTH; const text = truncated ? raw.substring(0, MAX_TEXT_LENGTH) + '\n\n... [文件内容过长,已截断]' : raw; return { text, mimeType: getMimeType(ext), truncated }; } // 2. PDF if (ext === '.pdf') { try { const pdfParse = require('pdf-parse'); const buffer = fs.readFileSync(filePath); const data = await pdfParse(buffer); const raw = data.text || ''; const truncated = raw.length > MAX_TEXT_LENGTH; const text = truncated ? raw.substring(0, MAX_TEXT_LENGTH) + '\n\n... [PDF 内容过长,已截断]' : raw; return { text, mimeType: 'application/pdf', truncated }; } catch (err) { return { text: `[PDF 解析失败: ${err.message}]`, mimeType: 'application/pdf', truncated: false }; } } // 3. DOCX if (ext === '.docx') { try { const mammoth = require('mammoth'); const result = await mammoth.extractRawText({ path: filePath }); const raw = result.value || ''; const truncated = raw.length > MAX_TEXT_LENGTH; const text = truncated ? raw.substring(0, MAX_TEXT_LENGTH) + '\n\n... [DOCX 内容过长,已截断]' : raw; return { text, mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', truncated }; } catch (err) { return { text: `[DOCX 解析失败: ${err.message}]`, mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', truncated: false }; } } // 4. 图片 if (IMAGE_EXTENSIONS.has(ext)) { return { text: `[用户上传了图片: ${fileName}]`, mimeType: getMimeType(ext), truncated: false, }; } // 5. 其他:尝试当文本读取 try { const raw = fs.readFileSync(filePath, 'utf-8'); const truncated = raw.length > MAX_TEXT_LENGTH; const text = truncated ? raw.substring(0, MAX_TEXT_LENGTH) + '\n\n... [文件内容过长,已截断]' : raw; return { text, mimeType: 'application/octet-stream', truncated }; } catch { return { text: `[不支持的文件格式: ${ext}]`, mimeType: 'application/octet-stream', truncated: false }; } } function getMimeType(ext) { const map = { '.txt': 'text/plain', '.md': 'text/markdown', '.log': 'text/plain', '.csv': 'text/csv', '.json': 'application/json', '.js': 'text/javascript', '.ts': 'text/typescript', '.py': 'text/x-python', '.html': 'text/html', '.css': 'text/css', '.xml': 'text/xml', '.yaml': 'text/yaml', '.yml': 'text/yaml', '.pdf': 'application/pdf', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml', }; return map[ext] || 'application/octet-stream'; } module.exports = { parseFileContent };