全局唯一随机邀请码实现方式

背景

  • 日常的网站开发中,会遇到网站的促销活动,就有涉及到邀请好礼的功能
  • 成功邀请好友,则获取相应奖励,这时候,就有邀请码的需求
  • 邀请码要求每个用户唯一
  • 方法一. 可根据用户的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
     * 进制转换 27*32(0) + 3*32(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%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的语法代码

转载于:https://www.cnblogs.com/fanfan259/p/11423288.html

你可能感兴趣的:(全局唯一随机邀请码实现方式)