├── shorturl_test.go ├── README.md └── shorturl.go /shorturl_test.go: -------------------------------------------------------------------------------- 1 | package shorturl 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestShorturl(t *testing.T) { 8 | url := "http://www.example.com?a=1&b=2&c=3&d=4" 9 | 10 | cb := func(url, keyword string) bool { 11 | // todo 查db或缓存判断keyword是否重复 12 | return true 13 | } 14 | 15 | domain := "http://shorturl.cn" 16 | surl := Generator(CHARSET_ALPHANUMERIC, domain, url, cb) 17 | if surl == "" { 18 | t.Fatalf("Failed: generator shorturl, url[%s]", url) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shorturl 2 | 短链接生成算法 3 | 4 | ## Installation 5 | 6 | $ go get github.com/kaimixu/shorturl 7 | 8 | ## Usage 9 | 10 | package main 11 | 12 | import ( 13 | "fmt" 14 | "github.com/kaimixu/shorturl" 15 | ) 16 | 17 | func main() { 18 | url := "http://www.example.com?a=1&b=2&c=3&d=4" 19 | 20 | cb := func(url, keyword string) bool { 21 | // todo 查db或缓存判断keyword是否重复 22 | return true 23 | } 24 | 25 | domain := "http://shorturl.cn" 26 | surl := shorturl.Generator(shorturl.CHARSET_ALPHANUMERIC, domain, url, cb) 27 | fmt.Println(surl) 28 | } 29 | -------------------------------------------------------------------------------- /shorturl.go: -------------------------------------------------------------------------------- 1 | package shorturl 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "crypto/md5" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | CHARSET_ALPHANUMERIC = iota 12 | CHARSET_RANDOM_ALPHANUMERIC 13 | ) 14 | 15 | func getCharset(t int) string { 16 | switch t { 17 | case CHARSET_ALPHANUMERIC: 18 | return "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 19 | case CHARSET_RANDOM_ALPHANUMERIC: 20 | return "A0a12B3b4CDc56Ede7FGf8Hg9IhJKiLjkMNlmOPnQRopqrSstTuvUVwxWXyYzZ" 21 | default: 22 | panic("invalid charset type t:" + strconv.Itoa(t)) 23 | } 24 | } 25 | 26 | // 生成6字符短key 27 | // 根据url生成32字符的签名,将其分成4段,每段8位字符 28 | // 循环处理4段8位字符,将每段转换成16进制与0x3FFFFFFF进行逻辑与操作,得到30位的无符号数 29 | // 将30位数分成6段,依次得到6个0-61的数字索引查字符集表获得6位字符串 30 | func generator6(charset ,url, hexMd5 string, sectionNum int, cb func(url, keyword string) bool) string { 31 | for i := 0; i < sectionNum; i++ { 32 | sectionHex := hexMd5[i*8:8+i*8] 33 | bits, _ := strconv.ParseUint(sectionHex, 16, 32) 34 | bits = bits & 0x3FFFFFFF 35 | 36 | keyword := "" 37 | for j := 0; j < 6; j++ { 38 | idx := bits & 0x3D 39 | keyword = keyword + string(charset[idx]) 40 | bits = bits >> 5 41 | } 42 | 43 | if cb(url, keyword) { 44 | return keyword 45 | } 46 | } 47 | 48 | return "" 49 | } 50 | 51 | // 生成8字符短key 52 | func generator8(charset, url, hexMd5 string, sectionNum int, cb func(url, keyword string) bool) string { 53 | for i := 0; i < sectionNum; i++ { 54 | sectionHex := hexMd5[i*8:i*8+8] 55 | bits, _ := strconv.ParseUint(sectionHex, 16, 32) 56 | bits = bits & 0xFFFFFFFF 57 | 58 | keyword := "" 59 | for j := 0; j < 8; j++ { 60 | idx := bits & 0x3D 61 | keyword = keyword + string(charset[idx]) 62 | bits = bits >> 4 63 | } 64 | 65 | if cb(url, keyword) { 66 | return keyword 67 | } 68 | } 69 | 70 | return "" 71 | } 72 | 73 | // 生成6-8字符的短链接,参数t表示字符集类型,回调函数(cb)用于检测短链接是否重复 74 | // 起初生成6位的短链接,当四组6位短链接都重复时,再生成8位的短链接 75 | func Generator(t int, domain string, url string, cb func(url, keyword string) bool) (shorturl string) { 76 | if domain == "" || url == "" || cb == nil { 77 | return 78 | } 79 | 80 | charset := getCharset(t) 81 | hexMd5 := fmt.Sprintf("%x", md5.Sum([]byte(url))) 82 | sections := len(hexMd5)/8 83 | 84 | keyword := generator6(charset, url, hexMd5, sections, cb) 85 | if keyword == "" { 86 | keyword = generator8(charset, url, hexMd5, sections, cb) 87 | if keyword == "" { 88 | return "" 89 | } 90 | } 91 | 92 | if strings.HasSuffix(domain, "/") { 93 | shorturl = domain + keyword 94 | }else { 95 | shorturl = domain + "/" + keyword 96 | } 97 | return 98 | } 99 | --------------------------------------------------------------------------------