🚀 超前科技感个人博客 — Python 全栈架构设计

技术分享 2026-06-30 172
预计阅读时间:24 分钟

🚀 超前科技感个人博客 — Python 全栈架构设计

一、技术栈总览

┌─────────────────────────────────────────────────────────────┐
│                         Cloudflare CDN                       │
└────────────────────────┬────────────────────────────────────┘
                         │
                    ┌────▼────┐
                    │  Nginx  │ 反向代理 / SSL / 限流
                    └────┬────┘
                         │
          ┌──────────────┼──────────────┐
          │              │              │
     ┌────▼────┐   ┌────▼────┐   ┌────▼────┐
     │ 前端 SPA │   │ 后端 API│   │ 管理后台 │
     │ React + │   │FastAPI  │   │ React   │
     │ Vite    │◄──►│+ Uvicorn│   │ Admin   │
     │ :3000   │   │ :8000   │   │ :3001   │
     └─────────┘   └────┬────┘   └─────────┘
                        │
           ┌────────────┼──────────────┐
           │            │              │
     ┌─────▼───┐  ┌────▼────┐  ┌──────▼──────┐
     │PostgreSQL│  │  Redis  │  │  MinIO/S3   │
     │  主数据库  │  │ 缓存/队列│  │  对象存储   │
     └──────────┘  └─────────┘  └─────────────┘
层级 技术选型 版本
前端 React 19 + Vite 6 + TypeScript 展示层,科技感 UI
后端 FastAPI + Uvicorn + Pydantic v2 RESTful API + WebSocket
数据库 PostgreSQL 16 主数据存储
ORM SQLAlchemy 2.0 (async) + Alembic 异步 ORM + 迁移
缓存 Redis 7 缓存 / Session / 消息队列
任务队列 Celery + Redis 异步任务(邮件、数据聚合)
存储 MinIO / 阿里云OSS 图片 / 附件
网关 Nginx 反向代理 / SSL / 静态文件

二、Monorepo 项目结构

blog/
├── packages/                     # 共享包(仅前端)
   └── shared/                   # 前端侧共享 TS 类型
       ├── src/
          ├── types/
          └── constants/
       └── package.json

