Prechádzať zdrojové kódy

Telegram 发送失败的问题

doge 5 dní pred
rodič
commit
f0a1fe5661

+ 45 - 0
app/Jobs/Concerns/HandlesTelegramJobFailure.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace App\Jobs\Concerns;
+
+use Illuminate\Support\Facades\Log;
+use RuntimeException;
+use Throwable;
+
+trait HandlesTelegramJobFailure
+{
+    private function handleTelegramJobFailure(Throwable $exception, string $message, array $context = [], bool $shouldThrow = true): void
+    {
+        $safeMessage = $this->sanitizeTelegramError($exception->getMessage());
+        $willRetry = $shouldThrow && $this->isRetryableTelegramJobError($safeMessage);
+        $context = array_merge($context, [
+            'attempt' => method_exists($this, 'attempts') ? $this->attempts() : 1,
+            'error' => $safeMessage,
+            'exception' => get_class($exception),
+        ]);
+
+        Log::channel('issue')->warning($message, $context + [
+            'will_retry' => $willRetry,
+        ]);
+
+        if ($willRetry) {
+            throw new RuntimeException($safeMessage);
+        }
+    }
+
+    private function sanitizeTelegramError(string $message): string
+    {
+        return preg_replace('/bot\d+:[A-Za-z0-9_-]+/', 'bot[redacted]', $message);
+    }
+
+    private function isRetryableTelegramJobError(string $message): bool
+    {
+        return str_contains($message, 'Too Many Requests')
+            || str_contains($message, 'retry after')
+            || str_contains($message, 'cURL error 7')
+            || str_contains($message, 'cURL error 28')
+            || str_contains($message, 'Failed to connect')
+            || str_contains($message, 'Connection timed out')
+            || str_contains($message, 'Operation timed out');
+    }
+}

+ 17 - 15
app/Jobs/SendTelegramGroupMessageJob.php

@@ -3,20 +3,21 @@
 
 namespace App\Jobs;
 
+use App\Jobs\Concerns\HandlesTelegramJobFailure;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Foundation\Bus\Dispatchable;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
-use Telegram\Bot\Api;
 use Illuminate\Support\Facades\Log;
 use App\Services\BaseService;
-use DragonCode\PrettyArray\Services\Formatters\Base;
-use Telegram\Bot\Exceptions\TelegramResponseException;
 
 class SendTelegramGroupMessageJob implements ShouldQueue
 {
-    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+    use Dispatchable, HandlesTelegramJobFailure, InteractsWithQueue, Queueable, SerializesModels;
+
+    public $tries = 1;
+    public $timeout = 85;
 
     public $chatId;
     public $text;
@@ -45,17 +46,18 @@ class SendTelegramGroupMessageJob implements ShouldQueue
     {
         try {
             BaseService::bettingGroupNotice($this->text, $this->buttons, $this->image, $this->isTop, $this->separator);
-        } catch (TelegramResponseException $e) {
-            try {
-                BaseService::bettingGroupNotice($this->text, $this->buttons, $this->image, $this->isTop, $this->separator);
-            } catch (TelegramResponseException $e) {
-                try {
-                    BaseService::bettingGroupNotice($this->text, $this->buttons, $this->image, $this->isTop, $this->separator);
-                } catch (TelegramResponseException $e) {
-                    Log::error('Telegram 消息发送失败: ' . $e->getMessage());
-                    Log::error("失败消息: \n" . $this->text);
-                }
-            }
+            Log::channel('issue')->info('Telegram群消息发送成功', [
+                'attempt' => $this->attempts(),
+                'text_length' => strlen((string)$this->text),
+                'has_image' => !empty($this->image),
+                'is_top' => $this->isTop,
+            ]);
+        } catch (\Throwable $exception) {
+            $this->handleTelegramJobFailure($exception, 'Telegram群消息发送失败', [
+                'text_length' => strlen((string)$this->text),
+                'has_image' => !empty($this->image),
+                'is_top' => $this->isTop,
+            ], false);
         }
     }
 }

