兄弟,这就是你写的收货地址接口?
昨天晚上加班都快十一点了,突然我们组那个小李发微信问我,“哥,你帮我看看收货地址那个接口呗,总感觉有点问题…” 我说你这不是要下班了嘛,非得拉着我review。结果一看,真有点意思。
收货地址“唯一默认”这个事
其实场景也简单,就是用户可以新增多个收货地址嘛,肯定只能有一个是默认对吧?然后小李他写的接口逻辑是这样的,前端传个 default_flag=1 就把这个地址设为默认。问题来了,我就随口问了句,“你确定用户表里永远只有一个默认地址?”
小李说“嗯…这个…应该只有一个吧,反正我每次加新的,前端都传这个flag…那应该没问题?” 我说你等等,Postman你点开调试下,你自己加三条全都勾默认,你看看库里现在几个是默认。结果一查…嗯,俩,甚至三个都变成默认了。
你以为你控制了,实际上是漏了
我跟他说,这种默认逻辑啊,不能完全信任前端传啥你就信啥,你得先把自己这边逻辑兜住,比如每次新增或者修改默认地址的时候,后台要主动把用户下其他地址的默认标志都置0。 你要是直接插入,没加任何处理,或者只是“有则改,无则插”,那肯定翻车——一天有十万用户加地址,就有一万个人遇到收货列表里有俩默认,哪个都点不下去。
我当时脑袋都大了,就那会公司楼下风贼大,我边抽烟边跟他复盘。他那代码大概是:
publicvoidaddAddress(Address address){
// 直接插入,没有处理其他默认
addressMapper.insert(address);
}
你说这样操作,是不是分分钟炸锅?你前端传啥就信啥,万一他前端出bug,后端直接GG。
改了之后是什么样
后来让他改成每次“设为默认”的时候,先查一下当前用户的所有收货地址,把别的全都default_flag=0,只保留这条设成1。 其实代码也不难,举个栗子:
@Transactional
publicvoidaddOrUpdateAddress(Address address){
if (address.getDefaultFlag() == 1) {
// 先把当前用户的所有默认都设为0
addressMapper.resetDefault(address.getUserId());
}
if (address.getId() == null) {
addressMapper.insert(address);
} else {
addressMapper.update(address);
}
}
SQL里就直接来一句:
UPDATE address SET default_flag=0WHERE user_id=#{userId} AND default_flag=1
小李最开始还想偷懒,他说“能不能我只管新增的时候置一下,改的时候不处理。”我说你别闹,你每种操作都要兜一遍逻辑——新增、修改、批量导入,哪个场景都要考虑,用户不是总规规矩矩按你设计的来用。
说句实在话,这个其实就是典型的“幂等+约束”问题,数据库你也可以加个唯一索引,比如(user_id, default_flag=1)唯一,这样强行兜底。 但MySQL还真不支持这种bool值唯一组合得太好用(你得用点骚操作,比如写触发器,或者用部分唯一索引啥的),很多人干脆就在业务层写死:
// 推荐加个联合唯一索引兜底
CREATE UNIQUE INDEX idx_user_default_addr
ON address(user_id, default_flag)
WHERE default_flag=1;
但别忘了,MySQL8.0.13以后才支持带WHERE条件的唯一索引,低版本还得自己写触发器。你们公司表结构如果很老,建议还是业务逻辑多加几道保险。
这事儿其实不是没见过,前年有个用户反馈,说自己收货页面有两个默认地址,App都卡死。我扒日志一看,果然有人疯狂点新增,结果后台没控制,俩默认地址都出来了。 那会紧急上线补救,差点老板给我扣工资,所以现在看到类似逻辑我都会提醒,真别偷懒。
除了新增,用户也可以“编辑”地址嘛,他可能把不是默认的改成默认,这时候一样要把原有的默认清掉。 所以建议代码结构统一下,新增和编辑都走一套处理逻辑,别拆成俩服务。甚至你可以这样写:
@Transactional
publicvoidsaveAddress(Address address){
if (address.getDefaultFlag() == 1) {
addressMapper.resetDefault(address.getUserId());
}
if (address.getId() == null) {
addressMapper.insert(address);
} else {
addressMapper.updateById(address);
}
}
你看,这样无论新增还是改,都不用担心多出来默认。
这不是小李写完代码又拿给我看嘛,功能基本都过了。但我还提醒他几点:
多线程问题假如高并发下有两个请求同时设置默认,可能会有竞态条件。最好加个行锁或者乐观锁,保证一次只有一个“默认”会被写进库。 返回数据的幂等性前端刷新列表的时候,必须后端保证只查出来一个默认,否则展示出问题。 接口安全校验防止用户改别人家的地址,多做一步用户权限校验。 异常兜底万一出现多个默认,定期有个任务检查下,主动修复,不然总有漏网之鱼。
还有就是,很多小厂只管接口实现,不管用户体验。其实加个小细节——比如用户新增默认时,自动把其他设为非默认,前端也要给点提示,别让用户看到俩“默认地址”,否则再完善的后端都救不了你前端UI翻车。
反正就是这种事,真不是代码多高级,就是基本功。你以为自己做得够严谨了,其实现场一测全漏了。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html