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)
}

浙公网安备 33010602011771号