今日头条极速版邀请码的功能是怎么实现的呢?

举报
银河 发表于 2019/05/24 11:31:13 2019/05/24
【摘要】 邀请码(例如C8RB8DXF)的特性唯一性:确保每个用户的邀请码都是独一无二的,这样系统才能判定谁为邀请者,甚至可以根据邀请码进行反向推导。随机性:不能让用户从邀请码上轻易的看出生成的规则。高效性:生成邀请码的算法不能过于复杂,或耗费过度系统资源。简洁性:用户可以方便的输入,记录,辨别是否输入错误等。平时我们看到的邀请码一般有两种类型:纯数字、数字+字母(通常大写),而邀请码的长度通常在6位...

邀请码(例如C8RB8DXF)的特性

唯一性:确保每个用户的邀请码都是独一无二的,这样系统才能判定谁为邀请者,甚至可以根据邀请码进行反向推导。

随机性:不能让用户从邀请码上轻易的看出生成的规则。

高效性:生成邀请码的算法不能过于复杂,或耗费过度系统资源。

简洁性:用户可以方便的输入,记录,辨别是否输入错误等。

平时我们看到的邀请码一般有两种类型:纯数字、数字+字母(通常大写),而邀请码的长度通常在6位左右(例如C8RB8DXF)就是为了满足简洁性。


随机生成邀请码

无论是纯数字还是数字加大写字母形式,使用随机算法生成一个邀请码然后判断此随机码是否已经被使用,如果被使用则重新生成。这可能是最初步的思路,但此种方法弊端甚多。


以6为随机数为例说明。6位随机数取0-9共10个数字,生成邀请码的范围为000000-999999,总数为10的6次方,也就是100万。试想一下,如果有50万的用户,那么采用随机数的生成,每次生成的重复概率将在50%以上,而且会越来越重复率越高,多么可怕的性能损耗。


当然,在用户量比较少的情况下此种方法不是完全不可行。可以通过数据库或redis预先生成一批邀请码,当注册新用户或用户使用邀请码的时候将邀请码分配给对应的用户。此种补漏的方法虽然解决了一部分性能的问题,但从根本上还是需要消耗数据库或redis资源,时间维度和空间维度都有一定的损耗。


base编码方式实现

在网络传输中,最常用的base编码是base64编码,那么我们就借鉴一下base64的编码思路来生成邀请码。


一般来说生成一个用户的邀请码需要一个唯一的输入参数,这里就用用户的ID(长整型数)来作为输入参数,输出结果为6为数字+大写字母。同时,通过邀请码可以反推出用户的ID。


首先,指定6位邀请码的数据格式:


6位邀请码:0-9十个数字,26个大写字母,在这其中再去除掉0和1,O和I防止它们两两混淆。

1

这样,总共获得了32个可用字符。那么能生成的邀请码总数为32的6次方,也就是1073741824个。10亿+个邀请码,在业务初期足够用户使用,如果随着业务的发展可对位数进行扩充。


一般情况用户ID或用户编号都为长整型数且递增,那么现在我们将用户ID映射成一个6位的base32编码。


/**

 * 邀请码生成器,基本原理:<br/>

 * 1)入参用户ID:1 <br/>

 * 2)使用自定义进制转换之后为:V <br/>

 * 3)转换未字符串,并在后面添加'A':VA <br/>

 * 4)在VA后面再随机补足4位,得到:VAHKHE <br/>

 * 5)反向转换时以'A'为分界线,'A'后面的不再解析 <br/>

 *

 * @author zzs

 */

public class ShareCodeUtils {


    /**

     * 自定义进制(0,1没有加入,容易与o,l混淆),数组顺序可进行调整增加反推难度,A用来补位因此此数组不包含A,共31个字符。

     */

    private static final char[] BASE = new char[]{'H', 'V', 'E', '8', 'S', '2', 'D', 'Z', 'X', '9', 'C', '7', 'P',

            '5', 'I', 'K', '3', 'M', 'J', 'U', 'F', 'R', '4', 'W', 'Y', 'L', 'T', 'N', '6', 'B', 'G', 'Q'};


    /**

     * A补位字符,不能与自定义重复

     */

    private static final char SUFFIX_CHAR = 'A';


    /**

     * 进制长度

     */

    private static final int BIN_LEN = BASE.length;


    /**

     * 生成邀请码最小长度

     */

    private static final int CODE_LEN = 6;


    /**

     * ID转换为邀请码

     *

     * @param id

     * @return

     */

    public static String idToCode(Long id) {

        char[] buf = new char[BIN_LEN];

        int charPos = BIN_LEN;


        // 当id除以数组长度结果大于0,则进行取模操作,并以取模的值作为数组的坐标获得对应的字符

        while (id / BIN_LEN > 0) {

            int index = (int) (id % BIN_LEN);

            buf[--charPos] = BASE[index];

            id /= BIN_LEN;

        }


        buf[--charPos] = BASE[(int) (id % BIN_LEN)];

        // 将字符数组转化为字符串

        String result = new String(buf, charPos, BIN_LEN - charPos);


        // 长度不足指定长度则随机补全

        int len = result.length();

        if (len < CODE_LEN) {

            StringBuilder sb = new StringBuilder();

            sb.append(SUFFIX_CHAR);

            Random random = new Random();

            // 去除SUFFIX_CHAR本身占位之后需要补齐的位数

            for (int i = 0; i < CODE_LEN - len - 1; i++) {

                sb.append(BASE[random.nextInt(BIN_LEN)]);

            }


            result += sb.toString();

        }


        return result;

    }


    /**

     * 邀请码解析出ID<br/>

     * 基本操作思路恰好与idToCode反向操作。

     *

     * @param code

     * @return

     */

    public static Long codeToId(String code) {

        char[] charArray = code.toCharArray();

        long result = 0L;

        for (int i = 0; i < charArray.length; i++) {

            int index = 0;

            for (int j = 0; j < BIN_LEN; j++) {

                if (charArray[i] == BASE[j]) {

                    index = j;

                    break;

                }

            }


            if (charArray[i] == SUFFIX_CHAR) {

                break;

            }


            if (i > 0) {

                result = result * BIN_LEN + index;

            } else {

                result = index;

            }

        }


        return result;


    }


    public static void main(String[] args) {

        String code = idToCode(1L);

        System.out.println(code);

        System.out.println(codeToId(code));

    }


}

以上方法通过31个字符长度的数组,外加一个分割字符“A”,完成了6位邀请码的生成过程。同时,根据生成的邀请码又可以反推出用户ID(或用户编号)。此种方法简单高效,又确保了根据每个用户ID生成的邀请码的唯一性。


当然,可以通过打乱BASE数组中字符的顺序让产生的邀请码更加随机一些。如果想让ID的复杂度更高,可以先将ID补全为指定位数,然后给它在指定位置加“盐”或者调换位置等方式进行处理。但需要保证加盐或调换之后ID的唯一性。


业务扩充

当业务不断发展,如果10亿的邀请码依旧无法满足业务需求,则可进行以下方式进行扩充: 

- 将邀请码位数进行扩充,比如变为7位、8位或更多位。 

- 将BASE数组里面的数据进行扩充,比如讲小写字母也添加进来。



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。