Skip to content

TRIP 项目核心原理与代码阅读指南

项目概述

TRIP (Tourism and Recreational Interest Points) 是一个自托管的极简地图追踪器与行程规划工具。

  • GitHub: https://github.com/itskovacs/trip
  • 前端: Angular (位于 src/ 目录)
  • 后端: Python/FastAPI (位于 backend/trip/ 目录)

目录结构

external/trip/
├── src/                          # Angular 前端
│   ├── src/app/                  # Angular 组件、服务
│   └── ...
├── backend/trip/                 # Python 后端 (FastAPI)
│   ├── main.py                   # FastAPI 应用入口
│   ├── config.py                 # 配置管理 (Pydantic Settings)
│   ├── security.py               # 认证: JWT/TOTP/OIDC
│   ├── deps.py                   # 依赖注入 (SessionDep)
│   ├── models/
│   │   └── models.py             # SQLModel ORM 模型定义
│   ├── db/
│   │   └── core.py               # 数据库连接与迁移
│   └── routers/                  # API 路由
│       ├── auth.py               # 认证相关
│       ├── trips.py              # 行程 CRUD
│       ├── places.py             # 地点 CRUD
│       ├── categories.py         # 分类
│       ├── token.py              # Token 管理
│       ├── providers.py          # 地图 provider
│       ├── settings.py           # 设置
│       └── admin.py              # 管理接口
└── docs/                         # Docusaurus 文档

核心原理

1. 认证与授权

技术栈: JWT + Argon2 + TOTP (可选) + OIDC (可选)

文件 职责
security.py hash_password(), verify_password(), create_access_token(), verify_totp_code(), OIDC 客户端
deps.py SessionDep (数据库会话 DI), get_current_username() (从 JWT 提取当前用户)
routers/auth.py 登录/注册/Token 刷新

Token 流程:

  1. 用户登录 → verify_password() 验证密码 → create_tokens() 生成 access + refresh token
  2. 后续请求携带 Authorization: Bearer <token>
  3. oauth_password_scheme + get_current_username() 从 JWT 解码出 sub (username)

关键代码路径: deps.py:get_current_username()security.py:verify_password()config.py:SECRET_KEY


2. 数据模型

核心模型 (models/models.py):

模型 关系
User 认证主体,可选 TOTP secret
Place POI (兴趣点),含坐标、分类、图片
Trip 行程,含多天 TripDay
TripDay 行程中的一天,含多个 TripItem
TripItem 行程项 (可标记状态: pending/booked/constraint/optional)
TripShare 分享链接 (token 机制)
TripMember 行程成员
TripInvitation 邀请
Image 图片
Category 地点分类 (默认 8 类)
TripPackingListItem / TripChecklistItem 行李/清单

关键设计:

  • 使用 sqlmodel (SQLAlchemy + Pydantic 混合)
  • 软删除通过 deleted_at timestamp 实现
  • after_commit hook 清理待删除文件

3. 数据库与迁移

技术栈: SQLite + Alembic

文件 职责
db/core.py get_engine() (单例), init_and_migrate_db() (启动时迁移)
alembic.ini Alembic 配置
db/migrations.py 数据迁移 (填充默认值等)

迁移流程 (main.py:lifespan):

  1. 启动时调用 init_and_migrate_db()
  2. 检查 SQLite 文件是否存在
  3. 不存在 → command.upgrade("head")
  4. 存在但无 Alembic 版本表 → command.stamp("b2ed4bf9c1b2") (标记初始版本)
  5. 已有版本 → command.upgrade("head")

4. API 路由设计

RESTful 风格,前缀 /api/

Router 路由 说明
auth.py /auth/login, /auth/register, /auth/refresh 认证
trips.py /api/trips 行程 CRUD、分享、成员管理
places.py /api/places 地点 CRUD
categories.py /api/categories 分类管理
token.py /token Token 操作
providers.py /api/providers 地图 provider
settings.py /api/settings 用户设置
admin.py /api/admin 管理功能

权限检查模式 (deps.py + security.py:verify_exists_and_owns):

def verify_exists_and_owns(username: str, obj) -> None:
    if not obj:
        raise HTTPException(status_code=404)
    if obj.user != username:
        raise HTTPException(status_code=403)

5. 前端架构 (Angular)

主要目录 (src/src/app/):

  • app.component.ts - 根组件
  • components/ - 共享组件
  • pages/ - 页面 (map, trips, places, settings 等)
  • services/ - API 服务 (trip.service.ts, place.service.ts 等)
  • models/ - TypeScript 接口定义
  • shared/map.ts - 地图相关工具函数

