From a01196d2bf6d39ef53c71df236d6dd512362f9d6 Mon Sep 17 00:00:00 2001 From: Iakov Lysenko Date: Fri, 13 Jun 2025 23:07:11 +0300 Subject: [PATCH 1/8] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=80=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=B4=20=D0=B8=20?= =?UTF-8?q?=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=81=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Homework/pom.xml | 52 ------ .../iakov/homework2/legacy/HibernateUtil.java | 40 ----- .../iakov/homework2/legacy/Runner.java | 138 --------------- .../iakov/homework2/legacy/dao/UserDao.java | 15 -- .../homework2/legacy/dao/UserDaoImpl.java | 57 ------- .../homework2/legacy/SimpleContainerTest.java | 161 ------------------ .../legacy/service/UserServiceTest.java | 126 -------------- 7 files changed, 589 deletions(-) delete mode 100644 Homework/src/main/java/ru/project/iakov/homework2/legacy/HibernateUtil.java delete mode 100644 Homework/src/main/java/ru/project/iakov/homework2/legacy/Runner.java delete mode 100644 Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDao.java delete mode 100644 Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDaoImpl.java delete mode 100644 Homework/src/test/java/ru/project/iakov/homework2/legacy/SimpleContainerTest.java delete mode 100644 Homework/src/test/java/ru/project/iakov/homework2/legacy/service/UserServiceTest.java diff --git a/Homework/pom.xml b/Homework/pom.xml index c051b94..c647764 100644 --- a/Homework/pom.xml +++ b/Homework/pom.xml @@ -12,69 +12,17 @@ UTF-8 - - org.hibernate.orm - hibernate-core - 6.6.15.Final - org.postgresql postgresql 42.7.5 - - org.slf4j - slf4j-api - 2.0.13 - org.projectlombok lombok 1.18.38 provided - - ch.qos.logback - logback-classic - 1.5.18 - compile - - - org.junit.jupiter - junit-jupiter - 5.10.0 - test - - - org.testcontainers - postgresql - 1.19.0 - test - - - org.testcontainers - junit-jupiter - 1.21.1 - test - - - org.slf4j - slf4j-log4j12 - 2.1.0-alpha1 - pom - - - org.mockito - mockito-core - 5.8.0 - test - - - org.assertj - assertj-core - 3.25.1 - test - org.springframework.boot spring-boot diff --git a/Homework/src/main/java/ru/project/iakov/homework2/legacy/HibernateUtil.java b/Homework/src/main/java/ru/project/iakov/homework2/legacy/HibernateUtil.java deleted file mode 100644 index a6542a9..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/legacy/HibernateUtil.java +++ /dev/null @@ -1,40 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy; - -import lombok.Getter; -import org.hibernate.SessionFactory; -import org.hibernate.cfg.Configuration; -import ru.project.iakov.homework2.User; - -public class HibernateUtil { - @Getter - private static SessionFactory sessionFactory = buildSessionFactory(); - - private static SessionFactory buildSessionFactory() { - try { - Configuration configuration = new Configuration(); - configuration.configure(); - configuration.addAnnotatedClass(User.class); - return configuration.buildSessionFactory(); - } catch (Exception e) { - System.err.println(e.getMessage()); - throw new ExceptionInInitializerError(e); - } - } - - public static void rebuildSessionFactoryForTests() { - if (sessionFactory != null) { - sessionFactory.close(); - } - - Configuration configuration = new Configuration(); - configuration.configure("hibernate.cfg.xml"); - configuration.setProperty("hibernate.connection.url", System.getProperty("DB_URL")); - configuration.setProperty("hibernate.connection.username", System.getProperty("DB_USERNAME")); - configuration.setProperty("hibernate.connection.password", System.getProperty("DB_PASSWORD")); - - configuration.addAnnotatedClass(User.class); - - sessionFactory = configuration.buildSessionFactory(); - } -}*/ diff --git a/Homework/src/main/java/ru/project/iakov/homework2/legacy/Runner.java b/Homework/src/main/java/ru/project/iakov/homework2/legacy/Runner.java deleted file mode 100644 index ae9fe60..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/legacy/Runner.java +++ /dev/null @@ -1,138 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy; - -import ru.project.iakov.homework2.User; -import ru.project.iakov.homework2.dao.UserDaoImpl; -import ru.project.iakov.homework2.service.UserService; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import java.util.Scanner; - -public class Runner { - private static final Scanner scanner = new Scanner(System.in); - private static final UserService userService = new UserService(new UserDaoImpl()); - - public static void main(String[] args) { - while (true) { - printMenu(); - int choice = readInt("Выберите пункт меню: "); - - switch (choice) { - case 1 -> createUser(); - case 2 -> findUserById(); - case 3 -> findAllUsers(); - case 4 -> updateUser(); - case 5 -> deleteUser(); - case 6 -> exitApp(); - default -> System.out.println("Неверный пункт меню. Попробуйте снова."); - } - } - } - - private static void printMenu() { - System.out.println(""" - ===== Меню ===== - 1. Создать пользователя - 2. Найти пользователя по ID - 3. Показать всех пользователей - 4. Обновить пользователя - 5. Удалить пользователя - 6. Выход - """); - } - - private static void createUser() { - String name = readLine("Имя: "); - String email = readLine("Email: "); - int age = readInt("Возраст: "); - - User user = User.builder() - .name(name) - .email(email) - .age(age) - .createdAt(LocalDateTime.now()) - .build(); - - userService.createUser(user); - System.out.println("Пользователь создан."); - } - - private static void findUserById() { - long id = readLong("Введите ID пользователя: "); - Optional userOpt = userService.findById(id); - userOpt.ifPresentOrElse( - System.out::println, - () -> System.out.println("Пользователь не найден.") - ); - } - - private static void findAllUsers() { - List users = userService.findAll(); - if (users.isEmpty()) { - System.out.println("Нет пользователей в базе."); - } else { - users.forEach(System.out::println); - } - } - - private static void updateUser() { - long id = readLong("ID обновляемого пользователя: "); - Optional userOpt = userService.findById(id); - - if (userOpt.isPresent()) { - User user = userOpt.get(); - String name = readLine("Новое имя (" + user.getName() + "): "); - String email = readLine("Новый email (" + user.getEmail() + "): "); - int age = readInt("Новый возраст (" + user.getAge() + "): "); - - user.setName(name.isEmpty() ? user.getName() : name); - user.setEmail(email.isEmpty() ? user.getEmail() : email); - user.setAge(age <= 0 ? user.getAge() : age); - - userService.update(user); - System.out.println("Пользователь обновлён."); - } else { - System.out.println("Пользователь с таким ID не найден."); - } - } - - private static void deleteUser() { - long id = readLong("ID пользователя для удаления: "); - userService.delete(id); - System.out.println("Пользователь удалён (если существовал)."); - } - - private static void exitApp() { - System.out.println("Завершение работы."); - System.exit(0); - } - - private static int readInt(String prompt) { - System.out.print(prompt); - while (!scanner.hasNextInt()) { - scanner.nextLine(); - System.out.print("Введите целое число: "); - } - int value = scanner.nextInt(); - scanner.nextLine(); - return value; - } - - private static long readLong(String prompt) { - System.out.print(prompt); - while (!scanner.hasNextLong()) { - scanner.nextLine(); - System.out.print("Введите корректный ID: "); - } - long value = scanner.nextLong(); - scanner.nextLine(); - return value; - } - - private static String readLine(String prompt) { - System.out.print(prompt); - return scanner.nextLine().trim(); - } -}*/ diff --git a/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDao.java b/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDao.java deleted file mode 100644 index 5063c18..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDao.java +++ /dev/null @@ -1,15 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy.dao; - -import ru.project.iakov.homework2.User; - -import java.util.List; -import java.util.Optional; - -public interface UserDao { - void create(User user); - Optional findById(Long id); - List findAll(); - void update(User user); - void delete(Long id); -}*/ diff --git a/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDaoImpl.java b/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDaoImpl.java deleted file mode 100644 index 251369c..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDaoImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy.dao; - -import ru.project.iakov.homework2.legacy.HibernateUtil; -import ru.project.iakov.homework2.User; -import org.hibernate.Session; -import org.hibernate.Transaction; - -import java.util.List; -import java.util.Optional; - -public class UserDaoImpl implements UserDao { - - @Override - public void create(User user) { - try (Session session = HibernateUtil.getSessionFactory().openSession()) { - Transaction tx = session.beginTransaction(); - session.persist(user); - tx.commit(); - } - } - - @Override - public Optional findById(Long id) { - try (Session session = HibernateUtil.getSessionFactory().openSession()) { - return Optional.ofNullable(session.get(User.class, id)); - } - } - - @Override - public List findAll() { - try (Session session = HibernateUtil.getSessionFactory().openSession()) { - return session.createQuery("from User", User.class).list(); - } - } - - @Override - public void update(User user) { - try (Session session = HibernateUtil.getSessionFactory().openSession()) { - Transaction tx = session.beginTransaction(); - session.merge(user); - tx.commit(); - } - } - - @Override - public void delete(Long id) { - try (Session session = HibernateUtil.getSessionFactory().openSession()) { - Transaction tx = session.beginTransaction(); - User user = session.get(User.class, id); - if (user != null) { - session.remove(user); - } - tx.commit(); - } - } -}*/ diff --git a/Homework/src/test/java/ru/project/iakov/homework2/legacy/SimpleContainerTest.java b/Homework/src/test/java/ru/project/iakov/homework2/legacy/SimpleContainerTest.java deleted file mode 100644 index 5769800..0000000 --- a/Homework/src/test/java/ru/project/iakov/homework2/legacy/SimpleContainerTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy; - -import jakarta.persistence.PersistenceException; -import org.junit.jupiter.api.*; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import ru.project.iakov.homework2.User; -import ru.project.iakov.homework2.legacy.dao.UserDao; -import ru.project.iakov.homework2.legacy.dao.UserDaoImpl; -import ru.project.iakov.homework2.legacy.HibernateUtil; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; - -@Testcontainers -public class SimpleContainerTest { - private static UserDao userDao; - @Container - public static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres") - .withDatabaseName("postgres") - .withUsername("postgres") - .withPassword("postgres"); - - @BeforeAll - static void setUp() { - postgres.start(); - System.setProperty("DB_URL", postgres.getJdbcUrl()); - System.setProperty("DB_USERNAME", postgres.getUsername()); - System.setProperty("DB_PASSWORD", postgres.getPassword()); - - HibernateUtil.rebuildSessionFactoryForTests(); - userDao = new UserDaoImpl(); - } - - @DisplayName("Создание и поиск по ID") - @Test - @Order(1) - void shouldCreateAndFindUserById() { - User user = User.builder() - .name("Iakov") - .email("lysenko_iakov@yahoo.com") - .age(25) - .createdAt(LocalDateTime.now()) - .build(); - - userDao.create(user); - - assertNotNull(user.getId()); - - Optional saved = userDao.findById(user.getId()); - assertTrue(saved.isPresent()); - assertEquals("Iakov", saved.get().getName()); - } - - @DisplayName("Обновление пользователя") - @Test - @Order(2) - void shouldUpdateUser() { - User user = User.builder() - .name("Original") - .email("original@email.com") - .age(30) - .createdAt(LocalDateTime.now()) - .build(); - - userDao.create(user); - user.setName("Updated"); - user.setEmail("updated@email.com"); - - userDao.update(user); - - Optional updated = userDao.findById(user.getId()); - assertTrue(updated.isPresent()); - assertEquals("Updated", updated.get().getName()); - assertEquals("updated@email.com", updated.get().getEmail()); - } - - @DisplayName("Удаление существующего пользователя") - @Test - @Order(3) - void shouldDeleteUser() { - User user = User.builder() - .name("ToDelete") - .email("delete@example.com") - .age(40) - .createdAt(LocalDateTime.now()) - .build(); - - userDao.create(user); - Long id = user.getId(); - - userDao.delete(id); - - Optional deleted = userDao.findById(id); - assertFalse(deleted.isPresent()); - } - - @DisplayName("Поиск по несуществующему ID") - @Test - @Order(4) - void shouldReturnEmptyForNonExistingUser() { - Optional user = userDao.findById(99999L); - assertTrue(user.isEmpty()); - } - - @DisplayName("Удаление несуществующего пользователя") - @Test - @Order(5) - void shouldNotThrowWhenDeletingNonExistingUser() { - assertDoesNotThrow(() -> userDao.delete(99999L)); - } - - @DisplayName("Создание пользователя с null-именем должно выбросить исключение") - @Test - @Order(6) - void shouldThrowWhenCreatingUserWithNullName() { - User user = User.builder() - .email("nullname@example.com") - .age(30) - .createdAt(LocalDateTime.now()) - .build(); - - assertThrows(PersistenceException.class, () -> userDao.create(user)); - } - - @DisplayName("Найти всех пользователей") - @Test - @Order(7) - void shouldFindAllUsers() { - User user1 = User.builder() - .name("User1") - .email("user1@email.com") - .age(20) - .createdAt(LocalDateTime.now()) - .build(); - - User user2 = User.builder() - .name("User2") - .email("user2@email.com") - .age(25) - .createdAt(LocalDateTime.now()) - .build(); - - userDao.create(user1); - userDao.create(user2); - - List allUsers = userDao.findAll(); - - assertTrue(allUsers.size() >= 2); - } - - @AfterAll - static void stopContainer() { - postgres.stop(); - } -}*/ diff --git a/Homework/src/test/java/ru/project/iakov/homework2/legacy/service/UserServiceTest.java b/Homework/src/test/java/ru/project/iakov/homework2/legacy/service/UserServiceTest.java deleted file mode 100644 index d78267f..0000000 --- a/Homework/src/test/java/ru/project/iakov/homework2/legacy/service/UserServiceTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy.service; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.BeforeEach; -import ru.project.iakov.homework2.User; -import ru.project.iakov.homework2.legacy.dao.UserDao; - -import java.util.Arrays; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -public class UserServiceTest { - private UserDao userDao; - private UserService userService; - - @BeforeEach - void setUp() { - userDao = mock(UserDao.class); - userService = new UserService(userDao); - } - - @DisplayName("Создание пользователя") - @Test - public void createUser() { - User user = new User(); - userService.createUser(user); - - verify(userDao, times(1)).create(user); - } - - @DisplayName("Пользователь найден по ID") - @Test - public void findById_shouldReturnUser_whenUserExist() { - User user = new User(); - user.setId(1L); - - when(userDao.findById(1L)).thenReturn(Optional.of(user)); - - Optional result = userService.findById(1L); - - assertTrue(result.isPresent()); - assertEquals(1L, result.get().getId()); - verify(userDao, times(1)).findById(1L); - } - - @DisplayName("Такого пользователя нет") - @Test - public void findById_shouldReturnEmpty_whenUserNotExist() { - when(userDao.findById(1L)).thenReturn(Optional.empty()); - - Optional result = userService.findById(1L); - - assertFalse(result.isPresent()); - verify(userDao, times(1)).findById(1L); - } - - @DisplayName("Отрицательный ID — выбрасывается исключение") - @Test - void findById_shouldThrowException_whenIdIsNegative() { - assertThrows(IllegalArgumentException.class, () -> userService.findById(-1L)); - verifyNoInteractions(userDao); - } - - @DisplayName("ID равен нулю — выбрасывается исключение") - @Test - void findById_shouldThrowException_whenIdIsZero() { - assertThrows(IllegalArgumentException.class, () -> userService.findById(0L)); - verifyNoInteractions(userDao); - } - - @DisplayName("ID равен null — выбрасывается исключение") - @Test - void findById_shouldThrowException_whenIdIsNull() { - assertThrows(IllegalArgumentException.class, () -> userService.findById(null)); - verifyNoInteractions(userDao); - } - - @DisplayName("Получение списка всех пользователей") - @Test - public void testGetAllUsers() { - List users = Arrays.asList(new User(), new User()); - when(userDao.findAll()).thenReturn(users); - - List result = userService.findAll(); - - assertEquals(2, result.size()); - verify(userDao, times(1)).findAll(); - } - - @DisplayName("Обновление данных пользователя") - @Test - public void updateUser() { - User user = new User(); - user.setId(2L); - - userService.update(user); - - verify(userDao, times(1)).update(user); - } - - @DisplayName("Пользователь успешно удален") - @Test - public void deleteUser_whenUserExist() { - userService.delete(3L); - - verify(userDao, times(1)).delete(3L); - } - - @DisplayName("При удалении пользователь не найден") - @Test - public void deleteUser_whenUserNotExist() { - Long noExistId = 999L; - doThrow(new NoSuchElementException("Пользователь не найден")) - .when(userDao).delete(noExistId); - - assertThrows(NoSuchElementException.class, () -> userService.delete(noExistId)); - - verify(userDao).delete(noExistId); - } -}*/ From 4ef40c9440dd56706ab03947c5dfb0c8eaca0376 Mon Sep 17 00:00:00 2001 From: Iakov Lysenko Date: Sun, 15 Jun 2025 00:03:25 +0300 Subject: [PATCH 2/8] =?UTF-8?q?=D0=BC=D0=B8=D0=BA=D1=80=D0=BE=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=B8=D1=81=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=BD=D0=B0=20=D0=BF=D0=BE=D1=87=D1=82=D1=83=20(kafka?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Homework/pom.xml | 16 +++--- .../homework2/config/KafkaTopicConfig.java | 49 +++++++++++++++++++ .../controller/NotificationController.java | 25 ++++++++++ .../{ => controller}/UserController.java | 3 +- .../iakov/homework2/dto/EmailRequest.java | 11 +++++ .../iakov/homework2/{ => dto}/UserDto.java | 2 +- .../iakov/homework2/dto/UserEvent.java | 12 +++++ .../iakov/homework2/{ => entity}/User.java | 2 +- .../homework2/kafka/KafkaConsumerService.java | 45 +++++++++++++++++ .../homework2/{ => mapper}/UserMapper.java | 4 +- .../{ => repository}/UserRepository.java | 3 +- .../iakov/homework2/service/EmailService.java | 37 ++++++++++++++ .../service/KafkaProducerService.java | 23 +++++++++ .../iakov/homework2/service/UserService.java | 24 +++++---- Homework/src/main/resources/application.yml | 24 +++++++++ Homework/src/main/resources/hibernate.cfg.xml | 2 +- .../iakov/homework2/UserControllerTest.java | 2 + 17 files changed, 261 insertions(+), 23 deletions(-) create mode 100644 Homework/src/main/java/ru/project/iakov/homework2/config/KafkaTopicConfig.java create mode 100644 Homework/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java rename Homework/src/main/java/ru/project/iakov/homework2/{ => controller}/UserController.java (95%) create mode 100644 Homework/src/main/java/ru/project/iakov/homework2/dto/EmailRequest.java rename Homework/src/main/java/ru/project/iakov/homework2/{ => dto}/UserDto.java (87%) create mode 100644 Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java rename Homework/src/main/java/ru/project/iakov/homework2/{ => entity}/User.java (93%) create mode 100644 Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java rename Homework/src/main/java/ru/project/iakov/homework2/{ => mapper}/UserMapper.java (81%) rename Homework/src/main/java/ru/project/iakov/homework2/{ => repository}/UserRepository.java (58%) create mode 100644 Homework/src/main/java/ru/project/iakov/homework2/service/EmailService.java create mode 100644 Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java diff --git a/Homework/pom.xml b/Homework/pom.xml index c647764..737bc02 100644 --- a/Homework/pom.xml +++ b/Homework/pom.xml @@ -23,11 +23,6 @@ 1.18.38 provided - - org.springframework.boot - spring-boot - 3.5.0 - org.springframework.boot spring-boot-starter-web @@ -45,9 +40,14 @@ test - com.fasterxml.jackson.core - jackson-annotations - 3.0-rc5 + org.springframework.kafka + spring-kafka + 3.3.6 + + + org.springframework.boot + spring-boot-starter-mail + 3.4.6 \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/config/KafkaTopicConfig.java b/Homework/src/main/java/ru/project/iakov/homework2/config/KafkaTopicConfig.java new file mode 100644 index 0000000..84b8109 --- /dev/null +++ b/Homework/src/main/java/ru/project/iakov/homework2/config/KafkaTopicConfig.java @@ -0,0 +1,49 @@ +package ru.project.iakov.homework2.config; + +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.support.serializer.JsonDeserializer; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.ConsumerFactory; +import ru.project.iakov.homework2.dto.UserEvent; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class KafkaTopicConfig { + @Bean + public NewTopic userEventsTopic() { + return new NewTopic("user-events", 1, (short) 1); + } + + @Bean + public ConsumerFactory consumerFactory() { + JsonDeserializer jsonDeserializer = new JsonDeserializer<>(UserEvent.class); + jsonDeserializer.setRemoveTypeHeaders(false); + jsonDeserializer.addTrustedPackages("*"); + jsonDeserializer.setUseTypeMapperForKey(true); + + Map props = new HashMap<>(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "notification-group"); + props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + + return new DefaultKafkaConsumerFactory<>( + props, + new StringDeserializer(), + jsonDeserializer + ); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory()); + return factory; + } +} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java b/Homework/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java new file mode 100644 index 0000000..4a4120f --- /dev/null +++ b/Homework/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java @@ -0,0 +1,25 @@ +package ru.project.iakov.homework2.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.project.iakov.homework2.dto.UserEvent; +import ru.project.iakov.homework2.service.KafkaProducerService; + +@Slf4j +@RestController +@RequestMapping("/api/v1/kafka") +public class NotificationController { + private final KafkaProducerService kafkaProducer; + + public NotificationController(KafkaProducerService kafkaProducer) { + this.kafkaProducer = kafkaProducer; + } + + @PostMapping("/publish") + public ResponseEntity send(@RequestBody UserEvent request) { + log.info("Получен запрос на публикацию события: {}", request); + kafkaProducer.sendUserEvent(request); + return ResponseEntity.ok().build(); + } +} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/UserController.java b/Homework/src/main/java/ru/project/iakov/homework2/controller/UserController.java similarity index 95% rename from Homework/src/main/java/ru/project/iakov/homework2/UserController.java rename to Homework/src/main/java/ru/project/iakov/homework2/controller/UserController.java index 6c3ca40..6f3833d 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/UserController.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/controller/UserController.java @@ -1,9 +1,10 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import ru.project.iakov.homework2.dto.UserDto; import ru.project.iakov.homework2.service.UserService; import java.util.List; diff --git a/Homework/src/main/java/ru/project/iakov/homework2/dto/EmailRequest.java b/Homework/src/main/java/ru/project/iakov/homework2/dto/EmailRequest.java new file mode 100644 index 0000000..e9ab3a7 --- /dev/null +++ b/Homework/src/main/java/ru/project/iakov/homework2/dto/EmailRequest.java @@ -0,0 +1,11 @@ +package ru.project.iakov.homework2.dto; + +import lombok.*; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class EmailRequest { + private String email; + private String eventType; +} diff --git a/Homework/src/main/java/ru/project/iakov/homework2/UserDto.java b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserDto.java similarity index 87% rename from Homework/src/main/java/ru/project/iakov/homework2/UserDto.java rename to Homework/src/main/java/ru/project/iakov/homework2/dto/UserDto.java index a789830..7cfa044 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/UserDto.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserDto.java @@ -1,4 +1,4 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.dto; import lombok.*; import java.time.LocalDateTime; diff --git a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java new file mode 100644 index 0000000..47cd7a4 --- /dev/null +++ b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java @@ -0,0 +1,12 @@ +package ru.project.iakov.homework2.dto; + +import lombok.*; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class UserEvent { + private String email; + private String eventType; +} diff --git a/Homework/src/main/java/ru/project/iakov/homework2/User.java b/Homework/src/main/java/ru/project/iakov/homework2/entity/User.java similarity index 93% rename from Homework/src/main/java/ru/project/iakov/homework2/User.java rename to Homework/src/main/java/ru/project/iakov/homework2/entity/User.java index d03b7ae..9e0ad6e 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/User.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/entity/User.java @@ -1,4 +1,4 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.entity; import jakarta.persistence.*; import lombok.AllArgsConstructor; diff --git a/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java b/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java new file mode 100644 index 0000000..dae8e8d --- /dev/null +++ b/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java @@ -0,0 +1,45 @@ +package ru.project.iakov.homework2.kafka; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; +import ru.project.iakov.homework2.dto.UserEvent; +import ru.project.iakov.homework2.service.EmailService; + +@Component +@Slf4j +public class KafkaConsumerService { + + private final EmailService emailService; + + public KafkaConsumerService(EmailService emailService) { + this.emailService = emailService; + } + + @KafkaListener(topics = "user-events", groupId = "notification-group") + public void listen(UserEvent event) { + log.info("Получено событие из Kafka: {}", event); + String email = event.getEmail(); + String eventType = event.getEventType(); + if (event == null || event.getEmail() == null || event.getEventType() == null) { + log.warn("Пропущено сообщение: event, email или eventType = null"); + return; + } + + String text = switch (event.getEventType()) { + case "CREATED" -> "Здравствуйте! Ваш аккаунт был создан."; + case "DELETED" -> "Здравствуйте! Ваш аккаунт был удалён."; + default -> { + log.warn("Неизвестный тип события: {}", event.getEventType()); + yield "Неизвестное событие."; + } + }; + + try { + emailService.sendEmail(email, text); + log.info("Email успешно отправлен на {}", event.getEmail()); + } catch (Exception e) { + log.error("Ошибка при отправке email на {}: {}", event.getEmail(), e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/UserMapper.java b/Homework/src/main/java/ru/project/iakov/homework2/mapper/UserMapper.java similarity index 81% rename from Homework/src/main/java/ru/project/iakov/homework2/UserMapper.java rename to Homework/src/main/java/ru/project/iakov/homework2/mapper/UserMapper.java index e3fc5b6..b75741e 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/UserMapper.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/mapper/UserMapper.java @@ -1,5 +1,7 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.mapper; import org.springframework.stereotype.Component; +import ru.project.iakov.homework2.entity.User; +import ru.project.iakov.homework2.dto.UserDto; @Component public class UserMapper { diff --git a/Homework/src/main/java/ru/project/iakov/homework2/UserRepository.java b/Homework/src/main/java/ru/project/iakov/homework2/repository/UserRepository.java similarity index 58% rename from Homework/src/main/java/ru/project/iakov/homework2/UserRepository.java rename to Homework/src/main/java/ru/project/iakov/homework2/repository/UserRepository.java index ef9f458..0479fba 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/UserRepository.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/repository/UserRepository.java @@ -1,5 +1,6 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.repository; import org.springframework.data.jpa.repository.JpaRepository; +import ru.project.iakov.homework2.entity.User; public interface UserRepository extends JpaRepository { } \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/service/EmailService.java b/Homework/src/main/java/ru/project/iakov/homework2/service/EmailService.java new file mode 100644 index 0000000..6946fb0 --- /dev/null +++ b/Homework/src/main/java/ru/project/iakov/homework2/service/EmailService.java @@ -0,0 +1,37 @@ +package ru.project.iakov.homework2.service; + + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class EmailService { + + private final JavaMailSender mailSender; + + @Value("${spring.mail.username}") + private String fromEmail; + + @Autowired + public EmailService(JavaMailSender mailSender) { + this.mailSender = mailSender; + } + + public void sendEmail(String to, String text) { + try { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(fromEmail); + message.setTo(to); + message.setText(text); + mailSender.send(message); + } catch (Exception e) { + log.error("Ошибка при отправке email на {}: {}", to, e.getMessage(), e); + throw new RuntimeException("Не удалось отправить email", e); + } + } +} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java b/Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java new file mode 100644 index 0000000..64ea6fc --- /dev/null +++ b/Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java @@ -0,0 +1,23 @@ +package ru.project.iakov.homework2.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; +import ru.project.iakov.homework2.dto.UserEvent; + +@Service +public class KafkaProducerService { + private static final Logger LOGGER = LoggerFactory.getLogger(KafkaProducerService.class); + private final KafkaTemplate kafkaTemplate; + private static final String TOPIC = "user-events"; + + public KafkaProducerService(KafkaTemplate kafkaTemplate) { + this.kafkaTemplate = kafkaTemplate; + } + + public void sendUserEvent(UserEvent event) { + LOGGER.info(String.format("Сообщение: %s", event)); + kafkaTemplate.send(TOPIC, event); + } +} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java b/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java index 5910e1e..7e8bac6 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java @@ -2,27 +2,33 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import ru.project.iakov.homework2.User; -import ru.project.iakov.homework2.UserDto; -import ru.project.iakov.homework2.UserMapper; -import ru.project.iakov.homework2.UserRepository; +import ru.project.iakov.homework2.dto.UserDto; +import ru.project.iakov.homework2.entity.User; +import ru.project.iakov.homework2.dto.UserEvent; +import ru.project.iakov.homework2.mapper.UserMapper; +import ru.project.iakov.homework2.repository.UserRepository; + +import java.io.Serializable; import java.util.List; import java.util.stream.Collectors; @Service -public class UserService { +public class UserService implements Serializable { private UserRepository userRepository; private UserMapper userMapper; + private KafkaProducerService kafkaProducer; @Autowired - public UserService(UserRepository userRepository, UserMapper userMapper) { + public UserService(UserRepository userRepository, UserMapper userMapper, KafkaProducerService kafkaProducer) { this.userRepository = userRepository; this.userMapper = userMapper; + this.kafkaProducer = kafkaProducer; } public UserDto createUser(UserDto userDto) { User user = userMapper.toEntity(userDto); User savedUser = userRepository.save(user); + kafkaProducer.sendUserEvent(new UserEvent(savedUser.getEmail(), "CREATED")); return userMapper.toDto(savedUser); } public UserDto findById(Long id) { @@ -45,9 +51,9 @@ public UserDto updateUser(Long id, UserDto userDto) { return userMapper.toDto(updatedUser); } public void deleteUser(Long id) { - if (!userRepository.existsById(id)) { - throw new IllegalArgumentException(); - } + User user = userRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("User not found")); userRepository.deleteById(id); + kafkaProducer.sendUserEvent(new UserEvent(user.getEmail(), "DELETED")); } } \ No newline at end of file diff --git a/Homework/src/main/resources/application.yml b/Homework/src/main/resources/application.yml index dc80496..0b5f9b1 100644 --- a/Homework/src/main/resources/application.yml +++ b/Homework/src/main/resources/application.yml @@ -13,6 +13,30 @@ spring: jackson: serialization: INDENT_OUTPUT: true + mail: + host: smtp.mail.ru + port: 465 + username: ${EMAIL_USERNAME} + password: ${EMAIL_PASSWORD} + protocol: smtps + properties: + mail.smtp.auth: true + mail.smtp.starttls.enable: true + mail.smtp.ssl.enable: true + kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: notification-group + auto-offset-reset: earliest + admin: + auto-create: true + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-serializer: org.springframework.kafka.support.serializer.JsonSerializer + properties: + spring.json.trusted.packages: '*' + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.springframework.kafka.support.serializer.JsonSerializer server: port: 8080 \ No newline at end of file diff --git a/Homework/src/main/resources/hibernate.cfg.xml b/Homework/src/main/resources/hibernate.cfg.xml index a49f2af..1515a41 100644 --- a/Homework/src/main/resources/hibernate.cfg.xml +++ b/Homework/src/main/resources/hibernate.cfg.xml @@ -20,6 +20,6 @@ true - + --> \ No newline at end of file diff --git a/Homework/src/test/java/ru/project/iakov/homework2/UserControllerTest.java b/Homework/src/test/java/ru/project/iakov/homework2/UserControllerTest.java index 08b60bf..2efe139 100644 --- a/Homework/src/test/java/ru/project/iakov/homework2/UserControllerTest.java +++ b/Homework/src/test/java/ru/project/iakov/homework2/UserControllerTest.java @@ -8,6 +8,8 @@ import org.springframework.http.MediaType; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; +import ru.project.iakov.homework2.controller.UserController; +import ru.project.iakov.homework2.dto.UserDto; import ru.project.iakov.homework2.service.UserService; import java.time.LocalDateTime; From 9faa92add5858e54eac0fc6cd184a76190389ef3 Mon Sep 17 00:00:00 2001 From: Iakov Lysenko Date: Sun, 15 Jun 2025 10:35:17 +0300 Subject: [PATCH 3/8] =?UTF-8?q?=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?= =?UTF-8?q?=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../homework2/controller/EmailController.java | 27 +++++++++++++++++++ .../iakov/homework2/dto/EmailRequest.java | 1 + .../iakov/homework2/dto/UserEvent.java | 1 + .../homework2/kafka/KafkaConsumerService.java | 8 +++--- .../iakov/homework2/service/EmailService.java | 16 ++++++----- .../iakov/homework2/service/UserService.java | 12 +++++++-- 6 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 Homework/src/main/java/ru/project/iakov/homework2/controller/EmailController.java diff --git a/Homework/src/main/java/ru/project/iakov/homework2/controller/EmailController.java b/Homework/src/main/java/ru/project/iakov/homework2/controller/EmailController.java new file mode 100644 index 0000000..b52f81a --- /dev/null +++ b/Homework/src/main/java/ru/project/iakov/homework2/controller/EmailController.java @@ -0,0 +1,27 @@ +package ru.project.iakov.homework2.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.project.iakov.homework2.dto.EmailRequest; +import ru.project.iakov.homework2.service.EmailService; + +@RestController +@RequestMapping("/api/v1/email") +@RequiredArgsConstructor +public class EmailController { + + private final EmailService emailService; + + @PostMapping("/send") + public ResponseEntity sendEmail(@RequestBody EmailRequest request) { + try { + emailService.sendEmail(request.getEmail(), "Тест", "Тестовое сообщение"); + return ResponseEntity.ok("Письмо отправлено"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Ошибка при отправке письма: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/dto/EmailRequest.java b/Homework/src/main/java/ru/project/iakov/homework2/dto/EmailRequest.java index e9ab3a7..43b4d00 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/dto/EmailRequest.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/dto/EmailRequest.java @@ -7,5 +7,6 @@ @AllArgsConstructor public class EmailRequest { private String email; + private String subject; private String eventType; } diff --git a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java index 47cd7a4..7ac0e2c 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java @@ -8,5 +8,6 @@ @Builder public class UserEvent { private String email; + private String subject; private String eventType; } diff --git a/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java b/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java index dae8e8d..0175daf 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java @@ -20,9 +20,9 @@ public KafkaConsumerService(EmailService emailService) { public void listen(UserEvent event) { log.info("Получено событие из Kafka: {}", event); String email = event.getEmail(); - String eventType = event.getEventType(); - if (event == null || event.getEmail() == null || event.getEventType() == null) { - log.warn("Пропущено сообщение: event, email или eventType = null"); + String subject = event.getSubject(); + if (event == null || event.getEmail() == null || event.getEventType() == null || event.getSubject() == null) { + log.warn("Пропущено сообщение: один из параметров null"); return; } @@ -36,7 +36,7 @@ public void listen(UserEvent event) { }; try { - emailService.sendEmail(email, text); + emailService.sendEmail(email, subject, text); log.info("Email успешно отправлен на {}", event.getEmail()); } catch (Exception e) { log.error("Ошибка при отправке email на {}: {}", event.getEmail(), e.getMessage(), e); diff --git a/Homework/src/main/java/ru/project/iakov/homework2/service/EmailService.java b/Homework/src/main/java/ru/project/iakov/homework2/service/EmailService.java index 6946fb0..599a329 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/service/EmailService.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/service/EmailService.java @@ -1,11 +1,13 @@ package ru.project.iakov.homework2.service; +import jakarta.mail.internet.MimeMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; @Service @@ -22,13 +24,15 @@ public EmailService(JavaMailSender mailSender) { this.mailSender = mailSender; } - public void sendEmail(String to, String text) { + public void sendEmail(String to, String subject, String text) { try { - SimpleMailMessage message = new SimpleMailMessage(); - message.setFrom(fromEmail); - message.setTo(to); - message.setText(text); - mailSender.send(message); + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, false, "utf-8"); + helper.setFrom(fromEmail); + helper.setTo(to); + helper.setSubject(subject); + helper.setText(text); + mailSender.send(mimeMessage); } catch (Exception e) { log.error("Ошибка при отправке email на {}: {}", to, e.getMessage(), e); throw new RuntimeException("Не удалось отправить email", e); diff --git a/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java b/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java index 7e8bac6..4f66ab5 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java @@ -28,7 +28,11 @@ public UserService(UserRepository userRepository, UserMapper userMapper, KafkaPr public UserDto createUser(UserDto userDto) { User user = userMapper.toEntity(userDto); User savedUser = userRepository.save(user); - kafkaProducer.sendUserEvent(new UserEvent(savedUser.getEmail(), "CREATED")); + kafkaProducer.sendUserEvent( + new UserEvent( + savedUser.getEmail(), + "Уведомление", + "CREATED")); return userMapper.toDto(savedUser); } public UserDto findById(Long id) { @@ -54,6 +58,10 @@ public void deleteUser(Long id) { User user = userRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("User not found")); userRepository.deleteById(id); - kafkaProducer.sendUserEvent(new UserEvent(user.getEmail(), "DELETED")); + kafkaProducer.sendUserEvent( + new UserEvent( + user.getEmail(), + "Уведомление", + "DELETED")); } } \ No newline at end of file From c19cfd93720985bd6510b21bb414c375b127cf54 Mon Sep 17 00:00:00 2001 From: Iakov Lysenko Date: Sun, 15 Jun 2025 11:18:50 +0300 Subject: [PATCH 4/8] =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EmailControllerTest.java | 39 +++++++++++++++ .../{ => controller}/UserControllerTest.java | 3 +- .../homework2/kafka/KafkaConsumerTest.java | 50 +++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 Homework/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java rename Homework/src/test/java/ru/project/iakov/homework2/{ => controller}/UserControllerTest.java (98%) create mode 100644 Homework/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java diff --git a/Homework/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java b/Homework/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java new file mode 100644 index 0000000..ae09553 --- /dev/null +++ b/Homework/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java @@ -0,0 +1,39 @@ +package ru.project.iakov.homework2.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import ru.project.iakov.homework2.dto.EmailRequest; +import ru.project.iakov.homework2.service.EmailService; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc +public class EmailControllerTest { + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private EmailService emailService; + + @Test + public void testSendEmail_success() throws Exception { + EmailRequest request = new EmailRequest("lysenko_iakov@yahoo.com", "Тест", "Тест"); + + mockMvc.perform(post("/api/v1/email/send") + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString(request))) + .andExpect(status().isOk()); + + verify(emailService, times(1)).sendEmail("lysenko_iakov@yahoo.com", "Тест", "Тест"); + } +} \ No newline at end of file diff --git a/Homework/src/test/java/ru/project/iakov/homework2/UserControllerTest.java b/Homework/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java similarity index 98% rename from Homework/src/test/java/ru/project/iakov/homework2/UserControllerTest.java rename to Homework/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java index 2efe139..4873e5d 100644 --- a/Homework/src/test/java/ru/project/iakov/homework2/UserControllerTest.java +++ b/Homework/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java @@ -1,4 +1,4 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.controller; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.DisplayName; @@ -8,7 +8,6 @@ import org.springframework.http.MediaType; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; -import ru.project.iakov.homework2.controller.UserController; import ru.project.iakov.homework2.dto.UserDto; import ru.project.iakov.homework2.service.UserService; diff --git a/Homework/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java b/Homework/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java new file mode 100644 index 0000000..e2854d5 --- /dev/null +++ b/Homework/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java @@ -0,0 +1,50 @@ +package ru.project.iakov.homework2.kafka; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import ru.project.iakov.homework2.dto.UserEvent; +import ru.project.iakov.homework2.service.EmailService; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@AutoConfigureMockMvc +@SpringBootTest +public class KafkaConsumerTest { + + @Autowired + private KafkaConsumerService kafkaConsumer; + + @MockitoBean + private EmailService emailService; + + @Test + public void testKafkaConsumer_sendsEmailOnCreatedEvent() { + UserEvent event = UserEvent.builder() + .email("lysenko_iakov@yahoo.com") + .subject("Тест: создание") + .eventType("CREATED") + .build(); + + kafkaConsumer.listen(event); + + verify(emailService, times(1)).sendEmail(eq("lysenko_iakov@yahoo.com"), anyString(), contains("создан")); + } + + @Test + public void testKafkaConsumer_sendsEmailOnDeletedEvent() { + UserEvent event = UserEvent.builder() + .email("lysenko_iakov@yahoo.com") + .subject("Тест: удаление") + .eventType("DELETED") + .build(); + + kafkaConsumer.listen(event); + + verify(emailService, times(1)).sendEmail(eq("lysenko_iakov@yahoo.com"), anyString(), contains("удалён")); + } +} \ No newline at end of file From 679527c3a23e11240a41461b21f55c0776fdb5a4 Mon Sep 17 00:00:00 2001 From: Iakov Lysenko Date: Sat, 21 Jun 2025 09:57:56 +0300 Subject: [PATCH 5/8] =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8C=20?= =?UTF-8?q?=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20objectMapper=20=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20@NonNu?= =?UTF-8?q?ll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/iakov/homework2/dto/UserDto.java | 5 +++++ .../iakov/homework2/dto/UserEvent.java | 3 +++ .../service/KafkaProducerService.java | 22 +++++++++++++++---- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserDto.java b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserDto.java index 7cfa044..b2f7c53 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserDto.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserDto.java @@ -10,9 +10,14 @@ public class UserDto { @Getter @Setter + @NonNull private Long id; + @NonNull private String name; + @NonNull private String email; + @NonNull private Integer age; + @NonNull private LocalDateTime createdAt; } \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java index 7ac0e2c..50c5e26 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java @@ -7,7 +7,10 @@ @NoArgsConstructor @Builder public class UserEvent { + @NonNull private String email; + @NonNull private String subject; + @NonNull private String eventType; } diff --git a/Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java b/Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java index 64ea6fc..1974327 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java @@ -1,5 +1,8 @@ package ru.project.iakov.homework2.service; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.kafka.core.KafkaTemplate; @@ -9,15 +12,26 @@ @Service public class KafkaProducerService { private static final Logger LOGGER = LoggerFactory.getLogger(KafkaProducerService.class); - private final KafkaTemplate kafkaTemplate; + private final KafkaTemplate kafkaTemplate; + private final ObjectMapper objectMapper; private static final String TOPIC = "user-events"; - public KafkaProducerService(KafkaTemplate kafkaTemplate) { + public KafkaProducerService(KafkaTemplate kafkaTemplate, ObjectMapper objectMapper) { this.kafkaTemplate = kafkaTemplate; + this.objectMapper = objectMapper; } public void sendUserEvent(UserEvent event) { - LOGGER.info(String.format("Сообщение: %s", event)); - kafkaTemplate.send(TOPIC, event); + try { + String message = objectMapper.writeValueAsString(event); + LOGGER.info(String.format("Сообщение: %s", message)); + kafkaTemplate.send(TOPIC, message); + } catch (JsonProcessingException e) { + LOGGER.error("Ошибка сериализации UserEvent: " + e.getMessage()); + } + + + + } } \ No newline at end of file From 49f065e250acb962d4c64ea306adf2f14c199525 Mon Sep 17 00:00:00 2001 From: Iakov Lysenko Date: Sat, 21 Jun 2025 10:19:07 +0300 Subject: [PATCH 6/8] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../homework2/config/KafkaTopicConfig.java | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 Homework/src/main/java/ru/project/iakov/homework2/config/KafkaTopicConfig.java diff --git a/Homework/src/main/java/ru/project/iakov/homework2/config/KafkaTopicConfig.java b/Homework/src/main/java/ru/project/iakov/homework2/config/KafkaTopicConfig.java deleted file mode 100644 index 84b8109..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/config/KafkaTopicConfig.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.project.iakov.homework2.config; - -import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; -import org.springframework.kafka.support.serializer.JsonDeserializer; -import org.apache.kafka.common.serialization.StringDeserializer; -import org.apache.kafka.clients.admin.NewTopic; -import org.apache.kafka.clients.consumer.ConsumerConfig; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.kafka.core.ConsumerFactory; -import ru.project.iakov.homework2.dto.UserEvent; -import org.springframework.kafka.core.DefaultKafkaConsumerFactory; - -import java.util.HashMap; -import java.util.Map; - -@Configuration -public class KafkaTopicConfig { - @Bean - public NewTopic userEventsTopic() { - return new NewTopic("user-events", 1, (short) 1); - } - - @Bean - public ConsumerFactory consumerFactory() { - JsonDeserializer jsonDeserializer = new JsonDeserializer<>(UserEvent.class); - jsonDeserializer.setRemoveTypeHeaders(false); - jsonDeserializer.addTrustedPackages("*"); - jsonDeserializer.setUseTypeMapperForKey(true); - - Map props = new HashMap<>(); - props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); - props.put(ConsumerConfig.GROUP_ID_CONFIG, "notification-group"); - props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); - - return new DefaultKafkaConsumerFactory<>( - props, - new StringDeserializer(), - jsonDeserializer - ); - } - - @Bean - public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { - ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); - factory.setConsumerFactory(consumerFactory()); - return factory; - } -} \ No newline at end of file From 69e37061e76a48b6e46b7d6524d1df3724f17f5b Mon Sep 17 00:00:00 2001 From: Iakov Lysenko Date: Sat, 21 Jun 2025 10:26:39 +0300 Subject: [PATCH 7/8] =?UTF-8?q?enum=20=D1=81=20CREATED=20=D0=B8=20DELETED?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=B1=D0=B5=D0=B7=D0=BE=D0=BF=D0=B0?= =?UTF-8?q?=D1=81=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../homework2/controller/NotificationController.java | 2 ++ .../ru/project/iakov/homework2/dto/UserEvent.java | 3 ++- .../iakov/homework2/kafka/KafkaConsumerService.java | 12 ++---------- .../ru/project/iakov/homework2/model/EventType.java | 6 ++++++ 4 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 Homework/src/main/java/ru/project/iakov/homework2/model/EventType.java diff --git a/Homework/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java b/Homework/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java index 4a4120f..f1ebb16 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java @@ -2,6 +2,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import ru.project.iakov.homework2.dto.UserEvent; import ru.project.iakov.homework2.service.KafkaProducerService; @@ -9,6 +10,7 @@ @Slf4j @RestController @RequestMapping("/api/v1/kafka") +@Validated public class NotificationController { private final KafkaProducerService kafkaProducer; diff --git a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java index 50c5e26..598398a 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java @@ -1,6 +1,7 @@ package ru.project.iakov.homework2.dto; import lombok.*; +import ru.project.iakov.homework2.model.EventType; @Data @AllArgsConstructor @@ -12,5 +13,5 @@ public class UserEvent { @NonNull private String subject; @NonNull - private String eventType; + private EventType eventType; } diff --git a/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java b/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java index 0175daf..2a8c007 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java +++ b/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java @@ -21,18 +21,10 @@ public void listen(UserEvent event) { log.info("Получено событие из Kafka: {}", event); String email = event.getEmail(); String subject = event.getSubject(); - if (event == null || event.getEmail() == null || event.getEventType() == null || event.getSubject() == null) { - log.warn("Пропущено сообщение: один из параметров null"); - return; - } String text = switch (event.getEventType()) { - case "CREATED" -> "Здравствуйте! Ваш аккаунт был создан."; - case "DELETED" -> "Здравствуйте! Ваш аккаунт был удалён."; - default -> { - log.warn("Неизвестный тип события: {}", event.getEventType()); - yield "Неизвестное событие."; - } + case CREATED -> "Здравствуйте! Ваш аккаунт был создан."; + case DELETED -> "Здравствуйте! Ваш аккаунт был удалён."; }; try { diff --git a/Homework/src/main/java/ru/project/iakov/homework2/model/EventType.java b/Homework/src/main/java/ru/project/iakov/homework2/model/EventType.java new file mode 100644 index 0000000..542ed90 --- /dev/null +++ b/Homework/src/main/java/ru/project/iakov/homework2/model/EventType.java @@ -0,0 +1,6 @@ +package ru.project.iakov.homework2.model; + +public enum EventType { + CREATED, + DELETED +} From 640ce8f906917c5cb6036f0bc0502bce39de07b6 Mon Sep 17 00:00:00 2001 From: Iakov Lysenko Date: Sun, 22 Jun 2025 17:34:25 +0300 Subject: [PATCH 8/8] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20=D1=87=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=8C=20notification=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../homework2/kafka/KafkaConsumerService.java | 37 ---------- .../service/KafkaProducerService.java | 37 ---------- notification-service/pom.xml | 43 +++++++++++ .../NotificationServiceApplication.java | 13 ++++ .../controller/EmailController.java | 16 +++-- .../dto/EmailRequest.java | 7 +- .../notificationservice/dto/UserEvent.java | 15 ++++ .../notificationservice/model/EventType.java | 6 ++ .../service/EmailService.java | 11 +-- .../service/KafkaConsumerService.java | 39 ++++++++++ .../src/main/resources/application.yml | 24 +++++++ {Homework => user-service}/pom.xml | 2 +- .../homework2/UserServiceApplication.java | 0 .../controller/NotificationController.java | 20 ++++-- .../homework2/controller/UserController.java | 0 .../project/iakov/homework2/dto/UserDto.java | 2 - .../iakov/homework2/dto/UserEvent.java | 8 +-- .../project/iakov/homework2/entity/User.java | 6 ++ .../iakov/homework2/mapper/UserMapper.java | 0 .../iakov/homework2/model/EventType.java | 2 +- .../homework2/repository/UserRepository.java | 0 .../service/KafkaProducerService.java | 20 ++++++ .../iakov/homework2/service/UserService.java | 72 ++++++++++++------- .../src/main/resources/application.yml | 11 +-- .../src/main/resources/hibernate.cfg.xml | 0 .../src/main/resources/log4j.properties | 0 .../controller/EmailControllerTest.java | 0 .../controller/UserControllerTest.java | 0 .../homework2/kafka/KafkaConsumerTest.java | 0 29 files changed, 252 insertions(+), 139 deletions(-) delete mode 100644 Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java delete mode 100644 Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java create mode 100644 notification-service/pom.xml create mode 100644 notification-service/src/main/java/ru/project/iakov/notificationservice/NotificationServiceApplication.java rename {Homework/src/main/java/ru/project/iakov/homework2 => notification-service/src/main/java/ru/project/iakov/notificationservice}/controller/EmailController.java (52%) rename {Homework/src/main/java/ru/project/iakov/homework2 => notification-service/src/main/java/ru/project/iakov/notificationservice}/dto/EmailRequest.java (50%) create mode 100644 notification-service/src/main/java/ru/project/iakov/notificationservice/dto/UserEvent.java create mode 100644 notification-service/src/main/java/ru/project/iakov/notificationservice/model/EventType.java rename {Homework/src/main/java/ru/project/iakov/homework2 => notification-service/src/main/java/ru/project/iakov/notificationservice}/service/EmailService.java (80%) create mode 100644 notification-service/src/main/java/ru/project/iakov/notificationservice/service/KafkaConsumerService.java create mode 100644 notification-service/src/main/resources/application.yml rename {Homework => user-service}/pom.xml (97%) rename {Homework => user-service}/src/main/java/ru/project/iakov/homework2/UserServiceApplication.java (100%) rename {Homework => user-service}/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java (53%) rename {Homework => user-service}/src/main/java/ru/project/iakov/homework2/controller/UserController.java (100%) rename {Homework => user-service}/src/main/java/ru/project/iakov/homework2/dto/UserDto.java (93%) rename {Homework => user-service}/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java (69%) rename {Homework => user-service}/src/main/java/ru/project/iakov/homework2/entity/User.java (81%) rename {Homework => user-service}/src/main/java/ru/project/iakov/homework2/mapper/UserMapper.java (100%) rename {Homework => user-service}/src/main/java/ru/project/iakov/homework2/model/EventType.java (97%) rename {Homework => user-service}/src/main/java/ru/project/iakov/homework2/repository/UserRepository.java (100%) create mode 100644 user-service/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java rename {Homework => user-service}/src/main/java/ru/project/iakov/homework2/service/UserService.java (53%) rename {Homework => user-service}/src/main/resources/application.yml (70%) rename {Homework => user-service}/src/main/resources/hibernate.cfg.xml (100%) rename {Homework => user-service}/src/main/resources/log4j.properties (100%) rename {Homework => user-service}/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java (100%) rename {Homework => user-service}/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java (100%) rename {Homework => user-service}/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java (100%) diff --git a/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java b/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java deleted file mode 100644 index 2a8c007..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/kafka/KafkaConsumerService.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.project.iakov.homework2.kafka; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.stereotype.Component; -import ru.project.iakov.homework2.dto.UserEvent; -import ru.project.iakov.homework2.service.EmailService; - -@Component -@Slf4j -public class KafkaConsumerService { - - private final EmailService emailService; - - public KafkaConsumerService(EmailService emailService) { - this.emailService = emailService; - } - - @KafkaListener(topics = "user-events", groupId = "notification-group") - public void listen(UserEvent event) { - log.info("Получено событие из Kafka: {}", event); - String email = event.getEmail(); - String subject = event.getSubject(); - - String text = switch (event.getEventType()) { - case CREATED -> "Здравствуйте! Ваш аккаунт был создан."; - case DELETED -> "Здравствуйте! Ваш аккаунт был удалён."; - }; - - try { - emailService.sendEmail(email, subject, text); - log.info("Email успешно отправлен на {}", event.getEmail()); - } catch (Exception e) { - log.error("Ошибка при отправке email на {}: {}", event.getEmail(), e.getMessage(), e); - } - } -} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java b/Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java deleted file mode 100644 index 1974327..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.project.iakov.homework2.service; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.stereotype.Service; -import ru.project.iakov.homework2.dto.UserEvent; - -@Service -public class KafkaProducerService { - private static final Logger LOGGER = LoggerFactory.getLogger(KafkaProducerService.class); - private final KafkaTemplate kafkaTemplate; - private final ObjectMapper objectMapper; - private static final String TOPIC = "user-events"; - - public KafkaProducerService(KafkaTemplate kafkaTemplate, ObjectMapper objectMapper) { - this.kafkaTemplate = kafkaTemplate; - this.objectMapper = objectMapper; - } - - public void sendUserEvent(UserEvent event) { - try { - String message = objectMapper.writeValueAsString(event); - LOGGER.info(String.format("Сообщение: %s", message)); - kafkaTemplate.send(TOPIC, message); - } catch (JsonProcessingException e) { - LOGGER.error("Ошибка сериализации UserEvent: " + e.getMessage()); - } - - - - - } -} \ No newline at end of file diff --git a/notification-service/pom.xml b/notification-service/pom.xml new file mode 100644 index 0000000..b9d3ef4 --- /dev/null +++ b/notification-service/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + ru.project.iakov + user-service + 1.0-SNAPSHOT + + 24 + 24 + UTF-8 + + + + org.projectlombok + lombok + 1.18.38 + provided + + + org.springframework.boot + spring-boot-starter-web + 3.5.0 + + + org.springframework.boot + spring-boot-starter-test + 3.5.0 + test + + + org.springframework.kafka + spring-kafka + 3.3.6 + + + org.springframework.boot + spring-boot-starter-mail + 3.4.6 + + + \ No newline at end of file diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/NotificationServiceApplication.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/NotificationServiceApplication.java new file mode 100644 index 0000000..c8f0a56 --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/NotificationServiceApplication.java @@ -0,0 +1,13 @@ +package ru.project.iakov.notificationservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class NotificationServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(NotificationServiceApplication.class, args); + } + +} diff --git a/Homework/src/main/java/ru/project/iakov/homework2/controller/EmailController.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/controller/EmailController.java similarity index 52% rename from Homework/src/main/java/ru/project/iakov/homework2/controller/EmailController.java rename to notification-service/src/main/java/ru/project/iakov/notificationservice/controller/EmailController.java index b52f81a..adb42d0 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/controller/EmailController.java +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/controller/EmailController.java @@ -1,11 +1,13 @@ -package ru.project.iakov.homework2.controller; +package ru.project.iakov.notificationservice.controller; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.project.iakov.homework2.dto.EmailRequest; -import ru.project.iakov.homework2.service.EmailService; +import ru.project.iakov.notificationservice.dto.EmailRequest; +import ru.project.iakov.notificationservice.model.EventType; +import ru.project.iakov.notificationservice.service.EmailService; + @RestController @RequestMapping("/api/v1/email") @@ -17,11 +19,17 @@ public class EmailController { @PostMapping("/send") public ResponseEntity sendEmail(@RequestBody EmailRequest request) { try { - emailService.sendEmail(request.getEmail(), "Тест", "Тестовое сообщение"); + emailService.sendEmail(request.getEmail(), request.getSubject(), generateText(request.getEventType())); return ResponseEntity.ok("Письмо отправлено"); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Ошибка при отправке письма: " + e.getMessage()); } } + private String generateText(EventType eventType) { + return switch (eventType) { + case CREATED -> "Здравствуйте! Ваш аккаунт был создан."; + case DELETED -> "Здравствуйте! Ваш аккаунт был удалён."; + }; + } } \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/dto/EmailRequest.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/EmailRequest.java similarity index 50% rename from Homework/src/main/java/ru/project/iakov/homework2/dto/EmailRequest.java rename to notification-service/src/main/java/ru/project/iakov/notificationservice/dto/EmailRequest.java index 43b4d00..ae59c07 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/dto/EmailRequest.java +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/EmailRequest.java @@ -1,6 +1,7 @@ -package ru.project.iakov.homework2.dto; +package ru.project.iakov.notificationservice.dto; import lombok.*; +import ru.project.iakov.notificationservice.model.EventType; @Data @NoArgsConstructor @@ -8,5 +9,5 @@ public class EmailRequest { private String email; private String subject; - private String eventType; -} + private EventType eventType; +} \ No newline at end of file diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/UserEvent.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/UserEvent.java new file mode 100644 index 0000000..232df3b --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/UserEvent.java @@ -0,0 +1,15 @@ +package ru.project.iakov.notificationservice.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.*; +import ru.project.iakov.notificationservice.model.EventType; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +@Builder +public class UserEvent { + private String email; + private EventType eventType; +} \ No newline at end of file diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/model/EventType.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/model/EventType.java new file mode 100644 index 0000000..b34bd25 --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/model/EventType.java @@ -0,0 +1,6 @@ +package ru.project.iakov.notificationservice.model; + +public enum EventType { + CREATED, + DELETED +} diff --git a/Homework/src/main/java/ru/project/iakov/homework2/service/EmailService.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/service/EmailService.java similarity index 80% rename from Homework/src/main/java/ru/project/iakov/homework2/service/EmailService.java rename to notification-service/src/main/java/ru/project/iakov/notificationservice/service/EmailService.java index 599a329..c81a712 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/service/EmailService.java +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/service/EmailService.java @@ -1,16 +1,16 @@ -package ru.project.iakov.homework2.service; +package ru.project.iakov.notificationservice.service; import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; @Service +@RequiredArgsConstructor @Slf4j public class EmailService { @@ -19,11 +19,6 @@ public class EmailService { @Value("${spring.mail.username}") private String fromEmail; - @Autowired - public EmailService(JavaMailSender mailSender) { - this.mailSender = mailSender; - } - public void sendEmail(String to, String subject, String text) { try { MimeMessage mimeMessage = mailSender.createMimeMessage(); diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/service/KafkaConsumerService.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/service/KafkaConsumerService.java new file mode 100644 index 0000000..08de591 --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/service/KafkaConsumerService.java @@ -0,0 +1,39 @@ +package ru.project.iakov.notificationservice.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; +import ru.project.iakov.notificationservice.dto.UserEvent; +import ru.project.iakov.notificationservice.model.EventType; + +@Component +@RequiredArgsConstructor +@Slf4j +public class KafkaConsumerService { + + private final EmailService emailService; + private final ObjectMapper objectMapper; + + @KafkaListener(topics = "user-events", groupId = "notification-group") + public void listen(ConsumerRecord record) { + String message = record.value(); + log.info("Kafka message: {}", message); + try { + UserEvent event = objectMapper.readValue(message, UserEvent.class); + log.info("Получено сообщение из Kafka: {}", event); + emailService.sendEmail(event.getEmail(), "Уведомление", generateText(event.getEventType())); + } catch (Exception e) { + log.error("Ошибка обработки сообщения: {}", e.getMessage(), e); + } + } + + private String generateText(EventType eventType) { + return switch (eventType) { + case CREATED -> "Здравствуйте! Ваш аккаунт был создан."; + case DELETED -> "Здравствуйте! Ваш аккаунт был удалён."; + }; + } +} \ No newline at end of file diff --git a/notification-service/src/main/resources/application.yml b/notification-service/src/main/resources/application.yml new file mode 100644 index 0000000..be161d0 --- /dev/null +++ b/notification-service/src/main/resources/application.yml @@ -0,0 +1,24 @@ +spring: + kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: notification-group + auto-offset-reset: earliest + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + + mail: + host: smtp.mail.ru + port: 587 + username: ${EMAIL_USERNAME} + password: ${EMAIL_PASSWORD} + properties: + mail.smtp.auth: true + mail.smtp.starttls.enable: true + +logging: + level: + root: INFO + +server: + port: 8081 \ No newline at end of file diff --git a/Homework/pom.xml b/user-service/pom.xml similarity index 97% rename from Homework/pom.xml rename to user-service/pom.xml index 737bc02..045cabe 100644 --- a/Homework/pom.xml +++ b/user-service/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 ru.project.iakov - Homework + user-service 1.0-SNAPSHOT 24 diff --git a/Homework/src/main/java/ru/project/iakov/homework2/UserServiceApplication.java b/user-service/src/main/java/ru/project/iakov/homework2/UserServiceApplication.java similarity index 100% rename from Homework/src/main/java/ru/project/iakov/homework2/UserServiceApplication.java rename to user-service/src/main/java/ru/project/iakov/homework2/UserServiceApplication.java diff --git a/Homework/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java b/user-service/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java similarity index 53% rename from Homework/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java rename to user-service/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java index f1ebb16..fe58216 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java @@ -1,5 +1,8 @@ package ru.project.iakov.homework2.controller; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -10,18 +13,23 @@ @Slf4j @RestController @RequestMapping("/api/v1/kafka") +@RequiredArgsConstructor @Validated public class NotificationController { - private final KafkaProducerService kafkaProducer; - public NotificationController(KafkaProducerService kafkaProducer) { - this.kafkaProducer = kafkaProducer; - } + private final KafkaProducerService kafkaProducer; + private final ObjectMapper objectMapper; @PostMapping("/publish") public ResponseEntity send(@RequestBody UserEvent request) { log.info("Получен запрос на публикацию события: {}", request); - kafkaProducer.sendUserEvent(request); - return ResponseEntity.ok().build(); + try { + String message = objectMapper.writeValueAsString(request); + kafkaProducer.sendUserEvent(message); + return ResponseEntity.ok().build(); + } catch (JsonProcessingException e) { + log.error("Ошибка сериализации события: {}", e.getMessage(), e); + return ResponseEntity.internalServerError().build(); + } } } \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/controller/UserController.java b/user-service/src/main/java/ru/project/iakov/homework2/controller/UserController.java similarity index 100% rename from Homework/src/main/java/ru/project/iakov/homework2/controller/UserController.java rename to user-service/src/main/java/ru/project/iakov/homework2/controller/UserController.java diff --git a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserDto.java b/user-service/src/main/java/ru/project/iakov/homework2/dto/UserDto.java similarity index 93% rename from Homework/src/main/java/ru/project/iakov/homework2/dto/UserDto.java rename to user-service/src/main/java/ru/project/iakov/homework2/dto/UserDto.java index b2f7c53..7660dce 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserDto.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/dto/UserDto.java @@ -10,7 +10,6 @@ public class UserDto { @Getter @Setter - @NonNull private Long id; @NonNull private String name; @@ -18,6 +17,5 @@ public class UserDto { private String email; @NonNull private Integer age; - @NonNull private LocalDateTime createdAt; } \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java b/user-service/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java similarity index 69% rename from Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java rename to user-service/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java index 598398a..e610159 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java @@ -1,5 +1,6 @@ package ru.project.iakov.homework2.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.*; import ru.project.iakov.homework2.model.EventType; @@ -7,11 +8,8 @@ @AllArgsConstructor @NoArgsConstructor @Builder +@JsonIgnoreProperties(ignoreUnknown = true) public class UserEvent { - @NonNull private String email; - @NonNull - private String subject; - @NonNull private EventType eventType; -} +} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/entity/User.java b/user-service/src/main/java/ru/project/iakov/homework2/entity/User.java similarity index 81% rename from Homework/src/main/java/ru/project/iakov/homework2/entity/User.java rename to user-service/src/main/java/ru/project/iakov/homework2/entity/User.java index 9e0ad6e..c24753b 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/entity/User.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/entity/User.java @@ -24,4 +24,10 @@ public class User { @Column(nullable = false) private int age; private LocalDateTime createdAt; + @PrePersist + public void prePersist() { + if (createdAt == null) { + createdAt = LocalDateTime.now(); + } + } } \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/mapper/UserMapper.java b/user-service/src/main/java/ru/project/iakov/homework2/mapper/UserMapper.java similarity index 100% rename from Homework/src/main/java/ru/project/iakov/homework2/mapper/UserMapper.java rename to user-service/src/main/java/ru/project/iakov/homework2/mapper/UserMapper.java diff --git a/Homework/src/main/java/ru/project/iakov/homework2/model/EventType.java b/user-service/src/main/java/ru/project/iakov/homework2/model/EventType.java similarity index 97% rename from Homework/src/main/java/ru/project/iakov/homework2/model/EventType.java rename to user-service/src/main/java/ru/project/iakov/homework2/model/EventType.java index 542ed90..897bd29 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/model/EventType.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/model/EventType.java @@ -3,4 +3,4 @@ public enum EventType { CREATED, DELETED -} +} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/repository/UserRepository.java b/user-service/src/main/java/ru/project/iakov/homework2/repository/UserRepository.java similarity index 100% rename from Homework/src/main/java/ru/project/iakov/homework2/repository/UserRepository.java rename to user-service/src/main/java/ru/project/iakov/homework2/repository/UserRepository.java diff --git a/user-service/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java b/user-service/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java new file mode 100644 index 0000000..8671267 --- /dev/null +++ b/user-service/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java @@ -0,0 +1,20 @@ +package ru.project.iakov.homework2.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class KafkaProducerService { + + private final KafkaTemplate kafkaTemplate; + private static final String TOPIC = "user-events"; + + public void sendUserEvent(String message) { + log.info("Отправка сообщения в Kafka: {}", message); + kafkaTemplate.send(TOPIC, message); + } +} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java b/user-service/src/main/java/ru/project/iakov/homework2/service/UserService.java similarity index 53% rename from Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java rename to user-service/src/main/java/ru/project/iakov/homework2/service/UserService.java index 4f66ab5..3de3493 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/service/UserService.java @@ -1,67 +1,85 @@ package ru.project.iakov.homework2.service; -import org.springframework.beans.factory.annotation.Autowired; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ru.project.iakov.homework2.dto.UserDto; -import ru.project.iakov.homework2.entity.User; import ru.project.iakov.homework2.dto.UserEvent; +import ru.project.iakov.homework2.entity.User; import ru.project.iakov.homework2.mapper.UserMapper; +import ru.project.iakov.homework2.model.EventType; import ru.project.iakov.homework2.repository.UserRepository; -import java.io.Serializable; +import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @Service -public class UserService implements Serializable { - private UserRepository userRepository; - private UserMapper userMapper; - private KafkaProducerService kafkaProducer; - - @Autowired - public UserService(UserRepository userRepository, UserMapper userMapper, KafkaProducerService kafkaProducer) { - this.userRepository = userRepository; - this.userMapper = userMapper; - this.kafkaProducer = kafkaProducer; - } +@RequiredArgsConstructor +public class UserService { + private final UserRepository userRepository; + private final UserMapper userMapper; + private final KafkaProducerService kafkaProducer; + private final ObjectMapper objectMapper; + + @Transactional public UserDto createUser(UserDto userDto) { User user = userMapper.toEntity(userDto); User savedUser = userRepository.save(user); - kafkaProducer.sendUserEvent( - new UserEvent( - savedUser.getEmail(), - "Уведомление", - "CREATED")); + + sendKafkaEvent(savedUser.getEmail(), EventType.CREATED); + return userMapper.toDto(savedUser); } + public UserDto findById(Long id) { User user = userRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException()); + .orElseThrow(IllegalArgumentException::new); + return userMapper.toDto(user); } + public List findAll() { List users = userRepository.findAll(); + return users.stream() .map(userMapper::toDto) .collect(Collectors.toList()); } + public UserDto updateUser(Long id, UserDto userDto) { User existingUser = userRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException()); + .orElseThrow(IllegalArgumentException::new); + existingUser.setName(userDto.getName()); existingUser.setEmail(userDto.getEmail()); + User updatedUser = userRepository.save(existingUser); + return userMapper.toDto(updatedUser); } + + @Transactional public void deleteUser(Long id) { User user = userRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("User not found")); + .orElseThrow(() -> new IllegalArgumentException("Пользователь не найден")); + userRepository.deleteById(id); - kafkaProducer.sendUserEvent( - new UserEvent( - user.getEmail(), - "Уведомление", - "DELETED")); + + sendKafkaEvent(user.getEmail(), EventType.DELETED); + } + + private void sendKafkaEvent(String email, EventType eventType) { + try { + UserEvent event = new UserEvent(email, eventType); + String json = objectMapper.writeValueAsString(event); + + kafkaProducer.sendUserEvent(json); + } catch (JsonProcessingException e) { + System.err.println("Ошибка сериализации события Kafka: " + e.getMessage()); + } } } \ No newline at end of file diff --git a/Homework/src/main/resources/application.yml b/user-service/src/main/resources/application.yml similarity index 70% rename from Homework/src/main/resources/application.yml rename to user-service/src/main/resources/application.yml index 0b5f9b1..39d62b0 100644 --- a/Homework/src/main/resources/application.yml +++ b/user-service/src/main/resources/application.yml @@ -28,15 +28,10 @@ spring: consumer: group-id: notification-group auto-offset-reset: earliest - admin: - auto-create: true key-deserializer: org.apache.kafka.common.serialization.StringDeserializer - value-serializer: org.springframework.kafka.support.serializer.JsonSerializer - properties: - spring.json.trusted.packages: '*' - + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer producer: - key-serializer: org.apache.kafka.common.serialization.StringSerializer - value-serializer: org.springframework.kafka.support.serializer.JsonSerializer + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer server: port: 8080 \ No newline at end of file diff --git a/Homework/src/main/resources/hibernate.cfg.xml b/user-service/src/main/resources/hibernate.cfg.xml similarity index 100% rename from Homework/src/main/resources/hibernate.cfg.xml rename to user-service/src/main/resources/hibernate.cfg.xml diff --git a/Homework/src/main/resources/log4j.properties b/user-service/src/main/resources/log4j.properties similarity index 100% rename from Homework/src/main/resources/log4j.properties rename to user-service/src/main/resources/log4j.properties diff --git a/Homework/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java b/user-service/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java similarity index 100% rename from Homework/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java rename to user-service/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java diff --git a/Homework/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java b/user-service/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java similarity index 100% rename from Homework/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java rename to user-service/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java diff --git a/Homework/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java b/user-service/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java similarity index 100% rename from Homework/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java rename to user-service/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java