从零到壹搭建一个商城架构--订单服务

如果想了解其他内容,请点击这里查看目录

1、订单中心

电商系统涉及到3流,分别是信息流,资金流,物流。而订单系统作为中枢将三者有机的集合起来。

订单模块是电商系统的枢纽,在订单这个环节上需求获取多个模块的数据和信息,同时对这些信息进行加工处理后流向下一个环节,这一系列就构成了订单的信息流通。

1.1、订单构成

在这里插入图片描述

1.2、用户信息

用户信息包括用户账号、用户等级、用户的收货地址、收货人、收货人电话等组成,用户账号需要绑定手机号码,但是用户绑定的手机号码不一定是收货信息上的电话。用户可以添加多个收货信息,用户等级信息可以用来和促销系统进行匹配,获取商品折扣,同时用户等级还可以获取积分的奖励等

1.3、订单基础信息

订单基础信息是订单流转的核心,其包括订单类型、父/子订单、订单编号、订单状态、订单流转的时间等。

  • 订单类型包括实体商品订单和虚拟订单商品等,这个根据商城商品和服务类型进行区分。
  • 同时订单都需要做父子订单处理,之前在初创公司一直只有一个订单,没有做父子订单,处理后期需要进行拆单的时候就比较麻烦,尤其是多商户商场,和不同仓库商品的时候,父子订单就是为后期做拆单准备的。
  • 订单编号不多说了,需要强调的一点是父子订单都需要有订单号,需要完善的时候可以对订单号的每个字段进行统一定义和诠释。
  • 订单状态记录订单每次流转过程,后面会对订单状态进行单独说明。
  • 订单流转时间需要记录下单时间、支付时间、发货时间、结束时间/关闭时间等等。
1.4、商品信息

商品信息从商品库中获取商品的SKU信息、图片、名称、属性规格、商品单价、商户信息等,从用户下单行为记录的用户下单数量,商品合计价格等。

1.5、优惠信息

优惠信息记录用户参与的优惠活动,包括优惠促销活动,比如满减、满赠、秒杀等,用户使用的优惠劵信息,优惠劵满足条件的优惠劵需要默认展示出来,具体方式已在之前的优惠劵篇做过详细介绍,另外还虚拟币抵扣信息等进行记录。

为什么把优惠劵信息单独拿出来而不放在支付信息里面呢?

因为优惠信息只是记录用户使用的条目,而支付信息需要加入数据进行计算,所以作为区分。

1.6、支付信息
  • 支付流水单号,这个流水单号是在唤起网关支付后支付通道返回给电子商业平台的支付流水号,财务通过订单号和流水号与支付通道进行对账使用。

  • 支付方式用户使用的支付方式,比如微信支付、支付宝支付、钱包支付、快捷支付等。支付方式有时候可能有两个—余额支付+第三方支付

  • 商品总金额,每个商品加总后的金额,运费,物流产生的费用;有汇总金额,包括促销活动的优惠劵金额,优惠劵优惠金额,虚拟积分或者虚拟币抵扣金额,会员折扣的金额等之和;实付金额,用户实际需要支付的金额

    用户实付金额=商品总金额+运费-优惠总金额

1.7、物流信息

物流信息包括配送方式,物流公司,物流单号,物流状态,物流状态可以通过第三方接口来获取和向用户展示物流每个状态节点。

2、订单状态
2.1、待付款

用户提交订单后,订单进行预下单,目前主流电商网站都会唤起支付,便于用户快速完成支付,需要注意的是待付款状态下可以对库存进行锁定,锁定库存需要配置支付超时时间,超时后将自动去掉订单,订单变更关闭状态。

2.2、已付款/待发货

用户完成订单支付,订单系统需要记录支付时间,支付流水单号便于对账,订单下放到WMS系统,仓库进行调拨、配货、分拣、出库等操作。

2.3、待收货/已发货

仓储将商品出库后,订单进入物流环节,订单系统需要同步物流信息,便于用户实时知悉物品物流状态

2.4、已完成

用户确认收货后,订单交易完成后,后续支付侧进行结算,如果订单存在问题进入售后状态

2.5、已取消

