跳转至

从零搭建 Web 项目的工程化规范全指南

一句话说明:以 Python/FastAPI + SQLite + Nginx + Linux 为例,覆盖一个真实 SaaS 项目从开发到上线的 13 项工程化规范,每项给出"为什么、怎么做、用什么工具"。


阅读前说明

本文面向已经会写代码、但没有完整上线过项目的开发者。每个章节分三档标注:

标记含义
[必须]不做就是埋雷,上线必出事
[推荐]做了省心省力,团队协作必备
[进阶]锦上添花,中大型项目再考虑

全文以一个假设项目为例:

项目名:t2d-api(2型糖尿病肠道菌群分析平台)
技术栈:FastAPI 0.115+ + SQLite + Nginx
运行环境:Python 3.12(FastAPI 0.130+ 最低要求 Python 3.10)
服务器:Ubuntu 24.04 LTS
包管理:uv(2025-2026 年 Python 生态首选工具)

一、版本控制(Git)

为什么需要

不用 Git 的后果: - 你的代码只有一份,硬盘坏了就全没了 - "上次改了什么来着?"——没有历史记录 - 多人协作靠微信传文件,覆盖别人代码

1.1 初始化仓库 [必须]

# 创建项目目录并初始化
mkdir t2d-api && cd t2d-api
git init                          # 初始化 Git 仓库

# 设置用户信息(首次使用 Git 必须配置)
git config user.name "你的名字"   # 提交记录里显示的名字
git config user.email "your@email.com"  # 和 GitHub 账号邮箱一致

1.2 .gitignore 最佳实践 [必须]

白话:告诉 Git "这些文件不要管"。不配 .gitignore,密码、临时文件、几百MB的模型文件全传上去。

# ===== Python =====
__pycache__/              # Python 编译缓存,每次运行自动生成
*.py[cod]                 # .pyc .pyo .pyd 编译文件
*.egg-info/               # 包安装信息
dist/                     # 打包产物
build/                    # 构建临时文件
.venv/                    # 虚拟环境目录(不要提交几百MB的依赖)
venv/

# ===== 环境与密钥 =====
.env                      # 【关键】数据库密码、API Key 都在这里
.env.local                # 本地覆盖配置
*.pem                     # SSL 私钥
*.key                     # 密钥文件

# ===== IDE =====
.vscode/                  # VS Code 个人配置
.idea/                    # PyCharm 配置
*.swp                     # vim 临时文件

# ===== 系统 =====
.DS_Store                 # macOS 文件夹元数据
Thumbs.db                 # Windows 缩略图缓存

# ===== 数据库 =====
*.db                      # SQLite 数据库文件(生产数据不入库)
*.sqlite3

# ===== 日志 =====
logs/                     # 日志目录
*.log                     # 日志文件

# ===== 测试 =====
htmlcov/                  # 测试覆盖率 HTML 报告
.coverage                 # 覆盖率数据文件
.pytest_cache/            # pytest 缓存

关键提醒.env 文件里存密码、密钥,绝对不能提交到 Git。一旦提交,即使之后删除,Git 历史里仍然有。

1.3 提交规范(Conventional Commits)[推荐]

白话:给每次提交写个标准格式的"日记标题",让人一眼看懂改了什么。

格式:<类型>: <描述>

# 常用类型
feat: 新增物种丰度查询接口       # 新功能
fix: 修复样本上传时文件名乱码     # 修 bug
docs: 更新 API 文档说明           # 文档
refactor: 重构数据库查询逻辑      # 重构(不改功能)
test: 添加用户认证单元测试        # 测试
chore: 升级 FastAPI  0.115      # 杂务(依赖、配置等)
perf: 优化大表分页查询性能        # 性能优化
ci: 添加 GitHub Actions 测试流程  # CI/CD 相关

用工具强制执行(Python 项目推荐 commitizen + pre-commit):

# 安装 commitizen(交互式提交工具)
pip install commitizen                       # 安装提交辅助工具

# 安装 pre-commit(Git 钩子框架)
pip install pre-commit                       # 安装 Git 钩子管理器

创建 .pre-commit-config.yaml

# .pre-commit-config.yaml —— 定义 Git 提交前自动执行的检查
repos:
  # Ruff:一个工具替代 Black + Flake8 + isort(Rust 写的,超快)
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.6                              # 指定 Ruff 版本
    hooks:
      - id: ruff                             # 代码检查(lint)
        args: [--fix]                        # 自动修复简单问题
      - id: ruff-format                      # 代码格式化(替代 Black)

  # commitizen:检查提交信息是否符合规范
  - repo: https://github.com/commitizen-tools/commitizen
    rev: v4.1.0                              # 指定版本
    hooks:
      - id: commitizen                       # 在 commit-msg 阶段检查格式
pre-commit install                           # 安装 pre-commit 钩子
pre-commit install --hook-type commit-msg    # 安装提交信息检查钩子

# 以后每次 git commit 都会自动检查代码格式和提交信息
# 用交互式提交(不用自己记格式):
cz commit                                    # 会弹出交互菜单让你选类型、填描述

1.4 Tag 版本标记 [推荐]

# 语义化版本号:主版本.次版本.修订号
# 主版本:不兼容的改动(1.0.0 → 2.0.0)
# 次版本:新增功能但兼容(1.0.0 → 1.1.0)
# 修订号:修 bug(1.0.0 → 1.0.1)

git tag -a v1.0.0 -m "首个正式发布版本"      # 创建带注释的标签
git push origin v1.0.0                       # 推送标签到远程

# 用 commitizen 自动打版本标签(根据提交记录自动判断版本号)
cz bump                                      # 自动分析 feat/fix 提交,决定版本号

二、分支管理策略

为什么需要

不管分支的后果: - 直接在 main 上开发,一个 bug 把线上搞崩 - 多人同时改同一个文件,合并冲突到崩溃 - 想回滚到上个版本?找不到那个"干净"的节点

2.1 三种主流策略对比

策略复杂度适合谁核心思想
Git Flow大团队、定期发版main + develop + feature + release + hotfix 五种分支
GitHub Flow小团队、持续部署只有 main + 短命 feature 分支,PR 合并即部署
Trunk-Based最低个人/小团队、CI/CD 成熟所有人直接往 main 提交,用 feature flag 隐藏未完成功能

2.2 推荐:小团队/个人用 GitHub Flow [推荐]

# 1. 从 main 创建功能分支
git checkout -b feat/species-query           # 新建分支:物种查询功能

# 2. 在分支上开发、提交
git add .
git commit -m "feat: 添加物种丰度查询接口"

# 3. 推送分支到远程
git push -u origin feat/species-query        # -u 设置上游追踪

# 4. 在 GitHub/Gitee 上创建 Pull Request
# 5. 代码审查通过后合并到 main
# 6. 删除已合并的分支
git branch -d feat/species-query             # 删除本地分支
git push origin --delete feat/species-query  # 删除远程分支

2.3 保护主分支 [必须]

