go微服务开发:go-zero入门教程(一)
以下内容,参考了go-zero官方文档,是对官方文档的进阶指南章节的梳理汇总。
go-zero的进阶指南,请参考 https://go-zero.dev/cn/docs/advance/business-dev
通过本文,你将学习到如下知识点:
1.如何使用go-zero定义api文件
2.如何为定义的api文件生成api服务
3.如何编写模块业务逻辑
4.go-zero开发注意实现,参见这里 https://www.cnblogs.com/jamstack/p/17223639.html
在开始之前,假设你已经对go-zero有了基本的了解,并且了解go-zero编写api文件的语法。如果还不了解,建议先阅读这里 https://go-zero.dev/cn/docs/design/grammar/
开发环境:
Windows 11
Terminal preview
go 1.19
go-zero的进阶指南的演示工程,共包含2个模块:user和search,本篇讲述的是user模块,search模块请参见:go微服务开发:go-zero入门教程(二)
第一步:下载演示工程、设计数据库表的ddl、生成数据库表和model文件(如果是已有的数据库表,也可以逆向生成model)
1.下载并解压go-zero进阶指南提供的演示工程,下载地址为 https://go-zero.dev/cn/assets/files/book-3d0b9e679f9e502cb07685b701c450cf.zip
2.假设我们使用的是一个新的测试库,不存在数据库表,user模块的model文件夹是我们定义数据库表ddl的地方,这里存在着一个名为user.sql的ddl文件,我们为这个ddl生成数据库表,执行如下命令:
goctl model mysql ddl -src user.sql -dir . -c
3.向生成的数据库表user,写入测试数据
INSERT INTO `user` (number,name,password,gender)values ('666','小明','123456','男');
题外话:如果user表已经存在,我们要为user表生成model,可以执行如下命令:
goctl model mysql datasource -url="$datasource" -table="user" -c -dir .
第二步:定义api文件、生成api服务
1.定义user.api
cd service/user/api/,转到user模块的api文件夹下,定义一个名为user.api的api文件,代码如下:
type ( LoginReq { Username string `json:"username"` Password string `json:"password"` } LoginReply { Id int64 `json:"id"` Name string `json:"name"` Gender string `json:"gender"` AccessToken string `json:"accessToken"` AccessExpire int64 `json:"accessExpire"` RefreshAfter int64 `json:"refreshAfter"` } ) service user-api { @handler login post /user/login (LoginReq) returns (LoginReply) }
2.为user.api生成api服务
goctl api go -api user.api -dir .
第三步:编写user模块的业务代码
1.定义数据库的配置
cd service/user/api/internal/config/,编辑config.go,代码如下:
package config import ( "github.com/zeromicro/go-zero/rest" "github.com/zeromicro/go-zero/core/stores/cache" ) type Config struct { rest.RestConf Mysql struct{ DataSource string } CacheRedis cache.CacheConf Auth struct{ AccessSecret string AccessExpire int64 } }
2.填写数据库的yaml配置参数
cd service/user/api/etc/,编辑user-api.yaml,代码如下:
Name: user-api Host: 0.0.0.0 Port: 8888 Mysql: DataSource: root:dev@123456@tcp(127.0.0.1)/book?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: 127.0.0.1:6379 Pass: Type: node Auth: AccessSecret: abcdefgh AccessExpire: 3600
3.为servicecontext完善服务依赖
cd service/user/api/internal/svc/,编辑servicecontext.go,代码如下:
type ServiceContext struct { Config config.Config UserModel model.UserModel } func NewServiceContext(c config.Config) *ServiceContext { conn:=sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, UserModel: model.NewUserModel(conn,c.CacheRedis), } }
我们可以看到,servicecontext.go里定义了一个名为ServiceContext的结构体,ServiceContext的结构体承载着config.Config和model.UserModel,分别对应的是user模块的数据库配置和UserModel。
4.编写user模块的业务逻辑,这里对应的是处理登录的业务逻辑
cd service/user/api/internal/logic/,编辑loginlogic.go,代码如下:
package logic import ( "book/service/user/model" "context" "errors" "github.com/golang-jwt/jwt/v4" "strings" "time" "book/service/user/api/internal/svc" "book/service/user/api/internal/types" "github.com/zeromicro/go-zero/core/logx" ) type LoginLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic { return &LoginLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *LoginLogic) Login(req *types.LoginReq) (*types.LoginReply, error) { if len(strings.TrimSpace(req.Username)) == 0 || len(strings.TrimSpace(req.Password)) == 0 { return nil, errors.New("参数错误") } userInfo, err := l.svcCtx.UserModel.FindOneByNumber(l.ctx, req.Username) switch err { case nil: case model.ErrNotFound: return nil, errors.New("用户名不存在") default: return nil, err } if userInfo.Password != req.Password { return nil, errors.New("用户密码不正确") } // ---start--- now := time.Now().Unix() accessExpire := l.svcCtx.Config.Auth.AccessExpire jwtToken, err := l.getJwtToken(l.svcCtx.Config.Auth.AccessSecret, now, l.svcCtx.Config.Auth.AccessExpire, userInfo.Id) if err != nil { return nil, err } // ---end--- return &types.LoginReply{ Id: userInfo.Id, Name: userInfo.Name, Gender: userInfo.Gender, AccessToken: jwtToken, AccessExpire: now + accessExpire, RefreshAfter: now + accessExpire/2, }, nil } func (l *LoginLogic) getJwtToken(secretKey string, iat, seconds, userId int64) (string, error) { claims := make(jwt.MapClaims) claims["exp"] = iat + seconds claims["iat"] = iat claims["userId"] = userId token := jwt.New(jwt.SigningMethodHS256) token.Claims = claims return token.SignedString([]byte(secretKey)) }
第四步:启动user模块
cd service/user/api,执行如下命令:
go run user.go -f etc/user-api.yaml
第五步:使用cmd测试user模块
curl -i -X POST http://127.0.0.1:8888/user/login -H "Content-Type: application/json" -d "{ \"username\":\"666\", \"password\":\"123456\" }"
可以看到/user/login接口以json的形式返回了对应的用户信息,但是并不包含password字段,如果我们想得到password字段,该怎么实现呢?使用go-zero的好处就体现出来了,我们不需要编辑生成的api服务,我们只需要在user.api文件的LoginReply结构体里加上Password,重新生成api服务,重新生成api服务时不会覆盖已经生成的文件:
type ( LoginReq { Username string `json:"username"` Password string `json:"password"` } LoginReply { Id int64 `json:"id"` Name string `json:"name"` Password string `json:"password"` Gender string `json:"gender"` AccessToken string `json:"accessToken"` AccessExpire int64 `json:"accessExpire"` RefreshAfter int64 `json:"refreshAfter"` } ) service user-api { @handler login post /user/login (LoginReq) returns (LoginReply) }