go-zero学习系列二:实现短链接服务

Golang
390
0
0
2022-11-12
标签   go-zero

这一节我们重现一下go-zero仓库中的短链接服务案例:短链接服务

短链接服务的流程一般是这样的

go-zero学习系列二:实现短链接服务

这里为了简单点我们就模拟一个生成短链接key的接口以及根据key去换取原url地址的接口

准备工作

这个案例我们要用到mysql、redis、以及etcd,这里为了简单我就用docker安装一下:

#安装etcd服务端
 docker pull bitnami/etcd:3.5
 docker run -d --name Etcd-server
    --publish 2379:2379
    --publish 2380:2380
    --env ALLOW_NONE_AUTHENTICATION=yes
    --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379
    bitnami/etcd:3.5
#安装mysql
docker run -p 3310:3306 --name mysql5.7 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
#安装redis
docker run -di --name=redis -p 6379:6379 redis

docker ps看到我们这几个容器启动起来了就行

go-zero学习系列二:实现短链接服务

goland创建有一个新项目shorturl-service

go-zero学习系列二:实现短链接服务

编写api代码

在根目录下新建api文件夹并在api文件下执行

命令最好都自己敲一次不要复制粘贴,能增加一点熟练度

goctl api -o shorturl.api

go-zero学习系列二:实现短链接服务

基于这个模板我们写自己想要的内容就好了

修改后的api文件内容如下:

syntax = "v1"

//获取短链接的request跟response结构
type getShortUrlRequest {
    Url string `json:"url"`
}
type getShortUrlResponse {
    ShortUrl string `json:"short_url"`
}

//获取长链接的request跟response结构
type getLongUrlRequest {
    ShortUrl string `json:"short_url"`
}
type getLongUrlResponse {
    Url string `json:"url"`
}

service shorturl-api {
    @handler GetShortUrl
    post /short(getShortUrlRequest) returns (getShortUrlResponse)

    @handler GetLongUrl
    post /long(getLongUrlRequest) returns (getLongUrlResponse)
}

然后我们通过goctl来生成api的代码,在api文件夹下执行下面的命令

goctl api go -api shorturl.api -dir .

然后会看到这样的目录结构

go-zero学习系列二:实现短链接服务

到了这里可能有一些文件会飘红

go-zero学习系列二:实现短链接服务

我们在命令行下执行go mod tidy把依赖都引入一下就好了

我们打开logic目下的两个文件

go-zero学习系列二:实现短链接服务

go-zero学习系列二:实现短链接服务

可以看到这里有提示 我们只需要在这里补充我们的逻辑代码就好了。

我们先来简单改写一下:

func (l *GetShortUrlLogic) GetShortUrl(req *types.GetShortUrlRequest) (resp *types.GetShortUrlResponse, err error) {
    return &types.GetShortUrlResponse{
        ShortUrl: "return short_url test",
    }, nil
}

func (l *GetLongUrlLogic) GetLongUrl(req *types.GetLongUrlRequest) (resp *types.GetLongUrlResponse, err error) {
   return &types.GetLongUrlResponse{
      Url: "return long_url test",
   }, nil
}

ok我们现在就来启动这个api服务

在api文件夹下执行

#以指定配置文件的方式启动
go run shorturl.go -f etc/shorturl-api.yaml

这样我们的服务就启动起来了,可以看到正在监听着8888端口

go-zero学习系列二:实现短链接服务

我们可以看下shorturl.go的代码

go-zero学习系列二:实现短链接服务

如果你不通过-f指定后面的配置文件的话 将会使用etc/shorturl-api.yaml作为配置文件。

下面我们通过postman请求一下写好的两个接口:

go-zero学习系列二:实现短链接服务

go-zero学习系列二:实现短链接服务

OK已经正常返回我们设定的值了,下面我们将数据库的操作加入进来

我们在数据库中新建一个shorturl-service的数据库,并创建一个shorturl的表

