本文共 6645 字,大约阅读时间需要 22 分钟。
- 日常的网站开发中,会遇到网站的促销活动,就有涉及到邀请好礼的功能
- 成功邀请好友,则获取相应奖励,这时候,就有邀请码的需求
- 邀请码要求每个用户唯一
方法一. 可根据用户的uid生成邀请码 方法二. 邀请码可根据某个初始化id生成,用户主动请求,生成code,绑定uid- 方法二,这种方式,需额外记录uid和code关系
- 方法一,根据uid生成,也可根据code反推出uid,不用额外查询,比较方便
- 记录方法一的实现
- 由长数字转换为特定长度的code,首先需确定code的字符范围
- 可转换为 0-9A-Z 36进制数,或者更多字符可添加小写字符
- 本次实现 转换为 32进制数
- 去掉0 1 和 o 容易混淆的字符和补位字符F,剩余32字符
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("")}
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()) }}
/** * 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/