环境安装
goctl
安装
bash
go install github.com/zeromicro/go-zero/tools/goctl@latest
etcd 安装
bash
docker run --name etcd -d -p 2379:2379 -p 2380:2380 -e ALLOW_NONE_AUTHENTICATION=yes bitnami/etcd:3.3.11 etcd
etcd 基本命令
bash
# 设置或更新值
etcdctl put name 张三
# 获取值
etcdctl get name
# 只要value
etcdctl get name --print-value-only
# 获取name前缀的键值对
etcdctl get --prefix name
# 删除键值对
etcdctl del name
# 监听键的变化
etcdctl watch name
api 语法
service
定义的代码块,是用于 api
服务
service
声明一个变量users
,用于定义服务的名称(可以小写)@handler
声明一个变量login
,用于定义服务的方法名称post
+/api/users/login
,用于定义服务的路径- 第一个括号内的
LoginRequest
是请求参数(如果没有请求参数,可以不写) returns
后面的括号内Response
是返回参数
- 第一个括号内的
go
type LoginRequest {
UserName string `json:"userName"`
Password string `json:"password"`
}
type Response {
Code int `json:"code"`
Data string `json:"data"`
Msg string `json:"msg"`
}
type UserInfo {
UserName string `json:"userName"`
Addr string `json:"addr"`
Id uint `json:"id"`
}
type UserInfoResponse {
Code int `json:"code"`
Data UserInfo `json:"data"`
Msg string `json:"msg"`
}
service users {
@handler login
post /api/users/login (LoginRequest) returns (Response)
@handler userInfo
get /api/users/info returns (UserInfoResponse)
}
生成代码,-api
表示 api
文件的路径,-dir
表示生成代码的路径,代码如下:
bash
goctl api go -api study/user/user.api -dir study/user
抽离公共代码
比如上面的 Response
和 UserInfoResponse
响应信息,有一部分是公共的,比如 Code
、Msg
、Data
,没有响应都有这些字段,所以可以抽离出来,代码如下:
-
新建一个公共包
zero_study/study/common
,代码如下:-
声明一个函数
Response
,接收4
个参数,用于返回响应信息r
是请求对象w
是响应对象res
是响应数据err
是错误信息
-
函数内部根据错误类型,返回不同的响应信息,这里是根据
err
是否为空,如果为空,返回500
,否则返回200
gotype Body struct { Code int `json:"code"` Data any `json:"data"` Msg string `json:"msg"` } func Response(r *http.Request, w http.ResponseWriter, res any, err error) { if err != nil { body := Body{ Code: 500, Data: nil, Msg: err.Error(), } httpx.WriteJson(w, http.StatusOK, body) return } body := Body{ Code: 200, Data: res, Msg: "成功", } httpx.WriteJson(w, http.StatusOK, body) }
-
-
修改接口处理函数
zero_study/study/api-v2/internal/handler/loginhandler.go
- 要注意的是,有多少接口处理函数,就要修改多少个文件,这里只修改
loginhandler.go
文件
gofunc loginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req types.LoginRequest if err := httpx.Parse(r, &req); err != nil { httpx.ErrorCtx(r.Context(), w, err) return } l := logic.NewLoginLogic(r.Context(), svcCtx) resp, err := l.Login(&req) // if err != nil { // httpx.ErrorCtx(r.Context(), w, err) // } else { // httpx.OkJsonCtx(r.Context(), w, resp) // } // 注释上面 4 行代码,使用下面的代码 response.Response(r, w, resp, err) } }
- 要注意的是,有多少接口处理函数,就要修改多少个文件,这里只修改
api 前缀
定义接口时,可能一类接口都有一个前缀,比如 users
接口,所有的接口都是以 /api/users
开头,这时可以使用,@server
用于定义服务的前缀,比如 prefix: /api/users
,代码如下:
go
@server (
prefix: /api/users
)
service users {
@handler login
post /login (LoginRequest) returns (string)
@handler userInfo
get /info returns (UserInfoResponse)
}
创建 api
在根目录下面创建 video
文件夹,然后在 video
文件夹下面创建 api/video.api
文件,内容如下:
go
type (
VideoReq {
Id string `path:"id"`
}
VideoRes {
Id string `json:"id"`
Name string `json:"name"`
}
)
service video {
@handler getVideo
get /api/videos/:id (VideoReq) returns (VideoRes)
}
然后运行命令:
bash
goctl api go -api video/api/video.api -dir video/api/
会在根目录下生成一系列文件:
bash
- etcd
- video.yaml
- internal
- config
- config.go
- handler
- getvideohandler.go
- routes.go
- logic
- getvideologic.go
- svc
- servicecontext.go
- types
- types.go
- video.go
- video.api
要在 video
服务中调用 user
服务,所以需要做以下修改
-
在
video/api/internal/config/config.go
文件中增加UserRpc
字段gotype Config struct { rest.RestConf // 增加这行代码 UserRpc zrpc.RpcClientConf }
-
在
video/api/internal/svc/servicecontext.go
文件中增加UserRpc
字段gotype ServiceContext struct { Config config.Config // 增加这行代码 UserRpc userclient.User } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, // 增加这行代码 UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)), } }
-
修改
video/api/etc/video.yaml
文件,增加etcd
配置yamlName: video Host: 0.0.0.0 Port: 8888 # 增加下面的配置 UserRpc: Etcd: Hosts: - etcd-server:2379 # 修改为你的 etcd 地址 Key: user.rpc
-
完善服务
video/api/internal/logic/getvideologic.go
gofunc (l *GetVideoLogic) GetVideo(req *types.VideoReq) (resp *types.VideoRes, err error) { // 增加下面的代码 user1, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.IdRequest{Id: "1"}) if err != nil { return nil, err } return &types.VideoRes{Id: req.Id, Name: user1.Name}, nil }
jwt
创建 jwt
首先在 user.api
中定义 jwt
的请求和响应参数,代码如下:
这两个接口分开是是因为 login
接口不需要 jwt
,userInfo
接口需要 jwt
,所以可以在 userInfo
接口上面加上 jwt: Auth
,代码如下:
go
@server (
prefix: /api/users
)
service users {
@handler login
post /login (LoginRequest) returns (string)
}
@server (
prefix: /api/users
jwt: Auth
)
service users {
@handler userInfo
get /info returns (UserInfoResponse)
}
运行命令生成代码:
bash
goctl api go -api study/user/user.api -dir study/user
让生成的代码支持 jwt
需要做以下修改:
-
在配置文件中添加
jwt
的配置信息AccessSecret
: 是不小于8
位的字符串AccessExpire
: 是jwt
的过期时间,单位是s
yamlAuth: AccessSecret: 123456784r AccessExpire: 3600
-
编写
jwt
中间件,主要是两个函数GenToken
生成jwt
的函数ParseToken
验证jwt
的函数
gotype JwtPayload struct { UserID uint `json:"user_id"` Username string `json:"username"` Role int `json:"role"` } type CustomClaims struct { JwtPayload jwt.RegisteredClaims } func GenToken(user JwtPayload, accessSecret string, expires int64) (string, error) { claim := CustomClaims{ JwtPayload: user, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * time.Duration(expires))), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim) return token.SignedString([]byte(accessSecret)) } func ParseToken(tokenStr string, accessSecret string, expires int64) (*CustomClaims, error) { token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte(accessSecret), nil }) if err != nil { return nil, err } claims, ok := token.Claims.(*CustomClaims) if !ok { return nil, errors.New("token claims type error") } return claims, nil }
-
调用
login
接口生成jwt
- 使用
l.svcCtx.Config.Auth
方式拿到配置信息
gofunc (l *LoginLogic) Login(req *types.LoginRequest) (resp string, err error) { // 拿到配置信息 auth := l.svcCtx.Config.Auth token, err := jwts.GenToken(jwts.JwtPayload{ UserID: 1, Username: "uccs", Role: 1, }, auth.AccessSecret, auth.AccessExpire) if err != nil { return "", err } return token, nil }
- 使用
-
userInfo
接口需要鉴权- 从
l.ctx.Value("user_id")
拿到userId
- 这里要注意的是,这里解析出来的事
json.Number
类型的数字
- 这里要注意的是,这里解析出来的事
gofunc (l *UserInfoLogic) UserInfo() (resp *types.UserInfoResponse, err error) { userId := l.ctx.Value("user_id").(json.Number) // 断言成 json.Number username := l.ctx.Value("username").(string) uid, _ := userId.Int64() // 拿到 userId,在进行 uint 转换 return &types.UserInfoResponse{Id: uint(uid), UserName: username}, nil }
- 从
-
如果没有
jwt
的接口需要响应json
,就需要需求user.go
中的代码-
定义一个
JwtUnauthorizedResult
函数,用来处理各种失败(可以根据错误信息,自定义响应信息)gofunc JwtUnauthorizedResult(w http.ResponseWriter, r *http.Request, err error) { httpx.WriteJson(w, http.StatusOK, response.Body{Code: 401, Msg: "鉴权失败", Data: nil}) }
-
修改
rest.MustNewServer
传入参数,将JwtUnauthorizedResult
传入进去goserver := rest.MustNewServer(c.RestConf, rest.WithUnauthorizedCallback(JwtUnauthorizedResult))
-