【Go】十七、grpc 服务的具体功能编写

服务的具体编写

获取品牌信息的基础逻辑

我们为了便于测试,可以先把方法写成下面这样:

go 复制代码
type GoodsServer struct {
	proto.UnimplementedGoodsServer
}

之后再 test/brands.go 中进行编写测试代码:

go 复制代码
// 创建客户端
var brandClient proto.GoodsClient
var conn *grpc.ClientConn

func Init() {
	var err error
	conn, err = grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}

	brandClient = proto.NewGoodsClient(conn)
}

func main() {
	Init()
	TestGetBrandList()
	conn.Close()
}

/**
* 测试获取用户信息
 */

func TestGetBrandList() {
	rsp, err := brandClient.BrandList(context.Background(), &proto.BrandFilterRequest{})
	if err != nil {
		panic(err)
	}
	for _, brand := range rsp.Data {
		fmt.Println(brand.Name)
	}
}

这里就会输出一项:

panic: rpc error: code = Unimplemented desc = method BrandList not implemented

便于我们提前测试代码连通性

brands.go 服务的编写:

go 复制代码
// 注意这里,所有的方法都绑定到 goods.go 中的 GoodsServer 类上
// 所有的方法都会需要context 和 需要的参数
// 注意这里是 GRPC 协议,不是 HTTP 协议了
func (s *GoodsServer) BrandList(context context.Context, req *proto.BrandFilterRequest) (*proto.BrandListResponse, error) {
	// 这里一般都是需要定义一个用来返回的变量
	var brandListResponse proto.BrandListResponse

	// 定义一个List用来存储取出来的集合
	var brands []model.Brands
	result := global.DB.Find(&brands)
	fmt.Println(result.RowsAffected)
	return &brandListResponse, nil
}

这是一个很基础的查库的写法

添加返回的信息:

go 复制代码
// 注意这里,所有的方法都绑定到 goods.go 中的 GoodsServer 类上
// 所有的方法都会需要context 和 需要的参数
// 注意这里是 GRPC 协议,不是 HTTP 协议了
func (s *GoodsServer) BrandList(context context.Context, req *proto.BrandFilterRequest) (*proto.BrandListResponse, error) {
	// 这里一般都是需要定义一个用来返回的变量
	var brandListResponse proto.BrandListResponse

	// 定义一个List用来存储取出来的集合
	// 数据库中取出数据
	var brands []model.Brands
	result := global.DB.Find(&brands)
	if result.Error != nil {
		return nil, result.Error
	}
	brandListResponse.Total = int32(result.RowsAffected)

	// 构造返回信息
	var brandInfoResponses []*proto.BrandInfoResponse
	for _, brand := range brands {
		var brandInfoResponse proto.BrandInfoResponse
		brandInfoResponse.Id = brand.ID
		brandInfoResponse.Name = brand.Name
		brandInfoResponse.Logo = brand.Logo
		brandInfoResponses = append(brandInfoResponses, &brandInfoResponse)
	}
	brandListResponse.Data = brandInfoResponses
	return &brandListResponse, nil
}

分页信息的添加

向 handler中添加一个新的文件base.go 中添加分页基础方法:

handler/base.go

go 复制代码
func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
	return func (db *gorm.DB) *gorm.DB{
		if page == 0 {
			page = 1
		}
		
		switch {
		case pageSize > 100:
			pageSize = 100
		case pageSize <= 0:
			pageSize = 10
		}
		
		offset := (page - 1) * pageSize
		return db.Offset(offset).Limit(pageSize)
	}
}

由于添加分页信息后会导致result.RowAffects 取出来的数为当页数,我们还需要 global.DB.Model(&xxx{}).Count(&total) 来对数量进行统计。

全量的代码如下所示:

brands.go

