Go-kratos 框架商城微服务实战之商品服务 (十) 商品创建
kratos 框架商城微服务实战之商品服务 (十)
大家好,今天咱们终于可以写商品服务中的商品模块了,前面花了 4 篇文章,都是为了创建一个完整的商品所需的数据,就这对比一些电商平台,像商品的售后信息、运费模版、商品促销活动信息等,还有很多需要补的,不过那些都不算有太大影响。咱们还是主要先把整个项目跑起来。
众所周知,一个电商的商品设计是比较复杂的,咱们这里不过多的深究商品设计的每个表是否合理,是否漏写之类的问题,主要是为了搞明白 kratos 的使用和微服务相关的调用关系。当然我真正的编写时也会尽可能的让此项目的商品设计合理一些。但大量的表设计呀,重复性的 curd 就不会在文章中体现了,具体的代码参看 GitHub 上的源码。当然你觉得不合理的地方,欢迎给项目提 PR。
注:竖排 … 代码省略,为了保持文章的篇幅简洁,我会将一些不必要的代码使用竖排的 . 来代替,你在复制本文代码块的时候,切记不要将 . 也一同复制进去。
⚠️ ⚠️ ⚠️ 接下来新增或修改的代码, wire 注入的文件中需要修改的代码,都不会再本文中提及了。例如 biz、service 层的修改,自己编写的过程中,千万不要忘记 wire 注入,更不要忘记,执行 make wire 命令,重新生成项目的 wire 文件。具体使用方法可参考 kratos 官方文档 ⚠️ ⚠️ ⚠️
商品信息
设计商品表
data目录下新建数据表相关的文件
base.go 文件内容:
package data import ( "database/sql/driver" "encoding/json" "gorm.io/gorm" "time" ) type GormList []string func (g GormList) Value() (driver.Value, error) { return json.Marshal(g) } func (g *GormList) Scan(value interface{}) error { return json.Unmarshal(value.([]byte), &g) } type BaseFields struct { ID int64 `gorm:"primarykey;type:int" json:"id"` // bigint CreatedAt time.Time `gorm:"column:add_time" json:"created_at"` UpdatedAt time.Time `gorm:"column:update_time" json:"updated_at"` DeletedAt gorm.DeletedAt `json:"deleted_at"` } goods.go文件内容
package data import ( "github.com/go-kratos/kratos/v2/log" "golang.org/x/net/context" "goods/internal/biz" "goods/internal/domain" ) // Goods 商品表 type Goods struct { BaseFields CategoryID int32 `gorm:"index:category_id;type:int;comment:分类ID;not null"` BrandsID int32 `gorm:"index:brand_id;type:int;comment:品牌ID ;not null"` TypeID int64 `gorm:"index:type_id;type:int;comment:商品类型ID ;not null"` Name string `gorm:"type:varchar(100);not null;comment:商品名称"` NameAlias string `gorm:"type:varchar(100);not null;comment:商品别名"` GoodsSn string `gorm:"type:varchar(100);not null;comment:商品编号"` GoodsTags string `gorm:"type:varchar(100);not null;comment:商品标签"` MarketPrice int64 `gorm:"type:int;default:0;not null;comment:商品展示价格"` GoodsBrief string `gorm:"type:varchar(100);not null;comment:商品简介"` GoodsFrontImage string `gorm:"type:varchar(200);not null;comment:商品封面图"` GoodsImages GormList `gorm:"type:varchar(1000);not null;comment:商品的介绍图"` // 切片类型转为 json 到数据库,取出来是切片类型 OnSale bool `gorm:"default:false;comment:是否上架;not null "` ShipFree bool `gorm:"default:false;comment:是否免运费; not null"` ShipID int32 `gorm:"type:int;comment:运费模版ID;not null"` IsNew bool `gorm:"default:false;comment:是否新品;not null"` IsHot bool `gorm:"comment:是否热卖商品;default:false;not null"` ClickNum int64 `gorm:"default:0;type:int; comment 商品详情点击数"` SoldNum int64 `gorm:"default:0;type:int; comment 商品销售数"` FavNum int64 `gorm:"default:0;type:int; comment 商品收藏数"` // 售前服务信息、售后服务信息、商品促销活动信息 } type goodsRepo struct { data *Data log *log.Helper } // NewGoodsRepo . func NewGoodsRepo(data *Data, logger log.Logger) biz.GoodsRepo { return &goodsRepo{ data: data, log: log.NewHelper(logger), } } func (p *Goods) ToDomain() *domain.Goods { return &domain.Goods{ ID: p.ID, CategoryID: p.CategoryID, BrandsID: p.BrandsID, TypeID: p.TypeID, Name: p.Name, NameAlias: p.NameAlias, GoodsSn: p.GoodsSn, GoodsTags: p.GoodsTags, MarketPrice: p.MarketPrice, GoodsBrief: p.GoodsBrief, GoodsFrontImage: p.GoodsFrontImage, GoodsImages: p.GoodsImages, OnSale: p.OnSale, ShipFree: p.ShipFree, ShipID: p.ShipID, IsNew: p.IsNew, IsHot: p.IsHot, ClickNum: p.ClickNum, SoldNum: p.SoldNum, FavNum: p.FavNum, } } - 新建
goods_sku.go文件
package data import ( "github.com/go-kratos/kratos/v2/log" "golang.org/x/net/context" "goods/internal/biz" "goods/internal/domain" ) // GoodsSku 商品SKU 表 type GoodsSku struct { BaseFields GoodsID int64 `gorm:"index:goods_id;type:int;comment:商品ID;not null"` GoodsSn string `gorm:"type:varchar(100);not null;comment:商品编号"` GoodsName string `gorm:"type:varchar(100);not null;comment:商品名称"` SkuName string `gorm:"type:varchar(100);comment:SKU名称;not null"` SkuCode string `gorm:"type:varchar(100);comment:SKUCode;not null"` BarCode string `gorm:"type:varchar(100);comment:条码;not null"` Price int64 `gorm:"type:int;comment:商品售价;not null"` PromotionPrice int64 `gorm:"type:int;comment:商品促销售价;not null"` Points int64 `gorm:"type:int;comment:赠送积分;not null"` RemarksInfo string `gorm:"type:varchar(100);comment:备注信息;not null"` Pic string `gorm:"type:varchar(500);not null;comment:规格参数对应的图片" json:"pic"` OnSale bool `gorm:"comment:是否上架;default:false;not null"` AttrInfo string `gorm:"type:varchar(2000);comment:商品属性信息JSON;not null"` Inventory int64 `gorm:"type:int;comment:商品SKU库存冗余字段;not null"` } // GoodsSpecificationSku 商品规格和商品Sku关联表 type GoodsSpecificationSku struct { BaseFields SkuID int64 `gorm:"index:sku_id;type:int;comment:商品SKU_ID;not null"` SkuCode string `gorm:"type:varchar(100);comment:商品SKU_Code;not null"` SpecificationId int64 `gorm:"index:specification_id;type:int;comment:商品规格ID;not null"` ValueId int64 `gorm:"index:value_id;type:int;comment:商品规格值表ID;not null"` } type goodsSkuRepo struct { data *Data log *log.Helper } // NewGoodsSkuRepoRepo . func NewGoodsSkuRepoRepo(data *Data, logger log.Logger) biz.GoodsSkuRepo { return &goodsSkuRepo{ data: data, log: log.NewHelper(logger), } } func (p *GoodsSku) ToDomain() *domain.GoodsSku { return &domain.GoodsSku{ ID: p.ID, GoodsID: p.GoodsID, GoodsSn: p.GoodsSn, GoodsName: p.GoodsName, SkuName: p.SkuName, SkuCode: p.SkuCode, BarCode: p.BarCode, Price: p.Price, PromotionPrice: p.PromotionPrice, Points: p.Points, RemarksInfo: p.RemarksInfo, Pic: p.Pic, Inventory: p.Inventory, OnSale: p.OnSale, AttrInfo: p.AttrInfo, } } - 新建
inventory.go文件
添加商品 sku 库存的方法比较简单这些先写了。
package data import ( "context" "github.com/go-kratos/kratos/v2/log" "goods/internal/biz" "goods/internal/domain" ) type GoodsInventory struct { BaseFields SkuID int64 `gorm:"index:sku_id;type:int;comment:商品SKU_ID;not null"` Inventory int64 `gorm:"type:int;comment:商品库存;not null"` } type inventoryRepo struct { data *Data log *log.Helper } // NewInventoryRepo . func NewInventoryRepo(data *Data, logger log.Logger) biz.InventoryRepo { return &inventoryRepo{ data: data, log: log.NewHelper(logger), } } func (p *GoodsInventory) ToDomain() *domain.Inventory { return &domain.Inventory{ ID: p.ID, SkuID: p.SkuID, Inventory: p.Inventory, } } func (i inventoryRepo) Create(ctx context.Context, inventory *domain.Inventory) (*domain.Inventory, error) { info := GoodsInventory{ SkuID: inventory.SkuID, Inventory: inventory.Inventory, } if err := i.data.DB(ctx).Save(&info).Error; err != nil { return nil, err } return info.ToDomain(), nil } 新建domain 层下的文件
goods.go
package domain type Goods struct { ID int64 CategoryID int32 BrandsID int32 TypeID int64 Name string NameAlias string GoodsSn string GoodsTags string MarketPrice int64 GoodsBrief string GoodsFrontImage string GoodsImages []string OnSale bool ShipFree bool ShipID int32 IsNew bool IsHot bool ClickNum int64 SoldNum int64 FavNum int64 Sku []*GoodsSku } type GoodsInfoResponse struct { GoodsID int64 } goods_sku.go
package domain type GoodsSku struct { ID int64 GoodsID int64 GoodsSn string GoodsName string SkuName string SkuCode string BarCode string Price int64 PromotionPrice int64 Points int64 RemarksInfo string Pic string Inventory int64 OnSale bool AttrInfo string Specification []*SpecificationInfo GroupAttr []*GroupAttr } type SpecificationInfo struct { SpecificationID int64 SpecificationValueID int64 } type GroupAttr struct { GroupId int64 `json:"group_id"` GroupName string `json:"group_name"` Attr []*Attr `json:"attr"` } type Attr struct { AttrID int64 `json:"attr_id"` AttrName string `json:"attr_name"` AttrValueID int64 `json:"attr_value_id"` AttrValueName string `json:"attr_value_name"` } type GoodsSpecificationSku struct { ID int64 SkuID int64 SkuCode string SpecificationId int64 ValueId int64 } inventory.go
package domain type Inventory struct { ID int64 SkuID int64 Inventory int64 } 构造商品创建方法
- 修改
goods.proto文件
syntax = "proto3"; ... service Goods { rpc CreateGoods(CreateGoodsRequest) returns (CreateGoodsResponse); } message CreateGoodsRequest { int64 id = 1; int32 categoryId = 2 [(validate.rules).int32.gte = 1]; int32 brandId = 3 [(validate.rules).int32.gte = 1]; int64 typeId = 4 [(validate.rules).int64.gte = 1]; string name = 5 [(validate.rules).string.min_len = 1]; string nameAlias = 6; string goodsTags = 7; string goodsSn = 8 [(validate.rules).string.min_len = 1]; int64 shopPrice = 9; int64 marketPrice = 10; int64 inventory = 11; string goodsBrief = 12; string goodsFrontImage = 13; repeated string goodsImages = 14; bool shipFree = 15; int32 shipId = 16; bool isNew = 17; bool isHot = 18; bool onSale = 19; // 根据商品类型 选择商品规格和商品属性信息 message goodsSku { int64 id = 1; int64 goodsId = 2; string skuName = 3 [(validate.rules).string.min_len = 1]; string code = 4 [(validate.rules).string.min_len = 1]; string barCode = 5 [(validate.rules).string.min_len = 1]; int64 price = 6; int64 promotionPrice = 7; int64 points = 8; string image = 9; int32 sort = 10; int64 inventory = 11; // 商品规格 message specification { int64 sId = 1 [(validate.rules).int64.gte = 1]; int64 vId = 2 [(validate.rules).int64.gte = 1]; } repeated specification specificationInfo = 12; // 商品属性组 message groupAttr { int64 groupId = 1 [(validate.rules).int64.gte = 1]; string groupName = 2 [(validate.rules).string.min_len = 1]; message attr { int64 attrId = 1 [(validate.rules).int64.gte = 1]; string attrName = 2 [(validate.rules).string.min_len = 1]; int64 attrValueId = 3 [(validate.rules).int64.gte = 1]; string attrValueName = 4 [(validate.rules).string.min_len = 1]; } repeated attr attrInfo = 3; } repeated groupAttr groupAttrInfo = 13; } repeated goodsSku sku = 20; } message CreateGoodsResponse { int64 ID = 1; } - 修改
service目录下的goods.go
package service import ( "context" v1 "goods/api/goods/v1" "goods/internal/domain" ) // CreateGoods 创建商品 func (g *GoodsService) CreateGoods(ctx context.Context, r *v1.CreateGoodsRequest) (*v1.CreateGoodsResponse, error) { var goodsSku []*domain.GoodsSku for _, sku := range r.Sku { res := &domain.GoodsSku{ GoodsName: r.Name, GoodsSn: r.GoodsSn, SkuName: sku.SkuName, SkuCode: sku.Code, BarCode: sku.BarCode, Price: sku.Price, PromotionPrice: sku.PromotionPrice, Points: sku.Points, Pic: sku.Image, Inventory: sku.Inventory, OnSale: r.OnSale, } for _, specification := range sku.SpecificationInfo { s := &domain.SpecificationInfo{ SpecificationID: specification.SId, SpecificationValueID: specification.VId, } res.Specification = append(res.Specification, s) } for _, attrGroup := range sku.GroupAttrInfo { group := &domain.GroupAttr{ GroupId: attrGroup.GroupId, GroupName: attrGroup.GroupName, } for _, attr := range attrGroup.AttrInfo { s := &domain.Attr{ AttrID: attr.AttrId, AttrName: attr.AttrName, AttrValueID: attr.AttrValueId, AttrValueName: attr.AttrValueName, } group.Attr = append(group.Attr, s) } res.GroupAttr = append(res.GroupAttr, group) } goodsSku = append(goodsSku, res) } goodsInfo := &domain.Goods{ ID: r.Id, CategoryID: r.CategoryId, BrandsID: r.BrandId, TypeID: r.TypeId, Name: r.Name, NameAlias: r.NameAlias, GoodsSn: r.GoodsSn, GoodsTags: r.GoodsTags, MarketPrice: r.MarketPrice, GoodsBrief: r.GoodsBrief, GoodsFrontImage: r.GoodsFrontImage, GoodsImages: r.GoodsImages, OnSale: r.OnSale, ShipFree: r.ShipFree, ShipID: r.ShipId, IsNew: r.IsNew, IsHot: r.IsHot, Sku: goodsSku, } result, err := g.g.CreateGoods(ctx, goodsInfo) if err != nil { return nil, err } return &v1.CreateGoodsResponse{ID: result.GoodsID}, nil } - 修改
biz目录下的goods.go
package biz import ( "context" "encoding/json" "errors" "github.com/go-kratos/kratos/v2/log" "goods/internal/domain" ) type GoodsRepo interface { CreateGoods(ctx context.Context, goods *domain.Goods) (*domain.Goods, error) } type GoodsUsecase struct { repo GoodsRepo tr Transaction skuRepo GoodsSkuRepo categoryRepo CategoryRepo brandRepo BrandRepo typeRepo GoodsTypeRepo specificationRepo SpecificationRepo goodsAttrRepo GoodsAttrRepo inventoryRepo InventoryRepo log *log.Helper } func NewGoodsUsecase(repo GoodsRepo, skuRepo GoodsSkuRepo, tx Transaction, gRepo GoodsTypeRepo, cRepo CategoryRepo, bRepo BrandRepo, sRepo SpecificationRepo, aRepo GoodsAttrRepo, iRepo InventoryRepo, logger log.Logger) *GoodsUsecase { return &GoodsUsecase{ repo: repo, skuRepo: skuRepo, tr: tx, typeRepo: gRepo, categoryRepo: cRepo, brandRepo: bRepo, specificationRepo: sRepo, goodsAttrRepo: aRepo, inventoryRepo: iRepo, log: log.NewHelper(logger), } } func (g GoodsUsecase) CreateGoods(ctx context.Context, r *domain.Goods) (*domain.GoodsInfoResponse, error) { var ( err error goods *domain.Goods ) // 判断品牌是否存在 _, err = g.brandRepo.IsBrandByID(ctx, r.BrandsID) if err != nil { return nil, errors.New("品牌不存在") } // 判断分类是否存在 _, err = g.categoryRepo.GetCategoryByID(ctx, r.CategoryID) if err != nil { return nil, errors.New("分类不存在") } // 判断商品类型是否存在 _, err = g.typeRepo.IsExistsByID(ctx, r.TypeID) if err != nil { return nil, errors.New("商品类型不存在") } // 判断商品规格和属性是否存在 for _, sku := range r.Sku { var sIDs []*int64 for _, info := range sku.Specification { sIDs = append(sIDs, &info.SpecificationID) } specList, err := g.specificationRepo.ListByIds(ctx, sIDs...) if err != nil { return nil, err } for _, sId := range sIDs { info := specList.FindById(*sId) if info == nil { return nil, errors.New("商品规格不存在") } } var attrIDs []int64 for _, attr := range sku.GroupAttr { for _, id := range attr.Attr { attrIDs = append(attrIDs, id.AttrID) } } attrList, err := g.goodsAttrRepo.ListByIds(ctx, attrIDs...) if err != nil { return nil, err } for _, attr := range sku.GroupAttr { for _, id := range attr.Attr { attrIDs = append(attrIDs, id.AttrID) true := attrList.IsNotExist(attr.GroupId, id.AttrID) if true { return nil, errors.New("商品属性不存在") } } } } err = g.tr.ExecTx(ctx, func(ctx context.Context) error { // 更新商品表 goods, err = g.repo.CreateGoods(ctx, &domain.Goods{ CategoryID: r.CategoryID, BrandsID: r.BrandsID, TypeID: r.TypeID, Name: r.Name, NameAlias: r.NameAlias, GoodsSn: r.GoodsSn, GoodsTags: r.GoodsTags, MarketPrice: r.MarketPrice, GoodsBrief: r.GoodsBrief, GoodsFrontImage: r.GoodsFrontImage, GoodsImages: r.GoodsImages, OnSale: r.OnSale, IsNew: r.IsNew, IsHot: r.IsHot, ShipFree: r.ShipFree, ShipID: r.ShipID, }) if err != nil { return err } // 更新商品 SKU 表 for _, v := range r.Sku { res := &domain.GoodsSku{ GoodsID: goods.ID, GoodsSn: goods.GoodsSn, GoodsName: goods.Name, SkuName: v.SkuName, SkuCode: v.SkuCode, BarCode: v.BarCode, Price: v.Price, PromotionPrice: v.PromotionPrice, Points: v.Points, RemarksInfo: v.RemarksInfo, Pic: v.Pic, Inventory: v.Inventory, OnSale: v.OnSale, } goodsAttr, err := json.Marshal(v.GroupAttr) if err != nil { return err } res.AttrInfo = string(goodsAttr) // 插入 sku 表 skuInfo, err := g.skuRepo.Create(ctx, res) if err != nil { return err } // 插入库存表 _, err = g.inventoryRepo.Create(ctx, &domain.Inventory{ SkuID: skuInfo.ID, Inventory: skuInfo.Inventory, }) if err != nil { return err } // 插入 sku 规格关联关系表 var skuRelation []*domain.GoodsSpecificationSku for _, spec := range v.Specification { skuRelation = append(skuRelation, &domain.GoodsSpecificationSku{ SkuID: skuInfo.ID, SkuCode: skuInfo.SkuCode, SpecificationId: spec.SpecificationID, ValueId: spec.SpecificationValueID, }) } // 插入商品规格关联关系表 err = g.skuRepo.CreateSkuRelation(ctx, skuRelation) if err != nil { return err } } return nil }) if err != nil { return nil, err } return &domain.GoodsInfoResponse{GoodsID: goods.ID}, nil } 实现业务逻辑方法
- 判断品牌
biz 目录下的 brand.go 新增方法
package biz ... type BrandRepo interface { ... IsBrandByID(context.Context, int32) (*domain.Brand, error) } 在data 录下的 brand.go 文件中实现 IsBrandByID 方法
package data ... func (r *BrandRepo) IsBrandByID(ctx context.Context, id int32) (*domain.Brand, error) { var b Brand if err := r.data.db.Table("brands").Where("id = ?", id).First(&b).Error; err != nil { return nil, err } return b.ToDomain(), nil } - 判断分类
biz 目录下的 category.go 新增方法
package biz ... type CategoryRepo interface { ... GetCategoryByID(ctx context.Context, id int32) (*CategoryInfo, error) } 在data 目录下的category.go 文件中实现 GetCategoryByID
package data ... func (r *CategoryRepo) GetCategoryByID(ctx context.Context, id int32) (*biz.CategoryInfo, error) { var categories Category if res := r.data.db.First(&categories, id); res.RowsAffected == 0 { return nil, errors.New("商品分类不存在") } info := &biz.CategoryInfo{ ID: categories.ID, Name: categories.Name, ParentCategory: categories.ParentCategoryID, Level: categories.Level, IsTab: categories.IsTab, Sort: categories.Sort, } return info, nil } - 查询商品类型
biz 目录下的 goods_type.go 新增方法
package biz ... type GoodsTypeRepo interface { ... IsExistsByID(context.Context, int64) (*domain.GoodsType, error) } 在data 目录下的goods_type.go 文件中实现 IsExistsByID
package data ... func (g *goodsTypeRepo) IsExistsByID(ctx context.Context, typeID int64) (*domain.GoodsType, error) { var goodsType GoodsType if res := g.data.db.First(&goodsType, typeID); res.RowsAffected == 0 { return nil, errors.New("商品类型不存在") } return goodsType.ToDomain(), nil } - 判断商品规格和属性
biz 目录下的 specifications.go 新增方法
package biz ... type SpecificationRepo interface { ... ListByIds(ctx context.Context, id ...*int64) (domain.SpecificationList, error) } 在data 目录下的specifications.go 文件中实现 ListByIds
package data ... func (g *specificationRepo) ListByIds(ctx context.Context, id ...*int64) (domain.SpecificationList, error) { var l []*SpecificationsAttr if err := g.data.DB(ctx).Where("id IN (?)", id).Find(&l).Error; err != nil { return nil, err } var res domain.SpecificationList for _, item := range l { res = append(res, item.ToDomain()) } return res, nil } 在 domain 目录下的 specification.go 文件中编写验证方法
package domain ... type SpecificationList []*Specification func (p SpecificationList) FindById(id int64) *Specification { for _, item := range p { if item.ID == id { return item } } return nil } biz 目录下的 goods_attr.go 新增方法
package biz ... type GoodsAttrRepo interface { ... ListByIds(ctx context.Context, id ...int64) (domain.GoodsAttrList, error) } 在data 目录下的goods_attr.go 文件中实现 ListByIds
package data ... func (g *goodsAttrRepo) ListByIds(ctx context.Context, ids ...int64) (domain.GoodsAttrList, error) { var l []*GoodsAttr if err := g.data.DB(ctx).Where("id IN (?)", ids).Find(&l).Error; err != nil { return nil, errors.New("属性不存在") } var res domain.GoodsAttrList for _, item := range l { res = append(res, item.ToDomain()) } return res, nil } 在 domain 目录下的 goods_attr.go 文件中编写验证方法
package domian ... type GoodsAttrList []*GoodsAttr func (p GoodsAttrList) IsNotExist(groupId, attrId int64) bool { for _, item := range p { if item.GroupID != groupId && item.ID != attrId { return true } } return false } 编写入库的业务逻辑
data目录下的goods.go实现CreateGoods方法
package data ... func (g goodsRepo) CreateGoods(c context.Context, goods *domain.Goods) (*domain.Goods, error) { product := &Goods{ CategoryID: goods.CategoryID, BrandsID: goods.BrandsID, TypeID: goods.TypeID, Name: goods.Name, NameAlias: goods.NameAlias, GoodsSn: goods.GoodsSn, GoodsTags: goods.GoodsTags, MarketPrice: goods.MarketPrice, GoodsBrief: goods.GoodsBrief, GoodsFrontImage: goods.GoodsFrontImage, GoodsImages: goods.GoodsImages, OnSale: goods.OnSale, ShipFree: goods.ShipFree, ShipID: goods.ShipID, IsNew: goods.IsNew, IsHot: goods.IsHot, } result := g.data.DB(c).Save(product) if result.Error != nil { return nil, result.Error } return product.ToDomain(), nil } - 在
biz目录下新建goods_sku.go
package biz import ( "context" "github.com/go-kratos/kratos/v2/log" "goods/internal/domain" ) type Sku struct { ID int64 GoodsID int64 GoodsSn string GoodsName string SkuName string SkuCode string BarCode string Price int64 PromotionPrice int64 Points int64 RemarksInfo string Pic string Inventory int64 OnSale bool AttrInfo string } type GoodsSkuRepo interface { Create(context.Context, *domain.GoodsSku) (*domain.GoodsSku, error) CreateSkuRelation(context.Context, []*domain.GoodsSpecificationSku) error } type GoodsSkuUsecase struct { repo GoodsSkuRepo log *log.Helper } func NewGoodsSkuUsecase(repo GoodsSkuRepo, logger log.Logger) *GoodsSkuUsecase { return &GoodsSkuUsecase{repo: repo, log: log.NewHelper(logger)} } data目录下的goods_sku.go实现Create方法和CreateSkuRelation
package data ... func (g *goodsSkuRepo) Create(ctx context.Context, req *domain.GoodsSku) (*domain.GoodsSku, error) { sku := &GoodsSku{ GoodsID: req.GoodsID, GoodsSn: req.GoodsSn, GoodsName: req.GoodsName, SkuName: req.SkuName, SkuCode: req.SkuCode, BarCode: req.BarCode, Price: req.Price, PromotionPrice: req.PromotionPrice, Points: req.Points, RemarksInfo: req.RemarksInfo, Pic: req.Pic, OnSale: req.OnSale, AttrInfo: req.AttrInfo, Inventory: req.Inventory, } if err := g.data.DB(ctx).Save(sku).Error; err != nil { return nil, err } return sku.ToDomain(), nil } func (g *goodsSkuRepo) CreateSkuRelation(ctx context.Context, req []*domain.GoodsSpecificationSku) error { var info []*GoodsSpecificationSku for _, sku := range req { i := GoodsSpecificationSku{ SkuID: sku.SkuID, SkuCode: sku.SkuCode, SpecificationId: sku.SpecificationId, ValueId: sku.ValueId, } info = append(info, &i) } result := g.data.DB(ctx).Table("goods_specification_skus").Save(&info) return result.Error } - 在
biz目录下新建inventory.go文件
package biz import ( "context" "github.com/go-kratos/kratos/v2/log" "goods/internal/domain" ) type InventoryRepo interface { Create(context.Context, *domain.Inventory) (*domain.Inventory, error) } type InventoryUsecase struct { repo InventoryRepo log *log.Helper } func NewInventoryUsecase(repo InventoryRepo, logger log.Logger) *InventoryUsecase { return &InventoryUsecase{repo: repo, log: log.NewHelper(logger)} } 创建库存的方法在上面已经写过了,商品规格和商品 sku 的关联关系创建方法上面也已经写过了。
测试
没错还是通过 BloomRPC 工具进行测试。

如图创建成功了,这里的参数太多了,如果你跟我用的是同一个工具,它会自动构建所需的参数,你只需根据自己的需要简单修改一下就可以了。
结束语
本篇只提供了一个商品创建的方法,其他方法没有在文章中体现,单元测试方法也没有编写,重复性的工作这里就不编写了,通过前几篇的文章,相信你可以自己完善剩余的方法。
经过这么多天的编写,商品服务算是告一断落了,当然商品管理的 HTTP 服务还是会编写的,以后会新建一个商城后台管理的前端的项目来管理商品,他们是通过 HTTP 服务进行的,并不是直接调用这里的 rpc 服务。后期也会加入 Elasticsearch 搜索服务进行商品检索。
下一篇准备开始写购物车和订单服务,敬请期待

感谢您的耐心阅读,动动手指点个赞吧。
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
哪位大佬能实现 biz 层 CreateGoods 方法的单元测试?感谢