+ 17 - 16
app/Jobs/SendTelegramMessageJob.php

@@ -2,19 +2,21 @@
 
 namespace App\Jobs;
 
+use App\Jobs\Concerns\HandlesTelegramJobFailure;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Foundation\Bus\Dispatchable;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
-use Telegram\Bot\Api;
 use Illuminate\Support\Facades\Log;
 use App\Services\BaseService;
-use DragonCode\PrettyArray\Services\Formatters\Base;
 
 class SendTelegramMessageJob implements ShouldQueue
 {
-    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+    use Dispatchable, HandlesTelegramJobFailure, InteractsWithQueue, Queueable, SerializesModels;
+
+    public $tries = 1;
+    public $timeout = 85;
 
     public $chatId;
     public $text;
@@ -41,19 +43,18 @@ class SendTelegramMessageJob implements ShouldQueue
     {
         try {
             BaseService::sendMessage($this->chatId ,$this->text, $this->buttons, $this->image);
-        } catch (\Telegram\Bot\Exceptions\TelegramResponseException $e) {
-            // 捕获 Too Many Requests
-            if (str_contains($e->getMessage(), 'Too Many Requests')) {
-                preg_match('/retry after (\d+)/', $e->getMessage(), $matches);
-                $retryAfter = $matches[1] ?? 5;
-                Log::warning("Telegram 429 限制,等待 {$retryAfter} 秒重试...");
-                sleep($retryAfter + 1);
-
-                // 重试
-                $this->handle();
-            } else {
-                Log::error('Telegram 消息发送失败: '.$e->getMessage());
-            }
+            Log::channel('issue')->info('Telegram私聊消息发送成功', [
+                'attempt' => $this->attempts(),
+                'chat_id' => $this->chatId,
+                'text_length' => strlen((string)$this->text),
+                'has_image' => !empty($this->image),
+            ]);
+        } catch (\Throwable $exception) {
+            $this->handleTelegramJobFailure($exception, 'Telegram私聊消息发送失败', [
+                'chat_id' => $this->chatId,
+                'text_length' => strlen((string)$this->text),
+                'has_image' => !empty($this->image),
+            ], false);
         }
     }
 }

+ 90 - 7
app/Services/BaseService.php

@@ -195,6 +195,65 @@ abstract class BaseService
         return app(Api::class);
     }
 
+    protected static function sendTelegramRequest(callable $callback, string $action, array $context = [])
+    {
+        $maxAttempts = max(1, (int)config('services.telegram.retry_attempts', 3));
+        $delaySeconds = max(1, (int)config('services.telegram.retry_delay_seconds', 3));
+
+        for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
+            try {
+                return $callback();
+            } catch (\Throwable $exception) {
+                $safeMessage = self::sanitizeTelegramError($exception->getMessage());
+                $logContext = array_merge($context, [
+                    'action' => $action,
+                    'attempt' => $attempt,
+                    'max_attempts' => $maxAttempts,
+                    'error' => $safeMessage,
+                    'exception' => get_class($exception),
+                ]);
+
+                if ($attempt >= $maxAttempts || !self::isRetryableTelegramError($safeMessage)) {
+                    Log::channel('issue')->warning('Telegram请求失败', $logContext);
+                    throw new \RuntimeException($safeMessage);
+                }
+
+                $retryAfter = self::getTelegramRetryAfter($safeMessage, $attempt, $delaySeconds);
+                Log::channel('issue')->warning('Telegram请求失败,准备重试', $logContext + [
+                    'retry_after_seconds' => $retryAfter,
+                ]);
+                sleep($retryAfter);
+            }
+        }
+    }
+
+    protected static function sanitizeTelegramError(string $message): string
+    {
+        return preg_replace('/bot\d+:[A-Za-z0-9_-]+/', 'bot[redacted]', $message);
+    }
+
+    protected static function isRetryableTelegramError(string $message): bool
+    {
+        return str_contains($message, 'Too Many Requests')
+            || str_contains($message, 'retry after')
+            || str_contains($message, 'cURL error 7')
+            || str_contains($message, 'cURL error 28')
+            || str_contains($message, 'Failed to connect')
+            || str_contains($message, 'Connection timed out')
+            || str_contains($message, 'Operation timed out');
+    }
+
+    protected static function getTelegramRetryAfter(string $message, int $attempt, int $delaySeconds): int
+    {
+        $maxDelaySeconds = max(1, (int)config('services.telegram.retry_max_delay_seconds', 10));
+
+        if (preg_match('/retry after (\d+)/i', $message, $matches)) {
+            return min($maxDelaySeconds, (int)$matches[1] + 1);
+        }
+
+        return min($maxDelaySeconds, $delaySeconds * $attempt);
+    }
+
     // /**
     //  * @description: 群组通知(自动分段发送,支持中文与多字节字符)
     //  * @param string $text 通知内容