go 复制代码
// 注意这里,所有的方法都绑定到 goods.go 中的 GoodsServer 类上
// 所有的方法都会需要context 和 需要的参数
// 注意这里是 GRPC 协议,不是 HTTP 协议了
func (s *GoodsServer) BrandList(context context.Context, req *proto.BrandFilterRequest) (*proto.BrandListResponse, error) {
	// 这里一般都是需要定义一个用来返回的变量
	var brandListResponse proto.BrandListResponse

	// 定义一个List用来存储取出来的集合
	// 数据库中取出数据
	var brands []model.Brands
	//result := global.DB.Find(&brands)
	// 获取全量数据个数信息
	var total int64
	// DB.Model 是一个非常好用,又非常危险的方法,其可以对数据库进行全量查询、删除、修改,也可以查询数量(常用)
	global.DB.Model(&model.Brands{}).Count(&total)
	// 超绝分页,巨简单
	result := global.DB.Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&brands)
	if result.Error != nil {
		return nil, result.Error
	}
	brandListResponse.Total = int32(total)

	// 构造返回信息
	var brandInfoResponses []*proto.BrandInfoResponse
	for _, brand := range brands {
		var brandInfoResponse proto.BrandInfoResponse
		brandInfoResponse.Id = brand.ID
		brandInfoResponse.Name = brand.Name
		brandInfoResponse.Logo = brand.Logo
		brandInfoResponses = append(brandInfoResponses, &brandInfoResponse)
	}
	brandListResponse.Data = brandInfoResponses
	return &brandListResponse, nil
}

增、删、改 的代码在这里:

go 复制代码
// 新建品牌
func (s *GoodsServer) CreateBrand(ctx context.Context, req *proto.BrandRequest) (*proto.BrandInfoResponse, error) {
	// 此处有一个逻辑,品牌不能同名,先查询是否有同名记录
	if result := global.DB.First(&model.Brands{}); result.RowsAffected >= 1 {
		// 证明有同名,应返回错误
		// 这里的后半部分是 google 包下的内容,其代表错误的信息码
		return nil, status.Errorf(codes.InvalidArgument, "品牌已存在")
	}

	// 若未出现重名问题,则执行插入
	brand := &model.Brands{
		Name: req.Name,
		Logo: req.Logo,
	}
	global.DB.Create(brand)

	//  保存完毕后,更新返回信息
	return &proto.BrandInfoResponse{Id: brand.ID}, nil
}

// 删除品牌
func (s *GoodsServer) DeleteBrand(ctx context.Context, req *proto.BrandRequest) (*emptypb.Empty, error) {
	// 直接开删
	if result := global.DB.Delete(&model.Brands{}, req.Id); result.RowsAffected < 1 {
		return nil, status.Errorf(codes.InvalidArgument, "删除失败,对应品牌不存在")
	}
	return &emptypb.Empty{}, nil
}

// 更新品牌
func (s *GoodsServer) UpdateBrand(ctx context.Context, req *proto.BrandRequest) (*emptypb.Empty, error) {
	brands := model.Brands{}
	if result := global.DB.First(&brands, req.Id); result.RowsAffected < 1 {
		return nil, status.Errorf(codes.InvalidArgument, "您指定更新的品牌不存在")
	}
	if req.Name != "" {
		brands.Name = req.Name
	}
	if req.Logo != "" {
		brands.Logo = req.Logo
	}
	global.DB.Save(&brands)
	return &emptypb.Empty{}, nil
}

轮播图部分过于简单,此处不再重复定义

子分类在 GORM 中的快速处理方式

假设我们要用到子分类的功能需求,例如下面这样:

[
	分类名:电子产品,
	父分类ID:xxx
	子分类:[
		分类名:xxx
		父分类ID:xxx
		子分类:[
			...
		]
	]
]

我们就可以使用GORM中的预加载功能快速处理分类场景:

下面是我们的分类接口的数据结构:

type Category struct {
	BaseModel
	Name             string    `gorm:"type:varchar(20);not null;"` // 分类	名
	ParentCategoryID int32     // 父分类ID
	ParentCategory   *Category // 父分类对象	此处因为是自己指向自己,必须使用指针
	Level            int32     `gorm:"type:int;not null;default:1"` // 分类级别
	IsTab            bool      `gorm:"default:false;not null"`      // 是否显示在 Tab 栏
}

下面的需求是:让我们获取数据时,可以直接获取子数据,就像上面的集合一样

我们这样添加:SubCategory []*Category gorm:"foreignKey:ParentCategoryID;references:ID"

foreignKey 指的是自己的ParentCategoryID应该去关联谁,而references指的是这个 foreignKey 应该去找谁,也就是说,根据自己的ParentCategoryID来推断应该在谁的ID下面

