diff --git a/src/main/java/ru/dmitriev/NauJavaSpring/config/SecurityConfig.java b/src/main/java/ru/dmitriev/NauJavaSpring/config/SecurityConfig.java index 03a708d..626cd38 100644 --- a/src/main/java/ru/dmitriev/NauJavaSpring/config/SecurityConfig.java +++ b/src/main/java/ru/dmitriev/NauJavaSpring/config/SecurityConfig.java @@ -65,7 +65,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth - .requestMatchers("/", "/home", "/register", "/login", "/css/**", "/js/**").permitAll() + .requestMatchers("/", "/reports/**", "/register", "/login", "/css/**", "/js/**").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").hasRole("ADMIN") .requestMatchers("/admin/**").hasRole("ADMIN") .requestMatchers("/user/**").hasRole("USER") diff --git a/src/main/java/ru/dmitriev/NauJavaSpring/controller/ReportController.java b/src/main/java/ru/dmitriev/NauJavaSpring/controller/ReportController.java new file mode 100644 index 0000000..30c4d8b --- /dev/null +++ b/src/main/java/ru/dmitriev/NauJavaSpring/controller/ReportController.java @@ -0,0 +1,46 @@ +package ru.dmitriev.NauJavaSpring.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.ModelAndView; +import ru.dmitriev.NauJavaSpring.entity.Report; +import ru.dmitriev.NauJavaSpring.services.ReportService; + +import java.util.concurrent.CompletableFuture; + +@RestController +@RequestMapping("/reports") +public class ReportController { + + private final ReportService reportService; + + @Autowired + public ReportController(ReportService reportService) { + this.reportService = reportService; + } + + @PostMapping + public ResponseEntity createReport() { + // Создаем отчет и запускаем асинхронное формирование + Long reportId = reportService.createReport(); + reportService.generateReport(reportId); + return ResponseEntity.ok(reportId); // Возвращаем ID созданного отчета + } + + @GetMapping("/{id}") + public ResponseEntity getReportContent(@PathVariable Long id) { + Report report = reportService.getReportById(id); + + if (report == null) { + return ResponseEntity.notFound().build(); // Возвращаем 404, если отчет не найден + } + + return switch (report.getStatus()) { + case CREATED -> ResponseEntity.ok("Отчет еще формируется. Пожалуйста, попробуйте позже."); + case ERROR -> ResponseEntity.ok("Произошла ошибка при формировании отчета."); + case COMPLETED -> ResponseEntity.ok(report.getContent()); // Возвращаем содержимое готового отчета + default -> ResponseEntity.status(500).body("Неизвестный статус отчета."); + }; + } +} diff --git a/src/main/java/ru/dmitriev/NauJavaSpring/entity/Report.java b/src/main/java/ru/dmitriev/NauJavaSpring/entity/Report.java new file mode 100644 index 0000000..d040fb8 --- /dev/null +++ b/src/main/java/ru/dmitriev/NauJavaSpring/entity/Report.java @@ -0,0 +1,35 @@ +package ru.dmitriev.NauJavaSpring.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@Entity +@Table(name = "reports") +public class Report { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false) + private ReportStatus status; + + @Column(name = "content", columnDefinition = "TEXT") + private String content; + + public Report() { + } + + public Report(ReportStatus status, String content) { + this.status = status; + this.content = content; + } +} diff --git a/src/main/java/ru/dmitriev/NauJavaSpring/entity/ReportStatus.java b/src/main/java/ru/dmitriev/NauJavaSpring/entity/ReportStatus.java new file mode 100644 index 0000000..80adbf4 --- /dev/null +++ b/src/main/java/ru/dmitriev/NauJavaSpring/entity/ReportStatus.java @@ -0,0 +1,7 @@ +package ru.dmitriev.NauJavaSpring.entity; + +public enum ReportStatus { + CREATED, + COMPLETED, + ERROR +} diff --git a/src/main/java/ru/dmitriev/NauJavaSpring/repository/ReportRepository.java b/src/main/java/ru/dmitriev/NauJavaSpring/repository/ReportRepository.java new file mode 100644 index 0000000..dd58a3e --- /dev/null +++ b/src/main/java/ru/dmitriev/NauJavaSpring/repository/ReportRepository.java @@ -0,0 +1,10 @@ +package ru.dmitriev.NauJavaSpring.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.dmitriev.NauJavaSpring.entity.Report; + +@Repository +public interface ReportRepository extends JpaRepository { + +} \ No newline at end of file diff --git a/src/main/java/ru/dmitriev/NauJavaSpring/services/ReportService.java b/src/main/java/ru/dmitriev/NauJavaSpring/services/ReportService.java new file mode 100644 index 0000000..5b548df --- /dev/null +++ b/src/main/java/ru/dmitriev/NauJavaSpring/services/ReportService.java @@ -0,0 +1,115 @@ +package ru.dmitriev.NauJavaSpring.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.servlet.ModelAndView; +import ru.dmitriev.NauJavaSpring.entity.Event; +import ru.dmitriev.NauJavaSpring.entity.Report; +import ru.dmitriev.NauJavaSpring.entity.ReportStatus; +import ru.dmitriev.NauJavaSpring.repository.EventRepository; +import ru.dmitriev.NauJavaSpring.repository.ReportRepository; +import ru.dmitriev.NauJavaSpring.repository.UserRepository; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.StreamSupport; +import java.util.stream.Collectors; + +@Service +public class ReportService { + + private final UserRepository userRepository; + private final EventRepository eventRepository; + + private final ReportRepository reportRepository; + + @Autowired + public ReportService(UserRepository userRepository, EventRepository eventRepository, ReportRepository reportRepository) { + this.userRepository = userRepository; + this.eventRepository = eventRepository; + this.reportRepository = reportRepository; + } + + public Report getReportById(Long id) { + Optional report = reportRepository.findById(id); + return report.orElse(null); // Вернуть отчет, если он найден, иначе вернуть null + } + + public String getReportContent(Long reportId) { + return reportRepository.findById(reportId) + .map(Report::getContent) + .orElse("Отчет не найден"); + } + + public Long createReport() { + Report report = new Report(); + report.setStatus(ReportStatus.CREATED); + report.setContent("Отчет находится в процессе формирования..."); + Report savedReport = reportRepository.save(report); + return savedReport.getId(); + } + + @Transactional + public CompletableFuture generateReport(Long reportId) { + return CompletableFuture.runAsync(() -> { + Report report = reportRepository.findById(reportId) + .orElseThrow(() -> new IllegalArgumentException("Отчет не найден")); + + try { + long startTime = System.currentTimeMillis(); + + // Задача для подсчета пользователей + CompletableFuture userCountFuture = CompletableFuture.supplyAsync(() -> { + long start = System.currentTimeMillis(); + long count = userRepository.count(); + System.out.println("Время для подсчета пользователей: " + (System.currentTimeMillis() - start) + " ms"); + return count; + }); + + // Задача для получения списка объектов Event + CompletableFuture> eventsFuture = CompletableFuture.supplyAsync(() -> { + long start = System.currentTimeMillis(); + List eventNames = StreamSupport.stream(eventRepository.findAll().spliterator(), false) + .map(Event::getTitle) + .collect(Collectors.toList()); + System.out.println("Время для получения объектов Event: " + (System.currentTimeMillis() - start) + " ms"); + return eventNames; + }); + + // Ожидание завершения задач + Long userCount = userCountFuture.join(); + List events = eventsFuture.join(); + + long totalElapsedTime = System.currentTimeMillis() - startTime; + + // Формирование содержимого отчета + StringBuilder reportContent = new StringBuilder(); + reportContent.append(""); + reportContent.append("

Статистика приложения

"); + reportContent.append("

Количество пользователей: ").append(userCount).append("

"); + reportContent.append("

Список объектов Event:

    "); + events.forEach(eventName -> reportContent.append("
  • ").append(eventName).append("
  • ")); + reportContent.append("
"); + reportContent.append("

Общее время формирования отчета: ").append(totalElapsedTime).append(" ms

"); + reportContent.append(""); + + // Сохранение содержимого и обновление статуса отчета + report.setContent(reportContent.toString()); + report.setStatus(ReportStatus.COMPLETED); + } catch (Exception e) { + report.setStatus(ReportStatus.ERROR); + report.setContent("Произошла ошибка при формировании отчета: " + e.getMessage()); + e.printStackTrace(); + } finally { + reportRepository.save(report); + } + }); + } + + public Report saveReport(Report report) { + return reportRepository.save(report); + } +} + diff --git a/src/main/resources/templates/report.html b/src/main/resources/templates/report.html new file mode 100644 index 0000000..1d2efa1 --- /dev/null +++ b/src/main/resources/templates/report.html @@ -0,0 +1,33 @@ + + + + + Отчет + + +

Отчет о статистике

+

Количество зарегистрированных пользователей:

+ +

Список событий

+ + + + + + + + + + + + + + + +
Название событияДатаОписание
+ +

Время на получение количества пользователей: мс

+

Время на получение списка событий: мс

+

Общее время формирования отчета: мс

+ +