打造 AI 友善的專案文檔:CLAUDE.md 完整指南(中)


系列文章:本文是 CLAUDE.md 完整指南系列的第二篇,深入探討複雜架構的文檔化技巧。

📖 上一篇:基礎概念與核心章節


🎯 前言

在上一篇文章中,我們介紹了 CLAUDE.md 的基礎概念和 7 個核心章節。但當你面對一個複雜的 Legacy 專案時,僅僅知道章節架構是不夠的。

本篇文章將深入探討:

  • 🏗️ 如何描述多租戶架構讓 AI 理解
  • 🐳 Docker 環境的完整文檔化策略
  • 🧪 測試流程的自動化文檔
  • 📊 760 行 CLAUDE.md 的實戰拆解

🏗️ 多租戶架構的文檔化

多租戶(Multi-tenant)架構是 Legacy 專案常見的複雜設計,每個租戶(機構/客戶)使用獨立的資料庫。這種架構對 AI 來說特別難理解,因為涉及動態切換和複雜的資料隔離。

📋 架構概述模板

首先,用清晰的圖示和說明讓 Claude 理解整體架構:

## 多租戶資料庫架構

### 架構圖

\`\`\`
請求 → Nginx → PHP-FPM → Session 讀取 OrgID → 動態連接對應資料庫

資料庫列表:
- system_main (id=1)       ← 系統主資料庫
- demo-org-2 (id=2)        ← 機構 A 的資料庫
- demo-org-3 (id=3)        ← 機構 B 的資料庫
- demo-org-n (id=n)        ← 機構 N 的資料庫
\`\`\`

### 核心機制

1. **登入時**:系統根據帳號查詢所屬機構,設定 `$_SESSION['mcareDBno']`
2. **運行時**:DBPDO 類別讀取 Session 動態連接資料庫
3. **隔離性**:每個 SQL 查詢自動限定在當前機構的資料庫內

💡 為什麼這樣寫

  • 使用簡單的 ASCII 圖示說明流程
  • 明確標示關鍵變數名稱($_SESSION['mcareDBno']
  • 說明三個關鍵時機(登入、運行、隔離)

🔐 Session 動態切換的實作細節

這是多租戶架構的核心,必須詳細說明:

### Session 變數與資料庫切換

#### 關鍵 Session 變數

| 變數名稱 | 用途 | 範例值 | 重要性 |
|:--------|:-----|:------|:------|
| `$_SESSION['mcareDBno']` | 當前機構的資料庫名稱 | `demo-org-2` | ⭐⭐⭐ |
| `$_SESSION['OrgID']` | 機構 ID | `2` | ⭐⭐⭐ |
| `$_SESSION['mOrgGroup']` | 機構群組 ID | `G0001` | ⭐⭐ |
| `$_SESSION['mcareID']` | 使用者 ID(登入驗證) | `123` | ⭐⭐⭐ |

#### 資料庫連接流程

**1. 初始化階段**`DBPDO.php`

\`\`\`php
// 讀取 Session 中的資料庫名稱
$dbName = $_SESSION['mcareDBno'] ?? 'demo-org-1';

// 建立 PDO 連接
$dsn = "mysql:host=mysql;dbname={$dbName};charset=utf8";
$pdo = new PDO($dsn, 'dbuser', 'dbpass');
\`\`\`

**2. 查詢階段**

所有 SQL 查詢自動在當前資料庫執行:

\`\`\`php
// 查詢當前機構的病患資料
$stmt = $pdo->prepare("SELECT * FROM patient WHERE status = 1");
$stmt->execute();
// ✅ 只會查詢 demo-org-2 資料庫的 patient 表
\`\`\`

**3. 切換機構**(管理員功能)

\`\`\`php
// 切換到其他機構(需權限驗證)
$_SESSION['mcareDBno'] = 'demo-org-3';
$_SESSION['OrgID'] = 3;

// 重新初始化資料庫連接
$pdo = null;  // 關閉舊連接
require_once 'DBPDO.php';  // 建立新連接
\`\`\`

#### ⚠️ 常見陷阱

1. **直接操作 PDO 而不透過 DBPDO**
   ```php
   // ❌ 錯誤:直接建立連接,無法切換資料庫
   $pdo = new PDO("mysql:host=mysql;dbname=demo-org-1", ...);

   // ✅ 正確:使用 DBPDO 類別
   require_once 'class/DBPDO.php';
   $pdo = DBPDO::getInstance();
  1. 忘記在跨模組操作時保持 Session

    // ❌ 錯誤:AJAX 請求未帶 Session
    fetch('/api/getData.php');
    
    // ✅ 正確:確保 Session 傳遞
    fetch('/api/getData.php', { credentials: 'include' });
  2. SQL 查詢未考慮機構隔離

    // ❌ 危險:可能查到其他機構的資料
    SELECT * FROM patient WHERE id = $userId;
    
    // ✅ 正確:加上機構 ID 條件(冗餘但安全)
    SELECT * FROM patient
    WHERE id = $userId AND OrgID = $_SESSION['OrgID'];

**💡 為什麼寫這麼詳細**:
- Claude 需要理解 Session 在多租戶架構中的關鍵作用
- 明確指出常見錯誤,避免 AI 給出危險建議
- 提供正確與錯誤的程式碼對比

---

### 🔒 多層級權限系統的文檔化

複雜的權限系統往往讓 AI 困惑,導致建議的功能繞過權限檢查。