// 商品分类数据对象:一级分类、二级分类...
type Category struct {
	BaseModel
	Name             string      `gorm:"type:varchar(20);not null;"` // 分类	名
	ParentCategoryID int32       // 父分类ID
	ParentCategory   *Category   // 父分类对象	此处因为是自己指向自己,必须使用指针
	SubCategory      []*Category `gorm:"foreignKey:ParentCategoryID;references:ID"`
	Level            int32       `gorm:"type:int;not null;default:1"` // 分类级别
	IsTab            bool        `gorm:"default:false;not null"`      // 是否显示在 Tab 栏
}

之后我们进行DB.Preload 进行预加载:

迭代一:

go 复制代码
// // 获取所有商品分类
// GetAllCategorysList(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CategoryListResponse, error)
func (s *GoodsServer) GetAllCategorysList(ctx context.Context, empty *emptypb.Empty) (*proto.CategoryListResponse, error) {
	var categorys []model.Category
	// 进行预加载,预加载出所需要的数据
	global.DB.Preload("SubCategory").Find(&categorys)
	// 尝试打印
	for _, category := range categorys {
		fmt.Println(category)
	}
	return nil, nil
}

注意,这里还没有结束,我们预加载之后出现了两个问题,第一个是我们的二级类目也被查询出来了,和一级类目放在了一起,第二个问题是我们只会往下查询一条,这里我们的解决方式是:

go 复制代码
// // 获取所有商品分类
// GetAllCategorysList(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CategoryListResponse, error)
func (s *GoodsServer) GetAllCategorysList(ctx context.Context, empty *emptypb.Empty) (*proto.CategoryListResponse, error) {
	var categorys []model.Category
	// 进行预加载,预加载出所需要的数据
	// 由于分级最多到 3 ,所以这里写一个 SubCategory.SubCategory 就可以完全覆盖 3 级了,如果最多到 4 级的话,就必须再写一个 .SubCategory 了
	global.DB.Where(&model.Category{Level: 1}).Preload("SubCategory.SubCategory").Find(&categorys)
	// 利用预留的 JSON 字段进行返回
	b, _ := json.Marshal(&categorys)
	return &proto.CategoryListResponse{JsonData: string(b)}, nil
}

由于我们预留出来了给前端的 JSON 字段,故我们可以将我们取得的数据装填到 JsonData 字段中。

另外:我们可以对我们想要的数据在 model 的位置进行格式化

// 商品分类数据对象:一级分类、二级分类...
type Category struct {
	BaseModel
	Name             string      `gorm:"type:varchar(20);not null;" json:"name"` // 分类	名
	ParentCategoryID int32       `json:"parent"`                                 // 父分类ID
	ParentCategory   *Category   `json:"-"`                                      // 父分类对象	此处因为是自己指向自己,必须使用指针
	SubCategory      []*Category `gorm:"foreignKey:ParentCategoryID;references:ID" json:"sub_category"`
	Level            int32       `gorm:"type:int;not null;default:1" json:"level"` // 分类级别
	IsTab            bool        `gorm:"default:false;not null" json:"is_tab"`     // 是否显示在 Tab 栏
}

子分类接口的编写,涉及到多层 preload 的灵活写法:

go 复制代码
// 获取子分类
func (s *GoodsServer) GetSubCategory(ctx context.Context, req *proto.CategoryListRequest) (*proto.SubCategoryListResponse, error) {
	// 构造需要的返回内容
	categoryListResponse := proto.SubCategoryListResponse{}

	// 查找对应的分类是否在数据库中存在
	var category model.Category
	if result := global.DB.First(&category, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品分类不存在")
	}

	// 构造要查找子分类的分类
	categoryListResponse.Info = &proto.CategoryInfoResponse{
		Id:             category.ID,
		Name:           category.Name,
		Level:          category.Level,
		IsTab:          category.IsTab,
		ParentCategory: category.ParentCategoryID,
	}

	// 查找子分类,这里有一个要注意的逻辑:
	// 我们必须选定分类的等级,若分类等级为一的话就意味着他可能有两级子分类,若分类等级为二的话,其就只可能有一级子分类
	var subCategoryList []model.Category
	preloadLevel := "SubCategory"
	if category.Level == 1 {
		preloadLevel = "SubCategory.Subcategory"
	}
	global.DB.Where(&model.Category{ParentCategoryID: req.Id}).Preload(preloadLevel).Find(&subCategoryList)

	var subCategoryInfoResponse []*proto.CategoryInfoResponse
	// 拼接字段:
	for _, subCategory := range subCategoryList {
		subCategoryInfoResponse = append(subCategoryInfoResponse, &proto.CategoryInfoResponse{
			Id:             subCategory.ID,
			Name:           subCategory.Name,
			ParentCategory: subCategory.ParentCategoryID,
			Level:          subCategory.Level,
			IsTab:          subCategory.IsTab,
		})
	}
	categoryListResponse.SubCategorys = subCategoryInfoResponse
	return &categoryListResponse, nil
}

