优惠券系统-第一章-系统设计
优惠券系统介绍
优惠券在很多系统会用到,本文结合项目实战谈谈优惠券系统怎么做。分为四个章节,第一个章节只介绍优惠券的设计,在第二章介绍优惠券的领取与补券,第三章设计一个类似京东的领券中心、活动中心,第四章聊聊优惠券使用。
优惠券整体流程图
优惠券整体流程使用流程如下图所示。注:下面这个图来自网络。
如上图所示,优惠券是先新建,然后由系统领导或者财务相关人员审核通过,再绑定活动发放出去。实际使用情况一般是运营小姐姐发邮件申请活动,审批通过后开发小哥哥直接配置线上活动,没有审批这个环节,这个环节比较简单,后面就不多介绍了。
优惠券设计
优惠券的设计原则就是尽量使单独的业务分割开来,首先就可以想到优惠券本身是一张表,我们叫做coupon_base,其次还需要一张用户表与优惠券的关联表,来记录用户与优惠券的关系叫做coupon_user,这里为了解耦业务,如果过多信息(比如用户)放在coupon_base上,后面表会无法控制的变大。
有了这两个表讨论一个问题,就是优惠券肯定是每个用户领完后有自己的券,所以量会很大。那么优惠券是先生成后给用户领取,还是用户领取的时候给用户生成呢?肯定是先生成后给用户领取,因为用户领取是一个高并发的操作,不宜做过多的业务逻辑进去,尽量简化用户领券的逻辑,简化成一个步骤最好,那就是从提前生成的"券池"中拿一个券SN号即可。
选择了将券生成后给用户领,每次配券只需要配一个券的模板,指定券的数量,然后开始生成,就需要一个模板表。这个表的名字可以是coupon_model或者coupon_act,这里我用的coupon_act。一共三张表,coupon_act记录了优惠券基本,coupon_base保存了生成后的券,coupon_user记录用户和券的关系。如下图所示:
第一张表coupon_act表,里面coupon_num是要生成的券数量,act_status是状态,coupon_money_min是优惠券使用的最低门栏,就是所谓的“满减金额”,订单满coupon_money_min即可减去coupon_value金额。这里注意类型coupon_type如果是“直接券”类型,即可不限制满减,直接抵扣订单金额,可以根据你的业务需求加类型,笔者只用到这两种。
CREATE TABLE `coupon_act` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',`act_sn` varchar(50) NOT NULL COMMENT '活动码',`coupon_name` varchar(255) DEFAULT NULL COMMENT '活动名称',`coupon_desc` varchar(255) DEFAULT NULL COMMENT '活动描述',`act_status` int(11) NOT NULL COMMENT '活动状态 1:未启用 2:已启用 3:已中止 4:已过期 5 生成中',`coupon_num` int(11) NOT NULL DEFAULT '0' COMMENT '优惠券数量',`limit_type` int(4) NOT NULL DEFAULT '0' COMMENT '1 固定时间范围过期,2 领券后固定时间过期',`limit_day` int(4) NOT NULL DEFAULT '0' COMMENT '优惠券限制使用天数:0不限制',`coupon_type` int(11) DEFAULT NULL COMMENT '优惠券类型 1:满减卷 2: 直减券',`coupon_money_min` int(11) DEFAULT NULL COMMENT '最少使用金额 0:不限制(单位:分,依赖coupon_type)',`coupon_money_max` int(11) DEFAULT NULL COMMENT '最大使用金额 0:不限制(单位:分,依赖coupon_type)',`coupon_value` int(11) NOT NULL DEFAULT '0' COMMENT '优惠券面值',`coupon_start_time` datetime DEFAULT NULL COMMENT '优惠券开始时间',`coupon_end_time` datetime DEFAULT NULL COMMENT '优惠券结束时间',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `IDXU_coupon_activity_act_sn` (`act_sn`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='优惠券主表';
下面是第二张表coupon_base,记录生成后的券,字段大部分继承自coupon_act(主表,也称之为模板表)。优惠券的coupon_sn号是生成时产生的分布式唯一ID,刚生成完coupon_status状态是1,被领取后是2,已使用与过期是4。coupon_base表还记录领取时间和使用时间。
CREATE TABLE `coupon_base` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',`act_sn` varchar(50) NOT NULL COMMENT '活动编号',`coupon_sn` varchar(50) NOT NULL COMMENT '优惠券号',`coupon_name` varchar(255) DEFAULT NULL COMMENT '优惠券名称',`coupon_desc` varchar(255) DEFAULT NULL COMMENT '优惠券描述',`coupon_status` int(11) DEFAULT '1' COMMENT '优惠券状态 1:未领取 2:已领取,未使用 3:已使用 4:已过期 5:已冻结 6:已作废',`coupon_type` int(11) DEFAULT NULL COMMENT '优惠券类型 1:满减卷 2: 直减券',`coupon_money_min` int(11) DEFAULT NULL COMMENT '最少使用金额 0:不限制(单位:分,依赖coupon_type)',`coupon_money_max` int(11) DEFAULT NULL COMMENT '最大使用金额 0:不限制(单位:分,依赖coupon_type)',`coupon_value` int(11) NOT NULL DEFAULT '0' COMMENT '优惠券面值',`coupon_start_time` datetime DEFAULT NULL COMMENT '优惠券开始时间',`coupon_end_time` datetime DEFAULT NULL COMMENT '优惠券结束时间',`draw_time` datetime DEFAULT NULL COMMENT '领取时间',`use_time` datetime DEFAULT NULL COMMENT '使用时间',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `unx_coupon_base_sn` (`coupon_sn`),KEY `idx_act_sn_coupon_status` (`act_sn`,`coupon_status`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='优惠券主表';
还有一张表是coupon_user,记录用户领取优惠券的信息,其中biz_source记录用户领券途径,可能是用户参与活动领取,biz_code记录某个活动id号,还一种情况可能是手动给某个用户补偿券,这个字段根据自身需求拓展。
CREATE TABLE `coupon_user` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',`coupon_sn` varchar(50) NOT NULL COMMENT '优惠券号',`biz_code` varchar(50) DEFAULT NULL COMMENT '活动来源码',`biz_source` int(11) DEFAULT '0' COMMENT '业务来源:1活动领取,2手动赠送',`user_id` varchar(50) DEFAULT NULL COMMENT '用户ID',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE,KEY `idx_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户领卷表';
优惠券的生成与装载
优惠券先生成后领取必然有一个问题需要处理就是"领重"问题,防止多个用户同时取得同一个券。我们将生成的券放入redis中,以list存储。每次用户从缓存中取券,我们只将券的SN号放入list队列。并不是全部放入redis,可以一次放几千张,部分代码参见如下。
//生成优惠券代码
public DataTransferObject<Void> startCouponActivity(Integer id) {DataTransferObject<Void> dto = new DataTransferObject();//取得coupon_act中的券模板CouponActivityEntity entity = couponActivityServiceImpl.getCouponActivity(id);try {//防止重复点击keyString sendKey = PromotionCacheConstant.COUPON_PRODUCT_KEY + id;long result = redisOperations.setnx(sendKey,"1");if (result != 1) {return new DataTransferObject("优惠券生成中,请勿操作");}redisOperations.expire(sendKey,PromotionCacheConstant.COUPON_PRODUCT_TIME);//由于生成券耗时,这里用的异步生成taskExecutor.execute(() -> {LogUtil.info(LOGGER,"生成券任务开始执行了,couponActId:{}",entity.getId());List<CouponBaseEntity> allCouponBaseEntityList = Collections.emptyList();try {//修改coupon_act状态,生成中CouponActivityRequest updateEntity = new CouponActivityRequest();updateEntity.setId(entity.getId());updateEntity.setActStatus(CouponActStatusEnum.PRODUCING.getCode());couponActivityServiceImpl.updateCouponActivity(updateEntity);//生成券,这个方法里面就是批量插入allCouponBaseEntityList = couponActivityServiceImpl.startCouponActivity(entity);//生成完毕后,修改coupon_act状态updateEntity.setCouponNum(entity.getCouponNum());updateEntity.setActStatus(CouponActStatusEnum.PRODUCED.getCode());couponActivityServiceImpl.updateCouponActivity(updateEntity);} catch (Exception e) {LogUtil.error(LOGGER,"生成券优惠券活动异常,{}",e);} finally {//删除防止并发的keyredisOperations.del(sendKey);//加载优惠券缓存队列if(!Check.NuNCollection(allCouponBaseEntityList)){couponBaseService.fillCacheCouponSnQueueForCreate(entity.getActSn(), allCouponBaseEntityList);}}});} catch (Exception e) {dto.setErrorMsg("生成券优惠券活动异常");}return dto;}
上面就是生成优惠券的代码,在finally代码块中有“fillCacheCouponSnQueueForCreate”方法就是将生成券加入到缓存中,详细代码见下面。
//将生成的券,加入缓存
public void fillCacheCouponSnQueueForCreate(String actSn, List<CouponBaseEntity> appendList){try{//如果无可用券,放入缓存标志位,用余判断某个券是否用完,不查库,直接查缓存if(Check.NuNCollection(appendList)){redisOperations.set(PromotionCacheConstant.getCacheCouponOverKey(actSn),PromotionCacheConstant.COMMON_LABEL);LogUtil.warn(logger, "可用优惠券已使用完("+actSn+")");return;}//有券,删除keyredisOperations.del(PromotionCacheConstant.getCacheCouponOverKey(actSn));if(!Check.NuNCollection(appendList)){String couponBaseQueueKey = PromotionCacheConstant.getCacheCouponQueueKey(actSn);appendList.parallelStream().forEach(base->{//缓存注意,是两步操作,将每个券的详情放入缓存,再将券号放入list队列,后面当用户领取时,直接从缓存中查券的信息,然后也将缓存删除redisOperations.set(base.getCouponSn(), JsonEntityTransform.Object2Json(base));redisOperations.lpush(couponBaseQueueKey,base.getCouponSn());});}} catch (Exception e){LogUtil.error(logger,"填充优惠券码队列失败,error:{}",e);}}
上面的代码,说明了券的生成和向redis的队列中加载券。下一章介绍优惠券的领取与补券。
优惠券系统-第一章-系统设计相关推荐
- VLSI数字信号处理系统——第一章数字信号处理系统导论
VLSI数字信号处理系统--第一章数字信号处理系统导论 作者:夏风喃喃 参考:VLSI数字信号处理系统:设计与实现 (美)Keshab K.Parhi/著 文章目录 VLSI数字信号处理系统--第一章 ...
- 学神python全栈学习笔记CMDB系统---第一章 python_cmdb_介绍,项目开始
第一章 python_cmdb_介绍,项目开始 本节所讲内容: 1.1 python cmdb系统介绍与需求分析 1.2 python cmdb数据库建模 1.3 python cmdb前端基础 ...
- 信号与系统第一章--基本知识
1.信号的基本概念 1.1信号分类 确定信号:可用确定时间函数表示的信号 (电流 电压 fx) 随机信号:不能用确定的函数描述,只可能知道它的统计特性.如概率 (雷电干扰信号) 1.1.1确定信号的分 ...
- 信号与系统——第一章 绪论
原文请前往 https://blog.csdn.net/qq_42635142/article/details/88380966 绪论 信号的分类 确定信号和随机信号 连续信号和离散信号 周期信号和非 ...
- 信号与系统 第一章 郭宝龙
转载于:https://www.cnblogs.com/iucforever/p/4542827.html
- 搭建asp会议签到系统:第一章 账密登录
搭建asp会议签到系统 第一章 账密登录 第二章 生成会议签到二维码 第三章 会议签到 第四章 会议统计 第一章 账密登录 搭建asp会议签到系统 前言 一.设置跳板 二.创建login.aspx页面 ...
- ppt_第一章_德塔自然语言图灵系统
开始组织核心ppt文字描述,图片和源码 在书籍中已经很丰富了. 第一章_德塔自然语言图灵系统 第一章_德塔自然语言图灵系统 分词,排序,神经网络索引,搜索,动态 POS函数流水阀门细化遍历 内核匹配, ...
- 计算机原理基础知识pdf,计算机原理第一章.pdf
计算机组成原理 讲授:李凌燕 学时:48 本课程考查目标 本课程对计算机专业来说是非常重要的专业基础 课. 理解单处理器计算机系统中各部件的内部工作原理.组 成结构以及相互连接方式,具有完整的计 ...
- 2021.12.26 第一章. 计算机组成与体系结构
本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net 该系列一共包括十三章 目录 第一章. 计算机组成与体系结构 第一节. 数据的表示 第三节. 计算机体系结构分类 (Flyn ...
最新文章
- 【荐】如何规划 Nginx 网站目录的权限(用户,用户组,ssh,sftp)
- 数据科学家技能地铁图
- 操作系统内存管理之 内部碎片vs外部碎片
- glibc升级_CentOS7下升级GLIBC2.31
- 【2018.3.31】模拟赛之三-ssl2408 比萨【搜索,dfs】
- ab753变频器参数怎么拷贝到面板_变频器怎么设置参数?变频器的基本参数设定...
- docker如何安装vim和yum命令?
- Nginx日志分割处理
- UGUI更换图片的三种方法
- pod镜像拉取策略、重启容器策略
- alpine日志中文乱码的问题解决方案
- 360主机卫士正式上线
- 项目中报错找不到.h或者.m文件解决方法
- mismatch,equal比较两序列
- 基于ngc的cuda镜像封装TensorFlow 实例
- html5 可拖动悬浮按钮,前端vue开发:可移动的悬浮按钮的应用
- (六) kityminder 协同编辑执行用例时,如果做到不相互干扰
- 中国科学技术大学计算机专业排名,2019中国科学技术大学专业排名
- 极家装修怎么样?擅长装修简约风吗?
- 字符输出流,缓冲流和序列化