update 22
This commit is contained in:
@@ -7,17 +7,17 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import research.loghunter.entity.*;
|
||||
import research.loghunter.repository.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@@ -450,4 +450,339 @@ public class ScanService {
|
||||
int errorsFound,
|
||||
String error
|
||||
) {}
|
||||
|
||||
// === 분석 결과 초기화 ===
|
||||
|
||||
/**
|
||||
* 서버별 분석 결과 초기화
|
||||
*/
|
||||
@Transactional
|
||||
public ResetResult resetScanData(Long serverId) {
|
||||
long deletedErrors = errorLogRepository.countByServerId(serverId);
|
||||
errorLogRepository.deleteByServerId(serverId);
|
||||
|
||||
List<ScannedFile> scannedFiles = scannedFileRepository.findByServerId(serverId);
|
||||
int deletedFiles = scannedFiles.size();
|
||||
scannedFileRepository.deleteAll(scannedFiles);
|
||||
|
||||
List<ScanHistory> histories = scanHistoryRepository.findByServerIdOrderByStartedAtDesc(serverId);
|
||||
int deletedHistories = histories.size();
|
||||
scanHistoryRepository.deleteAll(histories);
|
||||
|
||||
log.info("Reset scan data for server {}: {} errors, {} files, {} histories",
|
||||
serverId, deletedErrors, deletedFiles, deletedHistories);
|
||||
|
||||
return new ResetResult((int) deletedErrors, deletedFiles, deletedHistories);
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체 분석 결과 초기화
|
||||
*/
|
||||
@Transactional
|
||||
public ResetResult resetAllScanData() {
|
||||
long deletedErrors = errorLogRepository.count();
|
||||
errorLogRepository.deleteAll();
|
||||
|
||||
long deletedFiles = scannedFileRepository.count();
|
||||
scannedFileRepository.deleteAll();
|
||||
|
||||
long deletedHistories = scanHistoryRepository.count();
|
||||
scanHistoryRepository.deleteAll();
|
||||
|
||||
log.info("Reset all scan data: {} errors, {} files, {} histories",
|
||||
deletedErrors, deletedFiles, deletedHistories);
|
||||
|
||||
return new ResetResult((int) deletedErrors, (int) deletedFiles, (int) deletedHistories);
|
||||
}
|
||||
|
||||
// === 에러 통계 ===
|
||||
|
||||
/**
|
||||
* 파일별 에러 통계
|
||||
*/
|
||||
public List<FileErrorStats> getErrorStatsByFile(Long serverId) {
|
||||
List<Object[]> results = errorLogRepository.getErrorStatsByFile(serverId);
|
||||
return results.stream()
|
||||
.map(row -> new FileErrorStats(
|
||||
(String) row[0], // filePath
|
||||
(String) row[1], // serverName
|
||||
((Number) row[2]).intValue(), // totalCount
|
||||
((Number) row[3]).intValue(), // criticalCount
|
||||
((Number) row[4]).intValue(), // errorCount
|
||||
((Number) row[5]).intValue(), // warnCount
|
||||
(LocalDateTime) row[6] // lastOccurredAt
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 서버별 에러 통계
|
||||
*/
|
||||
public List<ServerErrorStats> getErrorStatsByServer() {
|
||||
List<Object[]> results = errorLogRepository.getErrorStatsByServer();
|
||||
return results.stream()
|
||||
.map(row -> new ServerErrorStats(
|
||||
((Number) row[0]).longValue(), // serverId
|
||||
(String) row[1], // serverName
|
||||
((Number) row[2]).intValue(), // totalCount
|
||||
((Number) row[3]).intValue(), // criticalCount
|
||||
((Number) row[4]).intValue(), // errorCount
|
||||
((Number) row[5]).intValue(), // warnCount
|
||||
(LocalDateTime) row[6] // lastOccurredAt
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 패턴별 에러 통계
|
||||
*/
|
||||
public List<PatternErrorStats> getErrorStatsByPattern(Long serverId) {
|
||||
List<Object[]> results = errorLogRepository.getErrorStatsByPattern(serverId);
|
||||
return results.stream()
|
||||
.map(row -> new PatternErrorStats(
|
||||
((Number) row[0]).longValue(), // patternId
|
||||
(String) row[1], // patternName
|
||||
(String) row[2], // severity
|
||||
((Number) row[3]).intValue(), // count
|
||||
(LocalDateTime) row[4] // lastOccurredAt
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 대시보드용: 서버별 최근 N일 일별 통계
|
||||
*/
|
||||
public List<ServerDailyStats> getDailyStatsByServer(int days) {
|
||||
LocalDateTime endDate = LocalDate.now().plusDays(1).atStartOfDay(); // 내일 00:00
|
||||
LocalDateTime startDate = LocalDate.now().minusDays(days - 1).atStartOfDay(); // N일 전 00:00
|
||||
|
||||
// epoch milliseconds로 변환
|
||||
long startMs = startDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
long endMs = endDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
|
||||
List<Object[]> results = errorLogRepository.getDailyStatsByServer(startMs, endMs);
|
||||
|
||||
// 서버별로 그룹화
|
||||
Map<Long, ServerDailyStats> serverMap = new LinkedHashMap<>();
|
||||
|
||||
for (Object[] row : results) {
|
||||
Long serverId = ((Number) row[0]).longValue();
|
||||
String serverName = (String) row[1];
|
||||
String dateStr = (String) row[2]; // SQLite date() 함수는 String 반환
|
||||
int total = ((Number) row[3]).intValue();
|
||||
int critical = ((Number) row[4]).intValue();
|
||||
int error = ((Number) row[5]).intValue();
|
||||
int warn = ((Number) row[6]).intValue();
|
||||
|
||||
serverMap.computeIfAbsent(serverId, k -> new ServerDailyStats(serverId, serverName, new ArrayList<>()))
|
||||
.dailyStats().add(new DailyStat(dateStr, total, critical, error, warn));
|
||||
}
|
||||
|
||||
// 모든 날짜 채우기 (데이터 없는 날짜는 0으로)
|
||||
List<String> allDates = new ArrayList<>();
|
||||
for (int i = days - 1; i >= 0; i--) {
|
||||
allDates.add(LocalDate.now().minusDays(i).toString());
|
||||
}
|
||||
|
||||
for (ServerDailyStats server : serverMap.values()) {
|
||||
Map<String, DailyStat> dateMap = server.dailyStats().stream()
|
||||
.collect(Collectors.toMap(DailyStat::date, s -> s));
|
||||
|
||||
List<DailyStat> filledStats = allDates.stream()
|
||||
.map(date -> dateMap.getOrDefault(date, new DailyStat(date, 0, 0, 0, 0)))
|
||||
.toList();
|
||||
|
||||
server.dailyStats().clear();
|
||||
server.dailyStats().addAll(filledStats);
|
||||
}
|
||||
|
||||
return new ArrayList<>(serverMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 월별현황용: 서버별 해당 월 일별 통계
|
||||
*/
|
||||
public List<ServerDailyStats> getMonthlyStatsByServer(int year, int month) {
|
||||
LocalDate firstDay = LocalDate.of(year, month, 1);
|
||||
LocalDate lastDay = firstDay.withDayOfMonth(firstDay.lengthOfMonth());
|
||||
|
||||
LocalDateTime startDate = firstDay.atStartOfDay();
|
||||
LocalDateTime endDate = lastDay.plusDays(1).atStartOfDay();
|
||||
|
||||
// epoch milliseconds로 변환
|
||||
long startMs = startDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
long endMs = endDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
|
||||
log.info("Monthly stats query: year={}, month={}, startMs={}, endMs={}", year, month, startMs, endMs);
|
||||
|
||||
List<Object[]> results = errorLogRepository.getDailyStatsByServer(startMs, endMs);
|
||||
|
||||
log.info("Monthly stats results count: {}", results.size());
|
||||
|
||||
// 서버별로 그룹화
|
||||
Map<Long, ServerDailyStats> serverMap = new LinkedHashMap<>();
|
||||
|
||||
for (Object[] row : results) {
|
||||
Long serverId = ((Number) row[0]).longValue();
|
||||
String serverName = (String) row[1];
|
||||
String dateStr = (String) row[2]; // SQLite date() 함수는 String 반환
|
||||
int total = ((Number) row[3]).intValue();
|
||||
int critical = ((Number) row[4]).intValue();
|
||||
int error = ((Number) row[5]).intValue();
|
||||
int warn = ((Number) row[6]).intValue();
|
||||
|
||||
log.debug("Row: serverId={}, serverName={}, date={}, total={}", serverId, serverName, dateStr, total);
|
||||
|
||||
serverMap.computeIfAbsent(serverId, k -> new ServerDailyStats(serverId, serverName, new ArrayList<>()))
|
||||
.dailyStats().add(new DailyStat(dateStr, total, critical, error, warn));
|
||||
}
|
||||
|
||||
// 모든 날짜 채우기
|
||||
List<String> allDates = new ArrayList<>();
|
||||
for (int day = 1; day <= firstDay.lengthOfMonth(); day++) {
|
||||
allDates.add(LocalDate.of(year, month, day).toString());
|
||||
}
|
||||
|
||||
for (ServerDailyStats server : serverMap.values()) {
|
||||
Map<String, DailyStat> dateMap = server.dailyStats().stream()
|
||||
.collect(Collectors.toMap(DailyStat::date, s -> s));
|
||||
|
||||
List<DailyStat> filledStats = allDates.stream()
|
||||
.map(date -> dateMap.getOrDefault(date, new DailyStat(date, 0, 0, 0, 0)))
|
||||
.toList();
|
||||
|
||||
server.dailyStats().clear();
|
||||
server.dailyStats().addAll(filledStats);
|
||||
}
|
||||
|
||||
return new ArrayList<>(serverMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 일별현황용: 서버별 해당 날짜 5분 단위 통계
|
||||
*/
|
||||
public List<ServerTimeStats> getTimeStatsByServer(LocalDate date, int intervalMinutes) {
|
||||
LocalDateTime startDate = date.atStartOfDay();
|
||||
LocalDateTime endDate = date.plusDays(1).atStartOfDay();
|
||||
|
||||
List<Object[]> results = errorLogRepository.getErrorsByDateRange(startDate, endDate);
|
||||
|
||||
// 서버별로 그룹화
|
||||
Map<Long, ServerTimeStats> serverMap = new LinkedHashMap<>();
|
||||
|
||||
// 시간 슬롯 초기화 (5분 단위 = 288개)
|
||||
int slots = 24 * 60 / intervalMinutes;
|
||||
|
||||
for (Object[] row : results) {
|
||||
Long serverId = ((Number) row[0]).longValue();
|
||||
String serverName = (String) row[1];
|
||||
LocalDateTime occurredAt = (LocalDateTime) row[2];
|
||||
String severity = (String) row[3];
|
||||
|
||||
ServerTimeStats stats = serverMap.computeIfAbsent(serverId,
|
||||
k -> new ServerTimeStats(serverId, serverName, initTimeSlots(slots, intervalMinutes)));
|
||||
|
||||
// 해당 시간 슬롯 찾기
|
||||
int minuteOfDay = occurredAt.getHour() * 60 + occurredAt.getMinute();
|
||||
int slotIndex = minuteOfDay / intervalMinutes;
|
||||
|
||||
if (slotIndex < stats.timeStats().size()) {
|
||||
TimeStat slot = stats.timeStats().get(slotIndex);
|
||||
slot.incrementTotal();
|
||||
switch (severity) {
|
||||
case "CRITICAL" -> slot.incrementCritical();
|
||||
case "ERROR" -> slot.incrementError();
|
||||
case "WARN" -> slot.incrementWarn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(serverMap.values());
|
||||
}
|
||||
|
||||
private List<TimeStat> initTimeSlots(int slots, int intervalMinutes) {
|
||||
List<TimeStat> timeStats = new ArrayList<>();
|
||||
for (int i = 0; i < slots; i++) {
|
||||
int minutes = i * intervalMinutes;
|
||||
String time = String.format("%02d:%02d", minutes / 60, minutes % 60);
|
||||
timeStats.add(new TimeStat(time));
|
||||
}
|
||||
return timeStats;
|
||||
}
|
||||
|
||||
// DTOs for stats
|
||||
public record ResetResult(int deletedErrors, int deletedFiles, int deletedHistories) {}
|
||||
|
||||
public record FileErrorStats(
|
||||
String filePath,
|
||||
String serverName,
|
||||
int totalCount,
|
||||
int criticalCount,
|
||||
int errorCount,
|
||||
int warnCount,
|
||||
LocalDateTime lastOccurredAt
|
||||
) {}
|
||||
|
||||
public record ServerErrorStats(
|
||||
Long serverId,
|
||||
String serverName,
|
||||
int totalCount,
|
||||
int criticalCount,
|
||||
int errorCount,
|
||||
int warnCount,
|
||||
LocalDateTime lastOccurredAt
|
||||
) {}
|
||||
|
||||
public record PatternErrorStats(
|
||||
Long patternId,
|
||||
String patternName,
|
||||
String severity,
|
||||
int count,
|
||||
LocalDateTime lastOccurredAt
|
||||
) {}
|
||||
|
||||
// 일별 통계 DTO
|
||||
public record ServerDailyStats(
|
||||
Long serverId,
|
||||
String serverName,
|
||||
List<DailyStat> dailyStats
|
||||
) {}
|
||||
|
||||
public record DailyStat(
|
||||
String date,
|
||||
int total,
|
||||
int critical,
|
||||
int error,
|
||||
int warn
|
||||
) {}
|
||||
|
||||
// 시간별 통계 DTO
|
||||
public record ServerTimeStats(
|
||||
Long serverId,
|
||||
String serverName,
|
||||
List<TimeStat> timeStats
|
||||
) {}
|
||||
|
||||
public static class TimeStat {
|
||||
private final String time;
|
||||
private int total;
|
||||
private int critical;
|
||||
private int error;
|
||||
private int warn;
|
||||
|
||||
public TimeStat(String time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getTime() { return time; }
|
||||
public int getTotal() { return total; }
|
||||
public int getCritical() { return critical; }
|
||||
public int getError() { return error; }
|
||||
public int getWarn() { return warn; }
|
||||
|
||||
public void incrementTotal() { total++; }
|
||||
public void incrementCritical() { critical++; }
|
||||
public void incrementError() { error++; }
|
||||
public void incrementWarn() { warn++; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user