关键特性:

  • Angular Service 进行 HTTP 调用后端 API
  • 地图使用 Leaflet + leaflet.markercluster (聚合) + leaflet-contextmenu
  • PWA 支持 (ngsw-config.json)

6. 地图处理

TRIP 的地图处理分两部分:前端渲染 + 后端搜索/路由

前端 (Leaflet)

核心文件: src/src/app/shared/map.ts

函数 用途
createMap() 初始化 Leaflet 地图,默认底图 CARTO Voyager
placeToMarker() 完整圆形标记 (含分类图标)
placeToDotMarker() 小圆点 (列表视图用)
tripDayMarker() 行程中的日程点
toDotMarker() 任意坐标点
gpxToPolyline() 解析 GPX XML 绘制轨迹线
openNavigation() 调用 Google Maps 网页版导航
createClusterGroup() 聚合标记组 (zoom < 11 时聚合成簇)

底图:

  • 默认: CARTO Voyager (https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png)
  • 用户可配置: User.custom_tile_layer (自定义瓦片 URL)

数据模型 (models/models.py):

  • User.map_provider — "osm" 或 "google"
  • User.custom_tile_layer — 自定义瓦片 URL
  • User.google_apikey — Google API Key
  • User.map_lat/lng/zoom — 地图初始位置

后端 Provider 抽象

目录: utils/providers/

文件 职责
base.py 抽象基类 BaseMapProvider,定义接口 + encoded polyline 解码
osm.py OpenStreetMap (Nominatim 搜索 + OSRM 路由)
google.py Google Maps (Places API, Directions API)

OpenStreetMapProvider (osm.py):

  • 搜索: https://nominatim.openstreetmap.org/search
  • 路由: https://routing.openstreetmap.de/routed-{profile}/route/v1/driving
  • 支持 profile: car, foot, bike
  • 地点分类映射: TYPES_MAPPER 将 OSM amenity/shop/tourism 标签映射到 TRIP 的 8 分类

API 端点 (routers/providers.py):

端点 功能
POST /api/completions/search 文本搜索地点
POST /api/completions/nearby 附近搜索 (仅 Google)
GET /api/completions/geocode 地址 → 边界框
POST /api/completions/route 路线规划
POST /api/completions/bulk 批量导入 Google Maps 链接
POST /api/completions/mymaps-import Google My Maps KMZ 导入
POST /api/completions/takeout-import Google Takeout CSV 导入
GET /api/completions/google/resolve-shortlink/{link_id} 解析 Google 短链接

批量处理: _process_batch() 使用 asyncio.Semaphore(4) 限流,并发处理最多 4 个请求。


代码阅读顺序

推荐按以下顺序阅读核心代码:

阶段 1: 理解应用骨架

  1. main.py - FastAPI 应用初始化、路由注册、中间件
  2. config.py - 配置管理、环境变量

阶段 2: 理解认证流程

  1. deps.py - 依赖注入模式
  2. security.py - JWT、密码哈希、TOTP、OIDC

阶段 3: 理解数据层

  1. models/models.py - ORM 模型定义 (Trip, Place, User 关系)
  2. db/core.py - 数据库初始化与迁移

阶段 4: 理解业务逻辑

  1. routers/trips.py - 行程核心逻辑 (最复杂)
  2. routers/places.py - 地点 CRUD
  3. routers/auth.py - 认证流程

阶段 5: 理解前端

  1. Angular services (src/src/app/services/) - API 调用
  2. Angular pages (src/src/app/pages/) - 页面组件

阶段 6: 理解地图

  1. shared/map.ts - Leaflet 地图初始化、标记、GPX 绘制
  2. utils/providers/base.py - Provider 抽象接口
  3. utils/providers/osm.py - OSM 搜索与路由
  4. routers/providers.py - 地图 API 端点

关键文件速查

目标 文件 关键函数/类
JWT Token 验证 deps.py get_current_username()
密码哈希 security.py hash_password(), verify_password()
创建 Token security.py create_access_token(), create_refresh_token()
数据库连接 db/core.py get_engine()
启动迁移 db/core.py init_and_migrate_db()
Trip 创建 routers/trips.py create_trip()
Place 创建 routers/places.py create_place()
权限检查 security.py verify_exists_and_owns()
图片处理 utils/utils.py save_image_to_file(), patch_image()
地图初始化 shared/map.ts createMap()
地点搜索 routers/providers.py text_search()
路线规划 utils/providers/osm.py get_route()
Provider 基类 utils/providers/base.py BaseMapProvider