CREATE TABLE `shorturl` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `short_url` varchar(255) NOT NULL COMMENT '生成的key',
    `url` varchar(255) NOT NULL COMMENT '原始的url',
    PRIMARY KEY (`id`),
    KEY `short_url` (`short_url`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

在api文件夹下新建model文件夹并新建shorturl.sql文件,文件内容就是上面的sql

然后我们在model文件夹下执行下面的命令用来生成 CRUD+cache 代码,-c 表示使用 redis cache

goctl model mysql ddl -c -src shorturl.sql -dir .
api/model
├── shorturl.sql
├── shorturlmodel.go              // 扩展代码
├── shorturlmodel_gen.go          // CRUD+cache 代码
└── vars.go                       // 定义常量和变量

因为我们后面要根据short_url去查询记录,所以我们在shorturlmodel_gen.go文件下增加FindOneByShortUrl方法

修改api/model/shorturlmodel_gen.go文件

shorturlModel interface {
   Insert(ctx context.Context, data *Shorturl) (sql.Result, error)
   FindOne(ctx context.Context, id int64) (*Shorturl, error)
   Update(ctx context.Context, data *Shorturl) error
  Delete(ctx context.Context, id int64) error
  FindOneByShortUrl(ctx context.Context, shortUrl string) (*Shorturl, error) //新增方法
}
//新增方法
func (m *defaultShorturlModel) FindOneByShortUrl(ctx context.Context, shortUrl string) (*Shorturl, error) {
    shorturlIdKey := fmt.Sprintf("%s%v", cacheShorturlIdPrefix, shortUrl)
    var resp Shorturl
    err := m.QueryRowCtx(ctx, &resp, shorturlIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
        query := fmt.Sprintf("select %s from %s where `short_url` = ? limit 1", shorturlRows, m.table)
        return conn.QueryRowCtx(ctx, v, query, shortUrl)
    })
    switch err {
    case nil:
        return &resp, nil 
    case sqlc.ErrNotFound:
        return nil, ErrNotFound
    default:
        return nil, err
    }
}

etc/shorturl-api.yaml文件增加以下配置

DataSource: root:123456@tcp(localhost:3310)/shorturl-service
Table: shorturl
Cache: 
  - Host: localhost:6379

修改 api/internal/config/config.go,如下:

type Config struct {
    rest.RestConf
    DataSource string          //增加代码
    Table      string          //增加代码
    Cache      cache.CacheConf //增加代码
}

修改 api/internal/svc/servicecontext.go,如下:

type ServiceContext struct {
   Config config.Config
  Model model.ShorturlModel //增加代码
}

func NewServiceContext(c config.Config) *ServiceContext {
   return &ServiceContext{
      Config: c,
      Model:  model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache),//增加代码
  }
}

重写api/internal/logic/getshorturllogic.go的GetShortUrl方法

func (l *GetShortUrlLogic) GetShortUrl(req *types.GetShortUrlRequest) (resp *types.GetShortUrlResponse, err error) {
    key := hash.Md5Hex([]byte(req.Url))[:6]
    _, err = l.svcCtx.Model.Insert(l.ctx, &model.Shorturl{
        ShortUrl: key,
        Url:      req.Url,
    })
    if err != nil {
        return nil, err
    }
    return &types.GetShortUrlResponse{
        ShortUrl: key,
    }, nil
}

重写api/internal/logic/getlongurllogic.go的GetLongUrl方法

func (l *GetLongUrlLogic) GetLongUrl(req *types.GetLongUrlRequest) (resp *types.GetLongUrlResponse, err error) {
    result, err := l.svcCtx.Model.FindOneByShortUrl(l.ctx, req.ShortUrl)
    if err != nil {
        return nil, err
    }
    return &types.GetLongUrlResponse{
        Url: result.Url,
    }, nil
}

重新启动服务

go run shorturl.go

再次请求接口

go-zero学习系列二:实现短链接服务

go-zero学习系列二:实现短链接服务

到这里我们的功能就已经实现了,在一个项目初期一般也都是只提供api接口就ok了,当项目变大后,我们可以将其扩展为微服务,下面我们改写一下,就不在logic直接调用model去操作数据库了,改为调用rpc服务去实现功能。

在项目根目录下新建rpc文件夹,在rpc文件夹下新建transform文件夹,在transform文件夹下执行下面的命令生成proto文件

goctl rpc template -o transform.proto

生成的proto模板文件是这样的

syntax = "proto3";

package transform;
option go_package="./transform";

message Request {
  string ping = 1;
}

message Response {
  string pong = 1;
}

service Transform {
  rpc Ping(Request) returns(Response);
}

我们跟上面的api文件一样根据自己的需求改写一下:

syntax = "proto3";

package transform;
option go_package="./transform";

message GetShortUrlRequest {
  string url = 1;
}

message GetShortUrlResponse {
  string short_url = 1;
}

message GetLongUrlRequest {
  string short_url = 1;
}

message GetLongUrlResponse {
  string url = 1;
}

service Transform {
  rpc GetShortUrl(GetShortUrlRequest) returns(GetShortUrlResponse);
  rpc GetLongUrl(GetLongUrlRequest) returns(GetLongUrlResponse);
}

然后用goctl生成rpc的代码,在transform目录下执行

goctl rpc protoc transform.proto --go_out=. --go-grpc_out=. --zrpc_out=. 

文件结构如下:

