PHP 工程師轉型之路:用 Python 和 FastAPI 打造現代電商網站 — 以 VueFastMart 為例
身為經驗豐富的 PHP 工程師,你可能已經在 Laravel、Symfony 或 CodeIgniter 的世界裡游刃有餘。但你是否曾思考,如何運用 Python 的簡潔優雅與 FastAPI 的極致性能,來打造一個兼具現代化與高效能的應用?本教學將以你的 VueFastMart 專案為核心,帶你從熟悉的 PHP 思維出發,一步步深入理解並實作一個基於 Python FastAPI 和 Vue.js 的現代電商產品管理系統。
我們將把你在 PHP 中習以為常的路由、ORM、認證、快取和部署等概念,完美對應到 Python 與 FastAPI 的生態中,助你快速掌握這項未來感十足的全端開發技能!
為什麼 PHP 工程師值得學習 Python 與 FastAPI?
你或許會問,為何要從熟悉的 PHP 生態轉向 Python 和 FastAPI?原因如下:
- FastAPI 的卓越性能與開發體驗:
- 閃電般的速度:FastAPI 建立在 Starlette (用於 Web 部分) 和 Pydantic (用於資料驗證) 之上,原生支援異步操作(
async/await
),尤其在處理 I/O 密集型任務時,能展現出驚人的吞吐量,遠超傳統同步框架。 - 自動化 API 文件:你只需編寫帶有型別提示的程式碼,FastAPI 便會自動生成 Swagger UI 和 ReDoc 互動式 API 文件,這在 PHP 中通常需要額外工具或手動維護。
- 強大的型別提示與資料驗證:Pydantic 與 Python 型別提示的結合,提供了自動的請求資料驗證、錯誤處理和回應資料序列化,極大地減少了樣板程式碼,並提升了程式碼品質和開發效率。
- 閃電般的速度:FastAPI 建立在 Starlette (用於 Web 部分) 和 Pydantic (用於資料驗證) 之上,原生支援異步操作(
- Python 的廣泛應用與生態系:
- Python 不僅是 Web 開發利器,它在資料科學、機器學習、自動化、DevOps 等領域都扮演著核心角色。掌握 Python 將為你打開更廣闊的職業發展道路。
- 與 PHP 豐富的 Composer 包類似,Python 擁有龐大的 PyPI 軟體包倉庫,幾乎任何功能都能找到現成的解決方案。
- 程式碼的簡潔與可讀性:Python 以其清晰、簡潔的語法而聞名,有時能用更少的程式碼實現相同的功能,進一步提升了團隊協作和維護效率。
VueFastMart 專案概覽
VueFastMart 是一個基於 Vue.js 和 FastAPI 的前後端分離電商產品管理系統。此專案專為展示現代全端開發能力而設計,提供完善的產品管理、用戶認證和購物車功能,並著重於 可測試性、安全性 和 效能。
專案核心功能詳解
VueFastMart 不僅僅是一個簡單的 CRUD 應用,它還包含了許多現代 Web 應用所需的關鍵特性:
一、前端功能 (Vue.js)
- 產品列表與詳情:
- 清晰展示產品名稱、描述、價格、庫存等資訊。
- 支援圖片懶加載 (Lazy Load),優化圖片載入速度,提升用戶體驗。
- 購物車管理:
- 直觀地將產品加入/移除購物車。
- 與後端 API 實時同步購物車狀態,確保資料一致性。
- 利用 Pinia 進行高效的前端狀態管理。
- 用戶認證流程:
- 提供完整的註冊和登入頁面,支援用戶身份驗證。
- 透過 JWT (JSON Web Tokens) 進行認證,確保 API 請求的安全。
- 響應式設計:
- 採用 Tailwind CSS 構建,確保界面在不同螢幕尺寸(手機、平板、桌面)上都能完美呈現。
二、後端 API (FastAPI)
後端 API 是專案的核心,採用 RESTful 風格設計,並透過明確的端點實現各種業務邏輯:
- 認證相關 (
/auth
):POST /auth/register
: 用戶註冊功能。- 請求:電子郵件 (
email
) 和密碼 (password
,至少 8 個字符,包含字母和數字)。 - 回應:新創建用戶的資料。
- 請求:電子郵件 (
POST /auth/token
: 用戶登入並獲取 JWT 訪問令牌。- 請求:用戶名 (
username
,即電子郵件) 和密碼 (password
)。 - 回應:
access_token
(JWT) 和token_type
(bearer)。
- 請求:用戶名 (
GET /auth/users/me
: 獲取當前登入用戶的資訊(需提供有效的 JWT)。- 回應:當前用戶的
id
,email
,is_admin
等。
- 回應:當前用戶的
- 產品管理 (
/products
):GET /products/
: 分頁查詢產品列表。- 查詢參數:
skip
(跳過數量),limit
(返回數量),q
(按名稱模糊搜索)。 - 優化:支援 Redis 快取,產品列表查詢快取 60 秒,提升性能。
- 查詢參數:
GET /products/{id}
: 獲取單一產品詳情。POST /products/
: 管理員專用,新增產品。- 請求:
name
,description
,price
,stock
,image_url
。
- 請求:
PUT /products/{id}
: 管理員專用,更新指定產品資訊。- 請求:可更新
name
,description
,price
,stock
,image_url
。
- 請求:可更新
DELETE /products/{id}
: 管理員專用,刪除指定產品。
- 購物車管理 (
/cart
):POST /cart/
: 將產品添加到當前用戶的購物車。- 請求:
product_id
,quantity
。 - 自動檢查產品庫存並更新。
- 請求:
GET /cart/
: 獲取當前用戶的購物車內容。- 回應:購物車中每個商品的詳情及總價。
DELETE /cart/{id}
: 從購物車中移除指定項目,並恢復對應庫存。
- 健康檢查 (
/health
):GET /health
: 檢查後端 API 和資料庫連線狀態。- 回應:
{"status": "ok", "database": "connected"}
或錯誤資訊。
- 回應:
三、性能與安全
- Redis 快取:產品列表查詢使用 Redis 快取 60 秒,大幅提升 API 響應速度。
- JWT 認證:所有敏感端點都透過 JWT 進行保護,確保只有合法用戶才能訪問。
- Bcrypt 密碼加密:用戶密碼在資料庫中以 Bcrypt 雜湊形式儲存,極大提高了安全性。
- CORS 和安全標頭:配置 Cross-Origin Resource Sharing (CORS) 以允許前端訪問,並添加
Content-Security-Policy
,X-Content-Type-Options
,X-Frame-Options
等 HTTP 安全標頭,有效防範常見 Web 漏洞。 - 環境變數管理:所有敏感資訊(如數據庫連接字符串、JWT 密鑰)都通過環境變數管理,不硬編碼在程式碼中。
四、可測試性與部署
- 後端測試 (Pytest):使用 Pytest 框架,對後端 API 進行全面的單元測試和集成測試,測試覆蓋率達到 80%。
- 前端測試 (Vitest):使用 Vitest 框架,對前端 Vue 組件進行測試,確保核心功能穩定可靠。
- 容器化部署 (Docker):提供完整的
docker-compose.yaml
配置,方便一鍵啟動所有服務 (FastAPI 後端, Vue 前端, PostgreSQL 資料庫, Redis 快取)。 - 雲端部署指南:提供在 Vercel (前端) 和 Render (後端) 上的部署教學,讓你可以輕鬆將專案發布到線上。
技術棧一覽
- 後端:
- FastAPI:基於 Starlette 和 Pydantic 的現代、快速 (高效能) Web 框架。
- SQLAlchemy:強大的 Python SQL 工具包和 ORM (Object Relational Mapper),用於資料庫操作。
- PostgreSQL:功能豐富、穩定可靠的開源關聯式資料庫系統(本地開發可選 SQLite)。
- JWT (Python-Jose):用於基於 Token 的用戶認證。
- Redis (redis-py):高性能的鍵值儲存資料庫,用於快取。
- pytest:流行的 Python 測試框架,提供豐富的測試功能。
- Gunicorn (生產環境):高效的 Python WSGI HTTP Server,用於運行 FastAPI 應用。
- 前端:
- Vue.js 3 (Composition API):漸進式 JavaScript 框架,用於構建用戶界面,採用 Composition API 提升代碼組織性。
- Pinia:輕量級、型別安全的 Vue 狀態管理庫,類似於 Vuex。
- Vue Router:Vue.js 的官方路由管理器。
- Axios:基於 Promise 的 HTTP 客戶端,用於前後端通信。
- Tailwind CSS:實用至上的 CSS 框架,用於快速構建響應式 UI。
- Vite:下一代前端構建工具,提供極速開發體驗。
- Vitest:Vue 官方推薦的快速單元測試框架。
- 其他:
- Docker 和 Docker Compose:用於容器化開發和部署。
- GitHub Actions:用於持續集成/持續部署 (CI/CD)。
第一步:自動創建專案骨架 (create_project.py
)
在開始之前,強烈建議你使用提供的 create_project.py
腳本來自動生成整個 VueFastMart 專案的檔案和目錄結構。這將省去你手動創建大量文件和目錄的繁瑣步驟,並確保你的專案結構與教學內容完全一致。
-
下載 create_project.py 檔案:
將 create_project.py 檔案下載到你的電腦上一個方便的位置(例如,你的桌面或專案工作區)。
-
打開終端機或命令提示字元 (CMD):
導航到你下載 create_project.py 檔案的目錄。
Bashcd /path/to/your/download/directory
-
執行腳本:
運行以下命令:
Bashpython create_project.py
如果你的系統同時安裝了 Python 2 和 Python 3,並且
python
命令預設指向 Python 2,請使用python3
:Bashpython3 create_project.py
腳本會輸出創建檔案的進度。完成後,你將在當前目錄下看到一個名為
VueFastMart
的新資料夾。這個資料夾就是專案的完整骨架。提示:如果重複執行此腳本,它將會覆蓋已存在的同名文件,請注意備份你的修改。
第二步:環境設置與 PHP 思維轉換
在 PHP 專案中,你可能習慣直接透過 Composer 管理依賴,或使用 Laragon/XAMPP 等整合環境。在 Python/JavaScript 世界,我們通常這樣做:
-
進入新創建的
VueFastMart
專案目錄:Bashcd VueFastMart
-
配置環境變數:
複製範例環境變數文件並進行編輯。這些文件類似於 Laravel 專案的 .env。
Bashcp backend/.env.example backend/.env cp frontend/.env.example frontend/.env
然後,打開
backend/.env
和frontend/.env
文件,根據你的需求設置環境變數。對於本地開發,你可以保留預設值以快速啟動 Docker Compose。backend/.env
(關鍵配置,用於 JWT 密鑰和資料庫連接)程式碼片段DATABASE_URL=postgresql://user:password@db:5432/vuefastmart_db # Docker 模式下,host 是 db SECRET_KEY=your-very-secure-secret-key-please-change-this-in-production # **務必修改為一個複雜的隨機字串** FRONTEND_URL=http://localhost:5173 # 或你的 Vercel 部署地址 REDIS_HOST=redis # Docker 模式下,host 是 redis REDIS_PORT=6379
frontend/.env
(關鍵配置,指向你的後端 API)程式碼片段VITE_API_URL=http://localhost:8000 # 或你的 Render 部署地址
第三步:本地安裝與執行
你可以選擇使用 Docker Compose 進行一鍵啟動,或手動配置和運行前後端。
選項 A: 使用 Docker Compose (推薦)
Docker Compose 提供了一個隔離且一致的開發環境,無需擔心本地環境衝突。
-
確保環境變數已配置:
確認 backend/.env 和 frontend/.env 檔案存在且已根據「第二步」正確配置。
-
啟動所有服務:
在 VueFastMart 專案的根目錄下,執行:
Bashdocker-compose up --build -d
--build
: 首次運行時會構建 Docker 映像。-d
: 在後台運行服務。
-
檢查服務狀態:
Bashdocker-compose ps
你會看到
backend
、frontend
、db
和redis
服務都已啟動。 -
初始化資料庫:
FastAPI 應用在啟動時會自動執行 Base.metadata.create_all(bind=engine) 來創建所有資料庫表格。所以一旦後端服務啟動,資料庫就會自動初始化。
-
訪問應用:
- 後端 API 文檔 (Swagger UI):
http://localhost:8000/docs
- 後端 API 健康檢查:
http://localhost:8000/health
- 前端應用:
http://localhost:5173
- 後端 API 文檔 (Swagger UI):
選項 B: 手動本地執行 (不使用 Docker)
如果你更傾向於在本地環境手動配置,請確保你已安裝 Node.js (18+), Python (3.11+), PostgreSQL, Redis。
設置後端
-
進入後端目錄:
Bashcd backend
-
創建 Python 虛擬環境 (venv):
這類似於 PHP 專案的 vendor 資料夾,但它更嚴格地隔離了 Python 環境,確保每個專案的依賴是獨立的。
Bashpython3 -m venv venv source venv/bin/activate # Windows 請使用 `venv\Scripts\activate`
你會看到終端機提示符前面多了一個
(venv)
,表示你已進入這個專案專屬的 Python 環境。 -
安裝依賴:
你的 requirements.txt 文件就是 Python 的 composer.json,它列出了專案所需的所有 Python 套件。
Bashpip install -r requirements.txt
-
設置資料庫 (PostgreSQL 或 SQLite):
- PostgreSQL (推薦):確保您本地安裝並啟動了 PostgreSQL 伺服器,並創建一個名為
vuefastmart_db
的資料庫。然後修改backend/.env
中的DATABASE_URL
,將db
改為localhost
:程式碼片段DATABASE_URL=postgresql://user:password@localhost:5432/vuefastmart_db
- SQLite (更簡單的本地開發選項):如果想快速上手,可以將
backend/.env
中的DATABASE_URL
改為sqlite:///./sql_app.db
。FastAPI 會自動創建一個sql_app.db
文件。
- PostgreSQL (推薦):確保您本地安裝並啟動了 PostgreSQL 伺服器,並創建一個名為
-
啟動後端:
Bashuvicorn app.main:app --reload
後端 API 文檔將在
http://localhost:8000/docs
可用,健康檢查在http://localhost:8000/health
。
設置前端
- 進入前端目錄:
Bash
cd frontend
- 安裝依賴:
Bash
npm install
- 配置環境變數:
編輯Bashcp .env.example .env
.env
,設置VITE_API_URL
指向你的後端,如果後端在本地運行,則為:程式碼片段VITE_API_URL=http://localhost:8000
- 啟動前端:
前端應用將在Bashnpm run dev
http://localhost:5173
可用。
第四步:後端 API 開發 (FastAPI 對比 PHP 框架)
現在,讓我們深入後端程式碼,看看 FastAPI 如何實現你在 PHP 框架中常見的功能。
1. 資料庫連接與 ORM (SQLAlchemy vs. Eloquent/Doctrine)
在 PHP 中,你可能習慣使用 Laravel 的 Eloquent 或 Doctrine ORM 來操作資料庫。FastAPI 常用 SQLAlchemy,它是一個更底層但功能強大的 ORM。
PHP (Laravel Eloquent 概念):
通常透過 config/database.php 或 .env 配置資料庫連接,並定義繼承 Model 的 Eloquent 模型。
// app/Models/Product.php (Laravel)
class Product extends Model {
protected $table = 'products';
// ... 其他屬性
}
// 使用:Product::all(); Product::find(1);
Python (FastAPI + SQLAlchemy 實現) - backend/app/database.py
:
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from dotenv import load_dotenv
import os
load_dotenv()
# 從 .env 讀取資料庫 URL,類似於 Laravel 從 .env 讀取 DB_HOST 等
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://user:password@localhost:5432/vuefastmart_db")
# 建立引擎,類似於資料庫連線實例
engine = create_engine(DATABASE_URL)
# 建立 Session,類似於每個請求的資料庫事務 (Database Transaction)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# ORM 基礎類別,所有模型都會繼承它 (類似於 Eloquent 的 Model 類)
Base = declarative_base()
# **依賴注入函數**:這是一個 FastAPI 的核心概念
# 每次請求時,FastAPI 會調用 get_db 並提供一個資料庫會話,請求結束後會自動關閉
def get_db():
db = SessionLocal() # 獲取一個新的會話
try:
yield db # 'yield' 關鍵字讓這個函數成為一個生成器,FastAPI 會自動管理會話的生命週期
finally:
db.close() # 請求結束後關閉會話
模型定義 (backend/app/models/product.py
等):
# backend/app/models/product.py
from sqlalchemy import Column, Integer, String, Float, Text, Index
from app.database import Base # 引入 ORM 基礎類別
class Product(Base): # 繼承 Base,類似於 Eloquent Model
__tablename__ = "products" # 對應的資料庫表名
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(Text, nullable=True)
price = Column(Float, nullable=False)
stock = Column(Integer, nullable=False)
__table_args__ = (Index('idx_name', 'name'),) # 定義索引
- 思維轉換:在 PHP ORM 中,你直接調用
Product::all()
或Product::where('id', 1)->first()
。在 SQLAlchemy 中,你需要先透過 FastAPI 的依賴注入獲取一個db
會話物件,然後使用db.query(Product).all()
或db.query(Product).filter(Product.id == 1).first()
來查詢。雖然語法不同,但核心思想都是透過物件操作資料庫,且 SQLAlchemy 提供更細粒度的控制。
2. 資料驗證與序列化 (Pydantic vs. Laravel Request Validation/Resource)
在 PHP 框架中,你常用表單請求 (Form Request) 進行輸入驗證,並用 API Resource 進行輸出資料的格式化。FastAPI 則透過 Pydantic 提供原生支援。
PHP (Laravel Request / Resource 概念):
// app/Http/Requests/StoreProductRequest.php
public function rules() {
return ['name' => 'required|string|max:255', 'price' => 'required|numeric'];
}
// app/Http/Resources/ProductResource.php
public function toArray($request) {
return ['id' => $this->id, 'name' => $this->name];
}
Python (FastAPI + Pydantic 實現) - backend/app/schemas/product.py
:
from pydantic import BaseModel, Field # 引入 BaseModel 定義資料模型,Field 用於更詳細的驗證規則
class ProductBase(BaseModel): # 定義基礎屬性,通常用於輸入數據
name: str = Field(min_length=3, max_length=100) # 類似 'required|string|min:3|max:100'
description: str | None = None # Python 3.10+ 的 Union Type,表示可以是 str 或 None (nullable)
price: float = Field(gt=0) # 'required|numeric|gt:0' (大於 0)
stock: int = Field(ge=0) # 'required|integer|ge:0' (大於等於 0)
image_url: str | None = None # 可選的圖片 URL
class ProductCreate(ProductBase): # 創建產品時的輸入驗證模型 (通常繼承 ProductBase)
pass # 繼承 ProductBase 的所有屬性,並可額外添加
class Product(ProductBase): # API 返回給前端的產品格式 (包含 ID)
id: int # 輸出時必須包含 ID
class Config:
from_attributes = True # Pydantic v2 用此設定,允許從 SQLAlchemy ORM 物件自動轉換為此 Pydantic 模型
- 思維轉換:FastAPI 透過 Pydantic 的型別提示和
Field
驗證器自動執行驗證和序列化。你只需要定義好BaseModel
,FastAPI 就會幫你處理資料的輸入驗證(如果輸入不符會自動返回 422 Unprocessable Entity 錯誤)和輸出格式化。這比手動編寫驗證規則和 Resource 類別更為簡潔和自動化,並且在開發階段就能獲得更好的 IDE 提示。
3. 路由與控制器 (APIRouter vs. Laravel Routes/Controllers)
在 PHP 中,你透過 web.php
或 api.php
定義路由,並將請求導向控制器方法。在 FastAPI 中,我們使用 APIRouter
。
PHP (Laravel Route/Controller 概念):
// routes/api.php (Laravel)
Route::get('/products', [ProductController::class, 'index']);
// app/Http/Controllers/ProductController.php (Laravel)
class ProductController extends Controller {
public function index(Request $request) {
return Product::paginate($request->limit);
}
}
Python (FastAPI 實現) - backend/app/api/products.py
:
from fastapi import APIRouter, Depends, HTTPException, Query # 引入 Query 用於參數驗證
from sqlalchemy.orm import Session
from sqlalchemy import select
from app.models.product import Product as ProductModel
from app.schemas.product import Product, ProductCreate # 引入 Pydantic 格式
from app.database import get_db # 引入資料庫依賴注入
from app.api.auth import get_current_user # 引入用戶認證依賴
from app.models.user import User
from app.cache import cache # 引入快取裝飾器
router = APIRouter() # 類似於 Laravel 的 Route::prefix() 或 Route::group()
@router.get("/", response_model=list[Product]) # 定義 GET 請求,回傳 Product 列表
@cache(timeout=60) # 這是一個自定義的快取裝飾器,會自動快取響應!
async def get_products( # 使用 async def 實現異步函數
skip: int = Query(0, ge=0), # 查詢參數,類似於 $_GET['skip'],並加上驗證
limit: int = Query(10, gt=0, le=100), # 查詢參數,類似於 $_GET['limit'],並加上驗證
q: str | None = Query(None, min_length=1, max_length=50), # 搜索查詢參數
db: Session = Depends(get_db) # **依賴注入**:FastAPI 會自動提供一個資料庫會話
):
query = select(ProductModel)
if q:
query = query.filter(ProductModel.name.ilike(f"%{q}%")) # 模糊搜索
products = db.execute(query.offset(skip).limit(limit)).scalars().all()
return products
@router.post("/", response_model=Product) # 定義 POST 請求,回傳創建後的 Product
async def create_product( # 使用 async def 實現異步函數
product: ProductCreate, # FastAPI 會自動驗證請求體是否符合 ProductCreate 格式
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user) # **依賴注入**:獲取當前登入用戶
):
if not current_user.is_admin: # 檢查用戶權限,類似於 PHP 的 Middleware 或 Gate
raise HTTPException(status_code=403, detail="僅管理員可新增產品")
db_product = ProductModel(**product.model_dump()) # Pydantic v2 使用 .model_dump() 轉換為字典
db.add(db_product)
db.commit()
db.refresh(db_product)
return db_product
- 思維轉換:FastAPI 的
@router.get("/")
就像 Laravel 的Route::get('/products', ...)
。Depends()
是 FastAPI 強大的依賴注入機制,它會自動將參數所需的值(例如db
或current_user
)傳入函數,類似於你在 Laravel 控制器方法中型別提示Request $request
或Product $product
。async def
讓你的 API 可以非阻塞地處理 I/O 操作(例如資料庫查詢或外部 API 請求),極大提升了應用程式的並發能力。自定義的@cache
裝飾器則是一個高級特性,在 PHP 中通常需要手動實現或使用框架提供的快取 facade。
4. 用戶認證與權限控制 (JWT & bcrypt vs. Laravel Auth/Passport)
在 PHP 中,你可能使用 Laravel 的內建認證系統或 Passport/Sanctum 處理 API 認證。FastAPI 則常用 JWT (JSON Web Tokens) 和 bcrypt。
PHP (Laravel Auth 概念):
通常透過 config/auth.php 配置認證守衛,並使用 Auth::attempt() 進行登入驗證,Hash::make() 雜湊密碼。
// app/Http/Controllers/AuthController.php (Laravel)
class AuthController extends Controller {
public function login(Request $request) {
if (Auth::attempt(['email' => $request->email, 'password' => $request->password])) {
// ... 生成 token
}
}
}
Python (FastAPI 實現) - backend/app/api/auth.py
:
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm # 用於自動處理登入表單
from sqlalchemy.orm import Session
from passlib.context import CryptContext # 雜湊密碼,類似於 PHP 的 password_hash() / bcrypt
from jose import JWTError, jwt # JWT 庫,用於編碼和解碼 Token
from datetime import datetime, timedelta
from dotenv import load_dotenv
from app.database import get_db
from app.models.user import User # 引入 ORM 模型
from app.schemas.user import UserCreate, User as UserSchema # 引入 Pydantic 驗證模型
import os
load_dotenv()
router = APIRouter()
SECRET_KEY = os.getenv("SECRET_KEY", "your-very-secure-secret-key") # 從環境變數讀取密鑰
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # 定義 bcrypt 雜湊方式
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token") # 定義 OAuth2 認證方案,告訴 Swagger UI 如何獲取 Token
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password) # 驗證密碼,類似於 PHP 的 password_verify()
def get_password_hash(password):
return pwd_context.hash(password) # 雜湊密碼
def create_access_token(data: dict): # 創建 JWT token
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire}) # 將過期時間加入 payload
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
# 這是 FastAPI 的認證依賴,它會從請求頭部解析 JWT token (Authorization: Bearer <token>)
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="無效的認證憑證",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) # 解碼 JWT
email: str = payload.get("sub") # 獲取 Token 中的用戶資訊
if email is None:
raise credentials_exception
except JWTError: # 如果 Token 無效或過期,則拋出 JWTError
raise credentials_exception
user = db.query(User).filter(User.email == email).first() # 根據 Token 中的資訊查詢用戶
if user is None:
raise credentials_exception
return user # 返回用戶物件,供後續 API 使用
@router.post("/register", response_model=UserSchema)
async def register_user(user: UserCreate, db: Session = Depends(get_db)):
db_user = db.query(User).filter(User.email == user.email).first()
if db_user:
raise HTTPException(status_code=400, detail="電子郵件已被註冊")
if len(user.password) < 8 or not any(char.isalpha() for char in user.password) or not any(char.isdigit() for char in user.password):
raise HTTPException(status_code=400, detail="密碼長度需至少8個字符,且包含字母和數字")
hashed_password = get_password_hash(user.password) # 雜湊密碼
db_user = User(email=user.email, hashed_password=hashed_password, is_admin=False) # 預設非管理員
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
@router.post("/token") # 登入 API
async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
user = db.query(User).filter(User.email == form_data.username).first()
if not user or not verify_password(form_data.password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="電子郵件或密碼不正確",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = create_access_token(data={"sub": user.email, "is_admin": user.is_admin}) # 創建 token,可包含用戶角色
return {"access_token": access_token, "token_type": "bearer"}
@router.get("/users/me", response_model=UserSchema)
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
- 思維轉換:
OAuth2PasswordBearer
和Depends(oauth2_scheme)
簡化了從請求頭部獲取 Token 的過程。get_current_user
函數作為一個依賴,可以被其他路由使用,類似於 PHP 的 Middleware,確保只有已認證的用戶才能訪問。角色權限檢查 (if not current_user.is_admin:
) 則是在 API 函數內部進行,非常清晰且易於維護。
5. 應用程式入口與 CORS (backend/app/main.py
)
在 PHP 框架中,通常有一個 public/index.php
作為入口,而 CORS 配置則在中間件或 Web 伺服器中設定。
Python (FastAPI 實現) - backend/app/main.py
:
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware # CORS 中間件
from dotenv import load_dotenv
from sqlalchemy.orm import Session
from sqlalchemy import text # 用於資料庫健康檢查
import os
# 引入路由模組
from app.api import auth, products, cart
from app.database import engine, Base, get_db # 引入資料庫引擎和 Base
load_dotenv()
app = FastAPI(
title="VueFastMart API",
description="一個基於 FastAPI 和 Vue.js 的現代電商產品管理系統。",
version="1.0.0",
) # 創建 FastAPI 應用實例,可添加元資料
# 配置 CORS,類似於 Laravel 的 cors.php 設定
app.add_middleware(
CORSMiddleware,
allow_origins=[os.getenv("FRONTEND_URL", "http://localhost:5173")], # 允許的前端來源,從環境變數讀取
allow_credentials=True, # 允許傳遞 Cookie (對於 JWT 通常不是必須的,但對其他認證方式有用)
allow_methods=["*"], # 允許所有 HTTP 方法 (GET, POST, PUT, DELETE 等)
allow_headers=["*"], # 允許所有頭部 (Authorization, Content-Type 等)
)
# 添加安全頭部 (Middleware),增強應用安全性
@app.middleware("http")
async def add_security_headers(request, call_next):
response = await call_next(request)
response.headers["Content-Security-Policy"] = "default-src 'self'" # CSP 頭部,限制資源來源
response.headers["X-Content-Type-Options"] = "nosniff" # 防止瀏覽器 MIME 嗅探
response.headers["X-Frame-Options"] = "DENY" # 防止點擊劫持
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" # HSTS,強制 HTTPS
return response
# 在應用啟動時創建資料庫表格 (類似於 PHP 的 Artisan migrate)
# FastAPI 應用在啟動時會運行這段程式碼
@app.on_event("startup")
def on_startup():
print("Creating database tables...")
Base.metadata.create_all(bind=engine) # 根據 ORM 模型創建所有資料庫表格
print("Database tables created.")
# 註冊 API 路由模組
app.include_router(auth.router, prefix="/auth", tags=["認證"])
app.include_router(products.router, prefix="/products", tags=["產品"])
app.include_router(cart.router, prefix="/cart", tags=["購物車"])
@app.get("/")
def read_root():
return {"message": "歡迎使用 VueFastMart API - 前往 /docs 查看 API 文件"}
@app.get("/health")
def health_check(db: Session = Depends(get_db)):
"""
檢查 API 和資料庫連線狀態。
"""
try:
db.execute(text("SELECT 1")) # 簡單查詢,檢查資料庫連接是否活躍
return {"status": "ok", "database": "connected"}
except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"資料庫連接失敗: {e}")
- 思維轉換:
FastAPI
實例就是你的應用程式核心。app.add_middleware
實現了類似 PHP 中間件的功能,用於處理跨域請求和添加安全頭部。@app.on_event("startup")
裝飾器確保在應用啟動時執行資料庫表格的創建,這正是php artisan migrate
的自動化版本。新增的/health
端點是一個很好的實踐,你可以用它來監控後端服務的狀態,在容器化和微服務架構中尤其重要。
6. 運行後端服務
在 PHP 中,你可能習慣使用 php artisan serve
或 Apache/Nginx + PHP-FPM。FastAPI 通常使用 uvicorn
(開發) 或 gunicorn
(生產環境)。
# 在 backend 目錄下
# 開發模式,自動重載
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# 生產模式 (搭配 gunicorn,通常在 Docker 環境中使用)
# gunicorn -w 4 -k uvicorn.workers.UvicornWorker app.main:app --bind 0.0.0.0:8000
- 思維轉換:
uvicorn
類似於 Laravel 的開發伺服器,方便即時修改。gunicorn
則類似於php-fpm
,用於生產環境處理高併發。--host 0.0.0.0
允許從外部訪問,這在 Docker 環境中尤其重要。
第五步:測試你的程式碼 (Pytest vs. PHPUnit)
在 PHP 中,你使用 PHPUnit 進行單元測試和集成測試。在 Python 中,pytest 是最受歡迎的測試框架。
PHP (PHPUnit 概念):
// tests/Unit/ProductTest.php (PHPUnit)
class ProductTest extends TestCase {
public function testProductCreation() { /* ... */ }
}
Python (FastAPI + pytest 實現) - backend/tests/test_products.py
:
import pytest
import json
from fastapi.testclient import TestClient # FastAPI 提供的測試客戶端
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.main import app
from app.database import Base, get_db
from app.models.product import Product
from app.models.user import User # 引入用戶模型
from jose import jwt
from datetime import datetime, timedelta
from passlib.context import CryptContext
import os
import redis.asyncio as redis # 測試 Redis 快取時需要
from unittest.mock import AsyncMock, patch # 用於 mock 異步操作
# 使用記憶體中的 SQLite 資料庫進行測試,避免影響真實資料庫
DATABASE_URL = "sqlite:///:memory:"
engine = create_engine(DATABASE_URL)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 覆寫 get_db 依賴,讓測試使用獨立的資料庫會話
def override_get_db():
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
# 將應用程式的 get_db 依賴替換為測試專用的 override_get_db
app.dependency_overrides[get_db] = override_get_db
client = TestClient(app) # 創建 FastAPI 測試客戶端,類似於 Laravel 的 $this->json() 或 $this->call()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
SECRET_KEY = os.getenv("SECRET_KEY", "your-test-secret-key") # 測試環境的密鑰
@pytest.fixture(scope="module") # 模組級別的 fixture,在測試文件開始前執行一次
def setup_database(): # 測試前創建資料庫表格,測試後清理
Base.metadata.create_all(bind=engine)
yield # 執行測試
Base.metadata.drop_all(bind=engine) # 測試結束後刪除所有表格
@pytest.fixture # 定義一個 fixture,用於測試中自動提供一個管理員用戶
def admin_user():
db = TestingSessionLocal()
hashed_password = pwd_context.hash("securepassword")
user = User(email="admin@example.com", hashed_password=hashed_password, is_admin=True)
db.add(user)
db.commit()
db.refresh(user)
token = jwt.encode(
{"sub": user.email, "is_admin": user.is_admin}, SECRET_KEY, algorithm="HS256"
) # 生成管理員 Token
db.close()
return {"user": user, "token": token}
def test_create_product(setup_database, admin_user): # 測試新增產品功能
response = client.post(
"/products/",
json={"name": "測試產品", "description": "這是一個測試產品", "price": 100.0, "stock": 10},
headers={"Authorization": f"Bearer {admin_user['token']}"} # 傳遞認證 token
)
assert response.status_code == 200 # 斷言 HTTP 狀態碼
data = response.json()
assert data["name"] == "測試產品" # 斷言返回數據的正確性
assert data["price"] == 100.0
assert "id" in data # 確認返回數據中包含 ID
@patch('app.cache.redis_client.get', new_callable=AsyncMock) # Mock Redis get 方法
@patch('app.cache.redis_client.setex', new_callable=AsyncMock) # Mock Redis setex 方法
def test_get_products_cached(mock_setex, mock_get, setup_database, admin_user):
# 模擬快取命中
mock_get.return_value = json.dumps([{"id": 1, "name": "Cached Product", "description": None, "price": 50.0, "stock": 5}]).encode('utf-8')
response = client.get(
"/products/",
headers={"Authorization": f"Bearer {admin_user['token']}"}
)
assert response.status_code == 200
assert response.json()[0]["name"] == "Cached Product"
mock_get.assert_called_once() # 確認有嘗試從快取中讀取
mock_setex.assert_not_called() # 確認沒有寫入快取,因為已命中
# 模擬快取未命中
mock_get.return_value = None
client.post(
"/products/",
json={"name": "新產品", "description": "描述", "price": 200.0, "stock": 20},
headers={"Authorization": f"Bearer {admin_user['token']}"}
)
response = client.get(
"/products/",
headers={"Authorization": f"Bearer {admin_user['token']}"}
)
assert response.status_code == 200
assert response.json()[0]["name"] == "新產品"
mock_setex.assert_called_once() # 確認有寫入快取
- 思維轉換:
TestClient(app)
讓你無需啟動真實伺服器就能測試 FastAPI 路由,類似於 Laravel 的actingAs()
或json()
方法。@pytest.fixture
是一個強大的概念,它可以在測試函數運行前提供預設的設置或數據(如資料庫連接、測試用戶),這比 PHPUnit 中的setUp()
方法更加靈活和模組化。@patch
裝飾器可以模擬(Mock)外部依賴(如 Redis 客戶端),讓你的測試更專注於單元邏輯,這在處理異步操作和外部服務時尤其有用。
運行測試指令
- 後端測試 (在
backend
目錄下):Bashpytest tests/ --cov=app --cov-report=html
--cov=app
用於生成測試覆蓋率報告,--cov-report=html
會在htmlcov/
目錄下生成 HTML 格式的報告,你可以在瀏覽器中打開查看。 - 前端測試 (在
frontend
目錄下):Bashnpm run test
第六步:部署指引
你的專案已經提供了 Vercel (前端) 和 Render (後端) 的部署範例,這對於 PHP 工程師來說,將會是一個很好的 CI/CD 和雲端部署的學習機會。
部署前端到 Vercel
- 將前端程式碼推送到 GitHub:
確保你的
frontend
目錄下的所有程式碼都已推送到一個 GitHub 倉庫。 - 在 Vercel 創建新專案: 登入 Vercel,選擇「Add New Project」,然後連接到你的前端 GitHub 倉庫。
- 配置構建設定 (通常 Vercel 會自動檢測):
- 框架預設:Vercel 會自動檢測你的專案是 Vue 應用。
- 構建指令:
npm run build
- 輸出目錄:
dist
- 設置環境變數:
在 Vercel 專案設定中,進入「Environment Variables」區塊,添加一個關鍵環境變數:
VITE_API_URL
: 填寫你的後端 API 公開可訪問的地址(例如,如果你已部署到 Render,可以是https://your-render-app-name.onrender.com
)。
- 部署:
點擊「Deploy」按鈕。Vercel 會自動拉取程式碼,執行構建指令,並將靜態檔案部署到全球 CDN。部署完成後,你會獲得一個前端的 URL。請將此 URL 更新到後端的
backend/.env
中的FRONTEND_URL
。
部署後端到 Render
- 將後端程式碼推送到 GitHub:
確保你的
backend
目錄下的所有程式碼都已推送到一個 GitHub 倉庫。 - 在 Render.com 創建一個新的 Web Service: 登入 Render,選擇「New Web Service」,然後連接到你的後端 GitHub 倉庫。
- 配置服務設定:
- Name:給你的服務一個名稱 (例如
vuefastmart-api
)。 - Root Directory:選擇
backend/
(因為你的 FastAPI 程式碼在backend
目錄下)。 - Runtime:Python
- Build Command (構建指令):
Bash
pip install -r requirements.txt
- Start Command (啟動指令):
這裡使用了Bashgunicorn -w 4 -k uvicorn.workers.UvicornWorker app.main:app --bind 0.0.0.0:$PORT
gunicorn
,它是一個生產級的 WSGI 伺服器,可以處理多個工作進程,提高應用程式的併發處理能力。-w 4
表示啟動 4 個工作進程。$PORT
是 Render 自動提供的環境變數。 - Scalability:可以設定實例類型和數量(建議從免費或最低級別開始)。
- Name:給你的服務一個名稱 (例如
- 設置環境變數:
在 Render 服務設定中,進入「Environment Variables」區塊,添加以下關鍵環境變數:
DATABASE_URL
: 填寫 Render PostgreSQL 資料庫的連接 URL (Render 會為你創建一個新的 PostgreSQL 資料庫並提供連接 URL)。SECRET_KEY
: 一個安全的密鑰字串,用於 JWT 加密(請使用與 Vercel 相同的複雜密鑰)。FRONTEND_URL
: 填寫你部署到 Vercel 的前端應用 URL (例如https://your-vercel-app.vercel.app
)。REDIS_HOST
: 如果使用 Render Redis,填寫其內部主機名 (Render 會為你創建並提供)。REDIS_PORT
: 填寫 Render Redis 的埠號。
- 部署: 點擊「Create Web Service」。Render 會自動拉取程式碼,執行構建和啟動指令。部署完成後,你會獲得一個後端的 API URL。
問題排查
在開發或部署過程中遇到問題是正常的。以下是一些常見問題及排查方向:
- 後端無法啟動:
- 檢查
.env
檔案:確保backend/.env
中的SECRET_KEY
和DATABASE_URL
配置正確。 - 資料庫連線:確認 PostgreSQL 或 SQLite 服務正在運行,且連接字符串無誤。
- 依賴安裝:確認所有 Python 依賴都已透過
pip install -r requirements.txt
正確安裝。 - 查看日誌:檢查
uvicorn
或gunicorn
的終端機輸出,錯誤訊息通常會提供線索。
- 檢查
- 前端無法訪問後端 API (CORS 錯誤):
- 檢查
FRONTEND_URL
:確保backend/.env
中的FRONTEND_URL
設置正確,包含了前端應用的完整 URL (包括協定和埠號)。如果有多個前端來源,可以在 FastAPI 的CORSMiddleware
中添加多個allow_origins
。 - 檢查後端是否運行:確認後端 API 服務已成功啟動並正在監聽正確的埠。
- 檢查
- 資料庫錯誤 (例如表不存在):
- 運行
Base.metadata.create_all(bind=engine)
:確保此行程式碼在backend/app/main.py
中被執行,或者你已手動執行資料庫遷移。在 Docker Compose 中,後端服務啟動時會自動執行。
- 運行
- 查看日誌:
- 後端:檢查
uvicorn
的終端機輸出。如果使用 Docker Compose,可以使用docker-compose logs backend
。 - 前端:使用瀏覽器開發者工具 (F12),查看「Console」和「Network」標籤,通常會有清晰的錯誤提示。
- 後端:檢查
總結與展望
恭喜你!作為一名 PHP 工程師,你已經透過 VueFastMart 這個專案,初步掌握了 Python FastAPI 和 Vue.js 的核心概念和實踐。你應該會發現,儘管語法和一些工具不同,但背後的 Web 開發邏輯、MVC (或更準確地說,MVT/MVVM) 架構模式以及解決問題的思維是相通的。這是一次成功的技術棧擴展!
你的 GitHub 專案連結:
繼續探索以下內容,你的 Python 全端開發技能將會突飛猛進:
- 異步編程 (Async/Await) 進階:深入理解
async/await
的工作原理,並將其應用於更多 I/O 密集型任務,如外部 API 調用。 - SQLAlchemy 進階:學習更多 ORM 關係(多對多、多對一)、聯結查詢、事務管理等高級功能。
- FastAPI 路由群組與標籤:更有效地組織和管理大型應用程式的 API。
- FastAPI 依賴注入的更深層次應用:創建更複雜的依賴,例如分頁依賴、權限驗證依賴等。
- 單元測試與集成測試覆蓋率提升:學習如何編寫更健壯的測試用例。
- 部署策略優化:研究 Nginx/Caddy 作為反向代理、Docker Swarm 或 Kubernetes 進行容器編排等生產環境部署方案。
- 性能監控與日誌管理:整合 Prometheus/Grafana 或 ELK Stack 等工具進行應用程式監控和日誌收集。
希望這篇文章能成為你從 PHP 邁向現代全端開發的堅實橋樑,祝你在新的技術旅程中取得更多成就!
沒有留言:
張貼留言