站长头像

清酒Blog

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

文章 评论 标签
19 23 8

在哪儿:给家里所有东西一个可检索的坐标 > 空间树、防泄露二维码、条码识别与一台标签打印机

作者头像 作者头像

清酒 / 10-03 / 0 阅读 / 编辑

在哪儿:给家里所有东西一个可检索的坐标,空间树、防泄露二维码、条码识别与一台标签打印机

摘要:"那个东西在哪儿?"是这个世界上被问得最多的问题之一。在哪儿是一个家庭寻物收纳 App:给每个家建一棵空间树,给每个柜子贴一枚防泄露的二维码,给每件物品一个可以搜索、可以提醒、可以打印出来的坐标。这篇讲它的形态,也讲那些用实物换来的工程教训。

一、问题的形状

家里百分之八十的找东西时间,其实花在"它可能在哪"这个猜测上。收纳博主的解决方案是"归位习惯",但习惯救不了急救药、救不了换季被褥、救不了半年没动过的工具箱——那些"一年用两次"的东西,恰恰是最容易失踪的。

我想要的方案非常具体:家里的每件东西,都应该像数据库里的一行——有位置、有照片、可搜索、可提醒。 在哪儿就是把这套东西塞进一个 App,顺带解决录入、查找、提醒、共享四个环节的每一环。

图片位 1:App 首页(搜索框 + 统计卡 + 物品列表)
建议尺寸:1080×2340 | 备注:主界面直截,突出"搜索优先"的定位

二、空间树:三级坐标,够用且克制

位置模型是三级树:家 → 房间 → 家具。家具可以自由挂子级——五斗柜下面可以有抽屉——但整个模型刻意止步于"三级 + 家具自由层级":位置粒度超过这个限度之后,录入成本的增长会快过检索收益。做减法比做加法难,第四级位置类型是被我亲手砍掉的,砍完之后录入流程清爽了一个量级。

这棵树的全名会自动拼成路径:"我的家 / 主卧 / 五斗柜"。每件物品都挂在某个路径上,于是:

  • 一搜即得。搜索框输两个字,秒回答案——位置路径、物品名、备注全文检索;
  • 树形过滤。列表可以按位置树收窄,站在卧室门口就能看"这个房间还有什么";
  • 分类筛选。药品、食品、母婴、工具、书籍……分类随用随学:扫一个新商品,它的分类会被自动记进候选,越用越懂这个家;
  • 选位置不用滚轮。一个逐级下钻的选择器:有子级点行进级、右侧按钮直接选本级,带面包屑跳回、搜索兜底、"上次放哪"快捷入口,还支持就地新建——收纳的心理门槛,一大半在"选位置太麻烦",把这一步做顺,整个产品就顺了。
图片位 2:位置选择器(逐级下钻 + 面包屑)或空间树页面
建议尺寸:1080×2340 | 备注:位置选择器更能体现交互设计

顺带记一个小小的工程教训:这棵树最早有个"三级展不开"的毛病,排查半天,原因是构建树的递归只处理了第一层——根节点挂上了孩子,孩子自己却没有孩子。一行递归的疏漏,表现成整个功能"不能用"。树形结构的问题,十有八九在递归的边界上。

三、防泄露二维码:贴在柜子上的"钥匙"

App 的核心交互之一是扫码收纳:给柜子生成一枚二维码贴上去,扫一下就进入这个柜子——看看里面有什么、该放什么。

但二维码贴在家里,谁来扫都行——它不能泄露任何信息。这是设计这层功能时唯一的约束,方案有四条:

  • 码的实体是一枚不透明的随机令牌,不是物品编号,外人拿不到任何可推断的信息;
  • 用微信或浏览器扫,只会看到一个零信息落地页——不是家庭成员,什么都拿不到,一个字节都不输出;
  • App 内扫码才凭令牌换取目标内容:物品码显示当前位置和上次位置,空间码直接进柜内清单;
  • 码是幂等的——同一目标重复发码拿到的是同一枚,不用每次重贴;物品删除时码自动吊销。

一枚贴纸,把"数字索引"长在了物理世界上,同时不让物理世界反向突破数字边界。这个设计我很满意,因为它证明安全和易用不是 trade-off——把"给谁看"想清楚,两头都能要。

图片位 3:二维码标签页(码列表 + 标签预览)
建议尺寸:1080×1620 | 备注:码号打码

四、标签打印:所见即所得的最后一公里

二维码要贴出来才有用,所以做了标签打印:热敏标签机,蓝牙直连,支持横版竖版几种规格,主副文字、字号、二维码大小都可调。

这一段踩的坑值得单独写,因为它的教训可以泛化到所有"预览 + 输出"类功能。

