Python 全栈应用最佳实践完整指南

引言

为什么"能让 AI 跑起来"和"能上线的项目"之间隔着一条鸿沟

过去两年,编程世界的门槛以肉眼可见的速度坍塌。Vibe coding 让无数从未写过一行代码的人,能够对着 AI 说一句"帮我做一个记账应用",然后看着代码在屏幕上流淌,几分钟后真的得到一个能跑的页面。这无疑是程序设计民主化进程中激动人心的一刻。

但如果你仔细观察,会发现一个有趣的现象:网上绝大多数教程,终点都停在"部署一个静态页面" ------把 HTML 扔到 Vercel 上,得到一个漂亮的网址,然后呢?然后就没有然后了。

一旦你的想法稍微"真实"一点------需要用户注册登录、需要保存数据、需要数据库、需要两个服务互相通信------你会发现 AI 给你的代码突然"散架"了:数据库连不上、环境变量丢失、本地跑得好好的项目部署后疯狂报错、改了一行代码整个应用崩掉。很多 Vibe Coder 和新手程序员在这一刻陷入了一种熟悉的循环:不停地把报错信息粘贴给 AI,碰运气式地修复,直到项目在某个深夜彻底无法挽救,然后重新开始。

问题不在于 AI 不够聪明,而在于:AI 擅长生成局部正确的代码,但没有人替你回答那些"代码之外"的问题------

  • 文件应该怎么组织?为什么数据库连接代码不能写在路由里?
  • 为什么 Web、数据库、缓存要拆开管理,而不是塞进一个"大而全"的包里?
  • 哪些文件是给机器看的、永远不该上传?哪些是项目的"档案"、必须妥善保管?
  • 为什么改数据库表结构要有"迁移"这回事?
  • 本地能跑和生产环境能跑,中间隔着什么?

这些恰恰是传统程序员在多年的项目中用无数次深夜事故换来的"默会知识",它们很少出现在教程里,却决定了项目是"能跑的玩具"还是"能活的系统"。在 AI 时代,这些知识不但没有贬值,反而升值了 ------因为 AI 可以替你写代码,但不能替你做架构决策;你可以不会手写 JWT 认证,但你需要知道它在整个系统中的位置。

所以这篇文章想做一件不太一样的事:走完一条完整的全景路线 。从一个空文件夹开始,用 React + FastAPI + PostgreSQL 这个最经典的全栈组合,一路走到生产环境部署------覆盖目录结构、代码分层、容器化、Git 工作流、代码规范、测试、CI/CD 的每一个环节,并且解释每一步"为什么这么做" 。

读完之后,无论你是 Vibe Coder 还是新手程序员,你和 AI 的协作方式都会发生质变:你不再是一个只会复制报错的"许愿者",而是一个知道系统全貌、能提出正确问题、能审查 AI 产出 的指挥者。AI 负责写代码,你负责让这个项目真正活下来。

这不是又一篇"十分钟部署静态页面"的教程------这是我们缺了很久的那一课。


技术栈:React (前端) + FastAPI (后端) + PostgreSQL (数据库) + Docker Compose (本地) + GitHub Actions (CI/CD) + 云服务器部署

第一部分:项目规划与仓库策略

1.1 Monorepo vs Polyrepo

对于中小型全栈项目,推荐 Monorepo(单仓库):

复制代码
myapp/                    # 单一 Git 仓库
├── backend/              # FastAPI
├── frontend/             # React
├── docker-compose.yml
├── .github/workflows/    # CI/CD
└── README.md

理由:版本同步简单(前端 API 调用和后端接口一起改)、一次 PR 全局评审、CI 配置集中。只有团队规模大、部署节奏完全不同时才拆分仓库。

1.2 创建仓库与初始提交

bash 复制代码
mkdir myapp && cd myapp
git init -b main
# 创建 GitHub 远程仓库后
git remote add origin git@github.com:yourname/myapp.git

第一笔提交只放基础文件 (提交信息遵循 Conventional Commits 规范):

