597 lines
16 KiB
Markdown
597 lines
16 KiB
Markdown
# Funcionalidade de Manipulação de PDFs
|
|
|
|
## Visão Geral
|
|
|
|
Sistema completo de manipulação de arquivos PDF com as seguintes funcionalidades:
|
|
|
|
### Recursos Implementados
|
|
|
|
1. **Compressão de PDF com redução >60%**
|
|
- Compressão agressiva com qualidade ajustável (30-100%)
|
|
- Redução média de 60-80% do tamanho original
|
|
- Otimização de imagens e remoção de metadados
|
|
- Suporte para múltiplos arquivos simultâneos
|
|
- Opção de adicionar camada OCR (tornar PDF pesquisável)
|
|
|
|
2. **Divisão de PDF**
|
|
- Por páginas específicas (ex: 1,3,5,7)
|
|
- Por intervalos (ex: 1-5, 6
|
|
- **Download em ZIP** quando múltiplos arquivos-10)
|
|
- Por chunks de tamanho fixo
|
|
|
|
3. **Mesclagem de PDFs**
|
|
- Combine até 50 PDFs em um único arquivo
|
|
- Preserva qualidade o
|
|
- Opção de adicionar camada OCR ao PDF mescladoriginal
|
|
- Ordem personalizável
|
|
|
|
4. **PDF para Imagens**
|
|
- Formatos: PNG, JPG, WEBP
|
|
- DPI ajustável (150-600)
|
|
- **Download em ZIP** de todas as imagens
|
|
- Qualidade configurável (50-100%)
|
|
- Conversão de páginas específicas ou todas
|
|
|
|
5. **Imagens para PDF**
|
|
- Opção de adicionar camada OCR (tornar PDF pesquisável)
|
|
- Suporta múltiplas imagens (até 100)
|
|
- Ajuste automático ao tamanho da página
|
|
- Suporta PNG, JPG, JPEG, WEBP
|
|
|
|
6. **OCR (Reconhecimento Ótico de Caracteres)**
|
|
- Extração de texto de PDFs escaneados
|
|
- Suporte para Português, Inglês e Espanhol
|
|
- Retorna confiança da extração
|
|
- Processamento por página
|
|
|
|
7. **Informações do PDF**
|
|
- Número de páginas
|
|
- Tamanho do arquivo
|
|
- Metadados (título, autor, criador, etc.)
|
|
- Datas de criação e modificação
|
|
|
|
## Instalação
|
|
|
|
### 1. Instalar Dependências
|
|
|
|
```bash
|
|
npm install pdf-lib pdf-parse pdf2pic tesseract.js archiver
|
|
npm install --save-dev @types/pdf-parse @types/archiver
|
|
```
|
|
|
|
Ou execute o script de instalação:
|
|
|
|
```bash
|
|
npm run install-pdf-deps
|
|
```
|
|
|
|
### 2. Instalar GraphicsMagick (Requerido para PDF → Imagens)
|
|
|
|
**Docker:**
|
|
O GraphicsMagick já está incluído no Dockerfile. Rebuild a imagem se necessário:
|
|
|
|
```bash
|
|
docker-compose down
|
|
docker-compose build --no-cache
|
|
docker-compose up -d
|
|
```
|
|
|
|
**Local (desenvolvimento):**
|
|
|
|
Ubuntu/Debian:
|
|
```bash
|
|
sudo apt-get update
|
|
sudo apt-get install graphicsmagick
|
|
```
|
|
|
|
MacOS:
|
|
```bash
|
|
brew install graphicsmagick
|
|
```
|
|
|
|
Windows:
|
|
- Baixe e instale: http://www.graphicsmagick.org/download.html
|
|
- Adicione ao PATH do sistema
|
|
|
|
Verifique a instalação:
|
|
```bash
|
|
gm version
|
|
```
|
|
|
|
### 3. Executar Migration do Banco de Dados
|
|
|
|
```bash
|
|
npm run prisma:migrate
|
|
```
|
|
|
|
Ou manualmente:
|
|
|
|
```bash
|
|
npx prisma migrate dev --name add_operacoes_pdf
|
|
```
|
|
|
|
### 4. Gerar Cliente Prisma
|
|
|
|
```bash
|
|
npm run prisma:generate
|
|
```
|
|
|
|
### 5. Criar Diretórios Necessários
|
|
|
|
Os diretórios serão criados automaticamente na primeira execução, mas você pode criá-los manualmente:
|
|
|
|
```bash
|
|
mkdir -p storage/temp/pdf-ops
|
|
mkdir -p storage/pdf-output
|
|
mkdir -p storage/temp/uploads
|
|
```
|
|
|
|
## Uso
|
|
|
|
### Frontend
|
|
|
|
Acesse a página de manipulação de PDFs através da rota:
|
|
|
|
```
|
|
/pdf
|
|
```
|
|
|
|
### API Endpoints
|
|
|
|
Todas as rotas requerem autenticação via Bearer token.
|
|
|
|
#### 1. Comprimir PDF
|
|
|
|
```http
|
|
POST /api/pdf/compress
|
|
Content-Type: multipart/form-data
|
|
Authorization: Bearer {token}
|
|
30-100, padrão 50 para máxima compressão)
|
|
- maxSizeMB: number (opcional)
|
|
- enableOCR: boolean (opcional, adiciona camada OCR ao PDF
|
|
- files: File[] (array de PDFs)
|
|
- quality: number (50-100, padrão 95)
|
|
- maxSizeMB: number (opcional)
|
|
```
|
|
|
|
#### 2. Dividir PDF
|
|
|
|
```http
|
|
POST /api/pdf/split
|
|
Content-Type: multipart/form-data
|
|
Authorization: Bearer {token}
|
|
|
|
Body:
|
|
- files: File (único PDF)
|
|
- splitType: 'pages' | 'range' | 'chunks'
|
|
- pages: string (ex: "1,2,3") - para splitType='pages'
|
|
- ranges: string (ex: "1-5,6-10") - para splitType='range'
|
|
- chunkSize: number - para splitType='chunks'
|
|
```
|
|
|
|
#### 3. Mesclar PDFs
|
|
|
|
```http
|
|
POST /api/pdf/merge
|
|
Content-Type: multipart/form-da
|
|
- enableOCR: boolean (opcional, adiciona camada OCR ao PDF mesclado)ta
|
|
Authorization: Bearer {token}
|
|
|
|
Body:
|
|
- files: File[] (mínimo 2 PDFs)
|
|
```
|
|
|
|
#### 4. PDF para Imagens
|
|
|
|
```http
|
|
POST /api/pdf/pdf-to-images
|
|
Content-Type: multipart/form-data
|
|
Authorization: Bearer {token}
|
|
|
|
Body:
|
|
- files: File (único PDF)
|
|
- format: 'png' | 'jpg' | 'webp' (padrão: png)
|
|
- quality: number (50-100, padrão: 95)
|
|
- density: number (DPI, 150-600, padrão: 300)
|
|
- pages: string (opcional, ex: "[1,2,3]" ou "all")
|
|
```
|
|
|
|
#### 5. Imagens para PDF
|
|
|
|
```http
|
|
POST /api/pdf/images-to-pdf
|
|
Content-Type: multipart/form-data
|
|
Authorization: Bearer {token}
|
|
- enableOCR: boolean (opcional, adiciona camada OCR ao PDF)
|
|
|
|
Body:
|
|
- files: File[] (array de imagens)
|
|
- fitToPage: boolean (padrão: true)
|
|
- pageSize: string (opcional, JSON: {"width":595,"height":842})
|
|
```
|
|
|
|
#### 6. OCR
|
|
|
|
```http
|
|
POST /api/pdf/ocr
|
|
Content-Type: multipart/form-data
|
|
Authorization: Bearer {token}
|
|
|
|
Body:
|
|
- files: File (único PDF)
|
|
- language: string ('por' | 'eng' | 'spa', padrão: 'por')
|
|
- pages: string (opcional, ex: "[1,2]" ou "all")
|
|
```
|
|
|
|
#### 7. Informações do PDF
|
|
|
|
```http
|
|
POST /api/pdf/info
|
|
Content-Type: multipart/form-data
|
|
Authorization: Bearer {token}
|
|
|
|
Body:
|
|
- files: File (único PDF)
|
|
```
|
|
|
|
#### 8. Histórico de Operações
|
|
|
|
```http
|
|
GET /api/pdf/history?page=1&limit=20
|
|
Authorization: Bearer {token}
|
|
```
|
|
|
|
#### 9. Download de Arquivo Processado
|
|
|
|
```http
|
|
GET /api/pdf/download/:operacaoId/:fileIndex
|
|
Authorization: Bearer {token}
|
|
```
|
|
|
|
## Estrutura de Resposta
|
|
|
|
### Sucesso (Compressão)
|
|
...],
|
|
"outputPaths": ["/path/compressed-1.pdf", "/path/compressed-2.pdf"],
|
|
"totalOriginalSize": 1048576,
|
|
"totalCompressedSize": 314572,
|
|
"reduction": 70.0,
|
|
"processingTime": 1234
|
|
}
|
|
```
|
|
|
|
### Sucesso (Split/PDF para Imagens - com ZIP)
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"operacaoId": "uuid",
|
|
"filesCreated": 10,
|
|
"outputPaths": ["/path/page-1.pdf", "/path/page-2.pdf", ...],
|
|
"zipPath": "/path/arquivos-uuid.zip",
|
|
"processingTime": 2345
|
|
],
|
|
"totalOriginalSize": 1048576,
|
|
"totalCompressedSize": 524288,
|
|
"totalReduction": 50.0,
|
|
"processingTime": 1234
|
|
}
|
|
```
|
|
|
|
### Sucesso (OCR)
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"operacaoId": "uuid",
|
|
"result": {
|
|
"text": "Texto extraído completo...",
|
|
"confidence": 95.5,
|
|
"pages": [
|
|
{
|
|
"pageNumber": 1,
|
|
"text": "Texto da página 1...",
|
|
"confidence": 96.2
|
|
}
|
|
]
|
|
},
|
|
"processingTime": 5678
|
|
}
|
|
```
|
|
|
|
### Erro
|
|
|
|
```json
|
|
{
|
|
"error": "Falha ao processar arquivo",
|
|
"message": "Detalhes específicos do erro"
|
|
}
|
|
```
|
|
|
|
## Modelo de Dados
|
|
|
|
### OperacaoPDF
|
|
|
|
```typescript
|
|
interface OperacaoPDF {
|
|
id: string;
|
|
tipoOperacao: 'COMPRIMIR' | 'DIVIDIR' | 'MESCLAR' | 'PDF_PARA_IMAGEM' | 'IMAGEM_PARA_PDF' | 'OCR';
|
|
status: 'PROCESSANDO' | 'CONCLUIDO' | 'ERRO';
|
|
usuarioId: string;
|
|
arquivosOriginais: string[];
|
|
arquivosResultado: string[];
|
|
tamanhoOriginal: bigint;
|
|
tamanhoResultado: bigint | null;
|
|
parametros: any;
|
|
mensagemErro: string | null;
|
|
detalhesOCR: any | null;
|
|
tempoProcessamento: number | null;
|
|
criadoEm: Date;
|
|
atualizadoEm: Date;
|
|
}
|
|
```
|
|
|
|
## Segurança
|
|
|
|
- ✅ Autenticação obrigatória em todas as rotas
|
|
- ✅ Validação de tipos de arquivo (apenas PDF e imagens)
|
|
- ✅ Limite de tamanho de arquivo (100MB por arquivo)
|
|
- ✅ Limite de quantidade de arquivos por operação
|
|
- ✅ Sanitização de nomes de arquivo
|
|
- ✅ Isolamento de arquivos por operação
|
|
- ✅ Limpeza automática de arquivos temporários
|
|
|
|
## Performance
|
|
|
|
- ✅ Processamento assíncrono
|
|
- ✅ Otimização de memória com streams
|
|
- ✅ Compressão sem perda de qualidade perceptível
|
|
- ✅ Cache de workers de OCR
|
|
- ✅ Índices no banco de dados para consultas rápidas
|
|
- ✅ Limpeza automática de arquivos antigos
|
|
|
|
## Tratamento de Erros
|
|
|
|
Todos os erros são:
|
|
- Logados no console do servidor
|
|
- Registrados no banco de dados (tabela operacoes_pdf)
|
|
- Retornados ao cliente com mensagens descritivas
|
|
- Capturados por try-catch em todas as operações
|
|
|
|
## Limitações
|
|
|
|
- Tamanho máximo por arquivo: 100MB
|
|
- Máximo de arquivos para compressão: 10
|
|
- Máximo de arquivos para mesclagem: 50
|
|
- Máximo de imagens para conversão em PDF: 100
|
|
- Arquivos processados são mantidos por 24 horas
|
|
- **Timeout de operações:** 10 minutos (600 segundos)
|
|
- Operações longas (OCR, compressão de PDFs grandes) podem levar alguns minutos
|
|
- O sistema está configurado para suportar até 10 minutos de processamento
|
|
|
|
## Manutenção
|
|
|
|
### Limpeza de Arquivos Antigos
|
|
|
|
Execute periodicamente (recomendado: diariamente via cron):
|
|
|
|
```typescript
|
|
import { PDFService } from './src/services/pdfService';
|
|
|
|
// Remove arquivos com mais de 24 horas
|
|
await PDFService.cleanupOldFiles(24);
|
|
```
|
|
|
|
### Monitoramento
|
|
|
|
Consulte a tabela `operacoes_pdf` para:
|
|
- Estatísticas de uso
|
|
- Taxa de erro
|
|
- Tempo médio de processamento
|
|
- Operações mais utilizadas
|
|
|
|
```sql
|
|
-- Operações por tipo
|
|
SELECT "tipoOperacao", COUNT(*) as total
|
|
FROM operacoes_pdf
|
|
GROUP BY "tipoOperacao"
|
|
ORDER BY total DESC;
|
|
|
|
-- Taxa de sucesso
|
|
SELECT
|
|
status,
|
|
COUNT(*) as total,
|
|
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) as percentual
|
|
FROM operacoes_pdf
|
|
GROUP BY status;
|
|
|
|
-- Tempo médio por operação
|
|
SELECT
|
|
"tipoOperacao",
|
|
AVG("tempoProcessamento") as tempo_medio_ms,
|
|
MIN("tempoProcessamento") as tempo_min_ms,
|
|
MAX("tempoProcessamento") as tempo_max_ms
|
|
FROM operacoes_pdf
|
|
WHERE status = 'CONCLUIDO'
|
|
GROUP BY "tipoOperacao";
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Erro: "Could not execute GraphicsMagick/ImageMagick"
|
|
|
|
**Sintoma:** Erro ao converter PDF para imagens:
|
|
```
|
|
Error: Could not execute GraphicsMagick/ImageMagick: gm "convert" ...
|
|
this most likely means the gm/convert binaries can't be found
|
|
```
|
|
|
|
**Causa:** GraphicsMagick não está instalado no sistema/container.
|
|
|
|
**Solução:**
|
|
|
|
**Docker:**
|
|
```bash
|
|
# Rebuild a imagem Docker com GraphicsMagick incluído
|
|
docker-compose down
|
|
docker-compose build --no-cache
|
|
docker-compose up -d
|
|
```
|
|
|
|
**Local:**
|
|
```bash
|
|
# Ubuntu/Debian
|
|
sudo apt-get install graphicsmagick
|
|
|
|
# MacOS
|
|
brew install graphicsmagick
|
|
|
|
# Verifique a instalação
|
|
gm version
|
|
```
|
|
|
|
### Erro 504: Gateway Timeout (Timeout ao processar PDF)
|
|
|
|
**Sintoma:** Operação de compressão ou OCR retorna erro 504 após ~60 segundos:
|
|
```
|
|
Failed to load resource: the server responded with a status of 504
|
|
```
|
|
|
|
**Causa:** Operações de PDF (especialmente compressão e OCR) podem levar vários minutos para arquivos grandes. O timeout padrão de 60 segundos é insuficiente.
|
|
|
|
**Solução:** As configurações de timeout foram aumentadas para **10 minutos** em:
|
|
- ✅ Express server (`src/server/index.ts`)
|
|
- ✅ Vite proxy (`vite.config.ts`)
|
|
- ✅ Axios frontend (`PDFManipulationPage.tsx`)
|
|
|
|
Se ainda ocorrer timeout:
|
|
1. Verifique se rebuild foi feito após as alterações
|
|
2. Reduza o tamanho do arquivo ou qualidade
|
|
3. Processe menos arquivos por vez
|
|
4. Para PDFs muito grandes (>50MB), considere fazer upload em partes
|
|
|
|
**Dicas de performance:**
|
|
- Compressão: Use qualidade 50-70 para PDFs grandes
|
|
- OCR: Processe apenas páginas necessárias, não o PDF inteiro
|
|
- Split: Use chunks menores para PDFs com muitas páginas
|
|
|
|
### Erro: ENOENT ao deletar arquivos do histórico
|
|
|
|
**Sintoma:** Erro ao limpar histórico:
|
|
```
|
|
Error: ENOENT: no such file or directory, unlink '/path/to/file.pdf'
|
|
```
|
|
|
|
**Causa:** Tentativa de deletar arquivos que não existem mais (já foram removidos manualmente ou por outro processo).
|
|
|
|
**Solução:** ✅ **Corrigido automaticamente** - O sistema agora verifica se o arquivo existe antes de tentar deletar e ignora erros ENOENT silenciosamente.
|
|
|
|
### Erro: Imagens baixadas com extensão .pdf (ou sem extensão correta)
|
|
|
|
**Sintoma:** Ao baixar imagens geradas pela conversão PDF→Imagens, os arquivos vêm com extensão .pdf ou precisam ser renomeados para .png/.jpg para serem visualizados.
|
|
|
|
**Causa:** O servidor não estava enviando o Content-Type correto no header HTTP, OU o frontend estava ignorando os headers do servidor e forçando o nome como `.pdf`.
|
|
|
|
**Solução:** ✅ **Corrigido automaticamente** - O sistema agora:
|
|
|
|
**Backend:**
|
|
- Detecta automaticamente o tipo MIME correto baseado na extensão (.png, .jpg, .webp, .zip, .pdf)
|
|
- Define headers HTTP apropriados (`Content-Type` e `Content-Disposition`)
|
|
- Garante que o nome do arquivo no download mantém a extensão original
|
|
- Adiciona logs de debug para facilitar troubleshooting
|
|
|
|
**Frontend:**
|
|
- Extrai o nome do arquivo do header `Content-Disposition` enviado pelo servidor
|
|
- Usa o `Content-Type` correto do servidor ao criar o Blob para download
|
|
- Não força mais extensão `.pdf` em todos os downloads
|
|
- Adiciona log no console do browser: `Download frontend: arquivo.png (image/png)`
|
|
- Libera memória do blob URL após download
|
|
|
|
Se ainda ocorrer o problema:
|
|
1. Abra o Console do DevTools (F12) e verifique os logs de download
|
|
2. Verifique se os headers estão sendo recebidos corretamente na aba Network
|
|
3. Limpe o cache do browser (Ctrl+Shift+Del)
|
|
4. Teste em uma aba anônima/privada
|
|
5. Use a opção de **Download ZIP** que sempre funciona corretamente
|
|
|
|
### Erro: "Tesseract worker failed"
|
|
|
|
Solução: Certifique-se de que o Tesseract está instalado corretamente:
|
|
|
|
```bash
|
|
npm install tesseract.js --force
|
|
```
|
|
|
|
### Erro: "Cannot find module 'sharp'"
|
|
|
|
Solução: Reinstale o Sharp com rebuild:
|
|
|
|
```bash
|
|
npm uninstall sharp
|
|
npm install sharp
|
|
npm rebuild sharp
|
|
```
|
|
|
|
### Erro: "ENOSPC: no space left on device"
|
|
|
|
Solução: Aumente o espaço em disco ou execute a limpeza:
|
|
|
|
```bash
|
|
npm run cleanup-pdf-files
|
|
```
|
|
|
|
### Performance lenta no OCR
|
|
|
|
Solução: Reduza o DPI ou processe menos páginas por vez.
|
|
|
|
## Suporte
|
|
|
|
Para questões ou problemas:
|
|
1. Verifique os logs do servidor
|
|
2. Consulte a tabela `operacoes_pdf` para detalhes do erro
|
|
3. Verifique se os diretórios de storage têm permissões corretas
|
|
4. Certifique-se de que todas as dependências estão instaladas
|
|
|
|
## Changelog
|
|
|
|
### v1.1.2 (2026-02-27)
|
|
- 🔧 **Correção DEFINITIVA de downloads** - Arquivos agora baixam com nome e extensão corretos
|
|
- **Backend:** Detecta tipo MIME e define headers HTTP apropriados (Content-Type, Content-Disposition)
|
|
- **Frontend:** Extrai nome do arquivo e tipo MIME dos headers da resposta
|
|
- **Frontend:** Não força mais `.pdf` em todos os downloads - usa nome real do servidor
|
|
- **Frontend:** Cria Blob com Content-Type correto preservando o formato original
|
|
- Adiciona logs de debug no backend e frontend para troubleshooting
|
|
- Suporta formato 'jpeg' além de 'jpg'
|
|
- Fallback para PNG em formatos não suportados
|
|
- Libera memória de blob URLs após download
|
|
- 🐛 **Melhorias no pdfToImages** - Adicionado suporte para 'jpeg' e fallback para formatos inválidos
|
|
|
|
### v1.1.1 (2026-02-27)
|
|
- 🔧 **Correção de timeout 504** - Aumentado timeout para 10 minutos em todas as camadas
|
|
- Express server: 600 segundos
|
|
- Vite proxy: 600 segundos
|
|
- Axios frontend: 600 segundos
|
|
- 🗑️ **Correção ENOENT** - Sistema agora ignora arquivos já deletados ao limpar histórico
|
|
- 📋 **Gerenciamento de histórico** - Botões para limpar histórico completo ou arquivos antigos
|
|
- 🐛 **Melhorias de estabilidade** - Verificação de existência de arquivos antes de deletar
|
|
- 📚 **Documentação expandida** - Adicionado troubleshooting completo
|
|
|
|
### v1.1.0 (2026-02-27)
|
|
- 🚀 **Compressão agressiva** com redução >60% do tamanho original
|
|
- 📦 **Download em ZIP** para operações com múltiplos arquivos (split, pdfToImages)
|
|
- 🔍 **OCR embutido** - opção de tornar PDFs pesquisáveis (compress, merge, imagesToPDF)
|
|
- ⚡ **Otimização de imagens** com qualidade ajustável (30-100%)
|
|
- 🎯 **Interface aprimorada** com indicadores visuais e opções de OCR
|
|
|
|
### v1.0.0 (2026-02-27)
|
|
- ✅ Implementação inicial
|
|
- ✅ Compressão sem perda de qualidade
|
|
- ✅ Divisão de PDFs
|
|
- ✅ Mesclagem de PDFs
|
|
- ✅ Conversão PDF para imagens
|
|
- ✅ Conversão de imagens para PDF
|
|
- ✅ OCR com Tesseract
|
|
- ✅ Histórico de operações
|
|
- ✅ Interface React completa
|
|
- ✅ Validação e segurança
|