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生成数据库表,执行如下命令:
1 | goctl model mysql ddl -src user.sql -dir . -c |
3.向生成的数据库表user,写入测试数据
1 | INSERT INTO `user` (number,name,password,gender)values ( '666' , '小明' , '123456' , '男' ); |
题外话:如果user表已经存在,我们要为user表生成model,可以执行如下命令:
1 | goctl model mysql datasource -url= "$datasource" -table= "user" -c -dir . |
第二步:定义api文件、生成api服务
1.定义user.api
cd service/user/api/,转到user模块的api文件夹下,定义一个名为user.api的api文件,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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服务
1 | goctl api go -api user.api -dir . |
第三步:编写user模块的业务代码
1.定义数据库的配置
cd service/user/api/internal/config/,编辑config.go,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 | 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,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 | 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,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 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,执行如下命令:
1 | go run user. go -f etc/user-api.yaml |
第五步:使用cmd测试user模块
1 | 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服务时不会覆盖已经生成的文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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) } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通