← 返回範例總覽
中等 30 mins

實戰範例 02:修復登入超時無提示錯誤

修復當後端 Session 或 Token 超時後,使用者在前端操作無任何提示、介面卡死,且未自動跳轉至登入頁的體驗錯誤。

使用指令 /opsx:explore/opsx:propose/opsx:apply/opsx:verify/opsx:archive
#Bug修復#Token驗證#攔截器#UI提示

實戰背景

在許多 Web 應用程式中,當使用者閒置時間過長,後端所核發的驗證權杖 (Token) 會超時失效。如果前端沒有妥善處理 API 請求回傳的 401 Unauthorized 錯誤,就會導致使用者點擊任何按鈕皆「無反應」,畫面陷入卡死狀態。

本範例中,我們將透過調查並修改 API 攔截器,實作當偵測到 Token 超時時,主動彈出倒數提示視窗 (Modal) 並安全重導向至登入頁的功能。我們將使用 完整嚴格工作流 (Full Flow) 來完成此項任務。


規格定義

本次變更計畫的 Capability 為 user-authentication。我們預計在規格增量 (Spec Delta) 中定義以下驗收標準:

## MODIFIED Requirements

### Requirement: Session timeout handling
The site SHALL intercept token expiry errors and gracefully guide the user to re-authenticate.

#### Scenario: Unauthorized response redirect
- **WHEN** any API call returns a 401 response status due to token expiry
- **THEN** the site SHALL show a Session Timeout Modal and redirect to `/login` after 3 seconds

端到端 6 輪 AI 協作紀錄

以下是 You 與 AI 助手的完整對話歷史。

── 第 1 輪:探索與診斷 codebase ──

  • You:
    /opsx:explore "使用者說登入久了點按鈕會沒反應,API 回傳 401 但網頁沒動靜。幫我查一下我們專案的 API 請求是怎麼發送的。"
    
  • AI:

    好的,已進入探索模式。我掃描了專案的 codebase:

    1. 我們的 API 呼叫主要封裝在 src/utils/apiClient.ts 中,使用 Axios 套件。
    2. 檢視 apiClient.ts 的內容,發現它只設定了 request 攔截器用來附加 Token,但是完全沒有設定 response 錯誤攔截器

    這是造成該 Bug 的根源:後端回傳 401 錯誤後,Axios 拋出 Promise Reject,但前端代碼中許多地方沒有寫 catch 邏輯,導致錯誤被 Swallow,使用者介面無任何回饋。

    建議方案: 我們應該在 apiClient.ts 加上 response 攔截器,統一捕捉 401 錯誤,彈出 Modal 提示使用者「登入已超時」,並在 3 秒後自動導向 /login

    如果您同意此方案,請退出探索模式並建立變更計畫。


── 第 2 輪:建立提案 ──

  • You:
    /opsx:propose "修復登入超時無提示的 Bug,攔截 401 錯誤並彈出 Modal 3 秒後跳轉"
    
  • AI:

    好的,已為您建立變更計畫 fix-session-timeout-bug

    • 建立 proposal.md: 說明 Why 與 What。
    • 建立 specs/user-authentication/spec.md: 新增超時處理與 401 跳轉的驗收場景 (Scenario)。
    • 建立 tasks.md: 包含以下實作任務:
      • 1.1 在 Axios 攔截器中捕捉 401 狀態碼
      • 1.2 建立全域超時提示 Modal 元件
      • 1.3 串接全域狀態與重導向邏輯
      • 1.4 撰寫模擬 401 錯誤的測試案例

    規劃就緒!請執行 /opsx:apply 開始實作。


