代码

代码仓库:地址

代码分支:master

博客:地址

简介

优惠券是常见的营销工具,每逢佳节必有促销活动,有活动就会有优惠券。线下活动通常限量提供优惠券,因此不需要特殊设计,但是互联网环境下,线上活动开展频繁,而且线上活动用户体量要比线下活动大很多,通常以万为单位进行发放,因此需要优化优惠券存储,降低空间成本(互联网活动通常采用广撒网的方式)。这里需要特别注意本文针对的优惠券需求,有以下几个特殊点:

  • 预先生成,在活动正式开始前生成优惠券(生成指的是需要预先知道优惠券活动Id、优惠券面值、优惠券序号,例如活动id为1,面值100,满1000减100,优惠券序号1~1,000,000)
  • 优惠券体量大,以万为单位,通常在10万级别以上

在互联网电商中的优惠券通常不需要预先生成,只需要在用户领取时分配优惠券信息即可,在线下和线上结合使用场景中,预生成方式很实用,在预生成的模式下,如果全量存储优惠券将浪费大量空间,因为优惠券不会100%被使用(瓶装饮料经常会出现输入优惠编码兑换奖品活动)。

需求

线上线下营销需要优惠券功能来支撑,传统的方式是生成优惠券方案,提供以下内容:

  • 活动id
  • 活动名称
  • 优惠券面值
  • 优惠券使用条件
  • 发放条件
  • 发放数量

创建优惠券方案后不会立即生成优惠券,只有当用户领取的时候才会生成用户优惠券记录,这是主流方案,可以避免生成大量优惠券记录,浪费存储空间,但是没办法满足需要预生成的要求。我们需要设置一套优惠券编码方案,既可以满足预生成要求,同时要避免浪费存储空间。

设计思路

预生成的优惠券号通常是一段无规则的字符串,类似于:yuIkJGGS,用户输入优惠编号领取对应的优惠券,yuIkJGGS经过解析可以得到活动id信息,

通过上述分析可以知道,优惠券通过活动id就可以知道优惠券详细信息,为了防止重复使用,我们可以为每一张优惠券设置一个唯一编号,同时为了验证优惠券的有效性,需要添加校验码信息,因此一个优惠券通常有以下字段组成:

  • 活动id
  • 优惠券编号
  • 校验码

我们可以设置一个功能函数:H活动Id,优惠券编号) => code,输入活动Id、优惠券编号参数,输出code,我们将code设置为校验码,H可以是md5、sha256等一系列单项函数。

得到活动Id、优惠券编号、校验码之后我们通过对其进行编码得到优惠券号信息,我们假设编码函数为E,那么转换函数可以写成如下形式:

E(活动Id、优惠券编号, H(活动Id、优惠券编号)) => yuIkJGGS    假设输出是yuIkJGGS

那么我们只需要设计实现H、E两个函数即可。

H函数分析

H可以理解为一个签名函数,我们使用这个函数生成的code来鉴别真伪,方式他人仿照,那么这里有很多种实现方案,例如,我们可以参照区块链签名方案(注意这里指示为了讲解方便,本文不是按照这个方式实现)。

secp256k1(keccak256(活动Id + 优惠券编码), privateKey) => 签名数据

签名数据长度不满足要求,我们可以截取前[n, n+m]位(bit)作为校验码,

本文使用13bit来表示检验码,计算方式如下所示

  • 前6位表示(活动Id + 优惠编码)组成数字中的二进制中1的个数,例如,活动id为2,二进制为10,优惠编码是5,二进制为101,那么(活动Id + 优惠编码)二进制是10101,1的个数为3
  • 后7位表示活动Id+优惠编码组成的数字进行取模运算,具体看代码

伪代码如下所示:

/// couponSchemeId表示活动id, redeemSerialNum表示优惠券序号
/// COUPON_ID_BIT_LEN 表示活动Id二进制位数
redeemSerialNum = redeemSerialNum << COUPON_ID_BIT_LEN;
/// r 表示 活动id + 优惠券编号
long r = couponSchemeId | redeemSerialNum;
/// 计算二进制表示形式中的1的个数(前6位值)
long n = numOfOne(r);
/// 取模运算
long re = r % DIVISOR;
/// 加入后7位值
n = (n << REMAINDER_BIT_LEN) | re;
return n;

E函数分析

E函数将输入的参数进行编码生成无规则字符串,同时能够对编号后的字符串进行还原,因此不能使用hash这类单向函数。这里我们参考进制转换,例如,二进制只有两个元素(0,1),16进制有16个元素(0-9,a-f),16进制中的某一位可以有16种选择,而二进制只有2种选择,类似的我们要先寻找一个编码空间。