依赖关系图

main.py (应用入口)
    ├── config.py (get_settings)
    ├── db/core.py (get_engine, init_and_migrate_db)
    │       └── models/models.py (SQLModel models)
    ├── routers/* (API endpoints)
    │       ├── deps.py (SessionDep, get_current_username)
    │       ├── security.py (JWT, password, verify_exists_and_owns)
    │       └── models/models.py
    └── security.py (token creation, password hashing)

调试技巧

  1. API 测试: 启动后端后直接访问 /api/info 获取版本
  2. 数据库: storage/trip.sqlite 可用 sqlite3 直接查看
  3. 日志: utils/utils.py:silence_http_logging() 默认抑制 FastAPI HTTP 日志
  4. 配置: storage/config.env 运行时配置

研究问题清单

  • 行程分享的 token 机制是如何工作的?
  • TOTP 2FA 的完整验证流程?
  • OIDC 认证的完整流程?
  • 图片上传和处理的完整流程?
  • TripDay 和 TripItem 的嵌套关系如何持久化?
  • 前端如何与后端 API 交互?
  • OSM Provider 如何将 Nominatim 结果映射到 TRIP 分类?
  • Google My Maps KMZ 导入的完整解析流程?
  • 批量导入的并发限流是如何实现的?
Backlinks (1)
flowchart LR
  n0["AI"]
  n1["Database"]
  n2["Dev Tools"]
  n3["Emoji"]
  n4["Frontend"]
  n5["Game Dev"]
  n6["Collection"]
  n7["Languages"]
  n8["Maps"]
  n9["Media"]
  n10["Monitor"]
  n11["Plans"]
  n12["对象存储基本用法(Bucket / Object / 常用操作)"]
  n13["对象存储数据迁移(R2 → MinIO / 跨厂商搬迁)"]
  n14["Object Storage"]
  n15["挂载 Bucket 为本地文件系统(FUSE Mount)"]
  n16["对象存储签名 URL(Signed URL)原理与实战"]
  n17["对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"]
  n18["Prototypes"]
  n19["Research"]
  n20["Better Auth 源码阅读指南"]
  n21["DuckDB 环境与基本使用"]
  n22["DuckDB 实战研究"]
  n23["DuckDB 模拟数据"]
  n24["PostgreSQL 数据用 DuckDB 加速查询"]
  n25["HK 角色阅读"]
  n26["HK 台词阅读"]
  n27["Hollow Knight 英语主题"]
  n28["HK 物品阅读"]
  n29["HK 地点阅读"]
  n30["HK 世界观 / Lore 阅读"]
  n31["HK 英语学习素材清单"]
  n32["英语学习 Dashboard"]
  n33["English Scraps Archive"]
  n34["English Scraps 使用指南"]
  n35["Jellyfin 源码阅读指南"]
  n36["Lux 资料整理"]
  n37["Nest Commander 学习资料"]
  n38["NestJS 源码阅读指南"]
  n39["Protomaps 自建底图研究"]
  n40["自制 PMTiles 地图(最简单例子)"]
  n41["MapLibre 集成 Protomaps"]
  n42["PMTiles 格式与工具链"]
  n43["上海地区底图项目"]
  n44["Redash 源码阅读指南"]
  n45["Rust 学习计划"]
  n46["shadcn/ui 源码阅读指南"]
  n47["TRIP 项目核心原理与代码阅读指南"]
  n1 --> n22
  n6 --> n0
  n6 --> n1
  n6 --> n2
  n6 --> n3
  n6 --> n4
  n6 --> n5
  n6 --> n7
  n6 --> n8
  n6 --> n9
  n6 --> n10
  n6 --> n11
  n6 --> n19
  n12 --> n13
  n12 --> n16
  n12 --> n17
  n12 --> n18
  n13 --> n12
  n13 --> n15
  n13 --> n16
  n13 --> n17
  n14 --> n12
  n14 --> n13
  n14 --> n15
  n14 --> n16
  n14 --> n17
  n15 --> n12
  n15 --> n16
  n15 --> n17
  n16 --> n18
  n17 --> n12
  n17 --> n13
  n17 --> n16
  n17 --> n18
  n18 --> n12
  n18 --> n15
  n18 --> n16
  n18 --> n17
  n18 --> n39
  n19 --> n20
  n19 --> n22
  n19 --> n32
  n19 --> n35
  n19 --> n36
  n19 --> n37
  n19 --> n38
  n19 --> n39
  n19 --> n44
  n19 --> n45
  n19 --> n46
  n19 --> n47
  n21 --> n23
  n22 --> n1
  n22 --> n19
  n22 --> n21
  n22 --> n23
  n22 --> n24
  n23 --> n21
  n23 --> n24
  n24 --> n22
  n24 --> n23
  n27 --> n25
  n27 --> n26
  n27 --> n28
  n27 --> n29
  n27 --> n30
  n27 --> n31
  n27 --> n34
  n31 --> n26
  n32 --> n27
  n32 --> n33
  n32 --> n34
  n34 --> n32
  n34 --> n33
  n39 --> n8
  n39 --> n40
  n39 --> n41
  n39 --> n42
  n39 --> n43
  n40 --> n42
  n41 --> n18
  n41 --> n43
  n42 --> n40
  n42 --> n43
  n43 --> n41
  n43 --> n42
  click n0 "../../../collection/ai/" "AI"
  click n1 "../../../collection/database/" "Database"
  click n2 "../../../collection/dev-tools/" "Dev Tools"
  click n3 "../../../collection/emoji/" "Emoji"
  click n4 "../../../collection/frontend/" "Frontend"
  click n5 "../../../collection/game-dev/" "Game Dev"
  click n6 "../../../collection/" "Collection"
  click n7 "../../../collection/languages/" "Languages"
  click n8 "../../../collection/maps/" "Maps"
  click n9 "../../../collection/media/" "Media"
  click n10 "../../../collection/monitor/" "Monitor"
  click n11 "../../../collection/scraps/plans/" "Plans"
  click n12 "../../../knowledge/infrastructure/cloud/object-storage/basic-usage/" "对象存储基本用法(Bucket / Object / 常用操作)"
  click n13 "../../../knowledge/infrastructure/cloud/object-storage/data-migration/" "对象存储数据迁移(R2 → MinIO / 跨厂商搬迁)"
  click n14 "../../../knowledge/infrastructure/cloud/object-storage/" "Object Storage"
  click n15 "../../../knowledge/infrastructure/cloud/object-storage/mount-bucket/" "挂载 Bucket 为本地文件系统(FUSE Mount)"
  click n16 "../../../knowledge/infrastructure/cloud/object-storage/signed-url/" "对象存储签名 URL(Signed URL)原理与实战"
  click n17 "../../../knowledge/infrastructure/cloud/object-storage/vendors-comparison/" "对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"
  click n18 "../../../prototypes/" "Prototypes"
  click n19 "../../" "Research"
  click n20 "../better-auth/" "Better Auth 源码阅读指南"
  click n21 "../duckdb/basic-usage/" "DuckDB 环境与基本使用"
  click n22 "../duckdb/" "DuckDB 实战研究"
  click n23 "../duckdb/mock-data/" "DuckDB 模拟数据"
  click n24 "../duckdb/postgresql-acceleration/" "PostgreSQL 数据用 DuckDB 加速查询"
  click n25 "../english/hollow-knight/characters/" "HK 角色阅读"
  click n26 "../english/hollow-knight/dialogues/" "HK 台词阅读"
  click n27 "../english/hollow-knight/" "Hollow Knight 英语主题"
  click n28 "../english/hollow-knight/items/" "HK 物品阅读"
  click n29 "../english/hollow-knight/locations/" "HK 地点阅读"
  click n30 "../english/hollow-knight/lore/" "HK 世界观 / Lore 阅读"
  click n31 "../english/hollow-knight/resources/" "HK 英语学习素材清单"
  click n32 "../english/" "英语学习 Dashboard"
  click n33 "../english/scraps/archive/" "English Scraps Archive"
  click n34 "../english/scraps/" "English Scraps 使用指南"
  click n35 "../jellyfin/" "Jellyfin 源码阅读指南"
  click n36 "../lux/" "Lux 资料整理"
  click n37 "../nest-commander/" "Nest Commander 学习资料"
  click n38 "../nestjs/" "NestJS 源码阅读指南"
  click n39 "../protomaps/" "Protomaps 自建底图研究"
  click n40 "../protomaps/make-own-map/" "自制 PMTiles 地图(最简单例子)"
  click n41 "../protomaps/maplibre/" "MapLibre 集成 Protomaps"
  click n42 "../protomaps/pmtiles/" "PMTiles 格式与工具链"
  click n43 "../protomaps/shanghai-map/" "上海地区底图项目"
  click n44 "../redash/" "Redash 源码阅读指南"
  click n45 "../rust/" "Rust 学习计划"
  click n46 "../shadcn-ui/" "shadcn/ui 源码阅读指南"
  click n47 "./" "TRIP 项目核心原理与代码阅读指南"