257 lines
5.8 KiB
Markdown
257 lines
5.8 KiB
Markdown
|
|
# 🗜️ PDF Compression в n8n
|
|||
|
|
|
|||
|
|
## 📋 Проблема
|
|||
|
|
Пользователь загружает PDF 5-10 MB → долгая обработка OCR
|
|||
|
|
|
|||
|
|
## ✅ Решение: 2-уровневая система
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 Уровень 1: Frontend (React)
|
|||
|
|
|
|||
|
|
**Что делаем:**
|
|||
|
|
- JPG/PNG → сжатие до 2MB → конвертация в PDF
|
|||
|
|
- PDF < 5MB → пропускаем
|
|||
|
|
- PDF > 10MB → **отклоняем** с сообщением
|
|||
|
|
|
|||
|
|
**Код:** `frontend/src/utils/pdfConverter.ts` ✅ УЖЕ ГОТОВО
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 Уровень 2: Backend (n8n)
|
|||
|
|
|
|||
|
|
### Workflow для сжатия PDF > 5MB
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Webhook (file upload)
|
|||
|
|
↓
|
|||
|
|
IF Node: file_size > 5 MB?
|
|||
|
|
├─ FALSE → S3 Upload (оригинал)
|
|||
|
|
└─ TRUE → Python Code Node (compress)
|
|||
|
|
↓
|
|||
|
|
S3 Upload (compressed)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🐍 Python Code Node - PDF Compression
|
|||
|
|
|
|||
|
|
### Установка библиотеки в n8n
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# В контейнере n8n
|
|||
|
|
docker exec -it <n8n_container_name> sh
|
|||
|
|
apk add --no-cache python3 py3-pip
|
|||
|
|
pip3 install pypdf
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Code Node конфигурация
|
|||
|
|
|
|||
|
|
**Language:** Python
|
|||
|
|
**Mode:** Run Once for All Items
|
|||
|
|
|
|||
|
|
**Code:**
|
|||
|
|
```python
|
|||
|
|
import io
|
|||
|
|
from pypdf import PdfReader, PdfWriter
|
|||
|
|
|
|||
|
|
# Получаем binary data из предыдущей ноды
|
|||
|
|
input_data = items[0].binary['data']
|
|||
|
|
pdf_bytes = input_data
|
|||
|
|
|
|||
|
|
# Читаем PDF
|
|||
|
|
reader = PdfReader(io.BytesIO(pdf_bytes))
|
|||
|
|
writer = PdfWriter()
|
|||
|
|
|
|||
|
|
# Копируем страницы с оптимизацией
|
|||
|
|
for page in reader.pages:
|
|||
|
|
# Удаляем неиспользуемые объекты
|
|||
|
|
page.compress_content_streams()
|
|||
|
|
writer.add_page(page)
|
|||
|
|
|
|||
|
|
# Применяем сжатие
|
|||
|
|
writer.compress_identical_objects()
|
|||
|
|
writer.remove_duplication()
|
|||
|
|
|
|||
|
|
# Сжимаем изображения (если есть)
|
|||
|
|
for page in writer.pages:
|
|||
|
|
for img in page.images:
|
|||
|
|
img.replace(img.image, quality=70)
|
|||
|
|
|
|||
|
|
# Выводим в bytes
|
|||
|
|
output = io.BytesIO()
|
|||
|
|
writer.write(output)
|
|||
|
|
compressed_bytes = output.getvalue()
|
|||
|
|
|
|||
|
|
# Логируем результат
|
|||
|
|
original_size = len(pdf_bytes) / (1024 * 1024)
|
|||
|
|
compressed_size = len(compressed_bytes) / (1024 * 1024)
|
|||
|
|
compression_ratio = ((original_size - compressed_size) / original_size) * 100
|
|||
|
|
|
|||
|
|
print(f"✅ Compressed: {original_size:.2f}MB → {compressed_size:.2f}MB ({compression_ratio:.1f}% reduction)")
|
|||
|
|
|
|||
|
|
# Возвращаем binary data
|
|||
|
|
return {
|
|||
|
|
'binary': {
|
|||
|
|
'data': compressed_bytes
|
|||
|
|
},
|
|||
|
|
'json': {
|
|||
|
|
'original_size_mb': round(original_size, 2),
|
|||
|
|
'compressed_size_mb': round(compressed_size, 2),
|
|||
|
|
'compression_ratio': round(compression_ratio, 1),
|
|||
|
|
'success': True
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔧 Вариант 2: Execute Command (Ghostscript)
|
|||
|
|
|
|||
|
|
**Требует:** `ghostscript` установлен в системе
|
|||
|
|
|
|||
|
|
### Execute Command Node:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
INPUT="/tmp/input_{{ $json.file_id }}.pdf"
|
|||
|
|
OUTPUT="/tmp/output_{{ $json.file_id }}.pdf"
|
|||
|
|
|
|||
|
|
# Сохраняем binary в файл
|
|||
|
|
echo "{{ $binary.data }}" | base64 -d > "$INPUT"
|
|||
|
|
|
|||
|
|
# Сжимаем через Ghostscript
|
|||
|
|
gs -sDEVICE=pdfwrite \
|
|||
|
|
-dCompatibilityLevel=1.4 \
|
|||
|
|
-dPDFSETTINGS=/ebook \
|
|||
|
|
-dNOPAUSE \
|
|||
|
|
-dQUIET \
|
|||
|
|
-dBATCH \
|
|||
|
|
-sOutputFile="$OUTPUT" \
|
|||
|
|
"$INPUT"
|
|||
|
|
|
|||
|
|
# Выводим compressed PDF
|
|||
|
|
cat "$OUTPUT" | base64
|
|||
|
|
|
|||
|
|
# Cleanup
|
|||
|
|
rm -f "$INPUT" "$OUTPUT"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Параметры `-dPDFSETTINGS`:**
|
|||
|
|
- `/screen` - 72 DPI (минимальное качество, максимальное сжатие)
|
|||
|
|
- `/ebook` - 150 DPI ⭐ **рекомендуется**
|
|||
|
|
- `/printer` - 300 DPI
|
|||
|
|
- `/prepress` - 300 DPI (максимальное качество)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔄 Полный Workflow
|
|||
|
|
|
|||
|
|
### 1. Webhook (File Upload)
|
|||
|
|
|
|||
|
|
**Input:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"claim_id": "CLM-2025-10-26-ABC123",
|
|||
|
|
"file_type": "policy_scan",
|
|||
|
|
"filename": "policy.pdf",
|
|||
|
|
"voucher": "E1000-302372730",
|
|||
|
|
"session_id": "sess-xyz-456"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Binary Data:** `data` (PDF file)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. IF Node: Check File Size
|
|||
|
|
|
|||
|
|
**Condition:**
|
|||
|
|
```
|
|||
|
|
{{ $binary.data.length }} > 5242880
|
|||
|
|
```
|
|||
|
|
(5MB = 5 * 1024 * 1024 bytes)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3a. FALSE → Direct Upload
|
|||
|
|
|
|||
|
|
**S3 Upload Node** → PostgreSQL
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3b. TRUE → Compress First
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Python Code (compress)
|
|||
|
|
↓
|
|||
|
|
Set Binary Data
|
|||
|
|
↓
|
|||
|
|
S3 Upload (compressed)
|
|||
|
|
↓
|
|||
|
|
PostgreSQL (update file_size)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 Результаты сжатия
|
|||
|
|
|
|||
|
|
| Метод | Скорость | Сжатие | Качество |
|
|||
|
|
|-------|----------|--------|----------|
|
|||
|
|
| **pypdf** | Быстро | 30-50% | Хорошее ⭐ |
|
|||
|
|
| **Ghostscript /ebook** | Средне | 50-70% | Среднее |
|
|||
|
|
| **Ghostscript /screen** | Средне | 70-85% | Низкое |
|
|||
|
|
| **Frontend (jspdf)** | Моментально | 60-80% | Хорошее ✅ |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 Итоговая стратегия
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
📱 Пользователь загружает файл
|
|||
|
|
↓
|
|||
|
|
🔍 Frontend проверка:
|
|||
|
|
├─ JPG/PNG → compress + convert → PDF (✅ готово)
|
|||
|
|
├─ PDF < 5MB → отправить как есть
|
|||
|
|
├─ PDF 5-10MB → отправить (n8n сожмёт)
|
|||
|
|
└─ PDF > 10MB → ❌ отклонить
|
|||
|
|
|
|||
|
|
🚀 n8n workflow:
|
|||
|
|
├─ file_size < 5MB → S3 + OCR
|
|||
|
|
└─ file_size > 5MB → Python compress → S3 + OCR
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 Тестирование
|
|||
|
|
|
|||
|
|
### curl пример:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Создаём большой PDF для теста
|
|||
|
|
curl -o large.pdf https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf
|
|||
|
|
|
|||
|
|
# Отправляем в n8n
|
|||
|
|
curl -X POST \
|
|||
|
|
-F "claim_id=CLM-TEST-001" \
|
|||
|
|
-F "file_type=policy_scan" \
|
|||
|
|
-F "fileInput=@large.pdf" \
|
|||
|
|
-F "voucher=TEST-123" \
|
|||
|
|
-F "session_id=sess-test" \
|
|||
|
|
https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ Готово!
|
|||
|
|
|
|||
|
|
**Frontend:** ✅ Ограничение 10MB + предупреждение
|
|||
|
|
**n8n:** ⏳ Нужно добавить Python Code Node
|
|||
|
|
|
|||
|
|
**Следующий шаг:** Добавить Python Code Node в workflow для файлов > 5MB
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|