├── apps/
   ├── client/                   # ===== 前端 (React + Vite) =====
      ├── public/
         ├── fonts/            # JetBrains Mono 等自定义字体
         ├── images/           # 静态图片
         └── models/           # 3D 模型文件 (.glb)
      
      ├── src/
         ├── main.tsx
         ├── App.tsx           # 路由 + 全局 Provider
         
         ├── pages/            # 路由页面
            ├── Home/         # 首页(Terminal 入口)
            ├── Posts/        # 文章列表 + 详情
            ├── Projects/     # 项目墙
            ├── About/        # 关于页 + 技能星盘
            ├── Guestbook/    # 留言板
            ├── Snippets/     # 代码片段
            └── NotFound/     # 404
         
         ├── components/       # 组件
            ├── ui/           # 基础组件(Button/Card/Input/Badge/Modal/Toast)
            ├── layout/       # 布局(Navbar/Footer/Sidebar/MobileNav)
            ├── effects/      # 特效(粒子背景/网格光效/故障文字/打字机/CRT/极光)
            ├── blog/         # 博客组件(PostCard/CodeBlock/CommentSection/TOC)
            └── home/         # 首页组件(Hero/TerminalIntro/FeaturedPosts/StatsBoard)
         
         ├── hooks/            # 自定义 Hooks
            ├── useMousePosition.ts
            ├── useScrollProgress.ts
            ├── useIntersectionObserver.ts
            └── useApi.ts
         
         ├── services/         # API 调用层
            ├── http.ts               # Axios 实例(拦截器 + 鉴权注入)
            ├── postService.ts        # 文章模块
            ├── projectService.ts     # 项目模块
            ├── commentService.ts     # 评论模块
            ├── authService.ts        # 认证模块
            └── guestbookService.ts   # 留言模块
         
         ├── stores/           # Zustand 状态管理
            ├── useThemeStore.ts      # 主题
            ├── useUserStore.ts       # 用户
            └── useUIStore.ts         # UI 状态
         
         ├── styles/           # 样式
            ├── globals.css
            ├── animations.css
            └── markdown.css
         
         └── utils/
             └── cn.ts
      
      ├── index.html
      ├── vite.config.ts
      ├── tailwind.config.ts
      ├── tsconfig.json
      └── package.json
   
   ├── server/                   # ===== 后端 (FastAPI + Python) =====
      ├── app/
         ├── __init__.py
         ├── main.py                   # 入口:FastAPI 实例 + 生命周期
         ├── config.py                 # Pydantic Settings 配置
         
         ├── api/                      # API 路由层
            ├── __init__.py
            ├── v1/                   # API v1 版本
               ├── __init__.py
               ├── router.py         # 主路由聚合
               ├── posts.py          # 文章接口
               ├── projects.py       # 项目接口
               ├── comments.py       # 评论接口
               ├── guestbook.py      # 留言板接口
               ├── auth.py           # 认证接口
               ├── users.py          # 用户接口
               ├── tags.py           # 标签接口
               ├── search.py         # 搜索接口
               ├── analytics.py      # 统计接口
               └── upload.py         # 文件上传
            └── deps.py               # 依赖注入(DB Session / 当前用户)
         
         ├── models/                   # SQLAlchemy 数据模型
            ├── __init__.py
            ├── base.py               # 基类(时间戳、UUID 主键)
            ├── user.py               # 用户模型
            ├── post.py               # 文章模型
            ├── comment.py            # 评论模型
            ├── project.py            # 项目模型
            ├── tag.py                # 标签模型
            ├── guestbook.py          # 留言模型
            ├── like.py               # 点赞模型
            ├── session.py            # 会话模型
            └── site_stats.py         # 站点统计
         
         ├── schemas/                  # Pydantic 请求/响应 Schema
            ├── __init__.py
            ├── post.py               # 文章 DTO
            ├── user.py               # 用户 DTO
            ├── comment.py
            ├── project.py
            ├── auth.py
            └── common.py             # 通用(分页/响应包装)
         
         ├── services/                 # 业务逻辑层
            ├── __init__.py
            ├── post_service.py
            ├── auth_service.py
            ├── comment_service.py
            ├── project_service.py
            ├── search_service.py
            ├── analytics_service.py
            └── upload_service.py
         
         ├── core/                     # 核心基础设施
            ├── __init__.py
            ├── database.py           # 异步 DB 引擎 + Session 工厂
            ├── redis.py              # Redis 客户端
            ├── security.py           # JWT / 密码哈希
            ├── cache.py              # 缓存装饰器(Cache-Aside)
            ├── celery_app.py         # Celery 异步任务
            └── minio.py              # MinIO 客户端
         
         ├── middleware/               # 中间件
            ├── __init__.py
            ├── cors.py               # CORS
            ├── rate_limit.py         # 限流
            ├── request_log.py        # 请求日志
            └── process_time.py       # 响应时间
         
         └── tasks/                    # Celery 异步任务
             ├── __init__.py
             ├── email_tasks.py        # 发送邮件
             ├── stats_tasks.py        # 统计数据聚合
             └── cleanup_tasks.py      # 清理过期数据
      
      ├── alembic/                      # 数据库迁移
         ├── env.py
         └── versions/                 # 迁移版本文件
      
      ├── tests/                        # 测试
         ├── conftest.py              # Fixtures(测试 DB / 客户端)
         ├── test_posts.py
         ├── test_auth.py
         └── test_comments.py
      
      ├── alembic.ini
      ├── pyproject.toml
      ├── requirements.txt
      ├── Dockerfile
      └── .env.example
   
   └── admin/                     # ===== 管理后台(React) =====
       ├── src/
          ├── pages/             # 页面
             ├── Dashboard/
             ├── Posts/         # 文章管理(MD 编辑器)
             ├── Comments/      # 评论审核
             ├── Users/
             ├── Settings/
             └── Analytics/
          ├── components/
             ├── RichEditor.tsx
             ├── ImageUploader.tsx
             └── DataTable.tsx
          ├── services/
          └── hooks/
       ├── vite.config.ts
       └── package.json

