如何配置 Mercure 捆绑包以将通知发送到 Symfony 应用程序上的 Web UI
我从美居中心开始,但我有一些疑虑阻止了我。这个想法是在 Symfony 5.4 Web 应用程序(它是基于 sf 5.4 的管理 Web 应用程序)中添加特定操作后为用户提供的经典通知铃声<;在这种特定情况下,一旦注册了新的患者转移请求,就必须显示通知。对于属于患者当前登记的医疗机构的用户,将启动通知铃声。
注意:在本地,它在 Windows 10 上安装了带有 https 虚拟主机的 WAMP 服务器。
首先,我安装了 Mercure 捆绑包: composer require Mercure
我已经下载了 Mercure 可执行文件Windows 操作系统。
必要的环境变量:据我了解,MERCURE_URL
是 Web 服务器与 Mercure Hub 之间通信的地址,MERCURE_PUBLIC_URL
是客户端订阅 Hub 的地址。
疑问:应根据什么数据生成 MERCURE_JWT_SECRET
。这必须是存储在不会更改的环境变量中的静态 jwt 令牌吗?
Mercure.yaml 上的 Mercure 配方:
mercure:
hubs:
default:
url: '%env(MERCURE_URL)%'
public_url: '%env(MERCURE_PUBLIC_URL)%'
jwt:
secret: '%env(MERCURE_JWT_SECRET)%'
publish: '*'
疑问:我不明白 Mercure 捆绑包上下文中 JWT 的组成,配置的发布部分应该如何?
这是存储新患者转移请求并通过向配置的消息总线发送消息来启动通知过程的操作:
/**
*
* @param Request $request
* @param ManagerRegistry $manager
* @param UserInterface $user
* @param UuidEncoder $uuidEncoder
* @param LoggerInterface $logger
* @param \Symfony\Component\Messenger\MessageBusInterface $messageBus
* @return Response
* @throws Exception
* @throws type
*/
public function solicitarTrasladoAction(Request $request, ManagerRegistry $manager, UserInterface $user, UuidEncoder $uuidEncoder, LoggerInterface $logger, \Symfony\Component\Messenger\MessageBusInterface $messageBus): Response
{
if ($request->isXmlHttpRequest()) {
try {
if (!$request->isMethod(Request::METHOD_POST)) {
return new Response("Operación no soportada!!!", 500);
}
$id = $uuidEncoder->decode($request->request->get('embarazadaId', null));
$cmfDestinoId = $uuidEncoder->decode($request->get('cmfDestino', null));
$em = $manager->getManager();
$conn = $em->getConnection();
$conn->beginTransaction();
try {
$cmfDestino = $manager->getRepository(EstructuraOrganizativa::class)->findOneJoinTipoEstructuraOrganizativa($cmfDestinoId);
if (\is_null($cmfDestino)) {
throw new \Exception("No se encontró la unidad de destino.", 404);
} else if ($cmfDestino->getTipoEstructuraOrganizativa()->getId() !== 6) {
throw new \Exception("No es posible ubicar una embarazada fuera de un CMF.", 406);
}
$embarazada = $em->getRepository(Embarazada::class)->findOneJoinEstructuraOrganizativa($id);
if (\is_null($embarazada)) {
throw new \Exception("No se encontró la embarazada solicitada", 404);
}
if ($embarazada->getEstructuraOrganizativa()->getId() === $cmfDestino->getId()) {
throw new \Exception("No es posible reubicar la embarazada en el CMF al que pertenece actualmente.", 406);
}
$posibleSolicitud = $em->getRepository(\App\Entity\SolicitudTrasladoEmbarazada::class)->findOneBy(['embarazada' => $embarazada, 'estado' => 'solicitado']);
if (!\is_null($posibleSolicitud)) {
throw new \Exception("Ya existe una solicitud de traslado para esta paciente.", 406);
}
$nuevaSolicitudTraslado = new \App\Entity\SolicitudTrasladoEmbarazada();
$nuevaSolicitudTraslado->setEmbarazada($embarazada);
$nuevaSolicitudTraslado->setCmfDestino($cmfDestino);
$nuevaSolicitudTraslado->setEstado("solicitado");
$nuevaSolicitudTraslado->setAsunto("Solicitud de traslado");
$nuevaSolicitudTraslado->setMensaje(\sprintf("Solicito reubicar a '%s' hacia provincia '%s', municipio '%s', CMF: %s.", $embarazada->getNombre(), $cmfDestino->getParent()->getParent()->getParent()->getParent()->getTitle(), $cmfDestino->getParent()->getParent()->getParent()->getTitle(), $cmfDestino->getTitle()));
$em->persist($nuevaSolicitudTraslado);
$em->flush();
$conn->commit();
} catch (\Exception $exc) {
$conn->rollback();
$conn->close();
if (in_array($exc->getCode(), array(404, 406))) {
return new Response($exc->getMessage(), 500);
}
$logger->error(sprintf("[%s:%s]: %s", __CLASS__, __FUNCTION__, $exc->getMessage()));
return new Response("Ocurrió un error inesperado al ejecutar la operación", 500);
}
$messageBus->dispatch(new \App\Message\NotificacionSolicitudTrasladoEmbarazadaMessage($uuidEncoder->encode($nuevaSolicitudTraslado->getIdPublico())));
return new Response("La solicitud de traslado fue enviada satisfactoriamente");
} catch (\Exception $exc) {
$logger->error(sprintf("[%s:%s]: %s", self::class, __FUNCTION__, $exc->getMessage()));
return new Response("Ocurrió un error inesperado al ejecutar la operación", 500);
}
} else {
throw $this->createNotFoundException("Recurso no encontrado");
}
}
在将响应发送给用户以生成新患者转移请求之前,并达到异步行为,我已经向 Symfony 消息总线添加了一条新消息(特别是它使用了学说传输),将已创建的新注册表的公共 id (idPublico) 作为消息内容的一部分进行传递。
//messenger.yaml 配方的内容:
framework:
messenger:
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
failure_transport: failed
reset_on_message: true
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
auto_setup: false
failed:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
queue_name: 'failed'
sync: 'sync://'
routing:
# Route your messages to the transports
'App\Message\NotificacionSolicitudTrasladoEmbarazadaMessage': async
Message 类:
namespace App\Message;
class NotificacionSolicitudTrasladoEmbarazadaMessage
{
private $content;
public function __construct(string $content)
{
$this->content = $content;
}
public function getContent(): string
{
return $this->content;
}
}
消息处理程序类,这里封装了 Mercure Hub 调用函数的逻辑:
use App\Message\NotificacionSolicitudTrasladoEmbarazadaMessage;
use App\Repository\SolicitudTrasladoEmbarazadaRepository;
use App\Services\UuidEncoder;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
/**
* Envia al Mercure bus la nueva solicitud de traslado para ser notificada a los usuarios
*/
class NotificacionSolicitudTrasladoEmbarazadaMessageHandler implements MessageHandlerInterface
{
private $mercureHub;
private $repositorySolicitudTrasladoEmbarazada;
private $uuidEncoder;
public function __construct(HubInterface $mercureHub, SolicitudTrasladoEmbarazadaRepository $repositorySolicitudTrasladoEmbarazada, UuidEncoder $uuidEncoder)
{
$this->mercureHub = $mercureHub;
$this->repositorySolicitudTrasladoEmbarazada = $repositorySolicitudTrasladoEmbarazada;
$this->uuidEncoder = $uuidEncoder;
}
public function __invoke(NotificacionSolicitudTrasladoEmbarazadaMessage $message)
{
// get the real content of the message
$idPublico = $this->uuidEncoder->decode($message->getContent());
// get the fresh object from the database
$solicitud = $this->repositorySolicitudTrasladoEmbarazada->findOneBy(['idPublico' => $idPublico]);
if(\is_null()){
return;
}
/** Count all unatended request of movements **/
$totalNoAtendidas = $this->repositorySolicitudTrasladoEmbarazada->contarNoAtendidas($solicitud->getCmfDestino());
// make a update to the hub
$actualizacion = new Update(
'https://the-uri-of-resource', // This URI must be generated with symfony routing services, or its a simple formality?
\json_encode(['ultimaSolictud' => $solicitud->getAsunto(), 'totalNoAtendidas' => $totalNoAtendidas]),
true // privado necesita jwt auth set that need JWT auth
);
$this->mercureHub->publish($actualizacion);
return new Response("Publicado");
}
}
Doubt
:Update 对象的 URI 参数必须是生成的 URL Symfony 路由还是一个简单的“形式”?
此时,消息不断传递到失败的队列,重新尝试发送到 Mercure Hub,因为我无法配置和运行 Mercure.exe
对于客户端,订阅案例,我还是不明白如何设置 JWT 令牌,但我将其留给另一个问题
I'm starting with Mercure hub, but I have some doubts that are stopping me. The idea is add to a Symfony 5.4 web application (its a management web application based on sf 5.4) the classic notification bell for users after a certain operation< in this specific case, the notification must appear once a new patient transfer request is registered. The notification bell will be activated for users who belong to the medical institution in which the patient is currently registered.
Note: locally it was installed a WAMP server with a https virtual host over Windows 10.
First, I installed the mercure bundle: composer require mercure
I have downloaded the mercure executable for Windows OS.
The necessary environment variables: I understand that MERCURE_URL
its a address for communication between the web server and the mercure hub, and MERCURE_PUBLIC_URL
its a address for the clients subscription to the hub.
Doubt: Based on what data the MERCURE_JWT_SECRET
should be generated. This must be a static jwt token stored in an environment variable that doesn't change?
Mercure recipe on mercure.yaml:
mercure:
hubs:
default:
url: '%env(MERCURE_URL)%'
public_url: '%env(MERCURE_PUBLIC_URL)%'
jwt:
secret: '%env(MERCURE_JWT_SECRET)%'
publish: '*'
Doubt: I don't understand the composition of JWT on the context of mercure bundle, how should be the publish section of the configuration?
This is the action that store the new patient transfer request and starts the notification process by sending a message to the configured message bus:
/**
*
* @param Request $request
* @param ManagerRegistry $manager
* @param UserInterface $user
* @param UuidEncoder $uuidEncoder
* @param LoggerInterface $logger
* @param \Symfony\Component\Messenger\MessageBusInterface $messageBus
* @return Response
* @throws Exception
* @throws type
*/
public function solicitarTrasladoAction(Request $request, ManagerRegistry $manager, UserInterface $user, UuidEncoder $uuidEncoder, LoggerInterface $logger, \Symfony\Component\Messenger\MessageBusInterface $messageBus): Response
{
if ($request->isXmlHttpRequest()) {
try {
if (!$request->isMethod(Request::METHOD_POST)) {
return new Response("Operación no soportada!!!", 500);
}
$id = $uuidEncoder->decode($request->request->get('embarazadaId', null));
$cmfDestinoId = $uuidEncoder->decode($request->get('cmfDestino', null));
$em = $manager->getManager();
$conn = $em->getConnection();
$conn->beginTransaction();
try {
$cmfDestino = $manager->getRepository(EstructuraOrganizativa::class)->findOneJoinTipoEstructuraOrganizativa($cmfDestinoId);
if (\is_null($cmfDestino)) {
throw new \Exception("No se encontró la unidad de destino.", 404);
} else if ($cmfDestino->getTipoEstructuraOrganizativa()->getId() !== 6) {
throw new \Exception("No es posible ubicar una embarazada fuera de un CMF.", 406);
}
$embarazada = $em->getRepository(Embarazada::class)->findOneJoinEstructuraOrganizativa($id);
if (\is_null($embarazada)) {
throw new \Exception("No se encontró la embarazada solicitada", 404);
}
if ($embarazada->getEstructuraOrganizativa()->getId() === $cmfDestino->getId()) {
throw new \Exception("No es posible reubicar la embarazada en el CMF al que pertenece actualmente.", 406);
}
$posibleSolicitud = $em->getRepository(\App\Entity\SolicitudTrasladoEmbarazada::class)->findOneBy(['embarazada' => $embarazada, 'estado' => 'solicitado']);
if (!\is_null($posibleSolicitud)) {
throw new \Exception("Ya existe una solicitud de traslado para esta paciente.", 406);
}
$nuevaSolicitudTraslado = new \App\Entity\SolicitudTrasladoEmbarazada();
$nuevaSolicitudTraslado->setEmbarazada($embarazada);
$nuevaSolicitudTraslado->setCmfDestino($cmfDestino);
$nuevaSolicitudTraslado->setEstado("solicitado");
$nuevaSolicitudTraslado->setAsunto("Solicitud de traslado");
$nuevaSolicitudTraslado->setMensaje(\sprintf("Solicito reubicar a '%s' hacia provincia '%s', municipio '%s', CMF: %s.", $embarazada->getNombre(), $cmfDestino->getParent()->getParent()->getParent()->getParent()->getTitle(), $cmfDestino->getParent()->getParent()->getParent()->getTitle(), $cmfDestino->getTitle()));
$em->persist($nuevaSolicitudTraslado);
$em->flush();
$conn->commit();
} catch (\Exception $exc) {
$conn->rollback();
$conn->close();
if (in_array($exc->getCode(), array(404, 406))) {
return new Response($exc->getMessage(), 500);
}
$logger->error(sprintf("[%s:%s]: %s", __CLASS__, __FUNCTION__, $exc->getMessage()));
return new Response("Ocurrió un error inesperado al ejecutar la operación", 500);
}
$messageBus->dispatch(new \App\Message\NotificacionSolicitudTrasladoEmbarazadaMessage($uuidEncoder->encode($nuevaSolicitudTraslado->getIdPublico())));
return new Response("La solicitud de traslado fue enviada satisfactoriamente");
} catch (\Exception $exc) {
$logger->error(sprintf("[%s:%s]: %s", self::class, __FUNCTION__, $exc->getMessage()));
return new Response("Ocurrió un error inesperado al ejecutar la operación", 500);
}
} else {
throw $this->createNotFoundException("Recurso no encontrado");
}
}
Before send the response to the user for the generation of the new patient transfer request, and to reach an asynchronously behavior, I have added a new message to a Symfony message bus (specifically its use a doctrine transport), passing as part of a content of message the public id (idPublico) of the new registry that has been created.
// content of messenger.yaml recipe:
framework:
messenger:
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
failure_transport: failed
reset_on_message: true
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
auto_setup: false
failed:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
queue_name: 'failed'
sync: 'sync://'
routing:
# Route your messages to the transports
'App\Message\NotificacionSolicitudTrasladoEmbarazadaMessage': async
The Message class:
namespace App\Message;
class NotificacionSolicitudTrasladoEmbarazadaMessage
{
private $content;
public function __construct(string $content)
{
$this->content = $content;
}
public function getContent(): string
{
return $this->content;
}
}
The Message handler class, here its encapsulated the mercure hub logic on invoke function:
use App\Message\NotificacionSolicitudTrasladoEmbarazadaMessage;
use App\Repository\SolicitudTrasladoEmbarazadaRepository;
use App\Services\UuidEncoder;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
/**
* Envia al Mercure bus la nueva solicitud de traslado para ser notificada a los usuarios
*/
class NotificacionSolicitudTrasladoEmbarazadaMessageHandler implements MessageHandlerInterface
{
private $mercureHub;
private $repositorySolicitudTrasladoEmbarazada;
private $uuidEncoder;
public function __construct(HubInterface $mercureHub, SolicitudTrasladoEmbarazadaRepository $repositorySolicitudTrasladoEmbarazada, UuidEncoder $uuidEncoder)
{
$this->mercureHub = $mercureHub;
$this->repositorySolicitudTrasladoEmbarazada = $repositorySolicitudTrasladoEmbarazada;
$this->uuidEncoder = $uuidEncoder;
}
public function __invoke(NotificacionSolicitudTrasladoEmbarazadaMessage $message)
{
// get the real content of the message
$idPublico = $this->uuidEncoder->decode($message->getContent());
// get the fresh object from the database
$solicitud = $this->repositorySolicitudTrasladoEmbarazada->findOneBy(['idPublico' => $idPublico]);
if(\is_null()){
return;
}
/** Count all unatended request of movements **/
$totalNoAtendidas = $this->repositorySolicitudTrasladoEmbarazada->contarNoAtendidas($solicitud->getCmfDestino());
// make a update to the hub
$actualizacion = new Update(
'https://the-uri-of-resource', // This URI must be generated with symfony routing services, or its a simple formality?
\json_encode(['ultimaSolictud' => $solicitud->getAsunto(), 'totalNoAtendidas' => $totalNoAtendidas]),
true // privado necesita jwt auth set that need JWT auth
);
$this->mercureHub->publish($actualizacion);
return new Response("Publicado");
}
}
Doubt
: The URI parameter of Update object must be a generated URL with Symfony routing or its a simple "formality"?
At this point the messages are constantly passing to a failed queue, retrying to be sent to a mercure hub, because I have not been able to configure and run the mercure.exe
For the client side, the subscription case, I still don't understand how to set the JWT token, but I'll leave that for another question
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论