FastAPI + Vue3 实战:从零搭建一个带用户认证的待办事项应用(附完整代码)

FastAPI + Vue3 实战:从零搭建一个带用户认证的待办事项应用(附完整代码)

你是否曾想过,一个看似简单的待办事项列表背后,其实隐藏着现代Web开发的完整技术栈?从用户登录、数据存储到界面交互,每一个环节都考验着开发者对前后端分离架构的深刻理解。今天,我将带你从零开始,用FastAPI和Vue3构建一个功能完整的待办事项应用,它不仅支持基础的增删改查,还集成了基于JWT的用户认证系统。这个项目将是你理解现代Web开发流程的绝佳实践,无论你是想巩固全栈技能,还是为下一个项目寻找技术方案,都能从中获得实实在在的收获。

我们将采用完全分离的开发模式:后端用FastAPI提供高性能的RESTful API,前端用Vue3构建动态交互界面,两者通过HTTP请求和WebSocket(可选)进行通信。整个过程我会提供可直接运行的代码片段,并解释每个关键决策背后的考量。准备好了吗?让我们开始这场从零到一的构建之旅。

1. 项目架构设计与环境搭建

在动手写代码之前,清晰的架构设计能让你少走很多弯路。我们的应用将采用经典的三层架构:数据层、业务逻辑层和表现层。后端使用FastAPI框架,它基于Python 3.6+的类型提示,提供了自动化的API文档生成和极高的性能。前端选择Vue3的组合式API,它比Vue2的选项式API更加灵活,更适合构建复杂的交互界面。

1.1 技术栈选择与考量

为什么选择FastAPI和Vue3这个组合?让我分享一些实际项目中的体会。FastAPI的异步支持在处理高并发请求时表现优异,特别是当你的待办事项应用需要实时同步多个客户端时,这一点至关重要。而Vue3的响应式系统经过重构,性能提升了近一倍,对于频繁更新的UI界面来说,这是不容忽视的优势。

核心依赖清单:

  • 后端

    • FastAPI 0.104+:Web框架核心
    • SQLAlchemy 2.0+:ORM工具
    • Pydantic 2.0+:数据验证与序列化
    • Passlib:密码哈希
    • python-jose[cryptography]:JWT令牌生成与验证
    • Uvicorn:ASGI服务器
  • 前端

    • Vue 3.3+:前端框架
    • Pinia 2.1+:状态管理
    • Vue Router 4.2+:路由管理
    • Axios 1.6+:HTTP客户端
    • Element Plus 2.3+:UI组件库(可选,但推荐)

1.2 开发环境配置

首先确保你的系统已经安装了Python 3.8+和Node.js 16+。我建议使用虚拟环境来管理Python依赖,这能避免不同项目间的包冲突。

后端环境配置:

# 创建项目目录
mkdir todo-app && cd todo-app
mkdir backend && cd backend

# 创建Python虚拟环境
python -m venv venv

# 激活虚拟环境(Windows)
venv\Scripts\activate
# 激活虚拟环境(macOS/Linux)
source venv/bin/activate

# 安装核心依赖
pip install fastapi[all] sqlalchemy pydantic[email] passlib[bcrypt] python-jose[cryptography] uvicorn

前端环境配置:

# 返回项目根目录,创建前端项目
cd ..
npm create vue@latest frontend

# 按照提示选择配置,我推荐:
# ✔ Add TypeScript? Yes
# ✔ Add JSX Support? No  
# ✔ Add Vue Router for Single Page Application development? Yes
# ✔ Add Pinia for state management? Yes
# ✔ Add Vitest for Unit Testing? No
# ✔ Add an End-to-End Testing Solution? No
# ✔ Add ESLint for code quality? Yes

cd frontend
npm install
npm install axios element-plus

提示:如果你在安装过程中遇到网络问题,可以考虑配置npm镜像源。执行 npm config set registry https://registry.npmmirror.com 可以切换到国内镜像,加速依赖下载。