第一版的实现是"两头排版":本地绘制一套逻辑负责打印,服务端渲染一套逻辑负责预览。两套排版的字号估算、换行规则、留白计算各自为政——用户拿着"预览挺好、打出来二维码压住了文字"的实物来反馈,我拿着两份代码对了一下午,把每一处不一致的默认值列成表格,修完一处又冒一处。

最终的方案是釜底抽薪:本地排版整个废弃,标签图全部由服务端生成,打印直接把那张预览图整图下发。 打印即预览,逐像素一致,从此再也没有"打出来不一样"。这次重构之后我给自己立了条铁律:

标签、票据类打印,永远整图下发,不做端上二次排版。 两套排版逻辑对齐的维护成本,远高于一次传输图片的成本——尤其当你已经修过一次之后。

主文字支持强制换行、行数按剩余高度自适应,放不下整体缩字号;二维码大小三档可调,缓存按参数指纹精确失效——同一个标签改一个字,出来的就是新图,不会吃到旧缓存。

图片位 4:标签编辑页 + 打印实物照(并排)
建议尺寸:1600×1200 | 备注:左边 App 预览截图、右边打印出来的实体标签拍照,最能打动人的一张图

五、条码识别:让录入快过犹豫

收纳体系能不能活,取决于录入成本。扫码收纳把录入压缩到几秒:商品条码一扫,自动查商品库带回名称、品牌、规格、分类、商品图,确认即入库。

这条链路下面藏着几层设计:

校验位闸门。 商品条码有标准的校验位算法,不同位制(八位、十二位、十三位、十四位)各自一套权重。扫码器经常在码没进全框时就抢先解码——半截码、错一位的码,如果没有闸门,会一路流到商品库变成"未收录",还作为负缓存写进去,污染七天内的结果。现在的闸门在结果层把非标准长度、校验位不对的码全部拦下,明确提示"条码没读全,请重新扫描"。在数据入口处设卡,比在下游每个环节收拾残局便宜得多。

双级缓存。 商品库查询是按次计费的外部服务,所以服务端有全平台共享的缓存——同一个条码全平台只扣一次费;App 本地再镜像一层,离线也能预填。正向缓存三十天,查无此码的负缓存七天。后台还有调用统计:命中、未收录、缓存命中、当日上游调用、累计费用,一屏看全——接了计费的外部服务,费用必须有仪表盘,不能靠月底账单惊吓自己。

一个"假故障"的破案。 某天用户反馈"商品库不可用,提示额度受限"。排查发现是两层事叠着:上游的额度确实用尽了——但兜底源明明返回了品牌、规格、齐全的信息,我的代码却因为"商品名为空"把它判成了"没查到"。兜底成功了,判定逻辑把它否了。修复之后我加了一条判断哲学:"查到"的标准是"有意义的字段非空",而不是"主字段非空"。 主字段缺失但其余齐全的残缺数据,比"未收录"有价值得多。

图片位 5:扫码识别结果页(商品信息自动预填)
建议尺寸:1080×2340 | 备注:商品名打码或用普通商品

六、拍文字识别:给没有条码的东西

没有条码的东西——药盒、食品袋、日化品——走另一条路:拍文字识别。拍一张照,识别引擎读出文字行,业务解析层从里面提取出有效期、生产日期、规格、厂商、分类,自动预填表单。

效期解析是这层解析的重头,容错规则一条条写出来的:

  • "有效期至某年某月"取当月月末;
  • "生产日期 + 保质期若干个月"推算到期日;
  • 紧凑的 20260205 式八位数字也能读;
  • 孤立的日期兜底识别。

分类推断也有优先级:药品批准文号的优先级最高,剂型词次之,食品词再次——识别成药品的物品,会自动归入药箱模式。跟礼簿的 OCR 一样,这条链路也是识别在本机、结果进可编辑的表单,机器打草稿,人做终审。

图片位 6:拍文字识别流程(拍照 → 预填的表单)
建议尺寸:1080×2340 | 备注:药盒是最好的示例,效期字段标红星那张

七、药箱与食品柜:效期是有安全属性的

大部分物品的"有效期"只是个信息,但药品和食品的效期是安全属性。所以它们各自有独立的页面,待遇也和普通物品不同:

  • 药箱——健康度卡一眼看到已过期、临期、低库存、在册四组数字,十种家庭常备药模板一键建档,识别出是药品的自动归位到"药箱"空间;
  • 食品柜——同款健康度视图,过期、临期、缺货一目了然;
  • 三级效期提醒——三十天、十四天、七天,越近越紧急;七天内的紧急项每天早上定时推送给全家,App 里还有临期横幅和每日一次的弹窗提醒,列表按紧急程度着色;
  • 硬性校验——药品和食品保存时效期必填,填不了就存不了,这是安全要求,不留例外;
  • 删除不产生孤儿——删掉临期物品时,它名下的提醒同步清除,"物品没了提醒还在"这种鬼故事不会发生。

