我是一段不羁的公告!
记得给艿艿这 3 个项目加油,添加一个 STAR 噢。
https://github.com/YunaiV/SpringBoot-Labs
https://github.com/YunaiV/onemall
https://github.com/YunaiV/ruoyi-vue-pro

数据库实体设计 —— 营销(4.1)之优惠劵&&优惠码

艿艿目前正在做一个开源的电商项目,胖友可以 star 下。https://gitee.com/zhijiantianya/onemall

1. 概述

本文主要分享营销模块的 优惠码优惠码 的数据库实体设计

基于如下信息,逆向猜测数据库实体:

【护脸旁白】
笔者非电商行业出身 && 非有赞工程师,所以有错误或不合理的地方,烦请斧正和探讨。
有赞是个各方面都很 NICE 的公司,推荐

2. 背景了解

2.1 优惠劵管理

参见 《优惠券的操作和玩法》 文档。

友情提示:同步微信卡劵部分,不在本文范围内。

2.2 优惠码管理

参见 《如何创建优惠码?》 文档。

从界面上,我们可以看出,优惠码和优惠劵的配置大体类似。在下面的数据实体的设计,我们会从字段上标记出来。

2.3 下单使用优惠

买家下单时,可以使用优惠劵或优惠码,进行使用。

2.4 验证使用优惠

买家除了下单可以使用优惠劵(码),也可以出示该劵(码)的二维码校验码,被卖家使用 APP / 后台,扫一扫或输入,核检使用。界面如下:

  1. APP】卖家输入校验功能
  2. 后台】卖家输入校验功能
  3. 后台】验证记录列表

3. 数据库实体

整体实体类关系如下图:

  • CouponGroup :卖家配置完优惠劵(码)时,生成优惠劵(码)分组
  • Coupon :优惠卷(码)。根据不同的 CouponGroup 配置的不同,买家获得 Coupon 。
    • 优惠劵
      • 卖家添加完 CouponGroup 后,生成任何 Coupon 记录。
      • 买家领取时,生成 Coupon 记录。
    • 优惠码【一卡一码】
      • 卖家添加完 CouponGroup 后,自动生成所有的 Coupon 记录,并且每个 Coupon 的优惠码不同。
      • 买家输入优惠码后,设置对应的 Coupon 属于买家。
    • 优惠码【通用码】
      • 卖家添加完 CouponGroup 后,自动生成一条 Coupon 记录。
      • 买家输入兑换码后,设置该条 Coupon 属于买家,并自动生成新一条 Coupon 记录,直到 CouponGroup 库存上限。
      • 买家使用兑换码下单后,若买家允许领取多张优惠劵,自动生成新一条 Coupon 记录归属该卖家,直到买家领取上限。
  • 因此,CouponGroup :Coupon = 1 : N
  • CouponVerifyLog :仅 「2.4 验证使用优惠」 时,生成一条 CouponVerifyLog 记录。

全部实体在 Github 优惠劵(码)实体目录 下可见。

3.1 CouponGroup

CouponGroup ,优惠劵(码)组。优惠劵和优惠劵的配置,都存储在 CouponGroup 中。

下面,我们分块来介绍每个字段。

3.1.1 基本信息