分类的其他接口

此处注意,更新接口必须进行传递判断,因为 proto 具备默认值,若不进行判断,会出现误更新的情况

分类-品牌关联接口

知识点:Preload 在 外键场景下的应用

category_brand.go

go 复制代码
package handler

import (
	"context"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"mxshop_srvs/goods_srv/global"
	"mxshop_srvs/goods_srv/model"
	"mxshop_srvs/goods_srv/proto"
)

// 全量接口:
// 取出所有品牌与分类的相关列表
func (s *GoodsServer) CategoryBrandList(ctx context.Context, req *proto.CategoryBrandFilterRequest) (*proto.CategoryBrandListResponse, error) {
	// 构造基础数据
	var categoryBrands []model.GoodsCategoryBrand
	categoryBrandListResponse := proto.CategoryBrandListResponse{}

	// 取出分页数据的总数
	var total int64
	global.DB.Model(&model.GoodsCategoryBrand{}).Count(&total)
	categoryBrandListResponse.Total = int32(total)

	// 全量
	// 注意这里必须添加 Preload 以便对于外键的相关信息进行存取的操作,因为元模型中有嵌套两层的环节
	global.DB.Preload("Category").Preload("Brands").Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&categoryBrands)

	// 根据要返回的信息构造需要返回的数据
	// 对于所有需要返回的数据来讲,我们需要遍历我们从数据库中取出的信息,来对返回进行拼装
	var categoryBrandResponses []*proto.CategoryBrandResponse
	for _, categoryBrandItem := range categoryBrands {
		categoryBrandResponses = append(categoryBrandResponses, &proto.CategoryBrandResponse{
			Category: &proto.CategoryInfoResponse{
				Id:             categoryBrandItem.CategoryID,
				Name:           categoryBrandItem.Category.Name,
				ParentCategory: categoryBrandItem.Category.ParentCategoryID,
				Level:          categoryBrandItem.Category.Level,
				IsTab:          categoryBrandItem.Category.IsTab,
			},
			Brand: &proto.BrandInfoResponse{
				Id:   categoryBrandItem.Brands.ID,
				Name: categoryBrandItem.Brands.Name,
				Logo: categoryBrandItem.Brands.Logo,
			},
		})
	}
	// 封装数据
	categoryBrandListResponse.Data = categoryBrandResponses
	return &categoryBrandListResponse, nil
}

// 功能点接口:
// 取出某个分类下的所有品牌
func (s *GoodsServer) GetCategoryBrandList(ctx context.Context, req *proto.CategoryInfoRequest) (*proto.BrandListResponse, error) {
	brandListResponse := proto.BrandListResponse{}
	var category model.GoodsCategoryBrand
	var categoryBrands []model.GoodsCategoryBrand
	var brandInfo []*proto.BrandInfoResponse

	// 先尝试查询分类,看分类是否存在
	if result := global.DB.Find(&category, req); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "所选商品分类不存在")
	}

	// 通过商品品牌关联表查询所有该分类下的品牌
	//
	if result := global.DB.Preload("Brands").Where(&model.GoodsCategoryBrand{CategoryID: category.ID}).Find(&categoryBrands); result.RowsAffected > 0 {
		brandListResponse.Total = int32(result.RowsAffected)
	}

	// 拼接下一段返回
	for _, brandItem := range categoryBrands {
		brandInfo = append(brandInfo, &proto.BrandInfoResponse{
			Id:   brandItem.Brands.ID,
			Name: brandItem.Brands.Name,
			Logo: brandItem.Brands.Logo,
		})
	}

	// 构造返回内容
	brandListResponse.Data = brandInfo
	return &brandListResponse, nil
}

