技术14 分钟阅读

从浏览器到 SQLite:Lineage Forest 技术栈与数据架构分析

基于公开页面、构建产物和只读 API 的黑盒分析,拆解 Lineage Forest 的前端、服务端、CBDB 数据来源、可视化链路与工程风险。

本文分析对象是 Lineage Forest(中华家族谱系森林)。结论来自 2026 年 8 月 19 日对公开 HTML、JavaScript 构建产物、PWA 文件和未登录只读 API 的检查,不包含源代码仓库、服务器配置或数据库模式访问,也没有进行压力测试、绕过认证或破坏性安全测试。

为了避免把猜测写成事实,文中使用三种标记:

  • 已确认:可以从响应头、构建产物或 API 返回值直接验证;
  • 高可信推断:有多条外部证据支持,但没有服务器源代码佐证;
  • 无法确认:仅凭公开客户端无法判断。

一句话结论

这是一个典型的“静态单页应用 + JSON API + 本地研究数据库快照”系统:React/Vite 前端通过 Caddy 入口访问 Nginx 静态资源和 Uvicorn ASGI API,服务端围绕一份 CBDB SQLite 快照做检索、聚类和统计,再由浏览器使用 SVG、Canvas 与 D3 模块绘制家族森林、迁徙、引力和比较视图。

