diff --git a/2024-01/spring-34-rabbit/README.md b/2024-01/spring-34-rabbit/README.md new file mode 100644 index 00000000..9ce1271d --- /dev/null +++ b/2024-01/spring-34-rabbit/README.md @@ -0,0 +1,41 @@ +## Пример взаимодействия приложений через RabbitMQ + +В примере демонстрируется: + +* *использование `@Scheduled` таймера для имитации пользовательской активности* +* *оправка и прием сообщений между двумя приложениями через RabbitMQ* +* *создание и отправка email сообщений средствами SpringMail* +* *аггрегация данных с помощью SpringData/JPQL* +* *создание кастомного endpoint-а actuator-а для вывода статистики* + +Описание примера: + +user-activity-emitter-microservice: + +* *по таймеру `UserActivityEmitterService` достает из БД случайный тип активности и пользователя* +* *после чего формирует из них объект активности (`UserActivity`) и отправляет в очередь сообщений RabbitMQ с + помощью `RabbitTemplate` + настроенный на работу с "main-exchange"* +* *большее число активностей имеют `routingKey` = "user.activity.message.simple" * +* *активности, у которых в названии типа есть вхождение "Вредн" имееют свой `routingKey` (" + user.activity.message.important") * +* *так же приложение по таймеру, с помощью `ActivityStatCalculationEmitterSerivce` инициирует подсчет статистики с + помощью отправки + сообщения в очередь RabbitMQ c `routingKey` = "user.activity.stat"* + +user-activity-processor-microservice: + +* *в приложении для обработки сообщений есть несколько очередей, куда попадают сообщения в зависимости от `routingKey`* +* *их прослушивает компонент `RabbitMqListener`, который содержит по методу на каждую очередь"* +* *в "all-activity-queue" попадают все активности. На выходе из данной очереди активности сохраняются в БД* +* *в "important-activity-queue" попадают важные активности. На выходе из данной очереди активности преобразуются в + письма и отправляются на + почту администратору* +* *в "stat-calc-commands-queue" попадают команды, которые инициируют расчет статистики. * +* *в методе-обработчике сообщений данной очереди происходит удаление старых статистических данных, а так же подсчет и + сохранение в БД новых* +* *за вывод статистических данных отвечает кастомный endpoint actuator-а `ActivityStatEndpoint`* + +Для работы приложений требуется работающий RabbitMQ. За его запуск отвечает docker-compose.yml +Адрес консоли RabbitMQ: http://localhost:15672/ +Логин и пароль: guest \ No newline at end of file diff --git a/2024-01/spring-34-rabbit/docker-compose.yml b/2024-01/spring-34-rabbit/docker-compose.yml new file mode 100644 index 00000000..2bdf971a --- /dev/null +++ b/2024-01/spring-34-rabbit/docker-compose.yml @@ -0,0 +1,7 @@ +version: '3' +services: + rabbitmq: + image: rabbitmq:management + ports: + - "5672:5672" + - "15672:15672" \ No newline at end of file diff --git a/2024-01/spring-34-rabbit/pom.xml b/2024-01/spring-34-rabbit/pom.xml new file mode 100644 index 00000000..fba9fd4c --- /dev/null +++ b/2024-01/spring-34-rabbit/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.3.0 + + + + ru.otus.example + spring-mail-rabbitmq-demo + 1.0 + + pom + + + user-activity-models + user-activity-emitter-microservice + user-activity-processor-microservice + + + + 17 + 17 + + + + + org.projectlombok + lombok + provided + + + diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/.gitignore b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/.gitignore new file mode 100644 index 00000000..a2a3040a --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/pom.xml b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/pom.xml new file mode 100644 index 00000000..98ca0cdf --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + + ru.otus.example + spring-mail-rabbitmq-demo + 1.0 + + + user-activity-emitter-microservice + 0.0.1-SNAPSHOT + user-activity-emitter-microservice + User activity emitter microservice + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-amqp + + + + ru.otus.example + user-activity-models + 0.0.1-SNAPSHOT + + + + com.h2database + h2 + runtime + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/EmitterMicroServiceApplication.java b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/EmitterMicroServiceApplication.java new file mode 100644 index 00000000..24ac6621 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/EmitterMicroServiceApplication.java @@ -0,0 +1,17 @@ +package ru.otus.example.rabbitmq; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.scheduling.annotation.EnableScheduling; + +@EnableScheduling +@EntityScan("ru.otus.example.useractivitymodels") +@SpringBootApplication +public class EmitterMicroServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(EmitterMicroServiceApplication.class, args); + } + +} diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/rabbitmq/RabbitMqConfig.java b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/rabbitmq/RabbitMqConfig.java new file mode 100644 index 00000000..1494cfdb --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/rabbitmq/RabbitMqConfig.java @@ -0,0 +1,35 @@ +package ru.otus.example.rabbitmq.rabbitmq; + +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Configuration +public class RabbitMqConfig { + + private static final String MAIN_EXCHANGE_NAME = "main-exchange"; + + @Bean + public Jackson2JsonMessageConverter jsonConverter(ObjectMapper objectMapper) { + return new Jackson2JsonMessageConverter(objectMapper); + } + + @Bean + public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, + Jackson2JsonMessageConverter jsonConverter) { + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setExchange(MAIN_EXCHANGE_NAME); + rabbitTemplate.setMessageConverter(jsonConverter); + return rabbitTemplate; + } + + @Bean + public TopicExchange topicExchange() { + return new TopicExchange(MAIN_EXCHANGE_NAME); + } +} diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityRepository.java b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityRepository.java new file mode 100644 index 00000000..da4de883 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityRepository.java @@ -0,0 +1,10 @@ +package ru.otus.example.rabbitmq.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +import ru.otus.example.useractivitymodels.UserActivity; + +@Transactional +public interface ActivityRepository extends JpaRepository { +} diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityTypeRepository.java b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityTypeRepository.java new file mode 100644 index 00000000..5517f1f8 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityTypeRepository.java @@ -0,0 +1,8 @@ +package ru.otus.example.rabbitmq.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; + +import ru.otus.example.useractivitymodels.ActivityType; + +public interface ActivityTypeRepository extends JpaRepository { +} diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/AppUserRepository.java b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/AppUserRepository.java new file mode 100644 index 00000000..6ef81d02 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/AppUserRepository.java @@ -0,0 +1,10 @@ +package ru.otus.example.rabbitmq.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +import ru.otus.example.useractivitymodels.AppUser; + +@Transactional +public interface AppUserRepository extends JpaRepository { +} diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/services/ActivityStatCalculationEmitterSerivce.java b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/services/ActivityStatCalculationEmitterSerivce.java new file mode 100644 index 00000000..983649ca --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/services/ActivityStatCalculationEmitterSerivce.java @@ -0,0 +1,25 @@ +package ru.otus.example.rabbitmq.services; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RequiredArgsConstructor +@Service +@Slf4j +public class ActivityStatCalculationEmitterSerivce { + private static final String USER_ACTIVITY_STAT_ROUTING_KEY = "user.activity.stat"; + private static final String CALC_STAT_COMMAND = "{\"command\": \"calc stat now\"}"; + + private final RabbitTemplate rabbitTemplate; + + @Scheduled(initialDelay = 3000, fixedRate = 10000) + public void emitAppUserActivityStatCalculation() { +// log.warn("Stat send!!!"); + rabbitTemplate.convertAndSend(USER_ACTIVITY_STAT_ROUTING_KEY, CALC_STAT_COMMAND); + log.warn("Stat calculated!!!"); + } +} diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/services/UserActivityEmitterService.java b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/services/UserActivityEmitterService.java new file mode 100644 index 00000000..82b2cc8a --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/java/ru/otus/example/rabbitmq/services/UserActivityEmitterService.java @@ -0,0 +1,41 @@ +package ru.otus.example.rabbitmq.services; + +import java.util.Random; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import ru.otus.example.rabbitmq.repositories.ActivityTypeRepository; +import ru.otus.example.rabbitmq.repositories.AppUserRepository; +import ru.otus.example.useractivitymodels.UserActivity; + +@RequiredArgsConstructor +@Service +@Slf4j +public class UserActivityEmitterService { + private final ActivityTypeRepository activityTypeRepository; + private final AppUserRepository appUserRepository; + private final RabbitTemplate rabbitTemplate; + + @SuppressWarnings("unused") + @Scheduled(initialDelay = 2000, fixedRate = 3000) + public void emitAppUserActivity() { + val random = new Random(); + val activityTypes = activityTypeRepository.findAll(); + val appUsers = appUserRepository.findAll(); + + val activityType = activityTypes.get(random.nextInt(activityTypes.size())); + val appUser = appUsers.get(random.nextInt(appUsers.size())); + val appUserActivity = new UserActivity(activityType, appUser); + val isImportant = activityType.getName().contains("Вредн"); + + val routingKey = String.format("user.activity.message.%s", isImportant ? "important" : "simple"); + rabbitTemplate.convertAndSend(routingKey, appUserActivity); + log.info("Send activity: {}", appUserActivity); + + } +} diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/resources/application.yml b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/resources/application.yml new file mode 100644 index 00000000..c9f02077 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/resources/application.yml @@ -0,0 +1,14 @@ +server: + port: 9090 +spring: + jpa: + generate-ddl: false + hibernate: + ddl-auto: none + show-sql: false + + rabbitmq: + addresses: "localhost" + sql: + init: + mode: always \ No newline at end of file diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/resources/data.sql b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/resources/data.sql new file mode 100644 index 00000000..e15ece69 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/resources/data.sql @@ -0,0 +1,44 @@ +insert into app_users(name, email) +values ('Рафаель Губерманович Тыгыдым', 'test@mail.ru'); + +insert into app_users(name, email) +values ('Артем Демосфенович Шмяк', 'test@mail.ru'); + +insert into app_users(name, email) +values ('Ифигения Бореславовна Фуфелшмерц', 'test@mail.ru'); + + +insert into activity_types(name) +values ('Очень полезное дело №4'); + +insert into activity_types(name) +values ('Очень полезное дело №13'); + +insert into activity_types(name) +values ('Очень полезное дело №34'); + +insert into activity_types(name) +values ('Очень полезное дело №48'); + +insert into activity_types(name) +values ('Очень полезное дело №53'); + +insert into activity_types(name) +values ('Вредное дело №11'); + +insert into activity_types(name) +values ('Вредное дело №12'); + +insert into activity_types(name) +values ('Вредное дело №13'); + +insert into activity_types(name) +values ('Вредное дело №14'); + +insert into activity_types(name) +values ('Вредное дело №15'); + +insert into activity_types(name) +values ('Вредное дело №16'); + + diff --git a/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/resources/schema.sql b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/resources/schema.sql new file mode 100644 index 00000000..03bcef37 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-emitter-microservice/src/main/resources/schema.sql @@ -0,0 +1,31 @@ +create table activity_types +( + id bigint auto_increment, + name varchar(255), + primary key (id) +); + +create table app_users +( + id bigint auto_increment, + email varchar(255), + name varchar(255), + primary key (id) +); + + +create table app_users_activity +( + id bigint auto_increment, + activity_time timestamp, + app_user_id bigint, + activity_type_id bigint, + primary key (id) +); + +alter table app_users_activity + add constraint app_users_activity_user_id_fk foreign key (app_user_id) references app_users; + +alter table app_users_activity + add constraint app_users_activity_activity_type_id_fk foreign key (activity_type_id) references activity_types; + diff --git a/2024-01/spring-34-rabbit/user-activity-models/.gitignore b/2024-01/spring-34-rabbit/user-activity-models/.gitignore new file mode 100644 index 00000000..a2a3040a --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-models/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/2024-01/spring-34-rabbit/user-activity-models/pom.xml b/2024-01/spring-34-rabbit/user-activity-models/pom.xml new file mode 100644 index 00000000..eae783db --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-models/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + + ru.otus.example + spring-mail-rabbitmq-demo + 1.0 + + + user-activity-models + 0.0.1-SNAPSHOT + user-activity-models + Models for spring mail rabbitmq demo project + + + + + com.fasterxml.jackson.core + jackson-annotations + + + + jakarta.persistence + jakarta.persistence-api + + + + + + diff --git a/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/ActivityStatElem.java b/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/ActivityStatElem.java new file mode 100644 index 00000000..bb0af10e --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/ActivityStatElem.java @@ -0,0 +1,42 @@ +package ru.otus.example.useractivitymodels; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@Entity +@Table(name = "activity_stat") +public class ActivityStatElem { + + @JsonIgnore + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @JsonProperty("Имя пользователя") + @Column(name = "app_user_name") + private String appUserName; + + @JsonProperty("Тип активности") + @Column(name = "activity_type") + private String activityType; + + @JsonProperty("Количество") + @Column(name = "activities_count") + private long activitiesCount; + + public ActivityStatElem(String appUserName, String activityType, long activitiesCount) { + this.appUserName = appUserName; + this.activityType = activityType; + this.activitiesCount = activitiesCount; + } +} diff --git a/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/ActivityType.java b/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/ActivityType.java new file mode 100644 index 00000000..41225afb --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/ActivityType.java @@ -0,0 +1,26 @@ +package ru.otus.example.useractivitymodels; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "activity_types") +public class ActivityType { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "name") + private String name; +} diff --git a/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/AppUser.java b/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/AppUser.java new file mode 100644 index 00000000..5d5c2415 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/AppUser.java @@ -0,0 +1,29 @@ +package ru.otus.example.useractivitymodels; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "app_users") +public class AppUser { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "email") + private String email; + + @Column(name = "name") + private String name; +} diff --git a/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/UserActivity.java b/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/UserActivity.java new file mode 100644 index 00000000..9dbff6cf --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-models/src/main/java/ru/otus/example/useractivitymodels/UserActivity.java @@ -0,0 +1,46 @@ +package ru.otus.example.useractivitymodels; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "app_users_activity") +public class UserActivity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "activity_time") + private LocalDateTime activityTime; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "activity_type_id") + private ActivityType type; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "app_user_id") + private AppUser appUser; + + public UserActivity(ActivityType type, AppUser appUser) { + this.id = id; + this.type = type; + this.appUser = appUser; + this.activityTime = LocalDateTime.now(); + } +} diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/.gitignore b/2024-01/spring-34-rabbit/user-activity-processor-microservice/.gitignore new file mode 100644 index 00000000..a2a3040a --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/pom.xml b/2024-01/spring-34-rabbit/user-activity-processor-microservice/pom.xml new file mode 100644 index 00000000..513df465 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + + ru.otus.example + spring-mail-rabbitmq-demo + 1.0 + + + user-activity-processor-microservice + 0.0.1-SNAPSHOT + user-activity-processor-microservice + User activity processor microservice + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-amqp + + + + org.springframework.boot + spring-boot-starter-mail + + + + ru.otus.example + user-activity-models + 0.0.1-SNAPSHOT + + + + com.h2database + h2 + runtime + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/ProcessorMicroServiceApplication.java b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/ProcessorMicroServiceApplication.java new file mode 100644 index 00000000..12b2af42 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/ProcessorMicroServiceApplication.java @@ -0,0 +1,17 @@ +package ru.otus.example.rabbitmq; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.scheduling.annotation.EnableScheduling; + +@EnableScheduling +@EntityScan("ru.otus.example.useractivitymodels") +@SpringBootApplication +public class ProcessorMicroServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(ProcessorMicroServiceApplication.class, args); + } + +} diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/actuator/ActivityStatEndpoint.java b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/actuator/ActivityStatEndpoint.java new file mode 100644 index 00000000..80b62c12 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/actuator/ActivityStatEndpoint.java @@ -0,0 +1,24 @@ +package ru.otus.example.rabbitmq.actuator; + +import java.util.List; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import ru.otus.example.rabbitmq.repositories.ActivityStatRepository; +import ru.otus.example.useractivitymodels.ActivityStatElem; + +@RequiredArgsConstructor +@Component +@Endpoint(id = "activity-stat") +public class ActivityStatEndpoint { + + private final ActivityStatRepository activityStatRepository; + + @ReadOperation + public List getAppUsersActivityStat() { + return activityStatRepository.findAll(); + } +} diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/config/AppProps.java b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/config/AppProps.java new file mode 100644 index 00000000..29070d24 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/config/AppProps.java @@ -0,0 +1,14 @@ +package ru.otus.example.rabbitmq.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import lombok.Data; + +@Data +@Component +@ConfigurationProperties("app") +public class AppProps { + private String serverEmail; + private String adminEmail; +} diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/rabbitmq/RabbitMqConfig.java b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/rabbitmq/RabbitMqConfig.java new file mode 100644 index 00000000..56abf585 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/rabbitmq/RabbitMqConfig.java @@ -0,0 +1,81 @@ +package ru.otus.example.rabbitmq.rabbitmq; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.FanoutExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.QueueBuilder; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Configuration +public class RabbitMqConfig { + @Bean + public Jackson2JsonMessageConverter jsonConverter(ObjectMapper objectMapper) { + return new Jackson2JsonMessageConverter(objectMapper); + } + + @Bean + public Queue allActivityQueue() { + return new Queue("all-activity-queue"); + } + + @Bean + public Queue importantActivityQueue() { + return new Queue("important-activity-queue"); + } + + @Bean + public Queue statCalcCommandsQueue() { + return QueueBuilder.durable("stat-calc-commands-queue") + .maxLength(5) + .deadLetterExchange("dead-letter-exchange") + .build(); + } + + @Bean + public Queue deadLetterQueue() { + return new Queue("dead-letter-queue"); + } + + @Bean + public TopicExchange topicExchange() { + return new TopicExchange("main-exchange"); + } + + @Bean + public FanoutExchange deadLetterExchange() { + return new FanoutExchange("dead-letter-exchange"); + } + + @Bean + public Binding allActivityBinding() { + return BindingBuilder.bind(allActivityQueue()) + .to(topicExchange()) + .with("user.activity.message.*"); + } + + @Bean + public Binding importantActivityBinding() { + return BindingBuilder.bind(importantActivityQueue()) + .to(topicExchange()) + .with("user.activity.message.important"); + } + + @Bean + public Binding statCalcCommandsBinding() { + return BindingBuilder.bind(statCalcCommandsQueue()) + .to(topicExchange()) + .with("user.activity.stat"); + } + + @Bean + public Binding deadLetterBinding() { + return BindingBuilder.bind(deadLetterQueue()) + .to(deadLetterExchange()); + } +} diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/rabbitmq/RabbitMqListener.java b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/rabbitmq/RabbitMqListener.java new file mode 100644 index 00000000..32375c1a --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/rabbitmq/RabbitMqListener.java @@ -0,0 +1,71 @@ +package ru.otus.example.rabbitmq.rabbitmq; + +import org.springframework.amqp.AmqpRejectAndDontRequeueException; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.support.AmqpHeaders; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.stereotype.Service; + +import com.rabbitmq.client.Channel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import ru.otus.example.rabbitmq.repositories.ActivityRepository; +import ru.otus.example.rabbitmq.repositories.ActivityStatRepository; +import ru.otus.example.rabbitmq.services.UserActivityToEmailTransformer; +import ru.otus.example.useractivitymodels.UserActivity; + +@RequiredArgsConstructor +@Service +@Slf4j +public class RabbitMqListener { + + private final ActivityRepository activityRepository; + private final ActivityStatRepository activityStatRepository; + private final UserActivityToEmailTransformer messageTransformer; + private final JavaMailSender mailSender; + + @RabbitListener(queues = "important-activity-queue") + public void processImportantMessages(UserActivity message) { + log.info("RECEIVED FROM important-activity-queue: " + message); + + try { + val mailMessage = messageTransformer.transform(message); + log.info("Как будто посылаем письмо: " + mailMessage); + //mailSender.send(mailMessage); + } catch (Exception e) { + throw new AmqpRejectAndDontRequeueException("Ooops"); + } + } + + @RabbitListener(queues = "all-activity-queue") + public void processAllMessages(UserActivity message) { + log.info("RECEIVED FROM all-activity-queue: " + message); + try { + activityRepository.save(message); + } catch (Exception e) { + throw new AmqpRejectAndDontRequeueException("Ooops"); + } + } + + @RabbitListener(queues = "stat-calc-commands-queue", ackMode = "MANUAL") + public void processStatCalcCommandsMessages(String message, + Channel channel, + @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception { + log.warn("RECEIVED FROM stat-calc-commands-queue: " + message); + + activityStatRepository.deleteAll(); + val activityStat = activityStatRepository.calcActivityStat(); + activityStatRepository.saveAll(activityStat); +// sleep(5000); + channel.basicAck(tag, false); + + // Для ackMode = "MANUAL" и перехода в dead letter exchange + //channel.basicNack(tag, false, false); + + // Для ackMode = "AUTO" и перехода в dead letter exchange + //throw new AmqpRejectAndDontRequeueException("Ooops"); + + } +} \ No newline at end of file diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityRepository.java b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityRepository.java new file mode 100644 index 00000000..da4de883 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityRepository.java @@ -0,0 +1,10 @@ +package ru.otus.example.rabbitmq.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +import ru.otus.example.useractivitymodels.UserActivity; + +@Transactional +public interface ActivityRepository extends JpaRepository { +} diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityStatRepository.java b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityStatRepository.java new file mode 100644 index 00000000..867d7229 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/repositories/ActivityStatRepository.java @@ -0,0 +1,22 @@ +package ru.otus.example.rabbitmq.repositories; + + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; + +import ru.otus.example.useractivitymodels.ActivityStatElem; + +@Transactional +public interface ActivityStatRepository extends JpaRepository { + + @Transactional(readOnly = true) + @Query("select new ru.otus.example.useractivitymodels.ActivityStatElem(u.name, t.name, count(a)) " + + "from UserActivity a left join a.appUser u left join a.type t " + + "group by u.name, t.name " + + "order by count(a) desc, u.name, t.name") + List calcActivityStat(); + +} diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/services/UserActivityToEmailTransformer.java b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/services/UserActivityToEmailTransformer.java new file mode 100644 index 00000000..5735c7f3 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/services/UserActivityToEmailTransformer.java @@ -0,0 +1,9 @@ +package ru.otus.example.rabbitmq.services; + +import org.springframework.mail.SimpleMailMessage; + +import ru.otus.example.useractivitymodels.UserActivity; + +public interface UserActivityToEmailTransformer { + SimpleMailMessage transform(UserActivity activity); +} diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/services/UserActivityToEmailTransformerImpl.java b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/services/UserActivityToEmailTransformerImpl.java new file mode 100644 index 00000000..9bc93bce --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/java/ru/otus/example/rabbitmq/services/UserActivityToEmailTransformerImpl.java @@ -0,0 +1,26 @@ +package ru.otus.example.rabbitmq.services; + +import org.springframework.mail.SimpleMailMessage; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import ru.otus.example.rabbitmq.config.AppProps; +import ru.otus.example.useractivitymodels.UserActivity; + +@RequiredArgsConstructor +@Service +public class UserActivityToEmailTransformerImpl implements UserActivityToEmailTransformer { + + private final AppProps appProps; + + @Override + public SimpleMailMessage transform(UserActivity activity) { + SimpleMailMessage mailMessage = new SimpleMailMessage(); + mailMessage.setTo(appProps.getAdminEmail()); + mailMessage.setFrom(appProps.getServerEmail()); + mailMessage.setSubject("Обнаружена вредная активность"); + mailMessage.setText(String.format("Внимание!!! Обнаружена вредная активность! Время: %s, пользователь: %s, тип активности: %s", + activity.getActivityTime(), activity.getAppUser().getName(), activity.getType().getName())); + return mailMessage; + } +} diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/resources/application.yml b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/resources/application.yml new file mode 100644 index 00000000..e0fa092d --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/resources/application.yml @@ -0,0 +1,40 @@ +app: + # адрес почты, через которую сервер отправляет письма + server-email: ${server.email} + # адрес почты администратора, на которую сервер отправляет письма + admin-email: ${admin.email} + +spring: + jpa: + generate-ddl: false + hibernate: + ddl-auto: none + show-sql: true + + rabbitmq: + addresses: "localhost" + + mail: + host: smtp.mail.ru + port: 465 + # логин и пароль для почты, через которую сервер отправляет письма + username: ${email.server.user} + password: ${email.server.password} + protocol: smtps + properties: + mail: + smtp: + auth: true + starttls.enable: true + sql: + init: + mode: + +management: + endpoints: + web: + exposure: + include: health, info, activity-stat + endpoint: + health: + show-details: always \ No newline at end of file diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/resources/data.sql b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/resources/data.sql new file mode 100644 index 00000000..e15ece69 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/resources/data.sql @@ -0,0 +1,44 @@ +insert into app_users(name, email) +values ('Рафаель Губерманович Тыгыдым', 'test@mail.ru'); + +insert into app_users(name, email) +values ('Артем Демосфенович Шмяк', 'test@mail.ru'); + +insert into app_users(name, email) +values ('Ифигения Бореславовна Фуфелшмерц', 'test@mail.ru'); + + +insert into activity_types(name) +values ('Очень полезное дело №4'); + +insert into activity_types(name) +values ('Очень полезное дело №13'); + +insert into activity_types(name) +values ('Очень полезное дело №34'); + +insert into activity_types(name) +values ('Очень полезное дело №48'); + +insert into activity_types(name) +values ('Очень полезное дело №53'); + +insert into activity_types(name) +values ('Вредное дело №11'); + +insert into activity_types(name) +values ('Вредное дело №12'); + +insert into activity_types(name) +values ('Вредное дело №13'); + +insert into activity_types(name) +values ('Вредное дело №14'); + +insert into activity_types(name) +values ('Вредное дело №15'); + +insert into activity_types(name) +values ('Вредное дело №16'); + + diff --git a/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/resources/schema.sql b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/resources/schema.sql new file mode 100644 index 00000000..0f67d6e4 --- /dev/null +++ b/2024-01/spring-34-rabbit/user-activity-processor-microservice/src/main/resources/schema.sql @@ -0,0 +1,41 @@ +create table activity_types +( + id bigint auto_increment, + name varchar(255), + primary key (id) +); + +create table app_users +( + id bigint auto_increment, + email varchar(255), + name varchar(255), + primary key (id) +); + + +create table app_users_activity +( + id bigint auto_increment, + activity_time timestamp, + app_user_id bigint, + activity_type_id bigint, + primary key (id) +); + +alter table app_users_activity + add constraint app_users_activity_user_id_fk foreign key (app_user_id) references app_users; + +alter table app_users_activity + add constraint app_users_activity_activity_type_id_fk foreign key (activity_type_id) references activity_types; + + +create table activity_stat +( + id bigint auto_increment, + app_user_name varchar(255), + activity_type varchar(255), + activities_count bigint, + primary key (id) +); +