https://learnku.com/articles/73964
https://www.cnblogs.com/54chensongxia/p/11673522.html
经常在牛客上看到有短链接系统的项目
短链接是将原本冗长的URL做一次"包装",变成一个简洁可读的URL。
为什么使用短链接
-
节省发送的内容
我们平时发微博或者短信是都是有最大字符限制的。如果你的一个URL太长,一个URL占用的字符数就已经超过了最大限制,根本发不出去。如果你细心观察过银行的信用卡中心给你发过来的活动短信的话,你会发现短信中的URL好多都是以短链接形式发送的。
-
提升用户体验
还是以上面的URL为列子。这一大串URL非常不美观,给人的感觉就是木马链接,用户可能都不会去点击。相反的,如果转换成http://url.cn/5MMEX3D,就非常易于阅读,看起来整洁干净,提高用户体验和点击率。
-
便于链接追踪,分析点击来源
通过短链接可以获知大部分用户的来源,和来源渠道的转化率、广告渠道的质量。通过渠道效果对比数据,更合理的进行广告投放和资源配置。
-
同一个页面不同的链接
每个用户需要推送带参数的唯一链接。
-
一定程度上保护原始网站链接
我们在玩Facebook,领英等社交时,有些需要分享一些带有我们网站链接的帖子和消息,尤其对于小白来说,如果发的内容次数过多,并且毫无互动可言或者直接被人举报多次,那么你的网址就会被社交平台标记,一旦标记,链接权重就会受影响,后面再投社交广告时就有麻烦了。
短链接生成原理
如http://url.cn/5MMEX3D这个短链接,当我们访问这个链接的时候我们先访问到url.cn对应的服务器,然后根据参数5MMEX3D找出它对应的原始连接,再重定向到这个原始链接上去。
比较著名的短链域名有腾讯的 url.cn,微博的 t.cn 等,也有很多公司提供了免费 / 收费的短链接开放 API
5MMEX3D和原始链接是如何进行映射的?
-
hash算法不行,这个场景是不能容许哈希碰撞的,一般不建议这么做
-
发号器(ID自增)->再使用62进制编码 。比如将https://github.com/ZhongFuCheng3y/3y这个长链接看作是10000,然后使用进制转换工具将10000进行62进制编码得到的结果是:2Bi,那么我们就可以得到一个短链接http://url.cn/2Bi。
62进制转换是因为62进制转换后只含数字+小写+大写字母。而64进制转换会含有/,+这样的符号(不符合正常URL的字符)
短链接系统
代码来源于https://learnku.com/articles/73964
https://github.com/SnDragon/go-treasure-chest
使用 Redis 来生成自增 id,并存储映射关系
python
// Storage 短链服务抽象接口
type Storage interface {
Shorten(url string, expSecond int64) (string, error) // 将长链转成短链,并设置过期时间
ShortLinkInfo(sid string) (*entity.UrlDetailInfo, error) // 根据短链id获取详情
UnShorten(sid string) (string, error) // 根据短链id转成原始长链
}
python
// RedisStorage Redis实现短链服务
type RedisStorage struct {
redisCli *redis.Client
}
func (r *RedisStorage) Shorten(url string, expSecond int64) (string, error) {
// 1. 获取自增id
id, err := r.redisCli.Incr(RedisKeyUrlGlobalId).Result()
if err != nil {
return "", errors.Wrap(err, "[Shorten] incr global id err")
}
// 2. 转成base62(base64包含`+`、`/`字符,对URL不友好)
sid := base62.EncodeInt64(Offset + id)
// 3. 设置短url对应的原始url
if err := r.redisCli.Set(fmt.Sprintf(RedisKeyShortUrl, sid), url,
time.Second*time.Duration(expSecond)).Err(); err != nil {
return "", errors.Wrap(err, "[Shorten] set RedisKeyShortUrl err")
}
// 4. 设置详情
urlDetail := &entity.UrlDetailInfo{
OriginUrl: url,
CreatedAt: time.Now().Unix(),
ExpiredAt: time.Now().Unix() + expSecond,
}
if err := r.redisCli.Set(fmt.Sprintf(RedisKeyUrlDetail, sid),
encoding.JsonMarshalString(urlDetail), 0).Err(); err != nil {
return "", errors.Wrap(err, "[Shorten] set RedisKeyUrlDetail err")
}
return config.AppConfig.BaseUrl + sid, nil
}
func (r *RedisStorage) ShortLinkInfo(sid string) (*entity.UrlDetailInfo, error) {
// 1. 获取详情
data, err := r.redisCli.Get(fmt.Sprintf(RedisKeyUrlDetail, sid)).Result()
if err != nil {
return nil, errors.Wrap(err, "[ShortLinkInfo] get url detail err")
}
// 2. 反序列化
info := &entity.UrlDetailInfo{}
if err := encoding.JsonUnMarshalString(data, info); err != nil {
return nil, errors.Wrapf(err, "[ShortLinkInfo] JsonUnMarshalString err: %v", data)
}
// 3. 获取计数器
countRet, err := r.redisCli.Get(fmt.Sprintf(RedisKeyUrlCounter, sid)).Result()
if err == redis.Nil {
countRet = "0"
} else if err != nil {
return nil, errors.Wrapf(err, "[ShortLinkInfo] get RedisKeyUrlCounter err, sid: %v", sid)
}
info.Counter = cast.ToInt64(countRet)
return info, nil
}
func (r *RedisStorage) UnShorten(sid string) (string, error) {
// 1. 获取对应长链
val, err := r.redisCli.Get(fmt.Sprintf(RedisKeyShortUrl, sid)).Result()
if err == redis.Nil {
return "", &serrors.StatusError{
Code: http.StatusNotFound,
Err: fmt.Errorf("unknown url: %v", sid),
}
} else if err != nil {
return "", errors.Wrap(err, "get RedisKeyShortUrl err")
}
// 2. 访问计数器+1
if err := r.redisCli.Incr(fmt.Sprintf(RedisKeyUrlCounter, sid)).Err(); err != nil {
// 只影响统计,不影响主流程,打印错误日志即可
log.Printf("[UnShorten]Incr RedisKeyUrlCounter err, sid: %v\n", sid)
}
return val, nil
}
假设短链域名为 myurl.cn, 配置 host
python
127.0.0.1 myurl.cn
修改 configs/shorturl/app.yaml 的配置
python
base_url: http://myurl.cn/
redis_config:
db_host: 127.0.0.1 db_port: 6379 db_passwd: db: 0
进入项目根目录启动服务
python
make run_short_url
go build -o build/shorturl ./cmd/shorturl && ./build/shorturl
测试
python
curl -X POST \
http://myurl.cn/api/shorten \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{"url":"https://www.baidu.com?name=SnDragon","expire_seconds":100}'
返回
{
"code": 0,
"msg": "ok",
"short_url": "http://myurl.cn/4C99"
}