用 golang 编写一个短链接服务

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

# 用 golang 编写一个短链接服务

[访问原文]( https://r.nsini.com/usGE1LxZg)

我们平时工作、生活中总会有各种各样的域名链接需要分享给同事或朋友或家人。但常常有域名的长度过长会有各种限制,或无法复制全而产生一些问题了,为了解决这个问题我们需要一个短链接生成器。

基于上面的想法我写了一个短链接的生成器:

[https://github.com/icowan/shorter]( https://github.com/icowan/shorter)

## 项目简介

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e23341d9b?w=1280&h=653&f=jpeg&s=37244)

该服务基于 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

```

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e22d1c3b1?w=1280&h=617&f=jpeg&s=71324)

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e23107705?w=1280&h=617&f=jpeg&s=144089)

## 在开普勒云平台进行部署

开普勒平台演示地址: [https://kplcloud.nsini.com/about.html]( https://kplcloud.nsini.com/about.html)

开普勒平台后端代码: [https://github.com/kplcloud/kplcloud]( https://github.com/kplcloud/kplcloud)

开普勒平台[安装教程]( https://docs.nsini.com/install/kpaas.html)

由于此项目依赖数据库: 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. 提交管理员审核

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e20b6152f?w=1280&h=920&f=jpeg&s=72542)

### 管理员审核、初始化发布应用

管理员收到通知后进入基础详情页进行审核:

主要查看提交的基础信息是否正确,自动生成的 YAML 文件是否正确,自动生成的 Jenkins 模版是否正确及用户项目里的 Dockerfile 文件是否有误,若没有问题点击**“开始部署”**按钮直接进行应用的构建及发布。

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e257374ad?w=1280&h=782&f=jpeg&s=55996)

应用部署成功后,系统会向像的邮件、微信发送通知,告知应用发布的情况。(微信通知需要您在“个人设置”->“账号绑定”->“绑定(关注微信公众号即可自动绑定)”->“消息订阅设置” 在消息订阅里勾选需要通知的类型及方式)详情请看文档:

- 绑定微信通知:

- 订阅通知类型及方式: [https://r.nsini.com/hi2hy1bWg]( https://r.nsini.com/hi2hy1bWg)

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e304867b8?w=750&h=1334&f=jpeg&s=91544)

若收到成功发布的信息,那应用就算是启动成功了。

后续若要升级应用,应用创建者或组成员可以直接在应用详情页选择**“Build”**按钮并且选择相应用版本就好了。

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e4abe24eb?w=1072&h=588&f=jpeg&s=30904)

回滚应用也同样方便:

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e4d9bdb86?w=1280&h=873&f=jpeg&s=65038)

只需要点击**“回滚”**按钮,在弹出的窗口选择所需要回滚的版本,点击 [“回滚”] 并确认,平台会将该版本的 Docker Image 进行启动。

### 生成外问地址

完成之后,为了让外部可以访问到该代理,需要生成一个对外可访问的地址。

在应用详情的最下方有一个“外部地址”的卡片,若是第一次创建应用,在卡片的 header 的右边有一个有**“添加”**按钮点击它,并确认就可以生成一个外部地址了。

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e52ffa0f6?w=1280&h=165&f=jpeg&s=10391)

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e6b8e7959?w=1280&h=336&f=jpeg&s=17242)

上面就是生成的地址,我们可以通过这个地址访问到 shorter 应用。

## 测试

我这部署了一个生通过短域名解析到该应用上的例子,点击下面地址进行短链生成页面。

- [https://r.nsini.com]( https://r.nsini.com)

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e23341d9b?w=1280&h=653&f=jpeg&s=37244)

把需要生成短链接的地址贴到输入框,并点击**“生成短链”**按钮即可生成。

点击**“复制”** 按钮即可将短地址复制并使用。

## 尾巴

golang 语言是一个非常高效且简单易学的编程语言,基于 golang 语言的特性,我们可以写出非常多有意思的工具或平台。

**你的打赏就是我更新的动力**

![]( https://user-gold-cdn.xitu.io/2019/11/26/16ea597e56ba3257?w=375&h=378&f=jpeg&s=46848)


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

本文来自:简书

感谢作者:

查看原文:用 golang 编写一个短链接服务

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

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