```markdown
### 權限系統架構

#### 資料庫表結構

\`\`\`
permission2          ← 主分類(如:病患管理、報表系統)

permission_subcate   ← 子分類(如:病患基本資料、病患評估)

permission_item      ← 具體項目(如:新增病患、編輯病患、刪除病患)

user_permission2     ← 使用者權限關聯(哪些使用者有哪些權限)

org_permission       ← 機構權限設定(哪些機構啟用哪些功能)
\`\`\`

#### 權限檢查流程

**1. 頁面載入時檢查**

\`\`\`php
// 檢查使用者是否有「病患管理」權限
$stmt = $pdo->prepare("
    SELECT COUNT(*) as has_permission
    FROM user_permission2 up
    INNER JOIN permission_item pi ON up.serNo = pi.serNo
    WHERE up.userID = :user_id
      AND up.OrgID = :org_id
      AND pi.item_name = 'patient_management'
      AND up.level = '1'
");
$stmt->execute([
    'user_id' => $_SESSION['userID'],
    'org_id' => $_SESSION['OrgID']
]);
$result = $stmt->fetch();

if ($result['has_permission'] == 0) {
    die('無權限訪問此頁面');
}
\`\`\`

**2. API 端點檢查**

\`\`\`php
// 每個 AJAX 端點都需要權限檢查
// File: ajax/savePatient.php

session_start();

// 1. 檢查登入
if (!isset($_SESSION['mcareID'])) {
    die(json_encode(['error' => '未登入']));
}

// 2. 檢查權限
if (!checkPermission($_SESSION['userID'], 'patient_edit')) {
    die(json_encode(['error' => '無權限']));
}

// 3. 執行操作
// ...
\`\`\`

#### ⚠️ 重要規則

**Claude 必須遵守的權限原則**:

1. **新增功能時,必須加入權限檢查**
   - 不要假設「使用者已經過權限驗證」
   - 每個新的 PHP 檔案都要有 `checkPermission()` 呼叫

2. **不要建議繞過權限的「快速解法」**
   - ❌ 錯誤:「先註解掉權限檢查來測試功能」
   - ✅ 正確:「先在資料庫中新增對應的權限項目」

3. **權限分離原則**
   - `user_permission2`:控制「誰」可以使用
   - `org_permission`:控制「哪些機構」啟用功能
   - 兩者必須同時滿足才能訪問

#### 實際案例

**需求**:新增「批次匯出 TypeA 表單」功能

**完整步驟**:

1. 新增權限項目
   \`\`\`sql
   INSERT INTO permission_item (item_name, subcateID, description)
   VALUES ('export_typeA_batch', 12, '批次匯出 TypeA 表單');
   \`\`\`

2. 新增機構權限
   \`\`\`sql
   INSERT INTO org_permission (serNo, OrgID, status)
   SELECT serNo, 2, '1'
   FROM permission_item
   WHERE item_name = 'export_typeA_batch';
   \`\`\`

3. 新增使用者權限
   \`\`\`sql
   INSERT INTO user_permission2 (userID, OrgID, serNo, level)
   SELECT 1, 2, serNo, '1'
   FROM permission_item
   WHERE item_name = 'export_typeA_batch';
   \`\`\`

4. 在程式碼中檢查權限
   \`\`\`php
   // File: module_a/export/batchTypeA.php
   if (!checkPermission($_SESSION['userID'], 'export_typeA_batch')) {
       die('無權限');
   }
   \`\`\`

💡 為什麼這麼重要

  • Legacy 專案的權限系統通常非常複雜
  • AI 容易建議「先跳過權限來測試」,這會留下安全漏洞
  • 明確說明完整流程,讓 Claude 給出安全的建議

🔄 資料庫遷移工具的說明

多租戶架構下,資料庫 schema 變更需要同步到所有機構,這是容易出錯的環節。

### 資料庫遷移工具

#### runSQL.sh - 批次執行 SQL

**用途**:在所有機構資料庫中執行 schema 變更

**基本用法**

\`\`\`bash
# 所有資料庫執行 SQL
./bin/runSQL.sh -a -f ./sql/add_column_patient.sql

# 只在包含 patient 表的資料庫執行
./bin/runSQL.sh -a -t patient -f ./sql/update_patient_schema.sql

# 指定單一資料庫
./bin/runSQL.sh -n demo-org-2 -f ./sql/script.sql
\`\`\`

**參數說明**

| 參數 | 說明 | 範例 |
|:-----|:-----|:-----|
| `-a` | 所有資料庫 | `-a` |
| `-n` | 指定資料庫名稱 | `-n demo-org-2` |
| `-o` | 指定機構 ID | `-o 2` |
| `-t` | 只在包含此表的資料庫執行 | `-t patient` |
| `-f` | SQL 檔案路徑 | `-f ./sql/script.sql` |

#### 遷移日誌追蹤

**migratelog 表結構**

\`\`\`sql
CREATE TABLE migratelog (
    id INT AUTO_INCREMENT PRIMARY KEY,
    filename VARCHAR(255),          -- SQL 檔案名稱
    file_hash VARCHAR(32),          -- MD5 hash(防止重複執行)
    executed_at DATETIME,           -- 執行時間
    status ENUM('init', 'applied'), -- 狀態
    notes TEXT                      -- 備註
);
\`\`\`

**使用流程**

1. **檢查是否已執行**
   \`\`\`bash
   # runSQL.sh 會自動檢查 migratelog
   # 如果 file_hash 已存在且 status='applied',則跳過
   \`\`\`

2. **記錄執行狀態**
   \`\`\`sql
   -- 開始執行時
   INSERT INTO migratelog (filename, file_hash, status)
   VALUES ('add_column_patient.sql', 'a1b2c3d4...', 'init');

   -- 執行成功後
   UPDATE migratelog
   SET status = 'applied', executed_at = NOW()
   WHERE file_hash = 'a1b2c3d4...';
   \`\`\`

#### ⚠️ 注意事項

1. **SQL 檔案命名規範**
   \`\`\`
   YYYYMMDD_description.sql

   範例:
   20260107_add_export_date_column.sql
   20260107_update_patient_index.sql
   \`\`\`

2. **SQL 內容要求**
   \`\`\`sql
   -- ✅ 好的寫法:使用 IF NOT EXISTS
   ALTER TABLE patient
   ADD COLUMN IF NOT EXISTS export_date DATE;

   -- ❌ 壞的寫法:直接 ADD,重複執行會報錯
   ALTER TABLE patient ADD COLUMN export_date DATE;
   \`\`\`

3. **無法 Rollback**
   - ⚠️ 目前工具不支援自動回滾
   - 需要手動撰寫反向 SQL 檔案
   - 重要變更前務必備份資料庫

4. **測試流程**
   \`\`\`bash
   # 1. 先在測試資料庫執行
   ./bin/runSQL.sh -n demo-org-test -f ./sql/new_migration.sql

   # 2. 檢查結果
   mysql -u dbuser -p demo-org-test -e "DESCRIBE patient;"

   # 3. 確認無誤後,批次執行
   ./bin/runSQL.sh -a -t patient -f ./sql/new_migration.sql
   \`\`\`

#### Claude 應該知道的

當用戶說「幫我新增一個欄位到 patient 表」時,完整流程是:

1. 建立 SQL 檔案(使用日期命名)
2. 使用 `IF NOT EXISTS` 語法
3. 使用 `runSQL.sh` 執行
4. 檢查 `migratelog` 確認執行狀態
5. 提醒用戶確認所有機構資料庫都已更新

**不要建議**
- ❌ 直接執行 ALTER TABLE(會漏掉其他機構)
- ❌ 手動逐個資料庫執行(容易遺漏)
- ❌ 忘記檢查 migratelog

💡 為什麼詳細記錄

  • 多租戶架構的 schema 變更容易遺漏某些資料庫
  • AI 需要知道完整的遷移流程
  • 避免建議危險的快捷方式

🐳 Docker 環境的完整文檔化

Docker 環境的文檔化不僅僅是列出 docker-compose.yml,而是要說明為什麼這樣配置常見問題的解決方案

📦 三層架構的詳細說明

## Docker 環境架構

### 服務架構圖

\`\`\`
請求流程:
Client → Nginx (Port 80) → PHP-FPM (Port 9000) → MySQL (Port 3306)

            XDebug (Port 9003) → IDE (VS Code)
\`\`\`

### 服務詳細配置

#### 1. nginx (Web Server)

**Dockerfile / Image**: `nginx:latest`

**配置檔**: `Docker/nginx/default.conf`

\`\`\`nginx
server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;

    # PHP-FPM 設定
    location ~ \.php$ {
        fastcgi_pass php-fpm:9000;  # ← 連接到 PHP-FPM 容器
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}
\`\`\`

**關鍵點**
- `fastcgi_pass php-fpm:9000`:使用 Docker 內部網路連接(不是 localhost)
- `$document_root`:對應容器內的 `/var/www/html`

#### 2. php-fpm (PHP-FPM)

**Dockerfile / Image**: `somosyampi/docker-php-fpm:7.4-xdebug`

**Volume 掛載**
\`\`\`yaml
volumes:
  - ./AppSystem:/var/www/html
  - ./Docker/fpm-conf/php.ini:/usr/local/etc/php/php.ini
  - ./Docker/fpm-conf/docker-php-ext-xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
\`\`\`

**PHP 配置** (`php.ini`):
\`\`\`ini
memory_limit = 128M
max_execution_time = 30
upload_max_filesize = 10M
post_max_size = 10M
\`\`\`

**為什麼這樣設定**
- `memory_limit = 128M`:批次輸出大量資料需要較高記憶體
- `max_execution_time = 30`:單一請求不應超過 30 秒(批次操作使用背景任務)

#### 3. mysql (資料庫)

**Dockerfile / Image**: `mysql:5.7`

**為什麼用 5.7 而不是 8.0**
- Legacy 程式碼依賴舊版 GROUP BY 語法
- 升級需大量測試和重構
- 5.7 持續提供安全更新至 2023 年(已過期但內部使用可接受)

**自訂配置** (`my-custom.cnf`):
\`\`\`ini
[mysqld]
sql_mode=''                          # 關閉嚴格模式
innodb_strict_mode=OFF               # 關閉 InnoDB 嚴格模式
group_concat_max_len=4294967295      # 增加 GROUP_CONCAT 長度限制
explicit_defaults_for_timestamp=OFF  # 舊版 timestamp 行為
\`\`\`

**為什麼關閉嚴格模式**
- Legacy SQL 不符合 SQL 標準(例如 SELECT 包含非 GROUP BY 欄位)
- 重構成本高,風險大
- 內部系統可接受此風險

💡 為什麼包含「為什麼」

  • AI 看到「關閉嚴格模式」可能會建議「應該修正 SQL 符合標準」
  • 明確說明原因和權衡,避免不切實際的建議

🐛 XDebug 除錯設定(跨平台相容)

XDebug 配置是 Docker 開發環境的痛點,需要詳細說明各平台差異。

### XDebug 設定指南

#### 配置檔案

**位置**: `Docker/fpm-conf/docker-php-ext-xdebug.ini`

\`\`\`ini
[xdebug]
zend_extension=xdebug
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_port=9003
xdebug.client_host=host.docker.internal  # ← 關鍵設定
xdebug.idekey=VSCODE
xdebug.log=/tmp/xdebug.log
\`\`\`

#### 跨平台差異

| 平台 | `xdebug.client_host` 設定 | 說明 |
|:-----|:-------------------------|:-----|
| **Windows (Docker Desktop)** | `host.docker.internal` | ✅ 推薦,無需額外設定 |
| **macOS (Docker Desktop)** | `host.docker.internal` | ✅ 推薦,無需額外設定 |
| **Linux (原生 Docker)** | `172.17.0.1` 或閘道 IP | ⚠️ 需要查詢閘道 IP |
| **WSL2 (Windows)** | `host.docker.internal` | ✅ Docker Desktop for Windows 支援 |

#### Linux 特殊設定

**查詢閘道 IP**
\`\`\`bash
# 方法 1:查看 docker0 網卡
ip addr show docker0 | grep inet

# 方法 2:在容器內查看
docker exec -it php-fpm ip route | grep default

# 輸出範例:
# default via 172.17.0.1 dev eth0
#             ^^^^^^^^^^ 這就是閘道 IP
\`\`\`

**更新配置**
\`\`\`ini
; Linux 使用閘道 IP
xdebug.client_host=172.17.0.1
\`\`\`

#### VS Code 配置

**`.vscode/launch.json`**:
\`\`\`json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug on Docker",
            "type": "php",
            "request": "launch",
            "port": 9003,
            "pathMappings": {
                "/var/www/html/": "${workspaceFolder}/AppSystem"
            },
            "log": true  // ← 開啟除錯日誌
        }
    ]
}
\`\`\`

**重要參數說明**
- `port`: 9003(XDebug 3.x 預設 port,舊版是 9000)
- `pathMappings`: 容器內路徑 → 本地路徑的映射
- `log: true`: 當連接失敗時,可以查看詳細日誌

#### 常見問題排查

**問題 1:無法連接到 XDebug**

\`\`\`bash
# 1. 檢查 XDebug 是否啟用
docker exec -it php-fpm php -v
# 應該看到:with Xdebug v3.x.x

# 2. 檢查 port 9003 是否開放
docker exec -it php-fpm netstat -tuln | grep 9003

# 3. 查看 XDebug 日誌
docker exec -it php-fpm cat /tmp/xdebug.log
\`\`\`

**問題 2:中斷點不會停止**

可能原因:
1. **路徑映射錯誤**
   \`\`\`json
   // ❌ 錯誤:路徑不匹配
   "pathMappings": {
       "/var/www/html": "${workspaceFolder}"
   }

   // ✅ 正確:確保對應到 AppSystem 目錄
   "pathMappings": {
       "/var/www/html/": "${workspaceFolder}/AppSystem"
   }
   \`\`\`

2. **XDebug 未自動啟動**
   \`\`\`ini
   ; 確保設定為 yes
   xdebug.start_with_request=yes
   \`\`\`

3. **防火牆阻擋**
   \`\`\`bash
   # Windows: 允許 port 9003
   # Linux: 檢查 iptables 規則
   \`\`\`

**問題 3:Mac M1/M2 效能問題**

\`\`\`yaml
# docker-compose.yml
services:
  php-fpm:
    platform: linux/amd64  # ← 強制使用 x86 模擬
    # 注意:會降低效能,但提升相容性
\`\`\`

#### 驗證步驟

**完整測試流程**

1. **啟動 Docker 環境**
   \`\`\`bash
   docker-compose up -d
   \`\`\`

2. **驗證 XDebug 配置**
   \`\`\`bash
   docker exec -it php-fpm php -i | grep xdebug
   \`\`\`

3. **在 VS Code 啟動除錯**(F5)

4. **訪問測試頁面**
   \`\`\`bash
   curl http://localhost/test.php
   \`\`\`

5. **檢查是否在中斷點停止**

如果失敗,依序檢查:
- XDebug 日誌 (`/tmp/xdebug.log`)
- VS Code 輸出面板(Debug Console)
- Docker logs (`docker logs php-fpm`)

💡 為什麼如此詳細

  • XDebug 設定是開發環境最常見的問題
  • 跨平台差異讓 AI 容易給出不適用的建議
  • 詳細的排查步驟讓 Claude 能夠協助除錯

🔧 常見問題與解決方案

將常見問題直接寫入 CLAUDE.md,可以大幅減少重複解釋。

### Docker 環境常見問題

#### 問題 1:容器無法啟動

**症狀**
\`\`\`bash
docker-compose up -d
# Error: port 80 is already in use
\`\`\`

**原因**:本機已有服務佔用 port 80(如 IIS、Apache)

**解決方案**
\`\`\`bash
# Windows: 停止 IIS
net stop was /y
net stop w3svc

# 或修改 docker-compose.yml 使用其他 port
ports:
  - "8080:80"  # 改用 8080
\`\`\`

#### 問題 2:PHP 檔案顯示為純文字

**症狀**:訪問 `http://localhost/index.php` 顯示原始碼

