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)
+);
+