在 GitHub 仓库设置中: - Settings → Branches → Add branch protection rule - Branch name pattern: main - 勾选 "Require a pull request before merging"(合并前必须 PR) - 勾选 "Require status checks to pass"(CI 必须通过)

这样就没人能直接 git push 到 main,所有改动必须经过 PR 审查。


三、远程仓库

为什么需要

代码只在本地 = 一场硬盘故障就能让你从零开始。

3.1 GitHub vs Gitee 对比

对比项GitHubGitee(码云)
国内访问速度偶尔慢/被墙
国际影响力全球最大国内为主
私有仓库免费无限免费有限制
CI/CDGitHub Actions(强大)Gitee Go(功能少)
社区生态绝对领先中文友好
推荐首选(面试加分)备份/国内协作用

3.2 SSH Key 配置 [必须]

# 生成 SSH 密钥对(如果已有可跳过)
ssh-keygen -t ed25519 -C "your@email.com"    # ed25519 比 RSA 更安全更短
# 一路回车用默认路径,可以设置密码也可以留空

# 查看公钥
cat ~/.ssh/id_ed25519.pub                    # 复制这段内容

# 去 GitHub → Settings → SSH and GPG keys → New SSH key,粘贴公钥

# 测试连接
ssh -T git@github.com                        # 成功会显示 "Hi xxx!"

# 添加远程仓库
git remote add origin git@github.com:你的用户名/t2d-api.git  # SSH 地址
git push -u origin main                      # 首次推送

3.3 同时推送到 GitHub 和 Gitee(双保险)[进阶]

# 添加 Gitee 作为第二个远程
git remote set-url --add --push origin git@gitee.com:你的用户名/t2d-api.git
git remote set-url --add --push origin git@github.com:你的用户名/t2d-api.git

# 现在 git push 会同时推送到两个平台
git push origin main                         # 一次推两个

四、环境隔离

为什么需要

不隔离环境的后果: - 开发时用的 SQLite,上线用 PostgreSQL——代码跑不了 - 本地装了 debug=True,上线忘关——被人看到完整报错信息 - 开发环境装了 100 个测试包,部署到服务器全装一遍——浪费 2 小时

4.1 三套环境分离 [必须]

环境用途特点
development本地开发开调试、热重载、用测试数据
testing/staging测试模拟生产环境、跑自动化测试
production线上关调试、开性能优化、真实数据

4.2 .env 文件管理 [必须]

# .env.example —— 提交到 Git,告诉别人需要哪些配置(值留空或填示例)
DATABASE_URL=sqlite:///./data.db
SECRET_KEY=change-me-to-a-random-string
DEBUG=false
LOG_LEVEL=INFO
ALLOWED_ORIGINS=http://localhost:3000

# .env(本地开发用,不提交到 Git!)
DATABASE_URL=sqlite:///./dev.db
SECRET_KEY=dev-secret-not-for-production
DEBUG=true
LOG_LEVEL=DEBUG
ALLOWED_ORIGINS=*

在 FastAPI 中用 pydantic-settings 读取:

# app/core/config.py —— 配置管理,用 pydantic-settings 自动读 .env 文件
from pydantic_settings import BaseSettings  # pydantic v2 的 settings 模块

class Settings(BaseSettings):
    """应用配置,自动从 .env 文件和环境变量读取"""
    database_url: str = "sqlite:///./data.db"  # 数据库连接地址
    secret_key: str                             # JWT 密钥,必须设置
    debug: bool = False                         # 调试模式,生产环境必须关
    log_level: str = "INFO"                     # 日志级别
    allowed_origins: list[str] = ["http://localhost:3000"]  # CORS 允许的源

    model_config = {                            # pydantic v2 配置方式
        "env_file": ".env",                     # 自动读取 .env 文件
        "env_file_encoding": "utf-8",           # 文件编码
    }

settings = Settings()                          # 全局单例,其他文件 import 即用

4.3 虚拟环境工具对比

工具适合场景优点缺点
uvPython 项目(2025 首选)极快(Rust 写的)、兼容 pip较新,文档少
venv纯 Python内置、零依赖功能少、速度慢
conda科学计算/生信能管理非 Python 依赖(如 R、C 库)体积大、慢
Docker部署完全隔离、可复现学习成本高

推荐组合:开发用 uv(或 venv),生信工具用 conda,部署用 Docker

# === 方案 1:uv(2025-2026 年 Python 官方推荐的包管理工具) ===
# uv 是 Astral 公司(Ruff 同厂)出品的 Rust 工具,一个工具替代 pip + venv + pyenv + pip-tools + poetry
# 不需要预装 Python!uv 自动管理 Python 版本

# 安装 uv(三选一)
curl -LsSf https://astral.sh/uv/install.sh | sh  # Linux/macOS 官方脚本
# pip install uv                                   # 或用 pip 安装
# brew install uv                                  # macOS 用 brew

# ---- 项目管理模式(推荐,完整项目管理) ----
uv init t2d-api                              # 初始化项目(自动创建 pyproject.toml)
cd t2d-api
uv add fastapi uvicorn sqlalchemy            # 添加生产依赖(自动写入 pyproject.toml)
uv add --dev pytest ruff pre-commit          # 添加开发依赖(写入 [dependency-groups])
uv run uvicorn app.main:app --reload         # 运行项目(自动管理虚拟环境,无需手动 activate)
uv run pytest                                # 运行测试(同上)
# uv run 会自动:1.检查 pyproject.toml 2.同步 uv.lock 3.激活 .venv 4.执行命令

# ---- pip 兼容模式(简单项目、快速迁移) ----
uv venv                                      # 创建虚拟环境(在 .venv 目录)
source .venv/bin/activate                     # 激活环境
uv pip install -r requirements.txt           # 安装依赖(比 pip 快 10-100 倍)

# === 方案 2:传统 venv(兜底方案) ===
python -m venv .venv                         # 创建虚拟环境
source .venv/bin/activate                    # 激活
pip install -r requirements.txt              # 安装依赖

2026 年建议:新项目直接用 uv init + uv add + uv run 三件套,不再需要手动管理虚拟环境。uv.lock 锁文件保证跨环境可复现,必须提交到 Git


五、自动化部署(CI/CD)

为什么需要

不用 CI/CD 的后果: - 每次上线手动 SSH 到服务器 → git pull → 重启服务,费时易错 - 忘了跑测试就部署,线上直接炸 - "上次部署的是哪个版本?"——没人记得

5.1 GitHub Actions 完整流程 [推荐]

创建 .github/workflows/deploy.yml

# .github/workflows/deploy.yml —— 自动测试 + 部署流程
name: CI/CD Pipeline                         # 工作流名称

on:
  push:
    branches: [main]                         # 推送到 main 时触发
  pull_request:
    branches: [main]                         # PR 到 main 时触发(只跑测试)

