From 6cda771f1f12b8c005634d02077c1aa1f850b0d5 Mon Sep 17 00:00:00 2001 From: Vanek_Vyaznikov Date: Sat, 28 Mar 2026 20:09:02 +0300 Subject: [PATCH 1/4] stage - add statistics service module --- docker-compose.yml | 32 ++++++--- pom.xml | 6 ++ statistics/client/pom.xml | 36 ++++++++++ .../statistics/client/StatsClient.java | 53 ++++++++++++++ statistics/dto/pom.xml | 27 +++++++ .../practicum/statistics/dto/EndpointHit.java | 20 ++++++ .../practicum/statistics/dto/ViewStats.java | 14 ++++ statistics/server/Dockerfile | 5 ++ statistics/server/pom.xml | 70 +++++++++++++++++++ .../practicum/statistics/StatsServerApp.java | 11 +++ .../controller/StatsController.java | 40 +++++++++++ .../statistics/exception/ErrorHandler.java | 38 ++++++++++ .../practicum/statistics/model/HitEntity.java | 31 ++++++++ .../statistics/repository/HitRepository.java | 28 ++++++++ .../statistics/service/StatsService.java | 42 +++++++++++ .../src/main/resources/application.properties | 14 ++++ .../server/src/main/resources/schema.sql | 8 +++ 17 files changed, 466 insertions(+), 9 deletions(-) create mode 100644 statistics/client/pom.xml create mode 100644 statistics/client/src/main/java/ru/practicum/statistics/client/StatsClient.java create mode 100644 statistics/dto/pom.xml create mode 100644 statistics/dto/src/main/java/ru/practicum/statistics/dto/EndpointHit.java create mode 100644 statistics/dto/src/main/java/ru/practicum/statistics/dto/ViewStats.java create mode 100644 statistics/server/Dockerfile create mode 100644 statistics/server/pom.xml create mode 100644 statistics/server/src/main/java/ru/practicum/statistics/StatsServerApp.java create mode 100644 statistics/server/src/main/java/ru/practicum/statistics/controller/StatsController.java create mode 100644 statistics/server/src/main/java/ru/practicum/statistics/exception/ErrorHandler.java create mode 100644 statistics/server/src/main/java/ru/practicum/statistics/model/HitEntity.java create mode 100644 statistics/server/src/main/java/ru/practicum/statistics/repository/HitRepository.java create mode 100644 statistics/server/src/main/java/ru/practicum/statistics/service/StatsService.java create mode 100644 statistics/server/src/main/resources/application.properties create mode 100644 statistics/server/src/main/resources/schema.sql diff --git a/docker-compose.yml b/docker-compose.yml index be96142..c5f2661 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,28 @@ services: - stats-server: - ports: - - "9090:9090" - stats-db: image: postgres:16.1 - - ewm-service: + container_name: stats-db ports: - - "8080:8080" + - "5432:5432" + environment: + POSTGRES_DB: statsdb + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 - ewm-db: - image: postgres:16.1 + stats-server: + build: ./statistics/server + container_name: stats-server + ports: + - "9090:9090" + depends_on: + stats-db: + condition: service_healthy + environment: + STATS_DB_URL: jdbc:postgresql://stats-db:5432/statsdb + STATS_DB_USER: postgres + STATS_DB_PASSWORD: password \ No newline at end of file diff --git a/pom.xml b/pom.xml index b15acb2..8d78f1a 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,12 @@ UTF-8 + + statistics/dto + statistics/client + statistics/server + + diff --git a/statistics/client/pom.xml b/statistics/client/pom.xml new file mode 100644 index 0000000..ed07f79 --- /dev/null +++ b/statistics/client/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + ru.practicum + explore-with-me + 0.0.1-SNAPSHOT + ../../pom.xml + + + statistics-client + + + + ru.practicum + statistics-dto + ${project.version} + + + org.springframework.boot + spring-boot-starter-web + + + org.apache.httpcomponents.client5 + httpclient5 + + + org.projectlombok + lombok + true + + + \ No newline at end of file diff --git a/statistics/client/src/main/java/ru/practicum/statistics/client/StatsClient.java b/statistics/client/src/main/java/ru/practicum/statistics/client/StatsClient.java new file mode 100644 index 0000000..338a4f6 --- /dev/null +++ b/statistics/client/src/main/java/ru/practicum/statistics/client/StatsClient.java @@ -0,0 +1,53 @@ +package ru.practicum.statistics.client; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import ru.practicum.statistics.dto.EndpointHit; +import ru.practicum.statistics.dto.ViewStats; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; + +@Service +public class StatsClient { + private final RestTemplate rest; + private final String serverUrl; + + public StatsClient(@Value("${stats-server.url:http://localhost:9090}") String serverUrl, + RestTemplateBuilder builder) { + this.serverUrl = serverUrl; + this.rest = builder.build(); + } + + public void sendHit(EndpointHit hit) { + HttpEntity requestEntity = new HttpEntity<>(hit); + rest.exchange(serverUrl + "/hit", HttpMethod.POST, requestEntity, Void.class); + } + + public List getStats(LocalDateTime start, LocalDateTime end, List uris, boolean unique) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + String startStr = URLEncoder.encode(start.format(formatter), StandardCharsets.UTF_8); + String endStr = URLEncoder.encode(end.format(formatter), StandardCharsets.UTF_8); + + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(serverUrl + "/stats") + .queryParam("start", startStr) + .queryParam("end", endStr) + .queryParam("unique", unique); + if (uris != null && !uris.isEmpty()) { + for (String uri : uris) { + builder.queryParam("uris", uri); + } + } + + ResponseEntity response = rest.getForEntity(builder.build().toUriString(), ViewStats[].class); + return Arrays.asList(response.getBody()); + } +} \ No newline at end of file diff --git a/statistics/dto/pom.xml b/statistics/dto/pom.xml new file mode 100644 index 0000000..c04ec71 --- /dev/null +++ b/statistics/dto/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + + ru.practicum + explore-with-me + 0.0.1-SNAPSHOT + ../../pom.xml + + + statistics-dto + + + + org.projectlombok + lombok + true + + + com.fasterxml.jackson.core + jackson-annotations + + + \ No newline at end of file diff --git a/statistics/dto/src/main/java/ru/practicum/statistics/dto/EndpointHit.java b/statistics/dto/src/main/java/ru/practicum/statistics/dto/EndpointHit.java new file mode 100644 index 0000000..1ff810a --- /dev/null +++ b/statistics/dto/src/main/java/ru/practicum/statistics/dto/EndpointHit.java @@ -0,0 +1,20 @@ +package ru.practicum.statistics.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class EndpointHit { + private Long id; + private String app; + private String uri; + private String ip; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timestamp; +} \ No newline at end of file diff --git a/statistics/dto/src/main/java/ru/practicum/statistics/dto/ViewStats.java b/statistics/dto/src/main/java/ru/practicum/statistics/dto/ViewStats.java new file mode 100644 index 0000000..079d83c --- /dev/null +++ b/statistics/dto/src/main/java/ru/practicum/statistics/dto/ViewStats.java @@ -0,0 +1,14 @@ +package ru.practicum.statistics.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ViewStats { + private String app; + private String uri; + private Long hits; +} \ No newline at end of file diff --git a/statistics/server/Dockerfile b/statistics/server/Dockerfile new file mode 100644 index 0000000..0ff1817 --- /dev/null +++ b/statistics/server/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/statistics/server/pom.xml b/statistics/server/pom.xml new file mode 100644 index 0000000..a82c870 --- /dev/null +++ b/statistics/server/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + + ru.practicum + explore-with-me + 0.0.1-SNAPSHOT + ../../pom.xml + + + statistics-server + + + + ru.practicum + statistics-dto + ${project.version} + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-actuator + + + org.postgresql + postgresql + runtime + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + com.h2database + h2 + test + + + org.springframework.boot + spring-boot-starter-validation + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/statistics/server/src/main/java/ru/practicum/statistics/StatsServerApp.java b/statistics/server/src/main/java/ru/practicum/statistics/StatsServerApp.java new file mode 100644 index 0000000..df27dc0 --- /dev/null +++ b/statistics/server/src/main/java/ru/practicum/statistics/StatsServerApp.java @@ -0,0 +1,11 @@ +package ru.practicum.statistics; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class StatsServerApp { + public static void main(String[] args) { + SpringApplication.run(StatsServerApp.class, args); + } +} \ No newline at end of file diff --git a/statistics/server/src/main/java/ru/practicum/statistics/controller/StatsController.java b/statistics/server/src/main/java/ru/practicum/statistics/controller/StatsController.java new file mode 100644 index 0000000..6daeb85 --- /dev/null +++ b/statistics/server/src/main/java/ru/practicum/statistics/controller/StatsController.java @@ -0,0 +1,40 @@ +package ru.practicum.statistics.controller; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.statistics.dto.EndpointHit; +import ru.practicum.statistics.dto.ViewStats; +import ru.practicum.statistics.service.StatsService; + +import java.time.LocalDateTime; +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class StatsController { + private final StatsService statsService; + + @PostMapping("/hit") + public ResponseEntity hit(@Valid @RequestBody EndpointHit hit) { + statsService.saveHit(hit); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @GetMapping("/stats") + public List getStats( + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @NotNull LocalDateTime start, + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @NotNull LocalDateTime end, + @RequestParam(required = false) List uris, + @RequestParam(defaultValue = "false") boolean unique) { + + if (start.isAfter(end)) { + throw new IllegalArgumentException("Дата начала должна быть раньше даты окончания."); + } + return statsService.getStats(start, end, uris, unique); + } +} \ No newline at end of file diff --git a/statistics/server/src/main/java/ru/practicum/statistics/exception/ErrorHandler.java b/statistics/server/src/main/java/ru/practicum/statistics/exception/ErrorHandler.java new file mode 100644 index 0000000..ce6bc7e --- /dev/null +++ b/statistics/server/src/main/java/ru/practicum/statistics/exception/ErrorHandler.java @@ -0,0 +1,38 @@ +package ru.practicum.statistics.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleIllegalArgument(IllegalArgumentException e) { + return new ErrorResponse("Ошибка валидации", e.getMessage()); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleTypeMismatch(MethodArgumentTypeMismatchException e) { + return new ErrorResponse("Ошибка валидации", "Некорректное значение параметра: " + e.getValue()); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleException(Exception e) { + return new ErrorResponse("Внутренняя ошибка сервера", e.getMessage()); + } + + @Getter + @AllArgsConstructor + static class ErrorResponse { + private final String error; + private final String description; + } +} \ No newline at end of file diff --git a/statistics/server/src/main/java/ru/practicum/statistics/model/HitEntity.java b/statistics/server/src/main/java/ru/practicum/statistics/model/HitEntity.java new file mode 100644 index 0000000..88e126a --- /dev/null +++ b/statistics/server/src/main/java/ru/practicum/statistics/model/HitEntity.java @@ -0,0 +1,31 @@ +package ru.practicum.statistics.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "hits") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class HitEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String app; + + @Column(nullable = false) + private String uri; + + @Column(nullable = false) + private String ip; + + @Column(name = "timestamp", nullable = false) + private LocalDateTime timestamp; +} \ No newline at end of file diff --git a/statistics/server/src/main/java/ru/practicum/statistics/repository/HitRepository.java b/statistics/server/src/main/java/ru/practicum/statistics/repository/HitRepository.java new file mode 100644 index 0000000..9f1045c --- /dev/null +++ b/statistics/server/src/main/java/ru/practicum/statistics/repository/HitRepository.java @@ -0,0 +1,28 @@ +package ru.practicum.statistics.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import ru.practicum.statistics.model.HitEntity; + +import java.time.LocalDateTime; +import java.util.List; + +public interface HitRepository extends JpaRepository { + + @Query("SELECT h.app, h.uri, COUNT(DISTINCT h.ip) as hits FROM HitEntity h " + + "WHERE h.timestamp BETWEEN :start AND :end " + + "AND (:uris IS NULL OR h.uri IN :uris) " + + "GROUP BY h.app, h.uri") + List countUniqueHits(@Param("start") LocalDateTime start, + @Param("end") LocalDateTime end, + @Param("uris") List uris); + + @Query("SELECT h.app, h.uri, COUNT(h) as hits FROM HitEntity h " + + "WHERE h.timestamp BETWEEN :start AND :end " + + "AND (:uris IS NULL OR h.uri IN :uris) " + + "GROUP BY h.app, h.uri") + List countAllHits(@Param("start") LocalDateTime start, + @Param("end") LocalDateTime end, + @Param("uris") List uris); +} \ No newline at end of file diff --git a/statistics/server/src/main/java/ru/practicum/statistics/service/StatsService.java b/statistics/server/src/main/java/ru/practicum/statistics/service/StatsService.java new file mode 100644 index 0000000..fd9703e --- /dev/null +++ b/statistics/server/src/main/java/ru/practicum/statistics/service/StatsService.java @@ -0,0 +1,42 @@ +package ru.practicum.statistics.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.statistics.dto.EndpointHit; +import ru.practicum.statistics.dto.ViewStats; +import ru.practicum.statistics.model.HitEntity; +import ru.practicum.statistics.repository.HitRepository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class StatsService { + private final HitRepository hitRepository; + + @Transactional + public void saveHit(EndpointHit hit) { + HitEntity entity = new HitEntity(); + entity.setApp(hit.getApp()); + entity.setUri(hit.getUri()); + entity.setIp(hit.getIp()); + entity.setTimestamp(hit.getTimestamp()); + hitRepository.save(entity); + } + + @Transactional(readOnly = true) + public List getStats(LocalDateTime start, LocalDateTime end, List uris, boolean unique) { + List results; + if (unique) { + results = hitRepository.countUniqueHits(start, end, uris); + } else { + results = hitRepository.countAllHits(start, end, uris); + } + return results.stream() + .map(row -> new ViewStats((String) row[0], (String) row[1], (Long) row[2])) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/statistics/server/src/main/resources/application.properties b/statistics/server/src/main/resources/application.properties new file mode 100644 index 0000000..f4fe0ee --- /dev/null +++ b/statistics/server/src/main/resources/application.properties @@ -0,0 +1,14 @@ +server.port=9090 +spring.application.name=stats-server + +spring.datasource.url=${STATS_DB_URL:jdbc:postgresql://localhost:5432/statsdb} +spring.datasource.username=${STATS_DB_USER:postgres} +spring.datasource.password=${STATS_DB_PASSWORD:password} +spring.datasource.driver-class-name=org.postgresql.Driver + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.format_sql=true +spring.sql.init.mode=always + +logging.level.org.springframework.orm.jpa=INFO +logging.level.org.springframework.transaction=INFO \ No newline at end of file diff --git a/statistics/server/src/main/resources/schema.sql b/statistics/server/src/main/resources/schema.sql new file mode 100644 index 0000000..18cec6e --- /dev/null +++ b/statistics/server/src/main/resources/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS hits ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + app VARCHAR(255) NOT NULL, + uri VARCHAR(512) NOT NULL, + ip VARCHAR(64) NOT NULL, + timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL, + CONSTRAINT pk_hit PRIMARY KEY (id) +); \ No newline at end of file From 99eee690ba5133109ce8b3976d56cb0ab9438a1a Mon Sep 17 00:00:00 2001 From: Vanek_Vyaznikov Date: Sat, 28 Mar 2026 20:18:05 +0300 Subject: [PATCH 2/4] fix1 --- .../ru/practicum/statistics/repository/HitRepository.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/statistics/server/src/main/java/ru/practicum/statistics/repository/HitRepository.java b/statistics/server/src/main/java/ru/practicum/statistics/repository/HitRepository.java index 9f1045c..9a9bd35 100644 --- a/statistics/server/src/main/java/ru/practicum/statistics/repository/HitRepository.java +++ b/statistics/server/src/main/java/ru/practicum/statistics/repository/HitRepository.java @@ -13,7 +13,8 @@ public interface HitRepository extends JpaRepository { @Query("SELECT h.app, h.uri, COUNT(DISTINCT h.ip) as hits FROM HitEntity h " + "WHERE h.timestamp BETWEEN :start AND :end " + "AND (:uris IS NULL OR h.uri IN :uris) " + - "GROUP BY h.app, h.uri") + "GROUP BY h.app, h.uri " + + "ORDER BY hits DESC") List countUniqueHits(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end, @Param("uris") List uris); @@ -21,7 +22,8 @@ List countUniqueHits(@Param("start") LocalDateTime start, @Query("SELECT h.app, h.uri, COUNT(h) as hits FROM HitEntity h " + "WHERE h.timestamp BETWEEN :start AND :end " + "AND (:uris IS NULL OR h.uri IN :uris) " + - "GROUP BY h.app, h.uri") + "GROUP BY h.app, h.uri " + + "ORDER BY hits DESC") List countAllHits(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end, @Param("uris") List uris); From fc46d5c251f204af0505035a4633ca06cb323c06 Mon Sep 17 00:00:00 2001 From: Vanek_Vyaznikov Date: Sun, 29 Mar 2026 12:06:43 +0300 Subject: [PATCH 3/4] fix2 --- docker-compose.yml | 30 +++++++++++++- ewm-service/Dockerfile | 5 +++ ewm-service/pom.xml | 40 +++++++++++++++++++ .../java/ru/practicum/ewm/EwmServiceApp.java | 11 +++++ .../src/main/resources/application.properties | 2 + pom.xml | 1 + .../statistics/client/StatsClient.java | 7 ++-- statistics/dto/pom.xml | 4 ++ .../practicum/statistics/dto/EndpointHit.java | 10 +++++ statistics/server/pom.xml | 1 - .../statistics/exception/ErrorHandler.java | 11 +++++ .../statistics/mapper/HitMapper.java | 19 +++++++++ .../statistics/service/StatsService.java | 7 +--- 13 files changed, 138 insertions(+), 10 deletions(-) create mode 100644 ewm-service/Dockerfile create mode 100644 ewm-service/pom.xml create mode 100644 ewm-service/src/main/java/ru/practicum/ewm/EwmServiceApp.java create mode 100644 ewm-service/src/main/resources/application.properties create mode 100644 statistics/server/src/main/java/ru/practicum/statistics/mapper/HitMapper.java diff --git a/docker-compose.yml b/docker-compose.yml index c5f2661..db8c301 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,4 +25,32 @@ services: environment: STATS_DB_URL: jdbc:postgresql://stats-db:5432/statsdb STATS_DB_USER: postgres - STATS_DB_PASSWORD: password \ No newline at end of file + STATS_DB_PASSWORD: password + + ewm-db: + image: postgres:16.1 + container_name: ewm-db + ports: + - "5433:5432" + environment: + POSTGRES_DB: ewmdb + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + ewm-service: + build: ./ewm-service + container_name: ewm-service + ports: + - "8080:8080" + depends_on: + ewm-db: + condition: service_healthy + environment: + SPRING_DATASOURCE_URL: jdbc:postgresql://ewm-db:5432/ewmdb + SPRING_DATASOURCE_USERNAME: postgres + SPRING_DATASOURCE_PASSWORD: password \ No newline at end of file diff --git a/ewm-service/Dockerfile b/ewm-service/Dockerfile new file mode 100644 index 0000000..0ff1817 --- /dev/null +++ b/ewm-service/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/ewm-service/pom.xml b/ewm-service/pom.xml new file mode 100644 index 0000000..ec1add2 --- /dev/null +++ b/ewm-service/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + + ru.practicum + explore-with-me + 0.0.1-SNAPSHOT + ../pom.xml + + + ewm-service + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.projectlombok + lombok + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/ewm-service/src/main/java/ru/practicum/ewm/EwmServiceApp.java b/ewm-service/src/main/java/ru/practicum/ewm/EwmServiceApp.java new file mode 100644 index 0000000..6345abd --- /dev/null +++ b/ewm-service/src/main/java/ru/practicum/ewm/EwmServiceApp.java @@ -0,0 +1,11 @@ +package ru.practicum.ewm; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class EwmServiceApp { + public static void main(String[] args) { + SpringApplication.run(EwmServiceApp.class, args); + } +} \ No newline at end of file diff --git a/ewm-service/src/main/resources/application.properties b/ewm-service/src/main/resources/application.properties new file mode 100644 index 0000000..055ebb3 --- /dev/null +++ b/ewm-service/src/main/resources/application.properties @@ -0,0 +1,2 @@ +server.port=8080 +spring.application.name=ewm-service \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8d78f1a..4e739ce 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ statistics/dto statistics/client statistics/server + ewm-service diff --git a/statistics/client/src/main/java/ru/practicum/statistics/client/StatsClient.java b/statistics/client/src/main/java/ru/practicum/statistics/client/StatsClient.java index 338a4f6..3d5ff58 100644 --- a/statistics/client/src/main/java/ru/practicum/statistics/client/StatsClient.java +++ b/statistics/client/src/main/java/ru/practicum/statistics/client/StatsClient.java @@ -18,6 +18,8 @@ @Service public class StatsClient { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private final RestTemplate rest; private final String serverUrl; @@ -33,9 +35,8 @@ public void sendHit(EndpointHit hit) { } public List getStats(LocalDateTime start, LocalDateTime end, List uris, boolean unique) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - String startStr = URLEncoder.encode(start.format(formatter), StandardCharsets.UTF_8); - String endStr = URLEncoder.encode(end.format(formatter), StandardCharsets.UTF_8); + String startStr = URLEncoder.encode(start.format(DATE_TIME_FORMATTER), StandardCharsets.UTF_8); + String endStr = URLEncoder.encode(end.format(DATE_TIME_FORMATTER), StandardCharsets.UTF_8); UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(serverUrl + "/stats") .queryParam("start", startStr) diff --git a/statistics/dto/pom.xml b/statistics/dto/pom.xml index c04ec71..d003ff2 100644 --- a/statistics/dto/pom.xml +++ b/statistics/dto/pom.xml @@ -23,5 +23,9 @@ com.fasterxml.jackson.core jackson-annotations + + jakarta.validation + jakarta.validation-api + \ No newline at end of file diff --git a/statistics/dto/src/main/java/ru/practicum/statistics/dto/EndpointHit.java b/statistics/dto/src/main/java/ru/practicum/statistics/dto/EndpointHit.java index 1ff810a..5f47f50 100644 --- a/statistics/dto/src/main/java/ru/practicum/statistics/dto/EndpointHit.java +++ b/statistics/dto/src/main/java/ru/practicum/statistics/dto/EndpointHit.java @@ -1,6 +1,8 @@ package ru.practicum.statistics.dto; import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -12,9 +14,17 @@ @AllArgsConstructor public class EndpointHit { private Long id; + + @NotBlank(message = "Название приложения не может быть пустым") private String app; + + @NotBlank(message = "URI не может быть пустым") private String uri; + + @NotBlank(message = "IP-адрес не может быть пустым") private String ip; + + @NotNull(message = "Временная метка не может быть null") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime timestamp; } \ No newline at end of file diff --git a/statistics/server/pom.xml b/statistics/server/pom.xml index a82c870..499b211 100644 --- a/statistics/server/pom.xml +++ b/statistics/server/pom.xml @@ -42,7 +42,6 @@ lombok true - org.springframework.boot spring-boot-starter-test diff --git a/statistics/server/src/main/java/ru/practicum/statistics/exception/ErrorHandler.java b/statistics/server/src/main/java/ru/practicum/statistics/exception/ErrorHandler.java index ce6bc7e..3438870 100644 --- a/statistics/server/src/main/java/ru/practicum/statistics/exception/ErrorHandler.java +++ b/statistics/server/src/main/java/ru/practicum/statistics/exception/ErrorHandler.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -29,6 +30,16 @@ public ErrorResponse handleException(Exception e) { return new ErrorResponse("Внутренняя ошибка сервера", e.getMessage()); } + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleMethodArgumentNotValid(MethodArgumentNotValidException e) { + String errorMessage = e.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .findFirst() + .orElse("Ошибка валидации"); + return new ErrorResponse("Ошибка валидации", errorMessage); + } + @Getter @AllArgsConstructor static class ErrorResponse { diff --git a/statistics/server/src/main/java/ru/practicum/statistics/mapper/HitMapper.java b/statistics/server/src/main/java/ru/practicum/statistics/mapper/HitMapper.java new file mode 100644 index 0000000..27ba82b --- /dev/null +++ b/statistics/server/src/main/java/ru/practicum/statistics/mapper/HitMapper.java @@ -0,0 +1,19 @@ +package ru.practicum.statistics.mapper; + +import ru.practicum.statistics.dto.EndpointHit; +import ru.practicum.statistics.model.HitEntity; + +public class HitMapper { + + public static HitEntity toEntity(EndpointHit dto) { + if (dto == null) { + return null; + } + HitEntity entity = new HitEntity(); + entity.setApp(dto.getApp()); + entity.setUri(dto.getUri()); + entity.setIp(dto.getIp()); + entity.setTimestamp(dto.getTimestamp()); + return entity; + } +} \ No newline at end of file diff --git a/statistics/server/src/main/java/ru/practicum/statistics/service/StatsService.java b/statistics/server/src/main/java/ru/practicum/statistics/service/StatsService.java index fd9703e..d2a3e8a 100644 --- a/statistics/server/src/main/java/ru/practicum/statistics/service/StatsService.java +++ b/statistics/server/src/main/java/ru/practicum/statistics/service/StatsService.java @@ -5,6 +5,7 @@ import org.springframework.transaction.annotation.Transactional; import ru.practicum.statistics.dto.EndpointHit; import ru.practicum.statistics.dto.ViewStats; +import ru.practicum.statistics.mapper.HitMapper; import ru.practicum.statistics.model.HitEntity; import ru.practicum.statistics.repository.HitRepository; @@ -19,11 +20,7 @@ public class StatsService { @Transactional public void saveHit(EndpointHit hit) { - HitEntity entity = new HitEntity(); - entity.setApp(hit.getApp()); - entity.setUri(hit.getUri()); - entity.setIp(hit.getIp()); - entity.setTimestamp(hit.getTimestamp()); + HitEntity entity = HitMapper.toEntity(hit); hitRepository.save(entity); } From 873ef187660b745373fd4ec0bac246b2868b079c Mon Sep 17 00:00:00 2001 From: Vanek_Vyaznikov Date: Mon, 30 Mar 2026 18:50:41 +0300 Subject: [PATCH 4/4] fix3 --- pom.xml | 4 +--- statistics/client/pom.xml | 4 ++-- statistics/dto/pom.xml | 4 ++-- statistics/pom.xml | 22 ++++++++++++++++++++++ statistics/server/pom.xml | 4 ++-- 5 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 statistics/pom.xml diff --git a/pom.xml b/pom.xml index 4e739ce..23a1372 100644 --- a/pom.xml +++ b/pom.xml @@ -23,9 +23,7 @@ - statistics/dto - statistics/client - statistics/server + statistics ewm-service diff --git a/statistics/client/pom.xml b/statistics/client/pom.xml index ed07f79..a311cdc 100644 --- a/statistics/client/pom.xml +++ b/statistics/client/pom.xml @@ -6,9 +6,9 @@ ru.practicum - explore-with-me + statistics 0.0.1-SNAPSHOT - ../../pom.xml + ../pom.xml statistics-client diff --git a/statistics/dto/pom.xml b/statistics/dto/pom.xml index d003ff2..c22aa54 100644 --- a/statistics/dto/pom.xml +++ b/statistics/dto/pom.xml @@ -6,9 +6,9 @@ ru.practicum - explore-with-me + statistics 0.0.1-SNAPSHOT - ../../pom.xml + ../pom.xml statistics-dto diff --git a/statistics/pom.xml b/statistics/pom.xml new file mode 100644 index 0000000..afb69c6 --- /dev/null +++ b/statistics/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + ru.practicum + explore-with-me + 0.0.1-SNAPSHOT + ../pom.xml + + + statistics + pom + + + dto + client + server + + \ No newline at end of file diff --git a/statistics/server/pom.xml b/statistics/server/pom.xml index 499b211..45706f5 100644 --- a/statistics/server/pom.xml +++ b/statistics/server/pom.xml @@ -6,9 +6,9 @@ ru.practicum - explore-with-me + statistics 0.0.1-SNAPSHOT - ../../pom.xml + ../pom.xml statistics-server