好的,各位学员,大家好!我是你们的IT培训导师。很高兴能和大家一起进入这个充满挑战与乐趣的全栈实战课程。
今天,我们要探讨的主题是“FastAPI + Vue3 项目实战”。这将是一堂干货满满、节奏紧凑的课程,旨在帮助大家快速掌握这套现代、高效的Web开发技术栈。
---
# FastAPI+Vue3项目实战
在当今的Web开发领域,前后端分离已成为绝对的主流。选择一个高效、现代、且对开发者友好的技术栈至关重要。
完成本课程后,你将具备独立搭建一个功能完备的全栈Web应用的核心能力。具体来说,你可以:
1. 独立设计并实现RESTful API:掌握从路由设计、请求校验到响应封装的完整流程。 2. 熟练使用SQLAlchemy进行数据库操作:实现模型定义、CRUD操作、复杂查询和事务管理。 3. 搭建基于Vue3 + Pinia的现代化前端:运用组合式API(Composition API)组织逻辑,使用Pinia进行高效的状态管理。 4. 实现安全的JWT用户认证系统:从前端登录到后端Token签发、验证,构建完整的权限闭环。 5. 完成前后端联调与项目部署:解决跨域问题,并将应用部署到生产环境。
---
本课程将围绕4个核心模块展开。
原理讲解:FastAPI基于Starlette构建,利用Python的类型提示(Type Hints)实现自动请求校验和自动生成API文档(Swagger UI / ReDoc)。其核心是路径操作装饰器(如@app.get())和依赖注入系统。
关键技术对比:
| 特性 | FastAPI | Flask | Django REST Framework | | :--- | :--- | :--- | :--- | | 异步支持 | 原生异步(基于ASGI) | 同步为主,异步支持较弱 | 同步为主,异步支持较弱 | | 性能 | 极高(媲美Node.js/Go) | 中等 | 较低 | | 自动文档 | 内置(Swagger/ReDoc) | 需第三方扩展(如Flask-RESTx) | 内置(Browsable API) | | 数据校验 | 基于Pydantic,自动校验 | 需手动或使用Marshmallow | 使用Serializer,功能强大但复杂 |
代码示例:
# main.py
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, Field
from typing import Optional
app = FastAPI(title="我的API", version="1.0.0")
# 1. 数据模型(Pydantic Model)
class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=50, description="项目名称")
price: float = Field(..., gt=0, description="价格")
is_offer: Optional[bool] = None
class ItemResponse(BaseModel):
id: int
name: str
price: float
is_offer: Optional[bool] = None
# 2. 模拟数据库
fake_db = {}
# 3. 路径操作与参数校验
@app.post("/items/", response_model=ItemResponse, status_code=201)
async def create_item(item: Item):
"""创建一个新项目"""
item_id = len(fake_db) + 1
fake_db[item_id] = item.model_dump()
return {"id": item_id, **item.model_dump()}
@app.get("/items/{item_id}", response_model=ItemResponse)
async def read_item(item_id: int):
"""根据ID获取项目"""
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"id": item_id, **fake_db[item_id]}
模块2:SQLAlchemy ORM与数据库交互
原理讲解:SQLAlchemy是Python中最强大的ORM(对象关系映射)库。它将数据库表映射为Python类,将数据库操作(SQL语句)转化为面向对象的方法调用,极大地提高了开发效率和代码可读性。我们使用其2.0风格的Declarative Mapping和Session。
代码示例:
# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker
# 数据库连接URL(以SQLite为例,生产环境请用PostgreSQL)
SQLALCHEMY_DATABASE_URL = "sqlite:///./myapi.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class Base(DeclarativeBase):
pass
# models.py
from sqlalchemy import Column, Integer, String, Float, Boolean
from database import Base
class ItemDB(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
price = Column(Float)
is_offer = Column(Boolean, default=False)
# crud.py
from sqlalchemy.orm import Session
from models import ItemDB
import schemas # 存放Pydantic模型
def get_item(db: Session, item_id: int):
return db.query(ItemDB).filter(ItemDB.id == item_id).first()
def create_item(db: Session, item: schemas.ItemCreate):
db_item = ItemDB(**item.model_dump())
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
模块3:Vue3组合式API与Pinia状态管理
原理讲解:
- **组合式API (Composition API)**:Vue3引入的`setup`语法糖,允许我们按逻辑功能组织代码,而不是按选项(`data`, `methods`, `computed`)。通过`ref`、`reactive`、`computed`、`watch`等函数,实现更灵活、可复用的逻辑组合。
- **Pinia**:Vue的官方状态管理库,替代Vuex。它完全支持TypeScript,API简洁直观,采用`Store`的概念,每个Store是一个独立的、响应式的数据仓库。
代码示例:
<!-- src/stores/counter.js (Pinia Store) -->
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// State
const count = ref(0)
// Getters
const doubleCount = computed(() => count.value * 2)
// Actions
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
<!-- src/components/Counter.vue (Vue3组件) -->
<script setup>
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
</script>
<template>
<div>
<p>Count: {{ counterStore.count }}</p>
<p>Double: {{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment">+1</button>
</div>
</template>
模块4:JWT认证与前后端联调
原理讲解:JWT(JSON Web Token)是一种无状态的认证机制。用户登录成功后,服务器签发一个包含用户身份信息的加密Token,客户端(前端)在后续请求的Authorization头中携带此Token。服务器通过验证Token来确认用户身份,无需在服务端存储会话信息。
前后端联调关键:跨域资源共享 (CORS)。前端(如localhost:5173)访问后端(如localhost:8000)时,浏览器会默认阻止跨域请求。我们需要在后端配置允许跨域的来源。
代码示例:
# main.py (后端配置CORS和JWT)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
# ... (省略其他import)
# CORS配置
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"], # 允许前端地址
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# JWT配置
SECRET_KEY = "your-secret-key-here" # 生产环境务必使用强随机密钥
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 创建Token
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# 登录接口
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# ... 验证用户名密码 ...
access_token = create_access_token(data={"sub": user.username})
return {"access_token": access_token, "token_type": "bearer"}
# 受保护的路由
@app.get("/users/me")
async def read_users_me(token: str = Depends(oauth2_scheme)):
# ... 解码Token,获取用户信息 ...
return current_user
---
三、实操步骤
我们将从零开始,构建一个简单的“待办事项 (Todo)”应用。
步骤1:搭建FastAPI后端骨架
1. 创建项目目录并安装依赖:
mkdir todo-fullstack
cd todo-fullstack
mkdir backend
cd backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install fastapi uvicorn sqlalchemy python-jose[cryptography] passlib[bcrypt] python-multipart
2. 创建main.py,写入模块1和模块4的代码框架,并添加一个简单的健康检查路由/。
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
3. 启动服务:
uvicorn main:app --reload
预期效果:访问 http://127.0.0.1:8000/docs 可以看到Swagger API文档。
步骤2:集成SQLAlchemy
1. 创建database.py、models.py、schemas.py、crud.py文件,内容参考模块2。
2. 在main.py中引入数据库初始化逻辑和依赖注入函数get_db。
3. 重启服务,确保数据库表自动创建。
步骤3:创建Vue3前端项目
1. 在todo-fullstack目录下,打开新终端:
npm create vue@latest frontend
# 选择:添加Pinia, 添加Router, 使用Composition API等
cd frontend
npm install
2. 安装axios:npm install axios
步骤4:编写前端核心页面 (Todo列表)
1. 创建Pinia Store:src/stores/todo.js,定义todos状态,以及fetchTodos, addTodo, toggleTodo等actions。
2. 修改src/views/TodoView.vue:使用<script setup>语法,引入todoStore,在onMounted中调用fetchTodos。使用v-for渲染列表,v-model绑定输入框。
3. 配置API请求:在src/utils/request.js中创建axios实例,设置baseURL为http://127.0.0.1:8000。
步骤5:实现JWT登录
1. 后端:实现/token登录接口和/users/me接口(参考模块4)。
2. 前端:
- 创建`LoginView.vue`,包含用户名和密码表单。
- 登录成功后,将`access_token`存储在`localStorage`中。
- 在axios请求拦截器中,从`localStorage`读取Token并添加到`Authorization: Bearer xxx`头中。
- 使用路由守卫(`router.beforeEach`)判断用户是否登录,未登录则跳转到登录页。
步骤6:前后端联调与功能测试
1. 启动后端(端口8000)和前端(端口5173)。
2. 在前端登录,创建、查看、删除Todo项。
3. 打开浏览器开发者工具,查看网络请求,确认Token在请求头中,数据交互正常。
---
四、常见问题与故障排查
问题1:前端请求后端时遇到CORS错误。
- **现象**:浏览器控制台报错 `Access to XMLHttpRequest at 'http://localhost:8000/...' from origin 'http://localhost:5173' has been blocked by CORS policy`。
- **判断思路**:后端未正确配置CORS中间件。
- **解决方法**:
1. 检查后端main.py中是否添加了CORSMiddleware。
2. 确认allow_origins列表中包含了前端的确切地址(如["http://localhost:5173"]),不要忘记协议和端口。
3. 如果使用*允许所有来源,在生产环境中不安全,开发时可以用。
问题2:JWT Token验证失败,接口返回401。
- **现象**:登录成功后,访问需要认证的接口时返回401 Unauthorized。
- **判断思路**:
1. Token是否已过期?
2. Token是否被正确发送?
3. 后端解码Token的密钥或算法是否与签发时一致?
- **排查流程**:
1. 在浏览器开发者工具 -> 网络 -> 请求头中,查看Authorization头是否存在,格式是否为 Bearer <token>。
2. 检查后端SECRET_KEY和ALGORITHM是否与签发时一致。
3. 检查Token的exp字段,确认未过期。可以通过在[jwt.io](https://jwt.io/)上解码Token查看。
问题3:SQLAlchemy数据库操作报错“Table already exists”或“No such table”。
- **现象**:启动后端或执行数据库操作时抛出异常。
- **判断思路**:数据库迁移或初始化问题。
- **解决方法**:
1. 初次启动:确保在main.py或app启动事件中调用了Base.metadata.create_all(bind=engine)。
2. 修改模型后:对于开发环境,最简单的做法是删除旧的数据库文件(如myapi.db),让程序重新创建。生产环境应使用Alembic等迁移工具。
问题4:Vue3组件中ref或reactive数据不更新视图。
- **现象**:数据在控制台打印已改变,但页面显示没有更新。
- **判断思路**:响应式丢失。
- **解决方法**:
1. ref:在<script setup>或setup()函数中,通过ref()创建的变量,在模板中会自动解包,但在JavaScript中操作时,必须使用.value属性(如count.value++)。
2. reactive:确保直接修改对象的属性,而不是替换整个对象。例如,正确:state.items.push(newItem);错误:state = { items: [...] }。如果需要替换整个对象