@@ -278,7 +337,10 @@ abstract class BaseService
             if (count($keyboard) > 0) {
                 $botMsg['reply_markup'] = json_encode(['inline_keyboard' => $keyboard]);
             }
-            $response = self::telegram()->sendPhoto($botMsg);
+            $response = self::sendTelegramRequest(fn() => self::telegram()->sendPhoto($botMsg), 'sendPhoto', [
+                'chat_id' => $botMsg['chat_id'],
+                'has_image' => true,
+            ]);
         } else {
             foreach ($array as $key => $line) {
                 if (empty(str_ireplace(" ", "", str_ireplace("\n", '', $line)))) {
@@ -306,7 +368,11 @@ abstract class BaseService
                 ];
                 if ($index > 0) {
                     $res[] = $botMsg;
-                    self::telegram()->sendMessage($botMsg);
+                    self::sendTelegramRequest(fn() => self::telegram()->sendMessage($botMsg), 'sendMessage', [
+                        'chat_id' => $botMsg['chat_id'],
+                        'chunk_index' => $index,
+                        'has_image' => false,
+                    ]);
                 } else {
                     if (count($keyboard) > 0) {
                         $botMsg['reply_markup'] = json_encode(['inline_keyboard' => $keyboard]);
@@ -318,15 +384,26 @@ abstract class BaseService
                         $botMsg['protect_content'] = true;
 
                         $res[] = $botMsg;
-                        $response = self::telegram()->sendPhoto($botMsg);
+                        $response = self::sendTelegramRequest(fn() => self::telegram()->sendPhoto($botMsg), 'sendPhoto', [
+                            'chat_id' => $botMsg['chat_id'],
+                            'chunk_index' => $index,
+                            'has_image' => true,
+                        ]);
                     } else {
                         $res[] = $botMsg;
-                        $response = self::telegram()->sendMessage($botMsg);
+                        $response = self::sendTelegramRequest(fn() => self::telegram()->sendMessage($botMsg), 'sendMessage', [
+                            'chat_id' => $botMsg['chat_id'],
+                            'chunk_index' => $index,
+                            'has_image' => false,
+                        ]);
                     }
                     if ($isTop === true) {
-                        self::telegram()->pinChatMessage([
+                        self::sendTelegramRequest(fn() => self::telegram()->pinChatMessage([
                             'chat_id' => "@{$bettingGroup}",
                             'message_id' => $response->get('message_id')
+                        ]), 'pinChatMessage', [
+                            'chat_id' => "@{$bettingGroup}",
+                            'message_id' => $response->get('message_id'),
                         ]);
                     }
                 }
@@ -371,10 +448,16 @@ abstract class BaseService
             $botMsg['photo'] = InputFile::create($image);
             $botMsg['caption'] = $text;
             $botMsg['protect_content'] = false;  // 防止转发
-            self::telegram()->sendPhoto($botMsg);
+            self::sendTelegramRequest(fn() => self::telegram()->sendPhoto($botMsg), 'sendPhoto', [
+                'chat_id' => $chatId,
+                'has_image' => true,
+            ]);
         } else {
             $botMsg['text'] = $text;
-            self::telegram()->sendMessage($botMsg);
+            self::sendTelegramRequest(fn() => self::telegram()->sendMessage($botMsg), 'sendMessage', [
+                'chat_id' => $chatId,
+                'has_image' => false,
+            ]);
         }
     }
 

+ 19 - 2
app/Services/BetService.php

@@ -1116,7 +1116,7 @@ class BetService extends BaseService
                     ['text' => lang("开奖历史"), 'callback_data' => "showLotteryHistory@@" . $issue_no]
                 ];
                 // self::sendMessage($v['member_id'],$text2,$keyboard);
-                SendTelegramMessageJob::dispatch($v['member_id'], $text2, $keyboard);
+                SendTelegramMessageJob::dispatch($v['member_id'], $text2, $keyboard)->afterCommit();
             }
 
         }