bash 复制代码
git commit -m "chore: initialize repository with gitignore and readme"

第二部分:目录结构设计(核心,请仔细看)

复制代码
myapp/
├── .gitignore
├── .editorconfig
├── .env.example              # 环境变量模板(真实 .env 永不入库)
├── README.md
├── Makefile                  # 常用命令快捷方式
├── docker-compose.yml        # 本地开发编排
├── docker-compose.prod.yml   # 生产覆盖配置
├── .github/
│   └── workflows/
│       ├── backend-ci.yml
│       ├── frontend-ci.yml
│       └── deploy.yml
│
├── backend/
│   ├── Dockerfile
│   ├── .dockerignore
│   ├── pyproject.toml        # 用 uv/poetry 管理依赖(不用 requirements.txt 裸奔)
│   ├── .python-version
│   ├── alembic.ini           # 数据库迁移
│   ├── alembic/
│   │   ├── env.py
│   │   └── versions/
│   ├── app/
│   │   ├── __init__.py
│   │   ├── main.py           # FastAPI 入口
│   │   ├── core/
│   │   │   ├── config.py     # pydantic-settings 配置
│   │   │   └── security.py   # JWT、密码哈希
│   │   ├── db/
│   │   │   ├── base.py       # Declarative Base
│   │   │   └── session.py    # engine、SessionLocal
│   │   ├── models/           # SQLAlchemy ORM 模型
│   │   │   └── user.py
│   │   ├── schemas/          # Pydantic 模型(请求/响应)
│   │   │   └── user.py
│   │   ├── api/              # 路由,按资源分层
│   │   │   ├── deps.py       # 依赖注入(get_db, get_current_user)
│   │   │   └── v1/
│   │   │       ├── router.py
│   │   │       └── endpoints/
│   │   │           ├── auth.py
│   │   │           └── users.py
│   │   ├── crud/             # 数据库操作层(与路由分离)
│   │   │   └── user.py
│   │   └── services/         # 业务逻辑层(复杂逻辑)
│   ├── tests/
│   │   ├── conftest.py
│   │   └── test_auth.py
│   └── scripts/
│       └── start.sh          # 容器启动脚本(等数据库就绪+迁移+启动)
│
├── frontend/
│   ├── Dockerfile
│   ├── .dockerignore
│   ├── package.json
│   ├── vite.config.ts
│   ├── tsconfig.json
│   ├── index.html
│   ├── nginx.conf            # 生产环境静态文件服务+反向代理
│   ├── public/
│   └── src/
│       ├── main.tsx
│       ├── App.tsx
│       ├── api/              # API 客户端(axios/fetch 封装)
│       │   ├── client.ts
│       │   └── auth.ts
│       ├── hooks/
│       ├── store/            # 状态管理(zustand)
│       ├── pages/
│       ├── components/       # 通用组件
│       │   └── ui/
│       ├── types/            # TS 类型定义
│       └── styles/
│
└── docs/
    ├── architecture.md
    └── api.md

关键分层原则 :api/(路由)→ services/(业务)→ crud/(数据访问)→ models/(表结构)。路由层不直接写 SQL,业务逻辑不混在路由函数里。

第三部分:后端搭建(FastAPI)

3.1 初始化项目与依赖管理

使用 uv(目前最快最主流的 Python 包管理器):

bash 复制代码
cd backend
uv init
uv add fastapi "uvicorn[standard]" sqlalchemy asyncpg alembic pydantic-settings "python-jose[cryptography]" "passlib[bcrypt]"
uv add --dev pytest pytest-asyncio httpx ruff mypy

3.2 配置管理 app/core/config.py

python 复制代码
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", env_prefix="APP_")
    PROJECT_NAME: str = "MyApp"
    DATABASE_URL: str
    SECRET_KEY: str
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
    CORS_ORIGINS: list[str] = ["http://localhost:5173"]
    DEBUG: bool = False
settings = Settings()

.env.example(入库)与 .env(不入库):

