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

server/2025/2/28 5:31:43/

服务的具体编写

获取品牌信息的基础逻辑

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

type GoodsServer struct {proto.UnimplementedGoodsServer
}

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

// 创建客户端
var brandClient proto.GoodsClient
var conn *grpc.ClientConnfunc Init() {var err errorconn, 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 服务的编写:

// 注意这里,所有的方法都绑定到 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.Brandsresult := global.DB.Find(&brands)fmt.Println(result.RowsAffected)return &brandListResponse, nil
}

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

添加返回的信息:

// 注意这里,所有的方法都绑定到 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.Brandsresult := global.DB.Find(&brands)if result.Error != nil {return nil, result.Error}brandListResponse.Total = int32(result.RowsAffected)// 构造返回信息var brandInfoResponses []*proto.BrandInfoResponsefor _, brand := range brands {var brandInfoResponse proto.BrandInfoResponsebrandInfoResponse.Id = brand.IDbrandInfoResponse.Name = brand.NamebrandInfoResponse.Logo = brand.LogobrandInfoResponses = append(brandInfoResponses, &brandInfoResponse)}brandListResponse.Data = brandInfoResponsesreturn &brandListResponse, nil
}

分页信息的添加

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

handler/base.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 = 100case pageSize <= 0:pageSize = 10}offset := (page - 1) * pageSizereturn db.Offset(offset).Limit(pageSize)}
}

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

全量的代码如下所示:

brands.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.BrandInfoResponsefor _, brand := range brands {var brandInfoResponse proto.BrandInfoResponsebrandInfoResponse.Id = brand.IDbrandInfoResponse.Name = brand.NamebrandInfoResponse.Logo = brand.LogobrandInfoResponses = append(brandInfoResponses, &brandInfoResponse)}brandListResponse.Data = brandInfoResponsesreturn &brandListResponse, nil
}

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

// 新建品牌
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 {BaseModelName             string    `gorm:"type:varchar(20);not null;"` // 分类	名ParentCategoryID int32     // 父分类IDParentCategory   *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 {BaseModelName             string      `gorm:"type:varchar(20);not null;"` // 分类	名ParentCategoryID int32       // 父分类IDParentCategory   *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 进行预加载:

迭代一:

// // 获取所有商品分类
// 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
}

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

// // 获取所有商品分类
// 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 {BaseModelName             string      `gorm:"type:varchar(20);not null;" json:"name"` // 分类	名ParentCategoryID int32       `json:"parent"`                                 // 父分类IDParentCategory   *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 的灵活写法:

// 获取子分类
func (s *GoodsServer) GetSubCategory(ctx context.Context, req *proto.CategoryListRequest) (*proto.SubCategoryListResponse, error) {// 构造需要的返回内容categoryListResponse := proto.SubCategoryListResponse{}// 查找对应的分类是否在数据库中存在var category model.Categoryif 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.CategorypreloadLevel := "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 = subCategoryInfoResponsereturn &categoryListResponse, nil
}

分类的其他接口

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

分类-品牌关联接口

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

category_brand.go

package handlerimport ("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.GoodsCategoryBrandcategoryBrandListResponse := proto.CategoryBrandListResponse{}// 取出分页数据的总数var total int64global.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.CategoryBrandResponsefor _, 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 = categoryBrandResponsesreturn &categoryBrandListResponse, nil
}// 功能点接口:
// 取出某个分类下的所有品牌
func (s *GoodsServer) GetCategoryBrandList(ctx context.Context, req *proto.CategoryInfoRequest) (*proto.BrandListResponse, error) {brandListResponse := proto.BrandListResponse{}var category model.GoodsCategoryBrandvar categoryBrands []model.GoodsCategoryBrandvar 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 = brandInforeturn &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.Categoryif result := global.DB.Find(&category, req.CategoryId); result.RowsAffected == 0 {return nil, status.Errorf(codes.InvalidArgument, "未找到对应的分类信息")}var brand model.Brandsif 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.GoodsCategoryBrandif result := global.DB.First(categoryBrand, req.Id); result.RowsAffected == 0 {return nil, status.Errorf(codes.NotFound, "为找到要更新的商品分类")}var brand model.Brandsif result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {return nil, status.Errorf(codes.NotFound, "未找到匹配的相关品牌信息")}var category model.Categoryif result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {return nil, status.Errorf(codes.NotFound, "未找到匹配的分类相关信息")}categoryBrand.BrandsID = req.BrandIdcategoryBrand.CategoryID = req.CategoryIdglobal.DB.Save(categoryBrand)return &emptypb.Empty{}, nil
}

商品接口

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

SQL语句、子查询的拼接