// 新建品牌分类
// CreateCategoryBrand(ctx context.Context, in *CategoryBrandRequest, opts ...grpc.CallOption) (*CategoryBrandResponse, error)
func (s *GoodsServer) CreateCategoryBrand(ctx context.Context, req *proto.CategoryBrandRequest) (*proto.CategoryBrandResponse, error) {
	// 确认分类和品牌存在才能继续操作
	var category model.Category
	if result := global.DB.Find(&category, req.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "未找到对应的分类信息")
	}

	var brand model.Brands
	if result := global.DB.Find(&brand, req.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "未找到对应的品牌信息")
	}

	// 一切准备就绪,执行插入操作
	categoryBrand := model.GoodsCategoryBrand{
		CategoryID: req.CategoryId,
		BrandsID:   req.BrandId,
	}

	global.DB.Save(&categoryBrand)
	return &proto.CategoryBrandResponse{
		Id: categoryBrand.ID,
	}, nil
}

// 删除品牌分类的关联关系
// DeleteCategoryBrand(ctx context.Context, in *CategoryBrandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
func (s *GoodsServer) DeleteCategoryBrand(ctx context.Context, req *proto.CategoryBrandRequest) (*emptypb.Empty, error) {
	if result := global.DB.Delete(&model.GoodsCategoryBrand{}, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "所指定的品牌分类不存在")
	}
	return &emptypb.Empty{}, nil
}

// 更新品牌分类信息
// UpdateCategoryBrand(ctx context.Context, in *CategoryBrandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
func (s *GoodsServer) UpdateCategoryBrand(ctx *context.Context, req *proto.CategoryBrandRequest) (*emptypb.Empty, error) {

	var categoryBrand model.GoodsCategoryBrand

	if result := global.DB.First(categoryBrand, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "为找到要更新的商品分类")
	}

	var brand model.Brands
	if result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "未找到匹配的相关品牌信息")
	}

	var category model.Category
	if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "未找到匹配的分类相关信息")
	}

	categoryBrand.BrandsID = req.BrandId
	categoryBrand.CategoryID = req.CategoryId

	global.DB.Save(categoryBrand)

	return &emptypb.Empty{}, nil
}

商品接口

知识点:多条件拼接查询,条件动态变化时请况的处理方式

SQL语句、子查询的拼接

go 复制代码
package handler

import (
	"context"
	"fmt"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"mxshop_srvs/goods_srv/global"
	"mxshop_srvs/goods_srv/model"
	"mxshop_srvs/goods_srv/proto"
)

type GoodsServer struct {
	proto.UnimplementedGoodsServer
}

// 数据库对象转返回对象
func ModelToResponse(goods model.Goods) proto.GoodsInfoResponse {
	return proto.GoodsInfoResponse{
		Id:              goods.ID,
		CategoryId:      goods.CategoryID,
		Name:            goods.Name,
		GoodsSn:         goods.GoodsSn,
		ClickNum:        goods.ClickNum,
		SoldNum:         goods.SoldNum,
		FavNum:          goods.FavNum,
		MarketPrice:     goods.MarketPrice,
		ShopPrice:       goods.ShopPrice,
		GoodsBrief:      goods.GoodsBrief,
		ShipFree:        goods.ShipFree,
		Images:          goods.Images,
		DescImages:      goods.DescImages,
		GoodsFrontImage: goods.GoodsFrontImage,
		IsNew:           goods.IsNew,
		IsHot:           goods.IsHot,
		OnSale:          goods.OnSale,
		Category: &proto.CategoryBriefInfoResponse{
			Id:   goods.Category.ID,
			Name: goods.Category.Name,
		},
		Brand: &proto.BrandInfoResponse{
			Id:   goods.Brands.ID,
			Name: goods.Brands.Name,
			Logo: goods.Brands.Logo,
		},
	}

}