付款之前取消订单,包括超时未付款或用户取消订单都会昌盛这种订单状态。

2.6、售后

用户在付款后申请退款,或商家发货后用户申请换货。

售后也同样存在各种状态,当发起售后申请后生成售后订单,售后订单状态为待审核,等待商家审核,商家审核通过后订单状态变更为待退货,带灯用户将商品寄回,商家收货后订单状态变更为待退款状态,退款到用户原账号后订单状态变更为售后成功状态。

3、订单流程

订单流程是指从订单产生到完成整个流转的过程从而形成了一套标准流程规则。而不同的产品类型或业务类型在系统中的流程会千变万化,比如上面提到的线上事务订单和虚拟订单的流程,线上实物订单与O2O订单等,所以需要根据不同的类型进行构建订单流程。

不管类型如何订单都包括正向流程和逆向流程,对应的场景就是购买商品和退货流程,正向流程就是一个正常的网购步骤:订单生成–》支付订单–》卖家发货–》确认收货–》交易成功。而每个步骤的背后,订单是如何在多系统之间交互流转的,可概括如下图:
在这里插入图片描述

3.1、订单创建于支付
  • 订单创建前需要预览订单,选择收货信息等
  • 订单创建需要锁定库存,库存有才可创建,否则不能创建
  • 订单创建后超时未支付需要解锁库存
  • 支付成功后,需要进行拆单,根据商品打包方式,所在仓库,物流等进行拆单
  • 支付的每笔流水都需要记录,以待查账
  • 订单创建,支付成功等状态都需要给MQ发送消息,方便其他系统感知订阅
3.2、逆向流程
  • 修改订单,用户没有提交订单,可以对订单一些信息进行修改,比如配送信息,优惠信息,及其他一些订单可修改范围的内容,此时只需要对数据进行变革即可。
  • 订单取消,用户主动取消订单和用户超时未支付,两种情况下订单都会取消订单,而超时情况是系统自动关闭订单,所以在订单支付的响应机制上面要做支付的限时处理,尤其是在前面说的下单减库存的情形下面,可以保证快三释放库存。另外需要处理的是促销优惠中使用的优惠劵,权益等视平台规则,进行相应的补回给用户。
  • 退款,在待发货订单状态下取消订单时,分为缺货退款和用户申请退款。如果是全部退款规则订单更新关闭状态,若只是做部分退款规则订单仍需进行,同时生成一条退款的售后订单,走退款流程。退款金额需原路返回用户账号。
  • 发货后的退款,发生在仓储货物配送,在配送过程中商品遗失,用户拒收,用户收货后对商品不满意,这样情况下用户发起退款的售后述求后,需要商户进行退款的审核,双方达成一致后,系统更新退款状态,对订单进行退款操作,金额原路返回用户的账号,同时关闭原订单数据。仅退款情况下暂不考虑仓库系统变化。如果发生双方协调不一致情况下,可以申请平台客服介入。在退款订单商户不处理的情况下,系统需要做限时判断,比如5天商户不处理,退款单自动变更为退款。

遇到的问题

  • Feign远程调用丢失请求头问题

在这里插入图片描述

  • Feign异步情况丢失上下文问题

在这里插入图片描述

4、接口幂等性
4.1、什么是幂等性

接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用,比如说支付场景,用户购买了商品支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额发现多扣钱了,流水记录也变成了两条,这就没有保证接口的幂等性。

4.2、哪些情况需要防止

用户多次点击按钮

用户页面回退再次提交

微服务互相调用,由于网络问题,导致请求失败,feign触发重试机制

其他业务情况

4.3、什么情况下需要幂等性

以SQL为例,有些操作是天然的幂等的。

SELECT * FROM table WHERE id=?,无论执行多少次都不会改变状态,是天然的幂等。

UPDATE table SET col1=1 WHERE col2=2,无论执行成功多少次状态都是一致的,也是幂等操作。

INSERT INTO user(userId,name) VALUES(1,‘a’),如userId为唯一主键,即重复操作上面的业务,只会插入一条用户数据,具备幂等性。


UPDATE table SET col1=col1+1 WHERE col2=2,每次执行的结果都会发生变化,不是幂等的。

