go-zero 实战 - Food Foodlist

333 阅读3分钟

我们通过一个系列文章跟大家详细展示一个 go-zero 微服务实例,整个系列分十三篇文章,目录结构如下:

  1. go-zero 实战 - 服务划分与项目创建
  2. go-zero 实战 - User API Gateway
  3. go-zero 实战 - User Login
  4. go-zero 实战 - User Register
  5. go-zero 实战 - User Userinfo
  6. go-zero 实战 - Food API Gateway
  7. go-zero 实战 - Food Search
  8. go-zero 实战 - Food AddFood
  9. go-zero 实战 - Food DeleteFood
  10. go-zero 实战 - Food Foodlist
  11. go-zero 实战进阶 - rpc 服务
  12. go-zero 实战进阶 - 用户管理 rpc 服务
  13. go-zero 实战进阶 - 食材管理 rpc 服务

期望通过本系列文章带你在本地利用 go-zero 快速开发一个《食谱指南》系统,让你快速上手微服务。

上一篇 文章中,我们介绍了如何搭建删除用户指定食材的接口。本篇我们将介绍如何搭建获取指定用户食谱列表的接口。

因为 /food/foodlist 接口需要返回指定用户下的所有食物信息,因此需要先根据 userIduser_food 表中查询所有匹配到记录的 foodid 集合,再利用 foodid 集合从 food 表中查询到所有匹配到的 food 集合。

