一、优惠券使用流程
分为手动领取、兑换码领取两种方式
1、手动领取
流程:
1、查询优惠券
2、校验使用时间
3、校验库存
4、校验领取数量
5、更新发放数量
6、新增用户券
前面四步我们需要频繁的访问数据库,这样对数据造成过载。我们可以将前四步设计成操作redis、将优惠券的信息,使用redis的hash类型,存储到redis缓存数据库里面。
2、兑换码领取
兑换码,我们要防止被用户猜出来,和高刷、防止恶意攻击,要数据量大、不可重复兑换、高效。
这一步我们采用base32编码,和借鉴jwt令牌的格式。生成优惠券兑换码。
首先,先知道什么是base32编码。base32编码就是将一个50位的二进制数、每5位对应一个字符,(5个1最大就是32)这样就能得到10位的优惠券兑换码。
兑换码:
兑换码由三部分组成:
第一部分称为序列号(32位),由id自增生成的十进制整数,我们把这个数转换成二进制、截取最后的14位、高位补0。
第二部分称为新鲜值(4位),优惠券的id,将优惠券id转换成二进制,截取4位(4个1最大16)、高位补0。我们设置一个二维数组的密匙,这个密匙16组,每一组9个数字。因为4个二进制位最大就是16。
第三部分称为签名(14位),由序列号+新鲜值,按位乘以密匙,得到一个十进制数,将这个十进制转换成二进制,截取14位、高位补0。
解释:
序列号32位、每四位一组,就是8个十进制数。
新鲜值4位、四位一组就是1个十进制数。
密匙有16组、通过新鲜值匹配哪一组,每一组9个十进制数,刚好对应序列号和新鲜值加起来的9个十进制数。依次相乘,就得到了签名
二、超卖问题:
我们理想的状态是:
查询优惠券、判断是否充足、充足则更新优惠券领取数量
但是这样不能保证原子性,比如:
a线程还没有减库存,b线程进来了,导致超卖。
解决方法:乐观锁,依赖数据库的行锁,加一个字段,记录优惠券卖了多少张,每次更新进行一次判断。
三、超领问题:
用户刷卷,导致一个用户领取了多张优惠券。
解决方式:
悲观锁(同步锁)
因为当时我们锁的用户id,用户id使用了tostring方法,因为这个方法的源码里面new了新对象,所以锁不住。使用intern()方法解决