这篇咱们开始编写商品服务的商品类型了。一个电商的商品设计是比较复杂的,咱们这里不过多的深究表是否合理,是否漏写之类的问题,主要是为了搞明白 kratos 的使用和微服务相关的调用关系。当然我真正的编写时也会尽可能的让此项目的商品设计合理一些。但大量的表设计呀,重复性的 curd 就不会再文章中体现了,具体参看 GitHub 上的源码,当然你觉得不合理的地方,也可以给项目提 PR。
文章写的不清晰的地方可通过 GitHub 源码进行查看, 也感谢您指出不足之处,欢迎大佬指教。
注:竖排 … 代码省略,为了保持文章的篇幅简洁,我会将一些不必要的代码使用竖排的 . 来代替,你在复制本文代码块的时候,切记不要将 . 也一同复制进去。
商品类型
商品类型不同于商品分类,它指的是依据某一类商品的相同规格和属性归纳成的一个类型集合,例如手机类型都有机身颜色、内存大小、屏幕尺寸、铃声、网络制式等共同的规格属性;书籍类型都有出版社、作者、ISBN 号等共同的属性。
编写代码
新增 service/goods/internal/data/good_type.go
文件;编写如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package data
import ( "context" "github.com/go-kratos/kratos/v2/log" "goods/internal/biz" "gorm.io/gorm" "time" )
type GoodsType struct { ID int64 `gorm:"primarykey;type:int" json:"id"` Name string `gorm:"type:varchar(50);not null;comment:商品类型名称" json:"name"` TypeCode string `gorm:"type:varchar(50);not null;comment:商品类型编码" json:"type_code"` NameAlias string `gorm:"type:varchar(50);not null;comment:商品类型别名" json:"name_alias"` IsVirtual bool `gorm:"comment:是否是虚拟商品显示;default:false" json:"is_virtual"` Desc string `gorm:"type:varchar(50);not null;comment:商品类型描述" json:"desc"` Sort int32 `gorm:"comment:类型排序;default:99;not null;type:int" json:"sort"` 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"` }
type goodsTypeRepo struct { data *Data log *log.Helper }
func NewGoodsTypeRepo(data *Data, logger log.Logger) biz.GoodsTypeRepo { return &goodsTypeRepo{ data: data, log: log.NewHelper(logger), } }
|
注入 GoodsType 修改 service/goods/internal/data/data.go
文件
1 2 3 4 5 6 7
| var ProviderSet = wire.NewSet( NewData, NewDB, NewRedis, NewBrandRepo, NewCategoryRepo, NewGoodsTypeRepo, )
|
⚠️ ⚠️ ⚠️ 注意: 接下来新增或修改的代码, wire 注入的文件中需要修改的代码,都不会再本文中提及了。例如 biz、service 层的修改,自己编写的过程中,千万不要忘记 wire 注入,更不要忘记,执行 make wire 命令,重新生成项目的 wire 文件 ⚠️ ⚠️ ⚠️
goods.proto
文件中新加入创建商品类型的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| ...
import "validate/validate.proto"; ...
service Goods { ... rpc CreateGoodsType(GoodsTypeRequest) returns(GoodsTypeResponse); }
...
message GoodsTypeRequest { int64 id = 1; string name = 2 [(validate.rules).string.min_len = 3];; string typeCode = 3 [(validate.rules).string.min_len = 3];; string nameAlias = 4; bool isVirtual = 5; string desc = 6; int32 sort = 7; }
message GoodsTypeResponse { int64 id = 1; }
|
新增 service/goods/internal/service/goods_type.go
文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package service
import ( "context" v1 "goods/api/goods/v1" "goods/internal/domain" )
func (g *GoodsService) CreateGoodsType(ctx context.Context, r *v1.GoodsTypeRequest) (*v1.GoodsTypeResponse, error) { id, err := g.gt.GoosTypeCreate(ctx, &domain.GoodsType{ Name: r.Name, TypeCode: r.TypeCode, NameAlias: r.NameAlias, IsVirtual: r.IsVirtual, Desc: r.Desc, Sort: r.Sort, }) if err != nil { return nil, err } return &v1.GoodsTypeResponse{ Id: id, }, nil }
|
你应该也注意到了,代码中接受不了到的值,经过 domain 进行转换了,之前都是直接通过 biz 层下面定义的参数结构体去转换。这里为什么又引入了一层 domain 呢?主要是,之前编写的服务都比较单一化,一个请求方法,只操作一张表,也没有过多的业务逻辑,但接下来商品服务会有更多的,新增商品、商品 SKU、商品属性、商品规格,一系列的业务逻辑处理,考虑到 biz 层尽量做到只编排 repo 和一些少量的业务逻辑处理,其他处理类似于参数校验呀、插入之前判断是否存在呀之类的,都放到了 domain 层去处理。
新建 service/goods/internal/domain/goods_type.go
文件,编写内容如下:
1 2 3 4 5 6 7 8 9 10 11 12
| package domain
type GoodsType struct { ID int64 Name string TypeCode string NameAlias string IsVirtual bool Desc string Sort int32 }
|
新增 service/goods/internal/biz/goods_type.go
文件,编写内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package biz
import ( "context" "errors" "github.com/go-kratos/kratos/v2/log" "goods/internal/domain" )
type GoodsTypeRepo interface { CreateGoodsType(context.Context, *domain.GoodsType) (int64, error) }
type GoodsTypeUsecase struct { repo GoodsTypeRepo bRepo BrandRepo log *log.Helper }
func NewGoodsTypeUsecase(repo GoodsTypeRepo, tx Transaction, bRepo BrandRepo, logger log.Logger) *GoodsTypeUsecase { return &GoodsTypeUsecase{ repo: repo, tx: tx, bRepo: bRepo, log: log.NewHelper(logger), } }
func (gt *GoodsTypeUsecase) GoosTypeCreate(ctx context.Context, r *domain.GoodsType) (int64, error) { id, err := gt.repo.CreateGoodsType(ctx, r) if err != nil { return err } return id, nil }
|
修改 data
层 goods_type.go
, 加入 createGoodsType 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package data
...
func (g *goodsTypeRepo) CreateGoodsType(ctx context.Context, req *domain.GoodsType) (int64, error) { goodsType := &GoodsType{ Name: req.Name, TypeCode: req.TypeCode, NameAlias: req.NameAlias, IsVirtual: req.IsVirtual, Desc: req.Desc, Sort: req.Sort, CreatedAt: time.Time{}, UpdatedAt: time.Time{}, } result := g.data.db.Save(goodsType) return goodsType.ID, result.Error }
|
- 测试提交
由于之前推荐的使用 goland ide 自带的工具进行 rpc 方法测试,很多同学反应各种问题,连接失败、协议缓冲区等错误,今天咱们推荐另一个免费的开源工具,BloomRPC ,安装比较简单,这里就不演示了,自己进去他的 GitHub 主页就能看到安装步骤,使用起来也超级简单。
打开 BloomRPC 工具,导入你的 proto 文件,它就会把你写好的 rpc 方法全部列在左侧,修改好你的 IP 和 port ,
它甚至把你每个请求的方法所需要的数据都构造好了,你只要稍作修改,就可以发起请求进行测试了。如下图启动服务之后,点击绿色的按钮,得到创建成功的 ID。
结束语
本篇只提供了一个商品类型的创建方法,其他方法没有在文章中体现,单元测试方法也没有编写,重复性的工作这里就不编写了,通过前几篇的文章,相信你可以自己完善剩余的方法。
这篇引入的 domain 层,并未做其他处理,只是接收参数之后通过 domain 下定义的结构体进行转换,你会想说那么跟之前一样,通过 biz 定义的结构体不也一样转换吗?事实确实是如此。这一篇只是带你先了解一下 domain 层,具体 domain 如何利用起来,期待下一篇文章吧。
感谢您的耐心阅读,动动手指点个赞吧。
关注我获取更新