环境配置完成后,你的项目结构应该大致如下:

todo-app/
├── backend/
│   ├── venv/
│   ├── app/
│   │   ├── __init__.py
│   │   ├── main.py
│   │   ├── models.py
│   │   ├── schemas.py
│   │   ├── crud.py
│   │   ├── database.py
│   │   └── auth.py
│   └── requirements.txt
└── frontend/
    ├── src/
    │   ├── components/
    │   ├── views/
    │   ├── stores/
    │   ├── router/
    │   └── App.vue
    ├── package.json
    └── vite.config.js

这个结构清晰地分离了前后端代码,便于团队协作和后期维护。在实际开发中,我习惯为每个功能模块创建独立的文件,而不是把所有代码都堆在main.py里。这样做虽然初期看起来有些繁琐,但当项目规模扩大时,你会发现模块化的价值。

2. 后端核心:FastAPI与数据库设计

后端是整个应用的大脑,负责数据处理、业务逻辑和API提供。我们将从数据库模型设计开始,逐步构建完整的RESTful API。

2.1 数据库模型与关系设计

我选择SQLite作为开发数据库,因为它无需额外安装服务,文件存储的方式也便于项目迁移。对于生产环境,你可以轻松切换到PostgreSQL或MySQL。

数据库连接配置:

# backend/app/database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "sqlite:///./todo.db"
# 生产环境建议使用PostgreSQL
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@localhost/todo_db"

engine = create_engine(
    SQLALCHEMY_DATABASE_URL, 
    connect_args={"check_same_thread": False}  # SQLite专用参数
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

接下来设计用户和待办事项两个核心模型。这里有个细节需要注意:密码字段不应该存储明文,我们使用Passlib的bcrypt算法进行哈希处理。

# backend/app/models.py
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime, Text
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from .database import Base

class User(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True, index=True)
    email = Column(String, unique=True, index=True, nullable=False)
    username = Column(String, unique=True, index=True, nullable=False)
    hashed_password = Column(String, nullable=False)
    is_active = Column(Boolean, default=True)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    
    # 建立一对多关系
    todos = relationship("Todo", back_populates="owner", cascade="all, delete-orphan")

class Todo(Base):
    __tablename__ = "todos"
    
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, index=True, nullable=False)
    description = Column(Text, nullable=True)
    completed = Column(Boolean, default=False)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), onupdate=func.now())
    due_date = Column(DateTime(timezone=True), nullable=True)
    
    # 外键关联
    owner_id = Column(Integer, ForeignKey("users.id"), nullable=False)
    
    # 关系定义
    owner = relationship("User", back_populates="todos")

注意:在定义模型关系时,我特意使用了cascade="all, delete-orphan"参数。这意味着当用户被删除时,其所有的待办事项也会自动删除,避免了数据库中的孤儿记录。这在生产环境中很重要,能保持数据的一致性。

2.2 Pydantic模式与数据验证

Pydantic是FastAPI的黄金搭档,它利用Python的类型提示来进行数据验证和序列化。我习惯为每个模型创建多个模式类,分别用于创建、更新和响应。

# backend/app/schemas.py
from pydantic import BaseModel, EmailStr, Field
from datetime import datetime
from typing import Optional, List

# 用户相关模式
class UserBase(BaseModel):
    email: EmailStr
    username: str = Field(..., min_length=3, max_length=50)

class UserCreate(UserBase):
    password: str = Field(..., min_length=8)

class UserUpdate(BaseModel):
    email: Optional[EmailStr] = None
    username: Optional[str] = Field(None, min_length=3, max_length=50)

class UserInDB(UserBase):
    id: int
    is_active: bool
    created_at: datetime
    
    class Config:
        from_attributes = True

# 待办事项相关模式
class TodoBase(BaseModel):
    title: str = Field(..., min_length=1, max_length=200)
    description: Optional[str] = None
    due_date: Optional[datetime] = None

class TodoCreate(TodoBase):
    pass

