user = $user; } public function handle(GmailParserService $parserService) { Log::info("====== [DEBUG START] 開始執行 Gmail 抓信任務 ======"); if (!$this->user->google_refresh_token) { Log::error("❌ 失敗:User ID {$this->user->id} 缺乏 google_refresh_token"); return; } try { Log::info("🔄 正在初始化 Google Client & 刷新 Token..."); $client = new GoogleClient(); $client->setClientId(config('services.google.client_id')); $client->setClientSecret(config('services.google.client_secret')); $client->refreshToken($this->user->google_refresh_token); $gmailService = new Gmail($client); $afterDate = Carbon::now()->subDays(3)->format('Y/m/d'); $searchQuery = "from:cathaybk.com.tw subject:消費彙整通知 after:{$afterDate}"; Log::info("🔍 正在向 Google API 發送搜尋 Query: [{$searchQuery}]"); $response = $gmailService->users_messages->listUsersMessages('me', [ 'q' => $searchQuery, 'maxResults' => 10 ]); $messages = $response->getMessages(); if (empty($messages)) { Log::warning("⚠️ 警告:Google API 回傳空陣列!"); return; } Log::info("🎯 成功拿到信件清單!總共發現 " . count($messages) . " 封潛在信件,開始逐封處理..."); foreach ($messages as $index => $msgItem) { $msgId = $msgItem->getId(); Log::info("--- [處理第 " . ($index + 1) . " 封信] ID: {$msgId} ---"); $message = $gmailService->users_messages->get('me', $msgId, ['format' => 'full']); $payload = $message->getPayload(); $subject = ''; foreach ($payload->getHeaders() as $header) { if ($header->getName() === 'Subject') { $subject = $header->getValue(); break; } } Log::info("📬 信件主旨: [{$subject}]"); $body = $this->getDecodedBody($payload); Log::info("📄 內文解碼完成,準備丟入 Parser..."); // 1. 執行解析(現在國泰會回報陣列,其餘單筆通知我們手動把它包成陣列) $parsedResult = $parserService->parse($subject, $body); if (!$parsedResult) { Log::error("❌ 失敗:GmailParserService 無法解析此信件內容"); continue; } // 💡 智慧防呆:如果回傳的是單筆消費(一維陣列),我們把它包成二維陣列,這樣下面統一用 foreach 處理 $expensesList = isset($parsedResult['amount']) ? [$parsedResult] : $parsedResult; Log::info("✨ Parser 解析成功!共發現 " . count($expensesList) . " 筆刷卡消費項目,開始遍歷寫入..."); // 🔄 遍歷這封信裡面的每一筆刷卡紀錄 foreach ($expensesList as $parsedData) { // 🟢 2. 智慧資產帳戶對照 $matchedAccountRule = \App\Models\AccountRule::where('user_id', $this->user->id) ->get() ->first(function ($rule) use ($subject) { return Str::contains($subject, $rule->keyword); }); $accountId = $matchedAccountRule ? $matchedAccountRule->account_id : null; // 🟢 3. 智慧消費分類對照 $matchedCategoryRule = \App\Models\CategoryRule::where('user_id', $this->user->id) ->get() ->first(function ($rule) use ($parsedData) { return Str::contains(strtoupper($parsedData['seller_name']), strtoupper($rule->keyword)); }); $categoryId = $matchedCategoryRule ? $matchedCategoryRule->category_id : null; // 4. 💾 嘗試寫入資料庫 $expense = Expense::firstOrCreate( [ 'user_id' => $this->user->id, 'amount' => $parsedData['amount'], 'seller_name' => $parsedData['seller_name'], 'date' => $parsedData['date'], ], [ 'item_name' => $parsedData['item_name'], 'account_id' => $accountId, 'category_id' => $categoryId, 'note' => 'Gmail自動同步(多筆完全體)', ] ); if ($expense->wasRecentlyCreated) { Log::info("✅ 成功!建立全新消費: [{$parsedData['seller_name']}] 額度: [{$parsedData['amount']}], ID: {$expense->id}"); } else { Log::info("ℹ️ 提示:[{$parsedData['seller_name']}] 額度: [{$parsedData['amount']}] 已存在,跳過。"); } } } Log::info("====== [DEBUG END] Gmail 抓信任務順利結束 ======"); } catch (\Exception $e) { Log::error("💥 崩潰!handle 內發生致命錯誤: " . $e->getMessage()); } } private function getDecodedBody($part) { $body = $part->getBody(); if ($body && $body->getData()) { return base64_decode(str_replace(['-', '_'], ['+', '/'], $body->getData())); } $parts = $part->getParts(); if ($parts) { foreach ($parts as $subPart) { $result = $this->getDecodedBody($subPart); if ($result) return $result; } } return ''; } }