go-zero 基本使用

环境安装

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

抽离公共代码

比如上面的 ResponseUserInfoResponse 响应信息,有一部分是公共的,比如 CodeMsgData,没有响应都有这些字段,所以可以抽离出来,代码如下:

  1. 新建一个公共包 zero_study/study/common,代码如下:

    • 声明一个函数 Response,接收 4 个参数,用于返回响应信息

      • r 是请求对象
      • w 是响应对象
      • res 是响应数据
      • err 是错误信息
    • 函数内部根据错误类型,返回不同的响应信息,这里是根据 err 是否为空,如果为空,返回 500,否则返回 200

      go 复制代码
      type 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)
      }
  2. 修改接口处理函数 zero_study/study/api-v2/internal/handler/loginhandler.go

    • 要注意的是,有多少接口处理函数,就要修改多少个文件,这里只修改 loginhandler.go 文件
    go 复制代码
    func 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 服务,所以需要做以下修改

  1. video/api/internal/config/config.go 文件中增加 UserRpc 字段

    go 复制代码
    type Config struct {
      rest.RestConf
      // 增加这行代码
      UserRpc zrpc.RpcClientConf
    }
  2. video/api/internal/svc/servicecontext.go 文件中增加 UserRpc 字段

    go 复制代码
    type 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)),
      }
    }
  3. 修改 video/api/etc/video.yaml 文件,增加 etcd 配置

    yaml 复制代码
    Name: video
    Host: 0.0.0.0
    Port: 8888
    # 增加下面的配置
    UserRpc:
      Etcd:
        Hosts:
          - etcd-server:2379 # 修改为你的 etcd 地址
        Key: user.rpc
  4. 完善服务 video/api/internal/logic/getvideologic.go

    go 复制代码
    func (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 接口不需要 jwtuserInfo 接口需要 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 需要做以下修改:

  1. 在配置文件中添加 jwt 的配置信息

    • AccessSecret: 是不小于 8 位的字符串
    • AccessExpire: 是 jwt 的过期时间,单位是 s
    yaml 复制代码
    Auth:
      AccessSecret: 123456784r
      AccessExpire: 3600
  2. 编写 jwt 中间件,主要是两个函数

    • GenToken 生成 jwt 的函数
    • ParseToken 验证 jwt 的函数
    go 复制代码
    type 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
    }
  3. 调用 login 接口生成 jwt

    • 使用 l.svcCtx.Config.Auth 方式拿到配置信息
    go 复制代码
    func (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
    }
  4. userInfo 接口需要鉴权

    • l.ctx.Value("user_id") 拿到 userId
      • 这里要注意的是,这里解析出来的事 json.Number 类型的数字
    go 复制代码
    func (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
    }
  5. 如果没有 jwt 的接口需要响应 json,就需要需求 user.go 中的代码

    • 定义一个 JwtUnauthorizedResult 函数,用来处理各种失败(可以根据错误信息,自定义响应信息)

      go 复制代码
      func JwtUnauthorizedResult(w http.ResponseWriter, r *http.Request, err error) {
        httpx.WriteJson(w, http.StatusOK, response.Body{Code: 401, Msg: "鉴权失败", Data: nil})
      }
    • 修改 rest.MustNewServer 传入参数,将 JwtUnauthorizedResult 传入进去

      go 复制代码
      server := rest.MustNewServer(c.RestConf, rest.WithUnauthorizedCallback(JwtUnauthorizedResult))
相关推荐
许野平21 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
童先生2 小时前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
LunarCod2 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
码农派大星。3 小时前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man3 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*3 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu3 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s3 小时前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子4 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算