當公司有多個 WordPress 外掛都需要串接 Google API 時,每個外掛各自實作一份 OAuth 2.0 認證邏輯,不但造成程式碼重複,更增加維護成本。
本文記錄我們將 orca-gsc-insight 外掛中的 OAuth 認證邏輯,經過三個階段的重構,最終抽取為 orca-admin 共用元件的完整過程。
1. 背景:原始架構的問
重構前,ORCA_GSC_OAuth_Api 這個類別身兼兩職:
- Google OAuth 2.0 認證流程(取得授權 URL、處理回調、刷新 Token、撤銷授權)
- Google Search Console API 資料查詢(熱門文章、關鍵字、連線測試)
這產生了幾個問題:
| 問題 | 影響 |
|---|---|
| OAuth 邏輯與 GSC 邏輯混在同一個類別 | 無法複用於 Google Analytics 等其他服務 |
| Option key 硬編碼在認證類別中 | 不同服務無法使用各自的 key 前綴 |
| 每個外掛都要複製一份 OAuth 程式碼 | Bug 修復需要同步多個副本 |
| 快取清除混在 OAuth 撤銷邏輯中 | 認證層不應知道業務層的 transient key |
2. Phase 1 — 職責分離:抽取 OAuth 認證類別
第一步是將 OAuth 認證邏輯從 ORCA_GSC_OAuth_Api 中抽出,建立獨立的 ORCA_Google_OAuth 類別。
重構前的結構
ORCA_GSC_OAuth_Api.php
├── OAuth 認證流程(get_oauth_url, handle_callback, refresh_token...)
├── GSC API 呼叫(get_hot_posts, test_connection...)
└── 快取管理(clear_cache, delete_transient...)
重構後的結構
ORCA_Google_OAuth.php ← 純 OAuth 認證(新檔案)
├── get_oauth_url()
├── handle_oauth_callback()
├── get_access_token()
├── refresh_access_token()
├── revoke_oauth()
├── is_authorized()
└── diagnose_oauth_setup()
ORCA_GSC_OAuth_Api.php ← 純 GSC API 呼叫(瘦身後)
├── __construct(ORCA_Google_OAuth $oauth)
├── get_hot_posts()
├── test_connection()
├── get_keywords_data()
└── clear_cache()
關鍵決策:GSC 呼叫層透過建構子注入 ORCA_Google_OAuth 實例,而非自己建立。這遵循了依賴注入(Dependency Injection)原則。
// ORCA_GSC_OAuth_Api 接收 OAuth 實例,不自己建立
class ORCA_GSC_OAuth_Api {
private $oauth;
public function __construct( ORCA_Google_OAuth $oauth = null ) {
$this->oauth = $oauth ?? self::create_oauth();
}
}
3. Phase 2 — 深度解耦:建構子注入 + 工廠方法
Phase 1 抽出了類別,但 ORCA_Google_OAuth 內部仍硬編碼了 GSC 專用的 option key 前綴、scope 和 redirect URI。這讓它無法直接複用於其他 Google 服務。
問題:硬編碼的設定值
// ❌ 重構前:OAuth 類別硬編碼了 GSC 專用值
class ORCA_Google_OAuth {
private $prefix = 'orca_gsc_insight_oauth'; // 寫死
private $scope = 'https://www.googleapis.com/auth/webmasters.readonly'; // 寫死
}
解法:所有參數由建構子注入
// ✅ 重構後:所有設定由外部注入
class ORCA_Google_OAuth {
private $prefix;
private $scope;
private $redirect_uri;
public function __construct(
string $option_prefix,
string $scope,
string $redirect_uri
) {
$this->prefix = $option_prefix;
$this->scope = $scope;
$this->redirect_uri = $redirect_uri;
}
// Option key 由前綴動態組合
private function opt_client_id() { return $this->prefix . '_client_id'; }
private function opt_client_secret() { return $this->prefix . '_client_secret'; }
private function opt_token() { return $this->prefix . '_token'; }
private function opt_refresh_token() { return $this->prefix . '_refresh_token'; }
// ...
}
工廠方法:集中管理 GSC 專屬設定
既然 ORCA_Google_OAuth 不再知道自己是為 GSC 服務,GSC 專屬的設定就需要一個集中定義的地方。我們在 ORCA_GSC_OAuth_Api 中建立了靜態工廠方法 create_oauth():
class ORCA_GSC_OAuth_Api {
// GSC 專屬常數
private const OAUTH_PREFIX = 'orca_gsc_insight_oauth';
private const OAUTH_SCOPE = 'https://www.googleapis.com/auth/webmasters.readonly';
/**
* 建立 GSC 專用的 OAuth 實例(靜態工廠方法)
*/
public static function create_oauth(): ORCA_Google_OAuth {
return new ORCA_Google_OAuth(
self::OAUTH_PREFIX,
self::OAUTH_SCOPE,
admin_url( 'admin.php?page=orca-gsc-settings&action=oauth_callback' )
);
}
}
所有需要 GSC OAuth 實例的地方(Admin Page、設定頁面、Elementor Handler)統一呼叫 ORCA_GSC_OAuth_Api::create_oauth(),不再自行 new ORCA_Google_OAuth()。
快取清除的分層
撤銷 OAuth 時,原本在 OAuth 類別裡直接清除 GSC 的 transient 快取(orca_gsc_insight_hot_posts)。這違反了關注點分離。重構後:
// ORCA_Google_OAuth::revoke_oauth()
// → 只撤銷 token,不清除任何業務快取
// ORCA_GSC_OAuth_Api::revoke_oauth()
// → 呼叫 OAuth 撤銷 + 清除 GSC 專屬快取
public function revoke_oauth(): bool {
$result = $this->oauth->revoke_oauth(); // OAuth 層:撤銷 token
$this->clear_cache(); // GSC 層:清除業務快取
return $result;
}
未來擴展範例
現在若要新增 Google Analytics 的 OAuth 串接,只需建立新的呼叫層,完全不用修改 OAuth 類別本身:
// 未來新增 GA 串接,只要寫一個新的呼叫層
class ORCA_GA_OAuth_Api {
private const OAUTH_PREFIX = 'orca_ga_oauth';
private const OAUTH_SCOPE = 'https://www.googleapis.com/auth/analytics.readonly';
public static function create_oauth(): ORCA_Google_OAuth {
return new ORCA_Google_OAuth(
self::OAUTH_PREFIX,
self::OAUTH_SCOPE,
admin_url( 'admin.php?page=orca-ga-settings&action=oauth_callback' )
);
}
}
4. Phase 3 — 跨外掛共用:移至 orca-admin
經過前兩階段,ORCA_Google_OAuth 已經是完全通用的 Google OAuth 2.0 認證類別,不包含任何 GSC 或其他服務的邏輯。這正是將它移至共用基底外掛 orca-admin 的最佳時機。
步驟一:移動檔案並更改命名空間
| 項目 | 移動前 | 移動後 |
|---|---|---|
| 檔案位置 | orca-gsc-insight/includes/api/ORCA_Google_OAuth.php | orca-admin/includes/api/ORCA_Google_OAuth.php |
| 命名空間 | OrcaGscInsight | OrcaBiz |
| 載入方式 | 由 orca-gsc-insight 自行 require_once | 由 orca-admin Bootstrap 統一載入 |
步驟二:修正呼叫端引用(3 個檔案)
在 orca-gsc-insight 中,需要修正所有引用 ORCA_Google_OAuth 的地方:
(1)主檔案 orca-gsc-insight.php — 移除 require_once
// ❌ 移除(檔案已不在本專案中)
require_once ORCA_GSC_INSIGHT_DIR . 'includes/api/ORCA_Google_OAuth.php';
// ✅ 改為註解說明來源
// ORCA_Google_OAuth 由 orca-admin 外掛提供(OrcaBiz\ORCA_Google_OAuth)
(2)ORCA_GSC_OAuth_Api.php — 新增跨命名空間 use
namespace OrcaGscInsight;
// ✅ 新增:從 orca-admin 的命名空間引入
use OrcaBiz\ORCA_Google_OAuth;
class ORCA_GSC_OAuth_Api {
// 型別提示 ORCA_Google_OAuth 會透過 use 正確解析
public function __construct( ORCA_Google_OAuth $oauth = null ) { ... }
}
(3)settings-page.php — 更改 use 命名空間
// ❌ 舊的引用
use OrcaGscInsight\ORCA_Google_OAuth;
// ✅ 新的引用
use OrcaBiz\ORCA_Google_OAuth;
步驟三:刪除本地檔案
確認所有引用都已修正後,刪除 orca-gsc-insight 中的本地副本:
rm includes/api/ORCA_Google_OAuth.php
5. 外掛依賴檢查實作
既然 orca-gsc-insight 現在依賴 orca-admin 提供 OAuth 元件,就必須在啟用時檢查依賴是否滿足。我們在主檔案中加入了 admin_init hook:
add_action('admin_init', function () {
if (!function_exists('is_plugin_active')) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
// 檢查 orca-admin 是否啟用
if (!is_plugin_active('orca-admin/orca-admin.php')
&& !class_exists('OrcaBiz\ORCA_Google_OAuth')) {
// 自動停用本外掛
deactivate_plugins(plugin_basename(ORCA_GSC_INSIGHT_FILE));
// 顯示管理員錯誤通知
add_action('admin_notices', function () {
echo '<div class="notice notice-error"><p>';
echo '<strong>ORCA GSC Insights</strong> 需要 ';
echo '<strong>ORCA Admin</strong> 外掛才能運作。';
echo '請先安裝並啟用 <code>orca-admin</code> 外掛。';
echo '</p></div>';
});
if (isset($_GET['activate'])) {
unset($_GET['activate']);
}
}
});
設計重點:
- 使用
is_plugin_active()搭配class_exists()雙重檢查,避免路徑不同造成的誤判 - 不滿足時自動
deactivate_plugins(),防止因缺少類別定義導致 PHP Fatal Error - 清除
$_GET['activate']以避免 WordPress 顯示「外掛已啟用」的誤導訊息
6. 最終架構總覽
┌─────────────────────────────────────────────────────────────┐
│ orca-admin(基底外掛,命名空間 OrcaBiz) │
│ │
│ ORCA_Google_OAuth │
│ ├── __construct($prefix, $scope, $redirect_uri) │
│ ├── get_oauth_url() │
│ ├── handle_oauth_callback($code, $state) │
│ ├── get_access_token() ← 自動處理 Token 過期刷新 │
│ ├── refresh_access_token() │
│ ├── revoke_oauth() ← 只處理 Token,不碰業務快取 │
│ ├── is_authorized() │
│ └── diagnose_oauth_setup() │
└──────────────────────────┬──────────────────────────────────┘
│ use OrcaBiz\ORCA_Google_OAuth
│
┌──────────────────────────▼──────────────────────────────────┐
│ orca-gsc-insight(GSC 外掛,命名空間 OrcaGscInsight) │
│ │
│ ORCA_GSC_OAuth_Api │
│ ├── create_oauth() ← 靜態工廠,注入 GSC 設定 │
│ ├── get_hot_posts() │
│ ├── test_connection() │
│ ├── revoke_oauth() ← 撤銷 Token + 清 GSC 快取 │
│ └── clear_cache() │
│ │
│ ORCA_Admin_Page │
│ └── 所有 OAuth 操作 → ORCA_GSC_OAuth_Api::create_oauth() │
└─────────────────────────────────────────────────────────────┘
資料流:OAuth 授權流程
使用者點擊「Google 授權」
│
▼
Admin Page 呼叫 ORCA_GSC_OAuth_Api::create_oauth()
│ ← 注入 prefix='orca_gsc_insight_oauth', scope, redirect_uri
▼
ORCA_Google_OAuth::get_oauth_url()
│ ← 產生 state、儲存到 wp_options(key = {prefix}_state)
▼
導向 Google 授權頁面 → 使用者同意 → 回調
│
▼
ORCA_Google_OAuth::handle_oauth_callback()
│ ← 驗證 state、交換 code 取得 Token
│ ← 儲存到 wp_options(key = {prefix}_token 等)
▼
授權完成,GSC API 可正常呼叫
Option Keys 對照表
所有 option key 由工廠方法注入的 OAUTH_PREFIX(orca_gsc_insight_oauth)動態組合:
| wp_options Key | 用途 |
|---|---|
orca_gsc_insight_oauth_client_id | OAuth Client ID |
orca_gsc_insight_oauth_client_secret | OAuth Client Secret |
orca_gsc_insight_oauth_token | Access Token |
orca_gsc_insight_oauth_refresh_token | Refresh Token |
orca_gsc_insight_oauth_token_expiry | Token 過期時間 |
orca_gsc_insight_oauth_state | CSRF State |
orca_gsc_insight_oauth_state_time | State 建立時間 |
7. 經驗總結與設計原則
遵循的設計原則
| 原則 | 實踐方式 |
|---|---|
| 單一職責原則(SRP) | OAuth 類別只管認證,GSC 呼叫層只管 API 查詢 |
| 依賴注入(DI) | OAuth 實例透過建構子注入到 GSC 呼叫層 |
| 開放封閉原則(OCP) | 新增 Google 服務只需建新的呼叫層,不修改 OAuth 類別 |
| 工廠方法模式 | create_oauth() 集中管理服務專屬設定 |
| 關注點分離 | 快取清除從 OAuth 層移至業務層 |
版本演進時間線
| 版本 | 內容 |
|---|---|
| v1.3.0 | Phase 1 — 抽取 ORCA_Google_OAuth 類別,職責分離 |
| v1.3.1 | Phase 2 — 建構子注入所有參數、工廠方法、快取分層 |
| v1.3.2 | Phase 3 — 移至 orca-admin,跨外掛共用,依賴檢查 |
給同仁的注意事項
- 不要直接
new ORCA_Google_OAuth()— 請一律使用對應呼叫層的create_oauth()工廠方法,確保設定值集中管理。 - 新增 Google 服務時 — 建立
ORCA_<服務名>_OAuth_Api.php,定義自己的OAUTH_PREFIX和OAUTH_SCOPE常數。 - 撤銷授權時 — 呼叫呼叫層的
revoke_oauth()(會同時清除業務快取),不要直接呼叫 OAuth 類別的revoke_oauth()。 - 外掛依賴 — 任何使用
OrcaBiz\ORCA_Google_OAuth的外掛,都需要加入orca-admin的依賴檢查。
本文記錄 2026-02-21 的重構工作。如有疑問,請聯繫開發團隊。