用golang编写一个短链接服务

dudulu · · 1253 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

# 用golang编写一个短链接服务 [原文](https://lattecake.com/post/20120) 我们平时工作、生活中总会有各种各样的域名链接需要分享给同事或朋友或家人。但常常有域名的长度过长会有各种限制,或无法复制全而产生一些问题了,为了解决这个问题我们需要一个短链接生成器。 基于上面的想法我写了一个短链接的生成器: [https://github.com/icowan/shorter](https://github.com/icowan/shorter) ## 项目简介 ![](http://source.qiniu.cnd.nsini.com/images/2019/11/48/bd/64/20191125-bfefea2da3a147e7616cfc58bd348c0b.jpeg?imageView2/2/w/1280/interlace/0/q/70) 该服务基于go-kit组件进行开发,数据库基于Redis或Mongo进行存储。 可以通过容器进行部署,也可以在kubernetes中进行部署。 ## 目录结构 - cmd: 应用启动入口 - dist: 前端静态文件目录 - install: 安装目录 - pkg - endpoint: 端点 - http: 传输处理 - logging: 日志中间件 - repository: 仓库存储逻辑实现 - service: 逻辑实现 ``` ├── Dockerfile ├── Makefile ├── README.md ├── cmd │   ├── main.go │   └── service ├── dist ├── go.mod ├── go.sum ├── install └── pkg ├── endpoint ├── http ├── logging ├── repository └── service ``` ## API 该服务一共有两个接口,一个是生成短地址,另一个是短地址进行跳转。 可以通过Redis进行数据存储也能通过MongoDB作为存储介质。 ### Repository **model** 存储的数据结构,主要三个字段 - code: 生成的唯一码 - url: 源地址 - created_at: 创建时间 ```go // pkg/service/model.go type Redirect struct { Code string `json:"code"` URL string `json:"url"` CreatedAt time.Time `json:"created_at"` } ``` **repository** repository提供了两个方法,一个是Find和Store. - Find 根据code查询url信息 - Store 存储url信息 ```go // pkg/service/repository.go type Repository interface { Find(code string) (redirect *Redirect, err error) Store(redirect *Redirect) error } ``` Repository是一个Interface 类型的结构体,没有具体实现。这里根据使用的存储数据库的不同需要实现不同的存储方式。 - mongodb: `pkg/repository/mongo/repository.go` - redis: `pkg/repository/redis/repository.go` 在启动入口 `cmd/service/service.go` 文件里可以看到启动是如何选择的: ```go // cmd/service/service.go var repo service.Repository switch *dbDrive { case "mongo": repo, err = mongodb.NewMongoRepository(*mongoAddr, "redirect", 60) if err != nil { _ = level.Error(logger).Log("connect", "db", "err", err.Error()) return } case "redis": db, _ := strconv.Atoi(*redisDB) repo, err = redis.NewRedisRepository(redis.RedisDrive(*redisDrive), *redisHosts, *redisPassword, "shorter", db) if err != nil { _ = level.Error(logger).Log("connect", "db", "err", err.Error()) return } } ``` ### Service service提供了两个方法 Get 和 Post。 ```go // pkg/service/service.go type Service interface { Get(ctx context.Context, code string) (redirect *Redirect, err error) Post(ctx context.Context, domain string) (redirect *Redirect, err error) } ``` - Get: 传入code码,根据code码去数据查找存储的地址信息 - Post: 传入原url地址,生成code码并存入数据库,返回结构体 ### Transport Post 生成地址需要通过POST的方式传入JSON结构, 接收参考文件: ```go // pkg/endpoint/endpoint.go type PostRequest struct { URL string `json:"url" validate:"required,url,lt=255"` } type dataResponse struct { Url string `json:"url"` Code string `json:"code"` CreatedAt time.Time `json:"created_at"` ShortUri string `json:"short_uri"` } type PostResponse struct { Err error `json:"err"` Data dataResponse `json:"data"` } ``` 该接口只接收一个参数 "url",返回四个参数。 - url: 原地址 - short_uri: 跳转的短地址 - code: 跳转短地址的code - created_at: 生成时间 ### Transport Get 通过uri的code进行查询例如: ```go r.Handle("/{code}", kithttp.NewServer( endpoints.GetEndpoint, decodeGetRequest, encodeGetResponse, options["Get"]...)).Methods(http.MethodGet) ``` 解析 Request: ```go func decodeGetRequest(_ context.Context, r *http.Request) (interface{}, error) { vars := mux.Vars(r) code, ok := vars["code"] if !ok { return nil, ErrCodeNotFound } req := endpoint.GetRequest{ Code: code, } return req, nil } ``` 跳转: ```go func encodeGetResponse(ctx context.Context, w http.ResponseWriter, response interface{}) (err error) { if f, ok := response.(endpoint.Failure); ok && f.Failed() != nil { ErrorRedirect(ctx, f.Failed(), w) return nil } resp := response.(endpoint.GetResponse) redirect := resp.Data.(*service.Redirect) http.Redirect(w, &http.Request{}, redirect.URL, http.StatusFound) return } // 错误跳回首页 func ErrorRedirect(_ context.Context, err error, w http.ResponseWriter) { http.Redirect(w, &http.Request{}, os.Getenv("SHORT_URI"), http.StatusFound) } ``` ## docker-compose部署 docker-compose启动比较简单,直接进入目录`install/docker-compose/` 然后执行: ``` $ docker-compose up ``` ![](http://source.qiniu.cnd.nsini.com/images/2019/11/bb/ad/d5/20191125-1d9a264413595916d47c797cad7bb822.jpeg?imageView2/2/w/1280/interlace/0/q/70) ![](http://source.qiniu.cnd.nsini.com/images/2019/11/d9/de/7e/20191125-64a59291340bef69726fb2bede3d99d1.jpeg?imageView2/2/w/1280/interlace/0/q/70) ## 在开普勒云平台进行部署 由于此项目依赖数据库: Redis、MongoDB,所以在创建应用之前我们得先部署Redis或MongoDB的持久化应用。在项目里我给出了两数据库部署的Demo,大家可以尝试在自己的环境中启动。 开普勒云平台倾向于部署无状态的应用也就是Deployment类型,像这种需要持久化的应用最好是部署成有状态的应用如: StatefulSet类型,相对来说比较好组成分布式集群或主从节构。 单点Redis服务: [install/kubernetes/redis/](https://github.com/icowan/shorter/tree/master/install/kubernetes/redis/) 单点MongoDB服务: [install/kubernetes/mongo/](https://github.com/icowan/shorter/tree/master/install/kubernetes/mongo/) ### 创建一个应用 我们创建一个名叫**shorter**的应用: 1. 输入github的地址: `icowan/shorter` 2. 选择版本: `v0.1.8` 3. 选择启动的容器数量: `2` 4. 最大`64Mi`内存,应用比较简单不需要太大的使用内存 5. 启动的端口: `8080` 6. 提交管理员审核 ![](http://source.qiniu.cnd.nsini.com/images/2019/11/65/44/a1/20191125-bb968dcf590910d9ab40798a44e65dba.jpeg?imageView2/2/w/1280/interlace/0/q/70) ### 管理员审核、初始化发布应用 管理员收到通知后进入基础详情页进行审核: 主要查看提交的基础信息是否正确,自动生成的YAML文件是否正确,自动生成的Jenkins模版是否正确及用户项目里的Dockerfile文件是否有误,若没有问题点击**“开始部署”**按钮直接进行应用的构建及发布。 ![](http://source.qiniu.cnd.nsini.com/images/2019/11/d1/76/1a/20191125-1739037bcd87a39615015d39763d1163.jpeg?imageView2/2/w/1280/interlace/0/q/70) 应用部署成功后,系统会向像的邮件、微信发送通知,告知应用发布的情况。(微信通知需要您在“个人设置”->“账号绑定”->“绑定(关注微信公众号即可自动绑定)”->“消息订阅设置” 在消息订阅里勾选需要通知的类型及方式)详情请看文档: - 绑定微信通知: - 订阅通知类型及方式: [https://r.nsini.com/hi2hy1bWg](https://r.nsini.com/hi2hy1bWg) ![](http://source.qiniu.cnd.nsini.com/images/2019/11/32/b7/8d/20191125-965a142d06f7d7c18f8f75f9b7f3239e.jpeg?imageView2/2/w/1280/interlace/0/q/70) 若收到成功发布的信息,那应用就算是启动成功了。 后续若要升级应用,应用创建者或组成员可以直接在应用详情页选择**“Build”**按钮并且选择相应用版本就好了。 ![](http://source.qiniu.cnd.nsini.com/images/2019/11/0c/a4/3a/20191125-4e42f1f8a557af7c343ba131a4e0c774.jpeg?imageView2/2/w/1280/interlace/0/q/70) 回滚应用也同样方便: ![](http://source.qiniu.cnd.nsini.com/images/2019/11/58/65/a4/20191125-24194e2a8fce413bad955f2f65f58e32.jpeg?imageView2/2/w/1280/interlace/0/q/70) 只需要点击**“回滚”**按钮,在弹出的窗口选择所需要回滚的版本,点击【“回滚”】并确认,平台会将该版本的Docker Image进行启动。 ### 生成外问地址 完成之后,为了让外部可以访问到该代理,需要生成一个对外可访问的地址。 在应用详情的最下方有一个“外部地址”的卡片,若是第一次创建应用,在卡片的header的右边有一个有**“添加”**按钮点击它,并确认就可以生成一个外部地址了。 ![](http://source.qiniu.cnd.nsini.com/images/2019/11/62/42/4e/20191125-713bab60f126e3b5434c8ca842562d7a.jpeg?imageView2/2/w/1280/interlace/0/q/70) ![](http://source.qiniu.cnd.nsini.com/images/2019/11/20/27/22/20191125-d743594599d82e6d20e209d027f20cd6.jpeg?imageView2/2/w/1280/interlace/0/q/70) 上面就是生成的地址,我们可以通过这个地址访问到shorter应用。 ## 测试 我这部署了一个生通过短域名解析到该应用上的例子,点击下面地址进行短链生成页面。 - [https://r.nsini.com](https://r.nsini.com) ![](http://source.qiniu.cnd.nsini.com/images/2019/11/48/bd/64/20191125-bfefea2da3a147e7616cfc58bd348c0b.jpeg?imageView2/2/w/1280/interlace/0/q/70) 把需要生成短链接的地址贴到输入框,并点击**“生成短链”**按钮即可生成。 点击**“复制”** 按钮即可将短地址复制并使用。 ## 尾巴 golang语言是一个非常高效且简单易学的编程语言,基于golang语言的特性,我们可以写出非常多有意思的工具或平台。 **你的打赏就是我更新的动力** ![](https://lattecake.oss-cn-beijing.aliyuncs.com/static%2Fimages%2Freward%2Fweixin-RMB-xxx.JPG)

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1253 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传