全局唯一隨機邀請碼實現方式


背景

  • 日常的網站開發中,會遇到網站的促銷活動,就有涉及到邀請好禮的功能
  • 成功邀請好友,則獲取相應獎勵,這時候,就有邀請碼的需求
  • 邀請碼要求每個用戶唯一
  • 方法一. 可根據用戶的uid生成邀請碼
    
  • 方法二. 邀請碼可根據某個初始化id生成,用戶主動請求,生成code,綁定uid
    
  • 方法二,這種方式,需額外記錄uid和code關系
  • 方法一,根據uid生成,也可根據code反推出uid,不用額外查詢,比較方便

實現

  • 記錄方法一的實現
  • 由長數字轉換為特定長度的code,首先需確定code的字符范圍
  • 可轉換為 0-9A-Z 36進制數,或者更多字符可添加小寫字符
  • 本次實現 轉換為 32進制數
  • 去掉0 1 和 o 容易混淆的字符和補位字符F,剩余32字符

代碼

php實現

/**

  • Class ShareCodeUtils

  • 邀請碼生成器,基本原理

  • 1)參數用戶ID

  • 2)使用自定義進制轉換之后為:V

  • 3)最小code長度為6位,若不足則在后面添加分隔字符'F':VF

  • 4)在VF后面再隨機補足4位,得到形如 VFAADD

  • 5)反向轉換時以'F'為分界線,'F'后面的不再解析
    */
    class ShareCodeUtils {

    // 32個進制字符(0,1 沒加入,容易和 o l 混淆,O 未加入,F 未加入,用於補位)
    // 順序可進行調整, 增加反推難度
    private static $base = ['H', 'V', 'E', '8', 'S', '2', 'D', 'Z', 'X', '9', 'C', '7', 'P','5', 'I', 'K', '3', 'M', 'J', 'U', 'A', 'R', '4', 'W', 'Y', 'L', 'T', 'N', '6', 'B', 'G', 'Q'];

    // F為補位字符,不能和上述字符重復
    private static $pad = "F";

    // 進制長度
    private static $decimal_len = 32;

    // 生成code最小長度
    private static $code_min_len = 6;

    /**

    • id轉為code
    • 相除去模法
    • @param $id
    • @return string
      */
      public static function idToCode($id)
      {
      \(result = ""; while (floor(\)id / static::$decimal_len) > 0){
      $index = \(id % static::\)decimal_len;
      \(result.= static::\)base[$index];
      \(id = floor(\)id / static::$decimal_len);
      }
      $index = \(id % static::\)decimal_len;
      \(result.= static::\)base[$index];
      // code長度不足,則隨機補全
      \(code_len = strlen(\)result);
      if (\(code_len < static::\)code_min_len) {
      \(result .= static::\)pad;
      for ($i = 0; \(i < static::\)code_min_len - $code_len - 1; $i ++) {
      \(result .= static::\)base[rand(0, static::$decimal_len -1)];
      }
      }
      return $result;
      }

    /**

    • code轉為id
    • 根據code獲取對應的下標
    • 在進行進制轉換
    • eg: N8FASR, F為分隔符, 后面不在處理
    • N ---> 27
    • 8 ---> 3
    • 進制轉換 2732(0) + 332(1) = 123
    • 32(0) ---> 32的0次方
    • 32(1) ---> 32的1次方
    • @param $code
    • @return string
      */
      public static function codeToId($code)
      {
      $result = 0;
      \(base_flip_map = array_flip(static::\)base);
      \(is_pad = strpos(\)code, static::\(pad); if (!empty(\)is_pad)) {
      $len_real = $is_pad;
      } else {
      \(len_real = strlen(\)code);
      }
      for ($i = 0; $i < $len_real; $i ++) {
      $str = \(code[\)i];
      $index = \(base_flip_map[\)str] ?? '';
      if ($index === '') {
      break;
      }
      \(result += pow(static::\)decimal_len, $i) * $index;
      }
      return \(result; } } \)num = "123";
      var_dump(ShareCodeUtils::idToCode(\(num)); \)code = "N8FMJ3";
      var_dump(ShareCodeUtils::codeToId($code));
go實現

package main

import (
"errors"
"fmt"
"math/rand"
"strings"
"time"
)

type code struct {
base string // 進制的包含字符, string類型
decimal uint64 // 進制長度
pad string // 補位字符,若生成的code小於最小長度,則補位+隨機字符, 補位字符不能在進制字符中
len int // code最小長度
}

// id轉code
func (c *code) idToCode (id uint64) string {
mod := uint64(0)
res := ""
for id!=0 {
mod = id % c.decimal
id = id / c.decimal
res += string(c.base[mod])
}
resLen := len(res)
if resLen < c.len {
res += c.pad
for i:=0; i< c.len - resLen - 1; i++ {
rand.Seed(time.Now().UnixNano())
res += string(c.base[rand.Intn(int(c.decimal))])
}
}
return res
}

