先预览再提单,为什么要这么设计?

把一个大象放进冰箱需要几个步骤?3步,那网购一个大象需要几步呢?

购买一个商品需要几步

电商业务场景中,用户下单流程是核心中的核心,用户下单流程的设计不光影响了用户的购买体验,也极大的影响系统可扩展能力。下单内容比较多,今天重点聊一下,为什么先预览再提单,在此之前先熟悉下用户网购的操作流程。

把大象放进冰箱需要 3 步,网购一个大象则需要 6 步。

步骤 系统做了什么?
1. 浏览商品列表页找到心仪的大象 Feeds流,推荐系统根据你的喜好给你推荐 大象
2. 点击进入大象详情页 查询商品中心,获取大象的详细信息
3. 点击去结算(去购买) App调用预览接口,生成购买详情、可用优惠
4. 商品预览页点击选择收货地址、优惠券 客户端记录订单信息
5. 点击去支付 触发系统提单行为,真正创建订单,锁定优惠、扣库存、15分钟支付超时Timer
6. 支付输密码 进入收银台页面,选择可用支付渠道

为了清晰的展示用户行为,以及背后的系统接口,我画了流程图如下。我发现,几个现象?

  1. 用户点击去结算,去购买按钮,实际上系统并没有真正提单。系统调用的是预览接口
  2. 预览接口预生成订单详情,用户可在预览页面修改地址和优惠信息。(但不能加购商品)
  3. 用户看到去支付,立即支付,实际系统并没有真的调支付接口,系统调用的是提交订单接口。
  4. 提单接口需要防重,防非法提单,锁定优惠,生成订单。提单成功,页面跳转支付页、收银台页面
sequenceDiagram User-)App:浏览商品列表页找到心仪的大象 User-)App: 点击进入大象商品详情页 App-)商品中心:查询大象详情 User-)App: 点击去结算(点击立即购买) App-->>User:跳转到大象预览页(提单页) App-)订单中心:预览接口生成大象订单详情 User-)App:选择收货地址,选择优惠 User-)App: 去支付 App-->>User: 跳转到支付页 App-)订单中心: 提交订单(一系列校验) User-)App: 选择支付方式,支付

你发现没有?好像用户看到的引导操作(去购买、去支付)并不会触发提单、支付等动作,而是触发操作的上一步接口。 例如 去购买触发的预览接口,去支付触发的提单接口。

小伙伴们在熟悉订单业务流程的时候要注意这一点。

点击去购买,不要真的生成订单数据

我举一个反面例子,前东家的订单系统踩了坑。用户点击去购买居然真的提交了订单,生成了订单数据。而在提单页用户可以修改订单上的优惠信息,收货地址等。用户下单前可能反复比较价格,反复衡量纠结......,在提单页和商品详情页、购物车之间反复切换,这导致了大量的提单动作,用户修改地址、选择优惠等行为也导致系统需要频繁修改订单数据,系统生成了很多无效订单。 为了减少用户在订单列表看到很多无效订单,就要取消上一笔订单,用户的切换行为给系统带来了非常大的性能压力。所幸的的是公司小,订单量不高。要不然高峰期,订单系统性能问题真的够喝一壶的。

不得不说,这真的是一个糟糕设计。我们日常沟通时也闹出很多误会。

关于提单,大家的理解就有不同,有的人认为去购买的行为就是提单,因为点完去购买真的生成了订单数据。但是有的人不那么认为。由于在生成订单数据后,才选择优惠券等行为,点击去支付,才真正锁定优惠券,负责营销的同事就认为用户点击去支付才是提单。因为这个动作真正选中地址、锁定优惠,这个接口包括提单的主要校验逻辑、锁定优惠、扣减库存等逻辑。这才是提单。各有各的道理,每次聊到提单,都要额外确认下,"你说的提单是哪个提单"。沟通成本太高了。

踩坑的原因我也有所了解,是公司第一代程序员对订单的理解浅尝辄止,不够透彻,调研不充分。看到京东、淘宝、美团等公司人家有去购买,去支付等操作流程,就真的以为 去购买就是提单,就要生成订单数据? 实际大错特错。

订单的提单接口一定要在用户选中优惠、选中地址等信息后,订单的商品信息、优惠信息、收货地址等板上钉钉以后,才能调用订单接口锁定资源(优惠券、库存),才能真正生成订单数据。

用户在提单页或者叫订单预览页 可选择优惠、选择地址。确定后,用户点击去支付,系统提交订单,唤起收银台。这才是正确的交互流程。

用户进入预览页不生成订单数据可以减少很多无效订单数据,那假如用户重复提单怎么办?

预览如何防止重复提单

防止用户重复提单,前端可以通过防抖、置灰等操作有效避免用户高频率点击提单按钮导致的重复提单。此外服务端也要通过系统设计,避免用户重复提单。我先介绍预览接口如何防重复提单,再介绍其他方案。

预览接口除了返回订单预览页展示给用户的商品、优惠信息,还可以返回给前端一个预览Token,唯一ID。前端调用提单时,需要指定预览Token。服务端校验预览Token是否存在,不存在则返回提单失败,前端重新刷新页面,重新预览或者返回商品详情页。

服务端要保证查询预览Token是否存在和删除预览Token操作的原子性。由于预览接口的并发量高于提单,需要保证预览接口的性能,可以考虑使用Redis方案,且无需存储数据库。Redis集群采用主从方案,可保证数据丢失仅仅在秒级别。假设Redis主节点挂掉,也只会丢失几秒的预览Token,只影响几秒内的提单成功率。所以预览Token校验使用Redis即可。Redis del 命令可以返回是否删除成功,能用DEL命令,直接删除Token吗?

超时场景不好处理,DEL命令如果调用超时就无法确定预览Token是否存在。重试删除也无法知道,因为超时的请求在Redis端可能删除成功,也可能预览Token不存在。所以重试也没用。

似乎DEL命令不合适,但是实际上Redis超时概率很低,就我们公司业务场景,分布式锁Redis加锁超时的概率低于0.0004% ,概率很低。可以容忍DEL超时场景导致的提单失败。

所以通过预览接口生成预览Token,可以避免重复提单的问题。还有其他方案吗?

使用购买信息生成唯一键防重复提单

用户在极短时间内提交订单,势必订单信息是相同的。例如同一个商品,购买数量相同等,在短时间内提交,被视为重复提交,可以阻断提单。

例如使用userId+商品Id+购买数量,作为Redis Key,提单时尝试使用 set key1 value1 nx ex 10"加锁"。 这个方案和分布式锁类似,提单瞬间先尝试加锁,加不上锁,则说明重复提单。不同的是,分布式锁是被释放的,此处的锁,可以不用释放,只需要配置超时时间即可。例如用户5s内对同一个商品购买被视为重复提单,那么超时时间配置5s即可。

想象下如果加锁超时,如何处理呢?在我的文章中# 【千万级日订单系统】分布式锁翻车了... 解释了,超时可以尝试get 查询,是否加锁成功。但此处因为超时时间只有10s,所以返回提单失败,用户再次提单时,大概率锁已经超时被释放。用户的再次提单不会被阻断。所以场景不同,方案设计也不同。

这个方案不需要预览接口预生成唯一键,使用业务购买信息进行校验,也是可以的,似乎方案还更简单,但是后续我们会看到使用订单Token扩展性更强,可以基于预览Token做更多的事情。

如果用户长时间停留在某个页面,为了避免订单信息变化,提单失败,如何处理呢?

如果用户停留预览页面过久怎么办

用户长时间停留在订单预览页,库存信息、营销活动信息、优惠券生效期等各种数据发生变化。提单可能就会失败,如何避免预览页长时间停留后的提单呢?

只需要配置预览Token Redis key的超时时间即可。例如10分钟。

如果用户篡改提单信息呢?

预览接口是用户从商品详情、购物车等页面跳转到提单页面时要调用的接口。预览接口从前端接收用户购买的商品信息展示在订单预览页,查询用户优惠资源供用户选择,甚至预览接口会给用户推荐一些加购商品,例如外卖会员卡加量包等。总之提单接口需要的数据都应该通过预览查询出来的。(新增地址除外)为的就是用户提单的数据是基本准确的。

那万一用户提单的数据不准确怎么办?例如黑产通过刷提单接口,选中了一个 非法商品提交。举个例子,假如运营在不同地区投放的会员卡商品不同,例如卡权益不同,甚至价格不同,一般情况下,用户看不到这个商品,自然无法提单购买。

但是用户如果通过修改Http报文,例如在浏览器修改购买商品id,提交订单,伪造请求呢?

有的小伙伴可能会想,提单接口校验一下订单数据呗,重新校验下用户是否应该看见这个商品。 当然这个方案是可以的,但是提单接口的工作量和耗时将大大增加。有没有更加简单的方案呢?

如果预览接口返回的关键信息,例如营销活动信息、优惠券信息、商品购买信息等关键数据 使用MD5加密呢?

  1. 预览接口对关键信息加密生成预览摘要,存储在Redis 中,Key为预览Token。Value为MD5值
  2. 提单接口计算关键信息的摘要。使用这个摘要和预览Token关联的摘要对比是否一致。不一致,说明订单信息被篡改了。

还记得使用UserId+商品Id生成Redis Key防止用户重复提单的方案吗? 假设用户可以通过购物车购买,一次性购买10个商品Id,这个Redis key得拼接多长啊!!是否可考虑使用Md5呢?是可以的

在这个方案中,使用MD5计算购买的商品信息等摘要。无论多少个商品ID,也不用担心长度问题,因为MD5 生成的摘要长度32位。并且不同商品列表计算MD5,出现碰撞的概率极低极低(可自行查阅)。

不要高兴太早。计算订单信息摘要,需要如下校验方式。

  1. 商品Id排序。商品Id+购买数量拼接成字符串后参与结算摘要
  2. 如果预览接口返回营销活动数据,也可以计算摘要。提单时摘要数据一致说明数据没有被篡改,再校验用户选中的营销活动id是否在有效营销活动id列表中。就可以校验用户是否伪造营销活动数据。

通过简单的摘要计算方式,可以简化一部分订单校验逻辑。但由于营销活动、商品存在时效性问题,可能还是需要订单校验时间等关键数据。计算购买信息MD5摘要数据,可以有效拦截篡改数据的行为。如果能在提单接口返回数据被篡改等响应结果,能够让黑产意识到"我们是有防数据被篡改能力的,你不要浪费精力刷接口了!"。

但是预览接口如何防止伪造数据呢?

预览接口作为C端重要的查询接口,理应对用户行为进行合法性校验,例如如果商品信息存在可见性问题,就应该校验是否可见。所以订单预览接口需要查询商品营销等下游数据,保证用户预览行为的合法性。

订单提单接口由于存在大量的业务逻辑,包括生成订单数据、锁定优惠、扣减库存等大量耗时较高的写流程,所以如何简化提单接口的流程至关重要。可以考虑把一部分合法性校验放到预览环节。这样不但提单接口耗时降下来,而且提单的业务逻辑也更加简单。

总结

  1. 预览Token 服务端生成,提单前端透传,可有效防止重复提单
  2. 预览Token+ 超时时间,可防止用户方式长时间停留预览页后提单。
  3. 预览Token+ 订单重要数据MD5加密,防止提单接口数据被篡改
  4. 提单接口的一部分数据合法性校验,可以前置到预览环节!!!降低提单接口耗时和简化流程。

最后一个问题? 为什么拼多多上买一个大象只需要三步,你猜到拼多多合并哪几步了吗?

相关推荐
九河云2 小时前
数字化转型中的网络安全风险与零信任架构实践
运维·科技·安全·web安全·架构
木木子99992 小时前
业务架构、应用架构、数据架构、技术架构
java·开发语言·架构
鼓掌MVP5 小时前
Java框架的发展历程体现了软件工程思想的持续进化
java·spring·架构
鬼火儿6 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
cr7xin6 小时前
缓存三大问题及解决方案
redis·后端·缓存
小马哥编程6 小时前
【软考架构】案例分析-Web应用设计(应用服务器概念)
前端·架构
yoke菜籽6 小时前
面试150——字典树
面试·职场和发展
花姐夫Jun7 小时前
在 Ubuntu ARM 架构系统中安装并使用花生壳实现内网穿透
arm开发·ubuntu·架构
间彧7 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧8 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端