我们通过一个系列文章跟大家详细展示一个 go-zero
微服务实例,整个系列分十三篇文章,目录结构如下:
- go-zero 实战 - 服务划分与项目创建
- go-zero 实战 - User API Gateway
- go-zero 实战 - User Login
- go-zero 实战 - User Register
- go-zero 实战 - User Userinfo
- go-zero 实战 - Food API Gateway
- go-zero 实战 - Food Search
- go-zero 实战 - Food AddFood
- go-zero 实战 - Food DeleteFood
- go-zero 实战 - Food Foodlist
- go-zero 实战进阶 - rpc 服务
- go-zero 实战进阶 - 用户管理 rpc 服务
- go-zero 实战进阶 - 食材管理 rpc 服务
期望通过本系列文章带你在本地利用 go-zero
快速开发一个《食谱指南》系统,让你快速上手微服务。
在 上一篇 文章中,我们介绍了如何搭建删除用户指定食材的接口。本篇我们将介绍如何搭建获取指定用户食谱列表的接口。
因为 /food/foodlist
接口需要返回指定用户下的所有食物信息,因此需要先根据 userId
从 user_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.go
和 foodmodel.go
这两个文件中添加自定义代码:
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),
}
}
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
接口:
- 在
Postman
的Authorization
选项中选择Bearer Token
,填写登录成功后Api
返回的accessToken
字段值。 - 点击发送请求按钮,有如下截图的响应说明接口运行正常。
这样 Food - Foodlist
接口就开发完成了。至此整个 go-zero
实战教程就结束了。如果你还想了解 rpc
服务相关的知识,请移步至下一篇 《go-zero 实战进阶 - rpc 服务》 的进阶教程。