FastAPI+Vue 搭建运维平台(二):后端开发,从 0 到 1

0 阅读8分钟

这是《FastAPI+Vue 搭建运维平台》系列的第二篇。

第一篇我们讲了项目架构和目录结构,这篇我们来写代码——从 0 到 1 实现后端核心功能。

GitHub 仓库: github.com/WGsummer/op…
Gitee 仓库(国内访问更快): gitee.com/wgsummer/op…


本文内容

  • ✅ FastAPI 项目初始化
  • ✅ 数据库模型设计(SQLAlchemy)
  • ✅ API 接口开发(增删改查)
  • ✅ 用户认证(JWT)
  • ✅ 异步任务(Celery + Redis)

前置知识:

  • Python 基础
  • 了解 RESTful API
  • 看过第一篇(项目架构)

代码量: 约 500 行(核心功能)


1. 项目初始化

1.1 创建虚拟环境

# 创建项目目录
mkdir ops-platform && cd ops-platform

# 创建虚拟环境
python -m venv venv
source venv/bin/activate  # Linux/Mac
# 或
venv\Scripts\activate  # Windows

# 安装依赖
pip install fastapi uvicorn sqlalchemy python-jose[cryptography] passlib[bcrypt] python-multipart
pip install celery redis  # 异步任务

1.2 项目结构

ops-platform/
├── app/
│   ├── __init__.py
│   ├── main.py          # FastAPI 应用入口
│   ├── config.py        # 配置文件
│   ├── database.py      # 数据库连接
│   ├── models/          # 数据模型
│   │   ├── __init__.py
│   │   └── user.py
│   ├── schemas/         # Pydantic 模型
│   │   ├── __init__.py
│   │   └── user.py
│   ├── api/             # API 路由
│   │   ├── __init__.py
│   │   └── users.py
│   ├── core/            # 核心功能
│   │   ├── __init__.py
│   │   ├── security.py  # 密码加密/JWT
│   │   └── celery_app.py
│   └── tasks/           # 异步任务
│       ├── __init__.py
│       └── example.py
├── requirements.txt
└── .env

2. 配置文件

2.1 环境变量(.env)

# 数据库
DATABASE_URL=sqlite:///./ops_platform.db
# 或用 MySQL/PostgreSQL
# DATABASE_URL=mysql+pymysql://user:password@localhost:3306/ops_platform

# JWT 配置
SECRET_KEY=your-secret-key-here-change-in-production
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30

# Redis(Celery 用)
REDIS_URL=redis://localhost:6379/0

2.2 配置类(app/config.py)

from pydantic_settings import BaseSettings
from functools import lru_cache

class Settings(BaseSettings):
    DATABASE_URL: str = "sqlite:///./ops_platform.db"
    SECRET_KEY: str = "your-secret-key-here-change-in-production"
    ALGORITHM: str = "HS256"
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
    REDIS_URL: str = "redis://localhost:6379/0"

    class Config:
        env_file = ".env"

@lru_cache()
def get_settings() -> Settings:
    return Settings()

settings = get_settings()

3. 数据库连接

3.1 数据库配置(app/database.py)

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from .config import settings

# 创建数据库引擎
engine = create_engine(
    settings.DATABASE_URL,
    connect_args={"check_same_thread": False}  # SQLite 需要
)

# 创建会话工厂
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# 创建基类
Base = declarative_base()

# 依赖注入:获取数据库会话
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

4. 数据模型

4.1 用户模型(app/models/user.py)

from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
from ..database import Base

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    username = Column(String(50), unique=True, index=True, nullable=False)
    email = Column(String(100), unique=True, index=True, nullable=False)
    hashed_password = Column(String(255), nullable=False)
    is_active = Column(Boolean, default=True)
    is_superuser = Column(Boolean, default=False)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), onupdate=func.now())

4.2 初始化数据库(在 main.py 中)

from .database import engine, Base

# 创建所有表
Base.metadata.create_all(bind=engine)

5. Pydantic 模型(数据验证)

5.1 用户 Schema(app/schemas/user.py)

from pydantic import BaseModel, EmailStr
from datetime import datetime
from typing import Optional

# 创建用户
class UserCreate(BaseModel):
    username: str
    email: EmailStr
    password: str

# 更新用户
class UserUpdate(BaseModel):
    username: Optional[str] = None
    email: Optional[EmailStr] = None
    is_active: Optional[bool] = None

