优惠卷表:优惠卷基本信息,优惠金额,使用规则 包含普通优惠卷和特价优惠卷(秒杀卷)
优惠卷的库存表:优惠卷的库存,开始抢购时间,结束抢购时间.只有特价优惠卷(秒杀卷)才需要填写这些信息
优惠卷订单表
卷的表里已经有一条普通优惠卷记录
下面首先新增一条秒杀优惠卷记录
{
"shopId": 1,
"title": "100元代金券",
"subTitle": "周一至周五均可使用",
"rules": "全场通用\\n无需预约\\n可无限叠加\\n不兑现、不找零\\n仅限堂食",
"payValue": 8000,
"actualValue": 10000,
"type": 1,
"stock": 100,
"beginTime": "2022-01-25T10:09:17",
"endTime": "2022-01-26T12:09:04"
}
返回一个订单
实现秒杀下单
就是往下面这张表中添加数据 创建订单
这里暂时只需要添加主键id 购买的代金卷id
对应的还需要去扣减库存
关于订单的主键
使用Redis生成全局唯一ID示例-CSDN博客
控制器
业务层
传入的优惠卷id
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService seckillVoucherService;@Autowiredprivate RedisIdWorker redisIdWorker;@Override@Transactional(rollbackFor = Exception.class)public Result seckillVoucher(Long voucherId) {//1.查询优惠券SeckillVoucher byId = seckillVoucherService.getById(voucherId);if (byId.getBeginTime().isAfter(LocalDateTime.now())) {//2.判断秒杀是否开始return Result.fail("秒杀尚未开始");}if (byId.getEndTime().isBefore(LocalDateTime.now())) {//3.判断秒杀是否结束return Result.fail("秒杀已经结束");}if (byId.getStock() < 1) {//4.判断库存是否充足return Result.fail("库存不足");}//5.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();if(!success){return Result.fail("库存不足");}//6.创建订单VoucherOrder voucherOrder = new VoucherOrder();//6.1.订单idlong order = redisIdWorker.nextId("order");voucherOrder.setId(order);voucherOrder.setUserId(1L);//登录用户先写死voucherOrder.setVoucherId(voucherId);save(voucherOrder);return Result.ok(order);}
}
以上代码,在高并发场景下会出现超卖现象
以下用乐观锁方式解决超卖问题
用乐观锁 CAS法解决超卖问题
就是在更新时候添加个条件
当200个并发线程打进去之后
虽然解决了并发安全问题,但是出现成功率低的问题 200个线程进来 没有卖完 很多都失败了
如何解决??
修改下条件 where id=? and stock>0 即可
这样就可以解决超卖问题
以上是用JMeter并发测试的,从结果看,系统还存在一个问题,例如对方开启并发访问的工具,这样会导致所有订单都是同一个用户购买的情况,或者说一个人购买了好几单
在订单表中出现如图情况
这样的漏洞,就好比黄牛了,那么系统如何实现一人一单,就是说一个用户最多下一个订单的需求
其实很简单 只要满足user_id 和 voucher_id唯一即可
就是做个查询,该用户有没有下单
如果这样写 并发情况下还是会出现问题
因为当你去判断count>0时候可能已经有10个线程都完成了查询 在判断count的时候 都查出来等于0的情况
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService seckillVoucherService;@Autowiredprivate RedisIdWorker redisIdWorker;@Overridepublic Result seckillVoucher(Long voucherId) {//1.查询优惠券SeckillVoucher byId = seckillVoucherService.getById(voucherId);if (byId.getBeginTime().isAfter(LocalDateTime.now())) {//2.判断秒杀是否开始return Result.fail("秒杀尚未开始");}if (byId.getEndTime().isBefore(LocalDateTime.now())) {//3.判断秒杀是否结束return Result.fail("秒杀已经结束");}if (byId.getStock() < 1) {//4.判断库存是否充足return Result.fail("库存不足");}Long id = UserHolder.getUser().getId();//表示获取用户id//id.toString()实际是new了一个String对象,然后调用intern()方法,这个方法会去常量池中查找有没有这个对象,如果有就返回这个对象,如果没有就添加到常量池中,然后返回这个对象synchronized (id.toString().intern()){//如果这样调用,前面有个this 事务使用代理对象去执行的//return createVoucherOrder(voucherId);IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}}@Transactional(rollbackFor = Exception.class)public Result createVoucherOrder(Long voucherId) {Long id = UserHolder.getUser().getId();////一人一单的查询Integer count = query().eq("user_id", id).eq("voucher_id", voucherId).count();if(count > 0){return Result.fail("您已经购买过一次了");}//5.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock", 0).update();if(!success){return Result.fail("库存不足");}//6.创建订单VoucherOrder voucherOrder = new VoucherOrder();//6.1.订单idlong order = redisIdWorker.nextId("order");voucherOrder.setId(order);voucherOrder.setUserId(id);//登录用户先写死voucherOrder.setVoucherId(voucherId);save(voucherOrder);Result.ok(order);}
}
以上方案仅适用于单机版本