站长头像

清酒Blog

岁岁平,岁岁安,岁岁平安!

文章 评论 标签
19 23 8

宝贝记录:一本不会丢的成长账 > 一个写给父母的记录 App,以及它背后那条图片管线

作者头像 作者头像

清酒 / 08-12 / 0 阅读 / 编辑

宝贝记录:一本不会丢的成长账,一个写给父母的记录 App,以及它背后那条图片管线

摘要:养孩子的第一年,父母会在手机里记下海量的东西:吃了多少、睡了多久、打了什么疫苗、第一次翻身是哪天。宝贝记录就是为这件事做的——而它真正花功夫的地方,不是记录本身,是让这些数据"绝不会丢"。

一、设计立场:记录是第一位的

做宝贝记录之前,我给自己定过一条原则:这是一个记录工具,记录本身高于一切功能。

理由很简单:这类数据的性质太特殊了。工时记错了可以补,账目记错了可以调,唯独孩子的成长数据是"不可再生资源"——宝宝三个月那天第一次吃辅食的照片,那天没记,就永远没了。

所以它的第一原则是"随手、快、可靠"。父母喂奶的时候一手抱着孩子一手拿手机,换尿布的时候满手是水,深夜喂完奶困得睁不开眼——任何"多一步操作"都会让记录中断。它的一切交互都围绕这个现实设计:打开就是记录入口,点哪类进哪类,不带二级菜单,能选的不打字,能默认的不询问。

1

二、十六类记录:长出来的,不是规划出来的

目前支持十六类记录:喂养(母乳/配方奶/辅食)、睡眠、换尿布、身高体重、体温与健康、用药、疫苗接种、体检、洗澡抚触、大小便、照片日记、成长瞬间、支出记录……分类清单我不想在这里逐条念完,因为它们有一个共同点:

这十六类不是产品经理坐在办公室里规划的,是一个真实家庭用了几百天之后一点点长出来的。

最早的版本只有六七类。用起来之后发现"今天宝宝第一次翻身"这种时刻没有地方放,就有了"成长瞬间";发现体检报告上的指标总想跟上次对比,就有了"体检";发现奶粉钱也是一笔不小的开销,就有了"支出"。每一类背后都是一次真实的"这个竟然没有地方记"。

这也是它和市面育儿工具最大的差别:大部分育儿 App 的出发点是"教你养",专家内容、月龄指南、广告位排满首页;宝贝记录的出发点只有一件事——替你记住。它不教你怎么养孩子,它只保证二十年后你想找"她第一次叫妈妈是哪天"的时候,答案就在那里。

2

三、时间线:一条成长的长河

所有记录汇成一条倒序的时间线,照片、文字、数值混排。往上滑是孩子的昨天、上个月、去年——这种"翻相册式"的回看体验,是它和表格类育儿工具的另一个分野:数据不是拿来分析的,是拿来重温的。

身高体重这类数值记录做了生长曲线的可视化,相邻两次测量自动计算变化;健康记录带体温曲线;睡眠记录自动统计全天总时长。数值类的呈现都往"一眼看懂"的方向收,因为看这些的人,大概率正单手抱着孩子。

记录里最重的资产是照片。多图上传、九宫格预览、灯箱原图查看都是标配。而照片,恰恰是最容易把一个记录 App 拖垮的东西——这就引出了这个项目里工程细节最密集的部分。

3

四、一条被逼出来的图片管线

第一版上线没多久就出了问题:父母开始上传手机原片——一张十九兆的照片,列表页一次拉几十张,流量和加载时间都不可接受,流量小的套餐更是肉疼。

于是我建了一条缩略图管线。它后来成了这个项目里最值得讲的一段工程:

上传时即生成。 每次图片上传,服务端立刻生成一份最长边五百一十二像素的 JPEG 缩略图。列表和时间线永远只走缩略图,点开灯箱才加载原图。原图一个字节都不动——缩略图坏了可以随时重生成,原图只有一份,动它就是犯罪。

方向修正。 手机拍的 JPEG 带着 EXIF 旋转标记,直接缩放会得到横躺的照片。管线先读方向标记再画布,横拍竖拍都对。

透明铺白。 截图类 PNG 的透明背景在缩略图里会变黑块,缩放时统一铺白底。

内存预检——这是最痛的一课。 有段时间用户反馈"有些照片的缩略图怎么也生成不出来",查了两天才发现:一张 19MB 的高像素原图,图像库解码的瞬间要吃掉两百多兆内存,而处理进程的内存上限是一百二十八兆——进程被系统静默杀死,不报错、不告警、不留日志,缓冲区里已经生成的输出也一并丢掉。它不是崩溃给你看,它是不存在了。修复分两层:先把进程内存上限提上去,再给管线加一道预检——解码前按"宽 × 高 × 每像素字节数"估算内存预算,超了就跳过缩略图、原图兜底。宁可少一张缩略图,不能让进程无声地死。 后来所有的存量图片都有了一个幂等的回填脚本:按需补齐,跑过的不会重跑。

