优惠券
管理端通过优惠券可以到达促销目的,用户通过优惠券可以抵扣商品部分花费。在一张优惠券到用户手中,首先,需要经过优惠券新增-->发放-->用户端查询-->领取或者兑换-->使用。
整个功能单独创建一个微服务。优惠券实现主要是对优惠券表的增删改。
在优惠券表中,要体现一下字段:
折扣类型 (满减、每满减、折扣、门槛);
具体折扣值;
门槛,最低消费;
最高优惠金额;
获取方式(手动、兑换码);
优惠券有效期;
开始\结束方法时间;
有效期开始\结束时间;
状态(待发放、未开始、进行中、已结束、暂停);
总发行量;
已发行量;
每个用户领取上限。
优惠券新增:
就是向数据库中插入一条优惠券记录。设置好折扣类型、获取方式、总发行量、以及每个用户限领多少。一开始状态默认是待发放。
优惠券发放:
管理端在优惠券管理中心,在相应的优惠券一栏点击发放后执行。本质上是对当前优惠券的有效期、开始\结束发放时间、有效期开始\结束时间的更新操作。发放之前,先判断当前优惠券状态是会否为暂停或者待发放,否则不能发放。发放的方式分为立刻发放和定时发放,区别点在于立刻发放没有开始发放时间。有效期限也可以设置成固定时间或者一个固定时间段。如果说领取方式是指定领取且状态为待发放,我们还要基于线程池异步生成兑换码。
优惠券查看:
用户端可以查看到所有手动领取,发放状态的优惠券信息。除了查看之外,还要知会前端,优惠券是可用、还是立即领取、亦或是已领完等信息。为了区分优惠券的归属信息,需要创建一张表记录用户和优惠券的关系。记录下用户下所有优惠券信息。
领取优惠券:
先查询优惠券,判断优惠券是否存在,存在才可以领取;再判断当前领取时间是否在优惠券的发放时间范围内,进行下一步;判断优惠券的库存是否充足,充足才能领取;最后判断优惠券的每人限领数量,超过了领取上限也是不能领取的。
如何防止优惠券超领的
不管是超人多卖还是单人超卖,本质上都是优惠券领取过程中加入的逻辑判断过多并且都是独立的代码,这部分代码不具备原子性最终导致的数据库数据和理想中产生偏差。要解决也很简单,思路就是如何将这些零散的独立代码当做一个整体运行?
就超人多卖而言,在判断完优惠券库存后,还没来得及更新入库的时候,高并发的情况下,就会出现实际发行的数量高于预定的数量。类似这种更新是导致的数据异常问题,采用乐观锁来解决,可以另外加一个版本字段,更新前判断版本是否改变,改变了就表示被其他线程操作了,本次操作失败或者重试;也可以在提交前判断发行量是否小于优惠券总量。
而单人多卖,判断完领取数量是否小于领取上限后,在向记录用优惠券信息表中插入数据时,导致的多卖问题。这里是写入操作,使用悲观锁更好。
事务失效的场景
- 事务方法不是public修饰。 Spring中声明式事务是基于AOP结合动态代理实现的,spring要求被代理方法必须是public的。(在
AbstractFallbackTransactionAttributeSource
类的computeTransactionAttribute
方法中有个判断,如果目标方法不是 public,则TransactionAttribute
返回 null,即不支持事务。)- 事务方法中的异常被捕获。Springh中的事务管理就是通过感知业务中的异常来进行回滚的,如果说,自己使用try-catch把异常捕获,或者抛出其他异常,那么spring就无法捕获异常,所以会失效。
- 事务异常类型不是RuntimeExeception。 Spring事务管理默认捕获运行时异常,如果,业务方法中抛出的异常不在其列,也会失效。(可以通过
@Transactional
注解中的rollbackFor
属性来指定异常类型 )。- 事务传播行为使用不当。 事务传播行为有7种,如果某一个事务方法调用了一个必须新事务的事务方法,上来不管是否存在事务,都会创建一个新事务,也会导致外部事务失效。
- 非事务方法调用事务方法。 在同一个类中,方法之间的调用都隐藏了this官关键字,意味着是通过当前类的对象,对于一个事务方法而言,是基于动态代理实现的,它的调用是用代理对象执行的,普通方法调用事务方法,相当于是将代理对象替换成了当前类型对象,事务管理的功能自然也会失效。(解决思想,获取代理对象,借助AspectJ来实现 )