├── docker/                        # Docker 编排
   ├── docker-compose.yml         # 一键启动全部服务
   ├── Dockerfile.client
   ├── Dockerfile.server
   └── nginx/
       ├── nginx.conf
       └── ssl/

├── scripts/
   ├── dev.sh                     # 本地开发启动脚本
   └── deploy.sh

├── .env.example
├── .gitignore
└── README.md

三、数据库设计 (SQLAlchemy 2.0)

# app/models/base.py
"""所有模型的基类"""
import uuid
from datetime import datetime
from sqlalchemy import DateTime, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.dialects.postgresql import UUID


class Base(DeclarativeBase):
    pass


class TimestampMixin:
    """自动时间戳 Mixin"""
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now()
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
    )


class UUIDMixin:
    """UUID 主键 Mixin"""
    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
    )
# app/models/user.py
"""用户模型"""
from sqlalchemy import String, Enum as SAEnum
from sqlalchemy.orm import Mapped, mapped_column, relationship
import enum

from app.models.base import Base, TimestampMixin, UUIDMixin


class UserRole(str, enum.Enum):
    USER = "USER"
    ADMIN = "ADMIN"


class User(Base, TimestampMixin, UUIDMixin):
    __tablename__ = "users"

    username: Mapped[str] = mapped_column(String(50), unique=True, index=True)
    email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
    password_hash: Mapped[str] = mapped_column(String(255))
    avatar: Mapped[str | None] = mapped_column(String(500))
    bio: Mapped[str | None] = mapped_column(String(500))
    role: Mapped[UserRole] = mapped_column(
        SAEnum(UserRole), default=UserRole.USER
    )
    github_id: Mapped[str | None] = mapped_column(
        String(100), unique=True, nullable=True
    )

    # 关系
    posts = relationship("Post", back_populates="author")
    comments = relationship("Comment", back_populates="author")
    guestbooks = relationship("Guestbook", back_populates="author")
    likes = relationship("Like", back_populates="user")
# app/models/post.py
"""文章模型"""
from sqlalchemy import String, Text, Boolean, Integer, ForeignKey, DateTime
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime

from app.models.base import Base, TimestampMixin, UUIDMixin


# 多对多关联表
post_tags = Table(
    "post_tags",
    Base.metadata,
    Column("post_id", ForeignKey("posts.id", ondelete="CASCADE"), primary_key=True),
    Column("tag_id", ForeignKey("tags.id", ondelete="CASCADE"), primary_key=True),
)


class Post(Base, TimestampMixin, UUIDMixin):
    __tablename__ = "posts"

    title: Mapped[str] = mapped_column(String(200))
    slug: Mapped[str] = mapped_column(String(200), unique=True, index=True)
    excerpt: Mapped[str | None] = mapped_column(String(500))
    content: Mapped[str] = mapped_column(Text)
    cover_image: Mapped[str | None] = mapped_column(String(500))
    published: Mapped[bool] = mapped_column(Boolean, default=False)
    pinned: Mapped[bool] = mapped_column(Boolean, default=False)
    views: Mapped[int] = mapped_column(Integer, default=0)
    published_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))

    author_id: Mapped[int] = mapped_column(
        ForeignKey("users.id"), index=True
    )
    author = relationship("User", back_populates="posts")
    tags = relationship("Tag", secondary=post_tags, back_populates="posts")
    comments = relationship("Comment", back_populates="post", cascade="all, delete-orphan")
    likes = relationship("Like", back_populates="post", cascade="all, delete-orphan")
# app/models/comment.py
"""评论模型(支持嵌套回复)"""
from sqlalchemy import String, Text, Integer, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship

from app.models.base import Base, TimestampMixin, UUIDMixin