env 复制代码
APP_DATABASE_URL=postgresql+asyncpg://myapp:myapp@db:5432/myapp
APP_SECRET_KEY=change-me-in-production
APP_DEBUG=true

3.3 数据库会话 app/db/session.py

python 复制代码
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_session_engine  # 实际为 create_async_engine
from app.core.config import settings
engine = create_async_engine(settings.DATABASE_URL, pool_pre_ping=True)
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
async def get_db() -> AsyncSession:
    async with AsyncSessionLocal() as session:
        yield session

3.4 ORM 模型示例 app/models/user.py

python 复制代码
from datetime import datetime
from sqlalchemy import DateTime, String, func
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
class User(Base):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
    hashed_password: Mapped[str] = mapped_column(String(255))
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

3.5 路由示例 app/api/v1/endpoints/users.py

python 复制代码
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_db
from app.crud import user as crud_user
from app.schemas.user import UserCreate, UserRead
router = APIRouter(prefix="/users", tags=["users"])
@router.post("", response_model=UserRead, status_code=status.HTTP_201_CREATED)
async def create_user(payload: UserCreate, db: AsyncSession = Depends(get_db)):
    if await crud_user.get_by_email(db, payload.email):
        raise HTTPException(status_code=409, detail="Email already registered")
    return await crud_user.create(db, payload)

3.6 数据库迁移(永远不要手写 SQL 建表)

bash 复制代码
alembic revision --autogenerate -m "create users table"
alembic upgrade head

迁移文件必须提交到 Git,生产部署靠它升级数据库。

第四部分:前端搭建(React)

4.1 初始化

bash 复制代码
cd frontend
npm create vite@latest . -- --template react-ts
npm install axios zustand react-router-dom
npm install -D tailwindcss @tanstack/react-query

4.2 API 客户端 src/api/client.ts

