文章目录

自己做一个短链接平台,如何?

由 Rorical 发布

学了GoLang不开发点东西能行吗!

先把成品摆在这里
shortlink
shortlinker
短链接地址

短链接的成分非常简单,只需要把缩短的网址对应上原域名就好,所以在这里简单的KV数据库就完全满足需求。我最终选择了Leveldb,因为我自己对它进行了一点点封装,在这里LevelGo

打算用前端生成缩短的id,算法大概就是MD5B64什么的拼一下啦,当然得有自定义和检测冲突~ Id生成就包含数字字母就行,所以后端用正则判断一下合法性~

package shortlink

import (
    "shortlink/internal/config"
    "shortlink/internal/levelgo"
    "shortlink/internal/regex"
)

type ShortLinkApi struct {
    db *levelgo.LevelRpcClient
}

func NewApi(conf *config.Setting) *ShortLinkApi {

    db := levelgo.RpcClient(conf.LevelGo.URI)
    db.Connect()
    return &ShortLinkApi{
        db: db,
    }
}

func (self *ShortLinkApi) SetLink(id string, link string) error {
    if regex.IsProperId(id) && regex.IsUrl(link) {
        idbytes := config.StringIn(id)
        linkbytes := config.StringIn(link)
        isexist, err := self.db.Has(idbytes)
        if isexist {
            return ErrAlreadyExists
        }
        err = self.db.Set(idbytes, linkbytes)
        if err != nil {
            return err
        }
        return nil
    }
    return ErrIllegalParameters
}

func (self *ShortLinkApi) GetLink(id string) (string, error) {
    if regex.IsProperId(id) {
        idbytes := config.StringIn(id)
        link, err := self.db.Get(idbytes)
        if err != nil {
            if err == levelgo.ErrNotFound {
                return "", ErrDoesNotExists
            }
            return "", err
        }
        return config.StringOut(link), nil
    }
    return "", ErrIllegalParameters
}

func (self *ShortLinkApi) IsLinkExist(id string) (bool, error) {
    if regex.IsProperId(id) {
        idbytes := config.StringIn(id)
        isexist, err := self.db.Has(idbytes)
        if isexist {
            return true, nil
        } else {
            if err != nil {
                return false, err
            } else {
                return false, nil
            }
        }
    }
    return false, ErrIllegalParameters
}

节选了一部分代码,可以看到逻辑是相当的简单~
加链接访问次数统计也会很简单。

有一个很重要的问题需要考虑,就是可能会存在恶意刷链接的,所以我们要引入验证码,当然是现成的啦。经过了一番深思熟虑,我最终选择了不仅能白嫖还倒贴钱的hCaptcha,官方给的文档也灰常简单,很快啊,这代码就写出来了。

package hcaptcha

import (
    "fmt"
    "shortlink/internal/config"

    "github.com/dghubble/sling"

    "github.com/gin-gonic/gin"
)

const VERIFY_URL = "https://hcaptcha.com/siteverify"

type HCaptcha struct {
    sling     *sling.Sling
    secretKey string
}

func NewhCaptcha(conf *config.Setting) *HCaptcha {
    s := sling.New().Base(VERIFY_URL)
    return &HCaptcha{
        sling:     s,
        secretKey: conf.HCaptcha.SecretKey,
    }
}

type VerifyParams struct {
    Secret   string `url:"secret,omitempty"`
    Response string `url:"response,omitempty"`
    RemoteIP string `url:"remoteip,omitempty"`
    SiteKey  string `url:"sitekey,omitempty"`
}

type VerifyResponse struct {
    Success bool `url:"success,omitempty"`
    //Challenge_TS string `url:"challenge_ts,omitempty"`
    //HostName     string `url:"hostname,omitempty"`
}

func (self *HCaptcha) Verify(token string, ip string, siteKey string) (*VerifyResponse, error) {
    verifyParams := &VerifyParams{
        Secret:   self.secretKey,
        Response: token,
        RemoteIP: ip,
        SiteKey:  siteKey,
    }
    verifyResponse := &VerifyResponse{}
    _, err := self.sling.New().BodyForm(verifyParams).ReceiveSuccess(verifyResponse)
    fmt.Println(verifyResponse)
    return verifyResponse, err
}

func (self *HCaptcha) GinVerify(c *gin.Context) bool {
    siteKey := c.DefaultPostForm("site-key", "")
    token := c.DefaultPostForm("h-captcha-response", "")
    ip := c.ClientIP()
    if token == "" || siteKey == "" {
        return false
    }

    res, err := self.Verify(token, ip, siteKey)
    if err != nil {
        return false
    }
    return res.Success
}

为了方便用了Gin库。

还有一件重要的事情,我们要访问界面跳转到原网址,我采用了meta标签refresh达到目的,并且将其也塞到后端。

前端还是偷懒用了Vue + ElementUI,hCaptcha有专门的Vue库来接入,十分的银杏。

这样我们就需要为难一下Nginx啦,Nginx作为处理静态的老大,配置当然也很灵活。为了让Nginx老大少忙活一下,我决定委屈一下Vue的Router,把它设置成Hash模式。

location /api/ {
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Scheme $scheme;
    proxy_pass http://localhost:8081/api/;
}
location / {
    try_files $uri $uri/ @redirect;
}
location @redirect{
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Scheme $scheme;
    proxy_pass http://localhost:8081/redirect$uri;
}

try_files是真的好用,配合location通配简直完美

hCaptcha会在解决一个验证码后给你发一些HMT(Human Token),一个好像还在开发中的数字货币

就这样啦~!


仅有一条评论

  1. 墨兮
    墨兮 · 2021-04-27 23:53

    芜湖,大佬教学誒

发表评论