class Comment(Base, TimestampMixin, UUIDMixin):
    __tablename__ = "comments"

    content: Mapped[str] = mapped_column(Text)
    likes: Mapped[int] = mapped_column(Integer, default=0)

    post_id: Mapped[int] = mapped_column(
        ForeignKey("posts.id", ondelete="CASCADE"), index=True
    )
    author_id: Mapped[int] = mapped_column(
        ForeignKey("users.id"), index=True
    )
    parent_id: Mapped[int | None] = mapped_column(
        ForeignKey("comments.id", ondelete="CASCADE"), nullable=True
    )

    post = relationship("Post", back_populates="comments")
    author = relationship("User", back_populates="comments")
    replies = relationship("Comment", backref="parent", remote_side="Comment.id", cascade="all, delete-orphan")

四、API 路由设计

4.1 路由注册

# app/api/v1/router.py
from fastapi import APIRouter
from app.api.v1 import posts, projects, comments, guestbook, auth, users, tags, search, analytics, upload

router = APIRouter(prefix="/api/v1")

router.include_router(posts.router, prefix="/posts", tags=["文章"])
router.include_router(projects.router, prefix="/projects", tags=["项目"])
router.include_router(comments.router, prefix="/comments", tags=["评论"])
router.include_router(guestbook.router, prefix="/guestbook", tags=["留言板"])
router.include_router(auth.router, prefix="/auth", tags=["认证"])
router.include_router(users.router, prefix="/users", tags=["用户"])
router.include_router(tags.router, prefix="/tags", tags=["标签"])
router.include_router(search.router, prefix="/search", tags=["搜索"])
router.include_router(analytics.router, prefix="/analytics", tags=["统计"])
router.include_router(upload.router, prefix="/upload", tags=["上传"])

4.2 文章模块示例

# app/api/v1/posts.py
from fastapi import APIRouter, Depends, Query, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_db, get_current_user
from app.schemas.post import PostCreate, PostUpdate, PostResponse, PostListResponse
from app.schemas.common import PaginationParams
from app.services.post_service import PostService
from app.models.user import User

router = APIRouter()


@router.get("", response_model=PostListResponse)
async def list_posts(
    page: int = Query(1, ge=1),
    limit: int = Query(10, ge=1, le=50),
    tag: str | None = None,
    published: bool = True,
    sort: str = "published_at_desc",
    db: AsyncSession = Depends(get_db),
):
    """文章列表(分页 + 筛选 + 排序)"""
    service = PostService(db)
    return await service.get_paginated(page=page, limit=limit, tag=tag,
                                        published=published, sort=sort)


@router.get("/{slug}", response_model=PostResponse)
async def get_post(
    slug: str,
    db: AsyncSession = Depends(get_db),
):
    """文章详情"""
    service = PostService(db)
    post = await service.get_by_slug(slug)
    if not post:
        raise HTTPException(status_code=404, detail="文章不存在")
    return post


@router.post("", response_model=PostResponse, status_code=201)
async def create_post(
    data: PostCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user(required_role="ADMIN")),
):
    """创建文章(仅管理员)"""
    service = PostService(db)
    return await service.create(data, author_id=current_user.id)


@router.post("/{post_id}/views")
async def increment_views(
    post_id: str,
    db: AsyncSession = Depends(get_db),
):
    """阅读量 +1"""
    service = PostService(db)
    await service.increment_views(post_id)
    return {"ok": True}

4.3 API 接口总览

方法 路径 说明 鉴权
GET /api/v1/posts 文章列表(分页/标签/排序) 公开
GET /api/v1/posts/{slug} 文章详情 公开
POST /api/v1/posts 创建文章 ADMIN
PUT /api/v1/posts/{id} 更新文章 ADMIN
DELETE /api/v1/posts/{id} 删除文章 ADMIN
POST /api/v1/posts/{id}/views 阅读量 +1 公开
POST /api/v1/posts/{id}/like 点赞/取消 USER
GET /api/v1/comments?post_id= 评论列表 公开
POST /api/v1/comments 发表评论 USER
GET /api/v1/guestbook 留言列表 公开
POST /api/v1/guestbook 发表留言 USER
GET /api/v1/projects 项目列表 公开
POST /api/v1/auth/register 注册 公开
POST /api/v1/auth/login 登录(JWT) 公开
POST /api/v1/auth/github GitHub OAuth 公开
GET /api/v1/search?q= 全站搜索 公开
POST /api/v1/upload 文件上传 USER
GET /api/v1/analytics 站点统计 ADMIN
WS /ws/online 实时在线人数