/**
* 编号,自增唯一。
*/
private Integer id;
/**
* 店铺编号
*/
private Integer shopId;
/**
* 别名
*
* 系统生成,作为唯一标识。例如,2fpa62tbmsl9h
* 可以用于生成优惠券(码)领取链接,例如,https://wap.youzan.com/v2/showcase/coupon/fetch?alias=17xcvjbd8
*/
private String alias;
/**
* 标题
*/
private String title;
/**
* 使用说明
*/
private String description;
/**
* 类型
*
* 1-优惠劵
* 2-优惠码
*/
private Integer type;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 优惠码状态
*
* 1-有效
* 2-已失效
* 3-已过期
* 4-已删除
*
* 当优惠劵(码)有效时,可以手动操作,设置成无效。
*/
private Integer status;
/**
* 是否可分享领取链接
*/
private Boolean isShare;
/**
* 设置为失效时间
*/
private Date invalidTime;
/**
* 删除时间
*/
private Date deleteTime;
  • id ,分组编号,自增唯一。
  • shopId ,店铺编号,指向 Shop.id
  • alias ,别名,系统生成,作为唯一标识。例如,2fpa62tbmsl9h 。可以用于生成优惠券(码)领取链接,保证安全性,例如,优惠码领取地址 https://wap.youzan.com/v2/showcase/coupon/fetch?alias=17xcvjbd8
  • title ,标题。
  • description ,使用说明。
  • type ,类型,用于标记是优惠劵还是优惠码。
  • createTime ,创建时间。
  • updateTime ,更新时间。
  • status ,状态,迁移过程如下图:
    • 生效中 :卖家创建完 CouponGroup ,处于该状态。
    • 已失效 :卖家手动设置 CouponGroup 为已失效,此处买家已领取 Coupon 依然有效,但是不能领取新的 Coupon 。参见 《如何让已领取的优惠券失效掉?》 文档。
    • 已过期 :当 CouponGroup 到达过期时间时,变更为已过期。当然也因此,仅有效期类型为固定日期的 CouponGroup 能自动过期。
    • 已删除 :当 CouponGroup 处于已过期时,卖家方可手动删除。删除效果参见 《优惠券可以删除吗?》 文档。
  • invalidTime ,设置为已失效状态的时间。
  • deleteTime ,设置为已删除状态的时间。
  • isShare ,是否可分享领取链接。

3.1.2 码信息

/**
* 码类型
*
* 1-一卡一码(UNIQUE)
* 2-通用码(GENERAL)
*
* 【优惠码独有】
*/
private Integer codeType;
/**
* 优惠码
*
* 【优惠码独有】
*/
private String code;
  • 优惠码独有
  • codeType ,优惠码类型。
  • code ,优惠码,当优惠码类型为通用码时设置。另外,优惠码类型为一卡一码时,每个优惠码在 Coupon 中存储。

3.1.3 领取规则

/**
* 是否限制领用者的等级
*
* 0-不限制
* 大于0-领用者必须是这个等级编号
*
* 【优惠劵独有】
*/
private Integer needUserLevel;
/**
* 每人限领个数
*
* 0-则表示不限制
*/
private Integer quota;
/**
* 剩余可用库存
*/
private Integer stock;
/**
* 总库存
*/
private Integer total;
/**
* 领取优惠券要给用户打上的标签的列表,使用逗号分隔标签编号
*/
private String markTags;

3.1.4 使用规则

/**
* 是否仅原价购买商品时可用
*
* true-是
* false-否
*/
private Boolean isForbidPreference;
/**
* 是否设置满多少金额可用,单位:分
*
* 0-不限制
* 大于0-多少金额可用
*/
private Integer condition;
/**
* 可用范围的类型
*
* 1-部分(ALL):全部商品可用
* 2-全部(PART):部分商品可用,或指定商品可用
*/
private Integer rangeType;
/**
* 指定可用商品列表,使用逗号分隔商品编号 {@link cn.iocoder.doraemon.itemgroup.item.entity.Item#id}
*/
private String rangeValues;
/**
* 生效日期类型
*
* 1-固定日期
* 2-领取日期:领到券 {@link #fixedBeginTerm} 日开始 N 天内有效
*/
private Integer dateType;
/**
* 固定日期-生效开始时间
*/
private Date validStartTime;
/**
* 固定日期-生效结束时间
*/
private Date validEndTime;
/**
* 领取日期-开始天数
*
* 例如,0-当天;1-次天
*/
private Integer fixedBeginTerm;
/**
* 领取日期-结束天数
*/
private Integer fixedTerm;
/**
* 是否到期前4天发送提醒
*
* true-发送
* false-不发送
*/
private Boolean expireNotice;
  • 价格维度
    • isForbidPreference / condition
  • 商品维度
    • rangeType / rangeValues
  • 时间维度
    • dateType
    • validStartTime / validEndTime
    • fixedBeginTerm / fixedTerm
    • expireNotice

3.1.5 使用效果

