Skip to content

技术架构

云族纪是一个跨端前端 + Serverless 后端 + 文档型数据库的三层应用。本文从工程视角讲清楚:数据怎么存、关系怎么算、服务怎么拆。

技术栈一览

层面选型为什么是它
跨端框架uni-app(Vue 3 Composition API + Vite)一套代码编译到微信小程序 / App / H5,自家人在哪都能用
后端uniCloud 阿里云(云函数 + 云数据库)Serverless,免运维,免费额度足够一个家族使用
数据库云数据库(MongoDB 风格的文档库,JQL 访问)关系结构灵活,适合字段会随成员增长的族谱数据
状态管理Pinia(+ 本地持久化插件)登录态与用户资料缓存
关系画布Canvas 2D 全自绘族谱布局/连线/命中测试高度定制,现成图库表达不了网状关系
UI 组件uv-ui、uni-ui、TDesign 多套按需取用表单/弹窗等标准组件不重复造轮子

整体代码约 3 万行(前端 2.7 万 + 云端 0.4 万),配套 50 个无头回归脚本守护核心算法。

三层架构

┌─────────────────────────────────────────────────────────┐
│  前端(uni-app 编译到三端)                                  │
│                                                           │
│  页面 pages/        族谱画布(Canvas 自绘)                   │
│      │                    │                              │
│      │            称呼引擎(纯 JS,本地实时算)               │
│      │                    │                              │
│      └───────── 统一云调用封装 callCloud ─────────┘         │
└───────────────────────────┬─────────────────────────────┘
                            │  uniCloud.callFunction(带 token)
┌───────────────────────────▼─────────────────────────────┐
│  云端(云函数,按业务域拆分)                                 │
│                                                           │
│  api-family   家族/成员/治理    api-tree    族谱树读取        │
│  api-identity 认领/分身         api-transfer 导入导出       │
│  api-record   家族记录          api-user    用户资料        │
│         │              公共业务模块 yz-family-core │         │
│         │      (权限断言 / 匿名净化 / 合并 / 归档 共用)       │
└───────────────────────────┬─────────────────────────────┘
                            │  JQL(受 schema 权限约束)
┌───────────────────────────▼─────────────────────────────┐
│  云数据库(文档集合)                                        │
│  用户即成员行 / 家族 / 分身可见性 / 主分身 / 特批名单 /         │
│  归档留痕 / 导入批次 / 家族记录 …                            │
└─────────────────────────────────────────────────────────┘

云函数按业务域拆分

最初所有逻辑挤在一个云函数里,随着功能变多,按业务域拆成了多个,并抽出一个公共模块:

云函数职责
api-family家族与成员的增删改、治理、搜索、默认家族偏好
api-tree族谱树的读取、外族访问裁剪、匿名净化
api-identity真人认领、身份合并、分身匿名、主分身
api-transferCSV 导入导出、GEDCOM 转换、批次撤销
api-record家族内部的图文记录与收支
api-user成员登记、头像、账号注销
yz-family-core(公共模块)上面多个函数共用的核心:权限断言、匿名净化、认领合并、归档恢复

拆分的好处是每个函数职责单一、改一处不牵连其他;公共逻辑沉到 yz-family-core,避免同一段权限/合并规则在各处写出不一致的版本。

数据模型:一个人,为什么有三种 ID

这是整个系统最关键的设计。传统族谱里"一个人 = 一行数据",但现实中一个人会同时出现在多个家族:嫁过来的配偶、随母出现的外亲子女……

云族纪的做法是:真人只有一行数据,但在不同家族的树上会生成不同的"呈现节点"。前端用一个叫 cloneId 的结构字段来定位每个节点,它有三种形态:

节点类型cloneId 形态含义
本家血亲就是本人数据行的 id他在自己家族的"本体"
配偶分身配偶id__host__宿主节点他作为配偶,出现在伴侣家族的那个节点
外亲子女孩子id__outsider__母亲节点他随母亲(外亲)出现在另一个家族的节点

这样设计带来的好处:

  • 数据不重复:一个真人无论出现在几个家族,资料只存一份,改一处处处生效。
  • 结构可表达:每个"分身"都是树上一个可定位、可连线、可算称呼的节点。
  • 隐私可控制:可以单独把某个家族里的分身匿名掉,而不影响他在自己家族的实名身份。

数据行里存的始终是真人之间的原始关系(父 id、母 id、配偶 id 数组);cloneId 这类结构字段只在读取族谱树时实时组装,不落库。这样原始数据保持干净,展示层怎么变都不污染事实源。

为什么不用图数据库

族谱听起来很像"图",但最终没有引入图数据库(如 Neo4j),原因是:

  1. 父系血亲部分本质是一棵严格的树——每个人有确定的父、母,辈分、排行沿树推导,路径唯一,文档库 + 树组装就够了。
  2. 真正"网状"的只有配偶和外亲这些挂接点,数量少、规则明确,用约定好的分身结构表达,比引入一整套图查询更可控。
  3. Serverless 场景下图数据库运维和成本都不划算,而文档库是 uniCloud 自带的,零额外基础设施。

换句话说:用结构化的约定,把"图"的复杂度收敛到了少数几类连边上,而不是为了这部分复杂度背上一个图数据库。

工程保障

  • 无头回归测试:50 个 Node 脚本覆盖称呼样例、布局方向、路径 id、匿名净化、特批名单、导入组装等,一条命令全跑,核心算法改动有兜底。
  • 数据真相唯一:服务端数据库行是唯一事实源,前端缓存只做展示加速,绝不反向定义关系语义。
  • 危险操作可逆:删除、认领合并、批量导入都写归档快照,而不是物理删除,详见归档可逆设计

下一步可以读:

私有部署 · 家族专用