这篇咱们开始编写商品服务的商品类型了。一个电商的商品设计是比较复杂的,咱们这里不过多的深究表是否合理,是否漏写之类的问题,主要是为了搞明白 kratos 的使用和微服务相关的调用关系。当然我真正的编写时也会尽可能的让此项目的商品设计合理一些。但大量的表设计呀,重复性的 curd 就不会再文章中体现了,具体参看 GitHub 上的源码,当然你觉得不合理的地方,也可以给项目提 PR。
文章写的不清晰的地方可通过 GitHub 源码进行查看, 也感谢您指出不足之处,欢迎大佬指教。
注:竖排 … 代码省略,为了保持文章的篇幅简洁,我会将一些不必要的代码使用竖排的 . 来代替,你在复制本文代码块的时候,切记不要将 . 也一同复制进去。
商品类型
商品类型不同于商品分类,它指的是依据某一类商品的相同规格和属性归纳成的一个类型集合,例如手机类型都有机身颜色、内存大小、屏幕尺寸、铃声、网络制式等共同的规格属性;书籍类型都有出版社、作者、ISBN 号等共同的属性。
编写代码
新增 service/goods/internal/data/good_type.go 文件;编写如下代码
| 12
 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 文件
| 12
 3
 4
 5
 6
 7
 
 | var ProviderSet = wire.NewSet(NewData,
 NewDB, NewRedis,
 NewBrandRepo,
 NewCategoryRepo,
 NewGoodsTypeRepo,
 )
 
 | 
⚠️ ⚠️ ⚠️ 注意: 接下来新增或修改的代码, wire 注入的文件中需要修改的代码,都不会再本文中提及了。例如 biz、service 层的修改,自己编写的过程中,千万不要忘记 wire 注入,更不要忘记,执行 make wire 命令,重新生成项目的 wire 文件 ⚠️ ⚠️ ⚠️
- goods.proto文件中新加入创建商品类型的方法
| 12
 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 文件,内容如下:
| 12
 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 文件,编写内容如下:
| 12
 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 文件,编写内容如下:
| 12
 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 方法
| 12
 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 如何利用起来,期待下一篇文章吧。
感谢您的耐心阅读,动动手指点个赞吧。
关注我获取更新
