zoukankan      html  css  js  c++  java
  • 全局唯一随机邀请码实现方式

    背景

    • 日常的网站开发中,会遇到网站的促销活动,就有涉及到邀请好礼的功能
    • 成功邀请好友,则获取相应奖励,这时候,就有邀请码的需求
    • 邀请码要求每个用户唯一
    • 方法一. 可根据用户的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 ", id, code)

    code = "HHC59YC8U6S"
    id = inviteCode.codeToId(code)
    fmt.Printf("code=%v, id=%v
    ", 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 ", 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 ", len(baseStr), len(base))
    res := Base34(1544804416)
    fmt.Printf("=base:1544804416->%s, %d ", string(res), len(res))
    str := "VIVZ4EH"
    num, err := Base34ToNum([]byte(str))
    if err == nil {
    fmt.Printf("
    =base:%s->%d ", str, num)
    } else {
    fmt.Printf("===============err:%s ", err.Error())
    }
    }

    总结

    • 本次实现由 php 和 go 两种语言实现
    • 最大的心得就是 go中的 类型转换是比较麻烦的,因为都是强类型
    • 和php 还不太一样
    • 但也算是进一步熟悉了go的语法代码
  • 相关阅读:
    函数式编程中的基本概念
    【VS】Visual Studio 就可以反编译查看源码了,再见了 Reflector
    【C#】CsvHelper 使用手册
    【C#】使用 System.Globalization.DateTimeFormatInfo 屏蔽系统时间格式对代码的影响
    【swagger】C# 中 swagger 的使用及避坑
    【C#】Newtonsoft.Json 中 JArray 添加数组报错:Could not determine JSON object type for type 'xxx'
    【C#】比较 Random 与 RandomNumberGenerator 生成随机字符串
    【C#】正则进阶
    【C#】写文件时如何去掉编码前缀
    【C#】定时器保活机制引起的内存泄露问题
  • 原文地址:https://www.cnblogs.com/fanfan259/p/11423288.html
Copyright © 2011-2022 走看看