回看“生灵集”这个仓库,我现在更愿意把它看成一次课程项目的工程练习,而不是一段已经被产品结果证明过的故事。仓库最近一条完整提交是 af1de30,时间是 2025 年 6 月 7 日。以这份提交和仓库里能读到的文件为准,能确认的内容并不神秘:一个静态项目展示页,一个 Android 子模块,以及一个用 Node.js 写的后端服务。
这和早期写下的那篇介绍稿不太一样。旧稿很容易从几个截图出发,顺手补上用户、社区、推荐和识别功能,再用一段漂亮的结尾把它们串成“完整应用”。但代码仓库更诚实。它会告诉我哪些目录真的存在,哪些接口真的写过,哪些功能只停留在设计文档里。重新整理以后,这篇文章只讨论仓库留下的结构和问题,不替它添加没有证据的上线情况。
先把仓库拆开看
父仓库的职责比较清楚:它保存项目说明、设计文档、静态展示页和一组 Android/网页截图,Android 客户端通过 Git submodule 关联到另一个仓库,后端则放在 shenglingji-backend 目录。这样的组织方式适合课程项目展示,但也带来一个容易忽略的阅读成本:只克隆父仓库并不会自动得到客户端的完整历史,查看某个页面时还要继续进入子模块。
展示页本身是 HTML、CSS 和 JavaScript 组成的静态文件。它能让人先看到页面效果,却不能证明截图中的每一个按钮都已经连上后端。项目说明里把 Android 客户端称为 Jetpack Compose 应用,把后端称为 Node.js 服务;这些信息可以和目录、依赖以及子模块指针互相对照。至于“用户会不会长期使用”“推荐是否有效”这类问题,仓库没有提供测量结果,我也不应该用想象替它回答。
后端真正做了什么
后端的 package.json 列出了 Express、Sequelize、MySQL、JWT、Multer 等依赖。源码按控制器、模型、中间件和路由分层:用户注册登录、个人资料、帖子、评论等入口分别放在对应目录,认证中间件负责读取 Bearer Token,上传中间件处理媒体文件。这种拆分不是复杂架构,却足够让课程项目里的请求路径有一个可以追踪的落点。
仓库还留下了一个很有价值的修复记录。帖子模型增加了 coverImage、mediaUrl 和 mediaType 字段,同时补了一份数据库更新脚本。实际原因是接口查询已经读取这些字段,而旧表结构还没有它们,于是详情接口会因为未知列返回 500。这个问题提醒我:模型文件、迁移脚本和部署步骤必须一起提交。只改模型不改数据库,代码在本地能启动,换一台机器就会在实际请求上失败。
后端 README 里能确认的接口也应该被当成合同来维护。例如注册和登录返回用户信息与令牌,资料接口需要认证,帖子详情还要处理媒体字段。接口文档如果只写“支持某功能”,却没有请求体、错误状态和迁移前置条件,下一次接手的人仍然得靠猜。项目越小,越应该把这些细节写在一个能随提交更新的地方。
设计文档和代码之间的距离
仓库里的 design.md 很完整,包含启动页、登录、首页、动植物内容和社区模块的章节;README 也写到了问答、关注、评论等设想。设计文档的价值在于记录当时想解决什么问题,但它不能自动变成实现清单。回看代码时,我会把每项描述分成三类:源码中能直接找到的、接口或数据库能部分支撑的、只在文档里出现的计划。
这一步看起来有点笨,却能避免项目复盘变成宣传稿。比如一个表名出现在 SQL 里,只能说明数据模型曾经被设计过,不能说明它已经在生产环境里被稳定使用;一个按钮出现在截图里,也不能说明网络失败时有完整回退。把“存在”“可运行”“被使用”分开,文章会少一些热闹的形容词,却多一层可以复查的诚实。
这次回看留下的几条原则
第一,先保存能够独立运行的最小路径。客户端、后端和数据库要有各自的启动说明,依赖版本要能重建;如果客户端是子模块,就在 README 里写清楚初始化命令和对应提交。
第二,把数据库变更当作发布的一部分。新增字段时同时提供迁移脚本、回滚方式和一次真实查询的验收结果。模型与表结构永远成对出现,不能把“本地能启动”当成完成。
第三,功能列表要标出状态。已经实现、只完成接口、只有设计稿,这三个状态的措辞应该不同。它们混在一起时,几年后的自己最容易误读,也最容易在文章里无意间夸大项目结果。
第四,给失败留位置。认证过期、数据库字段缺失、媒体地址失效,都是普通项目会遇到的事情。错误响应、日志和回退页面比一段“体验流畅”的描述更能说明工程质量。
“生灵集”没有因为这次重写变成一个更大的项目。它只是从一份容易写满的介绍,回到一组可以逐项检查的文件:客户端的入口、后端的路由、数据库的迁移,以及尚未兑现的设计。对个人项目来说,这已经是一种足够具体的成长记录。以后再打开它,我希望先看提交和测试,再决定要不要给它补一个新功能。