diff --git a/NULL_POINTER_FIX.md b/NULL_POINTER_FIX.md new file mode 100644 index 0000000..3acf5b0 --- /dev/null +++ b/NULL_POINTER_FIX.md @@ -0,0 +1,62 @@ +# Исправление NullPointerException в ReportGenerationService + +## Проблема + +Ошибка: `Cannot invoke "java.time.LocalDateTime.toLocalDate()" because "end" is null` + +## Причина + +В методах `generate()` и `simpleGenerate()` класса `ReportGenerationService` код пытался вызвать `end.toLocalDate()` без проверки на null, когда поле `endDate` в `ReportGenerateRequest` было null. + +## Исправления + +### 1. Добавлены значения по умолчанию для дат + +```java +// Устанавливаем значения по умолчанию, если даты не указаны +if (start == null) { + start = LocalDateTime.now().minusDays(30); // 30 дней назад +} +if (end == null) { + end = LocalDateTime.now(); // сейчас +} +``` + +### 2. Исправлены вызовы format() в buildDocx и buildPdf + +```java +// Было: +req.getStartDate().format(dt), req.getEndDate().format(dt) + +// Стало: +req.getStartDate() != null ? req.getStartDate().format(dt) : "не указано", +req.getEndDate() != null ? req.getEndDate().format(dt) : "не указано" +``` + +### 3. Упрощено создание имени файла + +```java +// Было: +String dateSuffix = end != null ? end.toLocalDate().toString() : "unknown_date"; + +// Стало: +String dateSuffix = end.toLocalDate().toString(); // end теперь всегда не null +``` + +## Затронутые методы + +- `generate(ReportGenerateRequest req)` +- `simpleGenerate(ReportGenerateRequest req)` +- `buildDocx(ReportGenerateRequest req, List items, Sections sections)` +- `buildPdf(ReportGenerateRequest req, List items, Sections sections)` + +## Результат + +- ✅ Устранена ошибка NullPointerException +- ✅ Добавлена обработка случаев, когда даты не указаны +- ✅ Установлены разумные значения по умолчанию (последние 30 дней) +- ✅ Сохранена обратная совместимость + +## Тестирование + +После исправлений асинхронная генерация отчётов должна работать корректно даже при отсутствии дат в запросе. diff --git a/src/main/java/kz/konturai/parser/service/ReportGenerationService.java b/src/main/java/kz/konturai/parser/service/ReportGenerationService.java index 8bf377f..ecb5671 100644 --- a/src/main/java/kz/konturai/parser/service/ReportGenerationService.java +++ b/src/main/java/kz/konturai/parser/service/ReportGenerationService.java @@ -103,6 +103,14 @@ public class ReportGenerationService { LocalDateTime start = req.getStartDate(); LocalDateTime end = req.getEndDate(); + // Устанавливаем значения по умолчанию, если даты не указаны + if (start == null) { + start = LocalDateTime.now().minusDays(30); // 30 дней назад + } + if (end == null) { + end = LocalDateTime.now(); // сейчас + } + List items = marketItemRepository .findByPublishedAtBetween(start, end, org.springframework.data.domain.Pageable.unpaged()) .getContent(); @@ -128,7 +136,7 @@ public class ReportGenerationService { generated = "(Не удалось сгенерировать текст отчёта по предоставленным данным.)"; } - String filename = "report_simple_" + (end == null ? "date" : end.toLocalDate().toString()) + "-" + String filename = "report_simple_" + end.toLocalDate().toString() + "-" + System.currentTimeMillis() + ".txt"; return new ReportBinary(filename, MediaType.TEXT_PLAIN, generated.getBytes(java.nio.charset.StandardCharsets.UTF_8)); @@ -150,6 +158,14 @@ public class ReportGenerationService { LocalDateTime start = req.getStartDate(); LocalDateTime end = req.getEndDate(); + // Устанавливаем значения по умолчанию, если даты не указаны + if (start == null) { + start = LocalDateTime.now().minusDays(30); // 30 дней назад + } + if (end == null) { + end = LocalDateTime.now(); // сейчас + } + List items = marketItemRepository .findByPublishedAtBetween(start, end, org.springframework.data.domain.Pageable.unpaged()) .getContent(); @@ -157,15 +173,16 @@ public class ReportGenerationService { Sections sections = buildSections(req, items); ReportBinary rb; + String dateSuffix = end.toLocalDate().toString(); if ("DOCX".equalsIgnoreCase(req.getFormat())) { byte[] docx = buildDocx(req, items, sections); - String filename = "report_" + end.toLocalDate().toString() + "-" + System.currentTimeMillis() + ".docx"; + String filename = "report_" + dateSuffix + "-" + System.currentTimeMillis() + ".docx"; rb = new ReportBinary(filename, MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"), docx); } else { byte[] pdf = buildPdf(req, items, sections); - String filename = "report_" + end.toLocalDate().toString() + "-" + System.currentTimeMillis() + ".pdf"; + String filename = "report_" + dateSuffix + "-" + System.currentTimeMillis() + ".pdf"; rb = new ReportBinary(filename, MediaType.APPLICATION_PDF, pdf); } persistReport(req, rb); @@ -279,7 +296,8 @@ public class ReportGenerationService { addParagraph(doc, sections.getIntroduction().isBlank() ? String.format( "Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.", - req.getStartDate().format(dt), req.getEndDate().format(dt)) + req.getStartDate() != null ? req.getStartDate().format(dt) : "не указано", + req.getEndDate() != null ? req.getEndDate().format(dt) : "не указано") : sections.getIntroduction()); // Main @@ -362,7 +380,8 @@ public class ReportGenerationService { pdf.add(new Paragraph(sections.getIntroduction().isBlank() ? String.format( "Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.", - req.getStartDate().format(dt), req.getEndDate().format(dt)) + req.getStartDate() != null ? req.getStartDate().format(dt) : "не указано", + req.getEndDate() != null ? req.getEndDate().format(dt) : "не указано") : sections.getIntroduction())); pdf.add(new Paragraph("Основная часть"));