From a5fd5278511e98b35a0d6d8aaeb54a5dab702844 Mon Sep 17 00:00:00 2001 From: Mikhail Statsenko <112765729+MikhailStatsenko@users.noreply.github.com> Date: Sun, 28 Apr 2024 23:46:42 +0300 Subject: [PATCH 1/4] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + pom.xml | 68 +++++++++++++++++++ .../croc/analytics/AnalyticsApplication.java | 22 ++++++ .../analytics/ApplicationConfiguration.java | 15 ++++ src/main/java/edu/croc/analytics/Order.java | 27 ++++++++ .../edu/croc/analytics/ReportService.java | 66 ++++++++++++++++++ 6 files changed, 200 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/edu/croc/analytics/AnalyticsApplication.java create mode 100644 src/main/java/edu/croc/analytics/ApplicationConfiguration.java create mode 100644 src/main/java/edu/croc/analytics/Order.java create mode 100644 src/main/java/edu/croc/analytics/ReportService.java diff --git a/README.md b/README.md index ee9d69b..650be4b 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,9 @@ 4. Найденный в соответствии с условием задачи месяц должен выводиться на английском языке в нижнем регистре. Если месяцев несколько, то на вывод они все подаются на английском языке в нижнем регистре в порядке их следования в течение года. ## Автор решения +Стаценко Михаил Александрович ## Описание реализации + ## Инструкция по сборке и запуску решения diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e5aae90 --- /dev/null +++ b/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + edu.croc + analitics + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 3.2.2 + + + + + UTF-8 + UTF-8 + + 21 + + 3.9.6 + + 21 + 21 + 3.12.1 + + + + + org.springframework.boot + spring-boot-starter + + + + org.projectlombok + lombok + true + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.17.0 + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/src/main/java/edu/croc/analytics/AnalyticsApplication.java b/src/main/java/edu/croc/analytics/AnalyticsApplication.java new file mode 100644 index 0000000..3011a28 --- /dev/null +++ b/src/main/java/edu/croc/analytics/AnalyticsApplication.java @@ -0,0 +1,22 @@ +package edu.croc.analytics; + +import lombok.RequiredArgsConstructor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@RequiredArgsConstructor +public class AnalyticsApplication implements CommandLineRunner { + private final ReportService reportService; + + public static void main(String[] args) { + SpringApplication.run(AnalyticsApplication.class); + } + + @Override + public void run(String... args) throws Exception { + String report = reportService.generateReport("format.json"); + System.out.println(report); + } +} diff --git a/src/main/java/edu/croc/analytics/ApplicationConfiguration.java b/src/main/java/edu/croc/analytics/ApplicationConfiguration.java new file mode 100644 index 0000000..01e0e2f --- /dev/null +++ b/src/main/java/edu/croc/analytics/ApplicationConfiguration.java @@ -0,0 +1,15 @@ +package edu.croc.analytics; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ApplicationConfiguration { + @Bean + public ObjectMapper objectMapper() { + return JsonMapper.builder().addModule(new JavaTimeModule()).build(); + } +} diff --git a/src/main/java/edu/croc/analytics/Order.java b/src/main/java/edu/croc/analytics/Order.java new file mode 100644 index 0000000..a5ffd1e --- /dev/null +++ b/src/main/java/edu/croc/analytics/Order.java @@ -0,0 +1,27 @@ +package edu.croc.analytics; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +public record Order ( + @JsonProperty("user_id") + String userId, + + @JsonProperty("ordered_at") + LocalDateTime orderedAt, + + @JsonProperty("status") + Status status, + + @JsonProperty("total") + BigDecimal total) { + + public enum Status { + COMPLETED, + CANCELED, + CREATED, + DELIVERY + } +} diff --git a/src/main/java/edu/croc/analytics/ReportService.java b/src/main/java/edu/croc/analytics/ReportService.java new file mode 100644 index 0000000..2fa94c8 --- /dev/null +++ b/src/main/java/edu/croc/analytics/ReportService.java @@ -0,0 +1,66 @@ +package edu.croc.analytics; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.time.Month; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Service +@RequiredArgsConstructor +public class ReportService { + private final ObjectMapper mapper; + + private record Report(List months) {} + + public String generateReport(String filePath) throws IOException { + Order[] orders = readOrdersJson(filePath); + + Map totals = getTotalByMonth(orders); + + List months = getMaxTotalMonths(totals); + return mapper.writeValueAsString(new Report(months)); + } + + private Order[] readOrdersJson(String filePath) throws IOException { + var source = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8)); + return mapper.readValue(source, Order[].class); + } + + private Map getTotalByMonth(Order[] orders) { + Map totals = new ConcurrentHashMap<>(Month.values().length); + for (Month month : Month.values()) { + totals.put(month, BigDecimal.ZERO); + } + + Arrays.stream(orders).parallel() + .filter(order -> order.status().equals(Order.Status.COMPLETED)) + .forEach(order -> totals.compute( + order.orderedAt().getMonth(), + (moth, currentMothTotal) -> currentMothTotal.add(order.total())) + ); + return totals; + } + + private List getMaxTotalMonths(Map totals) { + BigDecimal max = Collections.max(totals.values()); + return totals.entrySet() + .stream() + .filter(entry -> entry.getValue().equals(max)) + .map(Map.Entry::getKey) + .map(Month::name) + .map(String::toLowerCase) + .toList(); + } +} From 8b7324f958d58a4411905b5cf65d2bb7bb934eb6 Mon Sep 17 00:00:00 2001 From: Mikhail Statsenko <112765729+MikhailStatsenko@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:33:42 +0300 Subject: [PATCH 2/4] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8,=20?= =?UTF-8?q?=D0=BE=D1=82=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=84=D0=B0=D0=B9=D0=BB=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +++ pom.xml | 56 ++++++++++++------- .../croc/analytics/AnalyticsApplication.java | 15 +++-- .../ApplicationConfiguration.java | 2 +- .../edu/croc/analytics/{ => dto}/Order.java | 2 +- .../{ => service}/ReportService.java | 11 +++- 6 files changed, 65 insertions(+), 29 deletions(-) rename src/main/java/edu/croc/analytics/{ => configuration}/ApplicationConfiguration.java (91%) rename src/main/java/edu/croc/analytics/{ => dto}/Order.java (93%) rename src/main/java/edu/croc/analytics/{ => service}/ReportService.java (86%) diff --git a/README.md b/README.md index 650be4b..b7397ee 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,14 @@ Стаценко Михаил Александрович ## Описание реализации +Для выполнения поставленной задачи разработано консольное Spring Boot приложение. +Первым аргументом командной строки передается путь к файлу с исходными данными. `ReportService` в методе `generateReport` при помощи вспомогательных методов читает json файл по указанному пути, затем считает сумму, потраченную пользователями для каждого месяца, после чего находит месяц(-ы) с максимальной суммой и возвращает строку в формате json в соответствии с заданием. + +Поскольку речь идет об анализе заказов на маркетплейсе, отчет, вероятно, будет создаваться на основании миллионов заказов, поэтому для подсчета суммы, потраченной пользователями по месяцам используется параллельная обработка, а файл с исходными данными читается при помощи `BufferedReader`. ## Инструкция по сборке и запуску решения +Последовательно выполнить команды + +`mvn clean package` +`java -jar target/analytics-1.0.jar [путь к файлу с исходными данными]` \ No newline at end of file diff --git a/pom.xml b/pom.xml index e5aae90..d106f6d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,8 @@ 4.0.0 edu.croc - analitics - 1.0-SNAPSHOT + analytics + 1.0 org.springframework.boot @@ -21,8 +21,6 @@ 21 - 3.9.6 - 21 21 3.12.1 @@ -40,29 +38,47 @@ true - com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.17.0 - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - org.springframework.boot - spring-boot-maven-plugin - - - + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + prepare-package + + copy-dependencies + + + ${project.build.directory}/lib + false + false + true + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + lib/ + edu.croc.analytics.AnalyticsApplication + + + + + \ No newline at end of file diff --git a/src/main/java/edu/croc/analytics/AnalyticsApplication.java b/src/main/java/edu/croc/analytics/AnalyticsApplication.java index 3011a28..8bf0563 100644 --- a/src/main/java/edu/croc/analytics/AnalyticsApplication.java +++ b/src/main/java/edu/croc/analytics/AnalyticsApplication.java @@ -1,22 +1,29 @@ package edu.croc.analytics; +import edu.croc.analytics.service.ReportService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +@Slf4j @SpringBootApplication @RequiredArgsConstructor public class AnalyticsApplication implements CommandLineRunner { private final ReportService reportService; public static void main(String[] args) { - SpringApplication.run(AnalyticsApplication.class); + SpringApplication.run(AnalyticsApplication.class, args); } @Override - public void run(String... args) throws Exception { - String report = reportService.generateReport("format.json"); - System.out.println(report); + public void run(String... args) { + try { + String report = reportService.generateReport(args[0]); + System.out.println(report); + } catch (Exception e) { + log.error("Error generating report", e); + } } } diff --git a/src/main/java/edu/croc/analytics/ApplicationConfiguration.java b/src/main/java/edu/croc/analytics/configuration/ApplicationConfiguration.java similarity index 91% rename from src/main/java/edu/croc/analytics/ApplicationConfiguration.java rename to src/main/java/edu/croc/analytics/configuration/ApplicationConfiguration.java index 01e0e2f..5543903 100644 --- a/src/main/java/edu/croc/analytics/ApplicationConfiguration.java +++ b/src/main/java/edu/croc/analytics/configuration/ApplicationConfiguration.java @@ -1,4 +1,4 @@ -package edu.croc.analytics; +package edu.croc.analytics.configuration; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; diff --git a/src/main/java/edu/croc/analytics/Order.java b/src/main/java/edu/croc/analytics/dto/Order.java similarity index 93% rename from src/main/java/edu/croc/analytics/Order.java rename to src/main/java/edu/croc/analytics/dto/Order.java index a5ffd1e..27263dd 100644 --- a/src/main/java/edu/croc/analytics/Order.java +++ b/src/main/java/edu/croc/analytics/dto/Order.java @@ -1,4 +1,4 @@ -package edu.croc.analytics; +package edu.croc.analytics.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/edu/croc/analytics/ReportService.java b/src/main/java/edu/croc/analytics/service/ReportService.java similarity index 86% rename from src/main/java/edu/croc/analytics/ReportService.java rename to src/main/java/edu/croc/analytics/service/ReportService.java index 2fa94c8..5f7e0bf 100644 --- a/src/main/java/edu/croc/analytics/ReportService.java +++ b/src/main/java/edu/croc/analytics/service/ReportService.java @@ -1,7 +1,9 @@ -package edu.croc.analytics; +package edu.croc.analytics.service; import com.fasterxml.jackson.databind.ObjectMapper; +import edu.croc.analytics.dto.Order; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.io.BufferedReader; @@ -17,6 +19,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +@Slf4j @Service @RequiredArgsConstructor public class ReportService { @@ -34,8 +37,10 @@ public String generateReport(String filePath) throws IOException { } private Order[] readOrdersJson(String filePath) throws IOException { - var source = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8)); - return mapper.readValue(source, Order[].class); + try(var source = new BufferedReader( + new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) { + return mapper.readValue(source, Order[].class); + } } private Map getTotalByMonth(Order[] orders) { From 50a9b9c97b9dcb2f113b8a3994b0f2557a471082 Mon Sep 17 00:00:00 2001 From: Mikhail Statsenko <112765729+MikhailStatsenko@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:19:25 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++++- pom.xml | 1 - .../croc/analytics/service/ReportService.java | 20 +++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b7397ee..7ad25d2 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,11 @@ Поскольку речь идет об анализе заказов на маркетплейсе, отчет, вероятно, будет создаваться на основании миллионов заказов, поэтому для подсчета суммы, потраченной пользователями по месяцам используется параллельная обработка, а файл с исходными данными читается при помощи `BufferedReader`. ## Инструкция по сборке и запуску решения -Последовательно выполнить команды +На тестовом стенде должны быть установлены Maven и Java 21+ версии +Сначала необходимо клонировать репозиторий на локальную машину: +`git clone https://github.com/MikhailStatsenko/school2024-test-task1.git` + +Затем нужно перейти в склонированную директорию и последовательно выполнить команды: `mvn clean package` `java -jar target/analytics-1.0.jar [путь к файлу с исходными данными]` \ No newline at end of file diff --git a/pom.xml b/pom.xml index d106f6d..8ee326d 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,6 @@ 21 21 - 3.12.1 diff --git a/src/main/java/edu/croc/analytics/service/ReportService.java b/src/main/java/edu/croc/analytics/service/ReportService.java index 5f7e0bf..dd0b2e7 100644 --- a/src/main/java/edu/croc/analytics/service/ReportService.java +++ b/src/main/java/edu/croc/analytics/service/ReportService.java @@ -13,10 +13,7 @@ import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.time.Month; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; @Slf4j @@ -60,12 +57,13 @@ private Map getTotalByMonth(Order[] orders) { private List getMaxTotalMonths(Map totals) { BigDecimal max = Collections.max(totals.values()); - return totals.entrySet() - .stream() - .filter(entry -> entry.getValue().equals(max)) - .map(Map.Entry::getKey) - .map(Month::name) - .map(String::toLowerCase) - .toList(); + + List result = new ArrayList<>(); + for (Month month : Month.values()) { + if (totals.get(month).equals(max)) { + result.add(month.name().toLowerCase()); + } + } + return result; } } From 4db74f7c839882e49e5f7aa18bf9b8e31f8f8fb0 Mon Sep 17 00:00:00 2001 From: Mikhail Statsenko <112765729+MikhailStatsenko@users.noreply.github.com> Date: Wed, 1 May 2024 08:45:29 +0300 Subject: [PATCH 4/4] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=D1=80=D0=B0=D0=B2=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20BigDecimal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/edu/croc/analytics/service/ReportService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/croc/analytics/service/ReportService.java b/src/main/java/edu/croc/analytics/service/ReportService.java index dd0b2e7..bf0d0fc 100644 --- a/src/main/java/edu/croc/analytics/service/ReportService.java +++ b/src/main/java/edu/croc/analytics/service/ReportService.java @@ -60,7 +60,7 @@ private List getMaxTotalMonths(Map totals) { List result = new ArrayList<>(); for (Month month : Month.values()) { - if (totals.get(month).equals(max)) { + if (totals.get(month).compareTo(max) == 0) { result.add(month.name().toLowerCase()); } }