INSERT INTO user(userId,name) VALUES(1,‘a’) 如userId不是主键,可以重复,那上面业务多次操作,数据都会新增多条,不具备幂等性。

4.4、幂等解决方案
  • token机制

    • 用户服务端提供了发送token的接口,我们在分析业务的时候,哪些业务是存在幂等问题的,就必须在执行业务之前,先去获取token,服务器会把token保存到redis中。
    • 然后调用业务接口请求时,把token携带过去,一般放在请求头部。
    • 服务器判断token是否存在redis中,存在表示第一次请求,然后删除token继续执行业务。
    • 国务判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执行。

    危险性:

    • 先删除token还是后删除token

      • 先删除可能导致,业务确实还没有执行,重试还带上之前的token,由于防重设计导致,请求还是不能执行。
      • 后珊瑚可能导致,业务处理成功,但是服务闪断,出现超时,没有删除token,别人继续重试,导致业务被执行两遍。
      • 我们最好设计为先删除token,如果业务调用失败,就重新获取token再次请求
    • token获取、比较和删除必须是原子性的

      • redis.get(token)、token.equals、redis.del(token)如果这两个操作不是原子性的,可能导致,高并发下,都get到同样的数据,判断成功,继续业务并发执行

      • 可以在redis使用lua脚本完成这个操作

        if redis.call(‘get’,KEYS[1] == ARGV[1]) then return redis.call(‘del’,KEYS[1]) else return 0 end

  • 各种锁机制

    • 数据库悲观锁

      select * from table where id=1 for update;

      悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,需要根据实际情况使用。另外要注意的是,id字段一定是主键或者唯一索引,不然可能造成锁表的结果,处理起来会非常麻烦。

    • 数据库乐观锁

      这种方法适合在更新的场景中

      update t_goods set count=count-1,version=version+1 where good_id=2 and version=1

      根据version版本,也就是在操作库存前现货区当前商品的version版本号,然后操作的时候带上version此版本号,我们梳理下,我们第一次操作库存时,得到version为1,调用库存服务version变成了2,但返回给订单服务出现了问题,订单服务又一次发起调用库存服务,当订单服务的version还是1,在执行上面的sql语句时,就不会执行,因为version已经变为了2了,where 条件就不成立了。这样就保证了不管调用几次,只会真正的处理一次。

      乐观锁主要适用于处理读多写少的问题

    • 业务层分布式锁

      如果多个机器可能在同一时间同时处理相同的数据,比如多台机器定时任务都拿到了相同数据处理,我们就可以加分布式锁,锁定此数据,处理完后释放锁,获取到锁的必须先判断这个数据是否被处理过。

  • 各种唯一约束

    • 数据库唯一约束

      插入数据,应该按照唯一索引进行插入,比如订单号,相同的订单就不可能有两条记录插入。我们在数据库层方面防止重复。

      这个机制是利用了数据库的主键唯一约束的特性,解决了在insert场景时幂等性问题,但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键。

      如果是分布式场景下,路由规则要保证相同请求下,落地在同一个数据库和同一个表中,要不然数据库主键约束就不起效果了,因为是不同的数据库和表主键不相关。

    • redis set 防重

      很多数据粗腰处理,只能被处理一次,比如我们可以计算数据的MD5将其放入redis的set,每次处理数据,先看这个MD5是否存在,存在就不处理。

  • 防重表

    使用订单号orderNo做为去重的唯一索引,把唯一索引插入去重表,在进行业务操作,且他们在同一个食物中,这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等性问题,这里要注意的是,去重表和业务表应该在同一库中,这样就保证了在同一个事务,即使业务操作失败了,也会把去重表的数据回滚。这个很好的保证了数据一致性。

    之前说的redis防重也算。

  • 全局请求唯一id

    调用接口时,生成一个唯一id,redis将数据保存到集合中(去重),存在即处理过。可以使用nginx设置一个请求的唯一id。

    proxy_set_header X-Request-id $request_id

5、订单确认页流程

在这里插入图片描述

在这里插入图片描述

6、库存锁定流程

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

火柴有猿

您的鼓励,将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值