Browse Source

Merge branch 'development' of https://github.com/visuddhinanda/mint into development

visuddhinanda 9 months ago
parent
commit
f73b1b4601

+ 35 - 38
api-v8/app/Console/Commands/RabbitMQWorker.php

@@ -10,7 +10,8 @@ use App\Jobs\ProcessAITranslateJob;
 use App\Jobs\BaseRabbitMQJob;
 use Illuminate\Support\Facades\Log;
 use PhpAmqpLib\Exception\AMQPTimeoutException;
-
+use PhpAmqpLib\Wire\AMQPTable;
+use App\Services\RabbitMQService;
 
 class RabbitMQWorker extends Command
 {
@@ -30,7 +31,7 @@ class RabbitMQWorker extends Command
     private $queueConfig;
     private $shouldStop = false;
     private $timeout = 15;
-    public function handle()
+    public function handle(RabbitMQService $consume)
     {
         $this->queueName = $this->argument('queue');
         $this->queueConfig = config("mint.rabbitmq.queues.{$this->queueName}");
@@ -48,8 +49,8 @@ class RabbitMQWorker extends Command
         $this->info("重试次数: {$this->queueConfig['retry_times']}");
 
         try {
-            $this->setupConnection();
-            $this->setupQueues();
+            $consume->setupQueue($this->queueName);
+            $this->channel = $consume->getChannel();
             $this->startConsuming();
         } catch (\Exception $e) {
             $this->error("Worker 启动失败: " . $e->getMessage());
@@ -65,26 +66,14 @@ class RabbitMQWorker extends Command
         return 0;
     }
 
-    private function setupConnection()
-    {
-        $config = config('queue.connections.rabbitmq');
-        $this->connection = new AMQPStreamConnection(
-            $config['host'],
-            $config['port'],
-            $config['user'],
-            $config['password'],
-            $config['virtual_host']
-        );
-
-        $this->channel = $this->connection->channel();
-
-        // 设置 QoS - 每次只处理一条消息
-        $this->channel->basic_qos(null, 1, null);
-    }
-
+    /*
     private function setupQueues()
     {
         // 声明主队列
+        $arguments = new AMQPTable([
+            'x-dead-letter-exchange' => '',
+            'x-dead-letter-routing-key' => $this->queueConfig['dead_letter_queue'], // 死信路由键
+        ]);
         $this->channel->queue_declare(
             $this->queueName,
             false,  // passive
@@ -92,23 +81,21 @@ class RabbitMQWorker extends Command
             false,  // exclusive
             false,  // auto_delete
             false,  // nowait
-            [
-                'x-dead-letter-exchange' => ['S', ''],
-                'x-dead-letter-routing-key' => ['S', $this->queueConfig['dead_letter_queue']]
-            ]
+            $arguments
         );
 
         // 声明死信队列
         $dlqName = $this->queueConfig['dead_letter_queue'];
-        $dlqConfig = config("rabbitmq.dead_letter_queues.{$dlqName}", []);
+        $dlqConfig = config("mint.rabbitmq.dead_letter_queues.{$dlqName}", []);
 
         $dlqArgs = [];
         if (isset($dlqConfig['ttl'])) {
-            $dlqArgs['x-message-ttl'] = ['I', $dlqConfig['ttl']];
+            $dlqArgs['x-message-ttl'] =  $dlqConfig['ttl'];
         }
         if (isset($dlqConfig['max_length'])) {
-            $dlqArgs['x-max-length'] = ['I', $dlqConfig['max_length']];
+            $dlqArgs['x-max-length'] =  $dlqConfig['max_length'];
         }
+        $dlqArguments = new AMQPTable($dlqArgs);
 
         $this->channel->queue_declare(
             $dlqName,
@@ -117,12 +104,12 @@ class RabbitMQWorker extends Command
             false,  // exclusive
             false,  // auto_delete
             false,  // nowait
-            $dlqArgs
+            $dlqArguments
         );
 
         $this->info("队列设置完成,死信队列: {$dlqName}");
     }
-
+*/
     private function startConsuming()
     {
         $callback = function (AMQPMessage $msg) {
@@ -170,7 +157,9 @@ class RabbitMQWorker extends Command
     private function processMessage(AMQPMessage $msg)
     {
         try {
-            $data = json_decode($msg->body, true);
+            Log::info('processMessage start', ['message_id' => $msg->get('message_id')]);
+
+            $data = json_decode($msg->getBody(), true);
 
             if (json_last_error() !== JSON_ERROR_NONE) {
                 throw new \Exception("JSON 解析失败: " . json_last_error_msg());
@@ -203,11 +192,14 @@ class RabbitMQWorker extends Command
             Log::error("RabbitMQ 消息处理异常", [
                 'queue' => $this->queueName,
                 'error' => $e->getMessage(),
-                'message_body' => $msg->body
+                'message_body' => $msg->getBody()
             ]);
 
             // 拒绝消息并发送到死信队列
-            $msg->nack(false, false);
+            //$msg->nack(false, false);
+            $this->sendToDeadLetterQueue($data, $e);
+            $msg->ack(); // 确认原消息以避免重复
+            $this->error("已发送到死信队列");
             $this->processedCount++;
         }
     }
@@ -245,17 +237,21 @@ class RabbitMQWorker extends Command
     private function requeueMessage(AMQPMessage $msg, array $data, int $newRetryCount)
     {
         // 添加重试计数到消息头
-        $headers = [
+        // 使用 AMQPTable 包装头部数据
+        $headers = new AMQPTable([
             'retry_count' => $newRetryCount,
             'original_queue' => $this->queueName,
             'retry_timestamp' => time()
-        ];
+        ]);
 
         $newMsg = new AMQPMessage(
-            json_encode($data),
+            json_encode($data, JSON_UNESCAPED_UNICODE),
             [
                 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
-                'application_headers' => $headers
+                'timestamp' => time(),
+                'message_id' => $msg->get('message_id'),
+                'application_headers' => $headers,
+                "content_type" => 'application/json; charset=utf-8'
             ]
         );
 
@@ -299,7 +295,8 @@ class RabbitMQWorker extends Command
         $this->shouldStop = true;
 
         if ($this->channel && $this->channel->is_consuming()) {
-            $this->channel->basic_cancel_on_shutdown(true);
+            //$this->channel->basic_cancel_on_shutdown(true);
+            $this->channel->basic_cancel('');
         }
     }
 

+ 12 - 8
api-v8/app/Console/Commands/TestMq.php

@@ -10,6 +10,7 @@ use App\Models\Discussion;
 use App\Http\Resources\DiscussionResource;
 use App\Models\SentPr;
 use App\Http\Resources\SentPrResource;
+use App\Services\RabbitMQService;
 
 class TestMq extends Command
 {
@@ -19,7 +20,7 @@ class TestMq extends Command
      * @var string
      */
     protected $signature = 'test:mq {--discussion=} {--pr=}';
-
+    protected $publish;
     /**
      * The console command description.
      *
@@ -32,8 +33,9 @@ class TestMq extends Command
      *
      * @return void
      */
-    public function __construct()
+    public function __construct(RabbitMQService $publish)
     {
+        $this->publish = $publish;
         parent::__construct();
     }
 
@@ -44,18 +46,20 @@ class TestMq extends Command
      */
     public function handle()
     {
-        if(\App\Tools\Tools::isStop()){
+        if (\App\Tools\Tools::isStop()) {
             return 0;
         }
-		Mq::publish('hello',['hello world']);
+        $this->publish->publishMessage('ai_translate', ['text' => 'hello']);
+
+        Mq::publish('hello', ['hello world']);
         $discussion = $this->option('discussion');
-        if($discussion && Str::isUuid($discussion)){
-            Mq::publish('discussion',new DiscussionResource(Discussion::find($discussion)));
+        if ($discussion && Str::isUuid($discussion)) {
+            Mq::publish('discussion', new DiscussionResource(Discussion::find($discussion)));
         }
 
         $pr = $this->option('pr');
-        if($pr && Str::isUuid($pr)){
-            Mq::publish('suggestion',new SentPrResource(SentPr::where('uid',$pr)->first()));
+        if ($pr && Str::isUuid($pr)) {
+            Mq::publish('suggestion', new SentPrResource(SentPr::where('uid', $pr)->first()));
         }
 
         return 0;

+ 1 - 1
api-v8/app/Http/Api/Mq.php

@@ -115,7 +115,7 @@ class Mq
         /**
          * @param \PhpAmqpLib\Message\AMQPMessage $message
          */
-        $process_message = function ($message) use ($callback, $queue) {
+        $process_message = function (AMQPMessage $message) use ($callback, $queue) {
             Log::debug('received message', [
                 'message_id' => $message->get('message_id'),
                 'content_type' => $message->get('content_type')

+ 0 - 1
api-v8/app/Http/Controllers/BlogController.php

@@ -34,7 +34,6 @@ class BlogController extends Controller
             ->latest()
             ->paginate(10);
 
-        Log::info($posts[0]->formatted_created_at);
         $categories = $this->categories;
         /*
         $posts = Post::published()

+ 2 - 2
api-v8/app/Jobs/BaseRabbitMQJob.php

@@ -27,12 +27,12 @@ abstract class BaseRabbitMQJob implements ShouldQueue
         $this->currentRetryCount = $retryCount;
 
         // 从配置读取重试次数和超时时间
-        $queueConfig = config("rabbitmq.queues.{$queueName}");
+        $queueConfig = config("mint.rabbitmq.queues.{$queueName}");
         $this->tries = $queueConfig['retry_times'] ?? 3;
         $this->timeout = $queueConfig['timeout'] ?? 300;
     }
 
-    public function handle()
+    public function handle($messageId = null)
     {
         try {
             Log::info("开始处理队列消息", [

+ 1 - 0
api-v8/app/Services/AiTranslateSerevice.php → api-v8/app/Services/AiTranslateService.php

@@ -12,6 +12,7 @@ class AiTranslateService
 
     public function processTranslate(array $translateData): array
     {
+        $a = $translateData['count'] / 10;
         Log::debug('AiTranslateService processOrder', $translateData);
         return [];
     }

+ 253 - 0
api-v8/app/Services/RabbitMQService.php

@@ -0,0 +1,253 @@
+<?php
+
+namespace App\Services;
+
+use PhpAmqpLib\Connection\AMQPStreamConnection;
+use PhpAmqpLib\Channel\AMQPChannel;
+use PhpAmqpLib\Message\AMQPMessage;
+use PhpAmqpLib\Wire\AMQPTable;
+use Illuminate\Support\Facades\Log;
+
+class RabbitMQService
+{
+    private $connection;
+    private $channel;
+    private $config;
+
+    public function __construct()
+    {
+        $this->config = config('queue.connections.rabbitmq');
+        $this->connect();
+    }
+
+    private function connect()
+    {
+        $conn = $this->config;
+        $this->connection = new AMQPStreamConnection(
+            $conn['host'],
+            $conn['port'],
+            $conn['user'],
+            $conn['password'],
+            $conn['virtual_host']
+        );
+
+        $this->channel = $this->connection->channel();
+
+        // 设置 QoS - 每次只处理一条消息
+        $this->channel->basic_qos(null, 1, null);
+    }
+
+    public function getChannel(): AMQPChannel
+    {
+        return $this->channel;
+    }
+
+    public function setupQueue(string $queueName): void
+    {
+        $queueConfig = config("mint.rabbitmq.queues.{$queueName}");
+
+
+
+        // 创建死信交换机
+        $this->channel->exchange_declare(
+            $queueConfig['dead_letter_exchange'],
+            'direct',
+            false,
+            true,
+            false
+        );
+
+        $dlqName = $queueConfig['dead_letter_queue'];
+        $dlqConfig = config("mint.rabbitmq.dead_letter_queues.{$dlqName}", []);
+        $dlqArgs = [];
+        if (isset($dlqConfig['ttl'])) {
+            $dlqArgs['x-message-ttl'] =  $dlqConfig['ttl'];
+        }
+        if (isset($dlqConfig['max_length'])) {
+            $dlqArgs['x-max-length'] =  $dlqConfig['max_length'];
+        }
+        $dlqArguments = new AMQPTable($dlqArgs);
+
+        // 创建死信队列
+        $this->channel->queue_declare(
+            $dlqName,
+            false,  // passive
+            true,   // durable
+            false,  // exclusive
+            false,  // auto_delete
+            false,  // nowait
+            $dlqArguments
+        );
+
+        // 绑定死信队列到死信交换机
+        $this->channel->queue_bind(
+            $queueConfig['dead_letter_queue'],
+            $queueConfig['dead_letter_exchange']
+        );
+
+        // 创建主队列,配置死信
+        $arguments = new AMQPTable([
+            'x-dead-letter-exchange' => $queueConfig['dead_letter_exchange'],
+            'x-dead-letter-routing-key' => $queueConfig['dead_letter_queue'], // 死信路由键
+        ]);
+
+        $this->channel->queue_declare(
+            $queueName,
+            false,  // passive
+            true,   // durable
+            false,  // exclusive
+            false,  // auto_delete
+            false,  // nowait
+            $arguments
+        );
+    }
+
+    public function publishMessage(string $queueName, array $data): bool
+    {
+        try {
+            $this->setupQueue($queueName);
+
+            $message = new AMQPMessage(
+                json_encode($data, JSON_UNESCAPED_UNICODE),
+                [
+                    'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
+                    'timestamp' => time(),
+                    'message_id' => uniqid(),
+                    "content_type" => 'application/json; charset=utf-8'
+                ]
+            );
+
+            $this->channel->basic_publish($message, '', $queueName);
+
+            Log::info("Message published to queue: {$queueName}", $data);
+            return true;
+        } catch (\Exception $e) {
+            Log::error("Failed to publish message to queue: {$queueName}", [
+                'error' => $e->getMessage(),
+                'data' => $data
+            ]);
+            return false;
+        }
+    }
+
+    public function consume(string $queueName, callable $callback, int $maxIterations = null): void
+    {
+        $this->setupQueue($queueName);
+        $maxIterations = $maxIterations ?? $this->config['consumer']['max_iterations'];
+        $iteration = 0;
+
+        $consumerCallback = function (AMQPMessage $msg) use ($callback, $queueName, &$iteration) {
+            try {
+                $data = json_decode($msg->getBody(), true);
+                $retryCount = $this->getRetryCount($msg);
+                $maxRetries = $this->config['queues'][$queueName]['retry_count'];
+
+                Log::info("Processing message from queue: {$queueName}", [
+                    'data' => $data,
+                    'retry_count' => $retryCount,
+                    'delivery_tag' => $msg->getDeliveryTag()
+                ]);
+
+                // 执行回调处理消息
+                $result = $callback($data, $retryCount);
+
+                if ($result === true) {
+                    // 处理成功,确认消息
+                    $msg->ack();
+                    Log::info("Message processed successfully", ['delivery_tag' => $msg->getDeliveryTag()]);
+                } else {
+                    // 处理失败,检查重试次数
+                    if ($retryCount < $maxRetries) {
+                        // 重新入队,延迟处理
+                        $this->requeueWithDelay($msg, $queueName, $retryCount + 1);
+                        Log::warning("Message requeued for retry", [
+                            'delivery_tag' => $msg->getDeliveryTag(),
+                            'retry_count' => $retryCount + 1
+                        ]);
+                    } else {
+                        // 超过重试次数,拒绝消息(进入死信队列)
+                        $msg->nack(false, false);
+                        Log::error("Message rejected after max retries", [
+                            'delivery_tag' => $msg->getDeliveryTag(),
+                            'retry_count' => $retryCount
+                        ]);
+                    }
+                }
+            } catch (\Exception $e) {
+                Log::error("Error processing message", [
+                    'error' => $e->getMessage(),
+                    'delivery_tag' => $msg->getDeliveryTag()
+                ]);
+                $msg->nack(false, false);
+            }
+
+            $iteration++;
+        };
+
+        $this->channel->basic_qos(null, 1, null);
+        $this->channel->basic_consume($queueName, '', false, false, false, false, $consumerCallback);
+
+        Log::info("Starting consumer for queue: {$queueName}", ['max_iterations' => $maxIterations]);
+
+        while ($this->channel->is_consuming() && $iteration < $maxIterations) {
+            $this->channel->wait(null, false, $this->config['consumer']['sleep_between_iterations']);
+        }
+
+        Log::info("Consumer stopped", ['iterations_processed' => $iteration]);
+    }
+
+    private function getRetryCount(AMQPMessage $msg): int
+    {
+        $headers = $msg->get_properties();
+        return isset($headers['application_headers']['x-retry-count'])
+            ? $headers['application_headers']['x-retry-count'] : 0;
+    }
+
+    private function requeueWithDelay(AMQPMessage $msg, string $queueName, int $retryCount): void
+    {
+        $delay = $this->config['queues'][$queueName]['retry_delay'];
+
+        // 创建延迟队列
+        $delayQueueName = "{$queueName}_delay_{$retryCount}";
+        $arguments = new AMQPTable([
+            'x-message-ttl' => $delay,
+            'x-dead-letter-exchange' => '',
+            'x-dead-letter-routing-key' => $queueName,
+        ]);
+
+        $this->channel->queue_declare(
+            $delayQueueName,
+            false,
+            true,
+            false,
+            false,
+            false,
+            $arguments
+        );
+
+        // 重新发布消息到延迟队列
+        $data = json_decode($msg->getBody(), true);
+        $newMessage = new AMQPMessage(
+            json_encode($data),
+            [
+                'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
+                'application_headers' => new AMQPTable([
+                    'x-retry-count' => $retryCount
+                ])
+            ]
+        );
+
+        $this->channel->basic_publish($newMessage, '', $delayQueueName);
+        $msg->ack();
+    }
+
+    public function close(): void
+    {
+        if ($this->channel) {
+            $this->channel->close();
+        }
+        if ($this->connection) {
+            $this->connection->close();
+        }
+    }
+}

+ 4 - 3
api-v8/config/mint.php

@@ -127,10 +127,11 @@ return [
     'rabbitmq' => [
         'queues' => [
             'ai_translate' => [
-                'retry_times' => env('RABBITMQ_ORDERS_RETRY_TIMES', 3),
-                'max_loop_count' => env('RABBITMQ_ORDERS_MAX_LOOP', 10),
-                'timeout' => env('RABBITMQ_ORDERS_TIMEOUT', 300),
+                'retry_times' => env('RABBITMQ_AI_RETRY_TIMES', 3),
+                'max_loop_count' => env('RABBITMQ_AI_MAX_LOOP', 10),
+                'timeout' => env('RABBITMQ_AI_TIMEOUT', 300),
                 'dead_letter_queue' => 'ai_translate_dlq',
+                'dead_letter_exchange' => 'ai_translate_dlx',
             ],
         ],