昨天晚上加班那会儿,已经十一点多了,楼下都没什么人了,我正好在那抽烟顺便刷了下群消息,结果公司群里突然小李问我“收货地址接口是不是你刚才提的bug啊?”我说咋了,他说检查代码的时候,发现前端传default_flag=1之后,数据库里怎么会出现两个默认地址?他还挺懵,说他理解是只要前端传了1,就直接给那条设成默认的,其他不用管。你说这不就出事儿了嘛。
其实这事儿特别常见,尤其那种电商项目,用户一个账号最多只能有一个默认地址嘛,你不能让他有俩,对吧?我当时让小李用Postman随便加了几条地址,然后查数据库,果然能查出俩default_flag=1。我说你这个逻辑没加限制吧?他说“不是,前端已经传了参数啊。”我说你是不是只在意前端传啥了,没管数据库实际存了几个默认的?然后他才反应过来,好像确实是。
后来让他改了,几个小时后又给我看一遍代码,基本功能没啥毛病了,就是想让我再看看还能怎么优化优化。其实这类逻辑写多了,大家都知道,关键是你后端得兜底,前端传啥都不保险,你不能让客户端来控制“只有一个默认”的规则,这种必须写死在服务端。
讲讲这个接口咋写的
假设你就是用Spring Boot写的,常规流程:新增或者更新收货地址的时候,判断一下default_flag是不是1,如果是,就把这个用户下面所有地址的default_flag都先清零,然后再把当前这条设成1。
原来小李那代码,大概长这样(当然是有问题的):
if (address.getDefaultFlag() == 1) {
// 只给当前地址设默认,没管其他
address.setDefaultFlag(1);
} else {
address.setDefaultFlag(0);
}
addressRepository.save(address);
你看这样一搞,数据库里可能N条都是默认。
后来我让他改成这样:
if (address.getDefaultFlag() == 1) {
// 先把当前用户所有地址的 default_flag 都设为0
addressRepository.resetDefaultFlag(userId);
// 然后当前这条设为默认
address.setDefaultFlag(1);
} else {
address.setDefaultFlag(0);
}
addressRepository.save(address);
resetDefaultFlag其实就是一句SQL:
@Modifying
@Query("update Address a set a.defaultFlag = 0 where a.userId = :userId")
void resetDefaultFlag(@Param("userId") Long userId);
就这么简单,但很多人第一反应还真不是这么写的。
其实你要说代码还有啥提升空间,我觉得可以多考虑下事务安全。像这种批量update再insert/update当前那条,最好包一层事务,Spring直接加个@Transactional就完事了,否则你要是两步操作间服务器宕机,可能又出幺蛾子。
比如这样:
@Transactional
public void saveAddress(Address address, Long userId) {
if (address.getDefaultFlag() == 1) {
addressRepository.resetDefaultFlag(userId);
address.setDefaultFlag(1);
} else {
address.setDefaultFlag(0);
}
addressRepository.save(address);
}
还有就是你新建地址的时候,用户如果一条都没有,那应该自动设成默认。这个逻辑得加上:
long count = addressRepository.countByUserId(userId);
if (count == 0) {
address.setDefaultFlag(1);
}
别的也没啥了,唯一要吐槽的就是大家别偷懒,别老想着让前端兜底,数据一致性还是得靠服务端。小李也是年轻,学费交得有点多。
我说你们遇到这种情况会咋处理?还有什么骚操作没?