diff --git a/hw19/.docker/nginx/Dockerfile b/hw19/.docker/nginx/Dockerfile new file mode 100644 index 000000000..d19ea279d --- /dev/null +++ b/hw19/.docker/nginx/Dockerfile @@ -0,0 +1,5 @@ +FROM nginx + +WORKDIR /var/www/html + +COPY ngnix.conf /etc/nginx/nginx.conf diff --git a/hw19/.docker/nginx/ngnix.conf b/hw19/.docker/nginx/ngnix.conf new file mode 100644 index 000000000..291af686f --- /dev/null +++ b/hw19/.docker/nginx/ngnix.conf @@ -0,0 +1,25 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + server { + listen 80; + server_name localhost; + root /var/www/html/public/; + + location / { + index index.php; + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_pass php-fpm:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + } +} diff --git a/hw19/.docker/php-fpm/Dockerfile b/hw19/.docker/php-fpm/Dockerfile new file mode 100644 index 000000000..6e76f71c9 --- /dev/null +++ b/hw19/.docker/php-fpm/Dockerfile @@ -0,0 +1,11 @@ +FROM php:8.2-fpm + +RUN apt-get update && apt-get install -y \ + libzip-dev \ + && docker-php-ext-install zip sockets + +RUN chown -R www-data:www-data /var/www/html + +WORKDIR /var/www/html + +CMD ["php-fpm"] diff --git a/hw19/README.md b/hw19/README.md new file mode 100644 index 000000000..94fb65b7c --- /dev/null +++ b/hw19/README.md @@ -0,0 +1,12 @@ +# Работа с очередью + +## Отправка данных +1. Для генерации банковской выписки за указанные даты перейдите в браузере на главную страницу http://localhost/ +2. Выберите нужный интервал дат +3. Нажмите кнопку отправить +4. После чего форма вас переадресует на страницу обработчика формы /formHandler, в котором даты отправятся через RabbitMQ Producer в очередь + +## Получение данных +1. Чтобы считать данную очередь, вам нужно зайти в консоль и запуcтить команду php bin/console rabbitmq:consumer +2. Теперь в консоли вы можете видеть отправленные из браузерной формы даты +3. Также в консольной команде после вывода данных на экран происходит отправка письма. diff --git a/hw19/docker-compose.yml b/hw19/docker-compose.yml new file mode 100644 index 000000000..ee7740850 --- /dev/null +++ b/hw19/docker-compose.yml @@ -0,0 +1,36 @@ +version: "3" +services: + nginx: + container_name: nginx-otusphp + build: ./.docker/nginx + ports: + - "80:80" + volumes: + - ./www:/var/www/html + depends_on: + - php-fpm + networks: + - otusphp + + php-fpm: + container_name: php-fpm-otusphp + build: ./.docker/php-fpm/ + volumes: + - ./www:/var/www/html + depends_on: + - rabbitmq + networks: + - otusphp + + rabbitmq: + container_name: rabbitmq-otusphp + image: rabbitmq:3.10.7-management + ports: + - 15672:15672 + - 5672:5672 + networks: + - otusphp + +networks: + otusphp: + driver: bridge diff --git a/hw19/www/.env.example b/hw19/www/.env.example new file mode 100644 index 000000000..c6b0c3ac9 --- /dev/null +++ b/hw19/www/.env.example @@ -0,0 +1,14 @@ +BROKER_CONNECT='Shabanov\Otusphp\Connection\RabbitMqConnect' +BROKER_HOST='rabbitmq' +BROKER_PORT=5672 +BROKER_USER='guest' +BROKER_PASSWORD='guest' +EXCHANGE='shabanov' +QUEUE='otus' + +EMAIL_SMTP='smtp.yandex.ru' +EMAIL_PORT=465 +EMAIL_USER='saveliy' +EMAIL_PASSWORD='123456' +EMAIL_FROM='saveliy@mail.ru' +EMAIL_TO='infotest@mail.ru' diff --git a/hw19/www/bin/console b/hw19/www/bin/console new file mode 100755 index 000000000..0b0cad0cb --- /dev/null +++ b/hw19/www/bin/console @@ -0,0 +1,20 @@ +#!/usr/bin/env php +load(__DIR__ . '/../.env'); + +$application = new Application(); +$application->add(new ConsumerCommand(new $_ENV['BROKER_CONNECT']())); +try { + $application->run(); +} catch (Exception $e) { + throw new \Exception($e->getMessage()); +} diff --git a/hw19/www/composer.json b/hw19/www/composer.json new file mode 100644 index 000000000..0643f55f4 --- /dev/null +++ b/hw19/www/composer.json @@ -0,0 +1,21 @@ +{ + "name": "shabanov/otusphp", + "autoload": { + "psr-4": { + "Shabanov\\Otusphp\\": "src/" + } + }, + "authors": [ + { + "name": "Vyacheslav Shabanov", + "email": "saveliy@mail.ru" + } + ], + "require": { + "php-amqplib/php-amqplib": "^3.6", + "symfony/console": "^7.0", + "symfony/mailer": "^7.0", + "symfony/dotenv": "^7.0", + "phpmailer/phpmailer": "^6.9" + } +} diff --git a/hw19/www/public/index.php b/hw19/www/public/index.php new file mode 100644 index 000000000..f434cc343 --- /dev/null +++ b/hw19/www/public/index.php @@ -0,0 +1,13 @@ +getMessage()); +} diff --git a/hw19/www/src/App.php b/hw19/www/src/App.php new file mode 100644 index 000000000..398d2cef7 --- /dev/null +++ b/hw19/www/src/App.php @@ -0,0 +1,20 @@ +load(__DIR__ . '/../.env'); + } + + public function __invoke(): void + { + (new Route($_SERVER['REQUEST_URI']))->run(); + } +} diff --git a/hw19/www/src/Command/ConsumerCommand.php b/hw19/www/src/Command/ConsumerCommand.php new file mode 100644 index 000000000..9a21fe6b1 --- /dev/null +++ b/hw19/www/src/Command/ConsumerCommand.php @@ -0,0 +1,38 @@ +channel = $this->connect->getClient(); + $this->queue = $_ENV['QUEUE']; + } + + protected function configure(): void + { + $this->setName('rabbitmq:consumer') + ->setDescription('RabbitMQ consumer обработчик формы'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + (new RabbitMqConsumer($this->connect, $output))->run(); + + return Command::SUCCESS; + } +} diff --git a/hw19/www/src/Connection/ConnectionInterface.php b/hw19/www/src/Connection/ConnectionInterface.php new file mode 100644 index 000000000..a851cd355 --- /dev/null +++ b/hw19/www/src/Connection/ConnectionInterface.php @@ -0,0 +1,10 @@ +connect = new AMQPStreamConnection( + $_ENV['BROKER_HOST'], + $_ENV['BROKER_PORT'], + $_ENV['BROKER_USER'], + $_ENV['BROKER_PASSWORD'] + ); + } + public function getClient(): AMQPChannel|AbstractChannel + { + return $this->connect->channel(); + } + + /** + * @throws Exception + */ + public function close(): void + { + $this->connect->close(); + } +} diff --git a/hw19/www/src/Consumer/RabbitMqConsumer.php b/hw19/www/src/Consumer/RabbitMqConsumer.php new file mode 100644 index 000000000..51130f664 --- /dev/null +++ b/hw19/www/src/Consumer/RabbitMqConsumer.php @@ -0,0 +1,61 @@ +channel = $this->connect->getClient(); + $this->queue = $_ENV['QUEUE']; + } + + public function run(): void + { + $this->channel->basic_consume( + $this->queue, + '', + false, + true, + false, + false, + [$this, 'consumeHandler'] + ); + + while ($this->channel->is_consuming()) { + $this->channel->wait(); + } + + $this->close(); + } + + public function consumeHandler(AMQPMessage $message): void + { + /** + * Выведим в консоль данные + */ + $this->output->writeln('[x] ' . $message->body . ''); + /** + * Отправим строку на Email + */ + (new Mailer($message->body))->send(); + } + + private function close(): void + { + $this->channel->close(); + $this->connect->close(); + } +} diff --git a/hw19/www/src/Controller/PageController.php b/hw19/www/src/Controller/PageController.php new file mode 100644 index 000000000..ff972c7e8 --- /dev/null +++ b/hw19/www/src/Controller/PageController.php @@ -0,0 +1,26 @@ +show(); + } + + public function formHandler(): void + { + if (!empty($_REQUEST['send']) && !empty($_REQUEST['date_from'])) { + $message = 'Date from: ' . $_REQUEST['date_from'] . ' Date to: ' . $_REQUEST['date_to']; + (new RabbitMqProducer(new $_ENV['BROKER_CONNECT']())) + ->send($message); + echo (new FormSuccessRender())->show(); + } + } +} diff --git a/hw19/www/src/Mail/Mailer.php b/hw19/www/src/Mail/Mailer.php new file mode 100644 index 000000000..25aa36c82 --- /dev/null +++ b/hw19/www/src/Mail/Mailer.php @@ -0,0 +1,38 @@ +mail = new \PHPMailer(true); + $this->init(); + } + + private function init() + { + $this->mail->isSMTP(); + $this->mail->Host = $_ENV['EMAIL_SMTP']; + $this->mail->SMTPAuth = true; + $this->mail->Username = $_ENV['EMAIL_USER']; + $this->mail->Password = $_ENV['EMAIL_PASSWORD']; + $this->mail->Port = $_ENV['EMAIL_PORT']; + + $this->mail + ->setFrom($_ENV['EMAIL_FROM'], 'Sender') + ->addAddress($_ENV['EMAIL_TO'], 'Recipient'); + + $this->mail->Subject = 'Новое сообщение'; + $this->mail->Body = $this->body; + } + public function send(): void + { + try { + $this->mail->send(); + } catch (Exception $e) { + echo 'Ошибка отправки письма: ' . $this->mail->ErrorInfo; + } + } +} diff --git a/hw19/www/src/Producer/RabbitMqProducer.php b/hw19/www/src/Producer/RabbitMqProducer.php new file mode 100644 index 000000000..2a45572ae --- /dev/null +++ b/hw19/www/src/Producer/RabbitMqProducer.php @@ -0,0 +1,45 @@ +channel = $this->connect->getClient(); + $this->exchange = $_ENV['EXCHANGE']; + $this->queue = $_ENV['QUEUE']; + } + + public function send(string $message): void + { + $this->channel->queue_declare($this->queue, false, true, false, false); + $this->channel->exchange_declare($this->exchange, AMQPExchangeType::DIRECT, false, true, false); + $this->channel->queue_bind($this->queue, $this->exchange); + + $this->messagePublish($message); + + $this->channel->close(); + $this->connect->close(); + } + + private function messagePublish(string $message): void + { + $message = new AMQPMessage($message, [ + 'content_type' => 'text/plain', + 'delivery_mode' => AMQPMessage::DELIVERY_MODE_NON_PERSISTENT + ]); + $this->channel->basic_publish($message, $this->exchange); + } +} diff --git a/hw19/www/src/Render/Error404Render.php b/hw19/www/src/Render/Error404Render.php new file mode 100644 index 000000000..156e6e506 --- /dev/null +++ b/hw19/www/src/Render/Error404Render.php @@ -0,0 +1,19 @@ + + +

404 Not Found

+ +
+

Генерация банковской выписки за указанные даты:

+ От + По +

+
+ +

Спасибо за вашу заявку. Скоро на email вам поступит отчет

+ 'main', + '/formHandler' => 'formHandler', + ]; + public function __construct(private string $url) + {} + + public function run(): void + { + $action = self::MAP[$this->url] ?? null; + if ($action !== null) { + $controller = new PageController(); + if (method_exists($controller, $action)) { + $controller->$action(); + return; + } + } + + http_response_code(404); + echo (new Error404Render())->show(); + } +}