Files
leaudit-platform-frontend/docs/minio-docx-extraction-fix.md
T
2025-12-05 00:09:32 +08:00

8.2 KiB
Raw Blame History

MinIO 文件提取修复说明

问题描述

之前的 extractPlaceholdersFromDocx 函数使用 fs.readFileSync() 读取文件,但这只能读取本地文件系统路径,无法读取存储在 MinIO 对象存储中的文件。

错误代码

// ❌ 错误:尝试用 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

修改后的代码

export async function extractPlaceholdersFromDocx(
  filePath: string
): Promise<string[]> {
  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();

    // 转换为 BufferPizZip 需要)
    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 下载文件
  • 添加下载失败的错误处理
  • 移除 fsDocxtemplater 的导入(不再需要)

关键变化

// 之前:
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<LoaderData>json()
  • 导入 json 函数从 @remix-run/node

测试验证

1. 本地文件测试

node scripts/test-docx-parser.cjs

应该输出:

============================================================
测试从 DOCX 文件提取占位符功能(本地文件)
============================================================

✅ 文件存在
✅ 文件读取成功, 大小: 18.95 KB
✅ PizZip 解压成功
✅ document.xml 读取成功
✅ 提取纯文本成功

找到 18 个占位符:
1. {{地市名称}}
2. {{合同名称}}
...

2. MinIO 文件测试

启动开发服务器:

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 转换

// 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

错误处理

// 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

export const DOCUMENT_URL =
  process.env.NEXT_PUBLIC_DOCUMENT_URL ||
  apiConfig.documentUrl ||
  'http://10.76.244.156:9000/docauditai/';

MinIO 访问权限

确保 MinIO 存储桶的访问策略允许匿名读取(或配置了正确的认证):

{
  "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:

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 转换

测试完成

  • 本地文件测试通过
  • 类型检查无错误
  • 代码结构清晰

🎯 可以开始测试了!启动开发服务器,访问草稿页面验证功能。