From e3390d53ddf275c8a42ebf0b08c5cf69f834ae9f Mon Sep 17 00:00:00 2001 From: Zyzf Date: Wed, 30 Oct 2024 13:57:42 +0400 Subject: [PATCH 1/2] init --- kalyshev_yan_lab_3/README.md | 58 ++++++++ .../computer-service/.gitignore | 38 ++++++ .../computer-service/Dockerfile | 21 +++ kalyshev_yan_lab_3/computer-service/pom.xml | 128 ++++++++++++++++++ .../java/ru/computer/ComputerApplication.java | 14 ++ .../controller/ComputerController.java | 64 +++++++++ .../controller/ComputerControllerImpl.java | 45 ++++++ .../ru/computer/domain/ComputerEntity.java | 35 +++++ .../ru/computer/domain/ComputerResponse.java | 21 +++ .../domain/CreateComputerRequest.java | 17 +++ .../java/ru/computer/domain/RoomResponse.java | 17 +++ .../AbstractWebExceptionHandler.java | 41 ++++++ .../ru/computer/exception/BasicError.java | 5 + .../java/ru/computer/exception/ErrorCode.java | 9 ++ .../exception/ResourceNotFoundException.java | 28 ++++ .../ru/computer/exception/ResponseError.java | 24 ++++ .../exception/RoomAppRuntimeException.java | 27 ++++ .../exception/WebExceptionHandler.java | 22 +++ .../java/ru/computer/external/RoomClient.java | 15 ++ .../ru/computer/mapper/ComputerMapper.java | 24 ++++ .../repository/ComputerRepository.java | 11 ++ .../ru/computer/service/ComputerService.java | 21 +++ .../computer/service/ComputerServiceImpl.java | 68 ++++++++++ .../computer/service/adapter/RoomAdapter.java | 10 ++ .../service/adapter/RoomAdapterImpl.java | 20 +++ .../src/main/resources/application.yml | 32 +++++ .../resources/db/migration/V000001__init.sql | 7 + kalyshev_yan_lab_3/docker-compose.yml | 43 ++++++ kalyshev_yan_lab_3/init-database.sh | 8 ++ kalyshev_yan_lab_3/nginx/nginx.conf | 21 +++ kalyshev_yan_lab_3/room-service/.gitignore | 38 ++++++ kalyshev_yan_lab_3/room-service/Dockerfile | 21 +++ kalyshev_yan_lab_3/room-service/pom.xml | 66 +++++++++ .../main/java/ru/room/RoomApplication.java | 12 ++ .../ru/room/controller/RoomController.java | 43 ++++++ .../room/controller/RoomControllerImpl.java | 42 ++++++ .../ru/room/domain/CreateRoomRequest.java | 13 ++ .../main/java/ru/room/domain/RoomEntity.java | 32 +++++ .../AbstractWebExceptionHandler.java | 41 ++++++ .../java/ru/room/exception/BasicError.java | 5 + .../java/ru/room/exception/ErrorCode.java | 9 ++ .../exception/ResourceNotFoundException.java | 28 ++++ .../java/ru/room/exception/ResponseError.java | 24 ++++ .../exception/RoomAppRuntimeException.java | 27 ++++ .../room/exception/WebExceptionHandler.java | 22 +++ .../ru/room/repository/RoomRepository.java | 9 ++ .../java/ru/room/service/RoomService.java | 20 +++ .../java/ru/room/service/RoomServiceImpl.java | 57 ++++++++ .../src/main/resources/application.yml | 28 ++++ .../resources/db/migration/V000001__init.sql | 6 + 50 files changed, 1437 insertions(+) create mode 100644 kalyshev_yan_lab_3/README.md create mode 100644 kalyshev_yan_lab_3/computer-service/.gitignore create mode 100644 kalyshev_yan_lab_3/computer-service/Dockerfile create mode 100644 kalyshev_yan_lab_3/computer-service/pom.xml create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/ComputerApplication.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerController.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerControllerImpl.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerEntity.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerResponse.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/CreateComputerRequest.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/RoomResponse.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/AbstractWebExceptionHandler.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/BasicError.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ErrorCode.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResourceNotFoundException.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResponseError.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/RoomAppRuntimeException.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/WebExceptionHandler.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/external/RoomClient.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/mapper/ComputerMapper.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/repository/ComputerRepository.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerService.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerServiceImpl.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapter.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapterImpl.java create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/resources/application.yml create mode 100644 kalyshev_yan_lab_3/computer-service/src/main/resources/db/migration/V000001__init.sql create mode 100644 kalyshev_yan_lab_3/docker-compose.yml create mode 100644 kalyshev_yan_lab_3/init-database.sh create mode 100644 kalyshev_yan_lab_3/nginx/nginx.conf create mode 100644 kalyshev_yan_lab_3/room-service/.gitignore create mode 100644 kalyshev_yan_lab_3/room-service/Dockerfile create mode 100644 kalyshev_yan_lab_3/room-service/pom.xml create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/RoomApplication.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomController.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomControllerImpl.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/CreateRoomRequest.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/RoomEntity.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/AbstractWebExceptionHandler.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/BasicError.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ErrorCode.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResourceNotFoundException.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResponseError.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/RoomAppRuntimeException.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/WebExceptionHandler.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/repository/RoomRepository.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomService.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomServiceImpl.java create mode 100644 kalyshev_yan_lab_3/room-service/src/main/resources/application.yml create mode 100644 kalyshev_yan_lab_3/room-service/src/main/resources/db/migration/V000001__init.sql diff --git a/kalyshev_yan_lab_3/README.md b/kalyshev_yan_lab_3/README.md new file mode 100644 index 0000000..0aabe86 --- /dev/null +++ b/kalyshev_yan_lab_3/README.md @@ -0,0 +1,58 @@ +# Отчет. Лабораторная работа 3 + +## Описание +В рамках лабораторной работы № 3 были реализованы два сервиса (Java + Spring), осуществляющие CRUD-операции над сущностями. + +Модель данных следующая: + +Сущность "Компания" (сервис company) +- идентификатор компании +- название +- адрес + +Сущность "Вакансия" (сервис vacancy) +- идентификатор вакансии +- название +- описание +- нижняя граница зарплаты +- верхняя граница зарплаты +- идентификатор компании + +Компания с вакансией связана как "один ко многим". + +Каждый из сервисов имеет API с пятью эндпоинтами. При этом в сервисе vacancy при запросе вакансии по id происходит +дополнительный запрос в сервис company для получения информации по компании, связанной с вакансией. +Происходит это взаимодействие с помощью библиотеки OpenFeign, которая "под капотом" использует HttpClient. + +В качестве хранилища данных использовалась СУБД Postgres. У каждого сервиса своя база данных в поднятой СУБД. +Для создания схемы БД была использована библиотека Flyway, которая применила написанные миграции при старте приложения. + +Запросы к сервисам проксирует шлюз на основе Nginx. Для этого перед запуском nginx был описан конфигурационный файл nginx.conf, +в котором описан прослушиваемый порт и название сервера (в блоке server), маршруты до микросервисов и параметры проксирования (в блоке location). + +Таким образом, с помощью Docker Compose были подняты сервисы: +- company +- vacancy +- postgres +- nginx +## Как запустить +Для того, чтобы запустить сервисы, необходимо выполнить следующие действия: +1. Установить и запустить Docker Engine или Docker Desktop +2. Через консоль перейти в папку, в которой расположен файл docker-compose.yml +3. Выполнить команду: +``` +docker compose up --build +``` +В случае успешного запуска всех контейнеров в консоли будет выведено следующее сообщение: +``` +[+] Running 5/5 + ✔ Network borschevskaya_anna_lab_3_default Created 0.0s + ✔ Container postgres Started 0.6s + ✔ Container vacancy Started 1.1s + ✔ Container company Started 0.9s + ✔ Container borschevskaya_anna_lab_3-nginx-1 Started +``` +Далее можно осуществлять запросы к сервисам по адресу http://localhost/{location}, где часть пути location меняется в зависимости от сервиса и запроса. +## Видео-отчет +Работоспособность лабораторной работы можно оценить в следующем [видео](https://disk.yandex.ru/i/KPNBfnlcgl1auw). +Демонстрация взаимодействия с системой производится с применением сервиса Postman. \ No newline at end of file diff --git a/kalyshev_yan_lab_3/computer-service/.gitignore b/kalyshev_yan_lab_3/computer-service/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/kalyshev_yan_lab_3/computer-service/Dockerfile b/kalyshev_yan_lab_3/computer-service/Dockerfile new file mode 100644 index 0000000..cc673eb --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/Dockerfile @@ -0,0 +1,21 @@ +# Используем образ Maven для сборки +FROM maven:3.8-eclipse-temurin-21-alpine AS build + +# Устанавливаем рабочую директорию +WORKDIR /app + +# Копируем остальные исходные файлы +COPY pom.xml . +COPY src src + +# Собираем весь проект +RUN mvn clean package -DskipTests + +# Используем официальный образ JDK для запуска собранного jar-файла +FROM eclipse-temurin:21-jdk-alpine + +# Копируем jar-файл из предыдущего этапа +COPY --from=build /app/target/*.jar /app.jar + +# Указываем команду для запуска приложения +CMD ["java", "-jar", "app.jar"] diff --git a/kalyshev_yan_lab_3/computer-service/pom.xml b/kalyshev_yan_lab_3/computer-service/pom.xml new file mode 100644 index 0000000..cf1ae83 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/pom.xml @@ -0,0 +1,128 @@ + + + 4.0.0 + + ru.computer + computer-service + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + + + 21 + 21 + UTF-8 + 1.5.5.Final + 0.2.0 + 2023.0.3 + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.flywaydb + flyway-core + + + org.postgresql + postgresql + + + org.projectlombok + lombok + 1.18.30 + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + io.swagger.core.v3 + swagger-annotations + 2.2.1 + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok + 1.18.30 + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + + -Amapstruct.defaultComponentModel=spring + + + + + + + + + diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/ComputerApplication.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/ComputerApplication.java new file mode 100644 index 0000000..670a9e0 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/ComputerApplication.java @@ -0,0 +1,14 @@ +package ru.computer; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@SpringBootApplication +@EnableFeignClients +public class ComputerApplication { + + public static void main(String[] args) { + SpringApplication.run(ComputerApplication.class, args); + } +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerController.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerController.java new file mode 100644 index 0000000..ed2d971 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerController.java @@ -0,0 +1,64 @@ +package ru.computer.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import ru.computer.domain.CreateComputerRequest; +import ru.computer.domain.ComputerEntity; +import ru.computer.domain.ComputerResponse; + +import java.util.List; +import java.util.UUID; + +@Validated +@Tag(name = "computer", description = "API для управления компьютерами") +public interface ComputerController { + + @Operation(summary = "Получение всех компьютеров") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Данные успешно переданы"), + }) + @GetMapping(value = "/api/v1/computer") + List getComputers(); + + @Operation(summary = "Создание компьютера") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Компьютер успешно создана"), + }) + @PostMapping(value = "/api/v1/computer") + ComputerEntity createComputer(@RequestBody @NotNull CreateComputerRequest request); + + @Operation(summary = "Получение информации о компьютере по id") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Данные успешно переданы"), + @ApiResponse(responseCode = "404", description = "Компьютер не найден") + }) + @GetMapping(value = "/api/v1/computer/{computerId}") + ComputerResponse getComputer(@PathVariable UUID computerId); + + @Operation(summary = "Редактирование компьютера") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Данные компьютер успешно изменен"), + @ApiResponse(responseCode = "404", description = "Компьютер не найден") + }) + @PutMapping(value = "/api/v1/computer/{computerId}") + ComputerEntity updateComputer(@PathVariable UUID computerId, + @RequestBody @NotNull CreateComputerRequest request); + + @Operation(summary = "Удалении компьютера по id") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Компьютер успешно удален"), + @ApiResponse(responseCode = "404", description = "Компьютер не найден") + }) + @DeleteMapping(value = "/api/v1/computer/{computerId}") + void deleteComputer(@PathVariable UUID computerId); +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerControllerImpl.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerControllerImpl.java new file mode 100644 index 0000000..09eff04 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/controller/ComputerControllerImpl.java @@ -0,0 +1,45 @@ +package ru.computer.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RestController; +import ru.computer.domain.CreateComputerRequest; +import ru.computer.domain.ComputerEntity; +import ru.computer.domain.ComputerResponse; +import ru.computer.service.ComputerService; + +import java.util.List; +import java.util.UUID; + +@Slf4j +@RestController +@RequiredArgsConstructor +public class ComputerControllerImpl implements ComputerController { + + private final ComputerService computerService; + + @Override + public List getComputers() { + return computerService.getComputers(); + } + + @Override + public ComputerEntity createComputer(CreateComputerRequest request) { + return computerService.createComputer(request); + } + + @Override + public ComputerResponse getComputer(UUID computerId) { + return computerService.getComputer(computerId); + } + + @Override + public ComputerEntity updateComputer(UUID computerId, CreateComputerRequest request) { + return computerService.updateComputer(computerId, request); + } + + @Override + public void deleteComputer(UUID computerId) { + computerService.deleteComputer(computerId); + } +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerEntity.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerEntity.java new file mode 100644 index 0000000..4d11df1 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerEntity.java @@ -0,0 +1,35 @@ +package ru.computer.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@Entity +@Builder +@Table(name = "computer") +@AllArgsConstructor +@NoArgsConstructor +public class ComputerEntity { + + @Id + @GeneratedValue + private UUID id; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "description", nullable = true) + private String description; + + @Column(name = "room_id", nullable = false) + private UUID roomId; +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerResponse.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerResponse.java new file mode 100644 index 0000000..c7565d8 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/ComputerResponse.java @@ -0,0 +1,21 @@ +package ru.computer.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.util.UUID; + +@Data +@Builder +@Schema(description = "Данные по вакансии") +public class ComputerResponse { + + private UUID id; + + private String name; + + private String description; + + private RoomResponse room; +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/CreateComputerRequest.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/CreateComputerRequest.java new file mode 100644 index 0000000..deeed0c --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/CreateComputerRequest.java @@ -0,0 +1,17 @@ +package ru.computer.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.UUID; + +@Data +@AllArgsConstructor +public class CreateComputerRequest { + + private String name; + + private String description; + + private UUID roomId; +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/RoomResponse.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/RoomResponse.java new file mode 100644 index 0000000..8c9fefe --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/domain/RoomResponse.java @@ -0,0 +1,17 @@ +package ru.computer.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.UUID; + +@Data +@AllArgsConstructor +public class RoomResponse { + + private UUID id; + + private String name; + + private String address; +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/AbstractWebExceptionHandler.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/AbstractWebExceptionHandler.java new file mode 100644 index 0000000..ca8ce56 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/AbstractWebExceptionHandler.java @@ -0,0 +1,41 @@ +package ru.computer.exception; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.time.LocalDateTime; + +import static org.springframework.http.HttpStatus.NOT_FOUND; + +@Slf4j +public abstract class AbstractWebExceptionHandler { + + public static ResponseEntity buildResponse(ErrorCode errorCode, HttpStatus status, String msg, + boolean details, Throwable ex) { + return buildResponse(errorCode.name(), status, msg, details, ex, LocalDateTime.now()); + } + + public static ResponseEntity buildResponse(String errorCode, HttpStatus status, String msg, + boolean details, Throwable ex, LocalDateTime timestamp) { + if (details) { + log.error(msg, ex); // with stack-trace + } else { + var message = StringUtils.equals(msg, ex.getMessage()) ? msg : msg + ": " + ex.getMessage(); + if (status == NOT_FOUND) { + log.warn(message); + } else { + log.error(message); + } + } + + return ResponseEntity.status(status.value()) + .body(ResponseError.builder() + .code(errorCode) + .message(msg) + .timestamp(timestamp) + .status(status.value()) + .build()); + } +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/BasicError.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/BasicError.java new file mode 100644 index 0000000..849bb82 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/BasicError.java @@ -0,0 +1,5 @@ +package ru.computer.exception; + +public enum BasicError implements ErrorCode { + NOT_FOUND +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ErrorCode.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ErrorCode.java new file mode 100644 index 0000000..02629a4 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ErrorCode.java @@ -0,0 +1,9 @@ +package ru.computer.exception; + +import java.io.Serializable; + +public interface ErrorCode extends Serializable { + + String name(); + +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResourceNotFoundException.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResourceNotFoundException.java new file mode 100644 index 0000000..641e86e --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResourceNotFoundException.java @@ -0,0 +1,28 @@ +package ru.computer.exception; + +import org.springframework.http.HttpStatus; + +import static ru.computer.exception.BasicError.NOT_FOUND; + +public class ResourceNotFoundException extends RoomAppRuntimeException { + + public ResourceNotFoundException(ErrorCode errorCode, String message) { + this(errorCode, message, null); + } + + public ResourceNotFoundException(ErrorCode errorCode, String message, Throwable error) { + super(errorCode, HttpStatus.NOT_FOUND, message, error); + } + + public static ResourceNotFoundException notFound(String message, Object... args) { + return new ResourceNotFoundException(NOT_FOUND, String.format(message, args), null); + } + + public static ResourceNotFoundException notFound() { + return new ResourceNotFoundException(NOT_FOUND, "Запрашиваемые данные не найдены", null); + } + + public static ResourceNotFoundException notFound(Throwable error) { + return new ResourceNotFoundException(NOT_FOUND, error.getMessage(), error); + } +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResponseError.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResponseError.java new file mode 100644 index 0000000..eefdf99 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/ResponseError.java @@ -0,0 +1,24 @@ +package ru.computer.exception; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ResponseError { + @JsonProperty(required = true) + private LocalDateTime timestamp; + @JsonProperty(required = true) + private Integer status; + @JsonProperty(required = true) + private String message; + @JsonProperty(required = true) + private String code; +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/RoomAppRuntimeException.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/RoomAppRuntimeException.java new file mode 100644 index 0000000..d05330d --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/RoomAppRuntimeException.java @@ -0,0 +1,27 @@ +package ru.computer.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class RoomAppRuntimeException extends RuntimeException { + private final ErrorCode errorCode; + private final HttpStatus status; + private final boolean details; + + public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message) { + this(errorCode, status, message, null); + } + + public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, Throwable error) { + this(errorCode, status, message, true, error); + } + + public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, boolean details, + Throwable error) { + super(message, error); + this.errorCode = errorCode; + this.status = status; + this.details = details; + } +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/WebExceptionHandler.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/WebExceptionHandler.java new file mode 100644 index 0000000..d188922 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/exception/WebExceptionHandler.java @@ -0,0 +1,22 @@ +package ru.computer.exception; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@Getter +@RestControllerAdvice +@RequiredArgsConstructor +public class WebExceptionHandler extends AbstractWebExceptionHandler { + private final ObjectMapper objectMapper; + + @ExceptionHandler(RoomAppRuntimeException.class) + public ResponseEntity handleException(RoomAppRuntimeException ex) { + return buildResponse(ex.getErrorCode(), ex.getStatus(), ex.getMessage(), ex.isDetails(), ex); + } +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/external/RoomClient.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/external/RoomClient.java new file mode 100644 index 0000000..81e45d1 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/external/RoomClient.java @@ -0,0 +1,15 @@ +package ru.computer.external; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import ru.computer.domain.RoomResponse; + +import java.util.UUID; + +@FeignClient(name = "room", url = "${app.feign.room-url}") +public interface RoomClient { + + @GetMapping(value = "/api/v1/room/{roomId}") + RoomResponse getRoom(@PathVariable UUID roomId); +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/mapper/ComputerMapper.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/mapper/ComputerMapper.java new file mode 100644 index 0000000..439edf6 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/mapper/ComputerMapper.java @@ -0,0 +1,24 @@ +package ru.computer.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import ru.computer.domain.RoomResponse; +import ru.computer.domain.CreateComputerRequest; +import ru.computer.domain.ComputerEntity; +import ru.computer.domain.ComputerResponse; + +import java.util.UUID; + +@Mapper(imports = UUID.class) +public interface ComputerMapper { + + @Mapping(target = "room", source = "room") + @Mapping(target = "name", source = "entity.name") + @Mapping(target = "id", source = "entity.id") + ComputerResponse map(ComputerEntity entity, RoomResponse room); + + ComputerEntity map(CreateComputerRequest request); + + ComputerEntity map(@MappingTarget ComputerEntity entity, CreateComputerRequest request); +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/repository/ComputerRepository.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/repository/ComputerRepository.java new file mode 100644 index 0000000..7f1e3d3 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/repository/ComputerRepository.java @@ -0,0 +1,11 @@ +package ru.computer.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.computer.domain.ComputerEntity; + +import java.util.UUID; + +@Repository +public interface ComputerRepository extends JpaRepository { +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerService.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerService.java new file mode 100644 index 0000000..dbf6136 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerService.java @@ -0,0 +1,21 @@ +package ru.computer.service; + +import ru.computer.domain.CreateComputerRequest; +import ru.computer.domain.ComputerEntity; +import ru.computer.domain.ComputerResponse; + +import java.util.List; +import java.util.UUID; + +public interface ComputerService { + + List getComputers(); + + ComputerEntity createComputer(CreateComputerRequest request); + + ComputerEntity updateComputer(UUID computerId, CreateComputerRequest request); + + void deleteComputer(UUID computerId); + + ComputerResponse getComputer(UUID computerId); +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerServiceImpl.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerServiceImpl.java new file mode 100644 index 0000000..7ae682c --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/ComputerServiceImpl.java @@ -0,0 +1,68 @@ +package ru.computer.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.computer.domain.CreateComputerRequest; +import ru.computer.domain.ComputerEntity; +import ru.computer.domain.ComputerResponse; +import ru.computer.mapper.ComputerMapper; +import ru.computer.repository.ComputerRepository; +import ru.computer.service.adapter.RoomAdapter; + +import java.util.List; +import java.util.UUID; + +import static ru.computer.exception.ResourceNotFoundException.notFound; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ComputerServiceImpl implements ComputerService { + + private final ComputerRepository computerRepository; + private final RoomAdapter roomAdapter; + private final ComputerMapper mapper; + + @Override + public List getComputers() { + return computerRepository.findAll(); + } + + @Override + public ComputerEntity createComputer(CreateComputerRequest request) { + var entity = mapper.map(request); + return computerRepository.save(entity); + } + + @Override + public ComputerEntity updateComputer(UUID computerId, CreateComputerRequest request) { + var entity = getById(computerId); + entity = mapper.map(entity, request); + + return computerRepository.save(entity); + } + + @Override + public void deleteComputer(UUID computerId) { + var entity = getById(computerId); + computerRepository.delete(entity); + } + + @Override + public ComputerResponse getComputer(UUID computerId) { + var entity = getById(computerId); + var room = roomAdapter.getRoomById(entity.getRoomId()); + + return mapper.map(entity, room); + } + + private ComputerEntity getById(UUID computerId) { + var entity = computerRepository.findById(computerId); + if (entity.isEmpty()) { + log.warn("The computer with id '{}' was not found", computerId); + throw notFound("Компьютер не найден"); + } + return entity.get(); + } +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapter.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapter.java new file mode 100644 index 0000000..7c60dd5 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapter.java @@ -0,0 +1,10 @@ +package ru.computer.service.adapter; + +import ru.computer.domain.RoomResponse; + +import java.util.UUID; + +public interface RoomAdapter { + + RoomResponse getRoomById(UUID companyId); +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapterImpl.java b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapterImpl.java new file mode 100644 index 0000000..b38afd9 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/java/ru/computer/service/adapter/RoomAdapterImpl.java @@ -0,0 +1,20 @@ +package ru.computer.service.adapter; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.computer.domain.RoomResponse; +import ru.computer.external.RoomClient; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class RoomAdapterImpl implements RoomAdapter { + + private final RoomClient roomClient; + + @Override + public RoomResponse getRoomById(UUID roomId) { + return roomClient.getRoom(roomId); + } +} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/resources/application.yml b/kalyshev_yan_lab_3/computer-service/src/main/resources/application.yml new file mode 100644 index 0000000..1ed9851 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/resources/application.yml @@ -0,0 +1,32 @@ +server: + port: ${SERVER_PORT:8080} +spring: + application: + name: computer-app + jpa: + database: POSTGRESQL + open-in-view: false + show-sql: false + hibernate: + ddl-auto: none + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQLDialect + datasource: + url: ${DB_URL:jdbc:postgresql://localhost:5433/computer} + username: ${DB_USERNAME:postgres} + password: ${DB_PASSWORD:postgres} + driverClassName: org.postgresql.Driver + type: com.zaxxer.hikari.HikariDataSource + hikari: + maximum-pool-size: 15 + minimum-idle: 4 + idle-timeout: 180000 + max-lifetime: 599000 + flyway: + enabled: true + locations: db/migration/ + +app: + feign: + room-url: ${ROOM_URL:http://localhost:8081} diff --git a/kalyshev_yan_lab_3/computer-service/src/main/resources/db/migration/V000001__init.sql b/kalyshev_yan_lab_3/computer-service/src/main/resources/db/migration/V000001__init.sql new file mode 100644 index 0000000..15c1141 --- /dev/null +++ b/kalyshev_yan_lab_3/computer-service/src/main/resources/db/migration/V000001__init.sql @@ -0,0 +1,7 @@ +CREATE TABLE computer ( + id UUID + CONSTRAINT computer_pk PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description VARCHAR(1000), + room_id UUID NOT NULL +); diff --git a/kalyshev_yan_lab_3/docker-compose.yml b/kalyshev_yan_lab_3/docker-compose.yml new file mode 100644 index 0000000..59f53bd --- /dev/null +++ b/kalyshev_yan_lab_3/docker-compose.yml @@ -0,0 +1,43 @@ +services: + postgres: + image: postgres:latest + container_name: postgres + environment: + POSTGRES_USERNAME: postgres + POSTGRES_PASSWORD: postgres + PGDATA: "/var/lib/postgresql/data/pgdata" + ports: + - "5432:5432" + volumes: + - ./postgres_data:/var/lib/postgresql/data/ + - ./init-database.sh:/docker-entrypoint-initdb.d/init-database.sh + room: + build: ./room-service + container_name: room + depends_on: + - postgres + environment: + SERVER_PORT: 8080 + DB_URL: jdbc:postgresql://postgres:5432/room + DB_USERNAME: postgres + DB_PASSWORD: postgres + computer: + build: ./computer-service + container_name: computer + depends_on: + - postgres + environment: + SERVER_PORT: 8080 + DB_URL: jdbc:postgresql://postgres:5432/computer + DB_USERNAME: postgres + DB_PASSWORD: postgres + ROOM_URL: http://nginx/ + nginx: + image: nginx + depends_on: + - computer + - room + volumes: + - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf + ports: + - 80:80 diff --git a/kalyshev_yan_lab_3/init-database.sh b/kalyshev_yan_lab_3/init-database.sh new file mode 100644 index 0000000..f9c3afb --- /dev/null +++ b/kalyshev_yan_lab_3/init-database.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +# Создаем БД +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE DATABASE computer; + CREATE DATABASE room; +EOSQL diff --git a/kalyshev_yan_lab_3/nginx/nginx.conf b/kalyshev_yan_lab_3/nginx/nginx.conf new file mode 100644 index 0000000..786e529 --- /dev/null +++ b/kalyshev_yan_lab_3/nginx/nginx.conf @@ -0,0 +1,21 @@ +server { + listen 80; + listen [::]:80; + server_name localhost; + + location /api/v1/room { + proxy_pass http://room:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /api/v1/computer { + proxy_pass http://computer:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} diff --git a/kalyshev_yan_lab_3/room-service/.gitignore b/kalyshev_yan_lab_3/room-service/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/kalyshev_yan_lab_3/room-service/Dockerfile b/kalyshev_yan_lab_3/room-service/Dockerfile new file mode 100644 index 0000000..cc673eb --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/Dockerfile @@ -0,0 +1,21 @@ +# Используем образ Maven для сборки +FROM maven:3.8-eclipse-temurin-21-alpine AS build + +# Устанавливаем рабочую директорию +WORKDIR /app + +# Копируем остальные исходные файлы +COPY pom.xml . +COPY src src + +# Собираем весь проект +RUN mvn clean package -DskipTests + +# Используем официальный образ JDK для запуска собранного jar-файла +FROM eclipse-temurin:21-jdk-alpine + +# Копируем jar-файл из предыдущего этапа +COPY --from=build /app/target/*.jar /app.jar + +# Указываем команду для запуска приложения +CMD ["java", "-jar", "app.jar"] diff --git a/kalyshev_yan_lab_3/room-service/pom.xml b/kalyshev_yan_lab_3/room-service/pom.xml new file mode 100644 index 0000000..425e89f --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + ru.room + room-service + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + + + 21 + 21 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.flywaydb + flyway-core + + + org.postgresql + postgresql + + + org.projectlombok + lombok + 1.18.30 + + + io.swagger.core.v3 + swagger-annotations + 2.2.1 + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/RoomApplication.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/RoomApplication.java new file mode 100644 index 0000000..b8ae38f --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/RoomApplication.java @@ -0,0 +1,12 @@ +package ru.room; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class RoomApplication { + + public static void main(String[] args) { + SpringApplication.run(RoomApplication.class, args); + } +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomController.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomController.java new file mode 100644 index 0000000..a617862 --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomController.java @@ -0,0 +1,43 @@ +package ru.room.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import ru.room.domain.RoomEntity; +import ru.room.domain.CreateRoomRequest; + +import java.util.List; +import java.util.UUID; + +@Validated +@Tag(name = "room", description = "API для управления компаниями") +public interface RoomController { + + @Operation(summary = "Получение всех компаний") + @GetMapping(value = "/api/v1/room") + List getCompanies(); + + @Operation(summary = "Создание компании") + @PostMapping(value = "/api/v1/room") + RoomEntity createRoom(@RequestBody @NotNull CreateRoomRequest roomRequest); + + @Operation(summary = "Получение информации о компании по id") + @GetMapping(value = "/api/v1/room/{roomId}") + RoomEntity getRoom(@PathVariable UUID roomId); + + @Operation(summary = "Редактирование компании") + @PutMapping(value = "/api/v1/room/{roomId}") + RoomEntity updateRoom(@PathVariable UUID roomId, + @RequestBody @NotNull CreateRoomRequest roomRequest); + + @Operation(summary = "Удалении компании по id") + @DeleteMapping(value = "/api/v1/room/{roomId}") + void deleteRoom(@PathVariable UUID roomId); +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomControllerImpl.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomControllerImpl.java new file mode 100644 index 0000000..e1d5f8a --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/controller/RoomControllerImpl.java @@ -0,0 +1,42 @@ +package ru.room.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RestController; +import ru.room.domain.RoomEntity; +import ru.room.domain.CreateRoomRequest; +import ru.room.service.RoomService; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequiredArgsConstructor +public class RoomControllerImpl implements RoomController { + + private final RoomService roomService; + + @Override + public List getCompanies() { + return roomService.getCompanies(); + } + + @Override + public RoomEntity createRoom(CreateRoomRequest roomRequest) { + return roomService.createRoom(roomRequest); + } + + @Override + public RoomEntity getRoom(UUID roomId) { + return roomService.getRoom(roomId); + } + + @Override + public RoomEntity updateRoom(UUID roomId, CreateRoomRequest roomRequest) { + return roomService.updateRoom(roomId, roomRequest); + } + + @Override + public void deleteRoom(UUID roomId) { + roomService.deleteRoom(roomId); + } +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/CreateRoomRequest.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/CreateRoomRequest.java new file mode 100644 index 0000000..00bcfae --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/CreateRoomRequest.java @@ -0,0 +1,13 @@ +package ru.room.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class CreateRoomRequest { + + private String name; + + private String address; +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/RoomEntity.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/RoomEntity.java new file mode 100644 index 0000000..54d5de1 --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/domain/RoomEntity.java @@ -0,0 +1,32 @@ +package ru.room.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@Entity +@Builder +@Table(name = "room") +@AllArgsConstructor +@NoArgsConstructor +public class RoomEntity { + + @Id + @GeneratedValue + private UUID id; + + @Column(name = "name", unique = true, nullable = false) + private String name; + + @Column(name = "address", unique = true, nullable = false) + private String address; +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/AbstractWebExceptionHandler.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/AbstractWebExceptionHandler.java new file mode 100644 index 0000000..3623f56 --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/AbstractWebExceptionHandler.java @@ -0,0 +1,41 @@ +package ru.room.exception; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.time.LocalDateTime; + +import static org.springframework.http.HttpStatus.NOT_FOUND; + +@Slf4j +public abstract class AbstractWebExceptionHandler { + + public static ResponseEntity buildResponse(ErrorCode errorCode, HttpStatus status, String msg, + boolean details, Throwable ex) { + return buildResponse(errorCode.name(), status, msg, details, ex, LocalDateTime.now()); + } + + public static ResponseEntity buildResponse(String errorCode, HttpStatus status, String msg, + boolean details, Throwable ex, LocalDateTime timestamp) { + if (details) { + log.error(msg, ex); // with stack-trace + } else { + var message = StringUtils.equals(msg, ex.getMessage()) ? msg : msg + ": " + ex.getMessage(); + if (status == NOT_FOUND) { + log.warn(message); + } else { + log.error(message); + } + } + + return ResponseEntity.status(status.value()) + .body(ResponseError.builder() + .code(errorCode) + .message(msg) + .timestamp(timestamp) + .status(status.value()) + .build()); + } +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/BasicError.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/BasicError.java new file mode 100644 index 0000000..bb1034a --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/BasicError.java @@ -0,0 +1,5 @@ +package ru.room.exception; + +public enum BasicError implements ErrorCode { + NOT_FOUND +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ErrorCode.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ErrorCode.java new file mode 100644 index 0000000..3d25b35 --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ErrorCode.java @@ -0,0 +1,9 @@ +package ru.room.exception; + +import java.io.Serializable; + +public interface ErrorCode extends Serializable { + + String name(); + +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResourceNotFoundException.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResourceNotFoundException.java new file mode 100644 index 0000000..c55bc35 --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResourceNotFoundException.java @@ -0,0 +1,28 @@ +package ru.room.exception; + +import org.springframework.http.HttpStatus; + +import static ru.room.exception.BasicError.NOT_FOUND; + +public class ResourceNotFoundException extends RoomAppRuntimeException { + + public ResourceNotFoundException(ErrorCode errorCode, String message) { + this(errorCode, message, null); + } + + public ResourceNotFoundException(ErrorCode errorCode, String message, Throwable error) { + super(errorCode, HttpStatus.NOT_FOUND, message, error); + } + + public static ResourceNotFoundException notFound(String message, Object... args) { + return new ResourceNotFoundException(NOT_FOUND, String.format(message, args), null); + } + + public static ResourceNotFoundException notFound() { + return new ResourceNotFoundException(NOT_FOUND, "Запрашиваемые данные не найдены", null); + } + + public static ResourceNotFoundException notFound(Throwable error) { + return new ResourceNotFoundException(NOT_FOUND, error.getMessage(), error); + } +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResponseError.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResponseError.java new file mode 100644 index 0000000..d60ee4d --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/ResponseError.java @@ -0,0 +1,24 @@ +package ru.room.exception; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ResponseError { + @JsonProperty(required = true) + private LocalDateTime timestamp; + @JsonProperty(required = true) + private Integer status; + @JsonProperty(required = true) + private String message; + @JsonProperty(required = true) + private String code; +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/RoomAppRuntimeException.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/RoomAppRuntimeException.java new file mode 100644 index 0000000..a16520d --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/RoomAppRuntimeException.java @@ -0,0 +1,27 @@ +package ru.room.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class RoomAppRuntimeException extends RuntimeException { + private final ErrorCode errorCode; + private final HttpStatus status; + private final boolean details; + + public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message) { + this(errorCode, status, message, null); + } + + public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, Throwable error) { + this(errorCode, status, message, true, error); + } + + public RoomAppRuntimeException(ErrorCode errorCode, HttpStatus status, String message, boolean details, + Throwable error) { + super(message, error); + this.errorCode = errorCode; + this.status = status; + this.details = details; + } +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/WebExceptionHandler.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/WebExceptionHandler.java new file mode 100644 index 0000000..a9a6f90 --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/exception/WebExceptionHandler.java @@ -0,0 +1,22 @@ +package ru.room.exception; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@Getter +@RestControllerAdvice +@RequiredArgsConstructor +public class WebExceptionHandler extends AbstractWebExceptionHandler { + private final ObjectMapper objectMapper; + + @ExceptionHandler(RoomAppRuntimeException.class) + public ResponseEntity handleException(RoomAppRuntimeException ex) { + return buildResponse(ex.getErrorCode(), ex.getStatus(), ex.getMessage(), ex.isDetails(), ex); + } +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/repository/RoomRepository.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/repository/RoomRepository.java new file mode 100644 index 0000000..e4870a9 --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/repository/RoomRepository.java @@ -0,0 +1,9 @@ +package ru.room.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.room.domain.RoomEntity; + +import java.util.UUID; + +public interface RoomRepository extends JpaRepository { +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomService.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomService.java new file mode 100644 index 0000000..3881f4e --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomService.java @@ -0,0 +1,20 @@ +package ru.room.service; + +import ru.room.domain.RoomEntity; +import ru.room.domain.CreateRoomRequest; + +import java.util.List; +import java.util.UUID; + +public interface RoomService { + + List getCompanies(); + + RoomEntity createRoom(CreateRoomRequest roomRequest); + + RoomEntity updateRoom(UUID roomId, CreateRoomRequest roomRequest); + + void deleteRoom(UUID roomId); + + RoomEntity getRoom(UUID roomId); +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomServiceImpl.java b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomServiceImpl.java new file mode 100644 index 0000000..cab5adf --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/java/ru/room/service/RoomServiceImpl.java @@ -0,0 +1,57 @@ +package ru.room.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.room.domain.RoomEntity; +import ru.room.domain.CreateRoomRequest; +import ru.room.repository.RoomRepository; + +import java.util.List; +import java.util.UUID; + +import static ru.room.exception.ResourceNotFoundException.notFound; + +@Service +@RequiredArgsConstructor +public class RoomServiceImpl implements RoomService { + + private final RoomRepository roomRepository; + + @Override + public List getCompanies() { + return roomRepository.findAll(); + } + + @Override + public RoomEntity createRoom(CreateRoomRequest roomRequest) { + var entity = RoomEntity.builder() + .name(roomRequest.getName()) + .address(roomRequest.getAddress()) + .build(); + return roomRepository.save(entity); + } + + @Override + public RoomEntity updateRoom(UUID roomId, CreateRoomRequest roomRequest) { + var room = getById(roomId); + room.setName(roomRequest.getName()); + room.setAddress(roomRequest.getAddress()); + return roomRepository.save(room); + } + + @Override + public void deleteRoom(UUID roomId) { + var room = getById(roomId); + roomRepository.delete(room); + } + + @Override + public RoomEntity getRoom(UUID roomId) { + return getById(roomId); + } + + private RoomEntity getById(UUID roomId) { + return roomRepository.findById(roomId) + .orElseThrow(() -> notFound("Компания не найдена")); + } +} diff --git a/kalyshev_yan_lab_3/room-service/src/main/resources/application.yml b/kalyshev_yan_lab_3/room-service/src/main/resources/application.yml new file mode 100644 index 0000000..e98b0f6 --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/resources/application.yml @@ -0,0 +1,28 @@ +server: + port: ${SERVER_PORT:8080} +spring: + application: + name: room-app + jpa: + database: POSTGRESQL + open-in-view: false + show-sql: false + hibernate: + ddl-auto: none + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQLDialect + datasource: + url: ${DB_URL:jdbc:postgresql://postgres:5433/room} + username: ${DB_USERNAME:postgres} + password: ${DB_PASSWORD:postgres} + driverClassName: org.postgresql.Driver + type: com.zaxxer.hikari.HikariDataSource + hikari: + maximum-pool-size: 15 + minimum-idle: 4 + idle-timeout: 180000 + max-lifetime: 599000 + flyway: + enabled: true + locations: db/migration/ diff --git a/kalyshev_yan_lab_3/room-service/src/main/resources/db/migration/V000001__init.sql b/kalyshev_yan_lab_3/room-service/src/main/resources/db/migration/V000001__init.sql new file mode 100644 index 0000000..ea25073 --- /dev/null +++ b/kalyshev_yan_lab_3/room-service/src/main/resources/db/migration/V000001__init.sql @@ -0,0 +1,6 @@ +CREATE TABLE room ( + id UUID + CONSTRAINT room_pk PRIMARY KEY, + name VARCHAR(255) UNIQUE NOT NULL, + address VARCHAR(255) NOT NULL +); From a02eb5e9d9097e0345ff53636352c77b5cac2712 Mon Sep 17 00:00:00 2001 From: Zyzf Date: Sat, 2 Nov 2024 20:14:47 +0400 Subject: [PATCH 2/2] done --- kalyshev_yan_lab_3/README.md | 74 ++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/kalyshev_yan_lab_3/README.md b/kalyshev_yan_lab_3/README.md index 0aabe86..7f98469 100644 --- a/kalyshev_yan_lab_3/README.md +++ b/kalyshev_yan_lab_3/README.md @@ -1,58 +1,48 @@ # Отчет. Лабораторная работа 3 ## Описание -В рамках лабораторной работы № 3 были реализованы два сервиса (Java + Spring), осуществляющие CRUD-операции над сущностями. +В рамках лабораторной работы № 3 были реализованы два сервиса (Java + Spring), предоставляющие CRUD-операции для сущностей. Модель данных следующая: -Сущность "Компания" (сервис company) -- идентификатор компании -- название -- адрес +Сущность "Помещение" (сервис room): -Сущность "Вакансия" (сервис vacancy) -- идентификатор вакансии -- название -- описание -- нижняя граница зарплаты -- верхняя граница зарплаты -- идентификатор компании +1. идентификатор помещения +2. название +3. адрес -Компания с вакансией связана как "один ко многим". +Сущность "Компьютер" (сервис computer): -Каждый из сервисов имеет API с пятью эндпоинтами. При этом в сервисе vacancy при запросе вакансии по id происходит -дополнительный запрос в сервис company для получения информации по компании, связанной с вакансией. -Происходит это взаимодействие с помощью библиотеки OpenFeign, которая "под капотом" использует HttpClient. - -В качестве хранилища данных использовалась СУБД Postgres. У каждого сервиса своя база данных в поднятой СУБД. -Для создания схемы БД была использована библиотека Flyway, которая применила написанные миграции при старте приложения. - -Запросы к сервисам проксирует шлюз на основе Nginx. Для этого перед запуском nginx был описан конфигурационный файл nginx.conf, -в котором описан прослушиваемый порт и название сервера (в блоке server), маршруты до микросервисов и параметры проксирования (в блоке location). +1. идентификатор компьютера +2. название +3. описание +4. идентификатор помещения +Компьютер с помещением связан как "один ко многим". +Каждый из сервисов имеет API с пятью эндпоинтами. При этом в сервисе computer при запросе компьютера по id происходит дополнительный запрос в сервис room для получения информации о помещении, связанным с компьютером. +Происходит это взаимодействие с помощью библиотеки OpenFeign, которая использует HttpClient. +В качестве хранилища данных использовалась СУБД Postgres. У каждого сервиса своя база данных в поднятой СУБД. +Для создания схемы БД была использована библиотека Flyway, применившая написанные миграции при старте приложения. +Запросы к сервисам проксируют шлюз на основе Nginx. Для этого перед запуском nginx был описан конфигурационный файл nginx.conf, +в котором были указаны прослушиваемый порт и название сервера (в блоке server), маршруты до микросервисов и параметры проксирования (в блоке location). Таким образом, с помощью Docker Compose были подняты сервисы: -- company -- vacancy + +- room +- computer - postgres - nginx + ## Как запустить + Для того, чтобы запустить сервисы, необходимо выполнить следующие действия: -1. Установить и запустить Docker Engine или Docker Desktop -2. Через консоль перейти в папку, в которой расположен файл docker-compose.yml -3. Выполнить команду: -``` -docker compose up --build -``` -В случае успешного запуска всех контейнеров в консоли будет выведено следующее сообщение: -``` -[+] Running 5/5 - ✔ Network borschevskaya_anna_lab_3_default Created 0.0s - ✔ Container postgres Started 0.6s - ✔ Container vacancy Started 1.1s - ✔ Container company Started 0.9s - ✔ Container borschevskaya_anna_lab_3-nginx-1 Started -``` -Далее можно осуществлять запросы к сервисам по адресу http://localhost/{location}, где часть пути location меняется в зависимости от сервиса и запроса. + +Установите и запустите Docker Engine или Docker Desktop. +Перейдите в папку, содержащую файл docker-compose.yml через консоль. +Выполните команду: +`docker compose up --build` +Далее можно осуществлять запросы к сервисам по адресу http://localhost/{location} , где часть пути location меняется в зависимости от сервиса и запроса. + ## Видео-отчет -Работоспособность лабораторной работы можно оценить в следующем [видео](https://disk.yandex.ru/i/KPNBfnlcgl1auw). -Демонстрация взаимодействия с системой производится с применением сервиса Postman. \ No newline at end of file + +Работоспособность лабораторной работы можно оценить в следующем видео: https://zyzf.space/s/iWxb6b4EFQjPias. +Демонстрация взаимодействия с системой производится с помощью утилиты httpie.