/**
* 优惠类型
*
* 1-代金卷
* 2-折扣卷 【优惠劵独有】
*/
private Integer preferentialType;
/**
* 折扣。例如,80% 为 80。
*
*【优惠劵独有】
*/
private Integer discount;
/**
* 是否是随机优惠券
*
* true-随机
* false-不随机
*
* 【优惠劵独有】
*/
private Boolean isRandom;
/**
* 优惠金额,单位:分
*
* 当 {@link #isRandom} 为 true 时,代表随机优惠金额的下限
*/
private Integer value;
/**
* 优惠金额上限
*
* 【优惠劵独有】
*/
private Integer valueRandomTo;
  • preferentialType
  • discount
  • isRandom / value / valueRandomTo

3.1.6 统计信息

/**
* 领取优惠券的人数
*/
private Integer statFetchUserNum;
/**
* 领取优惠券的次数
*/
private Integer statFetchNum;
/**
* 使用优惠券的次数
*/
private Integer statUseNum;

3.1.7 微信卡卷

/**
* 是否同步微信卡券
*
* true-是
* false-否
*/
private Boolean isSyncWeixin;
/**
* 同步微信卡券,选择的卡券颜色的值。
*
* 例如,Color10
*/
private String weixinColor;
/**
* 同步微信卡券,选择的卡券颜色的RGB值
*
* 例如,#ffaaff
*/
private String weixinColorRGB;
/**
* 同步微信卡券,卡券的标题
*/
private String weixinTitle;
/**
* 同步微信卡券,卡券的副标题
*/
private String weixinSubTitle;
/**
* 同步微信卡券,卡券的客服电话
*/
private String servicePhone;
/**
* 同步微信卡券,设置是否可以转赠
*
* true-可以
* false-不可以
*/
private Boolean canGiveFriend;
  • // TODO 6014 微信卡卷,这块逻辑笔者不熟悉,并且不太合适做测试。

3.1.8 创建后可编辑属性

优惠劵(码)分组创建后,大部分属性不可编辑,否则会影响已发放优惠劵。如下是可编辑的属性:

  • title
  • description
  • total
  • expireNotice
  • markTags

3.2 Coupon

Coupon ,优惠劵(码)。

下面,我们继续分块来介绍每个字段。

3.2.1 基本信息

/**
* 卡券ID
*/
private Integer id;
/**
* 店铺编号
*/
private Integer shopId;
/**
* 类型
*
* 1-优惠劵
* 2-优惠码
*/
private Integer type;
/**
* 优惠劵(码)分组编号,{@link CouponGroup#id}
*/
private Integer couponGroupId;
/**
* 核销码
*/
private String verifyCode;
/**
* 创建时间
*/
private Date createTime;
/**
* 优惠码状态
*
* 1-生效中
* 2-已失效
* 3-已过期
* 4-已删除
* 5-已使用
*/
private Integer status;
  • id ,优惠劵(码)编号。
  • shopId ,店铺编号,指向 Shop.id
  • type ,类型,用于标记是优惠劵还是优惠码。
  • couponGroupId ,分组编号,指向 CouponGroup.id
  • verifyCode ,核销码,用于 「2.4 验证使用优惠」
  • createTime ,创建时间。
  • status ,状态。相比 CouponGroup.status ,增加了已使用状态。

3.2.2 码信息

/**
* 码类型
*
* 1-一卡一码(UNIQUE)
* 2-通用码(GENERAL)
*
* 【优惠码独有】
*/
private Integer codeType;
/**
* 优惠码
*
* 【优惠码独有】
*/
private String code;
  • code ,优惠码。
    • 优惠码格式要求为 6 到 12 位数字组合。
    • 一卡一码的情况下,可自动生成优惠码(目前为 12 位),也可以生手动批量上传。自动生成的优惠码如下图:
    • 通用码时,手动输入优惠码。

那么问题就来了,兑换码怎么自动生成呢

我们来考虑下兑换码需要保证的几个方面:

  1. 唯一性。需要保证输入兑换码后,获得唯一对应的优惠码。那么这里有有一个问题,考虑到用户的体验,兑换码不能涉及的过长,且最好是纯数字。那么已经失效的优惠码的兑换码是否可以重用?笔者认为,可以。只要保证唯一兑换即可。
  2. 随机性。需要保证兑换码不能轻易的被猜测到,否则会很危险。从有赞生成的兑换码,我们基本是看不出任何规则,很有可能,就是完全随机数字。
  3. 安全性。在用户兑换优惠码时,我们需要做一些安全的限制,类似用户输入密码。例如,连续输错 N 次兑换码,一小时内不允许再次重入,防止暴力破解。当然,恶意用户如果一次就输入正确,这个也是没有办法的。因为,没有绝对的安全。

