From 439a23450030a47f46c9226cc47e86e4a3382019 Mon Sep 17 00:00:00 2001 From: andrej1307 Date: Tue, 20 May 2025 14:40:14 +0700 Subject: [PATCH 1/8] =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B0?= =?UTF-8?q?=20=D1=81=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D1=83=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- ewm-service/README.md | 1 + ewm-service/pom.xml | 20 ++++++++++++++++++++ pom.xml | 6 +++++- stats-server/README.md | 3 +++ stats-server/pom.xml | 26 ++++++++++++++++++++++++++ stats-server/stat-client/README.md | 3 +++ stats-server/stat-client/pom.xml | 20 ++++++++++++++++++++ stats-server/stat-dto/README.md | 3 +++ stats-server/stat-dto/pom.xml | 20 ++++++++++++++++++++ stats-server/stat-svc/README.md | 3 +++ stats-server/stat-svc/pom.xml | 20 ++++++++++++++++++++ 12 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 ewm-service/README.md create mode 100644 ewm-service/pom.xml create mode 100644 stats-server/README.md create mode 100644 stats-server/pom.xml create mode 100644 stats-server/stat-client/README.md create mode 100644 stats-server/stat-client/pom.xml create mode 100644 stats-server/stat-dto/README.md create mode 100644 stats-server/stat-dto/pom.xml create mode 100644 stats-server/stat-svc/README.md create mode 100644 stats-server/stat-svc/pom.xml diff --git a/README.md b/README.md index 18a246e..d36813d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # java-explore-with-me -Template repository for ExploreWithMe project. +Итоговый проект учебного курса. май 2025. гр. 53. diff --git a/ewm-service/README.md b/ewm-service/README.md new file mode 100644 index 0000000..f6eea04 --- /dev/null +++ b/ewm-service/README.md @@ -0,0 +1 @@ +# ewm-service \ No newline at end of file diff --git a/ewm-service/pom.xml b/ewm-service/pom.xml new file mode 100644 index 0000000..1743e58 --- /dev/null +++ b/ewm-service/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + ru.practicum + explore-with-me + 0.0.1-SNAPSHOT + + + ewm-service + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index b15acb2..9ee476e 100644 --- a/pom.xml +++ b/pom.xml @@ -11,8 +11,12 @@ Explore With Me + + stats-server + ewm-service + - ru.practicum + ru.practicum explore-with-me 0.0.1-SNAPSHOT pom diff --git a/stats-server/README.md b/stats-server/README.md new file mode 100644 index 0000000..32f6d2a --- /dev/null +++ b/stats-server/README.md @@ -0,0 +1,3 @@ +# stats-server + +модуль сервера статистики посещений diff --git a/stats-server/pom.xml b/stats-server/pom.xml new file mode 100644 index 0000000..5cb3240 --- /dev/null +++ b/stats-server/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + ru.practicum + explore-with-me + 0.0.1-SNAPSHOT + + + stats-server + pom + + stat-dto + stat-svc + stat-client + + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/stats-server/stat-client/README.md b/stats-server/stat-client/README.md new file mode 100644 index 0000000..c1607f7 --- /dev/null +++ b/stats-server/stat-client/README.md @@ -0,0 +1,3 @@ +# stat-client + +модуль описания классов для подключения клиента. \ No newline at end of file diff --git a/stats-server/stat-client/pom.xml b/stats-server/stat-client/pom.xml new file mode 100644 index 0000000..168434c --- /dev/null +++ b/stats-server/stat-client/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + ru.practicum + stats-server + 0.0.1-SNAPSHOT + + + stat-client + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/stats-server/stat-dto/README.md b/stats-server/stat-dto/README.md new file mode 100644 index 0000000..7698437 --- /dev/null +++ b/stats-server/stat-dto/README.md @@ -0,0 +1,3 @@ +# stat-dto + +модуль описания объектов передачи данных \ No newline at end of file diff --git a/stats-server/stat-dto/pom.xml b/stats-server/stat-dto/pom.xml new file mode 100644 index 0000000..f519e1c --- /dev/null +++ b/stats-server/stat-dto/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + ru.practicum + stats-server + 0.0.1-SNAPSHOT + + + stat-dto + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/stats-server/stat-svc/README.md b/stats-server/stat-svc/README.md new file mode 100644 index 0000000..2a6a48f --- /dev/null +++ b/stats-server/stat-svc/README.md @@ -0,0 +1,3 @@ +# stat-svc + +модуль описания классов сервиса статистики посещений diff --git a/stats-server/stat-svc/pom.xml b/stats-server/stat-svc/pom.xml new file mode 100644 index 0000000..b42fc99 --- /dev/null +++ b/stats-server/stat-svc/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + ru.practicum + stats-server + 0.0.1-SNAPSHOT + + + stat-svc + + + 21 + 21 + UTF-8 + + + \ No newline at end of file From ee75d7b141c248541a74ea3d23296441e2abc05f Mon Sep 17 00:00:00 2001 From: andrej1307 Date: Tue, 20 May 2025 16:43:26 +0700 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=D1=81=D0=B5=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ewm-service/pom.xml | 58 ++++++++++ .../ru/practicum/evmsevice/EwmServiceApp.java | 11 ++ .../controller/TestClientController.java | 49 ++++++++ .../src/main/resources/application.properties | 2 + stats-server/pom.xml | 15 +++ stats-server/stat-client/pom.xml | 36 ++++++ .../ru/practicum/statclient/StatClient.java | 15 +++ .../practicum/statclient/StatClientImpl.java | 78 +++++++++++++ stats-server/stat-dto/pom.xml | 13 +++ .../ru/practicum/statdto/ErrorMessage.java | 10 ++ .../java/ru/practicum/statdto/HitDto.java | 19 ++++ .../java/ru/practicum/statdto/StatsDto.java | 14 +++ stats-server/stat-svc/pom.xml | 72 ++++++++++++ .../java/ru/practicum/statsvc/StatSvcApp.java | 11 ++ .../statsvc/controller/ErrorAdvisor.java | 30 +++++ .../statsvc/controller/StatController.java | 39 +++++++ .../exception/InternalServerException.java | 7 ++ .../exception/ValidationException.java | 7 ++ .../statsvc/mapper/EndpointMapper.java | 18 +++ .../statsvc/mapper/ViewStatsMapper.java | 18 +++ .../statsvc/mapper/ViewStatsRowMapper.java | 18 +++ .../practicum/statsvc/model/EndpointHit.java | 20 ++++ .../ru/practicum/statsvc/model/ViewStats.java | 16 +++ .../statsvc/repository/StatDbStorage.java | 105 ++++++++++++++++++ .../statsvc/repository/StatStorage.java | 13 +++ .../statsvc/service/StatService.java | 13 +++ .../statsvc/service/StatServiceImpl.java | 51 +++++++++ .../src/main/resources/application.properties | 14 +++ .../stat-svc/src/main/resources/schema.sql | 8 ++ 29 files changed, 780 insertions(+) create mode 100644 ewm-service/src/main/java/ru/practicum/evmsevice/EwmServiceApp.java create mode 100644 ewm-service/src/main/java/ru/practicum/evmsevice/controller/TestClientController.java create mode 100644 ewm-service/src/main/resources/application.properties create mode 100644 stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClient.java create mode 100644 stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClientImpl.java create mode 100644 stats-server/stat-dto/src/main/java/ru/practicum/statdto/ErrorMessage.java create mode 100644 stats-server/stat-dto/src/main/java/ru/practicum/statdto/HitDto.java create mode 100644 stats-server/stat-dto/src/main/java/ru/practicum/statdto/StatsDto.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/StatSvcApp.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/controller/ErrorAdvisor.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/controller/StatController.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/exception/InternalServerException.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/exception/ValidationException.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/EndpointMapper.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/ViewStatsMapper.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/ViewStatsRowMapper.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/model/EndpointHit.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/model/ViewStats.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatDbStorage.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatStorage.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatService.java create mode 100644 stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatServiceImpl.java create mode 100644 stats-server/stat-svc/src/main/resources/application.properties create mode 100644 stats-server/stat-svc/src/main/resources/schema.sql diff --git a/ewm-service/pom.xml b/ewm-service/pom.xml index 1743e58..e643bbc 100644 --- a/ewm-service/pom.xml +++ b/ewm-service/pom.xml @@ -11,10 +11,68 @@ ewm-service + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + ru.practicum + stat-dto + 0.0.1-SNAPSHOT + compile + + + + org.projectlombok + lombok + true + + + ru.practicum + stat-client + 0.0.1-SNAPSHOT + compile + + + + 21 21 UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/ewm-service/src/main/java/ru/practicum/evmsevice/EwmServiceApp.java b/ewm-service/src/main/java/ru/practicum/evmsevice/EwmServiceApp.java new file mode 100644 index 0000000..20b8584 --- /dev/null +++ b/ewm-service/src/main/java/ru/practicum/evmsevice/EwmServiceApp.java @@ -0,0 +1,11 @@ +package ru.practicum.evmsevice; + +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); + } +} diff --git a/ewm-service/src/main/java/ru/practicum/evmsevice/controller/TestClientController.java b/ewm-service/src/main/java/ru/practicum/evmsevice/controller/TestClientController.java new file mode 100644 index 0000000..5c1a56f --- /dev/null +++ b/ewm-service/src/main/java/ru/practicum/evmsevice/controller/TestClientController.java @@ -0,0 +1,49 @@ +package ru.practicum.evmsevice.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.statclient.StatClient; +import ru.practicum.statdto.HitDto; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping +public class TestClientController { + + private StatClient statClient; + + @PostMapping("/hit") + @ResponseStatus(HttpStatus.CREATED) + public void hit(@RequestBody HitDto dto) { + log.info("Поступила информация о посещении : " + dto.toString()); + statClient.post(dto); + } + + @GetMapping("/stats") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getStats( + @RequestParam(required = false) String start, + @RequestParam(required = false) String end, + @RequestParam(required = false) String uris, + @RequestParam(defaultValue = "false") Boolean unique, + @RequestParam(defaultValue = "10") Integer size) { + log.info("Запрашивается информация о посещении эндпоинта {} с {} до {}.", uris, start, end); + + Map parameters = new HashMap<>(); + if (start != null) parameters.put("start", start); + if (end != null) parameters.put("end", end); + if (uris != null) parameters.put("uris", uris); + if (unique != null) parameters.put("unique", unique); + if (size != null) parameters.put("size", size); + ResponseEntity response = statClient.get(parameters); + return response; + } +} + diff --git a/ewm-service/src/main/resources/application.properties b/ewm-service/src/main/resources/application.properties new file mode 100644 index 0000000..3794df8 --- /dev/null +++ b/ewm-service/src/main/resources/application.properties @@ -0,0 +1,2 @@ +server.port=8080 +stat-server.url=http://localhost:9090 \ No newline at end of file diff --git a/stats-server/pom.xml b/stats-server/pom.xml index 5cb3240..ae9460d 100644 --- a/stats-server/pom.xml +++ b/stats-server/pom.xml @@ -17,6 +17,21 @@ stat-client + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.projectlombok + lombok + true + + + + 21 21 diff --git a/stats-server/stat-client/pom.xml b/stats-server/stat-client/pom.xml index 168434c..4e3e96d 100644 --- a/stats-server/stat-client/pom.xml +++ b/stats-server/stat-client/pom.xml @@ -11,6 +11,42 @@ stat-client + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + ru.practicum + stat-dto + 0.0.1-SNAPSHOT + compile + + + + 21 21 diff --git a/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClient.java b/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClient.java new file mode 100644 index 0000000..8c59915 --- /dev/null +++ b/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClient.java @@ -0,0 +1,15 @@ +package ru.practicum.statclient; + + +import org.springframework.http.ResponseEntity; +import org.springframework.lang.Nullable; + +import java.util.Map; + +public interface StatClient { + ResponseEntity get(); + + ResponseEntity get(@Nullable Map parameters); + + ResponseEntity post(T body); +} diff --git a/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClientImpl.java b/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClientImpl.java new file mode 100644 index 0000000..9d4fea6 --- /dev/null +++ b/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClientImpl.java @@ -0,0 +1,78 @@ +package ru.practicum.statclient; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriBuilderFactory; + +import java.util.Map; + +@Component +public class StatClientImpl implements StatClient { + private static final String PREFIX_HIT = "/hit"; + private static final String PREFIX_STATS = "/stats"; + + protected final RestTemplate rest; + + @Autowired + public StatClientImpl(@Value("${stat-server.url}") String serverUrl, RestTemplateBuilder builder) { + this.rest = builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build(); + } + + private static ResponseEntity prepareClientResponse(ResponseEntity response) { + if (response.getStatusCode().is2xxSuccessful()) { + return response; + } + ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); + if (response.hasBody()) { + return responseBuilder.body(response.getBody()); + } + return responseBuilder.build(); + } + + @Override + public ResponseEntity get() { + return get(null); + } + + @Override + public ResponseEntity get(@Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.GET, PREFIX_STATS, parameters, null); + } + + @Override + public ResponseEntity post(T body) { + return makeAndSendRequest(HttpMethod.POST, PREFIX_HIT, null, body); + } + + private ResponseEntity makeAndSendRequest(HttpMethod method, String path, @Nullable Map parameters, @Nullable T body) { + HttpEntity requestEntity = new HttpEntity<>(body); + ResponseEntity statServerResponse; + try { + if (parameters != null) { + StringBuilder stringParametrs = new StringBuilder(path); + stringParametrs.append("?"); + for (String key : parameters.keySet()) { + stringParametrs.append(key + "={" + key + "}&"); + } + statServerResponse = rest.exchange(stringParametrs.toString(), method, requestEntity, Object.class, parameters); + } else { + statServerResponse = rest.exchange(path, method, requestEntity, Object.class); + } + } catch (HttpStatusCodeException e) { + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); + } + return prepareClientResponse(statServerResponse); + } +} diff --git a/stats-server/stat-dto/pom.xml b/stats-server/stat-dto/pom.xml index f519e1c..4189fa1 100644 --- a/stats-server/stat-dto/pom.xml +++ b/stats-server/stat-dto/pom.xml @@ -11,6 +11,19 @@ stat-dto + + + org.springframework.boot + spring-boot-starter-actuator + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.9.0 + + + 21 21 diff --git a/stats-server/stat-dto/src/main/java/ru/practicum/statdto/ErrorMessage.java b/stats-server/stat-dto/src/main/java/ru/practicum/statdto/ErrorMessage.java new file mode 100644 index 0000000..0300d40 --- /dev/null +++ b/stats-server/stat-dto/src/main/java/ru/practicum/statdto/ErrorMessage.java @@ -0,0 +1,10 @@ +package ru.practicum.statdto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ErrorMessage { + private String error; +} \ No newline at end of file diff --git a/stats-server/stat-dto/src/main/java/ru/practicum/statdto/HitDto.java b/stats-server/stat-dto/src/main/java/ru/practicum/statdto/HitDto.java new file mode 100644 index 0000000..66ce613 --- /dev/null +++ b/stats-server/stat-dto/src/main/java/ru/practicum/statdto/HitDto.java @@ -0,0 +1,19 @@ +package ru.practicum.statdto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.*; + +import java.time.LocalDateTime; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class HitDto { + private String app; + private String uri; + private String ip; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timestamp; +} diff --git a/stats-server/stat-dto/src/main/java/ru/practicum/statdto/StatsDto.java b/stats-server/stat-dto/src/main/java/ru/practicum/statdto/StatsDto.java new file mode 100644 index 0000000..9922a8e --- /dev/null +++ b/stats-server/stat-dto/src/main/java/ru/practicum/statdto/StatsDto.java @@ -0,0 +1,14 @@ +package ru.practicum.statdto; + +import lombok.*; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class StatsDto { + private String app; + private String uri; + private Integer hits; +} diff --git a/stats-server/stat-svc/pom.xml b/stats-server/stat-svc/pom.xml index b42fc99..319e64e 100644 --- a/stats-server/stat-svc/pom.xml +++ b/stats-server/stat-svc/pom.xml @@ -11,10 +11,82 @@ stat-svc + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-jdbc + 3.4.1 + + + + + org.postgresql + postgresql + runtime + + + + com.h2database + h2 + runtime + + + + ru.practicum + stat-dto + 0.0.1-SNAPSHOT + compile + + + + 21 21 UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/StatSvcApp.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/StatSvcApp.java new file mode 100644 index 0000000..4d521b8 --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/StatSvcApp.java @@ -0,0 +1,11 @@ +package ru.practicum.statsvc; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class StatSvcApp { + public static void main(String[] args) { + SpringApplication.run(StatSvcApp.class, args); + } +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/controller/ErrorAdvisor.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/controller/ErrorAdvisor.java new file mode 100644 index 0000000..b8fbc50 --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/controller/ErrorAdvisor.java @@ -0,0 +1,30 @@ +package ru.practicum.statsvc.controller; + +import lombok.extern.slf4j.Slf4j; +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 ru.practicum.statdto.ErrorMessage; +import ru.practicum.statsvc.exception.InternalServerException; +import ru.practicum.statsvc.exception.ValidationException; + +@Slf4j +@RestControllerAdvice +public class ErrorAdvisor { + + @ExceptionHandler(ValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorMessage onValidationException(ValidationException exception) { + log.error("400 {}.", exception.getMessage()); + return new ErrorMessage(exception.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorMessage onInternalException(final InternalServerException e) { + log.error("500 {}", e.getMessage()); + return new ErrorMessage(e.getMessage()); + } + +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/controller/StatController.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/controller/StatController.java new file mode 100644 index 0000000..98bb153 --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/controller/StatController.java @@ -0,0 +1,39 @@ +package ru.practicum.statsvc.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.statdto.HitDto; +import ru.practicum.statdto.StatsDto; +import ru.practicum.statsvc.service.StatService; + +import java.util.List; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping +public class StatController { + + private final StatService statService; + + @PostMapping("/hit") + @ResponseStatus(HttpStatus.CREATED) + public void hit(@RequestBody HitDto dto) { + log.info("Поступила информация о посещении : " + dto.toString()); + statService.addHit(dto); + } + + @GetMapping("/stats") + @ResponseStatus(HttpStatus.OK) + public List getStats( + @RequestParam(required = false) String start, + @RequestParam(required = false) String end, + @RequestParam(required = false) List uris, + @RequestParam(defaultValue = "false") Boolean unique, + @RequestParam(defaultValue = "10") Integer size) { + log.info("Запрашивается информация о посещении эндпоинта {} с {} до {}.", uris, start, end); + return statService.getStats(start, end, uris, unique, size); + } +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/exception/InternalServerException.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/exception/InternalServerException.java new file mode 100644 index 0000000..685725d --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/exception/InternalServerException.java @@ -0,0 +1,7 @@ +package ru.practicum.statsvc.exception; + +public class InternalServerException extends RuntimeException { + public InternalServerException(String message) { + super(message); + } +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/exception/ValidationException.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/exception/ValidationException.java new file mode 100644 index 0000000..dba8ec4 --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/exception/ValidationException.java @@ -0,0 +1,7 @@ +package ru.practicum.statsvc.exception; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/EndpointMapper.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/EndpointMapper.java new file mode 100644 index 0000000..83cc122 --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/EndpointMapper.java @@ -0,0 +1,18 @@ +package ru.practicum.statsvc.mapper; + +import ru.practicum.statdto.HitDto; +import ru.practicum.statsvc.model.EndpointHit; + +public class EndpointMapper { + private EndpointMapper() { + } + + public static EndpointHit toEndpointHit(HitDto dto) { + EndpointHit endpoint = new EndpointHit(); + endpoint.setApp(dto.getApp()); + endpoint.setUri(dto.getUri()); + endpoint.setIp(dto.getIp()); + endpoint.setTimestamp(dto.getTimestamp()); + return endpoint; + } +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/ViewStatsMapper.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/ViewStatsMapper.java new file mode 100644 index 0000000..5cf4a1a --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/ViewStatsMapper.java @@ -0,0 +1,18 @@ +package ru.practicum.statsvc.mapper; + +import ru.practicum.statdto.StatsDto; +import ru.practicum.statsvc.model.ViewStats; + +public class ViewStatsMapper { + private ViewStatsMapper() { + } + + public static StatsDto toDto(ViewStats viewStats) { + StatsDto statsDto = new StatsDto( + viewStats.getApp(), + viewStats.getUri(), + viewStats.getHits() + ); + return statsDto; + } +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/ViewStatsRowMapper.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/ViewStatsRowMapper.java new file mode 100644 index 0000000..6d27e13 --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/mapper/ViewStatsRowMapper.java @@ -0,0 +1,18 @@ +package ru.practicum.statsvc.mapper; + +import org.springframework.jdbc.core.RowMapper; +import ru.practicum.statsvc.model.ViewStats; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ViewStatsRowMapper implements RowMapper { + @Override + public ViewStats mapRow(ResultSet resultSet, int rowNum) throws SQLException { + ViewStats viewStats = new ViewStats(); + viewStats.setApp(resultSet.getString("app")); + viewStats.setUri(resultSet.getString("uri")); + viewStats.setHits(resultSet.getInt("hits")); + return viewStats; + } +} \ No newline at end of file diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/model/EndpointHit.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/model/EndpointHit.java new file mode 100644 index 0000000..35f8e8e --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/model/EndpointHit.java @@ -0,0 +1,20 @@ +package ru.practicum.statsvc.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class EndpointHit { + private Integer id; + private String app; + private String uri; + private String ip; + private LocalDateTime timestamp; +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/model/ViewStats.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/model/ViewStats.java new file mode 100644 index 0000000..343b3eb --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/model/ViewStats.java @@ -0,0 +1,16 @@ +package ru.practicum.statsvc.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class ViewStats { + String app; + String uri; + Integer hits; +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatDbStorage.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatDbStorage.java new file mode 100644 index 0000000..80d5c95 --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatDbStorage.java @@ -0,0 +1,105 @@ +package ru.practicum.statsvc.repository; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.stereotype.Repository; +import ru.practicum.statsvc.exception.InternalServerException; +import ru.practicum.statsvc.mapper.ViewStatsRowMapper; +import ru.practicum.statsvc.model.EndpointHit; +import ru.practicum.statsvc.model.ViewStats; + +import java.sql.Types; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +@Repository +public class StatDbStorage implements StatStorage { + public static final DateTimeFormatter DATA_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final String SQL_INSERT_HIT = """ + INSERT INTO endpointhits (app, uri, ip, timestamp) + VALUES ( :app, :uri, :ip, :timestamp) + """; + + private final NamedParameterJdbcTemplate jdbc; + + public StatDbStorage(NamedParameterJdbcTemplate jdbc) { + this.jdbc = jdbc; + } + + @Override + public EndpointHit addHit(EndpointHit hit) { + GeneratedKeyHolder generatedKeyHolder = new GeneratedKeyHolder(); + try { + jdbc.update(SQL_INSERT_HIT, + new MapSqlParameterSource() + .addValue("app", hit.getApp()) + .addValue("uri", hit.getUri()) + .addValue("ip", hit.getIp()) + .addValue("timestamp", hit.getTimestamp().format(DATA_TIME_FORMATTER), Types.TIMESTAMP), + generatedKeyHolder, new String[]{"id"} + ); + } catch (DataAccessException e) { + throw new InternalServerException("Ошибка при сохранении в базу данных. " + + e.getMessage()); + } + // получаем идентификатор + final Integer hitId = generatedKeyHolder.getKey().intValue(); + hit.setId(hitId); + + return hit; + } + + @Override + public List getViewStats(LocalDateTime start, LocalDateTime end, List uris, Boolean unique, Integer size) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT app, uri, count(ip) as hits FROM"); + if (unique) { + sql.append(" (SELECT DISTINCT ON (ip) app, uri, ip, timestamp FROM endpointhits)"); + } else { + sql.append(" endpointhits"); + } + MapSqlParameterSource parameters = new MapSqlParameterSource(); + Boolean whereFlag = false; + + if (uris != null && !uris.isEmpty()) { + sql.append(" WHERE uri IN (:uris)"); + parameters.addValue("uris", uris); + whereFlag = true; + } + + if (start != null) { + if (whereFlag) { + sql.append(" AND timestamp >= :start"); + } else { + sql.append(" WHERE timestamp >= :start"); + whereFlag = true; + } + parameters.addValue("start", start); + } + if (end != null) { + if (whereFlag) { + sql.append(" AND timestamp < :end"); + } else { + sql.append(" WHERE timestamp < :end"); + } + parameters.addValue("end", end); + } + sql.append(" GROUP BY uri, app ORDER BY hits DESC"); + if (size != null) { + parameters.addValue("size", size); + sql.append(" LIMIT :size"); + } + try { + List viewStatsList = jdbc.query(sql.toString(), + parameters, + new ViewStatsRowMapper()); + return viewStatsList; + } catch (EmptyResultDataAccessException ignored) { + return List.of(); + } + } +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatStorage.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatStorage.java new file mode 100644 index 0000000..573ee32 --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatStorage.java @@ -0,0 +1,13 @@ +package ru.practicum.statsvc.repository; + +import ru.practicum.statsvc.model.EndpointHit; +import ru.practicum.statsvc.model.ViewStats; + +import java.time.LocalDateTime; +import java.util.List; + +public interface StatStorage { + EndpointHit addHit(EndpointHit hit); + + List getViewStats(LocalDateTime start, LocalDateTime end, List uris, Boolean unique, Integer size); +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatService.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatService.java new file mode 100644 index 0000000..cbc6fa8 --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatService.java @@ -0,0 +1,13 @@ +package ru.practicum.statsvc.service; + +import ru.practicum.statdto.HitDto; +import ru.practicum.statdto.StatsDto; + +import java.util.List; + +public interface StatService { + + public void addHit(HitDto hitDto); + + public List getStats(String startTxt, String endTxt, List uris, Boolean unique, Integer size); +} diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatServiceImpl.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatServiceImpl.java new file mode 100644 index 0000000..d0d6181 --- /dev/null +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatServiceImpl.java @@ -0,0 +1,51 @@ +package ru.practicum.statsvc.service; + +import org.springframework.stereotype.Service; +import ru.practicum.statdto.HitDto; +import ru.practicum.statdto.StatsDto; +import ru.practicum.statsvc.exception.ValidationException; +import ru.practicum.statsvc.mapper.EndpointMapper; +import ru.practicum.statsvc.mapper.ViewStatsMapper; +import ru.practicum.statsvc.repository.StatDbStorage; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.List; + +@Service +public class StatServiceImpl implements StatService { + private final StatDbStorage storage; + + public StatServiceImpl(StatDbStorage storage) { + this.storage = storage; + } + + @Override + public void addHit(HitDto hitDto) { + storage.addHit(EndpointMapper.toEndpointHit(hitDto)); + } + + @Override + public List getStats(String startTxt, + String endTxt, + List uris, + Boolean unique, + Integer size) { + LocalDateTime start = null; + LocalDateTime end = null; + try { + if (startTxt != null && !startTxt.isEmpty()) { + start = LocalDateTime.parse(startTxt, storage.DATA_TIME_FORMATTER); + } + if (endTxt != null && !endTxt.isEmpty()) { + end = LocalDateTime.parse(endTxt, storage.DATA_TIME_FORMATTER); + } + } catch (DateTimeParseException e) { + throw new ValidationException("Некорректный формат времени. " + e.getMessage()); + } + return storage.getViewStats(start, end, uris, unique, size) + .stream() + .map(ViewStatsMapper::toDto) + .toList(); + } +} diff --git a/stats-server/stat-svc/src/main/resources/application.properties b/stats-server/stat-svc/src/main/resources/application.properties new file mode 100644 index 0000000..ae385fc --- /dev/null +++ b/stats-server/stat-svc/src/main/resources/application.properties @@ -0,0 +1,14 @@ +server.port=9090 +spring.sql.init.mode=always +spring.jackson.date-format=yyyy-MM-dd HH:mm:ss + +#spring.datasource.driverClassName=org.postgresql.Driver +#spring.datasource.url=jdbc:postgresql://192.168.0.102:5432/statdb +#spring.datasource.username=statdb +#spring.datasource.password=statdb + +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:statdb +spring.datasource.username=statdb +spring.datasource.password=statdb + diff --git a/stats-server/stat-svc/src/main/resources/schema.sql b/stats-server/stat-svc/src/main/resources/schema.sql new file mode 100644 index 0000000..431f17f --- /dev/null +++ b/stats-server/stat-svc/src/main/resources/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS endpointhits +( + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + app VARCHAR(128), + uri VARCHAR(128), + ip VARCHAR(128), + timestamp TIMESTAMP WITHOUT TIME ZONE +); From 76e9312f369a0527bc55c954ca85303c79c02451 Mon Sep 17 00:00:00 2001 From: Andrej Stelmaschuk Date: Tue, 20 May 2025 21:12:23 +0700 Subject: [PATCH 3/8] =?UTF-8?q?fix:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ewm-service/pom.xml | 11 ++- .../evmsevice/client/StatsClient.java | 38 +++++++++ .../controller/TestClientController.java | 12 +-- stats-server/pom.xml | 4 +- stats-server/stat-client/pom.xml | 4 +- .../ru/practicum/statclient/BaseClient.java | 59 ++++++++++++++ .../ru/practicum/statclient/StatClient.java | 15 ---- .../practicum/statclient/StatClientImpl.java | 78 ------------------- stats-server/stat-dto/pom.xml | 4 +- stats-server/stat-svc/pom.xml | 4 +- .../statsvc/repository/StatDbStorage.java | 2 +- .../statsvc/service/StatServiceImpl.java | 12 ++- .../src/main/resources/application.properties | 14 ++-- 13 files changed, 139 insertions(+), 118 deletions(-) create mode 100644 ewm-service/src/main/java/ru/practicum/evmsevice/client/StatsClient.java create mode 100644 stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java delete mode 100644 stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClient.java delete mode 100644 stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClientImpl.java diff --git a/ewm-service/pom.xml b/ewm-service/pom.xml index e643bbc..9c664f1 100644 --- a/ewm-service/pom.xml +++ b/ewm-service/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 @@ -57,6 +57,13 @@ compile + + ru.practicum + stat-client + 0.0.1-SNAPSHOT + compile + + diff --git a/ewm-service/src/main/java/ru/practicum/evmsevice/client/StatsClient.java b/ewm-service/src/main/java/ru/practicum/evmsevice/client/StatsClient.java new file mode 100644 index 0000000..38a7b39 --- /dev/null +++ b/ewm-service/src/main/java/ru/practicum/evmsevice/client/StatsClient.java @@ -0,0 +1,38 @@ +package ru.practicum.evmsevice.client; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.statclient.BaseClient; +import ru.practicum.statdto.HitDto; + +import java.util.Map; + +@Component +public class StatsClient extends BaseClient { + private static final String PREFIX_HIT = "/hit"; + private static final String PREFIX_STATS = "/stats"; + + @Autowired + public StatsClient(@Value("${stat-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public void post(HitDto dto) { + makeAndSendRequest(HttpMethod.POST, PREFIX_HIT, null, dto); + } + + public ResponseEntity get(Map parameters) { + return makeAndSendRequest(HttpMethod.GET, PREFIX_STATS, parameters, null); + } +} diff --git a/ewm-service/src/main/java/ru/practicum/evmsevice/controller/TestClientController.java b/ewm-service/src/main/java/ru/practicum/evmsevice/controller/TestClientController.java index 5c1a56f..c817743 100644 --- a/ewm-service/src/main/java/ru/practicum/evmsevice/controller/TestClientController.java +++ b/ewm-service/src/main/java/ru/practicum/evmsevice/controller/TestClientController.java @@ -5,25 +5,27 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.practicum.statclient.StatClient; +import ru.practicum.evmsevice.client.StatsClient; import ru.practicum.statdto.HitDto; import java.util.HashMap; import java.util.Map; +/** + * Класс для проверки работы клиента сервера посещений + */ @Slf4j @RequiredArgsConstructor @RestController @RequestMapping public class TestClientController { - - private StatClient statClient; + private final StatsClient statsClient; @PostMapping("/hit") @ResponseStatus(HttpStatus.CREATED) public void hit(@RequestBody HitDto dto) { log.info("Поступила информация о посещении : " + dto.toString()); - statClient.post(dto); + statsClient.post(dto); } @GetMapping("/stats") @@ -42,7 +44,7 @@ public ResponseEntity getStats( if (uris != null) parameters.put("uris", uris); if (unique != null) parameters.put("unique", unique); if (size != null) parameters.put("size", size); - ResponseEntity response = statClient.get(parameters); + ResponseEntity response = statsClient.get(parameters); return response; } } diff --git a/stats-server/pom.xml b/stats-server/pom.xml index ae9460d..978cf94 100644 --- a/stats-server/pom.xml +++ b/stats-server/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 diff --git a/stats-server/stat-client/pom.xml b/stats-server/stat-client/pom.xml index 4e3e96d..bf15958 100644 --- a/stats-server/stat-client/pom.xml +++ b/stats-server/stat-client/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 diff --git a/stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java b/stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java new file mode 100644 index 0000000..1125f74 --- /dev/null +++ b/stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java @@ -0,0 +1,59 @@ +package ru.practicum.statclient; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.Nullable; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; + +import java.util.Map; + +public class BaseClient { + protected final RestTemplate rest; + + public BaseClient(RestTemplate rest) { + this.rest = rest; + } + + private static ResponseEntity prepareClientResponse(ResponseEntity response) { + if (response.getStatusCode().is2xxSuccessful()) { + return response; + } + ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); + if (response.hasBody()) { + return responseBuilder.body(response.getBody()); + } + return responseBuilder.build(); + } + + /** + * Формируем и отправляем http запрос на сервер + * + * @param method - метод запроса + * @param path - эндпоинт + * @param parameters - карта параметров + * @param body - тело запроса + * @param - тип объекта тела запроса + * @return - ResponseEntity + */ + protected ResponseEntity makeAndSendRequest(HttpMethod method, String path, @Nullable Map parameters, @Nullable T body) { + HttpEntity requestEntity = new HttpEntity<>(body); + ResponseEntity serverResponse; + try { + if (parameters != null) { + StringBuilder stringParametrs = new StringBuilder(path); + stringParametrs.append("?"); + for (String key : parameters.keySet()) { + stringParametrs.append(key + "={" + key + "}&"); + } + serverResponse = rest.exchange(stringParametrs.toString(), method, requestEntity, Object.class, parameters); + } else { + serverResponse = rest.exchange(path, method, requestEntity, Object.class); + } + } catch (HttpStatusCodeException e) { + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); + } + return prepareClientResponse(serverResponse); + } +} diff --git a/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClient.java b/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClient.java deleted file mode 100644 index 8c59915..0000000 --- a/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClient.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.practicum.statclient; - - -import org.springframework.http.ResponseEntity; -import org.springframework.lang.Nullable; - -import java.util.Map; - -public interface StatClient { - ResponseEntity get(); - - ResponseEntity get(@Nullable Map parameters); - - ResponseEntity post(T body); -} diff --git a/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClientImpl.java b/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClientImpl.java deleted file mode 100644 index 9d4fea6..0000000 --- a/stats-server/stat-client/src/main/java/ru/practicum/statclient/StatClientImpl.java +++ /dev/null @@ -1,78 +0,0 @@ -package ru.practicum.statclient; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.lang.Nullable; -import org.springframework.stereotype.Component; -import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.DefaultUriBuilderFactory; - -import java.util.Map; - -@Component -public class StatClientImpl implements StatClient { - private static final String PREFIX_HIT = "/hit"; - private static final String PREFIX_STATS = "/stats"; - - protected final RestTemplate rest; - - @Autowired - public StatClientImpl(@Value("${stat-server.url}") String serverUrl, RestTemplateBuilder builder) { - this.rest = builder - .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl)) - .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) - .build(); - } - - private static ResponseEntity prepareClientResponse(ResponseEntity response) { - if (response.getStatusCode().is2xxSuccessful()) { - return response; - } - ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); - if (response.hasBody()) { - return responseBuilder.body(response.getBody()); - } - return responseBuilder.build(); - } - - @Override - public ResponseEntity get() { - return get(null); - } - - @Override - public ResponseEntity get(@Nullable Map parameters) { - return makeAndSendRequest(HttpMethod.GET, PREFIX_STATS, parameters, null); - } - - @Override - public ResponseEntity post(T body) { - return makeAndSendRequest(HttpMethod.POST, PREFIX_HIT, null, body); - } - - private ResponseEntity makeAndSendRequest(HttpMethod method, String path, @Nullable Map parameters, @Nullable T body) { - HttpEntity requestEntity = new HttpEntity<>(body); - ResponseEntity statServerResponse; - try { - if (parameters != null) { - StringBuilder stringParametrs = new StringBuilder(path); - stringParametrs.append("?"); - for (String key : parameters.keySet()) { - stringParametrs.append(key + "={" + key + "}&"); - } - statServerResponse = rest.exchange(stringParametrs.toString(), method, requestEntity, Object.class, parameters); - } else { - statServerResponse = rest.exchange(path, method, requestEntity, Object.class); - } - } catch (HttpStatusCodeException e) { - return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); - } - return prepareClientResponse(statServerResponse); - } -} diff --git a/stats-server/stat-dto/pom.xml b/stats-server/stat-dto/pom.xml index 4189fa1..a5963ff 100644 --- a/stats-server/stat-dto/pom.xml +++ b/stats-server/stat-dto/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 diff --git a/stats-server/stat-svc/pom.xml b/stats-server/stat-svc/pom.xml index 319e64e..580526a 100644 --- a/stats-server/stat-svc/pom.xml +++ b/stats-server/stat-svc/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatDbStorage.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatDbStorage.java index 80d5c95..e6ff0be 100644 --- a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatDbStorage.java +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/repository/StatDbStorage.java @@ -18,7 +18,7 @@ @Repository public class StatDbStorage implements StatStorage { - public static final DateTimeFormatter DATA_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final DateTimeFormatter DATA_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final String SQL_INSERT_HIT = """ INSERT INTO endpointhits (app, uri, ip, timestamp) VALUES ( :app, :uri, :ip, :timestamp) diff --git a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatServiceImpl.java b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatServiceImpl.java index d0d6181..dbb381e 100644 --- a/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatServiceImpl.java +++ b/stats-server/stat-svc/src/main/java/ru/practicum/statsvc/service/StatServiceImpl.java @@ -9,11 +9,13 @@ import ru.practicum.statsvc.repository.StatDbStorage; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.List; @Service public class StatServiceImpl implements StatService { + private static final DateTimeFormatter DATA_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final StatDbStorage storage; public StatServiceImpl(StatDbStorage storage) { @@ -35,14 +37,20 @@ public List getStats(String startTxt, LocalDateTime end = null; try { if (startTxt != null && !startTxt.isEmpty()) { - start = LocalDateTime.parse(startTxt, storage.DATA_TIME_FORMATTER); + start = LocalDateTime.parse(startTxt, DATA_TIME_FORMATTER); } if (endTxt != null && !endTxt.isEmpty()) { - end = LocalDateTime.parse(endTxt, storage.DATA_TIME_FORMATTER); + end = LocalDateTime.parse(endTxt, DATA_TIME_FORMATTER); } } catch (DateTimeParseException e) { throw new ValidationException("Некорректный формат времени. " + e.getMessage()); } + + if (start != null && end != null) { + if (start.isAfter(end)) { + throw new ValidationException("Указан недопустимый промежуток времени"); + } + } return storage.getViewStats(start, end, uris, unique, size) .stream() .map(ViewStatsMapper::toDto) diff --git a/stats-server/stat-svc/src/main/resources/application.properties b/stats-server/stat-svc/src/main/resources/application.properties index ae385fc..a69735a 100644 --- a/stats-server/stat-svc/src/main/resources/application.properties +++ b/stats-server/stat-svc/src/main/resources/application.properties @@ -2,13 +2,13 @@ server.port=9090 spring.sql.init.mode=always spring.jackson.date-format=yyyy-MM-dd HH:mm:ss -#spring.datasource.driverClassName=org.postgresql.Driver -#spring.datasource.url=jdbc:postgresql://192.168.0.102:5432/statdb -#spring.datasource.username=statdb -#spring.datasource.password=statdb - -spring.datasource.driverClassName=org.h2.Driver -spring.datasource.url=jdbc:h2:mem:statdb +spring.datasource.driverClassName=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://192.168.0.102:5432/statdb spring.datasource.username=statdb spring.datasource.password=statdb +#spring.datasource.driverClassName=org.h2.Driver +#spring.datasource.url=jdbc:h2:mem:statdb +#spring.datasource.username=statdb +#spring.datasource.password=statdb + From 59df618c198a00e1144e98e5f1506c016e428036 Mon Sep 17 00:00:00 2001 From: Andrej Stelmaschuk Date: Tue, 20 May 2025 21:59:02 +0700 Subject: [PATCH 4/8] =?UTF-8?q?fix:=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=B8=D0=B2=D0=B0=D0=B5=D0=BC=20docker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 32 ++++++++++++++++++++++++++++++-- ewm-service/Dockerfile | 5 +++++ stats-server/Dockerfile | 5 +++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 ewm-service/Dockerfile create mode 100644 stats-server/Dockerfile diff --git a/docker-compose.yml b/docker-compose.yml index be96142..191cd6a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,42 @@ services: stats-server: + build: stats-server + image: stats-server + container_name: stats-server ports: - "9090:9090" + - depends_on: + - stats-db + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://stats-db:5432/statdb + - SPRING_DATASOURCE_USERNAME=statdb + - SPRING_DATASOURCE_PASSWORD=statdb stats-db: image: postgres:16.1 + container_name: postgres-stat + ports: + - "6541:5432" + environment: + - POSTGRES_PASSWORD=statdb + - POSTGRES_USER=statdb + - POSTGRES_DB=statdb + healthcheck: + test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER + timeout: 5s + interval: 5s + retries: 10 ewm-service: + build: ewm-service + image: ewm-service + container_name: ewm-service ports: - "8080:8080" + depends_on: + - stats-server + environment: + - STAT_SERVER_URL=http://stats-server:9090 - ewm-db: - image: postgres:16.1 +# ewm-db: +# image: postgres:16.1 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/stats-server/Dockerfile b/stats-server/Dockerfile new file mode 100644 index 0000000..b3fe293 --- /dev/null +++ b/stats-server/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=stat-svc/target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file From 153c89fda84d5177ab86fab50f6aca705f6adf70 Mon Sep 17 00:00:00 2001 From: Andrej Stelmaschuk Date: Wed, 21 May 2025 00:50:40 +0700 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=B8=D0=B2=D0=B0=D0=B5=D0=BC=20docker=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 191cd6a..fc5c894 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,22 +1,10 @@ +version: '3.1' services: - stats-server: - build: stats-server - image: stats-server - container_name: stats-server - ports: - - "9090:9090" - - depends_on: - - stats-db - environment: - - SPRING_DATASOURCE_URL=jdbc:postgresql://stats-db:5432/statdb - - SPRING_DATASOURCE_USERNAME=statdb - - SPRING_DATASOURCE_PASSWORD=statdb - stats-db: image: postgres:16.1 container_name: postgres-stat ports: - - "6541:5432" + - "5432:5432" environment: - POSTGRES_PASSWORD=statdb - POSTGRES_USER=statdb @@ -27,6 +15,19 @@ services: interval: 5s retries: 10 + stats-server: + build: stats-server + image: stats-server + container_name: stats-server + ports: + - "9090:9090" + depends_on: + - stats-db + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://stats-db:5432/statdb + - SPRING_DATASOURCE_USERNAME=statdb + - SPRING_DATASOURCE_PASSWORD=statdb + ewm-service: build: ewm-service image: ewm-service @@ -36,7 +37,7 @@ services: depends_on: - stats-server environment: - - STAT_SERVER_URL=http://stats-server:9090 + - STATSERVER_URL=http://stat-server:9090 # ewm-db: # image: postgres:16.1 From e572a5bcd320e613114c55c6f7489d366963f636 Mon Sep 17 00:00:00 2001 From: Andrej Stelmaschuk Date: Wed, 21 May 2025 21:49:14 +0700 Subject: [PATCH 6/8] =?UTF-8?q?fix:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=B4=D1=83=D0=B1=D0=BB=D0=B8=D0=BA=D0=B0=D1=82=D1=8B?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 17 +++++++++++++++-- ewm-service/pom.xml | 10 ++-------- .../practicum/evmsevice/client/StatsClient.java | 2 +- .../src/main/resources/application.properties | 2 +- .../ru/practicum/statclient/BaseClient.java | 8 ++++++-- stats-server/stat-svc/pom.xml | 6 ------ 6 files changed, 25 insertions(+), 20 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index fc5c894..4e0e1eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,5 +39,18 @@ services: environment: - STATSERVER_URL=http://stat-server:9090 -# ewm-db: -# image: postgres:16.1 + ewm-db: + image: postgres:16.1 + container_name: postgres-ewm + ports: + - "5434:5434" + environment: + - POSTGRES_PASSWORD=statewm + - POSTGRES_USER=statewm + - POSTGRES_DB=statewm + healthcheck: + test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER + timeout: 5s + interval: 5s + retries: 10 + diff --git a/ewm-service/pom.xml b/ewm-service/pom.xml index 9c664f1..9eb9b4f 100644 --- a/ewm-service/pom.xml +++ b/ewm-service/pom.xml @@ -38,21 +38,15 @@ httpclient5 - - ru.practicum - stat-dto - 0.0.1-SNAPSHOT - compile - - org.projectlombok lombok true + ru.practicum - stat-client + stat-dto 0.0.1-SNAPSHOT compile diff --git a/ewm-service/src/main/java/ru/practicum/evmsevice/client/StatsClient.java b/ewm-service/src/main/java/ru/practicum/evmsevice/client/StatsClient.java index 38a7b39..811b2a1 100644 --- a/ewm-service/src/main/java/ru/practicum/evmsevice/client/StatsClient.java +++ b/ewm-service/src/main/java/ru/practicum/evmsevice/client/StatsClient.java @@ -19,7 +19,7 @@ public class StatsClient extends BaseClient { private static final String PREFIX_STATS = "/stats"; @Autowired - public StatsClient(@Value("${stat-server.url}") String serverUrl, RestTemplateBuilder builder) { + public StatsClient(@Value("${statserver.url}") String serverUrl, RestTemplateBuilder builder) { super( builder .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl)) diff --git a/ewm-service/src/main/resources/application.properties b/ewm-service/src/main/resources/application.properties index 3794df8..39a76d7 100644 --- a/ewm-service/src/main/resources/application.properties +++ b/ewm-service/src/main/resources/application.properties @@ -1,2 +1,2 @@ server.port=8080 -stat-server.url=http://localhost:9090 \ No newline at end of file +statserver.url=http://localhost:9090 \ No newline at end of file diff --git a/stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java b/stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java index 1125f74..a92cee5 100644 --- a/stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java +++ b/stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java @@ -37,7 +37,8 @@ private static ResponseEntity prepareClientResponse(ResponseEntity - тип объекта тела запроса * @return - ResponseEntity */ - protected ResponseEntity makeAndSendRequest(HttpMethod method, String path, @Nullable Map parameters, @Nullable T body) { + protected ResponseEntity makeAndSendRequest(HttpMethod method, String path, + @Nullable Map parameters, @Nullable T body) { HttpEntity requestEntity = new HttpEntity<>(body); ResponseEntity serverResponse; try { @@ -45,7 +46,10 @@ protected ResponseEntity makeAndSendRequest(HttpMethod method, Strin StringBuilder stringParametrs = new StringBuilder(path); stringParametrs.append("?"); for (String key : parameters.keySet()) { - stringParametrs.append(key + "={" + key + "}&"); + stringParametrs.append(key); + stringParametrs.append("={"); + stringParametrs.append(key); + stringParametrs.append("}&"); } serverResponse = rest.exchange(stringParametrs.toString(), method, requestEntity, Object.class, parameters); } else { diff --git a/stats-server/stat-svc/pom.xml b/stats-server/stat-svc/pom.xml index 580526a..3d2b1cd 100644 --- a/stats-server/stat-svc/pom.xml +++ b/stats-server/stat-svc/pom.xml @@ -23,11 +23,6 @@ spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-validation - - org.springframework.boot spring-boot-configuration-processor @@ -51,7 +46,6 @@ 3.4.1 - org.postgresql postgresql From ae9cdb71c917ee51953403a261509a799276ea9a Mon Sep 17 00:00:00 2001 From: Andrej Stelmaschuk Date: Wed, 21 May 2025 21:54:57 +0700 Subject: [PATCH 7/8] fix: BaseClient.java --- .../src/main/java/ru/practicum/statclient/BaseClient.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java b/stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java index a92cee5..28ce7fc 100644 --- a/stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java +++ b/stats-server/stat-client/src/main/java/ru/practicum/statclient/BaseClient.java @@ -3,7 +3,6 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; -import org.springframework.lang.Nullable; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; @@ -37,8 +36,10 @@ private static ResponseEntity prepareClientResponse(ResponseEntity - тип объекта тела запроса * @return - ResponseEntity */ - protected ResponseEntity makeAndSendRequest(HttpMethod method, String path, - @Nullable Map parameters, @Nullable T body) { + protected ResponseEntity makeAndSendRequest(HttpMethod method, + String path, + Map parameters, + T body) { HttpEntity requestEntity = new HttpEntity<>(body); ResponseEntity serverResponse; try { From 87ec4d9e3ee46c99f9fd87d9b3b0263931701352 Mon Sep 17 00:00:00 2001 From: Andrej Stelmaschuk Date: Wed, 21 May 2025 23:22:46 +0700 Subject: [PATCH 8/8] fix: docker-compose.yml --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4e0e1eb..a02f49e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,7 +37,7 @@ services: depends_on: - stats-server environment: - - STATSERVER_URL=http://stat-server:9090 + - STATSERVER_URL=http://stats-server:9090 ewm-db: image: postgres:16.1