- 已经基于Plan_txtMsg给用户发送了消息,但pushmsgid的值并没有回填
- 因为pushmsgid的值没有回填,理论上会有至多三次的发送,但是消息重复一直发送了
问题排查过程
- 了解发送业务,搞清楚了发送都会走一个统一入口Plan_txtMsg实体的send方法
- 进入Plan_txtMsg实体,阅读send方法
- 发现了bug表现2的原因:fixPushMsgId需要传入的参数是PushMsg实体,但是在发送失败的调用中传入的参数却是数字
- 继续阅读代码,明确表现1的问题就是没有返回PushMsg实体,但是层层追代码,感觉PushMsg实体应该是返回的
- 对send方法各条件分支打log,并修复了表现2
- 线上监控log,发现log没有打出来。排查后发现cron_plan_txtmsg.php脚本并未开启xworklog日志的刷新
- 开启后再次上线。继续监控线上日志,得到了我们之前打的日志。发现的确有PushMsg实体不存在的情况
- 日志坚定了我把问题定位到没有返回PushMsg实体的决心。也就是把问题定位到了sendTxtMsgToPatientByAuditor方法
public static function sendTxtMsgToPatientByAuditor(Patient $patient, Auditor $auditor, $content, $appendarr = array()) { $wxusers = WxUserDao::getListByPatient($patient); $pushmsg = null; $appendarr[‘unitid‘] = Pipe::getUnitidMD5($patient->id); foreach ($wxusers as $wxuser) { $pushmsg = self::sendTxtMsgToWxUserByAuditor($wxuser, $auditor, $content, $appendarr) ?? $pushmsg; } return $pushmsg; } public static function sendTxtMsgToWxUserByAuditor(WxUser $wxuser, Auditor $auditor, $content, $appendarr = array()) { if ($wxuser->isFromMiniProgram()) { return false; } $appendarr = self::setSendByObj($auditor, $appendarr); return WechatMsg::sendmsg2wxuser($wxuser, $content, $appendarr); }
- 查看上述代码发现在foreach处的诡异,推断最后一次foreach使$pushmsg的值为空了
- 考虑到这块代码比较底层,很少有人改。恰好昨天添加了一处isFromMiniProgram的判断,发现问题就出在这里
- isFromMiniProgram为真时返回了false,??语法糖操作符当前面是null时才会赋值其后面的值。所以当返回值是false时??并未起到赋值的作用,即最终$pushmsg == false
思考收获
- 之后如何避免类似隐性Bug的发生?
- 对核心基础功能引入单元测试,之后可以裹着发布系统玩
- code review升级。对于底层代码,实现者对review者讲解业务逻辑,及实现细节。review者逐行看代码
- 实现者必须写测试用例,哪怕很简单
- 排查bug的思考抓手
- 了解业务
- 查日志
- 缩小问题范围
- 有时候『笨』代码可能更可靠
public static function sendTxtMsgToPatientByAuditor(Patient $patient, Auditor $auditor, $content, $appendarr = array()) { $wxusers = WxUserDao::getListByPatient($patient); $pushmsg = null; $appendarr[‘unitid‘] = Pipe::getUnitidMD5($patient->id); foreach ($wxusers as $wxuser) { $the_pushmsg = self::sendTxtMsgToWxUserByAuditor($wxuser, $auditor, $content, $appendarr); if ($the_pushmsg instanceof PushMsg) { $pushmsg = $the_pushmsg; } } return $pushmsg; } public static function sendTxtMsgToPatientByAuditor(Patient $patient, Auditor $auditor, $content, $appendarr = array()) { $wxusers = WxUserDao::getListByPatient($patient); $pushmsg = null; $appendarr[‘unitid‘] = Pipe::getUnitidMD5($patient->id); foreach ($wxusers as $wxuser) { $pushmsg = self::sendTxtMsgToWxUserByAuditor($wxuser, $auditor, $content, $appendarr) ?? $pushmsg; } return $pushmsg; }
- ??操作符,当是null时才触发其后面的赋值操作。是false时不触发