typescript 复制代码
import axios from "axios";
export const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL ?? "/api/v1",
  timeout: 10000,
});
api.interceptors.request.use((config) => {
  const token = localStorage.getItem("token");
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

最佳实践 :前端请求 baseURL 用相对路径 /api/v1,由 Nginx 反向代理转发,避免 CORS 和环境切换问题。

4.3 环境变量

env 复制代码
# frontend/.env.development(入库)
VITE_API_URL=/api/v1

命名必须 VITE_ 前缀,且不要放任何密钥(前端一切皆公开)。

第五部分:容器化(每个服务一个容器)

5.1 backend/Dockerfile(多阶段构建)

dockerfile 复制代码
# ---------- 构建阶段 ----------
FROM python:3.12-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
# ---------- 运行阶段 ----------
FROM python:3.12-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY ./app ./app
COPY ./alembic ./alembic
COPY ./alembic.ini .
COPY ./scripts/start.sh .
RUN chmod +x start.sh
ENV PATH="/app/.venv/bin:$PATH"
USER appuser                          # 非 root 运行,安全最佳实践
EXPOSE 8000
CMD ["./start.sh"]

backend/scripts/start.sh:

bash 复制代码
#!/bin/bash
set -e
echo "Waiting for db..."
until python -c "import socket; socket.create_connection(('${APP_DB_HOST:-db}', 5432))" 2>/dev/null; do sleep 1; done
alembic upgrade head          # 启动前自动迁移
exec uvicorn app.main:app --host 0.0.0.0 --port 8000

5.2 frontend/Dockerfile(构建+nginx 两阶段)

dockerfile 复制代码
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80

frontend/nginx.conf:

nginx 复制代码
server {
    listen 80;
    root /usr/share/nginx/html;
    location / {
        try_files $uri $uri/ /index.html;   # SPA 路由回退
    }
    location /api/ {
        proxy_pass http://web:8000/api/;    # 反代到后端容器
        proxy_set_header Host $host;
    }
}

5.3 根目录 docker-compose.yml

yaml 复制代码
services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: myapp
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myapp"]
      interval: 5s
      retries: 5
  web:
    build: ./backend
    env_file: .env
    environment:
      APP_DATABASE_URL: postgresql+asyncpg://myapp:myapp@db:5432/myapp
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - ./backend/app:/app/app     # 开发热重载
    ports:
      - "8000:8000"
  frontend:
    build: ./frontend
    depends_on:
      - web
    ports:
      - "5173:80"
volumes:
  pgdata:

.dockerignore(backend 和 frontend 各一份,必须配置):

复制代码
.git
__pycache__/
*.pyc
.venv/
node_modules/
dist/
.env
tests/

第六部分:Git 工作流与提交规范

6.1 分支模型

复制代码
main          # 永远可部署,受保护分支,禁止直接 push
└── develop   # 集成分支(小团队可省略)
    └── feature/xxx   # 每个功能一个分支
    └── fix/xxx

6.2 每日工作流

bash 复制代码
git checkout -b feature/user-auth     # 从 main 切出
# ...编码...
git add -p                             # 逐块审查暂存
git commit -m "feat(auth): add login endpoint with JWT"
git push -u origin feature/user-auth
# 在 GitHub 上发起 Pull Request → CI 通过 → Code Review → Squash Merge

6.3 Conventional Commits 规范

复制代码
feat:     新功能        feat(auth): add refresh token
fix:      修bug         fix(db): handle connection pool exhaustion
refactor: 重构          refactor: extract crud layer
test:     测试          test: add auth endpoint tests
docs:     文档          docs: update api usage
chore:    杂务          chore: bump fastapi to 0.115
ci:       CI配置        ci: add docker build workflow

6.4 .gitignore 根目录关键内容

gitignore 复制代码
.env
.venv/
__pycache__/
node_modules/
dist/
*.log
.pgdata/
.idea/ .vscode/

第七部分:代码质量与测试

7.1 后端 pyproject.toml 工具配置

toml 复制代码
[tool.ruff]
line-length = 100
lint.select = ["E", "F", "I", "B", "UP"]
[tool.pytest.ini_options]
asyncio_mode = "auto"

7.2 测试 backend/tests/conftest.py

python 复制代码
import pytest
from httpx import ASGITransport, AsyncClient
from app.main import app
@pytest.fixture
async def client():
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
        yield c

测试用独立的测试数据库(或 SQLite 内存库),CI 中用 service 容器跑真实 PG。

7.3 Makefile 统一命令入口

makefile 复制代码
up:        ; docker compose up -d --build
down:      ; docker compose down
logs:      ; docker compose logs -f
migrate:   ; docker compose exec web alembic upgrade head
test:      ; docker compose exec web pytest -v
lint:      ; cd backend && uv run ruff check . && cd ../frontend && npm run lint

第八部分:CI/CD(GitHub Actions)

.github/workflows/backend-ci.yml:

yaml 复制代码
name: Backend CI
on:
  pull_request:
    paths: ["backend/**"]
  push:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    defaults: { run: { working-directory: backend } }
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: test
        options: >-
          --health-cmd pg_isready --health-interval 5s --health-retries 5
        ports: ["5432:5432"]
    env:
      APP_DATABASE_URL: postgresql+asyncpg://test:test@localhost:5432/test
      APP_SECRET_KEY: ci-secret
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync
      - run: uv run ruff check .
      - run: uv run mypy app
      - run: uv run alembic upgrade head
      - run: uv run pytest -v

.github/workflows/deploy.yml(合并到 main 后自动部署):

yaml 复制代码
name: Deploy
on:
  push:
    branches: [main]
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build & push images
        run: |
          echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker build -t ghcr.io/${{ github.repository }}/backend:latest ./backend
          docker build -t ghcr.io/${{ github.repository }}/frontend:latest ./frontend
          docker push ghcr.io/${{ github.repository }}/backend:latest
          docker push ghcr.io/${{ github.repository }}/frontend:latest
      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SSH_HOST }}
          username: deploy
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /opt/myapp
            docker compose -f docker-compose.prod.yml pull
            docker compose -f docker-compose.prod.yml up -d --remove-orphans

