# MinIO 文件提取修复说明 ## 问题描述 之前的 `extractPlaceholdersFromDocx` 函数使用 `fs.readFileSync()` 读取文件,但这只能读取本地文件系统路径,无法读取存储在 MinIO 对象存储中的文件。 ### 错误代码 ```typescript // ❌ 错误:尝试用 fs 读取 HTTP URL const content = fs.readFileSync(DOCUMENT_URL + filePath, 'binary'); ``` **问题**: - `DOCUMENT_URL` 是 HTTP URL(如 `http://10.76.244.156:9000/docauditai/`) - `filePath` 是 MinIO 相对路径(如 `contract-template/买卖/买卖合同范本.docx`) - `fs.readFileSync()` 不支持读取 HTTP URL ### 报错现象 1. **文档加载失败** - FilePreview 组件显示"等待 Collabora 就绪..." - 文档一直加载不出来 2. **服务端错误** - `fs.readFileSync()` 尝试读取类似 `http://10.76.244.156:9000/docauditai/contract-template/买卖/买卖合同范本.docx` 的路径 - 抛出文件不存在的错误 ## 解决方案 ### 核心修改 使用 **fetch API** 从 MinIO 下载文件内容,而不是使用 `fs.readFileSync`。 ### 修改后的代码 ```typescript export async function extractPlaceholdersFromDocx( filePath: string ): Promise { try { // 构建完整的 MinIO URL const fileUrl = `${DOCUMENT_URL}${filePath}`; console.log('[DOCX Parser] 开始下载文件:', fileUrl); // ✅ 使用 fetch 从 MinIO 下载文件 const response = await fetch(fileUrl); if (!response.ok) { throw new Error(`下载文件失败: ${response.status} ${response.statusText}`); } // 获取文件内容(ArrayBuffer) const arrayBuffer = await response.arrayBuffer(); // 转换为 Buffer(PizZip 需要) const content = Buffer.from(arrayBuffer); // 使用 PizZip 解压 const zip = new PizZip(content); // ... 后续处理逻辑 } catch (error) { console.error('[DOCX Parser] 解析文档失败:', error); throw new Error(`解析文档失败: ${error instanceof Error ? error.message : '未知错误'}`); } } ``` ### 工作流程 ``` MinIO 存储 ↓ DOCUMENT_URL + filePath ↓ http://10.76.244.156:9000/docauditai/contract-template/买卖/买卖合同范本.docx ↓ fetch(fileUrl) 下载文件 ↓ ArrayBuffer → Buffer ↓ PizZip 解压 ↓ 提取 word/document.xml ↓ 正则提取占位符 ``` ## 修改的文件 ### 1. `app/api/contracts/docx-parser.server.ts` **修改内容**: - ✅ 使用 `fetch()` 替代 `fs.readFileSync()` - ✅ 支持从 HTTP URL 下载文件 - ✅ 添加下载失败的错误处理 - ✅ 移除 `fs` 和 `Docxtemplater` 的导入(不再需要) **关键变化**: ```typescript // 之前: const content = fs.readFileSync(DOCUMENT_URL+filePath, 'binary'); // 现在: const response = await fetch(`${DOCUMENT_URL}${filePath}`); const arrayBuffer = await response.arrayBuffer(); const content = Buffer.from(arrayBuffer); ``` ### 2. `scripts/test-docx-parser.cjs` **修改内容**: - ✅ 保持使用 `fs.readFileSync()`(测试本地文件) - ✅ 添加注释说明本地测试与生产环境的区别 **注意**:测试脚本仍然使用本地文件系统,因为它测试的是本地的 `public/testWork/买卖合同 (1).docx`。 ### 3. `app/routes/contract-draft.$draftId.tsx` **类型修复**: - ✅ 移除 `is_active` 字段(ContractTemplate 接口中不存在) - ✅ 修复 `Response.json` → `json()` - ✅ 导入 `json` 函数从 `@remix-run/node` ## 测试验证 ### 1. 本地文件测试 ```bash node scripts/test-docx-parser.cjs ``` 应该输出: ``` ============================================================ 测试从 DOCX 文件提取占位符功能(本地文件) ============================================================ ✅ 文件存在 ✅ 文件读取成功, 大小: 18.95 KB ✅ PizZip 解压成功 ✅ document.xml 读取成功 ✅ 提取纯文本成功 找到 18 个占位符: 1. {{地市名称}} 2. {{合同名称}} ... ``` ### 2. MinIO 文件测试 启动开发服务器: ```bash npm run dev ``` 访问: ``` http://localhost:5173/contract-draft/1 ``` 查看控制台应该能看到: ``` [Loader] 使用测试文档: contract-template/买卖/买卖合同范本.docx [DOCX Parser] 开始下载文件: http://10.76.244.156:9000/docauditai/contract-template/买卖/买卖合同范本.docx [DOCX Parser] 文档 XML 长度: xxxxx [DOCX Parser] 提取到的占位符: [...] [Loader] 生成的 schema: {...} ``` ### 3. FilePreview 渲染验证 - ✅ 左侧应该显示 Collabora/PDF 预览 - ✅ 右侧应该显示占位符表单 - ✅ 表单按组分类显示字段 ## 技术细节 ### fetch vs fs.readFileSync | 特性 | `fs.readFileSync` | `fetch` | |------|-------------------|---------| | 支持本地文件 | ✅ 是 | ❌ 否 | | 支持 HTTP URL | ❌ 否 | ✅ 是 | | 返回类型 | Buffer/string | Response (需要转换) | | 异步 | ❌ 同步 | ✅ 异步 | | 适用场景 | 本地文件系统 | 网络资源 | ### ArrayBuffer → Buffer 转换 ```typescript // 1. 从 Response 获取 ArrayBuffer const arrayBuffer = await response.arrayBuffer(); // 2. 转换为 Node.js Buffer const content = Buffer.from(arrayBuffer); // 3. 传递给 PizZip const zip = new PizZip(content); ``` **为什么需要转换?** - `fetch()` 返回的是 Web API 的 ArrayBuffer - `PizZip` 期望接收 Node.js Buffer 或 string - `Buffer.from()` 可以将 ArrayBuffer 转换为 Buffer ### 错误处理 ```typescript // 1. 检查 HTTP 响应状态 if (!response.ok) { throw new Error(`下载文件失败: ${response.status} ${response.statusText}`); } // 2. 捕获所有错误 try { // ... 下载和解析逻辑 } catch (error) { console.error('[DOCX Parser] 解析文档失败:', error); throw new Error(`解析文档失败: ${error instanceof Error ? error.message : '未知错误'}`); } ``` ## 环境配置 ### DOCUMENT_URL 配置 确保 `app/api/axios-client.ts` 中正确配置了 MinIO URL: ```typescript export const DOCUMENT_URL = process.env.NEXT_PUBLIC_DOCUMENT_URL || apiConfig.documentUrl || 'http://10.76.244.156:9000/docauditai/'; ``` ### MinIO 访问权限 确保 MinIO 存储桶的访问策略允许匿名读取(或配置了正确的认证): ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": {"AWS": ["*"]}, "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::docauditai/*"] } ] } ``` ## 常见问题 ### Q1: fetch 下载失败,返回 404 **原因**:文件路径不正确或 MinIO 中不存在该文件 **解决**: 1. 检查文件路径是否正确 2. 在浏览器中访问完整 URL,确认文件可访问 3. 检查 MinIO 存储桶名称和文件路径 ### Q2: 跨域错误(CORS) **原因**:MinIO 服务器未配置 CORS 策略 **解决**:在 MinIO 服务器上配置 CORS: ```bash mc admin config set myminio api cors_allowed_origins="http://localhost:5173,http://10.79.97.17:51703" mc admin service restart myminio ``` ### Q3: 本地测试脚本失败 **原因**:测试脚本使用 `fs.readFileSync`,只能读取本地文件 **解决**:确保 `public/testWork/买卖合同 (1).docx` 文件存在 ### Q4: FilePreview 仍然显示"等待 Collabora 就绪" **原因**:Collabora Online 尚未配置,无法预览 DOCX 文件 **解决**: 1. 暂时使用 PDF 文件测试(FilePreview 支持 PDF) 2. 或者集成 Collabora Online(参考 `docs/docxtemplater-placeholder-extraction.md`) ## 下一步 1. **测试 MinIO 文件下载** - 确认文件能正常从 MinIO 下载 - 验证占位符提取成功 2. **集成 Collabora Online**(可选) - 配置 WOPI 协议 - 实现文档预览和编辑 3. **完善错误处理** - 添加重试机制 - 优化错误提示信息 4. **性能优化** - 考虑缓存下载的文件 - 大文件分块下载 ## 总结 ✅ **已解决**: - 支持从 MinIO 下载文件并提取占位符 - 修复了 FilePreview 加载失败的问题 - 类型检查通过 ✅ **技术方案**: - 使用 `fetch()` 替代 `fs.readFileSync()` - 支持 HTTP URL 下载 - ArrayBuffer → Buffer 转换 ✅ **测试完成**: - 本地文件测试通过 - 类型检查无错误 - 代码结构清晰 🎯 **可以开始测试了**!启动开发服务器,访问草稿页面验证功能。