jobs:
  # ===== 第一步:测试 =====
  test:
    runs-on: ubuntu-latest                   # 运行环境
    steps:
      - uses: actions/checkout@v4            # 拉取代码

      - name: Set up Python                  # 安装 Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"             # 指定 Python 版本

      - name: Cache pip packages             # 缓存依赖,加速后续构建
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}

      - name: Install dependencies           # 安装依赖
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt  # 开发/测试依赖

      - name: Lint with Ruff                 # 代码检查
        run: ruff check .                    # 快速检查代码规范

      - name: Run tests with coverage        # 运行测试并统计覆盖率
        run: pytest --cov=app --cov-report=xml --cov-fail-under=80
        # --cov=app          统计 app 目录的覆盖率
        # --cov-report=xml   生成 XML 报告(可上传到 Codecov)
        # --cov-fail-under=80  覆盖率低于 80% 则失败

      - name: Upload coverage to Codecov     # 上传覆盖率报告(可选)
        uses: codecov/codecov-action@v4
        if: github.event_name == 'push'      # 只在 push 时上传
        with:
          file: ./coverage.xml

  # ===== 第二步:部署(仅 push 到 main 时执行) =====
  deploy:
    needs: test                              # 测试通过才部署
    runs-on: ubuntu-latest
    if: github.event_name == 'push'          # PR 不触发部署

    steps:
      - name: Deploy to server via SSH       # SSH 到服务器执行部署
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}    # 服务器 IP(在 GitHub Secrets 配置)
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }} # SSH 私钥
          script: |
            cd /opt/t2d-api                  # 进入项目目录
            git pull origin main             # 拉最新代码
            source .venv/bin/activate        # 激活虚拟环境
            pip install -r requirements.txt  # 安装/更新依赖
            sudo systemctl reload t2d-api    # 优雅重载服务(不断连接)

5.2 配置 GitHub Secrets [必须]

在 GitHub 仓库 → Settings → Secrets and variables → Actions,添加:

Secret 名
SERVER_HOST你的服务器公网 IP
SERVER_USERSSH 登录用户名(不要用 root)
SSH_PRIVATE_KEY服务器对应的 SSH 私钥内容

5.3 手动部署脚本(GitHub Actions 的简化替代)[必须]

如果暂时不用 GitHub Actions,至少写个部署脚本:

#!/bin/bash
# deploy.sh —— 手动部署脚本,SSH 到服务器后执行
set -e                                       # 任何命令失败立即停止

echo "=== 拉取最新代码 ==="
cd /opt/t2d-api
git pull origin main

echo "=== 安装依赖 ==="
source .venv/bin/activate
pip install -r requirements.txt

echo "=== 运行数据库迁移 ==="
# alembic upgrade head                       # 如果用了 Alembic

echo "=== 重启服务 ==="
sudo systemctl reload t2d-api

echo "=== 检查服务状态 ==="
sudo systemctl status t2d-api --no-pager

echo "=== 部署完成 ==="

六、测试

为什么需要

不写测试的后果: - 改了一个函数,另一个功能悄悄坏了——你不知道 - 上线前手动测一遍要 2 小时——你懒得测,直接上 - 出了 bug,回归测试全靠人工——效率极低

6.1 测试分类

类型测什么数量速度
单元测试单个函数/方法最多最快
集成测试API 接口、数据库操作适量中等
E2E 测试完整用户流程少量最慢

6.2 pytest 基础用法 [必须]

项目结构:

tests/
├── __init__.py
├── conftest.py              # 共享的 fixtures(测试夹具)
├── test_species.py          # 物种相关测试
├── test_samples.py          # 样本相关测试
└── test_auth.py             # 认证相关测试

conftest.py(共享测试配置):

# tests/conftest.py —— pytest 共享夹具,所有测试文件自动可用
import pytest
from fastapi.testclient import TestClient  # FastAPI 自带的测试客户端
from app.main import app                   # 导入 FastAPI 应用实例

@pytest.fixture                            # 装饰器,声明这是一个夹具
def client():
    """创建测试用的 HTTP 客户端"""
    with TestClient(app) as c:             # TestClient 模拟 HTTP 请求
        yield c                            # yield 让测试函数使用这个客户端
    # with 块结束后自动清理资源

单元测试示例:

# tests/test_species.py —— 物种查询相关测试
def test_calculate_diversity_index():
    """测试多样性指数计算函数"""
    # Arrange(准备)
    abundances = [0.5, 0.3, 0.2]           # 三个物种的相对丰度

    # Act(执行)
    from app.services.diversity import shannon_index
    result = shannon_index(abundances)      # 调用被测函数

    # Assert(断言)
    assert round(result, 4) == 1.0297       # 检查结果是否正确

API 接口测试示例(同步方式,适合简单场景):

# tests/test_api.py —— API 接口集成测试(同步 TestClient)
def test_get_species_list(client):
    """测试获取物种列表接口"""
    response = client.get("/api/v1/species")  # 发送 GET 请求
    assert response.status_code == 200        # 状态码应该是 200
    data = response.json()                    # 解析 JSON 响应
    assert "items" in data                    # 响应中应该有 items 字段
    assert isinstance(data["items"], list)    # items 应该是列表

def test_create_sample_without_auth(client):
    """测试未认证时创建样本应该被拒绝"""
    response = client.post(
        "/api/v1/samples",
        json={"name": "test_sample"}          # 提交数据
    )
    assert response.status_code == 401        # 应该返回 401 未认证

def test_upload_invalid_file(client):
    """测试上传无效文件格式应该报错"""
    response = client.post(
        "/api/v1/upload",
        files={"file": ("test.exe", b"fake", "application/octet-stream")}
    )
    assert response.status_code == 422        # 422 验证错误
    assert "不支持的文件格式" in response.json()["detail"]

6.2.1 异步测试(httpx.AsyncClient)[推荐]

如果你的 FastAPI 路由用了 async def,推荐用 httpx.AsyncClient 替代 TestClient,完整走异步链路:

# tests/conftest.py —— 异步测试夹具(2025+ 推荐方式)
import pytest
from httpx import AsyncClient, ASGITransport  # httpx 异步客户端
from app.main import app

@pytest.fixture
async def async_client():
    """创建异步测试客户端"""
    transport = ASGITransport(app=app)        # 直接对接 ASGI 应用,不走网络
    async with AsyncClient(
        transport=transport,
        base_url="http://test"                # 基础 URL(不会真的发网络请求)
    ) as client:
        yield client

# tests/test_api_async.py —— 异步 API 测试
import pytest

@pytest.mark.asyncio                          # 声明这是异步测试
async def test_get_species_async(async_client):
    """异步版本的接口测试"""
    response = await async_client.get("/api/v1/species")
    assert response.status_code == 200
    data = response.json()
    assert "items" in data
# 安装异步测试依赖
pip install pytest-asyncio httpx              # 或 uv add --dev pytest-asyncio httpx

何时用哪个? 同步路由 (def) 用 TestClient 就够了;异步路由 (async def) 用 httpx.AsyncClient 确保完整的异步生命周期。混用 sync/async 客户端容易导致事件循环冲突。

