在只有PHP和mysql的环境下,做一个简单的发券、核销程序。其中券的兑换码必须为12位数字,数据量不会超过千万。如何生成不重复且乱序的券码?
最糟糕的方案是纯随机,然后到表里查一遍是否重复,重复则重新生成;不重复则插入
一个自认为好点的方案是用户id加一位随机数加截取时间戳后x位
例如用户ID为123,则是3位,加随机数4位。此时剩余8位则截取时间戳后8位补上。重复概率应该就很低了
在尽可能不遍历表的情况下,还有更难以重复的生成算法吗?求不吝赐教
怪我咯2017-04-10 18:07:05
如果是按照用户ID和时间戳等生成md5摘要的方式的话,可以用base_convert将哈希后的md5值转为10进制,不过这样转出来的结果值长度肯定就不止12位了,还需要用字符串截取函数截取一下,但原本几十位的十进制数只截取12位那么唯一性就无法保证了,可能还不如直接截取ID加时间戳,看你的描述应该是在用户请求的时候才生成码,那么一个用户同一秒不能生成多张的情况下组合部分时间戳和用户ID还是没问题的。
怪我咯2017-04-10 18:07:05
消息摘要算法太混沌了,压缩到12位后效果和随机没有区别,多了还是会碰撞。
楼主的做法(id + 时间戳 + 随机数)基本能满足不碰撞的要求了,问题在于乱序。
可以仿效线性同余生成伪随机数的方法,找个大素数a和常数b,把id + 时间戳 + 随机数生成的数字输入函数f(x) = a * x + b (mod 1,000,000,000,000)
里,只要a足够大(数量级跟1,000,000,000,000相近)就很随机了。而且由于a是素数,f是可逆的,这也保证了只要输入不重复,输出也不会重复。
另外建议拼接数字时把随机数放在高位,这样运算后输出的差别更大。
PHPz2017-04-10 18:07:05
我的卡券类号码都是一位特定大写随机英文字典中抽一位+uid+time()+rand(十位数),然后截取16位出来生成比如T132-0161-2052-3091,有些超市用户一个月就分发百万张出去,没有重复的反馈.
之所以加英文,主要还是增加接口查询被随机碰撞破解的难度.