五、核心代码文件详解

5.1 FastAPI 入口

# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles

from app.core.database import engine, Base
from app.core.redis import redis_client
from app.core.celery_app import celery_app
from app.api.v1.router import router as api_router
from app.middleware.rate_limit import RateLimitMiddleware
from app.middleware.request_log import RequestLogMiddleware


@asynccontextmanager
async def lifespan(app: FastAPI):
    """应用生命周期"""
    # 启动时: 检查数据库连接 / Redis 连通性
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)  # 开发用,生产用 Alembic
    await redis_client.ping()
    yield
    # 关闭时: 释放连接
    await engine.dispose()
    await redis_client.close()


app = FastAPI(
    title="Tech Blog API",
    version="1.0.0",
    lifespan=lifespan,
    docs_url="/docs",          # Swagger UI
    redoc_url="/redoc",        # ReDoc
)

# 中间件注册(顺序重要)
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
app.add_middleware(RequestLogMiddleware)
app.add_middleware(RateLimitMiddleware)

# 路由注册
app.include_router(api_router)

# 静态文件(用户上传的图片等)
app.mount("/static", StaticFiles(directory="static"), name="static")

5.2 配置管理

# app/config.py
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    # 应用
    APP_NAME: str = "Tech Blog"
    DEBUG: bool = False

    # 数据库
    DATABASE_URL: str = "postgresql+asyncpg://blog:password@localhost:5432/blog"

    # Redis
    REDIS_URL: str = "redis://:password@localhost:6379/0"

    # JWT
    JWT_SECRET: str = "change-this-in-production"
    JWT_ALGORITHM: str = "HS256"
    JWT_EXPIRES_MINUTES: int = 10080  # 7 天

    # GitHub OAuth
    GITHUB_CLIENT_ID: str = ""
    GITHUB_CLIENT_SECRET: str = ""

    # 对象存储(MinIO / S3)
    OSS_ENDPOINT: str = ""
    OSS_ACCESS_KEY: str = ""
    OSS_SECRET_KEY: str = ""
    OSS_BUCKET: str = "blog-assets"

    # Celery
    CELERY_BROKER_URL: str = "redis://:password@localhost:6379/1"
    CELERY_RESULT_BACKEND: str = "redis://:password@localhost:6379/1"

    model_config = {"env_file": ".env", "case_sensitive": True}


settings = Settings()

5.3 缓存装饰器

# app/core/cache.py
"""Cache-Aside 缓存装饰器"""
import json
import hashlib
from functools import wraps
from app.core.redis import redis_client


def cache(ttl: int = 300):
    """
    缓存装饰器,自动执行 Cache-Aside 模式

    用法:
        @cache(ttl=600)
        async def get_post(slug: str, db: AsyncSession):
            ...

    缓存 Key: f"blog:{func_name}:{hash(args+kwargs)}"
    """
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            # 生成缓存 Key
            key_base = f"blog:{func.__name__}"
            arg_hash = hashlib.md5(
                json.dumps({"args": str(args), "kwargs": str(kwargs)},
                           default=str).encode()
            ).hexdigest()[:12]
            cache_key = f"{key_base}:{arg_hash}"

            # 读缓存
            cached = await redis_client.get(cache_key)
            if cached:
                return json.loads(cached)

            # 查 DB
            result = await func(*args, **kwargs)

            # 写回缓存
            await redis_client.setex(cache_key, ttl, json.dumps(
                result, default=str
            ))
            return result
        return wrapper
    return decorator

5.4 业务逻辑层示例

# app/services/post_service.py
"""文章业务逻辑"""
from sqlalchemy import select, func, desc
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.post import Post, post_tags
from app.models.tag import Tag
from app.schemas.post import PostCreate, PostUpdate, PostResponse
from app.core.cache import cache