// // 商品部分
// // 获取商品的接口,包括条件获取
// func (s *GoodsServer) GoodsList(context.Context, *proto.GoodsFilterRequest) (*proto.GoodsListResponse, error) {}
// 其难点在于条件的拼接
func (s *GoodsServer) GoodsList(ctx context.Context, req *proto.GoodsFilterRequest) (*proto.GoodsListResponse, error) {
	goodsListResponse := &proto.GoodsListResponse{}

	var goods []model.Goods
	// 添加条件判断的拼接对象 queryMap 方式
	 但要注意的问题是: queryMap 仅仅适合于简单的相等条件的条件拼接,对于复杂条件其无能为力
	//queryMap := map[string]interface{}{}
	//
	//if req.KeyWords != "" {
	//	queryMap["name"] = "%" + req.KeyWords + "%"
	//}
	//if req.IsHot == true {
	//	queryMap["is_hot"] = true
	//}

	// 我们利用 global.DB 来进行条件的拼接
	// 这里要先试用 Model 指定表名,因为我们下面的条件拼凑是需要先完全拼凑完再进行查询的
	localDB := global.DB.Model(&model.Goods{})
	// 一旦有条件成立时,我们就进行拼接
	if req.KeyWords != "" {
		localDB = localDB.Where("name LIKE ?", ""+req.KeyWords+"")
	}
	if req.IsHot {
		localDB = localDB.Where("is_hot = true")
	}
	if req.IsNew {
		localDB = localDB.Where(model.Goods{IsNew: true})
	}
	if req.PriceMin > 0 {
		localDB = localDB.Where("shop_price > ?", req.PriceMin)
	}
	if req.PriceMax > 0 {
		localDB = localDB.Where("shop_price < ?", req.PriceMax)
	}
	// SQL语句、子查询的拼接
	var subQuery string
	if req.TopCategory > 0 {
		var category model.Category
		if result := global.DB.First(category, req.TopCategory); result.RowsAffected == 0 {
			return nil, status.Errorf(codes.NotFound, "未找到指定分类")
		}

		if category.Level == 1 {
			subQuery = fmt.Sprintf("SELECT id FROM category WHERE parent_category_id in (SELECT id FROM category WHERE parent_category_id = %d", category.ID)
		} else if category.Level == 2 {
			subQuery = fmt.Sprintf("SELECT id FROM category WHERE parent_category_id in (%d)", category.ID)
		} else if category.Level == 3 {
			subQuery = fmt.Sprintf("SELECT id FROM category WHERE id in (%d)", category.ID)
		}
		localDB.Where(fmt.Sprintf("category_id in (%s)"), subQuery)
	}

	// 计数 利用 Count 方法,注意这里必须在之前进行计数
	var count int64
	localDB.Count(&count)
	goodsListResponse.Total = int32(count)

	// 分页查询具体数据
	// 由于 goods 中涉及到外键,需要使用预加载
	result := localDB.Preload("Category").Preload("Brands").Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&goods)
	if result.Error != nil {
		return nil, result.Error
	}

	for _, goodItem := range goods {
		goodsResp := ModelToResponse(goodItem)
		goodsListResponse.Data = append(goodsListResponse.Data, &goodsResp)
	}

	return goodsListResponse, nil
}

// // 批量查询商品信息的接口,避免查商品时发生一个一个调用服务、一条一条查的低效情况
// BatchGetGoods(context.Context, *BatchGoodsIdInfo) (*GoodsListResponse, error)
func (s *GoodsServer) BatchGetGoods(ctx context.Context, req *proto.BatchGoodsIdInfo) (*proto.GoodsListResponse, error) {
	goodsListResponse := &proto.GoodsListResponse{}
	var goods []model.Goods

	// 取出所有的信息
	result := global.DB.Where(req.Id).Find(&goods)
	for _, goodItem := range goods {
		goodResp := ModelToResponse(goodItem)
		goodsListResponse.Data = append(goodsListResponse.Data, &goodResp)
	}
	goodsListResponse.Total = int32(result.RowsAffected)
	return goodsListResponse, nil
}

// // 获取商品信息(单独获取) 获取详情
// GetGoodsDetail(context.Context, *GoodInfoRequest) (*GoodsInfoResponse, error)
func (s *GoodsServer) GetGoodsDetail(ctx context.Context, req *proto.GoodInfoRequest) (*proto.GoodsInfoResponse, error) {
	var goods model.Goods

	if result := global.DB.First(&goods, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "未找到对应商品信息")
	}
	goodsInfoResponse := ModelToResponse(goods)
	return &goodsInfoResponse, nil
}

