Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,21 @@
4. Найденный в соответствии с условием задачи месяц должен выводиться на английском языке в нижнем регистре. Если месяцев несколько, то на вывод они все подаются на английском языке в нижнем регистре в порядке их следования в течение года.

## Автор решения
Стаценко Михаил Александрович

## Описание реализации
Для выполнения поставленной задачи разработано консольное Spring Boot приложение.

Первым аргументом командной строки передается путь к файлу с исходными данными. `ReportService` в методе `generateReport` при помощи вспомогательных методов читает json файл по указанному пути, затем считает сумму, потраченную пользователями для каждого месяца, после чего находит месяц(-ы) с максимальной суммой и возвращает строку в формате json в соответствии с заданием.

Поскольку речь идет об анализе заказов на маркетплейсе, отчет, вероятно, будет создаваться на основании миллионов заказов, поэтому для подсчета суммы, потраченной пользователями по месяцам используется параллельная обработка, а файл с исходными данными читается при помощи `BufferedReader`.

## Инструкция по сборке и запуску решения
На тестовом стенде должны быть установлены Maven и Java 21+ версии

Сначала необходимо клонировать репозиторий на локальную машину:
`git clone https://github.com/MikhailStatsenko/school2024-test-task1.git`

Затем нужно перейти в склонированную директорию и последовательно выполнить команды:
`mvn clean package`
`java -jar target/analytics-1.0.jar [путь к файлу с исходными данными]`
83 changes: 83 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>edu.croc</groupId>
<artifactId>analytics</artifactId>
<version>1.0</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<java.release>21</java.release>

<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>edu.croc.analytics.AnalyticsApplication</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
29 changes: 29 additions & 0 deletions src/main/java/edu/croc/analytics/AnalyticsApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +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, args);
}

@Override
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package edu.croc.analytics.configuration;

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();
}
}
27 changes: 27 additions & 0 deletions src/main/java/edu/croc/analytics/dto/Order.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package edu.croc.analytics.dto;

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
}
}
69 changes: 69 additions & 0 deletions src/main/java/edu/croc/analytics/service/ReportService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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;
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.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Service
@RequiredArgsConstructor
public class ReportService {
private final ObjectMapper mapper;

private record Report(List<String> months) {}

public String generateReport(String filePath) throws IOException {
Order[] orders = readOrdersJson(filePath);

Map<Month, BigDecimal> totals = getTotalByMonth(orders);

List<String> months = getMaxTotalMonths(totals);
return mapper.writeValueAsString(new Report(months));
}

private Order[] readOrdersJson(String filePath) throws IOException {
try(var source = new BufferedReader(
new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {
return mapper.readValue(source, Order[].class);
}
}

private Map<Month, BigDecimal> getTotalByMonth(Order[] orders) {
Map<Month, BigDecimal> 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<String> getMaxTotalMonths(Map<Month, BigDecimal> totals) {
BigDecimal max = Collections.max(totals.values());

List<String> result = new ArrayList<>();
for (Month month : Month.values()) {
if (totals.get(month).compareTo(max) == 0) {
result.add(month.name().toLowerCase());
}
}
return result;
}
}