class PostService:
    def __init__(self, db: AsyncSession):
        self.db = db

    @cache(ttl=3600)  # 文章详情缓存 1 小时
    async def get_by_slug(self, slug: str) -> Post | None:
        query = select(Post).where(Post.slug == slug)
        result = await self.db.execute(query)
        return result.scalar_one_or_none()

    async def get_paginated(
        self, page: int, limit: int, tag: str | None = None,
        published: bool = True, sort: str = "published_at_desc"
    ) -> dict:
        query = select(Post).where(Post.published == published)

        if tag:
            query = query.join(post_tags).join(Tag).where(Tag.name == tag)

        # 排序
        if sort == "published_at_desc":
            query = query.order_by(desc(Post.published_at))
        elif sort == "views_desc":
            query = query.order_by(desc(Post.views))

        # 分页
        offset = (page - 1) * limit
        query = query.offset(offset).limit(limit)

        result = await self.db.execute(query)
        posts = result.scalars().all()

        # 总数
        count_query = select(func.count()).select_from(Post).where(
            Post.published == published
        )
        total = await self.db.scalar(count_query)

        return {
            "items": posts,
            "total": total,
            "page": page,
            "limit": limit,
            "pages": (total + limit - 1) // limit,
        }

    async def create(self, data: PostCreate, author_id: str) -> Post:
        post = Post(**data.model_dump(), author_id=author_id)
        self.db.add(post)
        await self.db.commit()
        await self.db.refresh(post)
        return post

    async def increment_views(self, post_id: str) -> None:
        query = select(Post).where(Post.id == post_id)
        result = await self.db.execute(query)
        post = result.scalar_one_or_none()
        if post:
            post.views += 1
            await self.db.commit()

六、依赖注入

# app/api/deps.py
"""FastAPI 依赖注入"""

from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.ext.asyncio import AsyncSession
from jose import jwt, JWTError

from app.core.database import async_session
from app.core.security import decode_token
from app.models.user import User, UserRole

security = HTTPBearer()


async def get_db() -> AsyncSession:
    """数据库会话依赖"""
    async with async_session() as session:
        try:
            yield session
        finally:
            await session.close()


async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security),
    db: AsyncSession = Depends(get_db),
    required_role: UserRole | None = None,
) -> User:
    """获取当前登录用户(可选角色校验)"""
    token = credentials.credentials
    payload = decode_token(token)
    if payload is None:
        raise HTTPException(status_code=401, detail="无效的 Token")

    user_id = payload.get("sub")
    query = select(User).where(User.id == user_id)
    result = await db.execute(query)
    user = result.scalar_one_or_none()

    if not user:
        raise HTTPException(status_code=401, detail="用户不存在")

    if required_role and user.role != required_role:
        raise HTTPException(status_code=403, detail="权限不足")

    return user

七、数据流 & 缓存策略

请求进入  FastAPI Middleware日志/限流
               
         ┌─────▼─────┐
           API Route 
         └─────┬─────┘
               
         ┌─────▼──────┐
           Service     @cache 装饰器自动查 Redis
                   
         └─────┬──────┘
               
          ┌────▼────┐         ┌──────────┐
            Cache     hit →│   Redis   
            Check            └──────────┘
          └────┬────┘
            miss
          ┌────▼────┐
            DB       SQLAlchemy async session
            Query  
          └────┬────┘
               
          ┌────▼────┐
           写回缓存    Redis setex
          └─────────┘

缓存策略:
  Cache-Aside:    Redis  miss  DB  写回 + TTL
                DB 更新  删除缓存  下次读重建
  TTL:
    文章详情:      3600s (1h)
    文章列表:      1800s (30min)
    标签/项目:     3600s (1h)
    统计数据:      600s  (10min)

缓存 Key 格式:
  blog:post:{slug}
  blog:posts:page:{n}:tag:{tag}
  blog:projects
  blog:tags
  blog:analytics

八、异步任务 (Celery)

# app/core/celery_app.py
from celery import Celery
from app.config import settings

celery_app = Celery(
    "blog_tasks",
    broker=settings.CELERY_BROKER_URL,
    backend=settings.CELERY_RESULT_BACKEND,
)