# 用户响应(不包含密码)
class UserResponse(BaseModel):
    id: int
    username: str
    email: str
    is_active: bool
    created_at: datetime

    class Config:
        from_attributes = True

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

6. 核心功能

6.1 密码加密(app/core/security.py)

from passlib.context import CryptContext
from datetime import datetime, timedelta
from jose import JWTError, jwt
from ..config import settings

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

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) -> str:
    """创建 JWT Token"""
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
    
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    return encoded_jwt

7. API 路由

7.1 用户 API(app/api/users.py)

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from ..database import get_db
from ..models import user as models
from ..schemas import user as schemas
from ..core import security

router = APIRouter(prefix="/api/v1/users", tags=["用户管理"])

@router.post("/register", response_model=schemas.UserResponse, status_code=status.HTTP_201_CREATED)
def register(user: schemas.UserCreate, db: Session = Depends(get_db)):
    """用户注册"""
    # 检查用户名是否已存在
    db_user = db.query(models.User).filter(models.User.username == user.username).first()
    if db_user:
        raise HTTPException(status_code=400, detail="用户名已存在")
    
    # 检查邮箱是否已存在
    db_email = db.query(models.User).filter(models.User.email == user.email).first()
    if db_email:
        raise HTTPException(status_code=400, detail="邮箱已被注册")
    
    # 创建新用户
    hashed_password = security.get_password_hash(user.password)
    new_user = models.User(
        username=user.username,
        email=user.email,
        hashed_password=hashed_password
    )
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    
    return new_user

@router.get("/", response_model=List[schemas.UserResponse])
def get_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    """获取用户列表"""
    users = db.query(models.User).offset(skip).limit(limit).all()
    return users

@router.get("/{user_id}", response_model=schemas.UserResponse)
def get_user(user_id: int, db: Session = Depends(get_db)):
    """获取单个用户"""
    user = db.query(models.User).filter(models.User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="用户不存在")
    return user

@router.put("/{user_id}", response_model=schemas.UserResponse)
def update_user(user_id: int, user_update: schemas.UserUpdate, db: Session = Depends(get_db)):
    """更新用户"""
    user = db.query(models.User).filter(models.User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="用户不存在")
    
    # 更新字段
    update_data = user_update.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        setattr(user, field, value)
    
    db.commit()
    db.refresh(user)
    return user

@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int, db: Session = Depends(get_db)):
    """删除用户"""
    user = db.query(models.User).filter(models.User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="用户不存在")
    
    db.delete(user)
    db.commit()
    return None

8. 用户认证

8.1 认证路由(app/api/auth.py)

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from jose import JWTError, jwt
from datetime import timedelta
from ..database import get_db
from ..models import user as models
from ..schemas import user as schemas
from ..core import security
from ..config import settings

router = APIRouter(prefix="/api/v1/auth", tags=["认证"])

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/auth/login")

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"},
    )
    
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    
    user = db.query(models.User).filter(models.User.username == username).first()
    if user is None:
        raise credentials_exception
    
    return user

@router.post("/login", response_model=schemas.Token)
def login(
    form_data: OAuth2PasswordRequestForm = Depends(),
    db: Session = Depends(get_db)
):
    """用户登录"""
    # 验证用户
    user = db.query(models.User).filter(models.User.username == form_data.username).first()
    if not user or not security.verify_password(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户名或密码错误",
            headers={"WWW-Authenticate": "Bearer"},
        )
    
    # 创建 Token
    access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = security.create_access_token(
        data={"sub": user.username},
        expires_delta=access_token_expires
    )
    
    return {"access_token": access_token, "token_type": "bearer"}

@router.get("/me", response_model=schemas.UserResponse)
def get_current_user_info(current_user: models.User = Depends(get_current_user)):
    """获取当前用户信息"""
    return current_user

9. 异步任务(Celery)

9.1 Celery 配置(app/core/celery_app.py)

from celery import Celery
from ..config import settings

celery_app = Celery(
    "ops_platform",
    broker=settings.REDIS_URL,
    backend=settings.REDIS_URL
)

celery_app.conf.task_routes = {
    "app.tasks.*": "main-queue"
}

9.2 示例任务(app/tasks/example.py)

from ..core.celery_app import celery_app
import time

@celery_app.task
def send_email_task(email: str, subject: str, body: str):
    """发送邮箱任务(示例)"""
    print(f"发送邮件到 {email}")
    print(f"主题:{subject}")
    print(f"内容:{body}")
    time.sleep(2)  # 模拟发送延迟
    return {"status": "success", "email": email}

@celery_app.task
def backup_database_task(db_name: str):
    """数据库备份任务(示例)"""
    print(f"开始备份数据库:{db_name}")
    time.sleep(5)  # 模拟备份时间
    print(f"数据库备份完成:{db_name}")
    return {"status": "success", "db_name": db_name}

9.3 在 API 中调用异步任务

from fastapi import APIRouter, BackgroundTasks
from ..tasks.example import send_email_task, backup_database_task

router = APIRouter()

@router.post("/send-email")
async def send_email(email: str, subject: str, body: str):
    """发送邮箱(异步)"""
    task = send_email_task.delay(email, subject, body)
    return {"task_id": task.id, "status": "任务已提交"}

@router.post("/backup-db")
async def backup_database(db_name: str):
    """数据库备份(异步)"""
    task = backup_database_task.delay(db_name)
    return {"task_id": task.id, "status": "备份任务已提交"}

10. FastAPI 应用入口

10.1 主应用(app/main.py)

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .database import engine, Base
from .api import users, auth
from .config import settings

# 创建数据库表
Base.metadata.create_all(bind=engine)

# 创建 FastAPI 应用
app = FastAPI(
    title="运维管理平台",
    description="基于 FastAPI + Vue 的运维管理平台",
    version="1.0.0"
)

# CORS 配置(允许前端访问)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],  # Vue 开发服务器
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 注册路由
app.include_router(users.router)
app.include_router(auth.router)

@app.get("/")
def root():
    return {"message": "运维管理平台 API", "version": "1.0.0"}

@app.get("/health")
def health_check():
    return {"status": "healthy"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

11. 运行项目

11.1 启动 FastAPI

# 开发模式(热重载)
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

# 生产模式
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4

11.2 启动 Celery Worker

# 启动 Celery Worker
celery -A app.core.celery_app worker --loglevel=info

# 启动 Celery Beat(定时任务,可选)
celery -A app.core.celery_app beat --loglevel=info

11.3 启动 Redis

# Docker 启动 Redis
docker run -d -p 6379:6379 --name redis redis:latest

# 或用系统包管理器
# macOS: brew install redis
# Ubuntu: sudo apt-get install redis-server

12. 测试 API

12.1 使用 Swagger UI

访问:http://localhost:8000/docs

  • 先注册一个用户(POST /api/v1/users/register)
  • 然后登录获取 Token(POST /api/v1/auth/login)
  • 点击"Authorize",填入 Token
  • 测试其他接口

12.2 使用 curl 测试

# 注册用户
curl -X POST "http://localhost:8000/api/v1/users/register" \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "email": "admin@example.com", "password": "123456"}'

# 登录
curl -X POST "http://localhost:8000/api/v1/auth/login" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=admin&password=123456"

# 获取当前用户信息(替换 YOUR_TOKEN)
curl -X GET "http://localhost:8000/api/v1/auth/me" \
  -H "Authorization: Bearer YOUR_TOKEN"

13. 常见问题

Q1: SQLite vs MySQL/PostgreSQL?

开发阶段: SQLite 够用,方便快速原型。

生产环境: 建议 MySQL 或 PostgreSQL。

修改 .env 中的 DATABASE_URL 即可切换。

Q2: 密码忘了怎么办?

这篇先不写密码重置,第三篇前端篇再加。

临时方案:直接改数据库。

Q3: Celery 任务不执行?

检查:

  1. Redis 是否启动
  2. Celery Worker 是否运行
  3. 任务路由配置是否正确

14. 总结

这篇我们完成了:

✅ FastAPI 项目初始化
✅ 数据库模型设计
✅ 用户 CRUD 接口
✅ JWT 认证
✅ Celery 异步任务

代码量: 约 500 行
用时: 2-3 小时(跟着做)

代码仓库:


下一篇预告

第三篇:前端开发(Vue 3 + Element Plus)

  • Vue 3 项目初始化
  • Element Plus UI 组件库
  • 登录/注册页面
  • 用户管理界面
  • API 调用封装

预计发布时间: 本周内


我是运维老王,10 年运维老鸟。

有问题评论区聊,看到就回。

觉得有用,点个赞/收藏,让我知道没白写😄


系列文章:

  • 第一篇:避坑指南(已发布)
  • 第二篇:后端开发(本文)
  • 第三篇:前端开发(待发布)
  • 第四篇:部署上线(待发布)