在文章Laravel 將「點閱記錄」改成非同步 queue 寫入資料庫中,有時會發生Redis服務沒啟動,導致系統發生錯誤,針對這個問題,可以建立一個 Laravel Trait,用來檢查資料庫與 Redis 是否有正常連線,並可封裝為共用的「健康檢查套件」。這在系統監控、API 心跳檢查(health check)、DevOps 整合中很實用。
讓它能檢查以下項目:
| 項目 | 驗證內容 |
|---|---|
| Database | 資料庫是否可連線 |
| Redis | Redis 是否能 set/get 成功 |
| Queue | Laravel queue driver 是否正常 |
| Cache | Laravel cache 是否能寫入與讀取 |
步驟一:建立 Trait
<?php
namespace App\Traits;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Cache;
use Illuminate\Queue\RedisQueue;
use App\Jobs\TestQueueJob;
use Throwable;
trait SystemHealthCheck
{
/**
* 檢查資料庫連線
* @return bool
*/
public function checkDatabaseConnection(): bool
{
try {
DB::connection()->getPdo();
return true;
} catch (Throwable $e) {
report($e);
return false;
}
}
/**
* 檢查 Redis 連線
* @return bool
*/
public function checkRedisConnection(): bool
{
try {
Redis::set('health_check', 'ok');
return Redis::get('health_check') === 'ok';
} catch (Throwable $e) {
report($e);
return false;
}
}
/**
* 檢查 Queue 是否可用
* @return bool
*/
public function checkQueue(): bool
{
try {
$connection = Queue::connection();
// 若為 Redis queue 才呼叫 ping()
if ($connection instanceof RedisQueue) {
$ping = $connection->getRedis()->ping();
return $ping === '+PONG';
}
// 其他 driver fallback:派送同步 job 測試
TestQueueJob::dispatchSync();
return true;
} catch (\Throwable $e) {
report($e);
return false;
}
}
/**
* 檢查暫存 Cache 是否可用
* @return bool
*/
public function checkCache(): bool
{
try {
Cache::put('health_check_cache', 'ok', now()->addSeconds(5));
return Cache::get('health_check_cache') === 'ok';
} catch (Throwable $e) {
report($e);
return false;
}
}
/**
* 回報系統狀態
* @return array{cache: string, database: string, queue: string, redis: string}
*/
public function systemStatus(): array
{
return [
'database' => $this->checkDatabaseConnection() ? 'ok' : 'fail',
'redis' => $this->checkRedisConnection() ? 'ok' : 'fail',
'queue' => $this->checkQueue() ? 'ok' : 'fail',
'cache' => $this->checkCache() ? 'ok' : 'fail',
];
}
}
建立 Job: TestQueueJob
<?php
namespace App\Jobs;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Foundation\Queue\Queueable;
class TestQueueJob implements ShouldQueue
{
use Queueable, Dispatchable;
/**
* Execute the job.
*/
public function handle()
{
return true;
}
}步驟二:建立使用該 Trait 的 Controller
// app/Http/Controllers/SystemCheckController.php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use App\Traits\SystemHealthCheck;
class SystemCheckController extends Controller
{
use SystemHealthCheck;
public function status(): JsonResponse
{
$status = $this->systemStatus();
$isHealthy = collect($status)->every(fn ($v) => $v === 'ok');
return response()->json([
'status' => $isHealthy ? 'ok' : 'fail',
'services' => $status
], $isHealthy ? 200 : 500);
}
}步驟三:設定路由
// routes/web.php 或 routes/api.php
use App\Http\Controllers\SystemCheckController;
Route::get('/health-check', [SystemCheckController::class, 'status']);Middleware: LogBlogPostView 程式修正
<?php
namespace App\Http\Middleware;
use App\Jobs\LogBlogPostViewJob;
use App\Traits\SystemHealthCheck;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Models\Blog\BlogPost;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Queue;
class LogBlogPostView
{
use SystemHealthCheck;
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$postId = $request->route('id') ?? $request->route('post'); // 支援不同路由命名
$user = Auth::guard('sanctum')->user();
$user_id = $user?->id;
if ($postId) {
$blogPost = BlogPost::findOrFail($postId);
if ($blogPost) {
$ip = $request->ip();
$host = gethostbyaddr($ip) ?? 'unknown';
$userAgent = $request->header('User-Agent') ?? 'unknown';
$today = Carbon::now()->toDateString();
$cacheKey = "blog_viewed:{$postId}:{$ip}:{$today}:{$user_id}";
// 檢查是否使用 Redis 及可用性
if (env('QUEUE_CONNECTION') == 'redis' && !$this->checkRedisConnection()) {
throw new \Exception("Redis Service is not available", 404);
}
// 先檢查 Cache 若有值,改為非同步任務派送
if (!Cache::has($cacheKey)) {
// 放入table:cache裡
Cache::put($cacheKey, true, now()->endOfDay());
// 放入table:jobs裡, 下面兩行指令是相同, 擇一即可
LogBlogPostViewJob::dispatch($postId, $ip, $host, $userAgent, $user_id);
//Queue::push(new LogBlogPostViewJob($postId, $ip, $host, $userAgent, $user_id));
// 更新文章總點閱數
$blogPost->increment('view_count');
}
}
}
return $next($request);
}
}
問題分析
錯誤訊息:
Call to undefined method Illuminate\Queue\DatabaseQueue::getConnection()表示這行出錯:
$connection = Queue::connection();
$connection->getRedis()->ping(); // ❌ DatabaseQueue 沒這個方法getRedis() 只在 RedisQueue 類別中存在,不適用於 database、sync、sqs 等其他 driver。
正確解法:根據 driver 類型做條件判斷
我們應該偵測目前的 queue driver,只有在使用 redis 時才呼叫 ping(),否則用 fallback 方法(例如 dispatch 測試 job)。
✔ 修正後 checkQueue() 實作:
use Illuminate\Support\Facades\Queue;
use Illuminate\Queue\RedisQueue;
use App\Jobs\TestQueueJob;
public function checkQueue(): bool
{
try {
$connection = Queue::connection();
// 若為 Redis queue 才呼叫 ping()
if ($connection instanceof RedisQueue) {
$ping = $connection->getRedis()->ping();
return $ping === '+PONG';
}
// 其他 driver fallback:派送同步 job 測試
TestQueueJob::dispatchSync();
return true;
} catch (\Throwable $e) {
report($e);
return false;
}
}



