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))
相关推荐
武子康20 分钟前
大数据-269 实时数仓 - DIM DW ADS 层处理 Scala实现将数据写出HBase等
java·大数据·数据仓库·后端·flink·scala·hbase
山山而川粤2 小时前
酒店管理系统|Java|SSM|VUE| 前后端分离
java·开发语言·后端·学习·mysql
JINGWHALE12 小时前
设计模式 结构型 适配器模式(Adapter Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·适配器模式
绝无仅有3 小时前
gozero对接开放平台分贝通中新建费用报销的sdk设计与解决方案
后端·面试·架构
一线大码12 小时前
CMD 命令使用之 ping 命令
windows·后端
一线大码12 小时前
CMD 命令使用之 type 命令
windows·后端
豆约翰13 小时前
golang点类圆类求pi值
开发语言·后端·golang
一个单纯的少年14 小时前
HTTP STATUS CODE详情,HTTP状态码大全列表
服务器·前端·网络·后端·网络协议·http·产品运营
Cikiss14 小时前
Tomcat解析
java·服务器·后端·servlet·tomcat
vip1024p14 小时前
Spring Boot(七):Swagger 接口文档
java·spring boot·后端