Showing
4 changed files
with
79 additions
and
1 deletions
| ... | @@ -126,6 +126,10 @@ This creates `config/daito-google-chat.php`. | ... | @@ -126,6 +126,10 @@ This creates `config/daito-google-chat.php`. |
| 126 | ```dotenv | 126 | ```dotenv |
| 127 | DAITO_GOOGLE_CHAT_ENABLED=true | 127 | DAITO_GOOGLE_CHAT_ENABLED=true |
| 128 | DAITO_GOOGLE_CHAT_WEBHOOK_URL=https://chat.googleapis.com/v1/spaces/.../messages?key=...&token=... | 128 | DAITO_GOOGLE_CHAT_WEBHOOK_URL=https://chat.googleapis.com/v1/spaces/.../messages?key=...&token=... |
| 129 | +DAITO_GOOGLE_CHAT_QUEUE_NAME=google-chat | ||
| 130 | +DAITO_GOOGLE_CHAT_RATE_LIMIT_ENABLED=true | ||
| 131 | +DAITO_GOOGLE_CHAT_RATE_LIMIT_MAX_JOBS=20 | ||
| 132 | +DAITO_GOOGLE_CHAT_RATE_LIMIT_DECAY_SECONDS=60 | ||
| 129 | ``` | 133 | ``` |
| 130 | 134 | ||
| 131 | ### 3) Send immediately | 135 | ### 3) Send immediately |
| ... | @@ -195,6 +199,41 @@ DaitoGoogleChat::sendCardV2($arrCardV2); | ... | @@ -195,6 +199,41 @@ DaitoGoogleChat::sendCardV2($arrCardV2); |
| 195 | DaitoGoogleChat::queueCardV2($arrCardV2); | 199 | DaitoGoogleChat::queueCardV2($arrCardV2); |
| 196 | ``` | 200 | ``` |
| 197 | 201 | ||
| 202 | +### 8) Anti-spam notes (recommended for production) | ||
| 203 | + | ||
| 204 | +- Queue job is rate-limited globally by cache key (`daito-google-chat:webhook`). | ||
| 205 | +- Default limit is `20` jobs / `60` seconds (configurable). | ||
| 206 | +- Keep `queue_backoff_seconds` as-is to retry safely on transient errors. | ||
| 207 | + | ||
| 208 | +Main config keys in `config/daito-google-chat.php`: | ||
| 209 | + | ||
| 210 | +- `rate_limit_enabled` | ||
| 211 | +- `rate_limit_max_jobs` | ||
| 212 | +- `rate_limit_decay_seconds` | ||
| 213 | +- `rate_limit_key` | ||
| 214 | +- `queue_name` (default: `google-chat`) | ||
| 215 | + | ||
| 216 | +### 9) Run dedicated queue worker for google-chat | ||
| 217 | + | ||
| 218 | +Run a separate worker to isolate notification throughput: | ||
| 219 | + | ||
| 220 | +```bash | ||
| 221 | +php artisan queue:work --queue=google-chat --sleep=1 --tries=3 --timeout=30 | ||
| 222 | +``` | ||
| 223 | + | ||
| 224 | +Supervisor example: | ||
| 225 | + | ||
| 226 | +```ini | ||
| 227 | +[program:laravel-google-chat-worker] | ||
| 228 | +process_name=%(program_name)s_%(process_num)02d | ||
| 229 | +command=php /path/to/artisan queue:work --queue=google-chat --sleep=1 --tries=3 --timeout=30 | ||
| 230 | +autostart=true | ||
| 231 | +autorestart=true | ||
| 232 | +numprocs=1 | ||
| 233 | +redirect_stderr=true | ||
| 234 | +stdout_logfile=/var/log/supervisor/laravel-google-chat-worker.log | ||
| 235 | +``` | ||
| 236 | + | ||
| 198 | ## QueryLog (Laravel shared package) | 237 | ## QueryLog (Laravel shared package) |
| 199 | 238 | ||
| 200 | > **Provider registration note** | 239 | > **Provider registration note** | ... | ... |
| ... | @@ -2,6 +2,7 @@ | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | ||
| 3 | namespace Daito\Lib\DaitoGoogleChat\Jobs; | 3 | namespace Daito\Lib\DaitoGoogleChat\Jobs; |
| 4 | 4 | ||
| 5 | +use Daito\Lib\DaitoGoogleChat\Middleware\DaitoGoogleChatRateLimitMiddleware; | ||
| 5 | use Daito\Lib\DaitoGoogleChat\Services\DaitoGoogleChatWebhookClient; | 6 | use Daito\Lib\DaitoGoogleChat\Services\DaitoGoogleChatWebhookClient; |
| 6 | use Illuminate\Bus\Queueable; | 7 | use Illuminate\Bus\Queueable; |
| 7 | use Illuminate\Contracts\Queue\ShouldQueue; | 8 | use Illuminate\Contracts\Queue\ShouldQueue; |
| ... | @@ -31,4 +32,15 @@ class DaitoGoogleChatWebhookJob implements ShouldQueue | ... | @@ -31,4 +32,15 @@ class DaitoGoogleChatWebhookJob implements ShouldQueue |
| 31 | { | 32 | { |
| 32 | $client->send($this->arrPayload, $this->webhookUrl); | 33 | $client->send($this->arrPayload, $this->webhookUrl); |
| 33 | } | 34 | } |
| 35 | + | ||
| 36 | + public function middleware(): array | ||
| 37 | + { | ||
| 38 | + if (!(bool) config('daito-google-chat.rate_limit_enabled', true)) { | ||
| 39 | + return array(); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + return array( | ||
| 43 | + new DaitoGoogleChatRateLimitMiddleware(), | ||
| 44 | + ); | ||
| 45 | + } | ||
| 34 | } | 46 | } | ... | ... |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Daito\Lib\DaitoGoogleChat\Middleware; | ||
| 4 | + | ||
| 5 | +use Illuminate\Support\Facades\RateLimiter; | ||
| 6 | + | ||
| 7 | +class DaitoGoogleChatRateLimitMiddleware | ||
| 8 | +{ | ||
| 9 | + public function handle($job, $next): void | ||
| 10 | + { | ||
| 11 | + $rateLimitKey = (string) config('daito-google-chat.rate_limit_key', 'daito-google-chat:webhook'); | ||
| 12 | + $maxJobs = max(1, (int) config('daito-google-chat.rate_limit_max_jobs', 20)); | ||
| 13 | + $decaySeconds = max(1, (int) config('daito-google-chat.rate_limit_decay_seconds', 60)); | ||
| 14 | + | ||
| 15 | + if (RateLimiter::tooManyAttempts($rateLimitKey, $maxJobs)) { | ||
| 16 | + $job->release(max(1, RateLimiter::availableIn($rateLimitKey))); | ||
| 17 | + return; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + RateLimiter::hit($rateLimitKey, $decaySeconds); | ||
| 21 | + $next($job); | ||
| 22 | + } | ||
| 23 | +} |
| ... | @@ -12,8 +12,12 @@ return array( | ... | @@ -12,8 +12,12 @@ return array( |
| 12 | 'retry_times' => env('DAITO_GOOGLE_CHAT_RETRY_TIMES', 1), | 12 | 'retry_times' => env('DAITO_GOOGLE_CHAT_RETRY_TIMES', 1), |
| 13 | 'retry_sleep_ms' => env('DAITO_GOOGLE_CHAT_RETRY_SLEEP_MS', 200), | 13 | 'retry_sleep_ms' => env('DAITO_GOOGLE_CHAT_RETRY_SLEEP_MS', 200), |
| 14 | 'queue_connection' => env('DAITO_GOOGLE_CHAT_QUEUE_CONNECTION', null), | 14 | 'queue_connection' => env('DAITO_GOOGLE_CHAT_QUEUE_CONNECTION', null), |
| 15 | - 'queue_name' => env('DAITO_GOOGLE_CHAT_QUEUE_NAME', null), | 15 | + 'queue_name' => env('DAITO_GOOGLE_CHAT_QUEUE_NAME', 'google-chat'), |
| 16 | 'queue_tries' => env('DAITO_GOOGLE_CHAT_QUEUE_TRIES', 3), | 16 | 'queue_tries' => env('DAITO_GOOGLE_CHAT_QUEUE_TRIES', 3), |
| 17 | 'queue_backoff_seconds' => env('DAITO_GOOGLE_CHAT_QUEUE_BACKOFF', 10), | 17 | 'queue_backoff_seconds' => env('DAITO_GOOGLE_CHAT_QUEUE_BACKOFF', 10), |
| 18 | + 'rate_limit_enabled' => env('DAITO_GOOGLE_CHAT_RATE_LIMIT_ENABLED', true), | ||
| 19 | + 'rate_limit_max_jobs' => env('DAITO_GOOGLE_CHAT_RATE_LIMIT_MAX_JOBS', 20), | ||
| 20 | + 'rate_limit_decay_seconds' => env('DAITO_GOOGLE_CHAT_RATE_LIMIT_DECAY_SECONDS', 60), | ||
| 21 | + 'rate_limit_key' => env('DAITO_GOOGLE_CHAT_RATE_LIMIT_KEY', 'daito-google-chat:webhook'), | ||
| 18 | 'throw_on_error' => env('DAITO_GOOGLE_CHAT_THROW_ON_ERROR', false), | 22 | 'throw_on_error' => env('DAITO_GOOGLE_CHAT_THROW_ON_ERROR', false), |
| 19 | ); | 23 | ); | ... | ... |
-
Please register or sign in to post a comment