所有敏感信息(SSH、密码、密钥)存 GitHub Secrets,永不写入代码。

第九部分:生产部署

9.1 docker-compose.prod.yml(生产覆盖)

yaml 复制代码
services:
  web:
    image: ghcr.io/yourname/myapp/backend:latest   # 用镜像而非 build
    env_file: .env.production
    restart: always
    ports: []          # 不直接暴露,走反向代理
    command: ["./start.sh"]
  frontend:
    image: ghcr.io/yourname/myapp/frontend:latest
    restart: always
    ports:
      - "80:80"
      - "443:443"
  db:
    restart: always
    ports: []          # 数据库绝不暴露公网

9.2 服务器上(Ubuntu + 一台 VPS 即可起步)

bash 复制代码
# 服务器目录:/opt/myapp,只放 compose 文件和 .env.production
sudo ufw allow 80,443/tcp && sudo ufw enable   # 只开放 web 端口
# HTTPS 用 Caddy 或 certbot + nginx

生产检查清单:

  • ✅ 数据库端口不暴露公网
  • ✅ 容器以非 root 运行
  • ✅ DEBUG=false,强密钥走环境变量
  • ✅ 配置日志轮转与备份(pg_dump cron 定时备份)
  • ✅ HTTPS 强制

第十部分:完整里程碑时间线

阶段 动作 提交
1 git init + README + .gitignore chore: init repo
2 搭 backend 骨架 + config + db session feat(core): project skeleton
3 User 模型 + Alembic 迁移 feat(db): add user model and migration
4 注册/登录 API + 测试 feat(auth): add register and login
5 Dockerfile + docker-compose 全栈跑通 chore: add docker compose setup
6 Vite + React 骨架 + API client feat(frontend): init react app
7 登录页面 + JWT 存储 + 路由守卫 feat(frontend): add login page
8 CI workflows ci: add backend and frontend pipelines
9 生产 compose + 部署脚本 feat: add production deployment
10 上线 + 配置 HTTPS + 备份 cron chore: production launch

核心原则总结

  1. 一个容器一个进程------PG、后端、前端永远是三个容器
  2. .env 永不入库 ------用 .env.example 做模板
  3. 迁移文件必须入库------数据库变更可追溯、可回滚
  4. main 分支受保护------一切通过 PR + CI
  5. 提交信息规范化------半年后你会感谢自己
  6. 前端走 Nginx 反代------消灭 CORS 问题
  7. 密钥只存在于 Secrets ------代码里永远没有密码
    按这个流程走,你会得到一个开发体验好、可测试、可复现、可平滑扩展到更复杂场景 的项目。
相关推荐
智购科技智能售货柜1 小时前
顾客已经关门,视觉售货柜还在处理旧画面:延迟积压怎么查?~YH
python·单片机·嵌入式硬件·物联网·eclipse
CoderYanger1 小时前
Java EE 进阶:5. Spring IoC&DI(第三弹):Spring DI
java·开发语言·spring boot·spring·面试·java-ee·mvc
梯度下降者1 小时前
Python从0到100(三):Python中的变量介绍
python·作用域·变量·数据类型·计算机常识
AIFQuant1 小时前
Python期货行情API接入:股指、黄金、原油多品种数据管道实战
开发语言·python·金融·区块链·量化开发
echome8881 小时前
JavaScript async/await 错误处理:别让 Promise 静默失败
开发语言·前端·javascript·promise
运维行者_1 小时前
供应链安全成最大风险源,企业如何构建可控的运维体系?
运维·开发语言·网络·数据库·安全·云计算·流量监控
.YM.Z1 小时前
优选算法——【双指针算法2】C++实现
开发语言·c++·算法
逆向编程2 小时前
MATLAB高级数据结构:cell、struct与table深度应用
开发语言·数据结构·matlab
威哥爱编程2 小时前
Python 智能体开发实战 07:让它查课表它张口就编?给 Agent 接上真正能动手的工具
python·agent