我们使用a-z,A-Z,数字0-9元素,同时去除了容易混淆的大写O、大写I,(60个元素):

abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXZY0123456789 

我们来回顾一下10进制转二进制运算规则:

十进制转换成二进制1717/2 = 8 ... 18/2  = 4 ... 04/2  = 2 ... 02/2  = 1 ... 01/2  = 0 ... 1
十进制转换成n进制类似

转换代码如下所示:

private static final char[] r =new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o','p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E','F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W','X', 'Z', 'Y', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
private static final int l = r.length;public static String enRedeemCode(long redeemNum) {/// buf存储余数信息,这里需要保证余数位数小于buf长度char[] buf = new char[32];int charPos = 32;while ((redeemNum / l) > 0) {/// 得到余数int ind = (int) (redeemNum % l);/// 将余数映射到r字符空间中,buf存储映射后的字符buf[--charPos] = r[ind];redeemNum /= l;}buf[--charPos] = r[(int) (redeemNum % l)];/// 将存储的字符数组转化为字符串String str = new String(buf, charPos, (32 - charPos));return str;
}

上述代码中的redeemNum由活动Id、优惠券编号、校验码组成,组成结构如下:

-------------------------------------------------------------
|30位优惠券编号 |    15位活动id    |     13位校验位(前6 + 后7) |
---------------|------------------|--------------------------|- 优惠券编号:30位bit位可表示范围:1073741824(10亿优惠券)
- 活动Id:15位表示,可以表示范围:32768(考虑到运营活动的频率,15位足够,365天每天有运营活动,可以使用89年)

伪代码如下所示:

/*** 生成优惠券编码* -------------------------------------------------------* 30位优惠券编号         15位活动id      13位校验位(前6 + 后7)* -------------------|--------------|--------------------|** @param couponSchemeId 活动Id* @param redeemSerialNum 优惠券编号* @return*/
public static long enRedeemNum(long couponSchemeId, long redeemSerialNum) {/// COUPON_ID_BIT_LEN表示活动Id二进制位数,这里是15redeemSerialNum = redeemSerialNum << COUPON_ID_BIT_LEN;/// 使用或运算将活动id和优惠券编号组合在一起long r = couponSchemeId | redeemSerialNum;/// 下面整合校验码到编码中long n = numOfOne(r);long re = r % DIVISOR;r = (r << NUMBER_OF_ONE_BIT_LEN) | n;r = (r << REMAINDER_BIT_LEN) | re;return r;
}

生成优惠券编码后,在利用进制转换函数将Long类型转换为字符串。

校验函数分析

完成编码操作后得到优惠券编码信息,例如获取活动id为1,优惠券序号为10,那么得到编码为:dBhLzM

当用户输入上述编码后,需要对编码的正确性进行校验,并且需要解码出活动id和优惠券编号,逆向操作上述编码可以很容易实现,校验代码如下:

