这一节我们重现一下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看到我们这几个容器启动起来了就行
goland创建有一个新项目shorturl-service
编写api代码
在根目录下新建api文件夹并在api文件下执行
命令最好都自己敲一次不要复制粘贴,能增加一点熟练度
goctl api -o shorturl.api
基于这个模板我们写自己想要的内容就好了
修改后的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 mod tidy
把依赖都引入一下就好了
我们打开logic目下的两个文件
可以看到这里有提示 我们只需要在这里补充我们的逻辑代码就好了。
我们先来简单改写一下:
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端口
我们可以看下shorturl.go
的代码
如果你不通过-f
指定后面的配置文件的话 将会使用etc/shorturl-api.yaml作为配置文件。
下面我们通过postman请求一下写好的两个接口:
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
再次请求接口
到这里我们的功能就已经实现了,在一个项目初期一般也都是只提供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
然后我们再调一下接口:
可以看到我们的功能实现逻辑已经改在rpc服务这边去实现了