博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
go 生成邀请码,可逆
阅读量:4099 次
发布时间:2019-05-25

本文共 6645 字,大约阅读时间需要 22 分钟。

背景

  • 日常的网站开发中,会遇到网站的促销活动,就有涉及到邀请好礼的功能
  • 成功邀请好友,则获取相应奖励,这时候,就有邀请码的需求
  • 邀请码要求每个用户唯一
  • 方法一. 可根据用户的uid生成邀请码
  • 方法二. 邀请码可根据某个初始化id生成,用户主动请求,生成code,绑定uid
  • 方法二,这种方式,需额外记录uid和code关系
  • 方法一,根据uid生成,也可根据code反推出uid,不用额外查询,比较方便

实现

  • 记录方法一的实现
  • 由长数字转换为特定长度的code,首先需确定code的字符范围
  • 可转换为 0-9A-Z 36进制数,或者更多字符可添加小写字符
  • 本次实现 转换为 32进制数
  • 去掉0 1 和 o 容易混淆的字符和补位字符F,剩余32字符

代码

go实现方法一

package timport (	"errors"	"fmt"	"math/rand"	"strings"	"testing"	"time")type code struct {	base    string // 进制的包含字符, string类型	decimal uint64 // 进制长度	pad     string // 补位字符,若生成的code小于最小长度,则补位+随机字符, 补位字符不能在进制字符中	len     int    // code最小长度}func TestInviteCode(t *testing.T) {	inviteCode := code{		base:    "HVE8S2DZX9C7P5IK3MJUAR4WYLTN6BGQ",		decimal: 32,		pad:     "F",		len:     6,	}	// 初始化检查	if res, err := inviteCode.initCheck(); !res {		fmt.Println(err)		return	}	id := uint64(1)	code := inviteCode.idToCode(1)	fmt.Printf("id=%v, code=%v\n", id, code)	code = "VFXXXX"	id = inviteCode.codeToId(code)	fmt.Printf("code=%v, id=%v\n", code, id)}// id转codefunc (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转idfunc (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("")}

 

go实现2(作者原文有笔误b = 32 改为 b *= 32,已改正可用)

package timport (	"container/list"	"errors"	"fmt"	"testing")var baseStr string = "HVE8S2DZX9C7P5IK3MJUAR4WYLTN6BGQ"var base []byte = []byte(baseStr)var baseMap map[byte]intfunc 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 TestInviteCode2(t *testing.T) {	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实现

/** * 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));

 

总结

  • 本次实现由 php 和 go 两种语言实现
  • 最大的心得就是 go中的 类型转换是比较麻烦的,因为都是强类型
  • 和php 还不太一样
  • 但也算是进一步熟悉了go的语法代码

转载地址:http://skwsi.baihongyu.com/

你可能感兴趣的文章
【设计模式】—-(19)解释器模式(行为型)
查看>>
【Python】学习笔记——-4、Python解释器
查看>>
程序员,请对自己好一点
查看>>
【设计模式】—-(20)状态模式(行为型)
查看>>
【Python】学习笔记——-5、第一个Python程序
查看>>
【设计模式】—-(21)策略模式(行为型)
查看>>
【Python】学习笔记——-6、使用文本编辑器
查看>>
【第一章】—-(1)认识C++对象
查看>>
【设计模式】—-(22)责任链模式(行为型)
查看>>
【Python】学习笔记——-7、Python代码运行助手
查看>>
【Python】学习笔记——-8、输入和输出
查看>>
【大话数据结构】——-数据结构
查看>>
【鹅厂面试题】——-关于微信红包取中间值(笔试题)
查看>>
【猫厂面试题】——-2016(Web前端)
查看>>
【猫厂面试题】——-阿里常问面试题目汇总及答案(JAVA)
查看>>
【设计模式】—-(23)访问者模式(行为型)
查看>>
【Python】学习笔记——-2.0、Python基础
查看>>
【Python】学习笔记——-2.1、Python的数据类型与变量
查看>>
【分布式缓存】——-基于redis分布式缓存的实现
查看>>
【鸟叔的Linux私房菜】学习笔记(1)——Linux系统入门
查看>>