celery_app.conf.update(
    task_serializer="json",
    accept_content=["json"],
    result_serializer="json",
    timezone="Asia/Shanghai",
    task_track_started=True,
    beat_schedule={
        "aggregate-daily-stats": {
            "task": "app.tasks.stats_tasks.aggregate_daily_stats",
            "schedule": 3600,  # 每小时聚合一次
        },
        "cleanup-expired-sessions": {
            "task": "app.tasks.cleanup_tasks.cleanup_expired_sessions",
            "schedule": 86400,  # 每天清理一次
        },
    },
)
# app/tasks/stats_tasks.py
"""统计数据聚合任务"""
from app.core.celery_app import celery_app
from app.core.database import sync_session
from app.models.post import Post
from app.models.comment import Comment
from app.models.site_stats import SiteStats


@celery_app.task
def aggregate_daily_stats():
    """每小时聚合站点统计数据"""
    with sync_session() as db:
        total_posts = db.query(Post).count()
        total_comments = db.query(Comment).count()
        total_views = db.query(func.sum(Post.views)).scalar() or 0

        stats = db.query(SiteStats).first()
        if not stats:
            stats = SiteStats(id="main")
            db.add(stats)
        stats.total_posts = total_posts
        stats.total_comments = total_comments
        stats.total_views = total_views
        db.commit()

九、WebSocket 实时在线

# 嵌入 app/main.py 或单独 websocket_manager.py
from fastapi import WebSocket, WebSocketDisconnect
import json


class ConnectionManager:
    """WebSocket 连接管理"""
    def __init__(self):
        self.active_connections: dict[str, WebSocket] = {}

    async def connect(self, websocket: WebSocket, client_id: str):
        await websocket.accept()
        self.active_connections[client_id] = websocket
        await self.broadcast_online_count()

    def disconnect(self, client_id: str):
        self.active_connections.pop(client_id, None)

    async def broadcast_online_count(self):
        count = len(self.active_connections)
        for conn in self.active_connections.values():
            try:
                await conn.send_json({
                    "type": "online_count",
                    "data": count
                })
            except Exception:
                pass

    async def broadcast_post_update(self, post_id: str, action: str):
        """文章更新时推送(新评论、阅读量变化)"""
        for conn in self.active_connections.values():
            try:
                await conn.send_json({
                    "type": "post:updated",
                    "data": {"post_id": post_id, "action": action}
                })
            except Exception:
                pass


manager = ConnectionManager()


# WebSocket 路由
@app.websocket("/ws/online")
async def websocket_endpoint(websocket: WebSocket):
    client_id = str(id(websocket))
    await manager.connect(websocket, client_id)
    try:
        while True:
            data = await websocket.receive_text()
            # 处理客户端消息(可选)
    except WebSocketDisconnect:
        manager.disconnect(client_id)
        await manager.broadcast_online_count()

十、扩展能力设计

10.1 模块扩展 —— 加新功能只需三步

想给博客加一个「读书笔记」模块:

① app/models/booknote.py    ← 数据模型
② app/schemas/booknote.py   ← Pydantic Schema
③ app/api/v1/booknotes.py   ← 路由(CRUD 约 40 行)
④ app/services/booknote_service.py ← 业务逻辑
⑤ router.include_router(booknotes.router) ← 注册到主路由

每个模块完全解耦,不碰其他模块一行代码。

10.2 可插拔功能清单

功能 实现方式 影响范围
文章推荐 services/recommend_service.py 新增1文件
RSS 订阅 api/v1/rss.py 新增1路由
邮件通知 tasks/email_tasks.py 新增 Celery 任务
阅读排行榜 services/analytics_service.py + 1行 方法追加
Markdown 导入 services/import_service.py 新增1文件
友情链接 models/link.py + api/v1/links.py 2个文件
音乐播放器 前端组件 + api/v1/music.py 纯前端+1路由
相册集 models/album.py + api/v1/albums.py 2个文件
数据分析 前端图表 + api/v1/analytics + Celery 3个文件