现在 App 里翻一百张照片和翻十张一样快,父母完全感知不到这条管线的存在。我觉得这就是它做对了的证据——基础设施的最高成就是不被注意到。

5

五、离线优先:先渲染,后刷新

记录发生在真实生活里,而真实生活里信号并不总是好的:电梯、地下室、老家的院子。所以宝贝记录是离线优先的:

  • 所有记录本地先落盘,网络怎样都不影响记录本身,网络恢复后静默补推;
  • 首页做了离线快照:启动时先把上次的画面渲染出来,数据到了再刷新——弱网环境下用户看到的是"秒开",而不是一个转圈的加载态;
  • 同步状态是可视的:什么推上去了、什么还在队列里,页面上看得见。

对父母来说这意味着一个简单的契约:点了记录,就记上了。 至于什么时候上云,那是 App 和服务器之间的事,不该让用户等。

六、同步协议:为"绝不丢"设计的六条规矩

离线累积得越多,同步时的冲突面就越大。这条同步协议是自研的,六条核心规矩:

  1. 幂等。每条记录带客户端唯一标识,服务端同一条数据推一万次也只存一次。"同一条数据重复进入"是回归测试里的必备用例;
  2. 墓碑。删除记软删除加墓碑标记,旧设备的迟到数据碰不到已删除的记录——删了就是删了,不会被"复活";
  3. 增量游标。客户端记住同步进度,服务端按游标续传,只拉变化的部分;时钟在查询之前捕获,避免"查询和计时之间发生的修改被永久跳过"这种窗口期竞态;
  4. 服务端权威。凡是两个客户端可能打架的字段,以服务端裁决为准,客户端不许单方面定案;
  5. 降级透传。接口报错时把服务端的真实错误文案透传给用户,而不是笼统一句"请求失败"——用户应该知道是密码错了还是网络断了;
  6. 会话语义分型。会话过期和凭据错误是两回事:前者静默引导重新登录,后者如实提示——最忌讳的是冷启动时无端弹一个"请求失败(401)",把用户吓一跳。

这套协议经受住了真实家庭的日常轰炸,后来被我原样复用到了后面三个产品里。工程上最好的复用不是复制代码,是把教训变成结构。

七、QQ 一键登录与一次切号事故

给祖辈输邮箱密码是一件残酷的事,所以登录做了 QQ 一键登录——这是"家里老人也能用"的前提条件,不是锦上添花。

但这里埋着一个所有接入 QQ 登录的产品都会踩的坑:QQ 的客户端 SDK 会把上次的授权凭证缓存在本机,下次调用登录时静默复用——后果是,家里的手机 QQ 切了账号,App 一键登录却还是登回上一个人。对小家庭来说这是真事故:妈妈登进去,看到的是爸爸记的账。

修复发生在每一次登录、绑定、退出之前,先主动清掉 SDK 的本地授权缓存,强制重新走一遍授权页。代价是每次登录多一次点击,换来的是"登谁是谁"的确定性。这个修复后来同步到了我所有带 QQ 登录的产品里——又一次,一个产品的教训变成了四个产品的保险。

6

八、全家共用一本账

带孩子从来不是一个人的事。宝贝记录支持家庭共享:父母、祖辈用各自的账号登录,看的是同一本成长账。谁记了什么、什么时候记的,一清二楚;数据的边界以家庭为单位隔离,别人家的账,一个字节都看不到。

App 之外还有两块:一个轻量的官网落地页负责介绍和分发,一个管理后台负责数据总览和运维。整套东西跑在我自己的云端底座上——每日加密备份、分钟级巡检、全路由冒烟,都是底座统一提供的,宝贝记录自己不用操心这些。

7

九、功能全景与技术栈

功能全景:

  • 记录——十六类记录全覆盖、混排时间线、身高体重生长曲线、体温曲线、多图与灯箱原图查看;
  • 图片——上传即缩略图管线(方向修正、透明铺白、内存预检)、存量幂等回填、原图永不改动;
  • 同步——离线优先、防抖合并推送、幂等去重、墓碑语义、增量游标、离线快照首屏秒开;
  • 家庭——多成员共享一本成长账、家庭级数据隔离;
  • 账号与分发——QQ 一键登录(含授权缓存治理)、应用内自更新、公告触达;
  • 幕后——每日加密备份、分钟级巡检、全路由冒烟,全部由云端底座统一提供。

技术栈:

层选型
运行环境Linux + Nginx + PHP 8.2
后端自研 KX-Verse 底盘(Laravel 12)
数据库/缓存MySQL / 文件缓存 + 全页缓存
App 端uni-app(Vue 3),云打包分发
图片处理GD 缩略图管线(EXIF 方向、内存预算估算)
登录QQ 互联原生登录

十、写在最后

目前它迭代到了 5.9 版本。版本号已经走过五十大关这件事本身,就是几百天真实使用的度量。

对我来说它有一个特殊身份:四个产品里,它守护的数据最"不可再生"。所以我从不讳言这个产品在技术上没什么惊艳之处——它的惊艳在于:你已经忘了它的存在,而它记得你的一切。

9