# rpc调用 # 概要 本节将通过rpc、internal api来操作课程的相关数据 # internal api(可选) internal api指通过内网实现服务间的相互调用,使用场景一般多见于一些相对复杂的业务。其和调用rpc的区别是:rpc功能业务尽量保证单一,简单,而internal api服务面对的业务场景相对比较复杂。 internal api的调用即通过内网直接访问api协议的方式获取数据,因此这里就跳过怎么使用了。 # rpc服务创建 首先创建library、user两个rpc服务,这里就不详细描述rpc创建服务了,rpc快速开始请点击[通过goctl快速创建rpc服务](https://www.yuque.com/tal-tech/go-zero/apuyly) 这里我就直接把图书管理系统(library)和借阅系统(borrow)相关rpc服务代码上传到[github](https://github.com/anqiansong/book)中去了,可以把course模块的代码直接拿过来使用,其中使用到的table(数据表)`library`的ddl如下: ```sql -- library CREATE TABLE `library` ( `id` varchar(36) NOT NULL DEFAULT '' COMMENT '书籍序列号', `name` varchar(255) NOT NULL DEFAULT '' COMMENT '书籍名称', `author` varchar(255) DEFAULT '' COMMENT '书籍作者', `publish_date` date DEFAULT NULL, `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `name_unique` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; ``` > 注意:我们假设每本图书在图书馆中数量只存在一本,不考虑多本相同图书情况,即借图书馆、借阅系统按照书籍名称唯一处理 # 生成borrowsystemmodel `borrow_system`表建表语句 ```sql -- borrwo_system CREATE TABLE `borrow_system` ( `id` bigint NOT NULL AUTO_INCREMENT, `book_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '书籍号', `user_id` bigint NOT NULL COMMENT '借书人', `status` tinyint(1) DEFAULT '0' COMMENT '书籍状态,0-未归还,1-已归还', `return_plan_date` timestamp NOT NULL COMMENT '预计还书时间', `return_date` int DEFAULT '0' COMMENT '实际还书时间', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `user_idx` (`user_id`,`book_no`) USING BTREE, KEY `book_no_idx` (`book_no`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; ``` 生成model代码 ```golang $ goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/gozero" -table="borrow_system" -dir ./model ``` > NOTE: `user`和`password`需要替换为实际的值 ### 代码补充 ```go vi ~/book/borrow/model/borrowsystemmodel.go ``` 在var上方添加 ```go const ( _ = iota Borrowing Return ) ``` 在结尾补充 ```go func (m *defaultBorrowSystemModel) FindOneByUserAndBookNo(userId int64, bookNo string) (*BorrowSystem, error) { query := `select ` + borrowSystemRows + ` from ` + m.table + ` where user_id = ? and book_no = ? limit 1` var resp BorrowSystem err := m.conn.QueryRow(&resp, query, userId, bookNo) switch err { case nil: return &resp, nil case sqlc.ErrNotFound: return nil, ErrNotFound default: return nil, err } } func (m *defaultBorrowSystemModel) FindOneByBookNo(bookNo string, status int) (*BorrowSystem, error) { query := `select ` + borrowSystemRows + ` from ` + m.table + ` where book_no = ? and status = ? limit 1` var resp BorrowSystem err := m.conn.QueryRow(&resp, query, bookNo, status) switch err { case nil: return &resp, nil case sqlc.ErrNotFound: return nil, ErrNotFound default: return nil, err } } ``` 填充interface方法 ```go FindOneByUserAndBookNo(userId int64, bookNo string) (*BorrowSystem, error) FindOneByBookNo(bookNo string, status int) (*BorrowSystem, error) ``` # borrow api服务 我们通过borrow api服务来实现借阅系统相关api协议,这里可以通过前面学习的示例可以快速创建borrow api服务,这里我们们就不重复赘述了 1、新建borrow文件夹,并创建borrow.api文件 ```bash $ mkdir book/borrow && cd borrow $ touch borrow.api ``` 2、修改borrow.api文件内容,增加借书、还书协议 borrow api ```go info( title: "图书借阅系统api" desc: "图书借阅系统api" author: "keson" email: "keson@xiaoheiban.cn" version: "v1.0" ) type BorrowReq { BookName string `json:"bookName"` ReturnPlan int64 `json:"returnPlan"` } type ReturnReq { BookName string `json:"bookName"` } @server( jwt: Auth ) service borrow-api { @handler borrow post /borrow/do (BorrowReq) @handler return post /borrow/return (ReturnReq) } ``` 代码生成 ```bash $ goctl api go -api borrow.api -dir . ``` 我们看一下生成后的borrow服务的代码结构 ``` └── api ├── borrow.api ├── borrow.go ├── etc │   └── borrow-api.yaml └── internal ├── config │   └── config.go ├── handler │   ├── borrowhandler.go │   ├── returnhandler.go │   └── routes.go ├── logic │   ├── borrowlogic.go │   └── returnlogic.go ├── svc │   └── servicecontext.go └── types └── types.go ``` # 添加rpc配置项 在borrow api服务中,我们将用到user.rpc和library.rpc两个rpc服务,我们先来添加一下配置项, 1、编辑config.go文件 ```bash $ vi book/borrow/api/internal/config/config.go ``` 添加如下内容 ```golang Mysql struct { DataSource string } LibraryRpc zrpc.RpcClientConf UserRpc zrpc.RpcClientConf ``` 2、编辑borrow-api.yaml ```bash $ vi vi book/borrow/api/etc/borrow-api.yaml ``` 添加配置内容 borrow-api.yaml ```yaml Name: borrow-api Host: 0.0.0.0 Port: 9999 Mysql: DataSource: user:password@tcp(127.0.0.1:3306)/gozero?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai Auth: AccessSecret: ad879037-c7a4-4063-9236-6bfc35d54b7d AccessExpire: 86400 LibraryRpc: Etcd: Hosts: - 127.0.0.1:2379 Key: library.rpc UserRpc: Etcd: Hosts: - 127.0.0.1:2379 Key: user.rpc ``` > NOTE: `user`和`password`需要替换为实际的值 # 创建rpc client 在前面我们已经把rpc的配置添加好了,接下来我们需要进行rpc client对象放入依赖对象ServiceContext 1、编辑servicecontext.go ```bash $ vi book/borrow/api/internal/svc/servicecontext.go ``` 添加UserRpc、LibraryRpc、BorrowSystemModel依赖资源 servicecontext.go ```go package svc import ( "book/borrow/api/internal/config" "book/borrow/model" "book/library/rpc/libraryclient" "book/user/rpc/user" "github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/zrpc" ) type ServiceContext struct { Config config.Config BorrowSystemModel model.BorrowSystemModel UserRpc userclient.User LibraryRpc libraryclient.Library } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) ur := userclient.NewUser(zrpc.MustNewClient(c.UserRpc)) lr := libraryclient.NewLibrary(zrpc.MustNewClient(c.LibraryRpc)) return &ServiceContext{ Config: c, BorrowSystemModel: model.NewBorrowSystemModel(conn), UserRpc: ur, LibraryRpc: lr, } } ``` # 调用rpc 1、新建error.go文件,用于标记业务错误 ```bash $ touch book/borrow/api/internal/logic/error.go ``` 添加内容 ```golang package logic import ( "book/shared" ) var ( errUserNotFound = shared.NewDefaultError("用户不存在") errBookNotFound = shared.NewDefaultError("书籍不存在") errInvalidParam = shared.NewDefaultError("参数错误") errUserReturn = shared.NewDefaultError("没有查询到该用户的借书记录") errBookBorrowed = shared.NewDefaultError("该书籍已被借阅") ) ``` 2、填充借书逻辑 ```bash $ vi book/borrow/api/internal/logic/borrowlogic.go ``` borrowlogic.go#Borrow ```go func (l *BorrowLogic) Borrow(userId string,req types.BorrowReq) error { userInt, err := strconv.ParseInt(fmt.Sprintf("%v", userId), 10, 64) if err != nil { return err } if req.ReturnPlan < time.Now().Unix() { return errInvalidParam } reply, err := l.svcCtx.UserRpc.IsUserExist(l.ctx, &user.UserExistReq{Id: userInt}) if err != nil { // code error // 这里判断not found是为了有些业务场景需要使用到not found,然后进行数据更新 // 当前业务其实可以直接返回error if shared.IsGRPCNotFound(err) { return errUserNotFound } return err } if !reply.Exists { return errUserNotFound } book, err := l.svcCtx.LibraryRpc.FindBookByName(l.ctx, &library.FindBookReq{Name: req.BookName}) if err != nil { // code error if shared.IsGRPCNotFound(err) { return errBookNotFound } return err } _, err = l.svcCtx.BorrowSystemModel.FindOneByBookNo(book.No, model.Borrowing) switch err { case nil: return errBookBorrowed case model.ErrNotFound: _, err = l.svcCtx.BorrowSystemModel.Insert(model.BorrowSystem{ BookNo: book.No, UserId: userInt, Status: model.Borrowing, ReturnPlanDate: time.Unix(req.ReturnPlan, 0), }) return err default: return err } } ``` 3、填充还书逻辑 ```bash $ vi book/borrow/api/internal/logic/returnlogic.go ``` returnlogic.go#Return ```go func (l *ReturnLogic) Return(userId string,req types.ReturnReq) error { userInt, err := strconv.ParseInt(fmt.Sprintf("%v", userId), 10, 64) if err != nil { return err } book, err := l.svcCtx.LibraryRpc.FindBookByName(l.ctx, &library.FindBookReq{Name: req.BookName}) if err != nil { // code error if shared.IsGRPCNotFound(err) { return errBookNotFound } return err } info, err := l.svcCtx.BorrowSystemModel.FindOneByUserAndBookNo(userInt, book.No) switch err { case nil: if info.Status == model.Return { return errBookReturn } info.ReturnDate = time.Now().Unix() info.Status = model.Return err = l.svcCtx.BorrowSystemModel.Update(*info) return err case model.ErrNotFound: return errUserReturn default: return err } } ``` 4、分别在returnhandler.go和borrowhandler.go中修改调用logic方法逻辑 borrowhandler.go ```golang err := l.Borrow(r.Header.Get("x-user-id"),req) ``` retrunhandler.go ```golang err := l.Return(r.Header.Get("x-user-id"),req) ``` 至此,我们就完整rpc服务的调用,接下来我们来访问`/borrow/do`和`/borrow/return`协议来验证一下。 # 启动服务 ## 启动rpc服务 启动user.rpc、library.rpc ```bash $ go run user.go -f etc/user.yaml ``` ![user-rpc.png](https://cdn.nlark.com/yuque/0/2020/png/465993/1603693376735-b06518d5-1571-4e85-9387-356c889f5032.png#align=left&display=inline&height=256&margin=%5Bobject%20Object%5D&name=user-rpc.png&originHeight=256&originWidth=2224&size=35954&status=done&style=none&width=2224) ```bash $ go run library.go -f etc/library.yaml ``` ![lib-rpc.png](https://cdn.nlark.com/yuque/0/2020/png/465993/1603693385904-13f46aaa-5973-4d6a-8f4f-e99c85361f4b.png#align=left&display=inline&height=220&margin=%5Bobject%20Object%5D&name=lib-rpc.png&originHeight=220&originWidth=2224&size=36115&status=done&style=none&width=2224) > NOTE:启动rpc服务之前需要安装并启动etcd,etcd安装与启动可自行google ## 启动api服务 启动user.api、borrow.api服务 ```bash $ go run user.go -f etc/user-api.yaml ``` ![user-api-run.png](https://cdn.nlark.com/yuque/0/2020/png/465993/1603693402379-60587956-9d21-4dd3-b318-9ccbf55e9646.png#align=left&display=inline&height=220&margin=%5Bobject%20Object%5D&name=user-api-run.png&originHeight=220&originWidth=2224&size=33973&status=done&style=none&width=2224) ```bash $ go run borrow.go -f etc/borrow-api.yaml ``` ![borrow-api.png](https://cdn.nlark.com/yuque/0/2020/png/465993/1603693419431-1c1468f0-d0d7-41ca-8fa7-bd28ad9f5088.png#align=left&display=inline&height=220&margin=%5Bobject%20Object%5D&name=borrow-api.png&originHeight=220&originWidth=2224&size=35538&status=done&style=none&width=2224) # 访问服务 访问服务前我们插入一些书籍数据(这里不再实现图书管理系统了) ```sql INSERT INTO library (id,name,author,publish_date) value ('5f96634494d7e147b5d25b68','go-zero微服务框架从0开始','keson','2020-10-01'); INSERT INTO library (id,name,author,publish_date) value ('5f96634494d7e147b5d25b69','go-zero微服务设计理念','keson','2020-10-01'); INSERT INTO library (id,name,author,publish_date) value ('5f96634494d7e147b5d25b6a','go-zero使用最佳实践','keson','2020-10-01'); ``` 1、登录 ```bash $ curl -i -X POST \ http://127.0.0.1:8888/user/login \ -H 'cache-control: no-cache' \ -H 'content-type: application/json' \ -d '{ "username":"admin", "password":"666666" }' ``` > 注意:windows系统curl json需要对json进行转义。 ![user-login-result.png](https://cdn.nlark.com/yuque/0/2020/png/465993/1603693464189-f5f48fac-dc9a-42e8-9c94-0be65bbc69db.png#align=left&display=inline&height=724&margin=%5Bobject%20Object%5D&name=user-login-result.png&originHeight=724&originWidth=2224&size=186515&status=done&style=none&width=2224) 2、借书 ```bash $ curl -i -X POST \ http://127.0.0.1:9999/borrow/do \ -H 'authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDM3NzEzNjIsImlhdCI6MTYwMzY4NDk2MiwidXNlcklkIjoxfQ.cYxttodCit_kIJw88eXsf2UpdsPsIfg_YxiN7ZNq0aE' \ -H 'content-type: application/json' \ -H 'x-user-id: 1' \ -d '{ "bookName":"go-zero微服务框架从0开始", "returnPlan":1603777059 }' ``` > 注意:windows系统curl json需要对json进行转义。 ![curl-borrow.png](https://cdn.nlark.com/yuque/0/2020/png/465993/1603693967296-f8451f11-3bbc-485e-8723-059edf75d331.png#align=left&display=inline&height=652&margin=%5Bobject%20Object%5D&name=curl-borrow.png&originHeight=652&originWidth=2224&size=149012&status=done&style=none&width=2224) 3、还书 ```bash $ curl -i -X POST \ http://127.0.0.1:9999/borrow/return \ -H 'authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDM3NzEzNjIsImlhdCI6MTYwMzY4NDk2MiwidXNlcklkIjoxfQ.cYxttodCit_kIJw88eXsf2UpdsPsIfg_YxiN7ZNq0aE' \ -H 'content-type: application/json' \ -H 'x-user-id: 1' \ -d '{ "bookName":"go-zero微服务框架从0开始" }' ``` > 注意:windows系统curl json需要对json进行转义。 ![curl-return.png](https://cdn.nlark.com/yuque/0/2020/png/465993/1603693477685-bc278c41-aac6-41ad-b9aa-958d3dc560df.png#align=left&display=inline&height=616&margin=%5Bobject%20Object%5D&name=curl-return.png&originHeight=616&originWidth=2224&size=138852&status=done&style=none&width=2224) # 参考资源 [book源码](https://github.com/anqiansong/book)