打造 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();
-
忘記在跨模組操作時保持 Session
// ❌ 錯誤:AJAX 請求未帶 Session fetch('/api/getData.php'); // ✅ 正確:確保 Session 傳遞 fetch('/api/getData.php', { credentials: 'include' }); -
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 專案的朋友!
有任何問題或想法,歡迎在下方留言討論 👇