大众点评项目 订单功能 秒杀基本环境

  • 需求:订单功能 秒杀基本环境
  • Redis实现全局唯一ID
  • 业务实现
  • 代码总览
  • 总结


SpringCloud章节复习已经过去,新的章节Redis开始了,这个章节中将会回顾Redis实战项目 大众点评
主要依照以下几个原则

  1. 基础+实战的Demo和Coding上传到我的代码仓库
  2. 在原有基础上加入一些设计模式,stream+lamdba等新的糖
  3. 通过DeBug调试,进入组件源码去分析底层运行的规则和设计模式

代码会同步在我的gitee中去,觉得不错的同学记得一键三连求关注,感谢:

Redis优化-链接: RedisLightningDealsProject

redis防止订单重复提交 redis 订单_redis防止订单重复提交

需求:订单功能 秒杀基本环境

商品分析
在点评项目中,存在了优惠券这种商品,优惠券分成了两种

  1. 普通优惠券:无限量抢购,无时间限制
  2. 秒杀优惠券:限量,限时

redis防止订单重复提交 redis 订单_数据库_02

分析表结构

redis防止订单重复提交 redis 订单_redis防止订单重复提交_03


这里是基本优惠券表

redis防止订单重复提交 redis 订单_java_04


这里是秒杀优惠券表,本身就是基本优惠券,多了一些字段

redis防止订单重复提交 redis 订单_java_05

Redis实现全局唯一ID

redis防止订单重复提交 redis 订单_redis防止订单重复提交_06

redis防止订单重复提交 redis 订单_Redis_07


redis防止订单重复提交 redis 订单_redis_08

@Component
public class RedisIDProductor {
    /**
     * 我的当前时间号
     */
    private static final long BEGIN_TIMESTAMP = 1670847120L;

    /**
     * 序列号位数
     */
    private static final int COUNT_BITS = 32;

注入Redis

@Resource
    private StringRedisTemplate stringRedisTemplate;

    public RedisIDProductor(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }

通过Redis自增id创建,这里分成三个步骤

  1. 生成时间戳,就是时间差值,32位
  2. 生成Redis序列号,通过Redis的有序自增 ,32位
  3. 通过位运算进行拼接!
public long nextId(String keyPrefix){

// 生成时间戳,就是时间差值,32位
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;
    
 // 生成Redis序列号,通过Redis的有序自增  ,32位
        String curTime = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + curTime);

 // 通过位运算进行拼接!
        return timestamp<<COUNT_BITS | count;
    }

业务实现

先通过PostMan提交post请求,更新优惠券信息

redis防止订单重复提交 redis 订单_java_09

{
    "shopId":1,
    "title":"100元代金券",
    "subTitle":"周一至周日均可使用",
    "rules":"全场通用\\n无需预约\\n可无限叠加\\不兑现、不找零\\n仅限堂食",
    "payValue":8000,
    "actualValue":10000,
    "type":1,
    "stock":100,
    "beginTime":"2022-12-12T10:09:17",
    "endTime":"2022-12-26T14:09:04"
}

DB查看优惠券信息

redis防止订单重复提交 redis 订单_redis防止订单重复提交_10

通过请求的URL区修改对应的方法

@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIDProductor redisIDProductor;

由于是对多个表的操作,
**@Transactional **保证了事务

@Override
    @Transactional
    public Result seckillVoucher(Long voucherId) {

// voucher:通过id去查到底是那种优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);

        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            return Result.fail("秒杀尚未开始");
        }

        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            return Result.fail("秒杀已经结束");
        }

        if (voucher.getStock() < 1) {
            return Result.fail("库存不足");
        }

这里是通过MP本身的API直接进行库存的增减

//验证结束,扣减库存
        boolean flag = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .update();

        if (!flag) {
            return Result.fail("库存不足");
        }

链式API封装订单信息,将其保存到订单表中

//封装订单
        VoucherOrder voucherOrder = new VoucherOrder();
        Long userId = UserHolder.getUser().getId();
        long orderId = redisIDProductor.nextId("order");
        //订单Id、用户Id、优惠券Id
        voucherOrder.setId(orderId).setUserId(userId).setVoucherId(voucherId);
        save(voucherOrder);

        return Result.ok(orderId);

    }
}

代码总览

@RestController
@RequestMapping("/voucher-order")
public class VoucherOrderController {

    @Resource
    private IVoucherOrderService voucherOrderService;

    @PostMapping("seckill/{id}")
    public Result seckillVoucher(@PathVariable("id") Long voucherId) {
        return voucherOrderService.seckillVoucher(voucherId);
//        return Result.fail("功能未完成");
    }
}
public interface IVoucherOrderService extends IService<VoucherOrder> {

    Result seckillVoucher(Long voucherId);
}
@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIDProductor redisIDProductor;

    @Override
    @Transactional
    public Result seckillVoucher(Long voucherId) {

        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);

        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            return Result.fail("秒杀尚未开始");
        }

        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            return Result.fail("秒杀已经结束");
        }

        if (voucher.getStock() < 1) {
            return Result.fail("库存不足");
        }

        //验证结束,扣减库存
        boolean flag = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .update();

        if (!flag) {
            return Result.fail("库存不足");
        }

        //封装订单
        VoucherOrder voucherOrder = new VoucherOrder();
        Long userId = UserHolder.getUser().getId();
        long orderId = redisIDProductor.nextId("order");
        //订单Id、用户Id、优惠券Id
        voucherOrder.setId(orderId).setUserId(userId).setVoucherId(voucherId);
        save(voucherOrder);

        return Result.ok(orderId);

    }
}
@Service
public class VoucherServiceImpl extends ServiceImpl<VoucherMapper, Voucher> implements IVoucherService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Override
    public Result queryVoucherOfShop(Long shopId) {
        // 查询优惠券信息
        List<Voucher> vouchers = getBaseMapper().queryVoucherOfShop(shopId);
        // 返回结果
        return Result.ok(vouchers);
    }

    @Override
    @Transactional
    public void addSeckillVoucher(Voucher voucher) {
        // 保存优惠券
        save(voucher);
        // 保存秒杀信息
        SeckillVoucher seckillVoucher = new SeckillVoucher();
        seckillVoucher.setVoucherId(voucher.getId());
        seckillVoucher.setStock(voucher.getStock());
        seckillVoucher.setBeginTime(voucher.getBeginTime());
        seckillVoucher.setEndTime(voucher.getEndTime());
        seckillVoucherService.save(seckillVoucher);
    }


}

总结

redis防止订单重复提交 redis 订单_Redis_11