瞎比比了这么多,简单的说,就是纯随机 + 数据库保证唯一 + 兑换时安全限制

3.2.3 领取情况

/**
* 是否领取
*/
private Boolean isTake;
/**
* 领取用户编号
*/
private Integer userId;
/**
* 领取时间
*/
private Date takeTime;

3.3.4 使用规则

/**
* 固定日期-生效开始时间
*/
private Date validStartTime;
/**
* 固定日期-生效结束时间
*/
private Date validEndTime;
  • 其他使用规则,从 CouponGroup 查询。

3.3.5 使用效果

/**
* 优惠类型
*
* 1-代金卷
* 2-折扣卷 【优惠劵独有】
*/
private Integer preferentialType;
/**
* 折扣
*/
private Double discount;
/**
* 优惠金额,单位:分。
*/
private Integer value;

3.3.6 使用情况

/**
* 是否使用
*/
private Boolean isUsed;
/**
* 使用订单号
*/
private String usedInTid;
/**
* 订单中优惠面值,单位:分
*/
private Integer usedValue;
/**
* 使用时间
*/
private Date usedTime;
  • isUsed ,是否使用。
  • usedInTid ,使用交易编号。当使用方式为:
  • usedValue ,在交易中优惠的金额。
  • usedTime ,使用时间。

3.3 CouponFetchLog

// TODO 3.3 CounponFetchLog

获取优惠券/优惠码领取记录 接口中,看起来是有一个 CounponFetchLog 实体。但是从功能上,Coupon 完全可以满足。有经验的胖友,麻烦告知下。

3.4 CouponVerifyLog

CouponVerifyLog ,优惠券(码)核销记录。

/**
* 记录编号,唯一自增
*/
private Integer id;
/**
* 店铺编号
*/
private Integer shopId;
/**
* 创建时间(验证时间)
*/
private Date createTime;
/**
* 类型
*
* 1-优惠劵
* 2-优惠码
*/
private Integer type;
/**
* 优惠劵(码)分组编号,{@link CouponGroup#id}
*/
private Integer couponGroupId;
/**
* 标题
*/
private String title;
/**
* 使用说明
*/
private String descrption;
/**
* 优惠劵(码)编号,{@link Coupon#id}
*/
private Integer couponId;
/**
* 核销码
*/
private String verifyCode;
/**
* 验证方式
*
* 1-扫码
* 2-手动输入
*/
private Integer verifyType;
/**
* 验证管理员编号
*/
private Integer adminId;
/**
* 是否设置满多少金额可用,单位:分
*
* 0-不限制
* 大于0-多少金额可用
*/
private Integer condition;
/**
* 优惠类型
*
* 1-代金卷
* 2-折扣卷
*/
private Integer preferentialType;
/**
* 折扣
*/
private Double discount;
/**
* 优惠金额,单位:分
*/
private Integer value;
/**
* 使用订单号
*/
private String usedInTid;
  • 整体字段和 Coupon 类似,我们只看几个区别字段。
  • id ,记录编号,唯一自增。
  • verifyType ,输入方式,有扫码手动输入两种类型。
  • adminId ,验证管理员编号。

4. API

基于如下整理 API 类。

4.1 CouponGroupAPI

CouponGroupAPI ,优惠劵(码)组 API 。

4.2 CouponCardAPI

CouponCardAPI ,优惠劵 API 。

4.3 CouponCodeAPI

CouponCodeAPI ,优惠码 API 。和 CouponCardAPI 类似。

4.4 CouponConsumeAPI

CouponConsumeAPI ,优惠码(码)消费 API 。

666. 彩蛋

随着笔者的啰嗦,本文迈入结(彩)束(蛋)部分。

有经验的胖友,可能会问,优惠劵对交易订单的价格计算有什么影响。嘿嘿,这块内容,我们写完营销模块后,写一篇文章统一分享。

在分享一个京东的 促销API

总访客数 && 总访问量