手机 / 桌面浏览器
  ├─ HTML、CSS、JS、Manifest
  ├─ fetch('/api/...')
  └─ Google Fonts、百度统计
             │
             ▼
      Caddy 反向代理入口
        ├─ 前端静态站点 ──> Nginx
        └─ /api/*       ──> Uvicorn ASGI 应用
                                  │
                                  ├─ 查询与聚类逻辑
                                  ├─ 索引 / 活动证据缓存
                                  └─ CBDB SQLite 快照
                                     cbdb-2026-02-08.db

上图中的 Caddy、Nginx、Uvicorn 和 SQLite 均有公开响应证据;“查询与聚类逻辑”的内部拆分则是根据 API 设计作出的高可信推断。

前端技术栈

React + Vite 单页应用

首页 HTML 只提供 #root 容器,主要 UI 由带哈希的 JavaScript 和 CSS 构建产物加载。主包中可以确认 React 的生产运行时代码与 createRoot 调用,Vite 的模块预加载引导代码也清晰可见。

路由不是由 React Router 管理,而是项目自己封装了 history.pushStatepopstate 和路由表。公开页面包括:

  • 总览、家族森林、迁徙、引力、活动流、比较、证据;
  • 登录、注册、找回密码、重置密码、个人资料;
  • 管理端入口。

这能减少一个依赖,但也意味着路由匹配、跳转、404、滚动恢复和可访问性都要由项目自己维护。

可视化:SVG、Canvas 与 D3 模块混合

主包包含 SVG 路径、矩阵和弦图相关代码;迁徙页面的异步分包大量使用 Canvas,同时使用 D3 的缩放/手势能力和 SVG 交互覆盖层。没有发现 Leaflet、Mapbox GL、MapLibre、ECharts 或 Chart.js 的明确运行时代码,因此地图和关系图更像是定制绘制,而不是套用完整地图 SDK。

数据量较大的迁徙页面被拆成独立包。实测未压缩构建文件大致为:主包 362 KB、迁徙页 642 KB、Markdown 渲染包 118 KB、管理端 83 KB。带内容哈希的静态资源设置了长达一年的 immutable 缓存,这是合理的生产配置;迁徙页仍是明显的首个优化候选。

其他前端能力

  • 使用原生 fetchAbortController、超时和有限重试,没有发现 Axios;
  • 部分查询采用 0 / 400 / 1200 / 2500 ms 的重试节奏;
  • Markdown 内容通过懒加载的 react-markdown / remark / hast 工具链安全渲染;
  • 提供 PWA manifest,显示模式为 standalone,方向偏好为横屏;
  • 使用 Google Fonts 的 Noto Serif SC 与 Noto Sans SC;
  • 接入百度统计;
  • 没有发现 WebSocket、Server-Sent Events、IndexedDB 或客户端 SQLite。

服务端与网络架构

反向代理分流

前端 HTML 响应包含 Server: nginx/1.31.3Via: 1.1 Caddy;API 响应则包含 Server: uvicorn 和同样的 Caddy Via。这强烈表明 Caddy 是外层反向代理,按路径把静态请求交给 Nginx,把 /api 请求交给 Python ASGI 服务。

API 的 404 返回 {"detail":"Not Found"},接口风格也与 FastAPI/Starlette 接近。由于 /api/docs/api/openapi.json 都返回 404,能确认的是 Uvicorn 上的 Python ASGI 服务;“FastAPI/Starlette”只能列为高可信推断,不能定论。

API 组织

客户端将 API 基地址固定为 /api。只读接口大致分为五组:

能力 代表接口 用途
元数据 overviewhealthmethodsdynasties 数据规模、运行状态和筛选维度
家族网络 forestsurnameclusterbranchedge 聚类、分支和关系边
时空分析 migrationsactivity-rangesactivity-pointsgravitystreams 迁徙、活动范围和时序流
人物研究 searchpersonevidencecompare 搜索、详情、证据与比较
账户系统 auth/*profileaccess-policy 登录、注册、资料和访客权限

前端根据 allow_guest_compute 决定访客是否能使用计算量较大的森林、迁徙、引力、活动流、比较和搜索功能。本次检查时访客计算处于开启状态。

一次典型请求如何流动

  1. 用户在 React 界面选择姓氏、朝代、聚类方式和关系层;
  2. 浏览器把筛选项编码成 /api/forest/api/migrations 等查询参数;
  3. ASGI 服务查询 SQLite,并可能利用预建索引或活动证据缓存;
  4. API 返回人物、簇、边、地点、年份桶和证据等 JSON;
  5. 浏览器再把结果转换成 SVG、Canvas、矩阵或和弦图。

没有实时连接技术的痕迹,因此这是按需请求—响应模型,而非实时流式系统。

数据来源与数据库结构

数据来自 CBDB 的本地 SQLite 快照

页面 JSON-LD 和说明文字将数据集标为 China Biographical Database;更直接的证据是 /api/overview 返回了服务端数据库路径 /data/sqlite_db/cbdb-2026-02-08.db。所以该站很可能在服务器本地查询 CBDB 快照,而不是每次请求都转发到 CBDB 官方 API。

CBDB 官方结构说明将其定义为中国二十世纪初以前人物传记信息的关系数据库,核心实体包括人物、地点、亲属、社会关系、入仕、官职、机构和文本。官方也提供 SQLite 发布与使用说明。这些说明与目标站公开的表名和功能高度一致。

需要注意:2026-02-08 是目标站数据库文件名所表达的快照日期。CBDB 官方下载页在本次分析时已经列出 2026 年 6 月的更新版本,因此目标站可能落后数月;但在不知道其同步流程的情况下,不能仅凭文件名断言数据一定过期。

公开接口显示的规模

数据 记录数
人物主表 BIOG_MAIN 656,436
亲属关系 KIN_DATA 553,330
社会关系 ASSOC_DATA 186,876
任官记录 POSTED_TO_OFFICE_DATA 601,002
入仕记录 ENTRY_DATA 263,267
人物地址 BIOG_ADDR_DATA 455,705
地名代码 ADDR_CODES 30,099
官职代码 OFFICE_CODES 34,052

接口还列出了朝代、亲属代码、社会关系代码、郡望代码和入仕代码,共 13 张用于总览的表。总览显示 85 条朝代记录,而公开朝代筛选接口返回 84 项,可能是筛选掉了未知或占位记录;客户端之外无法确认具体规则。

聚类方法

站点公开了五种方法:

  1. 仅姓氏——只适合总览,不作为家族定义;
  2. 姓氏 + 郡望;
  3. 姓氏 + 籍贯地;
  4. 姓氏 + 亲属连通分量;
  5. 综合聚类——结合郡望、籍贯和亲属。

这套设计很重要:同姓不等于同一家族,所以系统必须用地点和亲属图把大姓拆开。姓氏接口当前返回 793 个可用姓氏,并要求至少 6 人、至少跨 2 个主要朝代且存在亲属或社会关系记录;迁徙与活动接口使用的姓氏集合更大,筛选标准也不同。

覆盖率决定了结论边界

字段或关系 覆盖率
姓氏 98.1%
朝代 98.3%
籍贯地 58.3%
指数年 45.9%
亲属记录 43.0%
任官记录 45.4%
入仕记录 33.4%
社会关系记录 6.6%
郡望代码 2.0%
女性 8.7%

这些不是普通的“缺几个字段”。郡望覆盖率只有 2.0%,意味着“姓氏 + 郡望”只能覆盖很小一部分人物,实际聚类会更依赖籍贯和亲属连通分量。指数年覆盖不足一半,朝代内部的早、中、晚或 25 年桶会把大量人物归入“年代未详”。社会关系覆盖率仅 6.6%,所以“没有社会关系记录”绝不能解释成“历史上社会联系很弱”。女性占比 8.7% 同样提示显著的史料与收录偏差。

CBDB 用户指南强调它是关系数据库,适合对人物群体、亲属与社会网络、地点和仕途进行组合查询。Lineage Forest 的价值正是在这个关系模型之上提供更易探索的可视化界面;其图形仍然受原始资料覆盖率约束。

缓存、索引与计算策略

公开健康接口显示应用识别到 11 个索引,其中名称涉及姓氏、朝代和郡望;活动证据缓存已处理约 44.1 万人、存储约 68.6 万条成员关系。结合 API 的分页/筛选方式,可以作出以下高可信推断:

  • 常见维度过滤依赖 SQLite 索引;
  • 活动范围或证据关系至少有一部分预计算缓存;
  • 聚类和边过滤主要在服务端完成,浏览器负责布局与绘制;
  • SQLite 很适合此类读多写少的单机研究应用,但横向扩容、并发写入和多实例缓存一致性会成为后续约束。

无法从外部确认缓存是内存对象、SQLite 辅助表还是独立文件,也无法确认部署是否只有一个应用实例。

身份、状态与第三方服务

客户端会把 lf_session 写入 localStorage 或 sessionStorage,并在受保护请求上发送 Authorization: Bearer ...;同时使用一个 X-LUID 客户端标识头。令牌载荷结构类似点分隔的紧凑令牌,客户端会解析其中的字段,但公开代码不足以证明服务器使用哪种签名算法或会话存储。

第三方请求主要是 Google Fonts 和百度统计。两者都会增加外部依赖,也意味着站点运营者应在隐私说明中解释字体与分析请求可能产生的网络元数据。

工程优点

  • 前后端职责清楚,静态资源和 API 由反向代理分流;
  • 带哈希资源采用长期不可变缓存,并对大型页面做代码分割;
  • API 支持超时、取消和有限重试,避免请求无限挂起;
  • 聚类方法与覆盖率警告直接呈现给用户,没有把缺失数据隐藏起来;
  • 证据、人物详情、簇和边都有独立查询入口,具备可追溯研究工具的雏形;
  • 没有公开 source map,减少了生产源代码的无意暴露。

风险与改进建议

1. 收紧健康接口

公开 /api/health 返回应用版本、运行时间、数据库可写状态、内部索引名和缓存规模。健康检查通常只需要 ok/ready。建议把详细诊断移到受认证的运维接口,公网只保留最少状态,数据库在生产查询进程中尽可能只读挂载。

2. 增加明确的安全响应头

本次 HTML 响应中未观察到 Content-Security-Policy、Strict-Transport-Security、X-Content-Type-Options、Referrer-Policy 或 Permissions-Policy。建议在 Caddy 或 Nginx 统一配置,并先用报告模式验证 CSP,尤其要覆盖 Google Fonts、百度统计、脚本、样式和图片来源。

3. 降低持久化令牌的 XSS 风险

localStorage 中的 bearer token 会被同源 JavaScript 读取。如果账户权限具有实际价值,应优先评估 HttpOnly + Secure + SameSite Cookie;若继续使用 bearer token,则应缩短有效期、使用刷新轮换、严格 CSP,并确保 Markdown 与所有富文本入口持续执行安全过滤。

4. 优化迁徙页载荷

迁徙分包未压缩约 642 KB。可以进一步拆分交互模块、延迟加载低频图层、审查 D3 子模块导入,并为大数据集采用分层抽样或视口裁剪。优化前应以真实设备的 LCP、INP 和内存峰值为基线。

5. 改善 SPA 的搜索与分享能力

当前服务端返回的是通用 #root 壳,正文依赖 JavaScript。虽然页面预置了 canonical、Open Graph 与 JSON-LD,但搜索引擎和社交抓取器不一定执行完整交互。可以对总览、方法说明和公开研究结果做静态生成或服务端渲染,并让 sitemap 覆盖实际公开路由,而不只列根地址。

6. 建立可见的数据版本与同步记录

页面应明确展示 CBDB 快照日期、校验值、同步方式、转换脚本版本和最后成功导入时间,并链接 CBDB 推荐引用方式。这能让研究者判断复现条件,也能解释为什么人数或关系数与官方最新版本不同。

结论

Lineage Forest 的核心并不是某个炫目的前端框架,而是把 CBDB 的关系型人物资料转换成可筛选、可追溯的家族与时空图。它的架构紧凑:静态 React SPA 承担交互和绘图,Python ASGI API 承担查询与聚类,SQLite 快照提供数据基础,Caddy 负责统一入口。

从公开证据看,这个组合非常适合目前的读密集型研究展示。下一阶段最值得投入的方向是:减少公开运维信息、补齐浏览器安全策略、记录数据版本谱系、改善 SPA 的可索引内容,并控制大型可视化分包在手机上的计算和内存成本。

最后要强调:这是一份黑盒技术画像,不是源码审计。FastAPI/Starlette 的具体选择、缓存实现、SQL 查询、部署拓扑实例数、认证签名与备份策略,都需要项目方提供仓库或运维配置才能最终确认。