// // 添加商品
// CreateGoods(context.Context, *CreateGoodsInfo) (*GoodsInfoResponse, error)
func (s *GoodsServer) CreateGoods(ctx context.Context, req *proto.CreateGoodsInfo) (*proto.GoodsInfoResponse, error) {
	// 需要先行判断 分类、品牌信息是否存在
	var category model.Category
	if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "未找到应插入的分类信息")
	}

	var brands model.Brands
	if result := global.DB.First(&brands, req.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "未找到相关品牌信息")
	}

	goods := model.Goods{
		CategoryID:      category.ID,
		Category:        category,
		BrandsID:        brands.ID,
		Brands:          brands,
		ShipFree:        req.ShipFree,
		IsNew:           req.IsNew,
		IsHot:           req.IsHot,
		OnSale:          req.OnSale,
		Name:            req.Name,
		GoodsSn:         req.GoodsSn,
		MarketPrice:     req.MarketPrice,
		ShopPrice:       req.ShopPrice,
		GoodsBrief:      req.GoodsBrief,
		Images:          req.Images, // 注意此处照片的上传是使用第三方技术进行上传的,此处仅为照片存储url
		DescImages:      req.DescImages,
		GoodsFrontImage: req.GoodsFrontImage,
	}
	global.DB.Save(&goods)
	return &proto.GoodsInfoResponse{
		Id: goods.ID,
	}, nil
}

// // 删除商品,没有明确需要返回的信息,返回一个占位符
// DeleteGoods(context.Context, *DeleteGoodsInfo) (*emptypb.Empty, error)
func (s *GoodsServer) DeleteGoods(ctx context.Context, req *proto.DeleteGoodsInfo) (*emptypb.Empty, error) {
	if result := global.DB.Delete(&model.Goods{}, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "未找到应删除的商品")
	}
	return &emptypb.Empty{}, nil
}

// // 更新商品信息
// UpdateGoods(context.Context, *CreateGoodsInfo) (*emptypb.Empty, error)
func (s *GoodsServer) UpdateGoods(ctx context.Context, req *proto.CreateGoodsInfo) (*emptypb.Empty, error) {
	// 找到要更新的商品、分类、品牌信息,并判断他们是否存在
	var goods model.Goods
	if result := global.DB.First(&goods, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "未找到对应商品")
	}

	var category model.Category
	if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "未找到对应分类")
	}

	var brands model.Brands
	if result := global.DB.First(&brands, req.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "未找到对应品牌")
	}

	goods.Brands = brands
	goods.BrandsID = brands.ID
	goods.Category = category
	goods.CategoryID = category.ID
	goods.Name = req.Name
	goods.GoodsSn = req.GoodsSn
	goods.MarketPrice = req.MarketPrice
	goods.ShopPrice = req.ShopPrice
	goods.GoodsBrief = req.GoodsBrief
	goods.Images = req.Images
	goods.DescImages = req.DescImages
	goods.GoodsFrontImage = req.GoodsFrontImage
	goods.IsNew = req.IsNew
	goods.IsHot = req.IsHot
	goods.OnSale = req.OnSale

	global.DB.Save(&goods)
	return &emptypb.Empty{}, nil
}
相关推荐
不懂906 分钟前
Spring Boot集成Jetty、Tomcat或Undertow及支持HTTP/2协议
spring boot·后端·http·https
.猫的树10 分钟前
Java集合List快速实现重复判断的10种方法深度解析
java·开发语言·list·集合
刀客12315 分钟前
C++ STL(三)list
开发语言·c++
朔北之忘 Clancy1 小时前
2022 年 12 月青少年软编等考 C 语言五级真题解析
c语言·开发语言·c++·学习·算法·青少年编程·题解
折枝寄北1 小时前
(21)从strerror到strtok:解码C语言字符函数的“生存指南2”
c语言·开发语言
m0_748236581 小时前
PHP进阶-在Ubuntu上搭建LAMP环境教程
开发语言·ubuntu·php
CoderCodingNo1 小时前
【GESP】C++二级模拟 luogu-b3995, [GESP 二级模拟] 小洛的田字矩阵
开发语言·c++·矩阵
pianmian11 小时前
python绘图之swarmplot分布散点图
开发语言·python
Jelena157795857922 小时前
爬虫获取微店商品快递费 item_feeAPI 接口的完整指南
开发语言·前端·爬虫
总是学不会.2 小时前
从“记住我”到 Web 认证:Cookie、JWT 和 Session 的故事
java·前端·后端·开发