**原因**:Nginx 沒有正確轉發給 PHP-FPM

**排查步驟**
\`\`\`bash
# 1. 檢查 PHP-FPM 是否運行
docker ps | grep php-fpm

# 2. 檢查 Nginx 配置
docker exec -it nginx nginx -t

# 3. 檢查 fastcgi_pass 設定
docker exec -it nginx cat /etc/nginx/conf.d/default.conf | grep fastcgi_pass
# 應該看到:fastcgi_pass php-fpm:9000;
\`\`\`

#### 問題 3:MySQL 連接失敗

**症狀**
\`\`\`
SQLSTATE[HY000] [2002] Connection refused
\`\`\`

**常見原因**

1. **Host 名稱錯誤**
   \`\`\`php
   // ❌ 錯誤:在 Docker 內不能用 localhost
   $pdo = new PDO('mysql:host=localhost;...', ...);

   // ✅ 正確:使用容器名稱
   $pdo = new PDO('mysql:host=mysql;...', ...);
   \`\`\`

2. **MySQL 尚未啟動完成**
   \`\`\`bash
   # 查看 MySQL 啟動狀態
   docker logs mysql
   # 等待看到:ready for connections
   \`\`\`

3. **帳號密碼錯誤**
   \`\`\`bash
   # 進入 MySQL 容器測試
   docker exec -it mysql mysql -u dbuser -p
   \`\`\`

#### 問題 4:檔案權限問題(Linux)

**症狀**
\`\`\`
Warning: file_put_contents(): Permission denied
\`\`\`

**原因**:Docker 容器內的 www-data 使用者無法寫入本地檔案

**解決方案**
\`\`\`bash
# 方法 1:修改本地目錄權限
sudo chown -R 33:33 ./AppSystem/uploads
# 33 是 www-data 的 UID

# 方法 2:在 docker-compose.yml 設定使用者
services:
  php-fpm:
    user: "1000:1000"  # 使用當前使用者的 UID
\`\`\`

#### 問題 5:容器內記憶體不足

**症狀**
\`\`\`
Fatal error: Allowed memory size exhausted
\`\`\`

**排查**
\`\`\`bash
# 1. 檢查 PHP memory_limit
docker exec -it php-fpm php -i | grep memory_limit

# 2. 檢查 Docker 記憶體限制
docker stats
\`\`\`

**解決方案**
\`\`\`yaml
# docker-compose.yml
services:
  php-fpm:
    mem_limit: 512m  # 增加容器記憶體限制
\`\`\`

\`\`\`ini
# Docker/fpm-conf/php.ini
memory_limit = 256M  # 增加 PHP 記憶體限制
\`\`\`

💡 為什麼收集常見問題

  • 減少用戶重複詢問相同問題
  • Claude 可以直接參考解決方案
  • 新手開發者也能快速解決問題

🧪 測試流程自動化的文檔化

測試流程的文檔化不僅僅是記錄測試指令,更重要的是說明測試策略自動化方法

📝 使用 curl 模擬完整登入流程

## 自動化測試流程

### curl 測試完整指南

#### 為什麼使用 curl?

-**可腳本化**:可以寫入 Shell 腳本自動執行
-**可重現**:相同指令產生相同結果
-**無需 GUI**:適合 CI/CD 環境
-**快速驗證**:不需要啟動瀏覽器

#### 完整登入流程

**步驟 1:建立開發登入工具**

\`\`\`php
<?php
// File: _dev_login.php
// ⚠️ 僅供本機開發使用,勿部署到生產環境

session_start();

// 從 URL 參數或使用預設值
$_SESSION['mcareDBno'] = $_GET['db'] ?? 'demo-org-2';
$_SESSION['userID'] = $_GET['uid'] ?? 1;
$_SESSION['OrgID'] = $_GET['oid'] ?? 2;
$_SESSION['mOrgGroup'] = $_GET['ogid'] ?? 'G0001';
$_SESSION['mcareID'] = $_SESSION['userID'];  // 登入驗證
$_SESSION['loginuser'] = $_GET['username'] ?? 'testuser';
$_SESSION['name'] = $_GET['name'] ?? '測試人員';
$_SESSION['mOrgName'] = $_GET['orgname'] ?? '測試機構';

// 重導向(如有指定)
if (isset($_GET['redirect'])) {
    header('Location: ' . $_GET['redirect']);
} else {
    echo 'Session 已設定';
}
?>
\`\`\`

**步驟 2:使用 curl 建立 Session**

\`\`\`bash
# 建立 session 並儲存 cookie
curl -s -c /tmp/cookies.txt "http://localhost/_dev_login.php?db=demo-org-2&oid=2" > /dev/null

# 查看儲存的 cookie
cat /tmp/cookies.txt
# 應該看到 PHPSESSID
\`\`\`

**步驟 3:使用 cookie 訪問受保護的頁面**

\`\`\`bash
# GET 請求
curl -b /tmp/cookies.txt "http://localhost/module_a/dashboard.php"

# POST 請求
curl -b /tmp/cookies.txt -X POST \
  -d "action=getData" \
  -d "id=123" \
  "http://localhost/module_a/ajax/getData.php"
\`\`\`

#### 實際測試案例

**案例 1:測試批次輸出功能**

\`\`\`bash
#!/bin/bash
# File: test_batch_export.sh

# 設定
BASE_URL="http://localhost"
COOKIE_FILE="/tmp/test_cookies.txt"

# 清理舊 cookie
rm -f $COOKIE_FILE

# 1. 建立 session
echo "建立 session..."
curl -s -c $COOKIE_FILE "${BASE_URL}/_dev_login.php?db=demo-org-2" > /dev/null

# 2. 測試批次輸出
echo "測試批次輸出..."
curl -b $COOKIE_FILE -X POST \
  -d "all_records=3,225" \
  -d "form_serNo[]=form_typeA_12a" \
  -d "start_date=2020/01/01" \
  -d "end_date=2025/12/31" \
  "${BASE_URL}/module_a/export.php?mod=batch&func=formview&id=3&type=2" \
  -o /tmp/export_result.html

# 3. 驗證結果
if [ -f /tmp/export_result.html ]; then
    LINE_COUNT=$(wc -l < /tmp/export_result.html)
    echo "✅ 輸出成功!共 $LINE_COUNT 行"

    # 檢查是否包含預期內容
    if grep -q "TypeA 表單" /tmp/export_result.html; then
        echo "✅ 內容驗證通過"
    else
        echo "❌ 內容驗證失敗"
        exit 1
    fi
else
    echo "❌ 輸出失敗"
    exit 1
fi

# 清理
rm -f $COOKIE_FILE /tmp/export_result.html

echo "✅ 所有測試通過"
\`\`\`

**案例 2:測試 API 端點**

\`\`\`bash
#!/bin/bash
# File: test_api.sh

test_api() {
    local endpoint=$1
    local expected=$2

    echo "測試: $endpoint"
    response=$(curl -b /tmp/cookies.txt -s "${BASE_URL}${endpoint}")

    if echo "$response" | grep -q "$expected"; then
        echo "✅ 通過"
        return 0
    else
        echo "❌ 失敗"
        echo "預期: $expected"
        echo "實際: $response"
        return 1
    fi
}

# 建立 session
curl -s -c /tmp/cookies.txt "http://localhost/_dev_login.php" > /dev/null

# 測試多個端點
test_api "/module_a/ajax/getPatientList.php" "success"
test_api "/module_a/ajax/getFormData.php?id=123" "form_data"
test_api "/module_a/ajax/saveRecord.php" "saved"

echo "測試完成"
\`\`\`

#### curl 進階技巧

**1. 顯示完整 HTTP 標頭**

\`\`\`bash
curl -v -b /tmp/cookies.txt "http://localhost/module_a/dashboard.php"
# -v: verbose,顯示完整請求/回應
\`\`\`

**2. 只顯示 HTTP 狀態碼**

\`\`\`bash
curl -o /dev/null -s -w "%{http_code}\n" -b /tmp/cookies.txt "http://localhost/api/test"
# 輸出:200
\`\`\`

**3. 測試 JSON API**

\`\`\`bash
curl -b /tmp/cookies.txt \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{"action":"getData","id":123}' \
  "http://localhost/api/v1/data"
\`\`\`

**4. 效能測試**

\`\`\`bash
# 測試回應時間
curl -b /tmp/cookies.txt \
  -w "\nTime: %{time_total}s\n" \
  -o /dev/null -s \
  "http://localhost/module_a/export.php?mod=batch"
\`\`\`

**5. 並行測試**

\`\`\`bash
# 模擬多個使用者同時訪問
for i in {1..10}; do
    curl -b /tmp/cookies.txt "http://localhost/api/test" &
done
wait
echo "並行測試完成"
\`\`\`

💡 為什麼詳細記錄測試流程

  • Claude 可以自動生成測試腳本
  • 新功能開發時,AI 可以建議對應的測試方法
  • 減少手動測試的時間

🎯 讓 Claude 自動執行測試

這是進階技巧:讓 Claude 不僅建議測試方法,還能實際執行測試驗證功能。

### Claude 自動化測試指南

#### 如何讓 Claude 執行測試

**1. 準備測試環境**

\`\`\`bash
# 確保 Docker 環境運行
docker-compose up -d

# 建立測試專用的 cookie
curl -s -c /tmp/claude_test_cookies.txt "http://localhost/_dev_login.php?db=demo-org-2" > /dev/null
\`\`\`

**2. 告訴 Claude 測試步驟**

當你完成功能開發後,可以這樣請求 Claude:

\`\`\`
我:幫我測試剛才新增的批次輸出功能

Claude 應該執行:
1. 使用已存在的測試 cookie
2. 執行 curl 命令測試端點
3. 檢查回應內容是否正確
4. 報告測試結果
\`\`\`

**3. 標準測試模板**

在 CLAUDE.md 中提供標準測試模板,讓 Claude 可以直接使用:

\`\`\`bash
# 測試模板 1:GET 端點
curl -b /tmp/claude_test_cookies.txt \
  "http://localhost${ENDPOINT}${PARAMS}" \
  | grep -q "${EXPECTED_STRING}" \
  && echo "✅ 測試通過" \
  || echo "❌ 測試失敗"

# 測試模板 2:POST 端點
curl -b /tmp/claude_test_cookies.txt \
  -X POST \
  -d "${POST_DATA}" \
  "http://localhost${ENDPOINT}" \
  -s -o /tmp/test_result.json

if jq -e '.success == true' /tmp/test_result.json > /dev/null; then
    echo "✅ 測試通過"
else
    echo "❌ 測試失敗"
    cat /tmp/test_result.json
fi
\`\`\`

**4. 實際使用範例**

**對話範例**

\`\`\`
你:幫我新增一個 API 端點,取得 patient ID=123 的資料

Claude:
[建立 PHP 檔案...]

你:幫我測試這個端點

Claude:我來測試這個端點
[執行]
curl -b /tmp/claude_test_cookies.txt \
  "http://localhost/module_a/ajax/getPatient.php?id=123" \
  -s | jq .

結果:
{
  "success": true,
  "data": {
    "id": 123,
    "name": "測試病患",
    ...
  }
}

✅ API 端點正常運作!回傳了病患資料。
\`\`\`

#### 測試失敗時的排查

當測試失敗時,Claude 應該依序檢查:

1. **Session 是否有效**
   \`\`\`bash
   curl -b /tmp/claude_test_cookies.txt \
     "http://localhost/checkSession.php"
   \`\`\`

2. **端點是否存在(HTTP 404)**
   \`\`\`bash
   curl -I -b /tmp/claude_test_cookies.txt \
     "http://localhost/module_a/ajax/newEndpoint.php"
   # 檢查狀態碼
   \`\`\`

3. **PHP 錯誤**
   \`\`\`bash
   docker logs php-fpm --tail 50
   # 查看最近的 PHP 錯誤
   \`\`\`

4. **權限問題**
   \`\`\`bash
   curl -b /tmp/claude_test_cookies.txt \
     "http://localhost/module_a/ajax/newEndpoint.php" \
     -s | grep -i "permission\|權限"
   \`\`\`

#### Claude 應該避免的

**不要執行危險的測試**
- 刪除資料的操作(除非在測試資料庫)
- 修改正式環境的設定
- 大量並行請求(可能造成伺服器負載)

**應該執行的安全測試**
- 讀取資料的 API
- 在測試資料庫中的寫入操作
- UI 頁面的載入測試
- 功能性驗證

#### 測試最佳實踐

1. **使用測試資料庫**
   \`\`\`bash
   # 建立測試專用 session
   curl -s -c /tmp/test_cookies.txt \
     "http://localhost/_dev_login.php?db=demo-org-test"
   \`\`\`

2. **測試前確認環境**
   \`\`\`bash
   # 檢查 Docker 容器狀態
   docker ps | grep -E "nginx|php-fpm|mysql"
   \`\`\`

3. **測試後清理**
   \`\`\`bash
   # 刪除測試產生的檔案
   rm -f /tmp/test_*.html /tmp/test_cookies.txt
   \`\`\`

4. **記錄測試結果**
   \`\`\`bash
   # 將測試結果寫入日誌
   echo "$(date): Test completed" >> test_log.txt
   \`\`\`

💡 為什麼這樣設計

  • 讓 Claude 不只是「建議」測試方法,而是「實際執行」測試
  • 提供標準模板,確保測試的一致性
  • 明確告知安全邊界,避免危險操作

📊 實際案例解析

讓我們拆解那份 760 行的 CLAUDE.md,看看每個章節如何撰寫。

📐 760 行 CLAUDE.md 的結構拆解

## 完整 CLAUDE.md 結構分析

### 行數分布

| 章節 | 行數 | 佔比 | 重要性 |
|:-----|:-----|:-----|:------|
| 目錄與說明 | 20 | 2.6% | ⭐ |
| 快速開始 | 100 | 13.2% | ⭐⭐⭐ |
| 專案概述 | 50 | 6.6% | ⭐⭐⭐ |
| 開發環境 | 200 | 26.3% | ⭐⭐⭐ |
| 程式架構 | 250 | 32.9% | ⭐⭐⭐ |
| 測試與自動化 | 100 | 13.2% | ⭐⭐ |
| 開發規範 | 40 | 5.3% | ⭐⭐ |
| **總計** | **760** | **100%** | - |

### 為什麼程式架構佔最多篇幅?

**原因 1:Legacy 專案的複雜性**
- 多租戶架構需要詳細解釋
- Session 管理機制不明顯
- 權限系統有 5 層資料表

**原因 2:AI 最容易誤解的部分**
- PHP include 路徑解析規則
- 變數作用域繼承
- 資料庫動態切換

**原因 3:影響最廣**
- 所有功能開發都涉及架構
- 錯誤理解會導致整批建議錯誤
- 一次說明清楚,終身受益

### 撰寫思路

#### 第一稿(3 小時):快速建立骨架

\`\`\`markdown
1. 複製 README.md 作為基礎
2. 新增「快速開始」章節
3. 列出核心目錄結構
4. 記錄 Docker 啟動指令
\`\`\`

#### 第二稿(1 小時):補充遇到的問題

每次與 Claude 溝通遇到誤解時,就補充對應章節:

- Claude 建議用 Eloquent → 補充「資料庫連接架構」
- Claude 建議升級 PHP 8 → 補充「技術限制」
- Claude 不理解路徑 → 補充「Include 路徑解析」

#### 第三稿(持續更新):優化與重構

- 新增實際案例(curl 測試流程)
- 新增常見問題 FAQ
- 調整章節順序(重要的放前面)

🎓 每個章節的撰寫技巧

### 技巧 1:快速開始 - 30 秒原則

**目標**:讓 Claude(或新人)在 30 秒內啟動專案

**必須包含**
1. 一鍵啟動指令
2. 測試帳號
3. 驗證方法

**範例**
\`\`\`markdown
## ⚡ 快速開始

\`\`\`bash
# 1. 啟動(10 秒)
docker-compose up -d

# 2. 驗證(5 秒)
curl http://localhost
# 應該看到:登入頁面

# 3. 測試帳號(5 秒)
帳號:demo
密碼:Demo@123
\`\`\`
\`\`\`

### 技巧 2:專案概述 - 用 Emoji 和表格

**目標**:用視覺元素快速傳達核心特性

**範例**
\`\`\`markdown
## 📋 專案概述

### 核心特點

- 🏥 **多模組系統**: ModuleA、ModuleB、ModuleC
- 🗄️ **多租戶架構**: 每個機構獨立資料庫
- 🐳 **Docker 部署**: Nginx + PHP-FPM + MySQL
- 📊 **批次輸出**: 支援大量資料匯出

### 技術棧

| 層級 | 技術 | 版本 | 原因 |
|:-----|:-----|:-----|:-----|
| 前端 | jQuery | 3.x | Legacy 相容性 |
| 後端 | PHP | 7.4 | 無法升級 8.x |
| 資料庫 | MySQL | 5.7 | 舊版 SQL 語法 |
| 容器 | Docker | 20.x | - |
\`\`\`

### 技巧 3:開發環境 - 從問題出發

**目標**:預先回答「為什麼這樣設定」

**範例**
\`\`\`markdown
## 🛠️ 開發環境

### Docker 設定

#### 為什麼用 MySQL 5.7 而不是 8.0?

- ✅ Legacy SQL 不符合嚴格模式
- ✅ 升級成本高,風險大
- ❌ 8.0 的 GROUP BY 嚴格檢查會破壞現有查詢

#### 為什麼關閉 sql_mode?

\`\`\`ini
sql_mode=''  # 關閉所有限制
\`\`\`

原因:
- 程式碼有 500+ 個 SELECT 使用非標準語法
- 重構需 2-3 個月,風險不可控
- 內部系統,可接受此風險
\`\`\`

### 技巧 4:程式架構 - 用實例說明

**目標**:不只說「是什麼」,更要說「怎麼用」

**範例**
\`\`\`markdown
## 🏗️ 程式架構

### PHP Include 路徑解析

#### ❌ 錯誤理解

「include 會搜尋整個專案找檔案」

#### ✅ 正確理解

「include 從當前檔案所在目錄解析相對路徑」

#### 🔍 實例

**檔案結構**
\`\`\`
AppSystem/
├── module_a/
│   ├── export/
│   │   ├── batch.php        ← 當前檔案
│   │   └── helper.php       ← 要引入的檔案
│   └── common/
│       └── utils.php
\`\`\`

**正確寫法**
\`\`\`php
// 在 batch.php 中

// ✅ 同目錄
include 'helper.php';

// ✅ 上一層目錄
include '../common/utils.php';

// ❌ 錯誤:會找不到(不在同目錄)
include 'common/utils.php';
\`\`\`
\`\`\`

### 技巧 5:測試與自動化 - 提供可執行範例

**目標**:複製貼上就能用

**範例**
\`\`\`markdown
## 🧪 測試與自動化

### 一鍵測試腳本

**檔案**`test.sh`

\`\`\`bash
#!/bin/bash
# 複製此腳本到專案根目錄即可使用

set -e  # 遇到錯誤立即停止

echo "🧪 開始測試..."

# 1. 建立 session
curl -s -c /tmp/cookies.txt "http://localhost/_dev_login.php" > /dev/null
echo "✅ Session 建立完成"

# 2. 測試 API
response=$(curl -b /tmp/cookies.txt -s "http://localhost/api/health")
if echo "$response" | grep -q "OK"; then
    echo "✅ API 健康檢查通過"
else
    echo "❌ API 健康檢查失敗"
    exit 1
fi

# 3. 測試批次輸出
curl -b /tmp/cookies.txt -X POST \
  -d "all_records=3" \
  "http://localhost/module_a/export.php?mod=batch" \
  -o /tmp/test_export.html

if [ -f /tmp/test_export.html ] && [ -s /tmp/test_export.html ]; then
    echo "✅ 批次輸出測試通過"
else
    echo "❌ 批次輸出測試失敗"
    exit 1
fi

echo "🎉 所有測試通過!"
\`\`\`

**使用方法**
\`\`\`bash
chmod +x test.sh
./test.sh
\`\`\`
\`\`\`

### 技巧 6:開發規範 - 用範例而非規則

**目標**:展示「好的」與「壞的」對比

**範例**
\`\`\`markdown
## 📝 開發規範

### Git 提交訊息

#### ✅ 好的範例

\`\`\`
feat: 新增 TypeA 表單批次輸出功能

- 新增 export/batchTypeA.php
- 新增權限檢查
- 新增 curl 測試腳本

Closes #123
\`\`\`

#### ❌ 壞的範例

\`\`\`
update

修改了一些東西
\`\`\`

**為什麼不好**
- 看不出做了什麼
- 無法搜尋(關鍵字不明確)
- 不知道影響範圍
\`\`\`

🚧 常見陷阱與解決方案

記錄撰寫 CLAUDE.md 時容易遇到的問題。

### 撰寫 CLAUDE.md 的常見陷阱

#### 陷阱 1:寫得太抽象

**❌ 壞範例**
\`\`\`markdown
系統使用多租戶架構,每個租戶有自己的資料。
\`\`\`

**✅ 好範例**
\`\`\`markdown
## 多租戶架構

每個機構使用獨立的 MySQL 資料庫:
- demo-org-2 → 機構 A 的資料庫
- demo-org-3 → 機構 B 的資料庫

登入時,系統設定 `$_SESSION['mcareDBno'] = 'demo-org-2'`
查詢時,DBPDO 類別讀取此變數連接對應資料庫
\`\`\`

**為什麼**:具體的變數名稱和範例讓 AI 更容易理解。

---

#### 陷阱 2:假設 AI 有背景知識

**❌ 壞範例**
\`\`\`markdown
使用標準的 MVC 架構。
\`\`\`

**✅ 好範例**
\`\`\`markdown
## 路由機制

非標準 MVC,使用參數路由:

- `?mod=xxx&func=yyy` → include `module/xxx/yyy.php`
- 沒有 Controller 層,直接在 PHP 檔案中處理邏輯
- View 與 Logic 混合在同一檔案(Legacy 設計)

範例:
- URL: `?mod=patient&func=list`
- 實際檔案: `module/patient/list.php`
\`\`\`

**為什麼**:明確說明與標準架構的差異。

---

#### 陷阱 3:遺漏關鍵限制

**❌ 壞範例**
\`\`\`markdown
資料庫使用 MySQL 5.7。
\`\`\`

**✅ 好範例**
\`\`\`markdown
## 資料庫限制

### MySQL 5.7(不可升級)

**限制**
- ❌ 不要建議升級到 8.0
- ❌ 不要建議修正 GROUP BY 語法
- ❌ 不要建議啟用嚴格模式

**原因**
- 500+ 個查詢使用舊版語法
- 升級需 2-3 個月測試
- 風險不可控

**可接受的建議**
- ✅ 新功能使用符合標準的 SQL
- ✅ 漸進式重構現有查詢
- ✅ 在註解中標註不符合標準之處
\`\`\`

**為什麼**:明確告知 AI「不要做什麼」和「為什麼」。

---

#### 陷阱 4:沒有實際範例

**❌ 壞範例**
\`\`\`markdown
使用 curl 可以測試 API。
\`\`\`

**✅ 好範例**
\`\`\`markdown
## API 測試

### 完整可執行範例

\`\`\`bash
# 1. 建立 session
curl -s -c /tmp/cookies.txt "http://localhost/_dev_login.php" > /dev/null

# 2. 測試 GET API
curl -b /tmp/cookies.txt "http://localhost/api/getData.php?id=123"

# 3. 測試 POST API
curl -b /tmp/cookies.txt -X POST \
  -d "action=save" \
  -d "data=test" \
  "http://localhost/api/saveData.php"

# 4. 驗證 JSON 回應
curl -b /tmp/cookies.txt "http://localhost/api/getData.php?id=123" \
  | jq '.success'
# 預期輸出:true
\`\`\`
\`\`\`

**為什麼**:可直接複製執行的範例最有用。

---

#### 陷阱 5:更新不及時

**問題**:CLAUDE.md 寫好後就忘記更新,最終與實際專案脫節。

**解決方案**

1. **在 Git commit 中提醒**
   \`\`\`bash
   # .git/hooks/pre-commit
   if git diff --cached --name-only | grep -q "module/.*\.php"; then
       echo "⚠️  記得更新 CLAUDE.md(如有架構變更)"
   fi
   \`\`\`

2. **定期檢查**
   - 每月第一個週一:檢查 CLAUDE.md
   - 大功能上線後:更新對應章節

3. **版本管理**
   \`\`\`markdown
   # CLAUDE.md

   > 📅 最後更新:2026-01-07
   > 📝 版本:v2.3
   > 👤 更新者:張三
   \`\`\`

4. **變更日誌**
   \`\`\`markdown
   ## 📜 變更歷史

   ### v2.3 (2026-01-07)
   - 新增:批次輸出測試流程
   - 更新:Docker XDebug 設定(支援 M1/M2)
   - 修正:權限檢查範例錯誤

   ### v2.2 (2025-12-15)
   - 新增:資料庫遷移工具說明
   - 更新:多租戶架構圖
   \`\`\`

🎯 總結與檢查清單

完成一份完整的 CLAUDE.md 後,使用這個檢查清單驗證:

✅ 內容完整性檢查

  • 多租戶架構

    • Session 變數說明
    • 資料庫切換流程
    • 常見陷阱與錯誤
  • 權限系統

    • 資料表結構
    • 權限檢查流程
    • 新增功能時的步驟
  • Docker 環境

    • 三層架構說明
    • XDebug 跨平台設定
    • 常見問題 FAQ
  • 測試自動化

    • curl 測試範例
    • 測試腳本模板
    • Claude 自動執行指南

✅ 品質檢查

  • 可執行性

    • 所有指令都可直接複製執行
    • 範例使用實際的檔案路徑
    • 提供預期輸出
  • 清晰度

    • 使用具體的變數名稱
    • 包含「為什麼」的說明
    • 好壞範例對比
  • 完整性

    • 涵蓋常見使用情境
    • 記錄常見錯誤
    • 提供排查步驟
  • 維護性

    • 標記最後更新日期
    • 版本號管理
    • 變更歷史記錄

🔜 下一步

恭喜你完成了本系列的第二篇!現在你應該掌握了:

  • ✅ 多租戶架構的文檔化策略
  • ✅ Docker 環境的完整說明方法
  • ✅ 測試流程的自動化文檔
  • ✅ 760 行 CLAUDE.md 的實戰拆解

📚 下一篇預告

在系列的最終篇中,我將探討:

《打造 AI 友善的專案文檔:CLAUDE.md 完整指南(下)》

  • 🎨 進階技巧

    • CLAUDE.md 的視覺設計
    • 如何處理機密資訊
    • 多人協作的最佳實踐
  • 🚀 實戰演練

    • 從零開始撰寫 CLAUDE.md
    • 現有專案的文檔化策略
    • 持續改進與版本管理
  • 🌟 成功案例分享

    • 不同類型專案的 CLAUDE.md 範例
    • 團隊導入經驗分享
    • ROI 量化分析

📎 相關資源

如果你覺得這篇文章有幫助,歡迎分享給正在維護 Legacy 專案的朋友!

有任何問題或想法,歡迎在下方留言討論 👇