七阶子博客: 杂文 | 游戏 | 戏剧 | 白蛇 | 文艺 | 编程 | 近期
请输入标题关键字或 yyyymmdd 格式的日期

    个人项目家谱系统开发经验略谈

    来源起因

    今年过年,初一拜年时,众亲人聊起一个话题,就叫我建了一个微信群,把太公名下子孙拉进来交流。我太公名下众人至今仍颇为亲近团结,每年拜年要聚在一起出行的。

    然后我发现很多堂弟妹及堂侄辈都叫不上名了。所以我想要有个方法把大家的名字记下来。正好我之前买了个优惠的简配的阿里云服务器,那就正好顺便以此练习,为家族做点有意义的事情吧。

    事实上,我们村谭氏有相当悠久的族谱传统。那是由长辈们自上而下维护的。我做这些家谱的初衷,是自下而上的,由近及远,打算先让当代年轻人录入最近几代的信息。比如我做出初步原型后,先发在太公群里,让最亲的人先录入小家信息,也算是小数据量的测试。

    后面如果这家谱系统若能做好,再扩大推广到更远的太公群里吧。至于能否推广到全村谭氏先祖的族谱电子化,可能不仅仅是技术问题了,更重要的是观念勾通,得有名望的长辈支持才行吧。然后仅从技术层面,若能做好,也不妨支援给外族人使用。

    数据库设计

    数据库就采用常见 mysql 关系型数据库。也有了解 monogdb ,但考虑过后,还是关系型数据库适合这种业务。并且自己还是对 mysql 更熟悉,之前服务器也已经装过,也就不想额外再装 monogdb 。

    每个人一条记录,用 id 标记。最重要的需要关联父亲的 id 表示父子传承关系。虽然父子继承这种关系,很像树这种数据结构,但是它嵌套层次理论上不可预期限制,但存储时显然应该拉平为线性结构。

    然后现代提倡男女平等的社会了,显然不能局限于只记男丁。所以也要记录女儿,于是也该记录母亲,就要在自己的记录上再加上母亲的 id 。然后母亲也要记录数据库,作为父亲配偶的关系入库,所以要加下配偶的 id 。

    接着就遇到一个问题需要决断。母亲的记录中,她的配偶 id 可以是父亲,但是她的父母 id 该怎么处理?把丈人亲家公的姓名也记录进来就立即散漫了,就不像纯正的家谱或族谱了。所以就规定,母亲的记录中,父母关系留空,也即为 null 。

    家谱或族谱另一个重要的概念是辈分,我在其他设计或说明文档中也称之为代际。将本库中能追溯到的最远先祖,设为 1 代,然后 2 代 3 代递增。然后母亲的代际如何算,本该是与父亲同辈,但为了区分特殊性,就用父亲代际的负数表示。于是负代际值的家庭成员就是外嫁过来的她姓族人。

    但是依男妇平等的观念看,女儿入库记录了,那女儿的儿子与后代是否也能入库呢。从技术设计的系统来看,是可以入库的。并且女儿那系的后代,也是应该用正数的代际表示,毕竟也是有先祖的遗传血脉(基因)的。但是女婿是外人,他的代际记录为负数;因为女儿的后代记录中要引用父母,所以女婿也入库。

    所以由数据库设计技术问题,归纳出一个重要概念。家谱收录的家庭成员,是直系后代,代际为正数,直系的配偶叫旁系,代际为负数。

    当然,这系统没法再兼容更激进的思想,比如同性恋,毕竟生物学上,同性也是无法诞生后代的。但是可以兼容再婚多婚的情况。那就是直系成员与旁系配偶可以是一对多的关系。允许有多个旁系记录,它们的配偶 id 关联到同一个直系成员;当然这个直系成员的记录本身,在配偶字段上只能记录一个 id ,不妨认识这个 id 标记的是元配可最值得记录(纪念)的伴侣吧。

    至于其他应该收集的信息字段,都是水平扩展,没有特别考究了。比如生日忌日,不过这对于先人来说,只能寄望原族谱有没记录相关信息了。如果还能记录更多文字的生平简介,最好另外建个表,以 id 关联。

    数据库重构

    试用过一段时间后,我的叔婶认为不应该录入外嫁女儿的后代。我之前为了支持录入女儿后代,把系统搞复杂了。最终让我放弃原来的想法,还是由于突然想到类似“多重继承”的困境。假设女儿后代用负代际表示,儿子后代用正代际表示。对于打算从远祖追溯的情况,很可能在隔了许多代之后,某个正代际的子嗣娶子负代际的传人(三代之后不算近亲,也无从追究乱伦吧),这样数据库系统内的数据就出现不一致了。虽然可以继续打补丁规定以正代际的子嗣为准,但无奈打补丁的做法,也正反映了原系统设计的疏忽。所以最终决定从简设计,不再收入儿子后代,也因此不再需求记录母亲配偶,只把配偶的姓名当作家族成员记录的一个字段了。简言之,就是数据库中不再出现负代际这种违反直觉的记录了。

    服务端

    由于个人兴趣或信仰原因,在自己服务器上大量使用 perl 脚本。所以服务端用 nginx + perl 技术,并且之前就搭建过,采用 FastCGI 模块包装成 fork 化的 CGI 脚本。所以在初期阶段,在不会出现显著性能瓶颈情况下,就用 perl 脚本提供 CGI 服务,这对于玩具或工具或原型足够简单粗暴。日后可优化的方向,比如直接用 FastCGI 提供 daemon 持久服务,避免 fork 脚本;或直接改用 C/C++ 重构。

    最开始我直接用 perl 在服务端生成 html 页面发前端。这其实是相对过时的技术,就连 perl 后来的版本都不默认支持 CGI 了,要额外自己装 CGI 模块。我没装这个重而过气的模块,自己直接撸 perl ,后来自己封装几个模块便于复用。一个取名 ForkCGI (避免与 CGI 模块重名),处理 GET POST 等输入参数;一个取名 HTPL ,利用模板化思想输出 html ;再撸一个 WebLog ,日志模块方便调试,可以随着正常页面一起发到前端。

    但是后来发现这仍然只能搞搞简单页面,只是服务端的伪动态页面,对前端来说还是静态网页。要做能拿出台面的网页,毕竟还是离不开 javascript 。只是我之前用 js 少,不熟悉,才用服务端的笨办法。

    正规办法是复习捡起 js ,反正要加 js ,不如彻底分离前后端。后端只设计 json 接口给 ajax 调用。

    前后端分离后,服务端代码也可进一步提炼可复用的 pm 模块。除了继续使用自己的 WebLog 轮子外,读取数据库的可用 DBI 与 SQL::Abstract 封装,命名为 MYDB.pm ; api 接口模块提炼为 JAPI.pm ,将具体业务的请求响应函数当作参数传入。

    前端

    我用 javascript 重构这个电子家谱系统算是新手吧,找了几本书复习,边学边用。

    主要是用到 jquery 库,太复杂的框架还是算了吧。$ 符号对于 perl 爱好者亲切。我偏向于手撸轮子,用 jquery 也只是简化 dom 操作写法,顺便不纠结浏览器兼容了。

    让我惊喜的是用 javascript + ajax 很容易做成单页界面,避免设计多个页面了,高效统一。将 ul 列表的 li 项目 float 左浮就做成水平导航条,再用 js 控制代表不同页的 div 显隐即可。

    刚开始用 js 重构时功能还不多,就两页的内容。一开始还写在一个 *.js 源文件中,主要用对象字面量的写法管理代码。后来代码行数增长很快,就大致拆成四个文件,分别对应四个全局变量 $DD $DV $DE $DJ ,管理数据、表现、事件、ajax 请求,其实也可以再把它们扔进一个 $DOC 全局变量。

    这就差不多了,有点类似 mvc 的思想,可以维护一个简单系统了吧。

    版本管理

    虽然初始只是一个人寂寞的项目。我还是将其分为了正式版与开发版两个 git 仓库。不是在一个仓库上拉分支,因为我经常 ssh 上服务器直接改代码与浏览器联调,切分支还是会让开发中代码处于不稳定状态。所以将一个暂时能访问能用的版本代码放在正式的目录地址下,而将开发版放到另外一个不怎么公开的地址目录中,自己随便调。然后阶段性地同步到正式地址目录中。