今日頭條極速版邀請碼的功能是怎麼實現的

平時我們看到的邀請碼(C8RB8DXF)一般有兩種類型:純數字、數字+字母(通常大寫),而邀請碼的長度通常在6位左右就是爲了滿足簡潔性。
邀請碼的特性
唯一性:確保每個用戶的邀請碼都是獨一無二的,這樣系統才能判定誰爲邀請者,甚至可以根據邀請碼進行反向推導。
隨機性:不能讓用戶從邀請碼上輕易的看出生成的規則。
高效性:生成邀請碼的算法不能過於複雜,或耗費過度系統資源。
簡潔性:用戶可以方便的輸入,記錄,辨別是否輸入錯誤等。
隨機生成邀請碼
無論是純數字還是數字加大寫字母形式,使用隨機算法生成一個邀請碼然後判斷此隨機碼是否已經被使用,如果被使用則重新生成。這可能是最初步的思路,但此種方法弊端甚多。

以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編碼。

/**

邀請碼生成器,基本原理:
1)入參用戶ID:1
2)使用自定義進制轉換之後爲:V
3)轉換未字符串,並在後面添加'A':VA
4)在VA後面再隨機補足4位,得到:VAHKHE
5)反向轉換時以'A'爲分界線,'A'後面的不再解析


@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數組裏面的數據進行擴充,比如講小寫字母也添加進來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章