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 协议》,转载必须注明作者和本文链接
哪位大佬能实现 biz 层 CreateGoods 方法的单元测试?感谢