From 58fbb48fc8793c3c873d198538762efe9315f50f Mon Sep 17 00:00:00 2001 From: CareyWong Date: Sun, 8 Mar 2020 23:25:35 +0800 Subject: [PATCH] Init project --- .gitignore | 2 + Makefile | 44 ++++++++++++++++++ README.md | 87 ++++++++++++++++++++++++++++++++++++ go.mod | 12 +++++ go.sum | 57 +++++++++++++++++++++++ main.go | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 331 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03920b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +build/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3013805 --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +BINARY_DEFAULT="build/myurls.service" +BINARY_LINUX="build/linux-amd64-myurls.service" +BINARY_DARWIN="build/darwin-amd64-myurls.service" +BINARY_WINDOWS="build/windows-amd64-myurls.service" + +GOFILES="main.go" +VERSION=1.0.0 +BUILD=`date +%FT%T%z` + +default: + @echo ${BINARY_DEFAULT} + @go build -o ${BINARY_DEFAULT} ${GOFILES} + +all: + @echo ${BINARY_LINUX} + @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY_LINUX} ${GOFILES} + @echo ${BINARY_DARWIN} + @CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ${BINARY_DARWIN} ${GOFILES} + @echo ${BINARY_WINDOWS} + @CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ${BINARY_WINDOWS} ${GOFILES} + +linux: + @echo ${BINARY_LINUX} + @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY_LINUX} ${GOFILES} + +darwin: + @echo ${BINARY_DARWIN} + @CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ${BINARY_DARWIN} ${GOFILES} + +windows: + @echo ${BINARY_WINDOWS} + @CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ${BINARY_WINDOWS} ${GOFILES} + +install: + @go mod tidy + +fmt: + @go fmt ./... + +clean: + @if [ -f ${BINARY_DEFAULT} ] ; then rm ${BINARY_DEFAULT} ; fi + @if [ -f ${BINARY_LINUX} ] ; then rm ${BINARY_LINUX} ; fi + @if [ -f ${BINARY_DARWIN} ] ; then rm ${BINARY_DARWIN} ; fi + @if [ -f ${BINARY_WINDOWS} ] ; then rm ${BINARY_WINDOWS} ; fi diff --git a/README.md b/README.md new file mode 100644 index 0000000..26fce78 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# bitly + +基于 golang1.13 与 Redis 实现的本地短链接服务,用于缩短请求链接与短链接还原。 + +## Table of Contents + +- [Update](#update) +- [Dependencies](#dependencies) +- [Docker](#Docker) +- [Install](#install) +- [Usage](#usage) +- [API](#api) +- [Maintainers](#maintainers) +- [Contributing](#contributing) +- [License](#license) + +# Dependencies + +本服务依赖于 Redis 提供长短链接映射关系存储,你需要本地安装 Redis 服务来保证短链接服务的正常运行。 + +```shell script +sudo apt-get update + +# 安装Redis +sudo add-apt-repository ppa:chris-lea/redis-server -y +sudo apt-get update +sudo apt-get install redis-server -y +``` + +## Docker + +TODO + +## Install + +安装项目依赖 + +```shell script +make install +``` + +生成可执行文件,目录位于 build/ 。默认当前平台,其他平台请参照 Makefile 或执行对应 go build 命令。 + +```shell script +make +``` + +## Usage + +前往 [Release](https://github.com/CareyWang/MyUrls/releases) 下载对应平台可执行文件。 + +```shell script +./build/myurls -h + +Usage of ./build/myurls.service: + -domain string + 短链接域名 (default "s.wcc.best") + -port int + 服务端口 (default 8002) + -ttl int + 短链接有效期,单位(天),默认90天。 (default 90) +``` + +建议配合 [pm2](https://pm2.keymetrics.io/) 开启守护进程。 + +```shell script +pm2 start myurls.service --watch --name myurls -- -domain example.com +``` + +## API + +[参考文档](https://myurls.mydoc.li) + + +## Maintainers + +[@CareyWang](https://github.com/CareyWang) + +## Contributing + +PRs accepted. + +Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. + +## License + +MIT © 2020 CareyWang diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fb4357f --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/CareyWang/MyUrls + +go 1.13 + +require ( + github.com/gin-gonic/gin v1.5.0 + github.com/gomodule/redigo v2.0.0+incompatible + github.com/kr/pretty v0.1.0 // indirect + golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/yaml.v2 v2.2.4 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..945819a --- /dev/null +++ b/go.sum @@ -0,0 +1,57 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a1ece93 --- /dev/null +++ b/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "encoding/base64" + "flag" + "fmt" + "github.com/gin-gonic/gin" + "github.com/gomodule/redigo/redis" + "math/rand" + "net/http" +) + +type Response struct { + Code int + Message string + LongUrl string + ShortUrl string +} + +const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +const defaultPort int = 8002 +const defaultDomain = "s.wcc.best" +const defaultExpire = 90 +const redisConfig = "127.0.0.1:6379" + +func main() { + gin.SetMode(gin.ReleaseMode) + router := gin.Default() + + port := flag.Int("port", defaultPort, "服务端口") + domain := flag.String("domain", defaultDomain, "短链接域名") + ttl := flag.Int("ttl", defaultExpire, "短链接有效期,单位(天),默认90天。") + flag.Parse() + + router.POST("/short", func(context *gin.Context) { + res := &Response{ + Code: 1, + Message: "", + LongUrl: "", + ShortUrl: "", + } + + longUrl := context.Query("longUrl") + _longUrl, _ := base64.StdEncoding.DecodeString(longUrl) + longUrl = string(_longUrl) + + shortKey := longToShort(longUrl, *ttl * 24 * 3600) + if shortKey == "" { + res.Code = 0 + res.Message = "短链接生成失败" + context.JSON(500, *res) + return + } + + res.LongUrl = longUrl + res.ShortUrl = "http://" + *domain + "/" + shortKey + context.JSON(200, *res) + }) + + router.GET("/:shortKey", func(context *gin.Context) { + shortKey := context.Param("shortKey") + longUrl := shortToLong(shortKey) + + if longUrl == "" { + context.String(http.StatusNotFound, "短链接不存在或已过期") + } else { + context.Redirect(http.StatusMovedPermanently, longUrl) + } + }) + + router.Run(fmt.Sprintf(":%d", *port)) +} + +// 短链接转长链接 +func shortToLong(shortKey string) string { + redisClient := initRedis() + + longUrl, _ := redis.String(redisClient.Do("get", shortKey)) + return longUrl +} + +// 长链接转短链接 +func longToShort(longUrl string, ttl int) string { + redisClient := initRedis() + + // 是否生成过该长链接对应短链接 + _existsKey, _ := redis.String(redisClient.Do("get", longUrl)) + if _existsKey != "" { + _, _ = redisClient.Do("expire", _existsKey, ttl) + + return _existsKey + } + + // 重试三次 + var shortKey string + for i := 0; i < 3; i++ { + shortKey = generate(6) + + _existsLongUrl, _ := redis.String(redisClient.Do("get", longUrl)) + if _existsLongUrl != "" { + break + } + } + + if shortKey != "" { + _, _ = redisClient.Do("mset", shortKey, longUrl, longUrl, shortKey) + + _, _ = redisClient.Do("expire", shortKey, ttl) + _, _ = redisClient.Do("expire", longUrl, ttl) + } + + return shortKey +} + +// 产生一个63位随机整数,除以字符数取余获取对应字符 +func generate(bits int) string { + b := make([]byte, bits) + for i := range b { + b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))] + } + return string(b) +} + +func initRedis() redis.Conn { + client, _ := redis.Dial("tcp", redisConfig) + + return client +}