【Golang】短链接系统

发布于:2024-08-02 ⋅ 阅读:(94) ⋅ 点赞:(0)

https://learnku.com/articles/73964
https://www.cnblogs.com/54chensongxia/p/11673522.html
https://www.cnblogs.com/Java3y/p/11976312.html
https://github.com/SnDragon/go-treasure-chest

经常在牛客上看到有短链接系统的项目

短链接是将原本冗长的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,并存储映射关系

// Storage 短链服务抽象接口
type Storage interface {
 Shorten(url string, expSecond int64) (string, error)     // 将长链转成短链,并设置过期时间
 ShortLinkInfo(sid string) (*entity.UrlDetailInfo, error) // 根据短链id获取详情
 UnShorten(sid string) (string, error)                    // 根据短链id转成原始长链
}
// 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

127.0.0.1 myurl.cn

修改 configs/shorturl/app.yaml 的配置

base_url: http://myurl.cn/
redis_config:
 db_host: 127.0.0.1 db_port: 6379 db_passwd: db: 0

进入项目根目录启动服务

make run_short_url
go build -o build/shorturl ./cmd/shorturl  && ./build/shorturl

测试

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"
}

浏览器访问http://myurl.cn/4C99重定向到baidu


网站公告

今日签到

点亮在社区的每一天
去签到