
工时记录:为"一趟一趟"的工作造的时间账本
工时记录:为"一趟一趟"的工作造的时间账本
摘要:有一类工作的计量单位不是"小时",而是"趟":几点出车、几点到站、走了哪条线、哪个站接的车。市面上的考勤工具全都按"上下班打卡"设计,对这种工作形态无能为力。所以我自己做了一个。这篇讲讲它的形态,更讲讲它为了"数据绝不能丢"付出过的代价。
一、问题:时间不是一整块,是一趟一趟的
考勤软件的默认假设是:你九点上班,六点下班,中间是连续的一块时间。
但很多真实工作不是这样的。跑运输的、跑线的、倒班的,他们的一天是离散的:出公寓、到库接车、到站接车、退乘换站、入库收车……每一段有自己的起止时刻和地点,任何一段漏记,这一趟的账就不完整,月底结算就说不清。
通用工具应付不了这种结构——它们的世界里只有"上班"和"下班"两个锚点;表格软件倒是灵活,但要求人在手机上跟 Excel 搏斗,隧道里、库房门口、深夜的驾驶室里,谁也做不到。
工时记录 App 就是为这个缝隙做的:它的一切都围绕"趟"这个最小单位设计。
二、六步打卡:把一条流程钉进界面
一趟完整的出勤被拆成六个关键节点,App 按流程顺序引导记录——出寓、接车库接、站接、退乘站换、入库、退勤。每一步一键打上当时的时间,不用手填;一趟走到了哪一步、卡了多久,列表上一目了然。
围绕"趟"的周边功能也全部按这个场景长的:
- 站名管理。常跑的站点建成字典,打卡时选择而不是输入;站名支持跨设备同步,换新手机不用重建;
- 十五个字段一趟全记录。出寓时间、接车库接、站接、退乘站换、入库、退勤,加备注、金额等辅助字段,一趟的所有维度都在,而且 App 端和服务端的字段定义逐字对齐——两头说同样的语言,同步才不会"意思走样";
- 趟次计算器。选两趟时间,自动算间隔时长,核对班表、复盘休息时间用;- 统计页。按月看趟数和时长,还有一行"开通以来累计 N 趟 · 工作 X 小时"——这一行是刻意设计的:记录越久,它越有分量。有人用它回顾一年,有人用它跟家人解释自己这一年是怎么过的;
- 深色模式全量适配。夜班的人会懂这个细节的分量——凌晨两点看手机,白花花的界面是一种暴力。
三、一次真实的数据事故
这个 App 立项时我写给自己一句话,后来成了整个项目最高优先级的约束:
功能可以有缺陷,但记录的趟、时间、站名,绝不能丢、不能重复、不能被覆盖。
这句话不是凭空写的。早期版本出过一次真实事故:某天打卡的时间没能上云,另一台设备的旧副本又同步回来盖在本地,最后云端出现重复趟。对普通应用这是个小 bug,对工时记录这是天塌——用户拿这个数据对账、算钱,一趟重复、一趟丢失,都是真金白银。
那次之后我把整条同步链路推倒重做,并且立了新的开发规矩:凡是碰数据链路的改动,必须先跑一组专门的回归用例——修改检出、推送、合并、删除墓碑,一个都不能少。新增功能可以缓,新增场景不行。
从那以后这条链路再没丢过一条记录。但我很快发现,"没丢"和"知道没丢"之间还有很远的距离——这就引出了下面这个更吓人的故事。
四、同步在生产环境坏着,而没有人知道
有一段时间,我在后台例行体检时顺手验证了一下云同步接口——结果发现工时数据的推送请求全部在服务端报错,一个能落库的都没有。
更让人后背发凉的是另一件事:这个问题已经存在了一段时间,而用户毫无感知。为什么?因为 App 的同步有本地兜底——推不上云就先留在本地,下次再试,界面上一切都好。兜底机制设计的初衷是保护数据,但它的副作用是把服务端的故障完美地掩盖了。 数据没丢,只是永远上不了云;换设备的用户会突然发现自己的历史是空的,而那时候距离最后一次成功同步,可能已经过去很久。
根因挖出来很荒诞:服务端对一个字段的校验规则引用了一个不存在的验证规则名,任何包含该字段的请求一律报错。它之所以一直没被发现,是因为那条代码路径恰好被前面的兜底"保护"着。
这个案子给我的教训比那次数据事故更深:
- 兜底是双刃剑——它保护用户,也掩护故障。所有"静默失败"的地方,都必须有另一双眼睛在盯着;
- 可靠性不能靠"用起来没发现问题",要靠主动的、端到端的验证——App 好好的不代表链路是通的;
- 自那以后,云同步的推送、拉取、幂等、墓碑每条路径都进了自动化回归,后台的体检命令也会验同步链路的活性。
现在这个 App 的同步链路是我所有产品里验证最密的一条。它配得上这个地位——毕竟它是用户的工资条。
五、同步协议的六条规矩
重做后的同步协议有六条核心设计,后来被原样复用到了我后面三个产品里:
- 本地先行。所有记录先落本地磁盘,网络状况与记录动作完全解耦——进隧道照样打卡,出来自动补推。离线是常态,不是异常;
- 防抖合并。改动后不立刻发请求,短暂静默后合并推送。狂点十下,最多两个请求;省流量也省电;
- 幂等去重。每条记录带唯一标识,服务端同一条数据推一万次也只存一次。"同一条数据重复进入"是回归测试的必备用例;
- 墓碑语义。删除记墓碑,已删除的数据不接受旧设备的迟到修改,删除语义永远优先;
- 增量游标。服务端在查询之前捕获时钟,游标续传——时钟捕获晚于查询的话,查询和计时之间的修改会被永久跳过,这是又一个真实的窗口期竞态案例;
- 合并而不是覆盖。多台设备各自记的站名做并集合并,谁加的都算数。
每一条背后都有一次教训。协议不是设计出来的,是赔偿出来的。
六、一个"定义了但没人调用"的函数
再讲一个同步链路上的小案子,因为它太典型了。
站名跨设备同步,服务端的拉取接口是写好了的——可代码审查时发现,App 里定义了这个调用,但全工程没有任何地方调用它。同步只剩"本地全量覆盖云端"一个方向。后果组合起来很惊悚:新设备登录后本地站名是空的,首次同步就把云端站名整个清空;而任何一台设备永远拉不到其他设备新加的站名。
修复并不复杂,难的是确立检查方式:现在每次大改之后都会做一遍"调用面核对"——每个接口函数,谁调用、在哪调用、什么时机调用,对不上号的就地处理。一个没人调用的函数,不是无害的,是定时炸弹。
七、后台:给管理者看的另一面
App 之外配了一个管理后台:使用人数、总趟数、今日出勤、进行中未退勤、最新版本、累计下载,六张指标卡一屏看全;近七日出勤趋势图和每个人的趟次明细单独成卡,明细的字段命名和 App 里逐字对齐——后台看到什么,就是当事人记了什么,没有二次翻译,也就没有二次失真。
八、功能全景与技术栈
功能全景:
- 记录——六步打卡流程、一趟十五个字段全维度、站名字典与跨设备并集同步;
- 同步——离线先行、防抖合并、幂等去重、墓碑语义、增量游标续传、同步状态可视化;
- 工具——趟次间隔计算器、月度统计、"开通以来累计"行、深色模式全量适配、记住登录邮箱;
- 后台——六张指标卡、近七日出勤趋势、按用户分组的趟次明细(字段与 App 逐字对齐);
- 可靠性——数据链路专项回归用例、端到端同步活性验证、应用内自更新。
技术栈:
| 层 | 选型 |
|---|---|
| 运行环境 | Linux + Nginx + PHP 8.2 |
| 后端 | 自研 KX-Verse 底盘(Laravel 12) |
| 数据库/缓存 | MySQL / 文件缓存 + 全页缓存 |
| App 端 | uni-app(Vue 3),云打包分发 |
| 同步协议 | 自研:幂等 + 墓碑 + 增量游标 + 服务端裁决 |
| 登录 | QQ 互联原生登录 |
九、写在最后
工时记录目前迭代到 1.5 版本,支持 QQ 一键登录、应用内自更新、记住登录邮箱,整套服务跑在我自己的云端底座上——每日加密备份、分钟级巡检,数据链路的每一环都在自动化的注视之下。
它是我四个产品里最"朴素"的一个:没有照片、没有 OCR、没有花哨的图表。但它也是被最严苛标准检验的一个——因为我始终记得那个立项目的下午定下的排序:
给一个司机省下的每一分钟都是小事,弄丢他的一趟记录是大事。工具的价值排序里,可靠永远排在漂亮前面。
这个 App 存在的意义,就是证明这句话。
请作者喝杯咖啡吧~
转载请注明出处,谢谢合作。
本文作者 清酒,首发于 开心公园

评论 0
友善交流,理性发言还没有评论,来说两句吧。