家庭共享沿用邀请制:成员看同一份数据,家庭之间严格隔离;后来又支持了多家庭——同一个账号可以管自己家、爸妈家、单位库房,随时切换,不再需要"退出再加入"。

图片位 7:药箱页(健康度卡 + 临期列表)
建议尺寸:1080×2340 | 备注:体现效期分级颜色的那一屏

八、一整圈完整的工程

围绕核心功能还有一整圈,每个都是真实使用里长出来的:

  • 借出管理——谁借走了什么、什么时候借的,一个页面管清;
  • 回收站——删除可恢复,彻底删除时自动吊销对应的二维码,一键清空也支持;
  • 批量操作——物品页勾选模式,批量移位、取出、借出、删除,后台同样支持;
  • CSV 往返——导出生成真文件(带编码头防表格软件乱码),下载模板、填好、导入,位置按全路径精确匹配,导出的清单改完可以直接导回;
  • 位置动态——物品每次搬家自动留痕,位置由服务端权威记录,客户端改不了历史。

安全这层也不含糊:注册走邮箱验证码,频控按接口分别设限;数据的隔离以家庭为单位,"外人零可见"是有专门测试用例盯着的一条硬标准——跨家庭访问、越权推送、注入攻击,都在自动化安全回归的清单里。

图片位 8:批量操作模式(勾选圈 + 底部操作栏)或借出管理页
建议尺寸:1080×1620 | 备注:体现"管理感"的一屏

九、几条用实物换来的铁律

这个 App 是四个产品里交互密度最高的,踩的坑也最多。挑几条泛化价值最高的记在这里:

  • 先找交互层的逻辑循环,再怀疑引擎。 扫码功能换过两轮引擎都没修好,最后发现是"没扫到结果就自动重新拉起扫码"——用户取消又弹出,永远退不出去。换引擎救不了代码里的死循环;
  • 原生覆盖层永远不要全屏透明。 全屏透明的原生视图层会拦截一切触摸——返回键、手势全部失效,表现为"页面退不出去"。自研的相机扫码页两轮真机失败都栽在这;
  • 页面清单的第一项永远是首页。 注册新页面时插错位置,把相机页插成了启动页——App 启动直接黑屏。脚本操作清单之后,必须核对第一项;
  • 换品牌不是全局替换字符串。 连字符形式的标识符、深色模式下的选择器,都是批量替换的漏网之鱼——替换完必须双向 grep 核对副作用。

这些条目日后大概还会继续加长。它们不体面,但真实。

十、功能全景与技术栈

功能全景:

  • 索引——三级空间树、全文搜索、树形过滤、扫码自动学习分类、位置路径自动拼接;
  • 连接——防泄露二维码(不透明令牌 + 零信息落地页)、标签打印(多规格、整图所见即所得)、位置动态留痕;
  • 识别——商品条码(校验位闸门、平台+本地双级缓存)、拍文字识别(效期/生产日期/规格/分类提取与预填);
  • 照护——药箱与食品柜独立视图、三级效期提醒、每日定时全家推送、效期硬性必填、药品自动归位;
  • 管理——借出登记、回收站(含码吊销)、批量移位/取出/借出/删除、CSV 真文件导出与模板导回、多家庭切换;
  • 安全——邮箱验证码注册、分接口频控、家庭级隔离回归测试、后台条码调用费用统计。

技术栈:

层选型
运行环境Linux + Nginx + PHP 8.2
后端自研 KX-Verse 底盘(Laravel 12)
数据库/缓存MySQL / 文件缓存 + 全页缓存
App 端uni-app(Vue 3)+ 蓝牙标签机原生插件,云打包分发
二维码/标签服务端二维码生成 + 图像库标签合成(参数指纹缓存)
条码/识别商品库查询(校验位闸门 + 双级缓存)、OCR 微服务文字提取
推送厂商推送通道 + 应用内提醒双轨
登录QQ 互联原生登录、邮箱验证码注册

十一、写在最后

在哪儿目前迭代到 1.9 版本。整套服务跑在我自己的云端底座上,每日加密备份,自动更新通道全通。

回头看,它做的所有事其实只有一个母题:把物理世界的混乱,映射成数字世界的秩序;再把数字世界的秩序,以二维码和标签的形式还回物理世界。 两个方向的桥都搭好之后,"找东西"就从猜谜变成了检索。

用户不需要知道这些。用户需要知道的是:找东西的时候输两个字,答案就出来了;药快过期的时候,手机先知道。

好的工具就是这样:把复杂性全部埋进去,只把确定性露出来。