优惠券系统介绍

优惠券在很多系统会用到,本文结合项目实战谈谈优惠券系统怎么做。分为四个章节,第一个章节只介绍优惠券的设计,在第二章介绍优惠券的领取与补券,第三章设计一个类似京东的领券中心、活动中心,第四章聊聊优惠券使用。

优惠券整体流程图

优惠券整体流程使用流程如下图所示。注:下面这个图来自网络。

如上图所示,优惠券是先新建,然后由系统领导或者财务相关人员审核通过,再绑定活动发放出去。实际使用情况一般是运营小姐姐发邮件申请活动,审批通过后开发小哥哥直接配置线上活动,没有审批这个环节,这个环节比较简单,后面就不多介绍了。

优惠券设计

优惠券的设计原则就是尽量使单独的业务分割开来,首先就可以想到优惠券本身是一张表,我们叫做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的队列中加载券。下一章介绍优惠券的领取与补券。

优惠券系统-第一章-系统设计相关推荐

  1. VLSI数字信号处理系统——第一章数字信号处理系统导论

    VLSI数字信号处理系统--第一章数字信号处理系统导论 作者:夏风喃喃 参考:VLSI数字信号处理系统:设计与实现 (美)Keshab K.Parhi/著 文章目录 VLSI数字信号处理系统--第一章 ...

  2. 学神python全栈学习笔记CMDB系统---第一章 python_cmdb_介绍,项目开始

    第一章 python_cmdb_介绍,项目开始 本节所讲内容: 1.1  python cmdb系统介绍与需求分析 1.2  python cmdb数据库建模 1.3  python cmdb前端基础 ...

  3. 信号与系统第一章--基本知识

    1.信号的基本概念 1.1信号分类 确定信号:可用确定时间函数表示的信号 (电流 电压 fx) 随机信号:不能用确定的函数描述,只可能知道它的统计特性.如概率 (雷电干扰信号) 1.1.1确定信号的分 ...

  4. 信号与系统——第一章 绪论

    原文请前往 https://blog.csdn.net/qq_42635142/article/details/88380966 绪论 信号的分类 确定信号和随机信号 连续信号和离散信号 周期信号和非 ...

  5. 信号与系统 第一章 郭宝龙

    转载于:https://www.cnblogs.com/iucforever/p/4542827.html

  6. 搭建asp会议签到系统:第一章 账密登录

    搭建asp会议签到系统 第一章 账密登录 第二章 生成会议签到二维码 第三章 会议签到 第四章 会议统计 第一章 账密登录 搭建asp会议签到系统 前言 一.设置跳板 二.创建login.aspx页面 ...

  7. ppt_第一章_德塔自然语言图灵系统

    开始组织核心ppt文字描述,图片和源码 在书籍中已经很丰富了. 第一章_德塔自然语言图灵系统 第一章_德塔自然语言图灵系统 分词,排序,神经网络索引,搜索,动态 POS函数流水阀门细化遍历 内核匹配, ...

  8. 计算机原理基础知识pdf,计算机原理第一章.pdf

    计算机组成原理 讲授:李凌燕 学时:48 本课程考查目标  本课程对计算机专业来说是非常重要的专业基础 课. 理解单处理器计算机系统中各部件的内部工作原理.组 成结构以及相互连接方式,具有完整的计 ...

  9. 2021.12.26 第一章. 计算机组成与体系结构

    本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net 该系列一共包括十三章 目录 第一章. 计算机组成与体系结构 第一节. 数据的表示 第三节. 计算机体系结构分类 (Flyn ...

最新文章

  1. 【荐】如何规划 Nginx 网站目录的权限(用户,用户组,ssh,sftp)
  2. 数据科学家技能地铁图
  3. 操作系统内存管理之 内部碎片vs外部碎片
  4. glibc升级_CentOS7下升级GLIBC2.31
  5. 【2018.3.31】模拟赛之三-ssl2408 比萨【搜索,dfs】
  6. ab753变频器参数怎么拷贝到面板_变频器怎么设置参数?变频器的基本参数设定...
  7. docker如何安装vim和yum命令?
  8. Nginx日志分割处理
  9. UGUI更换图片的三种方法
  10. pod镜像拉取策略、重启容器策略
  11. alpine日志中文乱码的问题解决方案
  12. 360主机卫士正式上线
  13. 项目中报错找不到.h或者.m文件解决方法
  14. mismatch,equal比较两序列
  15. 基于ngc的cuda镜像封装TensorFlow 实例
  16. html5 可拖动悬浮按钮,前端vue开发:可移动的悬浮按钮的应用
  17. (六) kityminder 协同编辑执行用例时,如果做到不相互干扰
  18. 中国科学技术大学计算机专业排名,2019中国科学技术大学专业排名
  19. 极家装修怎么样?擅长装修简约风吗?
  20. 字符输出流,缓冲流和序列化

热门文章

  1. 356 次小生成树(求解最近公共祖先优化)
  2. VMware:虚拟机磁盘空间不足怎么办
  3. 2月编程语言排行榜出炉,第一名势头强劲
  4. 计算机网络中的冗余部件大大降低了可靠,大学计算机第七篇练习题
  5. 互联网最大谣言:程序员35岁必淘汰?今天我就来击碎他!
  6. 专访|HPE测试中心总监徐盛:测试新思维-DevOps,持续测试,更敏捷,更快速
  7. 官方文档-丰富你的数据
  8. Jackson-操作XML
  9. NeoWorld之穿越者2046:第二章
  10. php中的网页漂浮代码,网页中上下漂浮的按钮JS代码-DEDE