项目-优惠券

9 篇文章 0 订阅
规则要点
过期方式:
1. 按固定时间过期
2. 按发放后的有效天数过期
发券方式:
1. 系统自动分发
2. 用户手动领取
发放规则:
限领次数
优惠券类型:
1. 满减券
2. 立减券
3. 折扣券
适用产品:
1. 指定单个商品优惠
2. 指定商品类型优惠
3. 全品类优惠

数据库相关
表的数量
60 多个( 66
负责的表
负责 11 张表
优惠券模板表 优惠券表 商品表 商品种类表
优惠券商品的关系表 优惠券商品种类关系表 积分兑换优惠
券表 用户积分表
已用优惠券表 过期优惠券表 ,还有一个 使用开源项目创建的
(美团开源项目的表)
设计表遇到的问题
在设计表的时候,就优惠券过期这个问题上, ER 图上只写了一
个是否过期的字段,但是没有给出过期时间这个字段;
因为过期方式有两种,一种是自领取后多少天过期的优惠券,
另一种是固定的过期时间;
主要的问题就处在固定的过期时间,对于固定的过期时间来
说,是没有办法按照自领取后多少天过期的规则来实现的,所
以在开发的时候遇到了这个问题,就与组长进行协商,添加了
一个过期时间的字段
使用多线程的地方, 用户领取优惠券的地方,使用 Jmeter 进行多
线程测试 使用的 springBoot 版本 2.2.5 release
项目描述
通过团队讨论之后,完成了数据库的设计;
优惠券模板创建
接着就是优惠券模板的创建,根据数据库的字段去考虑需要的参
数,
并且要考虑从前端接收优惠券模板参数的问题,因为优惠券的使
用规则各不相同,
所以就创建了一个用于接收前端参数的模板请求对象,
在接收到优惠券模板的相关参数后,就开始实现业务层逻辑;
首先要根据优惠券模板的请求参数,来设置优惠券模板的属性;
在设置优惠券模板 id 时,遇到了一个问题,
因为是在分布式环境下,可能会有多个用户同时对数据库进行操
作,这就导致可能会生成同样的 id
所以必须要保证 id 不能重复,为了生成全局的唯一 id
这里使用了一个美团的开源项目来生成优惠券的 id, 保证优惠券
模板的 id 不会重复。 美团的开源项目
leaf 分布式 id 算法,项目内使用的是 Leaf-segment 号段
模式 ,就是在数据库中取出一段自增的整数,每次调用都消耗
一个整数,当快要消耗完的时候再去数据库取出一段自增的整
数。不仅生成了全局唯一的 id ,还减少了数据库的压力
如何调用的这个开源项目:
使用的是 Forest 轻量级 HTTP 客户端框架,通过配置网络
请求注册快速接入第三方接口
在启动类上使用 ForestScan 注解来扫描对应的报下的接
口,在这个包内接口的方法上 @Request 注解,其中的 url
性写开源框架启动后的资源路径
解决了全局 id 的问题后,还有一个问题就是
优惠券模板有一个属性是适用产品,
就是指定的优惠券是针对单一产品使用,还是整个产品类型可
用,或者是全品类可用;
单一产品和全品类优惠券都比较容易实现,
但是产品类型实现功能时就有些问题,考虑到管理员在创建这个
模板的时候需要选择商品类型,
所以先将商品的全部类型在数据库查询出来,
我设置了一个缓存预热,在使用 @PostConstruct 注解,在项目
启动的时候直接将商品类型的全部信息存入 redis 中;
这样就能在创建优惠券模板的时候,在前端显示出全部商品类型
让管理员选择; 其他的优惠券模板属性就是直接通过请求获取,然后创建优惠券模
板,将模板信息持久化到数据库中
存到 redis 中的 key 是怎么设置的:
我在 common 公共类中设置了一些前缀,来区分存入 redis
时的 key ,再加上对应的唯一标识来完成 key 的设置
创建过优惠券模板之后,同时要生成指定数量的优惠券码,
这里防止生成的优惠券码过多导致线程阻塞,所以使用异步方
法,
线程池 中获取异步线程,异步生成优惠券码
线程池是 Spring 自带的线程池,如果优惠券过多,不使用异步
可能会导致阻塞时间过久
优惠券码的组成:
是由 优惠券 id + 优惠券的优惠类型码 + 优惠券适用范围码 +
优惠券创建时间 + hutool 工具生成 10 位随机值 ,组合成的
优惠券码
优惠券优惠类型码 优惠券适用范围码 在数据库中存储的
都是对应的整数,查询到之后通过相对应的枚举类获取到对应
的状态
定义的枚举类中的常量都是一个描述和一个 code ,定义一个
方法通过指定 code 获取指定的描述。 hutool 是一个 Java 工具类库,里边很多函数使用起来都很方
便,
比如线程工具,集合工具等,一般都是看这 api 文档用的
因为优惠券码也是不能重复的,所以这里使用 set 集合来存储优
惠券码,
因为 set 集合不能存放重复数据,之后再进行一次判断,
如果 set 集合的长度与创建优惠券的个数不一致,
则使用 while 循环再生成一些优惠券码,保证 set 集合的长度与
创建优惠券个数一致!!
生成的优惠券码是不持久化到数据库中,而是放到 redis 缓存
中,
一是能够能快的获取到优惠券码,
二是因为优惠券码是需要被消耗的,每当用户领取一张优惠券,
就消耗一个优惠券码,
当用户领取成功之后,将用户 id 和优惠券码还有领取时间等信息
一起持久化到优惠券表中
存入 redis 的类型是一个集合,
所以使用 opsForValue 方法,调用 leftPushAll 将优惠券码集
合存入到 redis 中;
leftPushAll 对应的 redis 命令是 lpush
优惠券自动分发
接着就是优惠券分发功能,优惠券分发功能主要分为系统自动发
放、用户手动领取;
先说系统自动分发的优惠券,首先需要获取 系统自动分发的优惠
券模板
因为分发功能和模板创建不是同一个服务,所以需要使用
openFeign 来完成服务间通信;
使用 openFeign 要在启动类中使用 EnableFeignClients
解,开启外部服务,然后在对应的接口方法上使用 FeignClient
注解,该注解的属性有
value -- 指定的服务名称,该名称是在 yml 文件设置
name -- 指定服务的名称
url -- 配置指定服务的地址
fallback -- 定义容错的处理类,也就是回退逻辑
primary -- true 时优先级最高
path -- 定义当前 FeignClient 访问接口时的统一前缀
等等
获取到系统自动分发的优惠券模板之后,判断优惠券模板是否过
期,
可能存在多个符合条件的优惠券,所以创建一个 list 用于存储未
过期的优惠券模板 id 然后需要根据指定的 用户 id 刚刚的优惠券模板 id 集合
redis 中去查用户领取的各个优惠券模板个数的集合,
如果 redis 中没有就代表此时还用户还没有领取过这个优惠券,
可以发放优惠券;
如果获取到了,遍历判断是否优惠券超过了限领次数,没有的可
以就发放优惠券
发放优惠券要创建一个优惠券对象,该对象需要根据优惠券模板
id redis 中获取一个优惠券码
用的 leftPop 对应于 redis 的命令就是 lpop
再给优惠券也生成一个唯一 id ,加上领取的用户 id 和 领取时
间,
因为刚才在遍历集合,所以应该再创建一个 list 集合,用于存储
创建好的优惠券对象,
遍历结束后使用 MyBatis-plus 将这个集合批量添加到数据库中
(一共三个集合,一个用于获取可用优惠券模板 id 的集合,一个
是从 redis 中获取到的用户领取各个优惠券模板个数的集合,一个
是用来存储优惠券对象的集合)
手动领取优惠券
然后就是用户手动领取优惠券,
这里有些不同的是,用户领取优惠券只能领取单个优惠券, 所以只需要获取单个的优惠券模板, 获取之后对优惠券模板进行
判断,看是否过期;
没有过期的话,还需要判断用户是否领取过,是否超过限领次
数,
满足条件就能创建优惠券绑定用户 id 存入到数据库中
在创建好的优惠券信息时 ,
为了防止优惠券出现丢失的情况 , 使用了 RabbitMq 中间件 ,
在持久化之前先发送到 消息队列 中 , 再持久化到数据库中 ,
并且在存放到 redis 中一份 , 可供用户来查询自己领取的优惠券
在 业务逻辑中 , 创建好优惠券 , 直接使用 convertAndSend()
方法,指定交换机名和路由规则,发送消息到 mq , 还需要写
一个构建 message 的方法 , 将优惠券转成 message
发送之后 , mq 的服务层中,使用 @RabbitListener 注解来
监听指定的队列,当队列有消息的时候,就会将消息持久化到
数据库,在缓存到 redis 中供用户查看
Confirm Return 机制来看是否到达交换机或队列
Redis 持久化机制, rdb aof
所以是先发送到 mq 再持久化到数据库 用户查看优惠券
分发优惠券说完了,还有一个用户查看领取的优惠券,
优惠券分为,可用优惠券,已用优惠券,过期优惠券,
Rabbit 的模式: 1. 工作队列模式 Work 2. 发布订阅模式
Publish/Subscribe 3. 路由模式 Routing 4. 主题模式 Topic
工作队列:(一个生产者,两个消费者,一个默认交换机
direct ),一个队列)
发布订阅模式:(一个生产者,两个消费者,一个交换机,两个队
列!!)借助交换机,生产者将消息发送到交换机,再通过交换机到达
队列 .
有四种交换机: direct / topic / headers / fanout ,默
认交换机是 direct
路由模式:(一个生产者,一个交换机,两个队列,两个消费
者),加上路由规则, 生产者发送的消息会指定一个路由 key ,那么消
息只会发送到相应 key 相同的队
主题模式:(一个生产者,一个交换机,两个队列,两个消费
者),与路由模式类似,
路由模式是通过路由规则精准匹配队列,而主题模式是模糊匹配,
通过通配符 * 号代表一个单词, # 号代表 0 个或 多个单词) 已用优惠券是在用户消费完之后,直接将可用优惠券从表中删
除,然后添加到已用优惠券表,
然后用户查询已用优惠券的时候直接去已用优惠券表查询,
在查询 可用优惠券 或者 过期优惠券 的时候,都是先查可用优惠
券,
然后再进行判断,判断优惠券是否过期
如果优惠券过期,直接在可用优惠券表中删除优惠券,并添加到
过期优惠券中
在查询不同状态的优惠券时,只需要去不同的表中查询即可
查询到的数据,一般是直接缓存 redis 中, 并设置过期时间,
防止用户多次查询优惠券,导致多次访问数据库,
所以,
查询的顺序应该是先从 redis 中查询,
如果 redis 不存在,再从 数据库 查询领取的全部优惠券信息,
这时可能会出现 redis 击穿的情况 ,为了防止击穿,
设置一个 id -1 的优惠券对象存入 redis
在从 数据库 中查到数据缓存到 redis 就不需要创建对应的键值
了, 只是后续查询到数据,需要过滤掉 id -1 的优惠券( Stream
filter 方法)
缓存击穿:当某个 key 在过期的瞬间,有大量的请求并发访
问,由于缓存过期,会同时访问数据库来查询最新数据,并回
写缓存,导致数据库瞬间压力过大。
解决: 1. 访问 redis 提前创建好一个 key 2. 添加互斥
锁,使用分布式锁同时只能一个线程访问
缓存雪崩:指在某一个时间段有过高的访问量,这时缓存集
中过期失效或 Redis 宕机导致的,
解决办法: 1. 搭集群, 2. 设置不同的过期时间,让缓存失
效的时间尽量均衡, 3. 限流降级,过期了就限制线程数量
判断过期:固定时间是直接用当前时间去和固定时间比
较,变动时间使用当前时间和领取时间 + 过期时间,使用
LocalDateTime plusDay 方法完成时间的相加
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员子衿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值