class TodoUpdate(BaseModel):
    title: Optional[str] = Field(None, min_length=1, max_length=200)
    description: Optional[str] = None
    completed: Optional[bool] = None
    due_date: Optional[datetime] = None

class Todo(TodoBase):
    id: int
    completed: bool
    created_at: datetime
    updated_at: Optional[datetime] = None
    owner_id: int
    
    class Config:
        from_attributes = True

# 响应模式
class Token(BaseModel):
    access_token: str
    token_type: str

class TokenData(BaseModel):
    username: Optional[str] = None

这里有几个设计要点值得注意:

  1. 使用EmailStr类型自动验证邮箱格式
  2. 通过Field添加额外的验证规则,如长度限制
  3. from_attributes = True允许Pydantic从ORM对象创建实例
  4. 为更新操作创建独立的模式类,所有字段都是可选的

2.3 JWT认证系统实现

用户认证是Web应用的安全基石。我们采用JWT(JSON Web Token)方案,它无状态、易于扩展,适合前后端分离架构。

密码哈希与验证:

# backend/app/auth.py
from passlib.context import CryptContext
from datetime import datetime, timedelta
from jose import JWTError, jwt
from typing import Optional

# 密码哈希上下文
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# JWT配置
SECRET_KEY = "your-secret-key-change-in-production"  # 生产环境务必修改
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

def verify_password(plain_password: str, hashed_password: str) -> bool:
    """验证密码"""
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password: str) -> str:
    """生成密码哈希"""
    return pwd_context.hash(password)

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    """创建JWT访问令牌"""
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        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

def verify_token(token: str) -> Optional[dict]:
    """验证JWT令牌"""
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            return None
        return payload
    except JWTError:
        return None

依赖注入与用户获取:

FastAPI的依赖注入系统让认证逻辑变得优雅。我们可以创建可重用的依赖项来获取当前用户。

# backend/app/dependencies.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session
from . import models, schemas, auth
from .database import SessionLocal

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def get_db():
    """数据库会话依赖"""
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: Session = Depends(get_db)
) -> models.User:
    """获取当前认证用户"""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无效的认证凭证",
        headers={"WWW-Authenticate": "Bearer"},
    )
    
    payload = auth.verify_token(token)
    if payload is None:
        raise credentials_exception
    
    username: str = payload.get("sub")
    if username is None:
        raise credentials_exception
    
    user = db.query(models.User).filter(models.User.username == username).first()
    if user is None:
        raise credentials_exception
    
    return user

async def get_current_active_user(
    current_user: models.User = Depends(get_current_user)
) -> models.User:
    """获取当前活跃用户"""
    if not current_user.is_active:
        raise HTTPException(status_code=400, detail="用户已被禁用")
    return current_user

这种依赖注入的设计模式有几个好处:首先,它让认证逻辑与业务逻辑分离;其次,可以在路由中轻松复用;最后,便于单元测试的模拟。

2.4 CRUD操作与业务逻辑

CRUD操作是待办事项应用的核心。我习惯将数据库操作封装在独立的CRUD模块中,这样既保持了代码的整洁,也便于测试。

# backend/app/crud.py
from sqlalchemy.orm import Session
from typing import List, Optional
from . import models, schemas
from .auth import get_password_hash

# 用户CRUD操作
def get_user_by_email(db: Session, email: str) -> Optional[models.User]:
    return db.query(models.User).filter(models.User.email == email).first()

def get_user_by_username(db: Session, username: str) -> Optional[models.User]:
    return db.query(models.User).filter(models.User.username == username).first()

def create_user(db: Session, user: schemas.UserCreate) -> models.User:
    hashed_password = get_password_hash(user.password)
    db_user = models.User(
        email=user.email,
        username=user.username,
        hashed_password=hashed_password
    )
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

# 待办事项CRUD操作
def get_todos(
    db: Session, 
    user_id: int, 
    skip: int = 0, 
    limit: int = 100,
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值