feat: Implémentation du système de cache JSON et de hash pour les uploads
- Ajout du système de hash SHA-256 pour éviter les doublons d'upload - Implémentation du cache JSON pour sauvegarder les résultats d'extraction - Nouvelles fonctions: calculateFileHash, findExistingFileByHash, saveJsonCache, getJsonCache - Nouvelles routes API: /api/cache, /api/cache/:hash, /api/uploads - Optimisation des performances: réutilisation des résultats en cache - Documentation mise à jour: API_BACKEND.md et nouveau fichier HASH_SYSTEM.md - Ajout du dossier cache/ au .gitignore
This commit is contained in:
parent
ab83be605e
commit
c6b5767d5d
3
.gitignore
vendored
3
.gitignore
vendored
@ -24,4 +24,5 @@ dist-ssr
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
test-files/
|
test-files/
|
||||||
uploads/
|
uploads/
|
||||||
|
cache/
|
||||||
@ -10,6 +10,7 @@ const multer = require('multer')
|
|||||||
const cors = require('cors')
|
const cors = require('cors')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
const crypto = require('crypto')
|
||||||
const { createWorker } = require('tesseract.js')
|
const { createWorker } = require('tesseract.js')
|
||||||
const { preprocessImageForOCR, analyzeImageMetadata } = require('./imagePreprocessing')
|
const { preprocessImageForOCR, analyzeImageMetadata } = require('./imagePreprocessing')
|
||||||
const pdf = require('pdf-parse')
|
const pdf = require('pdf-parse')
|
||||||
@ -22,7 +23,92 @@ app.use(cors())
|
|||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
app.use(express.static('public'))
|
app.use(express.static('public'))
|
||||||
|
|
||||||
// Configuration multer pour l'upload de fichiers
|
// Fonction pour calculer le hash d'un fichier
|
||||||
|
function calculateFileHash(buffer) {
|
||||||
|
return crypto.createHash('sha256').update(buffer).digest('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour vérifier si un fichier existe déjà par hash
|
||||||
|
function findExistingFileByHash(hash) {
|
||||||
|
const uploadDir = 'uploads/'
|
||||||
|
if (!fs.existsSync(uploadDir)) return null
|
||||||
|
|
||||||
|
const files = fs.readdirSync(uploadDir)
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(uploadDir, file)
|
||||||
|
try {
|
||||||
|
const fileBuffer = fs.readFileSync(filePath)
|
||||||
|
const fileHash = calculateFileHash(fileBuffer)
|
||||||
|
if (fileHash === hash) {
|
||||||
|
return { path: filePath, name: file }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`[HASH] Erreur lors de la lecture de ${file}:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour sauvegarder le cache JSON
|
||||||
|
function saveJsonCache(hash, result) {
|
||||||
|
const cacheDir = 'cache/'
|
||||||
|
if (!fs.existsSync(cacheDir)) {
|
||||||
|
fs.mkdirSync(cacheDir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheFile = path.join(cacheDir, `${hash}.json`)
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(cacheFile, JSON.stringify(result, null, 2))
|
||||||
|
console.log(`[CACHE] Résultat sauvegardé: ${hash.substring(0, 16)}...`)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[CACHE] Erreur lors de la sauvegarde:`, error.message)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour récupérer le cache JSON
|
||||||
|
function getJsonCache(hash) {
|
||||||
|
const cacheFile = path.join('cache/', `${hash}.json`)
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(cacheFile)) {
|
||||||
|
const cachedData = fs.readFileSync(cacheFile, 'utf8')
|
||||||
|
const result = JSON.parse(cachedData)
|
||||||
|
console.log(`[CACHE] Résultat récupéré: ${hash.substring(0, 16)}...`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`[CACHE] Erreur lors de la lecture du cache:`, error.message)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour lister les fichiers de cache
|
||||||
|
function listCacheFiles() {
|
||||||
|
const cacheDir = 'cache/'
|
||||||
|
if (!fs.existsSync(cacheDir)) return []
|
||||||
|
|
||||||
|
const files = fs.readdirSync(cacheDir)
|
||||||
|
return files.map(file => {
|
||||||
|
const filePath = path.join(cacheDir, file)
|
||||||
|
try {
|
||||||
|
const stats = fs.statSync(filePath)
|
||||||
|
const hash = path.basename(file, '.json')
|
||||||
|
return {
|
||||||
|
hash: hash,
|
||||||
|
fileName: file,
|
||||||
|
size: stats.size,
|
||||||
|
createdDate: stats.birthtime,
|
||||||
|
modifiedDate: stats.mtime
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`[CACHE] Erreur lors de la lecture de ${file}:`, error.message)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}).filter(file => file !== null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration multer pour l'upload de fichiers avec gestion des doublons
|
||||||
const storage = multer.diskStorage({
|
const storage = multer.diskStorage({
|
||||||
destination: (req, file, cb) => {
|
destination: (req, file, cb) => {
|
||||||
const uploadDir = 'uploads/'
|
const uploadDir = 'uploads/'
|
||||||
@ -32,8 +118,11 @@ const storage = multer.diskStorage({
|
|||||||
cb(null, uploadDir)
|
cb(null, uploadDir)
|
||||||
},
|
},
|
||||||
filename: (req, file, cb) => {
|
filename: (req, file, cb) => {
|
||||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
|
// Utiliser le nom original avec timestamp pour éviter les conflits
|
||||||
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname))
|
const timestamp = Date.now()
|
||||||
|
const ext = path.extname(file.originalname)
|
||||||
|
const name = path.basename(file.originalname, ext)
|
||||||
|
cb(null, `${name}-${timestamp}${ext}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -641,6 +730,42 @@ app.post('/api/extract', upload.single('document'), async (req, res) => {
|
|||||||
|
|
||||||
console.log(`[API] Traitement du fichier: ${req.file.originalname}`)
|
console.log(`[API] Traitement du fichier: ${req.file.originalname}`)
|
||||||
|
|
||||||
|
// Calculer le hash du fichier uploadé
|
||||||
|
const fileBuffer = fs.readFileSync(req.file.path)
|
||||||
|
const fileHash = calculateFileHash(fileBuffer)
|
||||||
|
console.log(`[HASH] Hash du fichier: ${fileHash.substring(0, 16)}...`)
|
||||||
|
|
||||||
|
// Vérifier d'abord le cache JSON
|
||||||
|
const cachedResult = getJsonCache(fileHash)
|
||||||
|
if (cachedResult) {
|
||||||
|
console.log(`[CACHE] Utilisation du résultat en cache`)
|
||||||
|
|
||||||
|
// Supprimer le fichier temporaire
|
||||||
|
fs.unlinkSync(req.file.path)
|
||||||
|
|
||||||
|
// Retourner le résultat en cache
|
||||||
|
return res.json(cachedResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si un fichier avec le même hash existe déjà
|
||||||
|
const existingFile = findExistingFileByHash(fileHash)
|
||||||
|
let isDuplicate = false
|
||||||
|
let duplicatePath = null
|
||||||
|
|
||||||
|
if (existingFile) {
|
||||||
|
console.log(`[HASH] Fichier déjà existant trouvé: ${existingFile.name}`)
|
||||||
|
isDuplicate = true
|
||||||
|
|
||||||
|
// Sauvegarder le chemin du doublon pour suppression ultérieure
|
||||||
|
duplicatePath = req.file.path
|
||||||
|
|
||||||
|
// Utiliser le fichier existant pour le traitement
|
||||||
|
req.file.path = existingFile.path
|
||||||
|
req.file.originalname = existingFile.name
|
||||||
|
} else {
|
||||||
|
console.log(`[HASH] Nouveau fichier, traitement normal`)
|
||||||
|
}
|
||||||
|
|
||||||
let ocrResult
|
let ocrResult
|
||||||
|
|
||||||
// Si c'est un PDF, extraire le texte directement
|
// Si c'est un PDF, extraire le texte directement
|
||||||
@ -671,8 +796,17 @@ app.post('/api/extract', upload.single('document'), async (req, res) => {
|
|||||||
// Génération du format JSON standard
|
// Génération du format JSON standard
|
||||||
const result = generateStandardJSON(req.file, ocrResult, entities, processingTime)
|
const result = generateStandardJSON(req.file, ocrResult, entities, processingTime)
|
||||||
|
|
||||||
|
// Sauvegarder le résultat dans le cache
|
||||||
|
saveJsonCache(fileHash, result)
|
||||||
|
|
||||||
// Nettoyage du fichier temporaire
|
// Nettoyage du fichier temporaire
|
||||||
fs.unlinkSync(req.file.path)
|
if (isDuplicate) {
|
||||||
|
// Supprimer le doublon uploadé
|
||||||
|
fs.unlinkSync(duplicatePath)
|
||||||
|
} else {
|
||||||
|
// Supprimer le fichier temporaire normal
|
||||||
|
fs.unlinkSync(req.file.path)
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`[API] Traitement terminé avec succès - Confiance: ${Math.round(result.metadata.quality.globalConfidence * 100)}%`)
|
console.log(`[API] Traitement terminé avec succès - Confiance: ${Math.round(result.metadata.quality.globalConfidence * 100)}%`)
|
||||||
|
|
||||||
@ -721,6 +855,98 @@ app.get('/api/test-files', (req, res) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Route de santé
|
// Route de santé
|
||||||
|
// Route pour lister les fichiers uploadés avec leurs hash
|
||||||
|
app.get('/api/uploads', (req, res) => {
|
||||||
|
try {
|
||||||
|
const uploadDir = 'uploads/'
|
||||||
|
if (!fs.existsSync(uploadDir)) {
|
||||||
|
return res.json({ files: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(uploadDir)
|
||||||
|
const fileList = files.map(file => {
|
||||||
|
const filePath = path.join(uploadDir, file)
|
||||||
|
try {
|
||||||
|
const stats = fs.statSync(filePath)
|
||||||
|
const fileBuffer = fs.readFileSync(filePath)
|
||||||
|
const hash = calculateFileHash(fileBuffer)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: file,
|
||||||
|
size: stats.size,
|
||||||
|
hash: hash,
|
||||||
|
uploadDate: stats.birthtime,
|
||||||
|
modifiedDate: stats.mtime
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`[API] Erreur lors de la lecture de ${file}:`, error.message)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}).filter(file => file !== null)
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
files: fileList,
|
||||||
|
count: fileList.length,
|
||||||
|
totalSize: fileList.reduce((sum, file) => sum + file.size, 0)
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[API] Erreur lors de la liste des fichiers:', error)
|
||||||
|
res.status(500).json({ error: 'Erreur lors de la récupération des fichiers' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Route pour lister les fichiers de cache JSON
|
||||||
|
app.get('/api/cache', (req, res) => {
|
||||||
|
try {
|
||||||
|
const cacheFiles = listCacheFiles()
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
files: cacheFiles,
|
||||||
|
count: cacheFiles.length,
|
||||||
|
totalSize: cacheFiles.reduce((sum, file) => sum + file.size, 0)
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[API] Erreur lors de la liste du cache:', error)
|
||||||
|
res.status(500).json({ error: 'Erreur lors de la récupération du cache' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Route pour récupérer un résultat de cache spécifique
|
||||||
|
app.get('/api/cache/:hash', (req, res) => {
|
||||||
|
try {
|
||||||
|
const { hash } = req.params
|
||||||
|
const cachedResult = getJsonCache(hash)
|
||||||
|
|
||||||
|
if (cachedResult) {
|
||||||
|
res.json(cachedResult)
|
||||||
|
} else {
|
||||||
|
res.status(404).json({ error: 'Résultat non trouvé dans le cache' })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[API] Erreur lors de la récupération du cache:', error)
|
||||||
|
res.status(500).json({ error: 'Erreur lors de la récupération du cache' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Route pour supprimer un fichier de cache
|
||||||
|
app.delete('/api/cache/:hash', (req, res) => {
|
||||||
|
try {
|
||||||
|
const { hash } = req.params
|
||||||
|
const cacheFile = path.join('cache/', `${hash}.json`)
|
||||||
|
|
||||||
|
if (fs.existsSync(cacheFile)) {
|
||||||
|
fs.unlinkSync(cacheFile)
|
||||||
|
console.log(`[CACHE] Fichier supprimé: ${hash.substring(0, 16)}...`)
|
||||||
|
res.json({ message: 'Fichier de cache supprimé avec succès' })
|
||||||
|
} else {
|
||||||
|
res.status(404).json({ error: 'Fichier de cache non trouvé' })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[API] Erreur lors de la suppression du cache:', error)
|
||||||
|
res.status(500).json({ error: 'Erreur lors de la suppression du cache' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
status: 'OK',
|
status: 'OK',
|
||||||
|
|||||||
@ -11,6 +11,7 @@ L'API Backend 4NK_IA est un service d'extraction et d'analyse de documents utili
|
|||||||
- ✅ **NER intelligent** : Reconnaissance d'entités par règles
|
- ✅ **NER intelligent** : Reconnaissance d'entités par règles
|
||||||
- ✅ **Format JSON standardisé** : Structure cohérente pour tous les documents
|
- ✅ **Format JSON standardisé** : Structure cohérente pour tous les documents
|
||||||
- ✅ **Préprocessing d'images** : Amélioration automatique de la qualité OCR
|
- ✅ **Préprocessing d'images** : Amélioration automatique de la qualité OCR
|
||||||
|
- ✅ **Gestion des doublons** : Système de hash SHA-256 pour éviter les fichiers dupliqués
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -63,7 +64,42 @@ Retourne la liste des fichiers de test disponibles.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔍 **3. Extraction de documents**
|
## 📂 **3. Liste des fichiers uploadés**
|
||||||
|
|
||||||
|
### **GET** `/api/uploads`
|
||||||
|
|
||||||
|
Retourne la liste des fichiers uploadés avec leurs métadonnées et hash SHA-256.
|
||||||
|
|
||||||
|
**Réponse :**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "document-1757980637671.pdf",
|
||||||
|
"size": 1015808,
|
||||||
|
"hash": "a1b2c3d4e5f6...",
|
||||||
|
"uploadDate": "2025-09-15T23:56:14.751Z",
|
||||||
|
"modifiedDate": "2025-09-15T23:56:14.751Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"count": 1,
|
||||||
|
"totalSize": 1015808
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exemple d'utilisation :**
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3001/api/uploads
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes :**
|
||||||
|
- Le hash SHA-256 permet d'identifier les fichiers identiques
|
||||||
|
- Les fichiers dupliqués sont automatiquement détectés lors de l'upload
|
||||||
|
- Seuls les fichiers uniques sont conservés dans le système
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 **4. Extraction de documents**
|
||||||
|
|
||||||
### **POST** `/api/extract`
|
### **POST** `/api/extract`
|
||||||
|
|
||||||
@ -73,6 +109,12 @@ Extrait et analyse un document (PDF ou image) pour identifier les entités et in
|
|||||||
- **`document`** (file, required) : Fichier à analyser (PDF, JPEG, PNG, TIFF)
|
- **`document`** (file, required) : Fichier à analyser (PDF, JPEG, PNG, TIFF)
|
||||||
- **Taille maximale :** 10MB
|
- **Taille maximale :** 10MB
|
||||||
|
|
||||||
|
#### **Gestion des doublons :**
|
||||||
|
- Le système calcule automatiquement un hash SHA-256 de chaque fichier uploadé
|
||||||
|
- Si un fichier avec le même hash existe déjà, le doublon est supprimé
|
||||||
|
- Le traitement utilise le fichier existant, évitant ainsi les calculs redondants
|
||||||
|
- Les logs indiquent clairement si un fichier est nouveau ou déjà existant
|
||||||
|
|
||||||
#### **Réponse - Format JSON Standard :**
|
#### **Réponse - Format JSON Standard :**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
168
docs/HASH_SYSTEM.md
Normal file
168
docs/HASH_SYSTEM.md
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
# 🔐 Système de Hash pour la Gestion des Doublons
|
||||||
|
|
||||||
|
## 📋 **Vue d'ensemble**
|
||||||
|
|
||||||
|
Le système de hash SHA-256 a été implémenté dans le backend 4NK_IA pour éviter les doublons d'upload et optimiser le stockage des fichiers. Ce système garantit qu'un fichier identique ne sera traité qu'une seule fois, même s'il est uploadé plusieurs fois.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 **Fonctionnement**
|
||||||
|
|
||||||
|
### **1. Calcul du Hash**
|
||||||
|
- Chaque fichier uploadé est analysé pour calculer son hash SHA-256
|
||||||
|
- Le hash est calculé sur le contenu binaire complet du fichier
|
||||||
|
- Utilisation de la fonction `crypto.createHash('sha256')` de Node.js
|
||||||
|
|
||||||
|
### **2. Détection des Doublons**
|
||||||
|
- Avant traitement, le système vérifie si un fichier avec le même hash existe déjà
|
||||||
|
- La fonction `findExistingFileByHash()` parcourt le dossier `uploads/`
|
||||||
|
- Si un doublon est trouvé, le fichier uploadé est supprimé
|
||||||
|
|
||||||
|
### **3. Traitement Optimisé**
|
||||||
|
- Le traitement utilise le fichier existant (pas le doublon)
|
||||||
|
- Les résultats d'extraction sont identiques pour les fichiers identiques
|
||||||
|
- Économie de ressources CPU et de stockage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 **Structure des Fichiers**
|
||||||
|
|
||||||
|
```
|
||||||
|
uploads/
|
||||||
|
├── document-1757980637671.pdf # Fichier original
|
||||||
|
├── document-1757980640576.pdf # Doublon (supprimé après traitement)
|
||||||
|
└── image-1757980651234.jpg # Autre fichier unique
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 **API Endpoints**
|
||||||
|
|
||||||
|
### **GET** `/api/uploads`
|
||||||
|
Liste tous les fichiers uploadés avec leurs métadonnées :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "document-1757980637671.pdf",
|
||||||
|
"size": 1015808,
|
||||||
|
"hash": "a1b2c3d4e5f6789...",
|
||||||
|
"uploadDate": "2025-09-15T23:56:14.751Z",
|
||||||
|
"modifiedDate": "2025-09-15T23:56:14.751Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"count": 1,
|
||||||
|
"totalSize": 1015808
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 **Logs et Monitoring**
|
||||||
|
|
||||||
|
### **Logs de Hash**
|
||||||
|
```
|
||||||
|
[HASH] Hash du fichier: a1b2c3d4e5f6789...
|
||||||
|
[HASH] Fichier déjà existant trouvé: document-1757980637671.pdf
|
||||||
|
[HASH] Nouveau fichier, traitement normal
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Indicateurs de Performance**
|
||||||
|
- **Temps de traitement réduit** pour les doublons
|
||||||
|
- **Stockage optimisé** (pas de fichiers redondants)
|
||||||
|
- **Logs clairs** pour le debugging
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ **Fonctions Techniques**
|
||||||
|
|
||||||
|
### **`calculateFileHash(buffer)`**
|
||||||
|
```javascript
|
||||||
|
function calculateFileHash(buffer) {
|
||||||
|
return crypto.createHash('sha256').update(buffer).digest('hex')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **`findExistingFileByHash(hash)`**
|
||||||
|
```javascript
|
||||||
|
function findExistingFileByHash(hash) {
|
||||||
|
const uploadDir = 'uploads/'
|
||||||
|
if (!fs.existsSync(uploadDir)) return null
|
||||||
|
|
||||||
|
const files = fs.readdirSync(uploadDir)
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(uploadDir, file)
|
||||||
|
try {
|
||||||
|
const fileBuffer = fs.readFileSync(filePath)
|
||||||
|
const fileHash = calculateFileHash(fileBuffer)
|
||||||
|
if (fileHash === hash) {
|
||||||
|
return { path: filePath, name: file }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`[HASH] Erreur lors de la lecture de ${file}:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ **Avantages**
|
||||||
|
|
||||||
|
1. **Économie de Stockage** : Pas de fichiers dupliqués
|
||||||
|
2. **Performance** : Traitement plus rapide pour les doublons
|
||||||
|
3. **Cohérence** : Résultats identiques pour fichiers identiques
|
||||||
|
4. **Monitoring** : Visibilité sur les fichiers stockés
|
||||||
|
5. **Sécurité** : Hash SHA-256 cryptographiquement sûr
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 **Workflow d'Upload**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Fichier Uploadé] --> B[Calcul Hash SHA-256]
|
||||||
|
B --> C{Hash Existe?}
|
||||||
|
C -->|Oui| D[Supprimer Doublon]
|
||||||
|
C -->|Non| E[Conserver Fichier]
|
||||||
|
D --> F[Utiliser Fichier Existant]
|
||||||
|
E --> G[Traitement Normal]
|
||||||
|
F --> H[Extraction & NER]
|
||||||
|
G --> H
|
||||||
|
H --> I[Résultat JSON]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 **Tests**
|
||||||
|
|
||||||
|
### **Test de Doublon**
|
||||||
|
```bash
|
||||||
|
# Premier upload
|
||||||
|
curl -X POST -F "document=@test.pdf" http://localhost:3001/api/extract
|
||||||
|
|
||||||
|
# Deuxième upload du même fichier
|
||||||
|
curl -X POST -F "document=@test.pdf" http://localhost:3001/api/extract
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Vérification**
|
||||||
|
```bash
|
||||||
|
# Lister les fichiers uploadés
|
||||||
|
curl http://localhost:3001/api/uploads
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 **Notes d'Implémentation**
|
||||||
|
|
||||||
|
- **Algorithme** : SHA-256 (256 bits)
|
||||||
|
- **Performance** : O(n) pour la recherche de doublons
|
||||||
|
- **Stockage** : Hash stocké en hexadécimal (64 caractères)
|
||||||
|
- **Compatibilité** : Fonctionne avec tous les types de fichiers supportés
|
||||||
|
- **Nettoyage** : Suppression automatique des doublons temporaires
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Documentation mise à jour le 15 septembre 2025*
|
||||||
Loading…
x
Reference in New Issue
Block a user