/// 将优惠券编码转化为10进制表示形式
public static long deRedeemCode(String redeemCode) {char chs[] = redeemCode.toCharArray();long res = 0L;for (int i = 0; i < chs.length; i++) {int ind = -1;for (int j = 0; j < l; j++) {if (chs[i] == r[j]) {ind = j;break;}}if (ind == -1) {return -1;}if (i > 0) {res = res * l + ind;} else {res = ind;}}return res;
}校验10进制表示的优惠券编码public static boolean checkVaild(long redeemNum) {if (redeemNum > 0) {/// 先获取校验码后7位long checkSum = redeemNum & REMAINDER_MASK;/// 得到校验码前6位long n = (redeemNum & NUMBER_OF_ONE_MASK) >> REMAINDER_BIT_LEN;/// 获取 优惠券编号 + 活动Id数值信息long r = redeemNum >> CHECK_SUM_BIT_LEN;/// 校验前6位信息是否一致if (numOfOne(r) == n) {/// 校验后7位数值是否一致if (r % DIVISOR == checkSum) {return Boolean.TRUE;}}}return Boolean.FALSE;
}

至此完成了无存储式优惠券编码方案

总结

主要实现了如下功能:

  • 选取字符空间
  • 设置活动Id、优惠券编号、校验码结构(二进制位数以及二进制内容组建方式)
  • 将10进制转化为N进制(本文N表示60)
  • 将转化后的N进制映射到字符空间中,然后转化为字符串输出
  • 设计校验函数
  • 解码函数

联系方式

技术更新换代速度很快,我们无法在有限时间掌握全部知识,但我们可以在他人的基础上进行快速学习,学习也是枯燥无味的,加入我们学习牛人经验:

点击:加群讨论

无存储式优惠券编码方案相关推荐

  1. 基于 Amazon Lambda 的无服务器视频转码方案

    在 re:Invent 2020 上,Amazon Lambda推出了大函数支持.Amazon Lambda客户可以设置Amazon Lambda函数的最大内存为 10,240 MB(10GB),与之 ...

  2. 游戏服务器框架升级-无入侵式代理解决方案

    框架升级的目的 主要为了提高服务器的整体承载能力以及游戏的体验. 框架升级遇到的主要障碍 主要遇到的困难,早期最原始的方案,就是直接进行业务代码切割,但对于一个线上 运营了多年的项目,系统模块耦合得非 ...

  3. 基于WASM的无侵入式全链路A/B Test实践

    简介:我们都知道,服务网格(ServiceMesh)可以为运行其上的微服务提供无侵入式的流量治理能力.通过配置VirtualService和DestinationRule,即可实现流量管理.超时重试. ...

  4. 【转】刨根究底字符编码之九——字符编码方案的演变与字节序

    字符编码方案的演变与字节序 一.字符编码方案的演变 1. 根据前面的介绍,对于字符编码方案的演变,我们大致上可简单地划分为三个阶段: ① ASCII编码方案阶段 → ② ANSI编码方案阶段 → ③ ...

  5. 播放视频无声音,视频编码来解决

    艾瑞巴蒂大家好,我二狗子又来啦!这段时间关于疫情的好消息不断,各个地区的疫情都得到了有效的控制,做到了新增病例个位数增长甚至连续多天零增长!这不,二狗子也终于可以出村回到大城市上班啦!由于要坐很久的火 ...

  6. 模块预制式数据中心方案评估与建议

    我国数据中心的发展 概况 大数据 (big data , mega data)信息技术产业是国家重点扶持的七大战略性新兴产业之一.在麦肯锡全球研究所McKinsey Global Institute ...

  7. python:编解码器基类之增量式的编码和解码

    python:编解码器基类之增量式的编码和解码 IncrementalEncoder 对象 IncrementalDecoder 对象 IncrementalEncoder 和 Incremental ...

  8. Spring Boot 无侵入式 实现 API 接口统一 JSON 格式返回

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 无侵入式 统一返回JSON格式 其实本没有没打算写这篇博客 ...

  9. DDos攻击,使用深度学习中 栈式自编码的算法

    转自:http://www.airghc.top/2016/11/10/Dection-DDos/ 最近研究了一篇论文,关于检测DDos攻击,使用了深度学习中 栈式自编码的算法,现在简要介绍一下内容 ...

最新文章

  1. BCI里程碑!脑机接口首次让患者输出完整句子
  2. 【小题目】判断一个数字是否是3的倍数
  3. php pdo mysql query_PHP+MYSQL中使用PDO的query方法
  4. 正则表达式贪婪与非贪婪模式
  5. 前端学习(2732):重读vue电商网站42之添加富文本编辑器
  6. python图标库_python图形库
  7. 7.13 Python基础语法
  8. 原生ajax 和jquery ajax 个人总结
  9. VS2010+OpenCV2.4.9配置
  10. mysql 系统变量_MySQL系统变量(查看和修改)
  11. 机器学习——DBN深度信念网络详解
  12. java http请求工具类全功能(get、put、delete、post、文件上传),使用easy-okhttp
  13. 小白用Math对象随机生成一个名字
  14. 计算机打印机能不能取消正在,打印机怎么取消正在排队打印的任务? 打印机删除打印任务的教程...
  15. 企业网络安全的重要性
  16. 计算机网络中OUI是什么意思,抓包出现oui Unknown是什么意思,请各位高手指教!!
  17. building sasl.wrapper extention
  18. 期货市场倒挂什么意思(期货市场倒挂什么意思啊)
  19. 综合应用 -- 购物车
  20. java debug dll_JavaDebug.dll,下载,简介,描述,修复,等相关问题一站搞定_DLL之家

热门文章

  1. 飞猪南极IP引发大讨论,承运船公司发说明回应质疑
  2. python和C++
  3. Linux学习笔记2:文件管理
  4. http 重定向 302报文
  5. 贝塞尔曲线能够很好的拟合车道线吗?
  6. 【JavaWeb】关于WebSocket的IM在线聊天技术(一)
  7. 戴尔710服务器硬盘灯,DELL R710服务器,做RAID5,更换一个硬盘后硬盘灯,黄灯和绿灯交替不断亮??急急求助...
  8. 【PCB硬件】PCB布线规范技巧
  9. 课后实践9:以拼多多为例,原型设计
  10. 数据分析案例-基于随机森林算法的商品评价情感分析