6.3 测试覆盖率 [推荐]

# 安装覆盖率工具
pip install pytest-cov                       # pytest 的覆盖率插件

# 运行测试并查看覆盖率
pytest --cov=app --cov-report=term-missing   # 终端显示,标出未覆盖的行号
# --cov=app             统计 app 目录的代码覆盖率
# --cov-report=term-missing  显示哪些行没被测试覆盖

# 生成 HTML 覆盖率报告(浏览器打开更直观)
pytest --cov=app --cov-report=html           # 生成 htmlcov/ 目录
# 用浏览器打开 htmlcov/index.html 查看

pyproject.toml 中统一配置 pytest:

# pyproject.toml 中的 pytest 配置
[tool.pytest.ini_options]
testpaths = ["tests"]                        # 测试文件所在目录
python_files = ["test_*.py"]                 # 测试文件命名模式
addopts = "-v --cov=app --cov-report=term-missing"  # 默认参数
asyncio_mode = "auto"                        # 自动处理异步测试

[tool.coverage.run]
source = ["app"]                             # 统计哪个目录的覆盖率
omit = ["app/core/config.py"]               # 排除配置文件

[tool.coverage.report]
fail_under = 80                              # 覆盖率低于 80% 视为失败
show_missing = true                          # 显示未覆盖行号

七、日志系统

为什么需要

不写日志的后果: - 线上出 bug,print() 早就被你删了——无法定位问题 - 用户反馈"打不开",你看不到任何错误记录 - 要查上周三的一个异常,日志文件 2GB——打不开

7.1 日志工具对比

工具特点推荐场景
logging(标准库)内置、无依赖、生态最好大型项目、和第三方库兼容
loguru零配置、开箱即用、语法优美小中型项目(本文推荐)
structlog结构化日志、处理器链、最快大型项目、需要日志聚合

7.2 loguru 配置(推荐方案)[必须]

# app/core/logging.py —— 日志配置
import sys
from loguru import logger                    # loguru 的 logger 是全局单例

from app.core.config import settings         # 读取配置

def setup_logging():
    """配置日志系统"""
    # 移除默认处理器
    logger.remove()                          # 清掉 loguru 默认的 stderr 输出

    # 开发环境:彩色输出到控制台(人类可读)
    if settings.debug:
        logger.add(
            sys.stderr,                      # 输出到终端
            level="DEBUG",                   # 显示 DEBUG 及以上级别
            format="<green>{time:HH:mm:ss}</green> | "  # 绿色时间
                   "<level>{level: <8}</level> | "       # 彩色级别
                   "<cyan>{name}:{function}:{line}</cyan> | "  # 来源
                   "{message}",              # 消息内容
            colorize=True,                   # 启用彩色
        )
    else:
        # 生产环境:JSON 格式输出到文件(机器可读,方便日志聚合工具解析)
        logger.add(
            "logs/app.log",                  # 日志文件路径
            level="INFO",                    # 生产环境只记录 INFO 及以上
            format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{line} | {message}",
            rotation="100 MB",              # 单个文件超过 100MB 自动轮转
            retention="30 days",            # 保留最近 30 天的日志
            compression="gz",               # 旧日志自动压缩为 .gz
            serialize=True,                 # 输出 JSON 格式(结构化日志)
            encoding="utf-8",               # 编码
        )

        # 错误日志单独存一份(方便快速定位严重问题)
        logger.add(
            "logs/error.log",
            level="ERROR",                   # 只记录 ERROR 和 CRITICAL
            rotation="50 MB",
            retention="90 days",             # 错误日志保留更久
            compression="gz",
        )

在 FastAPI 中使用:

# app/main.py
from loguru import logger
from app.core.logging import setup_logging

setup_logging()                              # 启动时初始化日志

@app.get("/api/v1/species/{species_id}")
async def get_species(species_id: int):
    logger.info(f"查询物种 ID: {species_id}")  # 记录正常操作
    try:
        result = await species_service.get(species_id)
        return result
    except Exception as e:
        logger.error(f"查询物种失败: {e}")     # 记录错误
        raise

7.3 日志级别说明

级别用途示例
DEBUG开发调试细节SQL 查询: SELECT * FROM species WHERE id=1
INFO正常业务操作用户 admin 登录成功
WARNING值得注意但不影响运行API 调用频率接近限制: 95/100
ERROR出错但系统仍运行上传文件解析失败: invalid FASTA format
CRITICAL系统级严重错误数据库连接断开

7.4 请求级日志追踪(中间件模式)[推荐]

生产环境最关键的需求:每条日志都能追溯到是哪个请求产生的

# app/middleware/logging.py —— 请求日志中间件
import uuid
from loguru import logger
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request

class RequestLoggingMiddleware(BaseHTTPMiddleware):
    """给每个请求分配唯一 ID,自动记录请求/响应日志"""
    async def dispatch(self, request: Request, call_next):
        # 生成唯一请求 ID
        request_id = str(uuid.uuid4())[:8]    # 取前 8 位够用了

        # 绑定上下文(loguru 的 contextualize 会自动附加到后续所有日志)
        with logger.contextualize(request_id=request_id):
            logger.info(
                f"{request.method} {request.url.path}",
                extra={"client_ip": request.client.host}
            )
            response = await call_next(request)
            logger.info(
                f"响应 {response.status_code}",
                extra={"status_code": response.status_code}
            )

        # 把 request_id 也返回给客户端(方便排查问题)
        response.headers["X-Request-ID"] = request_id
        return response

为什么重要? 用户报告 "500 错误",你只要拿到 X-Request-ID 就能在日志里精准定位,不用在几千行日志里大海捞针。structlog 和 loguru 都支持这种模式。

7.5 systemd + logrotate(系统级日志管理)[进阶]

如果不用 loguru 的内置轮转,可以用 Linux 系统的 logrotate:

# /etc/logrotate.d/t2d-api —— 系统级日志轮转配置
/opt/t2d-api/logs/*.log {
    daily                                    # 每天轮转一次
    rotate 30                                # 保留 30 份
    compress                                 # 压缩旧日志
    delaycompress                            # 延迟一天再压缩(方便查看昨天的)
    missingok                                # 日志文件不存在也不报错
    notifempty                               # 空文件不轮转
    create 0640 www-data www-data            # 新文件的权限和所有者
    postrotate
        systemctl reload t2d-api             # 轮转后通知应用重新打开文件
    endscript
}

八、数据库备份

为什么需要

不备份的后果:一行误操作的 SQL(DELETE FROM users——忘了 WHERE),所有用户数据永久丢失。

8.1 SQLite 备份策略 [必须]

#!/bin/bash
# /opt/t2d-api/scripts/backup_db.sh —— SQLite 数据库备份脚本

# === 配置 ===
DB_PATH="/opt/t2d-api/data.db"               # 数据库文件路径
BACKUP_DIR="/opt/t2d-api/backups"            # 本地备份目录
DATE=$(date +%Y%m%d_%H%M%S)                 # 时间戳(如 20260504_143000)
BACKUP_FILE="$BACKUP_DIR/data_${DATE}.db"    # 备份文件名

# === 创建备份目录 ===
mkdir -p "$BACKUP_DIR"                       # -p: 目录已存在也不报错

# === 使用 SQLite 的 .backup 命令(不锁表、一致性有保障) ===
sqlite3 "$DB_PATH" ".backup '$BACKUP_FILE'"  # 官方推荐的备份方式
# 不要用 cp 命令!正在写入时 cp 可能拿到损坏的文件

# === 压缩 ===
gzip "$BACKUP_FILE"                          # 压缩,节省空间
echo "[$(date)] 备份成功: ${BACKUP_FILE}.gz"

# === 删除 7 天前的旧备份 ===
find "$BACKUP_DIR" -name "*.gz" -mtime +7 -delete  # 只保留 7 天

8.2 cron 定时任务 [必须]

# 编辑定时任务
crontab -e                                   # 打开当前用户的 cron 编辑器

# 添加以下行(每天凌晨 3 点执行备份)
0 3 * * * /opt/t2d-api/scripts/backup_db.sh >> /opt/t2d-api/logs/backup.log 2>&1
# 分 时 日 月 周 命令
# 0  3  *  *  *  = 每天 03:00
# >> 追加输出到日志文件
# 2>&1 把错误也输出到同一个文件

# 验证 cron 是否生效
crontab -l                                   # 列出所有定时任务

8.3 备份到远程(异地容灾)[推荐]

# 方案 1:rsync 同步到远程服务器
rsync -avz /opt/t2d-api/backups/ user@backup-server:/backups/t2d-api/
# -a 归档模式  -v 详细输出  -z 压缩传输

# 方案 2:上传到对象存储(阿里云 OSS / 腾讯 COS)
# 安装阿里云 CLI
pip install oss2
# 在备份脚本最后添加上传命令:
# python /opt/t2d-api/scripts/upload_to_oss.py "$BACKUP_FILE.gz"

# 方案 3:rclone(通用云存储同步工具,支持几十种云服务)
rclone copy /opt/t2d-api/backups/ remote:t2d-backups/ --max-age 7d

九、进程管理与监控

为什么需要

不做进程管理的后果: - 服务器重启后你的 FastAPI 不会自动启动 - 进程崩溃了没人知道,直到用户找你 - 内存泄漏慢慢把服务器搞崩

9.1 systemd service 配置 [必须]

# /etc/systemd/system/t2d-api.service —— systemd 服务单元文件
[Unit]
Description=T2D API Service                  # 服务描述
After=network.target                         # 等网络就绪后再启动

[Service]
Type=notify                                  # Gunicorn 支持 systemd 通知
User=www-data                                # 运行用户(不要用 root!)
Group=www-data                               # 运行用户组
WorkingDirectory=/opt/t2d-api                # 工作目录

# 用 Gunicorn 启动 FastAPI(UvicornWorker 处理异步请求)
ExecStart=/opt/t2d-api/.venv/bin/gunicorn \
    app.main:app \
    --worker-class uvicorn.workers.UvicornWorker \
    --workers 3 \
    --bind unix:/opt/t2d-api/gunicorn.sock \
    --access-logfile - \
    --error-logfile - \
    --max-requests 1000 \
    --max-requests-jitter 50 \
    --graceful-timeout 30
# --worker-class     使用 Uvicorn 异步 worker
# --workers 3        启动 3 个 worker 进程(2*CPU+1)
# --bind unix:...    用 Unix Socket 通信(比 TCP 快)
# --max-requests     处理 1000 个请求后重启 worker(防内存泄漏)
# --max-requests-jitter  随机偏移,防止所有 worker 同时重启
# --graceful-timeout     优雅关闭等待时间

# 优雅重载(发送 HUP 信号,不断连接)
ExecReload=/bin/kill -s HUP $MAINPID

# 自动重启配置
Restart=on-failure                           # 异常退出时自动重启
RestartSec=10                                # 重启前等 10 秒
StartLimitBurst=5                            # 最多连续重启 5 次
StartLimitIntervalSec=60                     # 60 秒内

# 日志
StandardOutput=journal                       # 标准输出写入 systemd 日志
StandardError=journal                        # 错误输出也写入

# 安全加固
NoNewPrivileges=true                         # 禁止获取新特权
PrivateTmp=true                              # 使用独立的 /tmp

[Install]
WantedBy=multi-user.target                   # 开机自动启动
# 加载并启动服务
sudo systemctl daemon-reload                 # 重新加载 service 文件
sudo systemctl enable t2d-api                # 设为开机自启
sudo systemctl start t2d-api                 # 启动服务

# 常用管理命令
sudo systemctl status t2d-api                # 查看状态
sudo systemctl reload t2d-api                # 优雅重载(不断线)
sudo systemctl restart t2d-api               # 完全重启
sudo journalctl -u t2d-api -f               # 实时查看日志(-f = follow)
sudo journalctl -u t2d-api --since today     # 查看今天的日志

9.2 健康检查端点 [必须]

# app/api/health.py —— 健康检查接口
from fastapi import APIRouter
from datetime import datetime

router = APIRouter(tags=["健康检查"])

@router.get("/health")
async def health_check():
    """基础存活检查 —— 进程还活着吗"""
    return {"status": "ok", "timestamp": datetime.now().isoformat()}

@router.get("/health/ready")
async def readiness_check():
    """就绪检查 —— 能正常处理请求吗(检查数据库等依赖)"""
    checks = {}
    try:
        # 检查数据库连接
        from app.core.database import engine
        async with engine.connect() as conn:
            await conn.execute("SELECT 1")   # 最简单的数据库探活
        checks["database"] = "ok"
    except Exception as e:
        checks["database"] = f"error: {e}"
        return {"status": "unhealthy", "checks": checks}

    return {"status": "healthy", "checks": checks}

9.3 外部监控 [推荐]

工具费用功能
UptimeRobot免费 50 个监控每 5 分钟检查一次,宕机发邮件/微信
Better Uptime免费版可用更好看的状态页
自建 cron 检查免费curl 你的 /health 端点

在 UptimeRobot 中添加: - Monitor Type: HTTP(s) - URL: https://your-domain.com/health - Monitoring Interval: 5 minutes - Alert Contacts: 你的邮箱/Telegram


十、HTTPS 与域名

为什么需要

不用 HTTPS 的后果: - 用户密码明文传输,同一个 WiFi 的人都能截获 - 浏览器显示"不安全"大红警告——用户直接跑了 - 搜索引擎会降低你网站的排名

10.1 Nginx 反向代理配置 [必须]

# /etc/nginx/sites-available/t2d-api —— Nginx 配置
# 第一步:先配 HTTP(让 Certbot 能验证域名)

server {
    listen 80;                               # 监听 80 端口
    server_name your-domain.com;             # 你的域名

    # Certbot 域名验证用的路径
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;               # Certbot 验证文件存放目录
    }

    # 其他所有请求转发给 FastAPI
    location / {
        proxy_pass http://unix:/opt/t2d-api/gunicorn.sock;  # Unix Socket
        proxy_set_header Host $http_host;                    # 传递原始域名
        proxy_set_header X-Real-IP $remote_addr;             # 真实客户端 IP
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;          # http 或 https
        proxy_read_timeout 300s;                             # 长任务超时时间
    }
}
# 启用站点配置
sudo ln -s /etc/nginx/sites-available/t2d-api /etc/nginx/sites-enabled/
sudo nginx -t                                # 测试配置语法是否正确
sudo systemctl reload nginx                  # 重新加载配置

10.2 Let's Encrypt (Certbot) 配置 HTTPS [必须]

# 安装 Certbot
sudo apt update
sudo snap install --classic certbot          # 用 snap 安装(官方推荐)
sudo ln -s /snap/bin/certbot /usr/bin/certbot  # 创建快捷命令

# 申请证书(Certbot 会自动修改 Nginx 配置)
sudo certbot --nginx -d your-domain.com      # 自动配置 HTTPS
# Certbot 会:
# 1. 验证你对域名的控制权
# 2. 下载证书到 /etc/letsencrypt/live/your-domain.com/
# 3. 自动修改 Nginx 配置,添加 SSL 相关指令
# 4. 添加 HTTP→HTTPS 自动跳转

# 验证自动续期
sudo certbot renew --dry-run                 # 模拟续期(不真正执行)
# Let's Encrypt 证书有效期 90 天,Certbot 自动续期

Certbot 自动修改后的 Nginx 配置大致是:

# Certbot 自动添加的 HTTPS 配置(不需要手写)
server {
    listen 443 ssl;
    server_name your-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://unix:/opt/t2d-api/gunicorn.sock;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# HTTP 自动跳转到 HTTPS
server {
    listen 80;
    server_name your-domain.com;
    return 301 https://$host$request_uri;    # 永久重定向到 HTTPS
}

10.3 Cloudflare 配置(国内加速 + 防护)[推荐]

如果域名放在 Cloudflare: 1. 注册 Cloudflare → 添加你的域名 2. 修改域名 DNS 服务器为 Cloudflare 提供的 3. SSL/TLS 模式选 Full (Strict)(后端有证书时最安全) 4. 开启 "Always Use HTTPS" 5. 好处:CDN 加速、DDoS 防护、免费 SSL


十一、安全加固

为什么需要

不做安全加固的后果: - 服务器被黑客入侵当矿机 - 数据库被拖库,用户数据泄露 - SSH 被暴力破解(每天数千次尝试)

11.1 防火墙(UFW)[必须]

# 安装并配置 UFW(Uncomplicated Firewall)
sudo apt install ufw                         # Ubuntu 通常已预装

# 设置默认策略
sudo ufw default deny incoming               # 默认拒绝所有入站连接
sudo ufw default allow outgoing              # 允许所有出站连接

# 放行必要端口
sudo ufw allow 22/tcp                        # SSH(如果改了端口就改这里)
sudo ufw allow 80/tcp                        # HTTP
sudo ufw allow 443/tcp                       # HTTPS

# 启用防火墙
sudo ufw enable                              # 启用(会提示可能断开 SSH,确认即可)
sudo ufw status verbose                      # 查看规则

# 【更安全】限制 SSH 只允许特定 IP
sudo ufw delete allow 22/tcp                 # 先删掉通用规则
sudo ufw allow from 你的固定IP to any port 22  # 只允许你的 IP 连 SSH

11.2 SSH 安全加固 [必须]

# 编辑 SSH 配置
sudo nano /etc/ssh/sshd_config

# 以下是建议修改的配置项:
Port 2222                                    # 改掉默认 22 端口(减少扫描噪音)
PermitRootLogin no                           # 禁止 root 直接登录
PasswordAuthentication no                    # 禁用密码登录(只允许密钥)
PubkeyAuthentication yes                     # 启用公钥认证
MaxAuthTries 3                               # 最多尝试 3 次
ClientAliveInterval 300                      # 5 分钟无操作断开
ClientAliveCountMax 2                        # 发 2 次心跳包无响应断开

# 重启 SSH 服务(先开另一个终端测试能否登录!)
sudo systemctl restart sshd

重要提醒:修改 SSH 配置前,一定要保持一个已连接的终端窗口。如果配置出错导致无法登录,你还能用这个窗口改回来。

11.3 Fail2Ban [推荐]

# 安装
sudo apt install fail2ban                    # 安装 fail2ban

# 创建本地配置(不要直接改 jail.conf)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

# 编辑 jail.local,找到 [sshd] 段:
sudo nano /etc/fail2ban/jail.local
# /etc/fail2ban/jail.local 关键配置
[DEFAULT]
bantime = 86400                              # 封禁 24 小时(秒)
findtime = 600                               # 10 分钟内
maxretry = 5                                 # 失败 5 次就封

[sshd]
enabled = true                               # 启用 SSH 防护
port = 2222                                  # 你的 SSH 端口(如果改了的话)
banaction = ufw                              # 用 UFW 执行封禁(和防火墙联动)
logpath = /var/log/auth.log                  # SSH 日志路径

# 可选:保护 Nginx(防止恶意请求)
[nginx-http-auth]
enabled = true
# 启动并查看状态
sudo systemctl enable fail2ban               # 开机自启
sudo systemctl start fail2ban
sudo fail2ban-client status sshd             # 查看 SSH 防护状态
# 会显示被封的 IP 数量和列表

11.4 安全响应头 [推荐]

在 FastAPI 中添加安全头中间件:

# app/middleware/security.py —— 安全响应头中间件
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response

class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    """给每个响应添加安全头"""
    async def dispatch(self, request: Request, call_next):
        response: Response = await call_next(request)

        # 防止 MIME 类型嗅探攻击
        response.headers["X-Content-Type-Options"] = "nosniff"
        # 防止网页被嵌入 iframe(点击劫持防护)
        response.headers["X-Frame-Options"] = "DENY"
        # 控制 Referer 头泄露信息
        response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
        # 强制 HTTPS(开启后浏览器会记住,1年内都走 HTTPS)
        response.headers["Strict-Transport-Security"] = (
            "max-age=31536000; includeSubDomains"
        )
        # 禁用危险的浏览器功能
        response.headers["Permissions-Policy"] = (
            "camera=(), microphone=(), geolocation=()"
        )

        return response
# app/main.py 中注册中间件
from app.middleware.security import SecurityHeadersMiddleware
app.add_middleware(SecurityHeadersMiddleware)

11.5 自动安全更新 [推荐]

# 启用自动安全更新
sudo apt install unattended-upgrades          # 安装自动更新工具
sudo dpkg-reconfigure unattended-upgrades     # 交互式配置,选 Yes

# 检查是否开启
cat /etc/apt/apt.conf.d/20auto-upgrades
# 应该显示:
# APT::Periodic::Unattended-Upgrade "1";

十二、文档

为什么需要

不写文档的后果: - 三个月后你自己都忘了这个接口怎么用 - 新同事接手该项目,看不懂就从头写 - API 没有文档,前端同事天天找你问参数格式

12.1 README 规范 [必须]

一个好的 README 至少包含:

# T2D-API:2型糖尿病肠道菌群分析平台

> 基于 FastAPI 的宏基因组分析数据服务化平台

## 功能特性
- 物种丰度查询与可视化
- 样本上传与自动分析
- 多样性指数计算

## 快速开始

### 环境要求
- Python 3.12+
- SQLite 3.x

### 安装与运行
```bash
git clone git@github.com:xxx/t2d-api.git
cd t2d-api
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env  # 编辑 .env 填入配置
uvicorn app.main:app --reload

API 文档

启动后访问:http://localhost:8000/docs

项目结构

app/
├── main.py          # 应用入口
├── api/             # 路由
├── services/        # 业务逻辑
├── models/          # 数据模型
├── schemas/         # 请求/响应模型
└── core/            # 配置、数据库

部署

详见 部署文档

许可证

MIT License

### 12.2 API 文档(FastAPI 自带)[必须]

FastAPI 的杀手级特性:**自动生成交互式 API 文档**。

```python
# 只要在代码里写好类型标注和描述,文档自动生成
from fastapi import FastAPI, Query

app = FastAPI(
    title="T2D-API",                         # 文档页标题
    description="2型糖尿病肠道菌群分析平台",   # 描述
    version="1.0.0",                          # 版本号
)

@app.get(
    "/api/v1/species",
    summary="查询物种列表",                     # 接口概要
    description="支持按名称模糊搜索和分页",      # 详细描述
)
async def list_species(
    keyword: str = Query(                     # Query 参数
        default=None,
        description="物种名称关键词(支持模糊搜索)",
        examples=["Bacteroides"],             # 示例值
    ),
    page: int = Query(default=1, ge=1, description="页码"),
    size: int = Query(default=20, ge=1, le=100, description="每页数量"),
):
    ...

访问两个自动生成的文档页面: - Swagger UIhttp://localhost:8000/docs(交互式,能直接测试接口) - ReDochttp://localhost:8000/redoc(更好看的只读文档)

12.3 CHANGELOG [推荐]

# 用 commitizen 自动从提交记录生成 CHANGELOG
cz changelog                                 # 自动生成 CHANGELOG.md
# 原理:根据 feat/fix/refactor 等类型的提交信息,自动分类整理

生成的 CHANGELOG.md 大致长这样:

## 1.1.0 (2026-05-04)

### Features
- 新增物种丰度查询接口 (#12)
- 支持批量样本上传 (#15)

### Bug Fixes
- 修复大文件上传超时问题 (#13)
- 修复分页查询总数不准的问题 (#14)

十三、依赖管理

为什么需要

不管理依赖的后果: - "我电脑上能跑,服务器上跑不了"——版本不一致 - 某个依赖有安全漏洞——你完全不知道 - pip install xxx 装了最新版但不兼容——整个项目跑崩

13.1 requirements.txt vs pyproject.toml 对比

对比项requirements.txtpyproject.toml
格式纯文本,一行一个包TOML 结构化
标准化非标准PEP 621 标准
工具配置不支持可以集中放 pytest/ruff 等配置
适合简单项目、部署正式项目(2025 推荐)

13.2 pyproject.toml 完整示例 [推荐]

# pyproject.toml —— 项目的中央配置文件(一个文件管所有)

[build-system]
requires = ["hatchling"]                     # 构建后端
build-backend = "hatchling.build"            # 指定构建工具

[project]
name = "t2d-api"                             # 项目名
version = "1.0.0"                            # 版本号
description = "2型糖尿病肠道菌群分析平台"      # 描述
requires-python = ">=3.12"                   # Python 版本要求
license = {text = "MIT"}                     # 许可证
authors = [
    {name = "你的名字", email = "your@email.com"},
]

# === 生产依赖 ===
dependencies = [
    "fastapi>=0.115",                        # Web 框架
    "uvicorn[standard]>=0.32",               # ASGI 服务器
    "gunicorn>=23.0",                        # 进程管理器
    "pydantic-settings>=2.6",                # 配置管理
    "sqlalchemy>=2.0",                       # ORM
    "aiosqlite>=0.20",                       # SQLite 异步驱动
    "loguru>=0.7",                           # 日志
    "python-multipart>=0.0.12",              # 文件上传支持
]

# === 开发/测试依赖(两种写法任选其一) ===

# 写法 1(传统,兼容性好):[project.optional-dependencies]
# 安装方式:pip install -e ".[dev]"
[project.optional-dependencies]
dev = [
    "pytest>=8.0",                           # 测试框架
    "pytest-cov>=6.0",                       # 测试覆盖率
    "pytest-asyncio>=0.24",                  # 异步测试支持
    "httpx>=0.27",                           # 异步 HTTP 客户端(测试用)
    "ruff>=0.8",                             # 代码检查+格式化
    "pre-commit>=4.0",                       # Git 钩子
    "commitizen>=4.0",                       # 提交规范
    "pip-audit>=2.7",                        # 依赖漏洞扫描
]

# 写法 2(2025+ 新标准,PEP 735):[dependency-groups]
# uv 和 pip 24.3+ 原生支持,安装方式:uv add --dev pytest
# [dependency-groups]
# dev = [
#     "pytest>=8.0",
#     "pytest-cov>=6.0",
#     "pytest-asyncio>=0.24",
#     "httpx>=0.27",
#     "ruff>=0.8",
#     "pre-commit>=4.0",
#     "commitizen>=4.0",
#     "pip-audit>=2.7",
# ]
# 区别:[dependency-groups] 是专门给开发工具用的,不会被打包发布
# 如果用 uv,它会自动写入 [dependency-groups] 而非 [project.optional-dependencies]

# === Ruff 配置(一个工具替代 Black + Flake8 + isort) ===
[tool.ruff]
target-version = "py312"                     # 目标 Python 版本
line-length = 120                            # 行宽限制

[tool.ruff.lint]
select = [
    "E",                                     # pycodestyle errors
    "W",                                     # pycodestyle warnings
    "F",                                     # pyflakes
    "I",                                     # isort(import 排序)
    "B",                                     # flake8-bugbear
    "S",                                     # flake8-bandit(安全检查)
    "UP",                                    # pyupgrade(语法升级建议)
]

# === pytest 配置 ===
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --cov=app --cov-report=term-missing"
asyncio_mode = "auto"

# === 覆盖率配置 ===
[tool.coverage.report]
fail_under = 80
show_missing = true

# === commitizen 配置 ===
[tool.commitizen]
name = "cz_conventional_commits"             # 使用 conventional commits 规范
version = "1.0.0"                            # 和 [project] 中的 version 保持一致
tag_format = "v$version"                     # 标签格式:v1.0.0

13.3 依赖锁定 [推荐]

# === 方案 1(推荐):uv lock ===
# uv 项目模式自动维护 uv.lock 锁文件
uv lock                                      # 手动生成/更新 uv.lock
uv sync                                      # 从 uv.lock 精确安装(保证一致性)
uv run pytest                                # uv run 每次执行前自动检查并同步
# uv.lock 是跨平台的,包含每个平台的精确依赖树
# 【重要】uv.lock 必须提交到 Git,它才是真正的"锁定"

# 如果生产环境不用 uv,可以导出传统 requirements.txt:
uv export --format requirements-txt > requirements.txt  # 从 uv.lock 导出

# === 方案 2:pip-compile(pip-tools 提供,传统方案) ===
pip install pip-tools                        # 安装 pip-tools
pip-compile pyproject.toml -o requirements.txt  # 从 pyproject.toml 生成锁定文件
# 锁定文件包含精确版本号,确保不同环境安装完全一致的依赖

# 从锁定文件安装(确保版本一致)
pip install -r requirements.txt              # 精确安装锁定版本

为什么要锁定? pyproject.toml 写的是 fastapi>=0.115(范围),锁文件写的是 fastapi==0.115.6(精确版本)。没有锁文件,两个人 pip install 可能装出不同版本,导致"我电脑上能跑"的经典问题。

13.4 安全漏洞扫描 [推荐]

# pip-audit:扫描已安装的包是否有已知漏洞
pip install pip-audit                        # 安装
pip-audit                                    # 扫描当前环境
# 输出示例:
# Found 1 known vulnerability in 1 package
# Name    Version  ID               Fix Versions
# ------- -------- ---------------  ------------
# pillow  9.5.0    PYSEC-2023-175   10.0.1

# 自动修复(升级有漏洞的包)
pip-audit --fix                              # 自动升级到修复版本

# 在 CI 中使用
pip-audit -r requirements.txt                # 扫描 requirements.txt 中的依赖

在 GitHub Actions 中集成漏洞扫描:

# 在 CI 的 test job 中添加一步
      - name: Audit dependencies              # 依赖安全扫描
        run: pip-audit -r requirements.txt

13.5 工具对比总结

工具用途是否免费推荐度
pip-audit依赖漏洞扫描免费开源首选
Safety依赖漏洞扫描免费版有限制备选(付费版数据更全)
Ruff代码检查+格式化免费开源必装(替代 Black+Flake8+isort)
uv包管理器免费开源推荐(替代 pip,快 10-100 倍)
pip-tools依赖锁定免费开源稳妥之选

快速参考:13 项工程化规范优先级

#规范必须推荐进阶
1Git 初始化 + .gitignorex
1Conventional Commitsx
1Tag 版本标记x
2分支策略(GitHub Flow)x
2保护主分支x
3SSH Key + 远程仓库x
3双平台推送x
4.env 环境分离x
4虚拟环境x
5CI/CD(GitHub Actions)x
5部署脚本x
6单元测试 + pytestx
6测试覆盖率 80%x
7日志系统(loguru)x
7日志轮转x
8数据库备份脚本x
8cron 定时备份x
8远程备份x
9systemd 服务x
9健康检查端点x
9外部监控x
10Nginx 反向代理x
10HTTPS (Certbot)x
10Cloudflarex
11UFW 防火墙x
11SSH 加固x
11Fail2Banx
11安全响应头x
12READMEx
12API 文档(自动生成)x
12CHANGELOGx
13pyproject.tomlx
13依赖锁定x
13漏洞扫描x

推荐的实施顺序

如果你是从零开始,按这个顺序来:

第 1 天:Git 初始化 + .gitignore + 远程仓库 + SSH Key
第 2 天:虚拟环境 + pyproject.toml + .env 环境分离
第 3 天:写好 README + FastAPI 自带文档配置好
第 4 天:日志系统配置(loguru)
第 5 天:pytest 测试 + 覆盖率
第 6 天:pre-commit + Ruff + commitizen
第 7 天:服务器部署:systemd + Nginx + HTTPS
第 8 天:安全加固:UFW + SSH + Fail2Ban
第 9 天:数据库备份 + cron 定时任务
第 10 天:CI/CD (GitHub Actions)
第 11 天:健康检查 + 外部监控
第 12 天:CHANGELOG + 漏洞扫描 + 查漏补缺

参考资源

FastAPI 项目结构与最佳实践: - FastAPI Best Practices (zhanymkanov) -- FastAPI 最佳实践汇总 - Production-Ready FastAPI Project Structure (2026 Guide) -- 生产级项目结构 - FastAPI Best Practices for Production (2026) -- 生产部署最佳实践 - FastAPI Setup Guide for 2025 -- FastAPI 环境搭建

Python 工具链与项目管理: - Python Project Setup 2026: uv + Ruff + Ty + Polars (KDnuggets) -- 2026 年 Python 工具链 - Managing Python Projects With uv (Real Python) -- uv 完整教程 - Python Packaging: pyproject.toml Guide -- 官方 pyproject.toml 指南 - Working on projects with uv -- uv 项目管理文档

部署与运维: - Deploy FastAPI on Ubuntu 24.04 (Gunicorn + Nginx + Certbot) -- Ubuntu 部署全流程 - Deploy FastAPI Like a Pro: GitHub Actions, GitLab CI -- CI/CD + 零停机部署 - Enhancing GitHub Actions CI for FastAPI (PyImageSearch) -- CI 进阶 - FastAPI Deployment Guide (RamNode) -- 服务器部署指南

日志系统: - Choosing a Python Logging Library in 2026 (Dash0) -- Python 日志库选型 - Python Logging with Loguru (Dash0) -- Loguru 生产配置 - Python Logging with Structlog (Dash0) -- Structlog 结构化日志 - Logging in Python: Top 6 Libraries (Better Stack) -- 日志库对比

Git 与分支策略: - Choosing the Right Git Strategy in 2025 -- Git 分支策略对比 - Agile Git Branching Strategies in 2026 (Java Code Geeks) -- 2026 分支策略趋势 - Effortless Code Quality: Pre-Commit Hooks Guide 2025 -- pre-commit 最佳实践

测试: - Testing FastAPI Applications with Pytest -- FastAPI 测试实战 - FastAPI Testing Strategies (greeden.me) -- 测试策略全景

安全: - Linux Server Hardening: SSH + Fail2Ban + UFW -- 服务器安全加固 - Harden Your Linux Server: Fail2Ban and UFW (VPS.DO) -- Fail2Ban 配置 - VPS Security Hardening Checklist 2025 (VPS.DO) -- 安全加固清单 - Hardening SSH 2025 Best Settings -- SSH 加固详解 - pip-audit (PyPI) -- 依赖漏洞扫描