── 第 3 輪:實作任務 1 (Axios 攔截器修改) ──

  • You:
    /opsx:apply
    
  • AI:

    Implementing: fix-session-timeout-bug (schema: spec-driven)

    正在處理任務 1/4:在 Axios 攔截器中捕捉 401 狀態碼。

    我已修改了 src/utils/apiClient.ts,加入錯誤響應響應 (Response Error) 攔截器:

    import axios from 'axios';
    import { emitter } from './eventEmitter';
    
    export const apiClient = axios.create({ baseURL: '/api' });
    
    apiClient.interceptors.response.use(
      (response) => response,
      (error) => {
        if (error.response && error.response.status === 401) {
          // 發布全域事件「登入已超時」
          emitter.emit('session-timeout');
        }
        return Promise.reject(error);
      }
    );
    

    ✓ 任務已完成。已將任務 1.1 標記為完成。

    準備好進入任務 1.2「建立超時提示 Modal」了嗎?


── 第 4 輪:實作任務 2 (建立超時 Modal) ──

  • You:
    繼續建立 Modal 元件。
    
  • AI:

    Implementing: fix-session-timeout-bug

    正在處理任務 2/4:建立全域超時提示 Modal 元件。

    我已在 src/components/TimeoutModal.tsx 實作了 React 視窗元件。外觀採用乾淨的單欄排版、紫色按鈕、以及 3 秒倒數計時顯示。

    import React, { useEffect, useState } from 'react';
    
    export const TimeoutModal = () => {
      const [isOpen, setIsOpen] = useState(false);
      const [countdown, setCountdown] = useState(3);
      ...
    };
    

    ✓ 任務已完成。已將任務 1.2 標記為完成。

    下一步將實作全域狀態的訂閱與跳轉邏輯。是否繼續?


── 第 5 輪:實作任務 3 (整合狀態與跳轉) ──

  • You:
    繼續整合跳轉邏輯。
    
  • AI:

    Implementing: fix-session-timeout-bug

    正在處理任務 3/4:串接全域狀態與重導向邏輯。

    我已將 TimeoutModal.tsx 整合入全域 Layout 中,並使用 Event Emitter 訂閱 session-timeout 事件:

    useEffect(() => {
      const handleTimeout = () => {
        setIsOpen(true);
        const timer = setInterval(() => {
          setCountdown((prev) => {
            if (prev <= 1) {
              clearInterval(timer);
              window.location.href = '/login'; // 跳轉至登入頁
            }
            return prev - 1;
          });
        }, 1000);
      };
      emitter.on('session-timeout', handleTimeout);
      return () => emitter.off('session-timeout', handleTimeout);
    , []);
    

    ✓ 任務已完成。已將任務 1.3 標記為完成。

    剩下最後的測試任務。準備好要執行了嗎?


── 第 6 輪:撰寫測試、驗證與歸檔 ──

  • You:
    撰寫測試,然後執行 verify 並歸檔。
    
  • AI:

    Implementing: fix-session-timeout-bug

    正在處理任務 4/4:撰寫模擬 401 錯誤的測試案例。

    我已在 tests/apiClient.test.ts 中使用 mock server 模擬 401 Unauthorized 響應,並成功通過測試,驗證了 session-timeout 事件被觸發且 modal 能正確計時。

    ✓ 4/4 任務全數完成。

    執行自動驗證與歸檔:

    1. 驗證成功:實作細節符合 Scenario: Unauthorized response redirect 的要求。
    2. 智慧同步:已將變更合併至主規格 openspec/specs/user-authentication/spec.md
    3. 目錄歸檔:已移動計畫資料夾至 openspec/changes/archive/2026-07-14-fix-session-timeout-bug/

    Archive Complete

    變更計畫已成功關閉!工作目錄目前非常乾淨。


程式碼與設計亮點

  • 事件驅動解耦:使用事件訂閱機制 (Event Emitter) 讓 API 客戶端與 UI 視窗解耦,API 客戶端無需直接依賴 React 組件。
  • 倒數優雅跳轉:給予使用者 3 秒的緩衝提示,明確告知「登入已超時」才執行跳轉,避免突然的頁面跳轉造成困惑。
  • 寬容退路 (Fallback):若瀏覽器不支援某些 API,則直接降級 (Graceful Degradation) 執行立即重導向。