考虑到用 goctl model 命令生成的 mysql CRUD 代码不能满足我们查询的需要,因此我们需要新增自定义 model 方法。那我们应该把自定义方法写在哪里呢?(xxxmodel.go or xxxmodel_gen.go

在官方文档 goctl model 一文中提到:

新增自定义 model 方法示例

在以上代码生成中可以看出,每张表生成都会有 4 个文件,其中 xxmodel.go 和 xxmodelgen.go 是 model 代码文件,带有 gen.go 文件后缀的 代码一般都会包含 DO NOT EDIT 标识,因此不能在这个文件中添加自定义代码,当用户需要新增或者修改代码时,可以在 xxmodel.go 中进行编辑, 在 xxmodel.go 中我们提供了 customXXXModel 结构体便于开发者进行扩展。

因此,我们应该在 userfoodmodel.gofoodmodel.go 这两个文件中添加自定义代码:

  1. UserFoodModel 接口中新增方法 FindManyByUserid(ctx context.Context, userid int64) ([]*UserFood, error),并实现 customUserFoodModel
$ vim foodmanage/model/userfoodmodel.go

package model

import (
    "context"
    "fmt"
    "github.com/zeromicro/go-zero/core/stores/sqlc"
    "github.com/zeromicro/go-zero/core/stores/sqlx"
)

var _ UserFoodModel = (*customUserFoodModel)(nil)

type (
    // UserFoodModel is an interface to be customized, add more methods here,
    // and implement the added methods in customUserFoodModel.
    UserFoodModel interface {
       userFoodModel
       FindManyByUserid(ctx context.Context, userid int64) ([]*UserFood, error)
    }

    customUserFoodModel struct {
       *defaultUserFoodModel
    }
)

func (m *customUserFoodModel) FindManyByUserid(ctx context.Context, userid int64) ([]*UserFood, error) {
    var resp []*UserFood
    query := fmt.Sprintf("select %s from %s where `userid` = ?", userFoodRows, m.table)
    err := m.conn.QueryRowsCtx(ctx, &resp, query, userid)
    switch err {
    case nil:
       return resp, nil
    case sqlc.ErrNotFound:
       return nil, ErrNotFound
    default:
       return nil, err
    }
}

// NewUserFoodModel returns a model for the database table.
func NewUserFoodModel(conn sqlx.SqlConn) UserFoodModel {
    return &customUserFoodModel{
       defaultUserFoodModel: newUserFoodModel(conn),
    }
}
  1. FoodModel 接口中新增方法 FindMany(ctx context.Context, ids []string) ([]*Food, error),并实现 customFoodModel
$ vim foodmanage/model/foodmodel.go

package model

import (
    "context"
    "fmt"
    "github.com/zeromicro/go-zero/core/stores/sqlc"
    "github.com/zeromicro/go-zero/core/stores/sqlx"
    "strings"
)

var _ FoodModel = (*customFoodModel)(nil)

type (
    // FoodModel is an interface to be customized, add more methods here,
    // and implement the added methods in customFoodModel.
    FoodModel interface {
       foodModel
       FindMany(ctx context.Context, ids []string) ([]*Food, error)
    }

    customFoodModel struct {
       *defaultFoodModel
    }
)

func (m *customFoodModel) FindMany(ctx context.Context, ids []string) ([]*Food, error) {
    query := fmt.Sprintf("select %s from %s where `id` in (%s)", foodRows, m.table, strings.Join(ids, ","))
    var resp []*Food
    err := m.conn.QueryRows(&resp, query)
    switch err {
    case nil:
       return resp, nil
    case sqlc.ErrNotFound:
       return nil, ErrNotFound
    default:
       return nil, err
    }
}

// NewFoodModel returns a model for the database table.
func NewFoodModel(conn sqlx.SqlConn) FoodModel {
    return &customFoodModel{
       defaultFoodModel: newFoodModel(conn),
    }
}

添加食谱列表接口逻辑

$ vim foodmanage/api/internal/logic/foodlistlogic.go

package logic

import (
    "context"
    "encoding/json"
    "strconv"

    "FoodGuides/service/foodmanage/api/internal/svc"
    "FoodGuides/service/foodmanage/api/internal/types"

    "github.com/zeromicro/go-zero/core/logx"
)

type FoodListLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewFoodListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FoodListLogic {
    return &FoodListLogic{
       Logger: logx.WithContext(ctx),
       ctx:    ctx,
       svcCtx: svcCtx,
    }
}

func (l *FoodListLogic) FoodList() (*types.FoodListResponse, error) {
    // 获取 jwt 载体中 `uid` 信息,
    uid, _ := l.ctx.Value("uid").(json.Number).Int64()
    
    userFoods, err := l.svcCtx.UserFoodModel.FindManyByUserid(l.ctx, uid)
    if err != nil {
       return nil, err
    }

    var foodIds []string
    for _, food := range userFoods {
       foodIds = append(foodIds, strconv.FormatInt(food.Foodid.Int64, 10))
    }

    foods, err := l.svcCtx.FoodModel.FindMany(l.ctx, foodIds)
    if err != nil {
       return nil, err
    }

    var foodReplays []types.FoodReply
    for _, food := range foods {
       foodReply := types.FoodReply{
          Id:           strconv.FormatInt(food.Id, 10),
          Name:         food.Name,
          Protein:      food.Protein,
          Fat:          food.Fat,
          Carbohydrate: food.Carbohydrate,
          Calorie:      food.Calorie,
          Minerals:     food.Minerals,
          Calcium:      food.Calcium,
          Phosphorus:   food.Phosphorus,
          Iron:         food.Iron,
          Purine:       food.Purine,
       }
       foodReplays = append(foodReplays, foodReply)
    }

    return &types.FoodListResponse{List: foodReplays}, nil
}

修改 Response 返回格式

$ vim foodmanage/api/internal/handler/foodlisthandler.go

package handler

import (
    "FoodGuides/common/responsex"
    "net/http"

    "FoodGuides/service/foodmanage/api/internal/logic"
    "FoodGuides/service/foodmanage/api/internal/svc"
    "github.com/zeromicro/go-zero/rest/httpx"
)

func FoodListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
       l := logic.NewFoodListLogic(r.Context(), svcCtx)
       resp, err := l.FoodList()
       if err != nil {
          httpx.OkJson(w, responsex.FailureResponse(nil, err.Error(), 1000))
       } else {
          httpx.OkJson(w, responsex.SuccessResponse(resp, "请求成功"))
       }
    }
}

启动服务

启动 food api 服务, 运行成功后,food api 则运行在本机的 8889 端口

➜  service: 
$ go run foodmanage/api/food.go -f foodmanage/api/etc/food-api.yaml
Starting server at 0.0.0.0:8889...

我们用 Postman 尝试请求 /food/foodlist 接口:

  1. PostmanAuthorization 选项中选择 Bearer Token,填写登录成功后 Api 返回的 accessToken 字段值。
  2. 点击发送请求按钮,有如下截图的响应说明接口运行正常。

image.png

这样 Food - Foodlist 接口就开发完成了。至此整个 go-zero 实战教程就结束了。如果你还想了解 rpc 服务相关的知识,请移步至下一篇 《go-zero 实战进阶 - rpc 服务》 的进阶教程。

上一篇《go-zero 实战 - Food DeleteFood》

下一篇《go-zero 实战进阶 - rpc 服务》