FastAPI 标准化目录骨架
做这个目录骨架,其实是给“过去的自己”收个尾。
以前每开一个 FastAPI 项目,都要重新纠结一遍:GeoTIFF 和 GPKG 究竟丢哪儿、
SQLite 主库要不要跟代码放一起、前端工程要不要塞进 assets/里凑合用。
项目一多,坑也踩得够多,换服务器不好迁、挂卷不好配、调试和生产混在一起更是灾难。 所以干脆把这些年折腾下来的经验,整理成一套可以反复复用的目录约定: 哪些是只读数据、哪些是运行期写入、哪些是第三方依赖,前后端怎样互不打架。
希望这篇“小小的标准化骨架”,能帮你少掉几次重构,哪怕只是打开一个老项目时,不再一脸问号地对着一堆散乱的文件夹。
目录分层
角色分三类:只读数据、运行期读写、第三方依赖。再加一个前端工程的独立空间。
.
├─ api/ # FastAPI 后端源码
├─ web/ # Angular 工作区(CLI 或 Nx)
├─ data/ # ✅ 只读、可版本化的数据(GeoTIFF/GPKG/MBTiles)
│ ├─ raw/ # 原始不可改
│ ├─ reference/ # 基准:行政区划、底图
│ ├─ processed/ # 预处理后(重投影/重采样/COG)
│ └─ samples/ # 轻量样例/单测数据
├─ var/ # ✅ 运行期可写(挂卷)
│ ├─ db/ # SQLite 库与 -wal/-shm
│ ├─ cache/ # GDAL/应用缓存
│ ├─ tmp/ # 任务中间产物
│ └─ exports/ # 导出成果
├─ vendor/ # ✅ 第三方二进制/扩展(只读)
│ └─ sqlite-extensions/
│ ├─ linux-x86_64/…
│ ├─ linux-aarch64/…
│ └─ darwin-arm64/…
├─ public/ # 小体积静态文件(如前端构建产物的托管目录,选用)
└─ infra/ # 部署脚本(docker/k8s/terraform)
为什么这样放?
data/:版本可控、便于校验;容器里挂只读卷,避免误写污染底库。var/:运行期所有写入统一收口,方便迁移、备份、清理;容器里挂可写卷。vendor/:第三方二进制跟业务代码分开,利于审计与升级回滚。web/:前端是完整工程,不和assets//public/混用。
不要把大体积 GeoTIFF/GPKG 放 assets/ 或 public/;那两处用于小静态资源或前端编译产物托管。
全国级 GPKG 命名规范
文件名模式:
{region}_{theme}_{geom}_{lod}_{crs}_{source}_{date}_v{ver}.gpkg
region:chn(全国)或带 adcode:chn_44、chn_4403theme:roads|railways|transmission|hydro|heritage…(小写蛇形,可细分)geom:pt|ln|pglod:1k|10k|50k|100k|250k|1m或z8|z10|z12|full(二选一体系,别混)crs:如epsg4490(CGCS2000)或epsg4326source:osm|mwr|miit|sgcc|ncha|org…date:YYYYMMDD或YYYYQnv{ver}:v1/v1.1
示例:
chn_roads_ln_100k_epsg4490_osm_20251001_v1.gpkg
chn_transmission_ln_full_epsg4490_sgcc_20250115_v1.gpkg
chn_hydro_pg_250k_epsg4490_mwr_202410_v1.gpkg
图层命名(GPKG 内部):
roads_motorway_z10, roads_trunk_z12
railways_mainline, railways_hsr
transmission_220kv, transmission_hvdc
hydro_river_250k, hydro_lake
heritage_site_full, heritage_buffer_1km
可选:侧车 JSON(同名 .json)记录来源、处理流程、字段字典、SHA256 与更新日志,便于自动校验与同步。
SQLite:扩展放哪儿、怎么加载、有哪些坑
放置规则
第三方扩展属于“可执行依赖”,放 vendor/sqlite-extensions/{os-arch}/,只读管理。示例:
vendor/sqlite-extensions/
linux-aarch64/libsimple.so
linux-x86_64/libsimple.so
darwin-arm64/libsimple.dylib
加载示例(按平台自动挑库):
import os, sys, platform, sqlite3
from pathlib import Path
BASE = Path(__file__).resolve().parents[1]
EXT_DIR = Path(os.getenv("SQLITE_EXT_DIR", BASE / "vendor" / "sqlite-extensions"))
def platform_key():
sysname = sys.platform
arch = platform.machine().lower()
if sysname.startswith("linux"):
return f"linux-{'aarch64' if 'aarch64' in arch or 'arm64' in arch else 'x86_64'}", ".so"
if sysname == "darwin":
return f"darwin-{'arm64' if 'arm64' in arch else 'x86_64'}", ".dylib"
if sysname in ("win32", "cygwin"):
return "win-amd64", ".dll"
raise RuntimeError(f"unsupported: {sysname}/{arch}")
def load_ext(conn: sqlite3.Connection, name: str):
plat, suf = platform_key()
path = EXT_DIR / plat / f"{name}{suf}"
conn.enable_load_extension(True)
conn.load_extension(str(path))
避坑指南
- Alpine(musl)与 Debian(glibc)二进制不通用;镜像基座要对应。
- 尽量用系统包管理器的(如
libsqlite3-mod-spatialite),生产上更省心。
SQLite 主库放哪儿:var/db/
位置:var/db/app_prod.sqlite(以及同目录的 -wal/-shm)
原因:运行期写入集中在 var/ 才好挂卷、好备份、好清理;镜像保持无状态、可复用。
最小可用配置:
# app/core/settings.py
from pathlib import Path
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
VAR_ROOT: Path = Path("./var").resolve()
DB_PATH: Path | None = None # 支持环境变量覆盖
def db_file(self) -> Path:
return self.DB_PATH or (self.VAR_ROOT / "db" / "app_prod.sqlite")
settings = Settings()
settings.db_file().parent.mkdir(parents=True, exist_ok=True)
DATABASE_URL = f"sqlite+aiosqlite:///{settings.db_file()}"
运行期参数(建议):
PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL; -- 更稳:FULL
PRAGMA foreign_keys=ON;
PRAGMA busy_timeout=5000;
写多读多场景,考虑 PostgreSQL/PostGIS,SQLite 适合单机或写入压力不高的服务。
Angular 放哪儿、怎么对接
位置:仓库根的 web/(或 frontend/),独立工作区。
开发态:ng serve → http://localhost:4200;后端 http://localhost:8000。
CORS(开发态):
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:4200"],
allow_methods=["*"],
allow_headers=["*"],
allow_credentials=True,
)
生产部署
- 推荐:前端构建产物上 CDN/Nginx;Nginx 反代后端
/api。 - 小项目可由 FastAPI 挂静态目录(把
web/dist/<app>/browser同步到public/,配一个 SPA fallback)。
.gitignore 与备份策略
/web/node_modules/
/web/dist/
/var/** # 运行期写入一律不入库
/data/** # 大数据不入库
!/data/samples/** # 仅小样例允许
/vendor/sqlite-extensions/** # 二进制通常不入库
!/vendor/sqlite-extensions/README.md
!/vendor/sqlite-extensions/checksums/INDEX.json
var/db/纳入备份计划(整库快照或定期.dump)。data/走对象存储/独立磁盘 + 校验和;仓库仅保存侧车元数据(来源、SHA、处理流程)。