Mq.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <?php
  2. namespace App\Http\Api;
  3. use App\Tools\Tools;
  4. use Illuminate\Support\Facades\Log;
  5. use Illuminate\Support\Str;
  6. use PhpAmqpLib\Channel\AMQPChannel;
  7. use PhpAmqpLib\Connection\AbstractConnection;
  8. use PhpAmqpLib\Connection\AMQPStreamConnection;
  9. use PhpAmqpLib\Exception\AMQPTimeoutException;
  10. use PhpAmqpLib\Exchange\AMQPExchangeType;
  11. use PhpAmqpLib\Message\AMQPMessage;
  12. class Mq
  13. {
  14. private static function connection()
  15. {
  16. $host = config('queue.connections.rabbitmq.host');
  17. $port = config('queue.connections.rabbitmq.port');
  18. $user = config('queue.connections.rabbitmq.user');
  19. $password = config('queue.connections.rabbitmq.password');
  20. $vhost = config('queue.connections.rabbitmq.password');
  21. if (empty($host) || empty($port) || empty($user) || empty($password) || empty($vhost)) {
  22. Log::error('rabbitmq set error');
  23. return;
  24. }
  25. $connection = new AMQPStreamConnection($host, $port, $user, $password, $vhost);
  26. return $connection;
  27. }
  28. public static function publish(string $queue, $message)
  29. {
  30. // 一对一
  31. try {
  32. Log::debug('mq publish', ['queue' => $queue, 'message' => $message]);
  33. $host = config('queue.connections.rabbitmq.host');
  34. $port = config('queue.connections.rabbitmq.port');
  35. $user = config('queue.connections.rabbitmq.user');
  36. $password = config('queue.connections.rabbitmq.password');
  37. $vhost = config('queue.connections.rabbitmq.virtual_host');
  38. if (empty($host) || empty($port) || empty($user) || empty($password) || empty($vhost)) {
  39. Log::error('rabbitmq set error');
  40. return;
  41. }
  42. $connection = new AMQPStreamConnection($host, $port, $user, $password, $vhost);
  43. $channel = $connection->channel();
  44. // $channel->queue_declare($queue, false, true, false, false);
  45. $msgId = Str::uuid();
  46. Log::info("mq push message queue={$queue} id={$msgId}");
  47. $msg = new AMQPMessage(
  48. json_encode($message, JSON_UNESCAPED_UNICODE),
  49. [
  50. 'message_id' => $msgId,
  51. 'content_type' => 'application/json; charset=utf-8',
  52. ]
  53. );
  54. $channel->basic_publish($msg, '', $queue);
  55. $channel->close();
  56. $connection->close();
  57. } catch (\Exception $e) {
  58. Log::error($e);
  59. return;
  60. }
  61. }
  62. /**
  63. * @param string $exchange
  64. * @param string $queue
  65. * @param callable|null $callback
  66. */
  67. public static function worker($exchange, $queue, $callback = null)
  68. {
  69. $consumerTag = 'consumer';
  70. $host = config('queue.connections.rabbitmq.host');
  71. $port = config('queue.connections.rabbitmq.port');
  72. $user = config('queue.connections.rabbitmq.user');
  73. $password = config('queue.connections.rabbitmq.password');
  74. $vhost = config('queue.connections.rabbitmq.virtual_host');
  75. $connection = new AMQPStreamConnection($host, $port, $user, $password, $vhost);
  76. $channel = $connection->channel();
  77. /*
  78. The following code is the same both in the consumer and the producer.
  79. In this way we are sure we always have a queue to consume from and an
  80. exchange where to publish messages.
  81. */
  82. /*
  83. name: $queue
  84. passive: false
  85. durable: true // the queue will survive server restarts
  86. exclusive: false // the queue can be accessed in other channels
  87. auto_delete: false //the queue won't be deleted once the channel is closed.
  88. */
  89. $channel->queue_declare($queue, false, true, false, false);
  90. /*
  91. name: $exchange
  92. type: direct
  93. passive: false
  94. durable: true // the exchange will survive server restarts
  95. auto_delete: false //the exchange won't be deleted once the channel is closed.
  96. */
  97. $channel->exchange_declare($exchange, AMQPExchangeType::DIRECT, false, true, false);
  98. $channel->queue_bind($queue, $exchange);
  99. /**
  100. * @param AMQPMessage $message
  101. */
  102. $process_message = function (AMQPMessage $message) use ($callback, $queue) {
  103. Log::debug('received message', [
  104. 'message_id' => $message->get('message_id'),
  105. 'content_type' => $message->get('content_type'),
  106. ]);
  107. if ($callback !== null) {
  108. try {
  109. $result = $callback(json_decode($message->getBody()), $message->get('message_id'));
  110. Log::debug(
  111. 'mq done',
  112. [
  113. 'message_id' => $message->get('message_id'),
  114. ]
  115. );
  116. if ($result !== 0) {
  117. throw new \Exception('task error');
  118. }
  119. } catch (\Exception $e) {
  120. Log::error("mq worker {$queue} exception", [
  121. 'queue' => $queue,
  122. 'message_id' => $message->get('message_id'),
  123. 'exception' => $e,
  124. ]);
  125. }
  126. if (Tools::isStop()) {
  127. Log::info('mq worker: .stop file exist. cancel the consumer.');
  128. $message->getChannel()->basic_cancel($message->getConsumerTag());
  129. }
  130. }
  131. // exit
  132. foreach (config('mint.mq.loop_limit') as $key => $value) {
  133. if ($queue === $key) {
  134. if ($value > 0) {
  135. if (isset($GLOBALS[$key])) {
  136. $GLOBALS[$key]++;
  137. } else {
  138. $GLOBALS[$key] = 1;
  139. }
  140. if ($GLOBALS[$key] >= $value) {
  141. Log::info("mq exit queue={$queue} loop=".$GLOBALS[$key]);
  142. $message->getChannel()->basic_cancel($message->getConsumerTag());
  143. }
  144. }
  145. }
  146. }
  147. // Send a message with the string "quit" to cancel the consumer.
  148. /*
  149. if ($message->body === 'quit') {
  150. $message->getChannel()->basic_cancel($message->getConsumerTag());
  151. }
  152. */
  153. };
  154. /*
  155. queue: Queue from where to get the messages
  156. consumer_tag: Consumer identifier
  157. no_local: Don't receive messages published by this consumer.
  158. no_ack: If set to true, automatic acknowledgement mode will be used by this consumer. See https://www.rabbitmq.com/confirms.html for details.
  159. exclusive: Request exclusive consumer access, meaning only this consumer can access the queue
  160. nowait:
  161. callback: A PHP Callback
  162. */
  163. $channel->basic_consume($queue, $consumerTag, false, true, false, false, $process_message);
  164. /**
  165. * @param AMQPChannel $channel
  166. * @param AbstractConnection $connection
  167. */
  168. $shutdown = function ($channel, $connection) {
  169. $channel->close();
  170. $connection->close();
  171. };
  172. register_shutdown_function($shutdown, $channel, $connection);
  173. $timeout = 15;
  174. // Loop as long as the channel has callbacks registered
  175. while ($channel->is_consuming()) {
  176. try {
  177. $channel->wait(null, false, $timeout);
  178. } catch (AMQPTimeoutException $e) {
  179. // ignore it
  180. }
  181. }
  182. }
  183. }