一家服装厂上了计件工资系统:工人在手机上按工序填件数,老板在后台点「结账」,再照着待发金额给工人转账。上线后复查,我们问了自己一个很普通的问题:已经结账的工单,工人还能不能填?
界面上的答案是不能,工人一刷新,已结账的单子就从手机上消失了。可真去试,答案是能:老板那边点了结账,工人手机上的页面还开着没刷新,改个数量点保存,照样提示成功。我们往一张已结账的单子里存了一个 888,数字实实在在写进了系统。要是老板这时正盯着屏幕上的金额转账,工人那边还在改,转出去的钱和系统里的账就对不上了。好在洞堵上的时候,这家厂还没从系统里发出过一笔工资。
看不到,不等于改不了
手机上每一次「看」和「存」,背后都是发给服务器的一个请求,服务器上接这些请求的地方叫接口。工人「能看到哪些单」和「能往哪些单里写」,是两个接口。读的那个按状态过滤了,已结账的不返回;写的那个只查数量有没有超额,一句状态检查都没有。
做的时候很容易把两者当成一回事:看不到这张单,自然就填不了。可页面只是某一刻送到手机上的快照,老板在后台点了什么,它不会自己收回去;何况任何一个登录着的人,都可以不开页面,直接往服务器发请求。界面上藏起来,拦不住绕开界面直接发来的请求;每一个接口都得在服务器上自己查一遍。
同一种病,换成了看和删
同一个月,一家广告公司的结算系统体检,也翻出这种病。开销和报销列表靠页面自己说一声「只看我的」,不说就给全表,项目经理能看到全公司的工资、房租和所有人的报销;下载票据不查归属,别人的原件照样下得走;删附件只要登录就行。正门关着,后门开着。
我们现在的规矩
服装厂那边堵严之后还返工过一次:客户点「结账」其实是想先看看这单要发多少钱,工人往往还没填完,一锁就全填不进去。于是锁点挪到了「已转账」,没发钱照样能填,谁拿到钱就对谁锁死。锁挪了位置,但始终锁在服务器上。结账到转账之间那段空档,靠下面第二条兜住:钱按转账那一刻重算,对不上就不让发。现在我们照这几条做:
- 谁此刻能碰哪些单,看和改用同一条规矩。 这条规矩只写一份,列单子时拿它筛,存数量时拿它查,不许再裂成两套。
- 动钱的那一下,把屏幕上的数一起带上。 老板点「已转账」时,他屏幕上看到的金额跟着送到服务器;服务器按此刻的数据重算,差额超过一分钱就不让发,并告诉他「你看到的是多少、现在是多少」,请他关掉重新打开再发。
- 发放要么整笔成功,要么整笔作废。 一笔工资要给哪几条记录打上「已发」是事先算好的,实际标上的条数对不上,就整笔撤回,像没点过一样;手抖连点两下,也只会成功一次。
- 每个角色能看什么,全系统只认一把尺子。 系统里有好几种角色时,台账、看板、报表,每一处查询都用同一段条件圈定范围;只读的人去改、看别家的数据、进没开通的模块,服务器一律拒绝。界面上把入口藏起来,只是顺带。
- 机器人在群里的回执默认简版。 一家给木材加工厂供料的物流公司,业务员在飞书外部群里发过磅小票,货主也在群里;回执要是带着抽成和单价,就等于当面摊给货主看。群里有谁在场系统挑不了,能挑的只有回什么:单价和金额只在单聊出现,抽成默认也不进群,带利润的查询指令只认单聊。
- 验收换上每个角色的账号,逐个接口去试。 该拒的确认真拒了,不该拦的也确认照样能过,免得堵洞时误伤正常操作。
藏起来的门,只拦得住不去推的人。

