使用Go语言开发一个短链接服务:四、生成code算法
章节
Gitee https://gitee.com/alxps/short_link
Github https://github.com/1911860538/short_link
上一篇介绍了项目目录接口,这篇将实现短链接code算法。
前言
假如某个用户,有个长链接为:https://github.com/gin-gonic/gin/blob/master/internal/bytesconv/bytesconv_1.19.go,我们短链接服务域名为https://a.b.c,为这个长链接生成对应的短链接为https://a.b.c/N26jas。这里code便是Na6jas。
code必须满足以下几个要求:
1、只包含,大小写字母或数字
2、短
3、唯一
大小字母加上数字,共62个,当code长度为N时,code可以有62的N次方个。N为5、6、7,code个数大约为1600万, 568亿, 3.5万亿。586亿够用,因此系统code长度为6,够短。
如何生成唯一code
这还不简单!生成6位随机大小写字母或数字的字符,数据库存在,重新随机生成,递归直到不冲突。本篇完……,当然不是。
至于如何生成6位随机大小写字母或数字,可以看看这篇文章:Golang 生成随机字符串的八种方式与性能测试。文章随机字符串只有大小写字母,我们在此基础上稍作修改,代码如下:
package service
import (
"math/rand"
"time"
"unsafe"
)
const letters = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
var src = rand.NewSource(time.Now().UnixNano())
const (
// 6 bits to represent a character index
charIdBits = 6
// All 1-bits as many as charIdBits
charIdMask = 1<<charIdBits - 1
// 由于现在包含了62个字符,计算新的charIdMax
charIdMax = 63 / charIdBits
// 字符集的大小
charsetSize = len(letters)
)
func randStr(n int) string {
b := make([]byte, n)
for i, cache, remain := n-1, src.Int63(), charIdMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), charIdMax
}
if idx := int(cache & charIdMask); idx < charsetSize {
b[i] = letters[idx]
i--
}
cache >>= charIdBits
remain--
}
return unsafe.String(unsafe.SliceData(b), len(b))
}
但是,我想法是,code要根据用户id和长链接url推算出来,即相同的user_id+long_url,每次得到的code都一样。先上代码,再讲思路
package service
import (
"crypto/md5"
"encoding/hex"
"hash/fnv"
"io"
"unsafe"
"github.com/1911860538/short_link/config"
)
const letters = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
var confCodeLen = config.Conf.Core.CodeLen
// GenCode
func GenCode(userId string, longUlr string, salt string) (string, error) {
// 首先对userId+longUrl+salt md5 主要为了防止longUrl包含汉字等字符串
hasher := md5.New()
if _, err := io.WriteString(hasher, userId+longUlr+salt); err != nil {
return "", err
}
hashStr := hex.EncodeToString(hasher.Sum(nil))
stepLen := len(hashStr) / confCodeLen
remain := len(hashStr) % confCodeLen
if remain > 0 {
stepLen += 1
}
lettersLen := uint32(len(letters))
b := make([]byte, confCodeLen)
for i := 0; i < confCodeLen; i++ {
// 根据要生成的code长度,切分md5字符串
var piece string
if remain > 0 && i == confCodeLen-1 {
piece = hashStr[i*stepLen : i*stepLen+remain]
} else {
piece = hashStr[i*stepLen : i*stepLen+stepLen]
}
// 为切片元素生成对应的整形数值
h := fnv.New32a()
pieceBytes := unsafe.Slice(unsafe.StringData(piece), len(piece))
if _, err := h.Write(pieceBytes); err != nil {
return "", err
}
pieceHash32 := h.Sum32()
// 切片字符的整形,取len(letters)余数,并取letters索引为该余数的letter
letterIdx := pieceHash32 % lettersLen
b[i] = letters[letterIdx]
}
return unsafe.String(unsafe.SliceData(b), len(b)), nil
}
比如user_id为"1f70a466-1449-4676-b2d7-2037341c718e",long_url为"https://i.cnblogs.com/posts/edit;postId=18090256":
1、将user_id+long_url字符串,生成md5的哈希字符串,这一步是为了得到固定长度的字符。结果为,"101360eb993b977d9f6969813ee84338"
2、根据要生成的code长度,切分步骤1的字符串。我们要得到6位code,因而我们将得到的字符切片结果为,["101360", "eb993b", "977d9f", "696981", "3ee843", "38"]
3、接着我们对步骤2每个字符切片元素,使用fnv哈希,获得uint32,结果为[791694210, 3988549581, 2254501405, 2706880430, 3291227237, 2414894606]
4、对步骤3,uint32切片,每个元素,取letters长度余数,获得余数切片,[28, 53, 55, 48, 17, 0]
5、取letters索引为步骤4余数切片的字母或数字字符,得到最终结果,"2RTMra"
步骤3/4参考了一致性哈希。当然,上面两种获取6位code方式凭个人想法哈,网上也有其它算法实现可参考。本项目中使用上面说的后者。
总结
本篇讲了短链接code生成逻辑,下一篇讲添加和获取短链接逻辑。