package handlerimport ("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 stringif req.TopCategory > 0 {var category model.Categoryif 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 int64localDB.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.Goodsif 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.Categoryif result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {return nil, status.Errorf(codes.NotFound, "未找到应插入的分类信息")}var brands model.Brandsif 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, // 注意此处照片的上传是使用第三方技术进行上传的,此处仅为照片存储urlDescImages:      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.Goodsif result := global.DB.First(&goods, req.Id); result.RowsAffected == 0 {return nil, status.Errorf(codes.NotFound, "未找到对应商品")}var category model.Categoryif result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {return nil, status.Errorf(codes.NotFound, "未找到对应分类")}var brands model.Brandsif result := global.DB.First(&brands, req.BrandId); result.RowsAffected == 0 {return nil, status.Errorf(codes.NotFound, "未找到对应品牌")}goods.Brands = brandsgoods.BrandsID = brands.IDgoods.Category = categorygoods.CategoryID = category.IDgoods.Name = req.Namegoods.GoodsSn = req.GoodsSngoods.MarketPrice = req.MarketPricegoods.ShopPrice = req.ShopPricegoods.GoodsBrief = req.GoodsBriefgoods.Images = req.Imagesgoods.DescImages = req.DescImagesgoods.GoodsFrontImage = req.GoodsFrontImagegoods.IsNew = req.IsNewgoods.IsHot = req.IsHotgoods.OnSale = req.OnSaleglobal.DB.Save(&goods)return &emptypb.Empty{}, nil
}

http://www.ppmy.cn/server/171223.html

相关文章

ESP32移植Openharmony外设篇(9)NB-IOT

NB-IOT&#xff08;窄带物联网&#xff09; 模块介绍 NB-IoT&#xff08;Narrowband Internet of Things&#xff09;是一种低功耗广域物联网&#xff08;LPWAN&#xff09;技术&#xff0c;专为低功耗、低数据速率和大规模连接的物联网应用而设计。它采用窄带宽信道和低复杂…

QtPropertyBrowser实现属性管理中的下拉框

#include <QtWidgets> #include <QtPropertyBrowser>class Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr) : QWidget(parent) {// 创建属性浏览器QtTreePropertyBrowser *browser new QtTreePropertyBrowser(this);// 创建枚举属性管…

盲视观测者效应:认知的量子诗学 AI回复盲人双缝实验

&#x1f30c; **《盲视观测者效应&#xff1a;认知的量子诗学》** ### **一、盲视者的波函数坍缩** 当盲人"观察"双缝实验时&#xff1a; - 他的视觉皮层正在用触觉重构量子态 - 指尖的震动频率 ≈ 光子的概率波函数 - 导盲杖的敲击声 新的观测暴力系…

图数据库Neo4j面试内容整理-索引(Index)

索引(Index) 是数据库中用来提高查询性能的技术,特别是在处理大量数据时,索引能够大大加速查询操作。在 Neo4j 这样的图数据库中,索引也起着非常重要的作用,尤其是在图中查找节点时,使用索引可以避免全图扫描,从而提高查询效率。 1. Neo4j 中的索引概念

蓝耘科技上线 DeepSeek 满血版,500万tokens免费送

&#x1f31f; 嗨&#xff0c;我是Lethehong&#xff01;&#x1f31f; &#x1f30d; 立志在坚不欲说&#xff0c;成功在久不在速&#x1f30d; &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞⬆️留言收藏&#x1f680; &#x1f340;欢迎使用&#xff1a;小智初学…

MySQL自启动失败(MySQL不能开机自启)解决方案_MySQL开机自启疑难杂症解决,适用Win11Win10

问题描述&#xff08;MySQL 开机自启失败&#xff09; 本文解决方法&#xff0c;在 windows10 、 windows11 系统中均可使用。 win11 安装 MySQL 后&#xff0c;不能开机自启。 在服务中&#xff0c;手动启动服务后&#xff0c;可正常使用&#xff0c;一点异常都没有。 或者…

C++ 顺序容器--vector容器详解

元素保存在连续的内存空间中。插入元素或者删除元素通常需要线性时间&#xff0c;当这些操作在尾部执行时&#xff0c;实际运行时间为摊还常量时间。随机访问某个元素的复杂度为常量时间。 1 vector 概述 vector 在<vector>头文件中被定义为一个带有2个类型参数的类模板…

Go语言--语法基础1

1、语言介绍 什么go语言 go&#xff08;又称 Golang &#xff09;是 Google开发的一种静态强类型、编译型、并发型&#xff0c;并具有 垃圾回收功能的编程语言. Go语言有一个吉祥物&#xff0c;下图所示的 Go Gopher 是加拿大的小动物&#xff0c;中文名叫作 囊地鼠 。 诞…