10.3 后期可接入的 AI 功能

功能 需要 代码量
文章自动标签 jieba + TF-IDF \~30行
阅读量预测 sklearn LinearRegression \~50行
文章摘要生成 调用 LLM API \~40行
相似文章推荐 cosine similarity \~30行
评论情感分析 transformers pipeline \~20行

因为这些全部是 Python 生态,不需要换栈或起新服务。


十一、部署 & 开发流程

11.1 本地开发

# 后端
cd apps/server
python -m venv .venv
pip install -r requirements.txt
alembic upgrade head          # 数据库迁移
uvicorn app.main:app --reload # 热重载开发 → http://localhost:8000
# Swagger 自动生成 → http://localhost:8000/docs

# 前端
cd apps/client
pnpm install
pnpm dev                     # → http://localhost:5173

# Celery 任务队列(另开终端)
cd apps/server
celery -A app.core.celery_app worker -l info
celery -A app.core.celery_app beat -l info

# Redis + PostgreSQL + MinIO
docker compose up -d postgres redis minio

11.2 docker-compose.yml

version: "3.8"
services:
  client:
    build:
      context: ./apps/client
      dockerfile: ../../docker/Dockerfile.client
    ports:
      - "3000:80"
    depends_on:
      - server

  server:
    build:
      context: ./apps/server
      dockerfile: ../../docker/Dockerfile.server
    ports:
      - "8000:8000"
    env_file: .env
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

  admin:
    build:
      context: ./apps/admin
      dockerfile: ../../docker/Dockerfile.client
    ports:
      - "3001:80"
    depends_on:
      - server

  celery_worker:
    build:
      context: ./apps/server
      dockerfile: ../../docker/Dockerfile.server
    command: celery -A app.core.celery_app worker -l info
    env_file: .env
    depends_on:
      - server
      - redis

  postgres:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: blog
      POSTGRES_USER: blog
      POSTGRES_PASSWORD: ${DB_PASSWORD:?err}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U blog"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redisdata:/data
    command: redis-server --requirepass ${REDIS_PASSWORD:?err}

  minio:
    image: minio/minio
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - miniodata:/data
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: ${OSS_ACCESS_KEY}
      MINIO_ROOT_PASSWORD: ${OSS_SECRET_KEY}

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./docker/nginx/ssl:/etc/nginx/ssl
    depends_on:
      - client
      - server
      - admin

volumes:
  pgdata:
  redisdata:
  miniodata:

11.3 依赖清单

# apps/server/requirements.txt
# === Web 框架 ===
fastapi==0.115.*
uvicorn[standard]==0.34.*
pydantic==2.*
pydantic-settings==2.*

# === 数据库 ===
sqlalchemy[asyncio]==2.0.*
asyncpg==0.30.*
alembic==1.14.*

# === 认证 ===
python-jose[cryptography]==3.3.*
passlib[bcrypt]==1.7.*
python-multipart==0.0.*

# === 缓存 / 队列 ===
redis[hiredis]==5.*
celery[redis]==5.*

# === 存储 ===
minio==7.*

# === 工具 ===
httpx==0.28.*
python-slugify==8.*
Pillow==11.*

十二、与 NestJS 版对比总结

维度 NestJS (TS) FastAPI (Python) 对你
学习成本 高(DI/IoC/装饰器体系) (3 小时上手)
开发效率 编译 + 重启 热重载 + 自动 docs
数据库 ORM Prisma SQLAlchemy 2.0 功能对等
类型安全 TS 编译时 Pydantic 运行时 ✅ 够用
AI/ML 扩展 ❌ 需另外起 Python 服务 原生支持
秋招加成 Node 后端岗位 Python 后端岗位更多
微信生态 ✅ wechaty/itchat 等 ✅ 你之前玩过

结论: Python 版对成品效果没有显著提升或下降,但对你个人——备考压力小、秋招面更广、后期加 AI 功能不需要换栈,是更划算的选择


本文由 admin 原创,转载请注明出处。

相关推荐

暂无评论,来发表第一条评论吧

发表评论

登录 后发表评论