Java根据唯一ID生成唯一邀请码
场景:
需要通过资料码,拿到数据库中的某个文件资源
需要通过邀请码,对某个用户做一些操作
这个资料码和邀请码都是随机码,多位,由数字和字母组成,可以与整形类型的ID相互转换
网络上找到的实现方法,进行一些简单思考和更改
1. 如何保证不重复性质?
我们发现进制数是不重复的,我们来看一下进制数
比如二进制, 二位二进制数可以表示 2^2个十进制数 [0,3]
比如四进制, 二位四进制数可以表示 4^2个十进制数 [0,15]
比如三十二进制,二位三十二进制数可以表示 32^2个十进制数 [0,1023]
我们得出: X位N进制数可以表示 N^X 个十进制数
不重复与唯一数字ID的联系
当唯一数字ID可以用十进制表示的时候,那么该十进制就有对应的唯一的某进制的表示,同理,某进制数有唯一的十进制表示。
假设用户在数据库中主键ID为17,我们用二进制作为邀请码来表示这个用户: 10001
,如果我们希望邀请码是8位数的,那么可以在前面或者后面用一些不属于 二进制 的字符补位,比如 2 3 4 a b c这些不属于二进制的字符。那么就有邀请码:
J10001
我们后续在通过邀请码计算用户的主键ID时候,就可以进行: 过滤进制外的字符,提取进制数,计算出十进制ID
2. 如何引申到邀请码(自定义数字+字母)?
我们把进制引申一下
我们看到平时见到的十六进制 用a表示10 b表示11等等,都是通过一个字符来代替这个数字
那么,二进制就是 0 1表示吗?
我们可以拿其它符号表示,效果也是一样的
0 | 1 |
---|---|
a | b |
那么上面这个自定义二进制表示十进制数 3 就是: ab
(等同于原来的二进制11
)
同样的, 四进制就是 0 1 2 3 表示吗?
我们拿其他符号表示,效果也是一样的
0 | 1 | 2 | 3 |
---|---|---|---|
w | a | n | y |
那么上面这个自定义四进制表示十进制数 13 就是: ya
(等同于原来的四进制 31
)
我们把这种引申的方法引到我们的邀请码中
假设我们的邀请码是4位的32进制数+随机字符补齐
首先我们自定义一下我们的32进制数
/** 自定义进制(0,1没有加入,容易与o,l混淆) */
private static final char[] r=new char[]
{'q', 'w', 'e', '8', 'a', 's', '2', 'd',
'z', 'x', '9', 'c', '7', 'p', '5', 'i',
'k', '3', 'm', 'j', 'u', 'f', 'r', '4',
'v', 'y', 'l', 't', 'n', '6', 'b', 'g'};
这样看,三十二进制中每一位的 0 用 q 表示, 1 用 w 表示,如此类推,31 用 g 表示。同时,我们在自定义的时候尽量不要加入 0 和1 ,容易与 o, l 混淆
接下来我们来自定义一下用来补齐的随机字符
我们用 o
作为分隔符,前面是随机数字,后面是唯一进制数
比如 7ojp
邀请码中, 7o
是随机补齐字符,过滤掉,剩下的 jp
就是我们的自定义32进制数,我们查表知道: j表示19,p表示13,那么计算出来的ID就是
ID = 13 * 32^0+ 19 * 32^1 = 13 + 608 = 621
我们换算得出用户ID是621。
同样的,根据这个ID,利用我们的自定义32进制和随机字符补齐,就可以得出一个这个ID一对一的邀请码
首先我们明确一点,之前提到的 邀请码中的 7o
是被这个ID一对一唯一生成过程中产生的随机补齐字符,所以接下来我们就假设生成的 补齐字符是 7o
如果在这里有误解,请理清产生过程和先来后到
根据ID 621,我们得出进制数表示 jp
在不足4位,我们补足4位,生成随机补齐字符 7o
放在进制数前面
生成邀请码 7ojp
如果已足四位则不用补齐,如果是三位,前面只补 o
,如果三位以下,前面补 对应个数随机数字
+ o
我们生成的邀请码的一些性质
1. 最大可表示的 ID
X位N进制可以表示 max = N^X 的十进制ID
比如 4位32进制可以表示 max = 1_048_576 的十进制ID (一百万)
2. 其他
好像没啥其他…
贴上网传代码(注意:没有处理溢出)
public class CodeUtil {
/** 自定义进制(0,1没有加入,容易与o,l混淆) */
private static final char[] r=new char[]{'q', 'w', 'e', '8', 'a', 's', '2', 'd', 'z', 'x', '9', 'c', '7', 'p', '5', 'i', 'k', '3', 'm', 'j', 'u', 'f', 'r', '4', 'v', 'y', 'l', 't', 'n', '6', 'b', 'g', 'h'};
/** (不能与自定义进制有重复) */
private static final char b='o';
/** 进制长度 */
private static final int binLen=r.length;
/** 序列长度 */
private static final int s=4;
/**
* 根据ID生成六位随机码
* @param id ID
* @return 随机码
*/
public static String toSerialCode(long id) {
char[] buf=new char[32];
int charPos=32;
while((id / binLen) > 0) {
int ind=(int)(id % binLen);
// System.out.println(num + "-->" + ind);
buf[--charPos]=r[ind];
id /= binLen;
}
buf[--charPos]=r[(int)(id % binLen)];
// System.out.println(num + "-->" + num % binLen);
String str=new String(buf, charPos, (32 - charPos));
// 不够长度的自动随机补全
if(str.length() < s) {
StringBuilder sb=new StringBuilder();
sb.append(b);
Random rnd=new Random();
for(int i=1; i < s - str.length(); i++) {
sb.append(r[rnd.nextInt(binLen)]);
}
str+=sb.toString();
}
return str;
}
public static long codeToId(String code) {
char chs[]=code.toCharArray();
long res=0L;
for(int i=0; i < chs.length; i++) {
int ind=0;
for(int j=0; j < binLen; j++) {
if(chs[i] == r[j]) {
ind=j;
break;
}
}
if(chs[i] == b) {
break;
}
if(i > 0) {
res=res * binLen + ind;
} else {
res=ind;
}
// System.out.println(ind + "-->" + res);
}
return res;
}
}