rpc/transform
├── etc
│   └── transform.yaml              // 配置文件
├── internal
│   ├── config
│   │   └── config.go               // 配置定义
│   ├── logic
│   │   ├── getlongurllogic.go          // longurl 业务逻辑在这里实现
│   │   └── getshorturllogic.go         // shorturl 业务逻辑在这里实现
│   ├── server
│   │   └── transformerserver.go    // 调用入口, 不需要修改
│   └── svc
│       └── servicecontext.go       // 定义 ServiceContext,传递依赖
├── transform
│   ├── transform.pb.go
│   └── transform_grpc.pb.go
├── transform.go                    // rpc 服务 main 函数
├── transform.proto
└── transformclient
    └── transform.go              // 提供了外部调用方法,无需修改

我们将上面api文件夹下面的model文件夹剪切到rpc文件夹下,然后把对应的配置也加上

transform.yaml加

DataSource: root:123456@tcp(localhost:3310)/shorturl-service
Table: shorturl
Cache: 
 - Host: localhost:6379

transform/internal/config/config.go修改

type Config struct {
   zrpc.RpcServerConf
  DataSource string // 手动代码
  Table string // 手动代码
  Cache cache.CacheConf // 手动代码
}

transform/internal/svc/servicecontext.go修改

type ServiceContext struct {
    Config config.Config
    Model  model.ShorturlModel //增加代码
}

func NewServiceContext(c config.Config) *ServiceContext {
    return &ServiceContext{
        Config: c,
        Model:  model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache), //增加代码
    }
}

然后把我们的logic文件补充一下:

getlongurllogic的GetLongUrl改为:

func (l *GetLongUrlLogic) GetLongUrl(in *transform.GetLongUrlRequest) (*transform.GetLongUrlResponse, error) {
    result, err := l.svcCtx.Model.FindOneByShortUrl(l.ctx, in.ShortUrl)
    if err != nil {
        return nil, err
    }
    return &transform.GetLongUrlResponse{
        Url: result.Url,
    }, nil
}

getshorturllogic的GetShortUrl改为:

func (l *GetShortUrlLogic) GetShortUrl(in *transform.GetShortUrlRequest) (*transform.GetShortUrlResponse, error) {
    key := hash.Md5Hex([]byte(in.Url))[:6]
    _, err := l.svcCtx.Model.Insert(l.ctx, &model.Shorturl{
        ShortUrl: key,
        Url:      in.Url,
    })
    if err != nil {
        return nil, err
    }
    return &transform.GetShortUrlResponse{
        ShortUrl: key,
    }, nil
}

ok rpc这部分就改完了,我们去修改一下api的部分,将原本调用model实现的逻辑改为调用rpc服务去实现

修改api/etc/shorturl-api.yaml文件

//新增
//通过 etcd 自动去发现可用的 transform 服务
Transform:
 Etcd: Hosts: - localhost:2379 
  Key: transform.rpc

修改api/internal/config/config.go文件

type Config struct {
    rest.RestConf
    Transform zrpc.RpcClientConf //新增代码
}

修改api/internal/svc/servicecontext.go文件

type ServiceContext struct {
    Config      config.Config
    Transformer transformclient.Transform //新增代码
}

func NewServiceContext(c config.Config) *ServiceContext {
    return &ServiceContext{
        Config:      c,
        Transformer: transformclient.NewTransform(zrpc.MustNewClient(c.Transform)), //新增代码
    }
}

修改我们的logic文件,改为调用rpc服务

getlongurllogic的GetLongUrl改为:

func (l *GetLongUrlLogic) GetLongUrl(req *types.GetLongUrlRequest) (resp *types.GetLongUrlResponse, err error) {
    ret, err := l.svcCtx.Transformer.GetLongUrl(l.ctx, &transform.GetLongUrlRequest{
        ShortUrl: req.ShortUrl,
    })
    if err != nil {
        return nil, err
    }
    return &types.GetLongUrlResponse{
        Url: ret.Url,
    }, nil
}

getshorturllogic的GetShortUrl改为:

func (l *GetShortUrlLogic) GetShortUrl(req *types.GetShortUrlRequest) (resp *types.GetShortUrlResponse, err error) {
    ret, err := l.svcCtx.Transformer.GetShortUrl(l.ctx, &transform.GetShortUrlRequest{
        Url: req.Url,
    })
    if err != nil {
        return nil, err
    }
    return &types.GetShortUrlResponse{
        ShortUrl: ret.ShortUrl,
    }, nil
}

ok 我们重启api服务跟rpc服务

#在api文件夹下执行
go run shorturl.go
#在rpc/transform文件夹下执行
go run transform.go

然后我们再调一下接口:

go-zero学习系列二:实现短链接服务

go-zero学习系列二:实现短链接服务

可以看到我们的功能实现逻辑已经改在rpc服务这边去实现了