背景
大家都可能在饭堂消费过,在饭堂人多的地方一般都是使用刷卡消费,最近还兴起了人脸支付方式消费,人脸支付嘛,新鲜事物,神奇还带点人工智能概念,而且支付确实也方便,不需要带卡刷卡,带脸就行,还不会忘带。
我要说的这件事情就发生在饭堂的人脸支付中,这个饭堂还不小,是一个大厂的饭堂,人数几千人每天上万笔消费记录(推算),金额未知(机密),整个支付过程是这样的 ,终端人脸支付设备扫描人脸,负责确权认证,通过后调用支付接口完成支付动作。
问题出在哪里?
本来在测试环境整个流程都是走通并且逻辑是对的,测试结果也是完美的,能正常消费,能正常扣费成功。然而生产环境并不是这样的,问题出现在扣费不成功,并且代码中没有抛出异常,就是update账户的时候 返回的更新成功记录是0。
账户是在事务开始的时候调出来的当前用户账户,然后udpate的条件是 当前账户的id,即是
update account set xx=xx where id=当前账户id
这条语句执行后既然返回0条记录。
经过反复日志分析和数据对比,发现了一个很隐蔽的bug,原来在查找当前账户的时候关联了另外一个表,这个表也有个字段id,因为orm框架设计不够严谨导致,把另外一个表的id赋值给了账户的id字段,问题语句是这样的
select * from account a left join user u on u.id=a.user_id where xxxxx
user表和account表都有个字段id,结果集中有2个id,然后orm映射的时候就映射错了,把user表的id映射到了account的对象中,导致了后面的安全事故。
验证了《中国机长》那句话
其实在update账户的时候,更新成功记录数为0的时候应该抛出异常,让支付事务回滚,返回消费失败。眼看这句update不可能返回0的情况,但就是发生了,印证了《中国机长》那句话,“当你认为没错误的时候,错误就会找上你”,对于程序员来说就是“当你认为没有bug的时候,bug就会找上你”。
数据恢复方案
问题找到了就需要修复bug,还有对造成的影响进行弥补。问题找到了修复bug就简单了,这里不说怎么修复bug,重点说下数据恢复方案,
首先安抚+说明情况
发现问题立马停掉服务,并且发布公告说明情况。那些员工其实还是发现了这个漏洞,只是默不吭声,默默的刷脸消费,又不扣钱,虽然不能怪他们,但是觉得中国人贪小便宜的惯性还是比较厉害的。
大家互相转告,就这样刷了十几万,饭堂老板要把我们砍了,一个问题把他们饭堂搞倒闭了,流汗...。但是情况并不会那么糟,我们有消费日志,有了这个日志老板的钱还是能要回来的。
统计消费人数和金额
通过消费日志得知消费人数和每个人的消费金额,然后汇总。
备份和数据恢复
这里的数据恢复只要恢复用户账户表即可,
第一步:备份账户数据库表
这一步必须要做的,不然造成二次事故
create table accoun_bak as select * from account
第二步:执行数据恢复脚本
这个脚本有点长,大体思想就是找到未扣费的金额,然后修改金额, 金额 = 金额-应扣金额。
日志里面最起码要有一下信息,消费金额,消费前金额,消费后金额这个三个字段,
恢复服务
数据恢复后就要测试一遍,然后恢复服务了。
总结
这次事故的数据恢复还算顺利,不算太难。但是踩过的坑不能再踩,翻过的错不能再犯,要写个博客总结下。
不要对自己的代码太自信,要反复测试和确认
涉及金额的问题代码逻辑一定要严谨,不能自以为没问题
涉及金额的问题一定要有日志,不然事故发生了没办反恢复。
最后为程序员捏把汗,程序员承担了好多高压力的工作,出现问题还得自己解决,真不容易。客户理解还好,不理解可能还各种辱骂。
加油,继续搬砖
网友评论