@@ -1128,8 +1128,25 @@ class BetService extends BaseService
             // 群通知
             $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
             if (($pc28Switch == 0 && is_numeric($issue_no)) || $pc28Switch == 1 && !is_numeric($issue_no)) {
-                SendTelegramGroupMessageJob::dispatch($text, $inlineButton, '', false, '--------------------------------');
+                Log::channel('issue')->info('结算列表群通知入队', [
+                    'issue_no' => $issue_no,
+                    'pc28_switch' => $pc28Switch,
+                    'text_length' => strlen($text),
+                    'open_list_count' => count($openList),
+                ]);
+                SendTelegramGroupMessageJob::dispatch($text, $inlineButton, '', false, '--------------------------------')->afterCommit();
+            } else {
+                Log::channel('issue')->info('结算列表群通知跳过', [
+                    'issue_no' => $issue_no,
+                    'pc28_switch' => $pc28Switch,
+                    'reason' => 'pc28_switch_not_match_issue_type',
+                ]);
             }
+        } else {
+            Log::channel('issue')->info('结算列表群通知跳过', [
+                'issue_no' => $issue_no,
+                'reason' => 'send_telegram_disabled',
+            ]);
         }
 
 

+ 3 - 2
app/Services/IssueService.php

@@ -356,6 +356,7 @@ class IssueService extends BaseService
             $info->status = self::model()::STATUS_DRAW;
             $info->winning_numbers = $winning_numbers;
             $info->combo = $combo;
+            $info->saveQuietly();
 
 
             $size = in_array("大", $awards);
@@ -384,13 +385,13 @@ class IssueService extends BaseService
 
                 }
                 // self::bettingGroupNotice($text, $buttons, $image, true);
-                if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch($text, $buttons, $image, true);
+                if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch($text, $buttons, $image, true)->afterCommit();
             }
 
             $recordImage = self::lotteryImage($info->issue_no);
             if ($recordImage) {
                 // self::bettingGroupNotice('', [], url($recordImage));
-                if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch('', [], url($recordImage), false);
+                if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch('', [], url($recordImage), false)->afterCommit();
             }
             $info->image = $recordImage;
             $info->save();

+ 3 - 0
config/services.php

@@ -11,6 +11,9 @@ return [
     'telegram' => [
         'token' => env('TELEGRAM_BOT_TOKEN'),
         'username' => env('TELEGRAM_BOT_USERNAME'),
+        'retry_attempts' => env('TELEGRAM_RETRY_ATTEMPTS', 3),
+        'retry_delay_seconds' => env('TELEGRAM_RETRY_DELAY_SECONDS', 3),
+        'retry_max_delay_seconds' => env('TELEGRAM_RETRY_MAX_DELAY_SECONDS', 10),
     ],
 
 

+ 3 - 0
example.env

@@ -32,6 +32,9 @@ SMS_CONTENT=
 # TG机器人
 TELEGRAM_BOT_TOKEN=
 TELEGRAM_BOT_USERNAME=
+TELEGRAM_RETRY_ATTEMPTS=3
+TELEGRAM_RETRY_DELAY_SECONDS=3
+TELEGRAM_RETRY_MAX_DELAY_SECONDS=10
 
 # 平台归集地址
 USDT_ADDRESS=