feat(backend): consolidation finale Spec 001 LCB-FT + Flyway V1-V5
Migrations Flyway (consolidées) : - V1 : Schéma complet (69 tables, 1322 lignes) - V2 : Colonnes BaseEntity (cree_par, modifie_par) - V3 : Colonnes métier manquantes (adresses, alert_configuration) - V4 : Correction system_logs (renommage colonnes, ajout timestamp) - V5 : Nettoyage alert_configuration (suppression colonnes obsolètes) - Suppression V2-V6 obsolètes (fragmentés) Entités LCB-FT : - AlerteLcbFt : Alertes anti-blanchiment - AlertConfiguration : Configuration alertes - SystemAlert : Alertes système - SystemLog : Logs techniques (DÉJÀ COMMITÉE avec super.onCreate fix) Services LCB-FT (T015, T016) : - AlerteLcbFtService + Resource : Dashboard alertes admin - AlertMonitoringService : Surveillance transactions - SystemLoggingService : Logs centralisés - FileStorageService : Upload documents Repositories : - AlerteLcbFtRepository - AlertConfigurationRepository - SystemAlertRepository - SystemLogRepository Tests : - GlobalExceptionMapperTest : 17 erreurs corrigées (toResponse()) Spec 001 : 27/27 tâches (100%) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.entity.AlertConfiguration;
|
||||
import dev.lions.unionflow.server.entity.SystemAlert;
|
||||
import dev.lions.unionflow.server.repository.AlertConfigurationRepository;
|
||||
import dev.lions.unionflow.server.repository.SystemAlertRepository;
|
||||
import dev.lions.unionflow.server.repository.SystemLogRepository;
|
||||
import io.quarkus.scheduler.Scheduled;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Service de monitoring automatique qui vérifie les métriques système
|
||||
* et génère des alertes automatiquement selon la configuration.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2026-03-15
|
||||
*/
|
||||
@Slf4j
|
||||
@ApplicationScoped
|
||||
public class AlertMonitoringService {
|
||||
|
||||
@Inject
|
||||
AlertConfigurationRepository alertConfigurationRepository;
|
||||
|
||||
@Inject
|
||||
SystemAlertRepository systemAlertRepository;
|
||||
|
||||
@Inject
|
||||
SystemLogRepository systemLogRepository;
|
||||
|
||||
// Stocker la dernière valeur CPU pour détecter les dépassements prolongés
|
||||
private Double lastCpuUsage = 0.0;
|
||||
private LocalDateTime lastCpuHighTime = null;
|
||||
|
||||
/**
|
||||
* Scheduler qui s'exécute toutes les minutes pour vérifier les métriques
|
||||
*/
|
||||
@Scheduled(cron = "0 * * * * ?") // Toutes les minutes à la seconde 0
|
||||
@Transactional
|
||||
public void monitorSystemMetrics() {
|
||||
try {
|
||||
log.debug("Running scheduled system metrics monitoring...");
|
||||
|
||||
AlertConfiguration config = alertConfigurationRepository.getConfiguration();
|
||||
|
||||
// Vérifier CPU
|
||||
if (config.getCpuHighAlertEnabled()) {
|
||||
checkCpuThreshold(config);
|
||||
}
|
||||
|
||||
// Vérifier mémoire
|
||||
if (config.getMemoryLowAlertEnabled()) {
|
||||
checkMemoryThreshold(config);
|
||||
}
|
||||
|
||||
// Vérifier erreurs critiques
|
||||
if (config.getCriticalErrorAlertEnabled()) {
|
||||
checkCriticalErrors();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error in scheduled system monitoring", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si le CPU dépasse le seuil configuré
|
||||
*/
|
||||
private void checkCpuThreshold(AlertConfiguration config) {
|
||||
try {
|
||||
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
|
||||
double loadAvg = osBean.getSystemLoadAverage();
|
||||
int processors = osBean.getAvailableProcessors();
|
||||
|
||||
// Calculer l'utilisation CPU en pourcentage
|
||||
double cpuUsage = loadAvg < 0 ? 0.0 : Math.min(100.0, (loadAvg / processors) * 100.0);
|
||||
lastCpuUsage = cpuUsage;
|
||||
|
||||
int threshold = config.getCpuThresholdPercent();
|
||||
int durationMinutes = config.getCpuDurationMinutes();
|
||||
|
||||
// Vérifier si le seuil est dépassé
|
||||
if (cpuUsage > threshold) {
|
||||
if (lastCpuHighTime == null) {
|
||||
lastCpuHighTime = LocalDateTime.now();
|
||||
} else {
|
||||
// Vérifier si le dépassement dure depuis assez longtemps
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
long minutesSinceHigh = java.time.Duration.between(lastCpuHighTime, now).toMinutes();
|
||||
|
||||
if (minutesSinceHigh >= durationMinutes) {
|
||||
// Créer une alerte seulement si pas déjà créée récemment
|
||||
if (!hasRecentCpuAlert()) {
|
||||
createCpuAlert(cpuUsage, threshold);
|
||||
log.warn("CPU alert created: {}% (threshold: {}%)", cpuUsage, threshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Reset le compteur si CPU revient sous le seuil
|
||||
lastCpuHighTime = null;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error checking CPU threshold", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si la mémoire dépasse le seuil configuré
|
||||
*/
|
||||
private void checkMemoryThreshold(AlertConfiguration config) {
|
||||
try {
|
||||
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
|
||||
long maxMemory = memoryBean.getHeapMemoryUsage().getMax();
|
||||
long usedMemory = memoryBean.getHeapMemoryUsage().getUsed();
|
||||
|
||||
double memoryUsage = maxMemory > 0 ? (usedMemory * 100.0 / maxMemory) : 0.0;
|
||||
int threshold = config.getMemoryThresholdPercent();
|
||||
|
||||
if (memoryUsage > threshold) {
|
||||
// Créer une alerte seulement si pas déjà créée récemment
|
||||
if (!hasRecentMemoryAlert()) {
|
||||
createMemoryAlert(memoryUsage, threshold);
|
||||
log.warn("Memory alert created: {}% (threshold: {}%)", memoryUsage, threshold);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error checking memory threshold", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier le nombre d'erreurs critiques dans la dernière heure
|
||||
*/
|
||||
private void checkCriticalErrors() {
|
||||
try {
|
||||
long criticalCount = systemLogRepository.countByLevelLast24h("CRITICAL");
|
||||
|
||||
// Si plus de 5 erreurs critiques dans les dernières 24h, créer une alerte
|
||||
if (criticalCount > 5) {
|
||||
if (!hasRecentCriticalErrorsAlert()) {
|
||||
createCriticalErrorsAlert(criticalCount);
|
||||
log.warn("Critical errors alert created: {} errors in last 24h", criticalCount);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error checking critical errors", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer une alerte CPU
|
||||
*/
|
||||
private void createCpuAlert(double currentValue, int threshold) {
|
||||
SystemAlert alert = new SystemAlert();
|
||||
alert.setLevel("WARNING");
|
||||
alert.setTitle("Utilisation CPU élevée");
|
||||
alert.setMessage(String.format("L'utilisation CPU dépasse le seuil configuré"));
|
||||
alert.setTimestamp(LocalDateTime.now());
|
||||
alert.setAcknowledged(false);
|
||||
alert.setSource("CPU");
|
||||
alert.setAlertType("THRESHOLD");
|
||||
alert.setCurrentValue(currentValue);
|
||||
alert.setThresholdValue((double) threshold);
|
||||
alert.setUnit("%");
|
||||
alert.setRecommendedActions("Vérifier les processus en cours d'exécution. Redémarrer les services si nécessaire.");
|
||||
|
||||
systemAlertRepository.persist(alert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer une alerte mémoire
|
||||
*/
|
||||
private void createMemoryAlert(double currentValue, int threshold) {
|
||||
SystemAlert alert = new SystemAlert();
|
||||
alert.setLevel("WARNING");
|
||||
alert.setTitle("Utilisation mémoire élevée");
|
||||
alert.setMessage(String.format("L'utilisation mémoire dépasse le seuil configuré"));
|
||||
alert.setTimestamp(LocalDateTime.now());
|
||||
alert.setAcknowledged(false);
|
||||
alert.setSource("MEMORY");
|
||||
alert.setAlertType("THRESHOLD");
|
||||
alert.setCurrentValue(currentValue);
|
||||
alert.setThresholdValue((double) threshold);
|
||||
alert.setUnit("%");
|
||||
alert.setRecommendedActions("Vérifier les applications consommant de la mémoire. Considérer l'augmentation des ressources.");
|
||||
|
||||
systemAlertRepository.persist(alert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer une alerte pour erreurs critiques
|
||||
*/
|
||||
private void createCriticalErrorsAlert(long errorCount) {
|
||||
SystemAlert alert = new SystemAlert();
|
||||
alert.setLevel("CRITICAL");
|
||||
alert.setTitle("Erreurs critiques détectées");
|
||||
alert.setMessage(String.format("%d erreurs critiques détectées dans les dernières 24h", errorCount));
|
||||
alert.setTimestamp(LocalDateTime.now());
|
||||
alert.setAcknowledged(false);
|
||||
alert.setSource("System");
|
||||
alert.setAlertType("ERROR");
|
||||
alert.setCurrentValue((double) errorCount);
|
||||
alert.setThresholdValue(5.0);
|
||||
alert.setUnit("erreurs");
|
||||
alert.setRecommendedActions("Consulter les logs système pour identifier la cause. Corriger les erreurs critiques.");
|
||||
|
||||
systemAlertRepository.persist(alert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si une alerte CPU a été créée récemment (dans les 30 dernières minutes)
|
||||
*/
|
||||
private boolean hasRecentCpuAlert() {
|
||||
LocalDateTime thirtyMinutesAgo = LocalDateTime.now().minusMinutes(30);
|
||||
return systemAlertRepository.findByTimestampBetween(thirtyMinutesAgo, LocalDateTime.now()).stream()
|
||||
.anyMatch(alert -> "CPU".equals(alert.getSource()) && !alert.getAcknowledged());
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si une alerte mémoire a été créée récemment (dans les 30 dernières minutes)
|
||||
*/
|
||||
private boolean hasRecentMemoryAlert() {
|
||||
LocalDateTime thirtyMinutesAgo = LocalDateTime.now().minusMinutes(30);
|
||||
return systemAlertRepository.findByTimestampBetween(thirtyMinutesAgo, LocalDateTime.now()).stream()
|
||||
.anyMatch(alert -> "MEMORY".equals(alert.getSource()) && !alert.getAcknowledged());
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si une alerte erreurs critiques a été créée récemment (dans la dernière heure)
|
||||
*/
|
||||
private boolean hasRecentCriticalErrorsAlert() {
|
||||
LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
|
||||
return systemAlertRepository.findByTimestampBetween(oneHourAgo, LocalDateTime.now()).stream()
|
||||
.anyMatch(alert -> "System".equals(alert.getSource()) && "ERROR".equals(alert.getAlertType()) && !alert.getAcknowledged());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.entity.AlerteLcbFt;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import dev.lions.unionflow.server.repository.AlerteLcbFtRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service pour la génération automatique d'alertes LCB-FT.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2026-03-15
|
||||
*/
|
||||
@Slf4j
|
||||
@ApplicationScoped
|
||||
public class AlerteLcbFtService {
|
||||
|
||||
@Inject
|
||||
AlerteLcbFtRepository alerteLcbFtRepository;
|
||||
|
||||
@Inject
|
||||
OrganisationRepository organisationRepository;
|
||||
|
||||
@Inject
|
||||
MembreRepository membreRepository;
|
||||
|
||||
/**
|
||||
* Génère une alerte lorsqu'un seuil LCB-FT est dépassé.
|
||||
*/
|
||||
@Transactional
|
||||
public void genererAlerteSeuilDepasse(
|
||||
UUID organisationId,
|
||||
UUID membreId,
|
||||
String typeOperation,
|
||||
BigDecimal montant,
|
||||
BigDecimal seuil,
|
||||
String transactionRef,
|
||||
String origineFonds
|
||||
) {
|
||||
try {
|
||||
Organisation organisation = organisationRepository.findByIdOptional(organisationId)
|
||||
.orElse(null);
|
||||
|
||||
Membre membre = membreId != null ? membreRepository.findByIdOptional(membreId).orElse(null) : null;
|
||||
|
||||
String description = String.format(
|
||||
"Transaction %s de %s FCFA dépassant le seuil LCB-FT de %s FCFA",
|
||||
typeOperation != null ? typeOperation : "inconnue",
|
||||
montant != null ? montant.toPlainString() : "0",
|
||||
seuil != null ? seuil.toPlainString() : "0"
|
||||
);
|
||||
|
||||
String details = String.format(
|
||||
"Référence: %s | Origine des fonds: %s | Écart: %s FCFA",
|
||||
transactionRef != null ? transactionRef : "N/A",
|
||||
origineFonds != null && !origineFonds.isBlank() ? origineFonds : "NON FOURNI",
|
||||
montant != null && seuil != null ? montant.subtract(seuil).toPlainString() : "N/A"
|
||||
);
|
||||
|
||||
// Déterminer la sévérité selon l'écart avec le seuil
|
||||
String severite = "INFO";
|
||||
if (montant != null && seuil != null) {
|
||||
BigDecimal ecart = montant.subtract(seuil);
|
||||
BigDecimal ratio = seuil.compareTo(BigDecimal.ZERO) > 0 ?
|
||||
ecart.divide(seuil, 2, BigDecimal.ROUND_HALF_UP) : BigDecimal.ZERO;
|
||||
|
||||
if (ratio.compareTo(new BigDecimal("2.0")) >= 0) { // > 200% du seuil
|
||||
severite = "CRITICAL";
|
||||
} else if (ratio.compareTo(new BigDecimal("0.5")) >= 0) { // > 50% du seuil
|
||||
severite = "WARNING";
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifier si origine des fonds est fournie
|
||||
if (origineFonds == null || origineFonds.isBlank()) {
|
||||
details += " | ⚠️ JUSTIFICATION MANQUANTE";
|
||||
severite = "WARNING"; // Toujours WARNING minimum si pas de justification
|
||||
}
|
||||
|
||||
AlerteLcbFt alerte = AlerteLcbFt.builder()
|
||||
.organisation(organisation)
|
||||
.membre(membre)
|
||||
.typeAlerte("SEUIL_DEPASSE")
|
||||
.dateAlerte(LocalDateTime.now())
|
||||
.description(description)
|
||||
.details(details)
|
||||
.montant(montant)
|
||||
.seuil(seuil)
|
||||
.typeOperation(typeOperation)
|
||||
.transactionRef(transactionRef)
|
||||
.severite(severite)
|
||||
.traitee(false)
|
||||
.build();
|
||||
|
||||
alerteLcbFtRepository.persist(alerte);
|
||||
|
||||
log.info("Alerte LCB-FT générée : {} - Sévérité: {} - Montant: {} FCFA",
|
||||
alerte.getId(), severite, montant);
|
||||
|
||||
} catch (Exception e) {
|
||||
// Ne pas bloquer la transaction si la génération d'alerte échoue
|
||||
log.error("Erreur lors de la génération d'alerte LCB-FT", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère une alerte pour justification manquante.
|
||||
*/
|
||||
@Transactional
|
||||
public void genererAlerteJustificationManquante(
|
||||
UUID organisationId,
|
||||
UUID membreId,
|
||||
String typeOperation,
|
||||
BigDecimal montant,
|
||||
String transactionRef
|
||||
) {
|
||||
try {
|
||||
Organisation organisation = organisationRepository.findByIdOptional(organisationId)
|
||||
.orElse(null);
|
||||
|
||||
Membre membre = membreId != null ? membreRepository.findByIdOptional(membreId).orElse(null) : null;
|
||||
|
||||
String description = String.format(
|
||||
"Origine des fonds non fournie pour %s de %s FCFA",
|
||||
typeOperation != null ? typeOperation : "transaction",
|
||||
montant != null ? montant.toPlainString() : "0"
|
||||
);
|
||||
|
||||
AlerteLcbFt alerte = AlerteLcbFt.builder()
|
||||
.organisation(organisation)
|
||||
.membre(membre)
|
||||
.typeAlerte("JUSTIFICATION_MANQUANTE")
|
||||
.dateAlerte(LocalDateTime.now())
|
||||
.description(description)
|
||||
.details("Référence: " + (transactionRef != null ? transactionRef : "N/A"))
|
||||
.montant(montant)
|
||||
.typeOperation(typeOperation)
|
||||
.transactionRef(transactionRef)
|
||||
.severite("WARNING")
|
||||
.traitee(false)
|
||||
.build();
|
||||
|
||||
alerteLcbFtRepository.persist(alerte);
|
||||
|
||||
log.warn("Alerte justification manquante générée pour transaction: {}", transactionRef);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la génération d'alerte justification manquante", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service de stockage de fichiers sur le système de fichiers.
|
||||
* Gère l'upload, le stockage, et le calcul des hash.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2026-03-15
|
||||
*/
|
||||
@Slf4j
|
||||
@ApplicationScoped
|
||||
public class FileStorageService {
|
||||
|
||||
// Taille max: 5 MB
|
||||
private static final long MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||||
|
||||
// Types MIME autorisés
|
||||
private static final String[] ALLOWED_MIME_TYPES = {
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"application/pdf"
|
||||
};
|
||||
|
||||
@ConfigProperty(name = "unionflow.upload.directory", defaultValue = "./uploads")
|
||||
String uploadDirectory;
|
||||
|
||||
/**
|
||||
* Stocke un fichier uploadé et retourne les métadonnées.
|
||||
*
|
||||
* @param inputStream Flux du fichier
|
||||
* @param fileName Nom du fichier original
|
||||
* @param mimeType Type MIME
|
||||
* @param fileSize Taille du fichier
|
||||
* @return Métadonnées du fichier stocké
|
||||
* @throws IOException Si erreur d'I/O
|
||||
* @throws IllegalArgumentException Si validation échoue
|
||||
*/
|
||||
public FileMetadata storeFile(InputStream inputStream, String fileName, String mimeType, long fileSize)
|
||||
throws IOException {
|
||||
|
||||
// Validation de la taille
|
||||
if (fileSize > MAX_FILE_SIZE) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Fichier trop volumineux. Taille max: %d MB", MAX_FILE_SIZE / (1024 * 1024))
|
||||
);
|
||||
}
|
||||
|
||||
// Validation du type MIME
|
||||
if (!isAllowedMimeType(mimeType)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Type de fichier non autorisé. Types acceptés: JPEG, PNG, GIF, PDF"
|
||||
);
|
||||
}
|
||||
|
||||
// Générer un nom unique pour le fichier
|
||||
String uniqueFileName = generateUniqueFileName(fileName);
|
||||
|
||||
// Créer le chemin de stockage (organisé par date: uploads/2026/03/15/)
|
||||
String relativePath = getRelativePath(uniqueFileName);
|
||||
Path targetPath = Paths.get(uploadDirectory, relativePath);
|
||||
|
||||
// Créer les répertoires si nécessaire
|
||||
Files.createDirectories(targetPath.getParent());
|
||||
|
||||
// Écrire le fichier et calculer les hash simultanément
|
||||
MessageDigest md5 = null;
|
||||
MessageDigest sha256 = null;
|
||||
try {
|
||||
md5 = MessageDigest.getInstance("MD5");
|
||||
sha256 = MessageDigest.getInstance("SHA-256");
|
||||
} catch (Exception e) {
|
||||
log.warn("Impossible de créer les MessageDigest pour hash: {}", e.getMessage());
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
long totalBytes = 0;
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(targetPath.toFile())) {
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, bytesRead);
|
||||
totalBytes += bytesRead;
|
||||
|
||||
// Calculer les hash
|
||||
if (md5 != null) {
|
||||
md5.update(buffer, 0, bytesRead);
|
||||
}
|
||||
if (sha256 != null) {
|
||||
sha256.update(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Générer les hash
|
||||
String hashMd5 = md5 != null ? bytesToHex(md5.digest()) : null;
|
||||
String hashSha256 = sha256 != null ? bytesToHex(sha256.digest()) : null;
|
||||
|
||||
log.info("Fichier stocké : {} ({} octets) - MD5: {}", uniqueFileName, totalBytes, hashMd5);
|
||||
|
||||
return FileMetadata.builder()
|
||||
.nomFichier(uniqueFileName)
|
||||
.nomOriginal(fileName)
|
||||
.cheminStockage(relativePath)
|
||||
.typeMime(mimeType)
|
||||
.tailleOctets(totalBytes)
|
||||
.hashMd5(hashMd5)
|
||||
.hashSha256(hashSha256)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le type MIME est autorisé
|
||||
*/
|
||||
private boolean isAllowedMimeType(String mimeType) {
|
||||
if (mimeType == null) {
|
||||
return false;
|
||||
}
|
||||
for (String allowed : ALLOWED_MIME_TYPES) {
|
||||
if (allowed.equalsIgnoreCase(mimeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un nom unique pour le fichier
|
||||
*/
|
||||
private String generateUniqueFileName(String originalFileName) {
|
||||
String extension = "";
|
||||
int lastDot = originalFileName.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
extension = originalFileName.substring(lastDot);
|
||||
}
|
||||
return UUID.randomUUID().toString() + extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le chemin relatif organisé par date (YYYY/MM/DD/)
|
||||
*/
|
||||
private String getRelativePath(String fileName) {
|
||||
LocalDate today = LocalDate.now();
|
||||
return String.format("%04d/%02d/%02d/%s",
|
||||
today.getYear(),
|
||||
today.getMonthValue(),
|
||||
today.getDayOfMonth(),
|
||||
fileName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit un tableau de bytes en hexadécimal
|
||||
*/
|
||||
private String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe pour encapsuler les métadonnées d'un fichier stocké
|
||||
*/
|
||||
@lombok.Data
|
||||
@lombok.Builder
|
||||
public static class FileMetadata {
|
||||
private String nomFichier;
|
||||
private String nomOriginal;
|
||||
private String cheminStockage;
|
||||
private String typeMime;
|
||||
private Long tailleOctets;
|
||||
private String hashMd5;
|
||||
private String hashSha256;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,15 @@ import dev.lions.unionflow.server.api.dto.logs.response.AlertConfigResponse;
|
||||
import dev.lions.unionflow.server.api.dto.logs.response.SystemAlertResponse;
|
||||
import dev.lions.unionflow.server.api.dto.logs.response.SystemLogResponse;
|
||||
import dev.lions.unionflow.server.api.dto.logs.response.SystemMetricsResponse;
|
||||
import dev.lions.unionflow.server.entity.AlertConfiguration;
|
||||
import dev.lions.unionflow.server.entity.SystemAlert;
|
||||
import dev.lions.unionflow.server.entity.SystemLog;
|
||||
import dev.lions.unionflow.server.repository.AlertConfigurationRepository;
|
||||
import dev.lions.unionflow.server.repository.SystemAlertRepository;
|
||||
import dev.lions.unionflow.server.repository.SystemLogRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
@@ -27,6 +35,15 @@ public class LogsMonitoringService {
|
||||
|
||||
private final LocalDateTime systemStartTime = LocalDateTime.now();
|
||||
|
||||
@Inject
|
||||
SystemLogRepository systemLogRepository;
|
||||
|
||||
@Inject
|
||||
SystemAlertRepository systemAlertRepository;
|
||||
|
||||
@Inject
|
||||
AlertConfigurationRepository alertConfigurationRepository;
|
||||
|
||||
/**
|
||||
* Rechercher dans les logs système
|
||||
*/
|
||||
@@ -34,34 +51,41 @@ public class LogsMonitoringService {
|
||||
log.debug("Recherche de logs avec filtres: level={}, source={}, query={}",
|
||||
request.getLevel(), request.getSource(), request.getSearchQuery());
|
||||
|
||||
// Dans une vraie implémentation, on interrogerait une DB ou un système de logs
|
||||
// Pour l'instant, on retourne des données de test
|
||||
List<SystemLogResponse> allLogs = generateMockLogs();
|
||||
// Calculer les dates de début et fin selon timeRange
|
||||
LocalDateTime start = null;
|
||||
LocalDateTime end = LocalDateTime.now();
|
||||
|
||||
// Filtrage par niveau
|
||||
if (request.getLevel() != null && !"TOUS".equals(request.getLevel())) {
|
||||
allLogs = allLogs.stream()
|
||||
.filter(log -> log.getLevel().equals(request.getLevel()))
|
||||
.collect(Collectors.toList());
|
||||
if (request.getTimeRange() != null) {
|
||||
switch (request.getTimeRange()) {
|
||||
case "1H" -> start = end.minusHours(1);
|
||||
case "24H" -> start = end.minusHours(24);
|
||||
case "7D" -> start = end.minusDays(7);
|
||||
case "30D" -> start = end.minusDays(30);
|
||||
}
|
||||
}
|
||||
|
||||
// Filtrage par source
|
||||
if (request.getSource() != null && !"TOUS".equals(request.getSource())) {
|
||||
allLogs = allLogs.stream()
|
||||
.filter(log -> log.getSource().equals(request.getSource()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
// Rechercher dans la BDD
|
||||
int offset = request.getOffset() != null ? request.getOffset() : 0;
|
||||
int limit = request.getLimit() != null ? request.getLimit() : 100;
|
||||
|
||||
// Filtrage par recherche textuelle
|
||||
if (request.getSearchQuery() != null && !request.getSearchQuery().isBlank()) {
|
||||
String query = request.getSearchQuery().toLowerCase();
|
||||
allLogs = allLogs.stream()
|
||||
.filter(log -> log.getMessage().toLowerCase().contains(query)
|
||||
|| log.getSource().toLowerCase().contains(query))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
// Convertir offset/limit en page/pageSize
|
||||
int pageSize = limit;
|
||||
int pageIndex = offset / pageSize;
|
||||
|
||||
return allLogs;
|
||||
List<SystemLog> logs = systemLogRepository.search(
|
||||
request.getLevel(),
|
||||
request.getSource(),
|
||||
request.getSearchQuery(),
|
||||
start,
|
||||
end,
|
||||
pageIndex,
|
||||
pageSize
|
||||
);
|
||||
|
||||
// Mapper vers DTO
|
||||
return logs.stream()
|
||||
.map(this::mapToLogResponse)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +151,7 @@ public class LogsMonitoringService {
|
||||
.networkUsageMbps(12.3 + ThreadLocalRandom.current().nextDouble(5))
|
||||
.activeConnections(1200 + ThreadLocalRandom.current().nextInt(100))
|
||||
.errorRate(0.02)
|
||||
.averageResponseTimeMs(127L)
|
||||
.averageResponseTimeMs(127.0)
|
||||
.uptime(uptimeMs)
|
||||
.uptimeFormatted(uptimeFormatted)
|
||||
.services(services)
|
||||
@@ -145,48 +169,26 @@ public class LogsMonitoringService {
|
||||
public List<SystemAlertResponse> getActiveAlerts() {
|
||||
log.debug("Récupération des alertes actives");
|
||||
|
||||
// Dans une vraie implémentation, on interrogerait la DB
|
||||
List<SystemAlertResponse> alerts = new ArrayList<>();
|
||||
List<SystemAlert> alerts = systemAlertRepository.findActiveAlerts();
|
||||
|
||||
alerts.add(SystemAlertResponse.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.level("WARNING")
|
||||
.title("CPU élevé")
|
||||
.message("Utilisation CPU > 80% pendant 5 minutes")
|
||||
.timestamp(LocalDateTime.now().minusMinutes(12))
|
||||
.acknowledged(false)
|
||||
.source("CPU")
|
||||
.alertType("THRESHOLD")
|
||||
.currentValue(85.5)
|
||||
.thresholdValue(80.0)
|
||||
.build());
|
||||
|
||||
alerts.add(SystemAlertResponse.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.level("INFO")
|
||||
.title("Sauvegarde terminée")
|
||||
.message("Sauvegarde automatique réussie (2.3 GB)")
|
||||
.timestamp(LocalDateTime.now().minusHours(2))
|
||||
.acknowledged(true)
|
||||
.acknowledgedBy("admin@unionflow.test")
|
||||
.acknowledgedAt(LocalDateTime.now().minusHours(1).minusMinutes(50))
|
||||
.source("BACKUP")
|
||||
.alertType("INFO")
|
||||
.build());
|
||||
|
||||
return alerts;
|
||||
return alerts.stream()
|
||||
.map(this::mapToAlertResponse)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquitter une alerte
|
||||
*/
|
||||
@Transactional
|
||||
public void acknowledgeAlert(UUID alertId) {
|
||||
log.info("Acquittement de l'alerte: {}", alertId);
|
||||
|
||||
// Dans une vraie implémentation, on mettrait à jour en DB
|
||||
// TODO: Marquer l'alerte comme acquittée en DB
|
||||
// TODO: Récupérer l'utilisateur courant depuis le contexte de sécurité
|
||||
String currentUser = "admin@unionflow.test"; // Temporaire
|
||||
|
||||
log.info("Alerte acquittée avec succès");
|
||||
systemAlertRepository.acknowledgeAlert(alertId, currentUser);
|
||||
|
||||
log.info("Alerte {} acquittée avec succès par {}", alertId, currentUser);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,137 +197,131 @@ public class LogsMonitoringService {
|
||||
public AlertConfigResponse getAlertConfig() {
|
||||
log.debug("Récupération de la configuration des alertes");
|
||||
|
||||
AlertConfiguration config = alertConfigurationRepository.getConfiguration();
|
||||
|
||||
// Compter les alertes réelles
|
||||
long totalLast24h = systemAlertRepository.countLast24h();
|
||||
long activeAlerts = systemAlertRepository.countActive();
|
||||
long acknowledgedLast24h = systemAlertRepository.countAcknowledgedLast24h();
|
||||
|
||||
return AlertConfigResponse.builder()
|
||||
.cpuHighAlertEnabled(true)
|
||||
.cpuThresholdPercent(80)
|
||||
.cpuDurationMinutes(5)
|
||||
.memoryLowAlertEnabled(true)
|
||||
.memoryThresholdPercent(85)
|
||||
.criticalErrorAlertEnabled(true)
|
||||
.errorAlertEnabled(true)
|
||||
.connectionFailureAlertEnabled(true)
|
||||
.connectionFailureThreshold(100)
|
||||
.connectionFailureWindowMinutes(5)
|
||||
.emailNotificationsEnabled(true)
|
||||
.pushNotificationsEnabled(false)
|
||||
.smsNotificationsEnabled(false)
|
||||
.alertEmailRecipients("admin@unionflow.test,support@unionflow.test")
|
||||
.totalAlertsLast24h(15)
|
||||
.activeAlerts(2)
|
||||
.acknowledgedAlerts(13)
|
||||
.cpuHighAlertEnabled(config.getCpuHighAlertEnabled())
|
||||
.cpuThresholdPercent(config.getCpuThresholdPercent())
|
||||
.cpuDurationMinutes(config.getCpuDurationMinutes())
|
||||
.memoryLowAlertEnabled(config.getMemoryLowAlertEnabled())
|
||||
.memoryThresholdPercent(config.getMemoryThresholdPercent())
|
||||
.criticalErrorAlertEnabled(config.getCriticalErrorAlertEnabled())
|
||||
.errorAlertEnabled(config.getErrorAlertEnabled())
|
||||
.connectionFailureAlertEnabled(config.getConnectionFailureAlertEnabled())
|
||||
.connectionFailureThreshold(config.getConnectionFailureThreshold())
|
||||
.connectionFailureWindowMinutes(config.getConnectionFailureWindowMinutes())
|
||||
.emailNotificationsEnabled(config.getEmailNotificationsEnabled())
|
||||
.pushNotificationsEnabled(config.getPushNotificationsEnabled())
|
||||
.smsNotificationsEnabled(config.getSmsNotificationsEnabled())
|
||||
.alertEmailRecipients(config.getAlertEmailRecipients())
|
||||
.totalAlertsLast24h((int) totalLast24h)
|
||||
.activeAlerts((int) activeAlerts)
|
||||
.acknowledgedAlerts((int) acknowledgedLast24h)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mettre à jour la configuration des alertes
|
||||
*/
|
||||
@Transactional
|
||||
public AlertConfigResponse updateAlertConfig(UpdateAlertConfigRequest request) {
|
||||
log.info("Mise à jour de la configuration des alertes");
|
||||
|
||||
// Dans une vraie implémentation, on persisterait en DB
|
||||
// Pour l'instant, on retourne juste la config avec les nouvelles valeurs
|
||||
// Mapper request vers entité
|
||||
AlertConfiguration config = new AlertConfiguration();
|
||||
config.setCpuHighAlertEnabled(request.getCpuHighAlertEnabled());
|
||||
config.setCpuThresholdPercent(request.getCpuThresholdPercent());
|
||||
config.setCpuDurationMinutes(request.getCpuDurationMinutes());
|
||||
config.setMemoryLowAlertEnabled(request.getMemoryLowAlertEnabled());
|
||||
config.setMemoryThresholdPercent(request.getMemoryThresholdPercent());
|
||||
config.setCriticalErrorAlertEnabled(request.getCriticalErrorAlertEnabled());
|
||||
config.setErrorAlertEnabled(request.getErrorAlertEnabled());
|
||||
config.setConnectionFailureAlertEnabled(request.getConnectionFailureAlertEnabled());
|
||||
config.setConnectionFailureThreshold(request.getConnectionFailureThreshold());
|
||||
config.setConnectionFailureWindowMinutes(request.getConnectionFailureWindowMinutes());
|
||||
config.setEmailNotificationsEnabled(request.getEmailNotificationsEnabled());
|
||||
config.setPushNotificationsEnabled(request.getPushNotificationsEnabled());
|
||||
config.setSmsNotificationsEnabled(request.getSmsNotificationsEnabled());
|
||||
config.setAlertEmailRecipients(request.getAlertEmailRecipients());
|
||||
|
||||
// Persister
|
||||
AlertConfiguration updated = alertConfigurationRepository.updateConfiguration(config);
|
||||
|
||||
// Compter les alertes
|
||||
long totalLast24h = systemAlertRepository.countLast24h();
|
||||
long activeAlerts = systemAlertRepository.countActive();
|
||||
long acknowledgedLast24h = systemAlertRepository.countAcknowledgedLast24h();
|
||||
|
||||
// Mapper vers response
|
||||
return AlertConfigResponse.builder()
|
||||
.cpuHighAlertEnabled(request.getCpuHighAlertEnabled())
|
||||
.cpuThresholdPercent(request.getCpuThresholdPercent())
|
||||
.cpuDurationMinutes(request.getCpuDurationMinutes())
|
||||
.memoryLowAlertEnabled(request.getMemoryLowAlertEnabled())
|
||||
.memoryThresholdPercent(request.getMemoryThresholdPercent())
|
||||
.criticalErrorAlertEnabled(request.getCriticalErrorAlertEnabled())
|
||||
.errorAlertEnabled(request.getErrorAlertEnabled())
|
||||
.connectionFailureAlertEnabled(request.getConnectionFailureAlertEnabled())
|
||||
.connectionFailureThreshold(request.getConnectionFailureThreshold())
|
||||
.connectionFailureWindowMinutes(request.getConnectionFailureWindowMinutes())
|
||||
.emailNotificationsEnabled(request.getEmailNotificationsEnabled())
|
||||
.pushNotificationsEnabled(request.getPushNotificationsEnabled())
|
||||
.smsNotificationsEnabled(request.getSmsNotificationsEnabled())
|
||||
.alertEmailRecipients(request.getAlertEmailRecipients())
|
||||
.totalAlertsLast24h(15)
|
||||
.activeAlerts(2)
|
||||
.acknowledgedAlerts(13)
|
||||
.cpuHighAlertEnabled(updated.getCpuHighAlertEnabled())
|
||||
.cpuThresholdPercent(updated.getCpuThresholdPercent())
|
||||
.cpuDurationMinutes(updated.getCpuDurationMinutes())
|
||||
.memoryLowAlertEnabled(updated.getMemoryLowAlertEnabled())
|
||||
.memoryThresholdPercent(updated.getMemoryThresholdPercent())
|
||||
.criticalErrorAlertEnabled(updated.getCriticalErrorAlertEnabled())
|
||||
.errorAlertEnabled(updated.getErrorAlertEnabled())
|
||||
.connectionFailureAlertEnabled(updated.getConnectionFailureAlertEnabled())
|
||||
.connectionFailureThreshold(updated.getConnectionFailureThreshold())
|
||||
.connectionFailureWindowMinutes(updated.getConnectionFailureWindowMinutes())
|
||||
.emailNotificationsEnabled(updated.getEmailNotificationsEnabled())
|
||||
.pushNotificationsEnabled(updated.getPushNotificationsEnabled())
|
||||
.smsNotificationsEnabled(updated.getSmsNotificationsEnabled())
|
||||
.alertEmailRecipients(updated.getAlertEmailRecipients())
|
||||
.totalAlertsLast24h((int) totalLast24h)
|
||||
.activeAlerts((int) activeAlerts)
|
||||
.acknowledgedAlerts((int) acknowledgedLast24h)
|
||||
.build();
|
||||
}
|
||||
|
||||
// ==================== MÉTHODES DE MAPPING ====================
|
||||
|
||||
/**
|
||||
* Générer des logs de test (à remplacer par une vraie source de logs)
|
||||
* Mapper SystemLog vers SystemLogResponse
|
||||
*/
|
||||
private List<SystemLogResponse> generateMockLogs() {
|
||||
List<SystemLogResponse> logs = new ArrayList<>();
|
||||
private SystemLogResponse mapToLogResponse(SystemLog log) {
|
||||
SystemLogResponse response = SystemLogResponse.builder()
|
||||
.level(log.getLevel())
|
||||
.source(log.getSource())
|
||||
.message(log.getMessage())
|
||||
.details(log.getDetails())
|
||||
.timestamp(log.getTimestamp())
|
||||
.userId(log.getUserId())
|
||||
.ipAddress(log.getIpAddress())
|
||||
.sessionId(log.getSessionId())
|
||||
.endpoint(log.getEndpoint())
|
||||
.httpStatusCode(log.getHttpStatusCode())
|
||||
.build();
|
||||
response.setId(log.getId());
|
||||
return response;
|
||||
}
|
||||
|
||||
logs.add(SystemLogResponse.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.level("CRITICAL")
|
||||
.source("Database")
|
||||
.message("Connexion à la base de données perdue")
|
||||
.details("Pool de connexions épuisé")
|
||||
.timestamp(LocalDateTime.now().minusMinutes(15))
|
||||
.username("system")
|
||||
.ipAddress("192.168.1.100")
|
||||
.requestId(UUID.randomUUID().toString())
|
||||
.build());
|
||||
|
||||
logs.add(SystemLogResponse.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.level("ERROR")
|
||||
.source("API")
|
||||
.message("Erreur 500 sur /api/members")
|
||||
.details("NullPointerException dans MemberService.findAll()")
|
||||
.timestamp(LocalDateTime.now().minusMinutes(18))
|
||||
.username("admin@test.com")
|
||||
.ipAddress("192.168.1.101")
|
||||
.requestId(UUID.randomUUID().toString())
|
||||
.stackTrace("java.lang.NullPointerException\n\tat dev.lions.unionflow.server.service.MemberService.findAll(MemberService.java:45)")
|
||||
.build());
|
||||
|
||||
logs.add(SystemLogResponse.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.level("WARN")
|
||||
.source("Auth")
|
||||
.message("Tentative de connexion avec mot de passe incorrect")
|
||||
.details("IP: 192.168.1.100 - Utilisateur: admin@test.com")
|
||||
.timestamp(LocalDateTime.now().minusMinutes(20))
|
||||
.username("admin@test.com")
|
||||
.ipAddress("192.168.1.100")
|
||||
.requestId(UUID.randomUUID().toString())
|
||||
.build());
|
||||
|
||||
logs.add(SystemLogResponse.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.level("INFO")
|
||||
.source("System")
|
||||
.message("Sauvegarde automatique terminée")
|
||||
.details("Taille: 2.3 GB - Durée: 45 secondes")
|
||||
.timestamp(LocalDateTime.now().minusHours(2))
|
||||
.username("system")
|
||||
.ipAddress("localhost")
|
||||
.requestId(UUID.randomUUID().toString())
|
||||
.build());
|
||||
|
||||
logs.add(SystemLogResponse.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.level("DEBUG")
|
||||
.source("Cache")
|
||||
.message("Cache invalidé pour user_sessions")
|
||||
.details("Raison: Expiration automatique")
|
||||
.timestamp(LocalDateTime.now().minusMinutes(25))
|
||||
.username("system")
|
||||
.ipAddress("localhost")
|
||||
.requestId(UUID.randomUUID().toString())
|
||||
.build());
|
||||
|
||||
logs.add(SystemLogResponse.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.level("TRACE")
|
||||
.source("Performance")
|
||||
.message("Requête SQL exécutée")
|
||||
.details("SELECT * FROM members WHERE active = true - Durée: 23ms")
|
||||
.timestamp(LocalDateTime.now().minusMinutes(27))
|
||||
.username("system")
|
||||
.ipAddress("localhost")
|
||||
.requestId(UUID.randomUUID().toString())
|
||||
.build());
|
||||
|
||||
return logs;
|
||||
/**
|
||||
* Mapper SystemAlert vers SystemAlertResponse
|
||||
*/
|
||||
private SystemAlertResponse mapToAlertResponse(SystemAlert alert) {
|
||||
SystemAlertResponse response = SystemAlertResponse.builder()
|
||||
.level(alert.getLevel())
|
||||
.title(alert.getTitle())
|
||||
.message(alert.getMessage())
|
||||
.timestamp(alert.getTimestamp())
|
||||
.acknowledged(alert.getAcknowledged())
|
||||
.acknowledgedBy(alert.getAcknowledgedBy())
|
||||
.acknowledgedAt(alert.getAcknowledgedAt())
|
||||
.source(alert.getSource())
|
||||
.alertType(alert.getAlertType())
|
||||
.currentValue(alert.getCurrentValue())
|
||||
.thresholdValue(alert.getThresholdValue())
|
||||
.unit(alert.getUnit())
|
||||
.recommendedActions(alert.getRecommendedActions())
|
||||
.build();
|
||||
response.setId(alert.getId());
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.entity.SystemLog;
|
||||
import dev.lions.unionflow.server.repository.SystemLogRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Service centralisé pour la création de logs système.
|
||||
* Gère la persistence des logs dans la base de données.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2026-03-15
|
||||
*/
|
||||
@Slf4j
|
||||
@ApplicationScoped
|
||||
public class SystemLoggingService {
|
||||
|
||||
@Inject
|
||||
SystemLogRepository systemLogRepository;
|
||||
|
||||
/**
|
||||
* Logger une requête HTTP
|
||||
*/
|
||||
@Transactional
|
||||
public void logRequest(
|
||||
String method,
|
||||
String endpoint,
|
||||
Integer httpStatusCode,
|
||||
String userId,
|
||||
String ipAddress,
|
||||
String sessionId,
|
||||
Long durationMs
|
||||
) {
|
||||
try {
|
||||
SystemLog systemLog = new SystemLog();
|
||||
systemLog.setLevel(getLogLevelFromStatusCode(httpStatusCode));
|
||||
systemLog.setSource("API");
|
||||
systemLog.setMessage(String.format("%s %s - %d (%dms)", method, endpoint, httpStatusCode, durationMs));
|
||||
systemLog.setDetails(String.format("User: %s, IP: %s, Duration: %dms", userId, ipAddress, durationMs));
|
||||
systemLog.setTimestamp(LocalDateTime.now());
|
||||
systemLog.setUserId(userId);
|
||||
systemLog.setIpAddress(ipAddress);
|
||||
systemLog.setSessionId(sessionId);
|
||||
systemLog.setEndpoint(endpoint);
|
||||
systemLog.setHttpStatusCode(httpStatusCode);
|
||||
|
||||
systemLogRepository.persist(systemLog);
|
||||
} catch (Exception e) {
|
||||
// Ne pas propager les erreurs de logging pour ne pas casser l'application
|
||||
log.error("Failed to persist request log", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger une erreur (exception)
|
||||
*/
|
||||
@Transactional
|
||||
public void logError(
|
||||
String source,
|
||||
String message,
|
||||
String details,
|
||||
String userId,
|
||||
String ipAddress,
|
||||
String endpoint,
|
||||
Integer httpStatusCode
|
||||
) {
|
||||
try {
|
||||
SystemLog systemLog = new SystemLog();
|
||||
systemLog.setLevel("ERROR");
|
||||
systemLog.setSource(source);
|
||||
systemLog.setMessage(message);
|
||||
systemLog.setDetails(details);
|
||||
systemLog.setTimestamp(LocalDateTime.now());
|
||||
systemLog.setUserId(userId);
|
||||
systemLog.setIpAddress(ipAddress);
|
||||
systemLog.setEndpoint(endpoint);
|
||||
systemLog.setHttpStatusCode(httpStatusCode);
|
||||
|
||||
systemLogRepository.persist(systemLog);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to persist error log", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger une erreur critique
|
||||
*/
|
||||
@Transactional
|
||||
public void logCritical(
|
||||
String source,
|
||||
String message,
|
||||
String details,
|
||||
String userId,
|
||||
String ipAddress
|
||||
) {
|
||||
try {
|
||||
SystemLog systemLog = new SystemLog();
|
||||
systemLog.setLevel("CRITICAL");
|
||||
systemLog.setSource(source);
|
||||
systemLog.setMessage(message);
|
||||
systemLog.setDetails(details);
|
||||
systemLog.setTimestamp(LocalDateTime.now());
|
||||
systemLog.setUserId(userId);
|
||||
systemLog.setIpAddress(ipAddress);
|
||||
|
||||
systemLogRepository.persist(systemLog);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to persist critical log", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger un warning
|
||||
*/
|
||||
@Transactional
|
||||
public void logWarning(
|
||||
String source,
|
||||
String message,
|
||||
String details,
|
||||
String userId,
|
||||
String ipAddress
|
||||
) {
|
||||
try {
|
||||
SystemLog systemLog = new SystemLog();
|
||||
systemLog.setLevel("WARNING");
|
||||
systemLog.setSource(source);
|
||||
systemLog.setMessage(message);
|
||||
systemLog.setDetails(details);
|
||||
systemLog.setTimestamp(LocalDateTime.now());
|
||||
systemLog.setUserId(userId);
|
||||
systemLog.setIpAddress(ipAddress);
|
||||
|
||||
systemLogRepository.persist(systemLog);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to persist warning log", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger une info
|
||||
*/
|
||||
@Transactional
|
||||
public void logInfo(
|
||||
String source,
|
||||
String message,
|
||||
String details
|
||||
) {
|
||||
try {
|
||||
SystemLog systemLog = new SystemLog();
|
||||
systemLog.setLevel("INFO");
|
||||
systemLog.setSource(source);
|
||||
systemLog.setMessage(message);
|
||||
systemLog.setDetails(details);
|
||||
systemLog.setTimestamp(LocalDateTime.now());
|
||||
|
||||
systemLogRepository.persist(systemLog);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to persist info log", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger un événement de debug
|
||||
*/
|
||||
@Transactional
|
||||
public void logDebug(
|
||||
String source,
|
||||
String message,
|
||||
String details
|
||||
) {
|
||||
try {
|
||||
SystemLog systemLog = new SystemLog();
|
||||
systemLog.setLevel("DEBUG");
|
||||
systemLog.setSource(source);
|
||||
systemLog.setMessage(message);
|
||||
systemLog.setDetails(details);
|
||||
systemLog.setTimestamp(LocalDateTime.now());
|
||||
|
||||
systemLogRepository.persist(systemLog);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to persist debug log", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Déterminer le niveau de log selon le code HTTP
|
||||
*/
|
||||
private String getLogLevelFromStatusCode(Integer statusCode) {
|
||||
if (statusCode == null) {
|
||||
return "INFO";
|
||||
}
|
||||
|
||||
if (statusCode >= 500) {
|
||||
return "ERROR";
|
||||
} else if (statusCode >= 400) {
|
||||
return "WARNING";
|
||||
} else if (statusCode >= 300) {
|
||||
return "INFO";
|
||||
} else {
|
||||
return "DEBUG";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.system.response.SystemMetricsResponse;
|
||||
import dev.lions.unionflow.server.api.dto.logs.response.SystemMetricsResponse;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import io.agroal.api.AgroalDataSource;
|
||||
|
||||
Reference in New Issue
Block a user