一般订单号之类的业务编码,大概都由:固定字符、日期和流水号组成。如果第一个订单的流水号是 0001,第二个是 0002 这样子的话,很容易就被别人知道你的订单量。所以,就产生了流水号加密的需求。例如第一个订单是 3765,第二个是 1927,第三个是 0014,这样订单号就毫无规律可循,自然也就看不出来你今天到底有几个订单了。
但流水号加密有几个问题:
1、唯一性问题 订单号是需要保证唯一的,如果用随机数或者时间戳之类,理论上是不可能保证唯一性的。及时毫秒数加 4 位随机数,碰到就是这么挫,照样重复给你看。
2、长度问题 如果可以不管长度,那么采用 UUID 或者自己搞个雪花算法也可以保证唯一性。但作为一个业务编码,长度超过 15 位就难以记忆和传递了。
3、内含业务信息问题 譬如包含组织机构代码、包含业务发生日期、业务类型等等。 基于以上几个问题,订单号如果要满足 15 位以内(数字 /符号 /字母),并且要包含日期、业务类型等信息,估计流水号也就只能剩下 4-5 位了。5 位也有 10 万的容量,一天 10 万单的企业也是不多的,真的不够,增加一位就是 100 万单了。
那么问题回到了如何加密这个流水号了。简单交换位置肯定不灵,一眼就看穿。但搞一个从 00000-99999 的集合随机取,取后删除。倒是效率很高,也足够乱序,但空间资源占用太高了,不是什么好方案。搞个大字典也能起到同样作用,但坏处也是一个样,太占空间。 那么,有什么办法可以减少空间的占用,同时也能达到加密数据的目的呢?让我们步步推进,先从一个两位数的加密开始。
对于一个两位数而言,通过一个较小的字典就可以进行加密。譬如 00 转换为 73,01 转换为 36,02 转换为 91,只要一对一就可以实现数据的简单加密。在你不知道字典和输入的情况下,是不可能解密的。因为业务编码是服务端产生的,客户端是只知道输出不知道输入的,当然更没有这个密码本,所以客户端的信息不可能支持由密文反推明文,完美地实现了两位数的加密。
那么三位数怎么办呢?把字典扩展到 1000 位么?显然不行,这样就会导致字典的无限扩张,直到你的内存无法承载为止。 这种情况我们可以通过可逆算法来实现加密而不必扩大字典。我们可以对低位先进行加密,譬如 000 可以用字典转换后两位变成 073,001 转换成 036。当然,止步于此的话,首位都是 0,容易被人一眼看穿。我们可以把首位 0 交换到末位,073 变成 730,然后对 30 再进行一次替换,这样 000 变成 768(000->073->730->768),001 变成 325(001->036->360-325),面目全非了吧。
划重点:只要这个过程是可逆的,且替换次数一致,源数字和目标数字就永远保持一对一的关系。也就是说,不同的源数字不会被转换成相同的目标数字。简单地说,就是不会导致重复。这样,无论原数字有多少位,我们都可以通过位数减一次迭代,得到完全加密的目标数字。当然,利用更大的字典,我们就可以以更少的迭代次数来完成加密。
C#代码在此: https://github.com/xuanbg/WCF/blob/master/Redis/Generator.cs
Java 代码虽然也有,但在公司的项目里面,不方便贴出来,大家自己翻译吧。