// code轉id
func (c *code) codeToId (code string) uint64 {
res:=uint64(0)
lenCode:=len(code)

//var baseArr [] byte = []byte(c.base)
baseArr := [] byte (c.base) // 字符串進制轉換為byte數組
baseRev := make(map[byte] int) // 進制數據鍵值轉換為map
for k, v := range baseArr {
	baseRev[v] = k
}

// 查找補位字符的位置
isPad := strings.Index(code, c.pad)
if isPad != -1 {
	lenCode = isPad
}

r := 0
for i:=0; i< lenCode; i++ {
	// 補充字符直接跳過
	if string(code[i]) == c.pad {
		continue
	}
	index := baseRev[code[i]]
	b := uint64(1)
	for j:=0; j < r; j ++ {
		b *= c.decimal
	}
	// pow 類型為 float64 , 類型轉換太麻煩, 所以自己循環實現pow的功能
	//res += float64(index) * math.Pow(float64(32), float64(2))
	res += uint64(index) * b
	r ++
}
return res

}

// 初始化檢查
func (c *code) initCheck () (bool, error) {
lenBase := len(c.base)
// 檢查進制字符
if c.base == "" {
return false, errors.New("base string is nil or empty")
}
// 檢查長度是否符合
if uint64(lenBase) != c.decimal {
return false, errors.New("base length and len not match")
}
return true, errors.New("")
}

func main() {
inviteCode := code{
base: "HVE8S2DZX9C7P5IK3MJUAR4WYLTN6BGQ",
decimal: 32,
pad: "F",
len: 6,
}
// 初始化檢查
if res, err := inviteCode.initCheck(); !res {
fmt.Println(err)
return
}
id := uint64(5509767398598656)
code := inviteCode.idToCode(id)
fmt.Printf("id=%v, code=%v\n", id, code)

code = "HHC59YC8U6S"
id = inviteCode.codeToId(code)
fmt.Printf("code=%v, id=%v\n", code, id)

}

go實現2

package main

import(
"container/list"
"errors"
"fmt"
)

var baseStr string = "HVE8S2DZX9C7P5IK3MJUAR4WYLTN6BGQ"
var base [] byte = []byte(baseStr)
var baseMap map[byte] int

func InitBaseMap(){
baseMap = make(map[byte]int)
for i, v := range base {
baseMap[v] = i
}
}
func Base34(n uint64)([]byte){
quotient := n
mod := uint64(0)
l := list.New()
for quotient != 0 {
//fmt.Println("---quotient:", quotient)
mod = quotient%32
quotient = quotient/32
l.PushFront(base[int(mod)])
//res = append(res, base[int(mod)])
//fmt.Printf("---mod:%d, base:%s\n", mod, string(base[int(mod)]))
}
listLen := l.Len()

if listLen >= 6 {
	res := make([]byte,0,listLen)
	for i := l.Front(); i != nil ; i = i.Next(){
		res = append(res, i.Value.(byte))
	}
	return res
} else {
	res := make([]byte,0,6)
	for i := 0; i < 6; i++ {
		if i < 6-listLen {
			res = append(res, base[0])
		} else {
			res = append(res, l.Front().Value.(byte))
			l.Remove(l.Front())
		}

	}
	return res
}

}

func Base34ToNum(str []byte)(uint64, error){
if baseMap == nil {
return 0, errors.New("no init base map")
}
if str == nil || len(str) == 0 {
return 0, errors.New("parameter is nil or empty")
}
var res uint64 = 0
var r uint64 = 0
for i:=len(str)-1; i>=0; i-- {
v, ok := baseMap[str[i]]
if !ok {
fmt.Printf("")
return 0, errors.New("character is not base")
}
var b uint64 = 1
for j:=uint64(0); j<r; j++ {
b = 32
}
res += b
uint64(v)
r++
}
return res, nil
}

func main() {
InitBaseMap()
fmt.Printf("len(baseStr):%d, len(base):%d\n", len(baseStr), len(base))
res := Base34(1544804416)
fmt.Printf("=base:1544804416->%s, %d\n", string(res), len(res))
str := "VIVZ4EH"
num, err := Base34ToNum([]byte(str))
if err == nil {
fmt.Printf("
=base:%s->%d\n", str, num)
} else {
fmt.Printf("===============err:%s\n", err.Error())
}
}

總結

  • 本次實現由 php 和 go 兩種語言實現
  • 最大的心得就是 go中的 類型轉換是比較麻煩的,因為都是強類型
  • 和php 還不太一樣
  • 但也算是進一步熟悉了go的語法代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM