328 lines
8.2 KiB
Markdown
328 lines
8.2 KiB
Markdown
# 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<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();
|
||
|
||
// 转换为 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<LoaderData>` → `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 转换
|
||
|
||
✅ **测试完成**:
|
||
- 本地文件测试通过
|
||
- 类型检查无错误
|
||
- 代码结构清晰
|
||
|
||
🎯 **可以开始测试了**!启动开发服务器,访问草稿页面验证功能。
|