从零搭建 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 对比¶
| 对比项 | GitHub | Gitee(码云) |
|---|---|---|
| 国内访问速度 | 偶尔慢/被墙 | 快 |
| 国际影响力 | 全球最大 | 国内为主 |
| 私有仓库 | 免费无限 | 免费有限制 |
| CI/CD | GitHub 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 虚拟环境工具对比¶
| 工具 | 适合场景 | 优点 | 缺点 |
|---|---|---|---|
| uv | Python 项目(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_USER | SSH 登录用户名(不要用 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
何时用哪个? 同步路由 (
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 UI:http://localhost:8000/docs(交互式,能直接测试接口) - ReDoc:http://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.txt | pyproject.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 中集成漏洞扫描:
13.5 工具对比总结¶
| 工具 | 用途 | 是否免费 | 推荐度 |
|---|---|---|---|
| pip-audit | 依赖漏洞扫描 | 免费开源 | 首选 |
| Safety | 依赖漏洞扫描 | 免费版有限制 | 备选(付费版数据更全) |
| Ruff | 代码检查+格式化 | 免费开源 | 必装(替代 Black+Flake8+isort) |
| uv | 包管理器 | 免费开源 | 推荐(替代 pip,快 10-100 倍) |
| pip-tools | 依赖锁定 | 免费开源 | 稳妥之选 |
快速参考:13 项工程化规范优先级¶
| # | 规范 | 必须 | 推荐 | 进阶 |
|---|---|---|---|---|
| 1 | Git 初始化 + .gitignore | x | ||
| 1 | Conventional Commits | x | ||
| 1 | Tag 版本标记 | x | ||
| 2 | 分支策略(GitHub Flow) | x | ||
| 2 | 保护主分支 | x | ||
| 3 | SSH Key + 远程仓库 | x | ||
| 3 | 双平台推送 | x | ||
| 4 | .env 环境分离 | x | ||
| 4 | 虚拟环境 | x | ||
| 5 | CI/CD(GitHub Actions) | x | ||
| 5 | 部署脚本 | x | ||
| 6 | 单元测试 + pytest | x | ||
| 6 | 测试覆盖率 80% | x | ||
| 7 | 日志系统(loguru) | x | ||
| 7 | 日志轮转 | x | ||
| 8 | 数据库备份脚本 | x | ||
| 8 | cron 定时备份 | x | ||
| 8 | 远程备份 | x | ||
| 9 | systemd 服务 | x | ||
| 9 | 健康检查端点 | x | ||
| 9 | 外部监控 | x | ||
| 10 | Nginx 反向代理 | x | ||
| 10 | HTTPS (Certbot) | x | ||
| 10 | Cloudflare | x | ||
| 11 | UFW 防火墙 | x | ||
| 11 | SSH 加固 | x | ||
| 11 | Fail2Ban | x | ||
| 11 | 安全响应头 | x | ||
| 12 | README | x | ||
| 12 | API 文档(自动生成) | x | ||
| 12 | CHANGELOG | x | ||
| 13 | pyproject.toml | x | ||
| 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) -- 依赖漏洞扫描