这篇开始咱们就要编写商品服务了。一个电商的商品设计是比较复杂的,咱们这里不过多的深究表是否合理,是否漏写之类的问题,主要是为了搞明白 kratos 的使用和微服务相关的调用关系。当然我真正的编写时也会尽可能的让此项目的商品设计合理一些。但大量的表设计呀,重复性的 curd 就不会再文章中体现了,具体参看 GitHub 上的源码,当然你觉得不合理的地方,也可以给项目提 PR。
文章写的不清晰的地方可通过 GitHub 源码进行查看, 也感谢您指出不足之处,欢迎大佬指教。
注:竖排 … 代码省略,为了保持文章的篇幅简洁,我会将一些不必要的代码使用竖排的 . 来代替,你在复制本文代码块的时候,切记不要将 . 也一同复制进去。
准备工作
由于前面几篇文章(第一篇和第四篇)都已经写过了,如何初始化一个 kratos 项目,并修改部分主要的文件,来变成自己的服务并编写业务。所以此篇文章就不做重复性的工作了,这篇编写一个商品分类的创建,让商品服务先存在。废话少说开始写
Goods 服务目录存放的位置
| 12
 3
 4
 5
 6
 
 | // 整体的项目 目录结构如下|-- kratos-shop
 |-- service
 |-- user // 原先的用户服务 grpc
 |-- goods // 新增的商品服务 grpc
 |-- shop //  interface
 
 | 
编写业务
- 修改 goods服务下的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
 28
 29
 
 | syntax = "proto3";
 package goods.v1;
 
 import "google/protobuf/empty.proto";
 
 option go_package = "goods/api/goods/v1;v1";
 
 service Goods {
 rpc CreateCategory(CategoryInfoRequest) returns(CategoryInfoResponse);
 }
 
 message CategoryInfoRequest {
 int32 id = 1;
 string name = 2;
 int32 parentCategory = 3;
 int32 level = 4;
 bool isTab = 5;
 int32 sort = 6;
 }
 
 message CategoryInfoResponse {
 int32 id = 1;
 string name = 2;
 int32 parentCategory = 3;
 int32 level = 4;
 bool isTab = 5;
 int32 sort = 6;
 }
 
 | 
- 修改 service目录下的category.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
 
 | 
 package service
 
 import (
 "context"
 "encoding/json"
 v1 "goods/api/goods/v1"
 "goods/internal/biz"
 "google.golang.org/protobuf/types/known/emptypb"
 )
 
 
 func (g *GoodsService) CreateCategory(ctx context.Context, r *v1.CategoryInfoRequest) (*v1.CategoryInfoResponse, error) {
 result, err := g.cac.CreateCategory(ctx, &biz.CategoryInfo{
 Name:           r.Name,
 ParentCategory: r.ParentCategory,
 Level:          r.Level,
 IsTab:          r.IsTab,
 Sort:           r.Sort,
 })
 if err != nil {
 return nil, err
 }
 
 return &v1.CategoryInfoResponse{
 Id:             result.ID,
 Name:           result.Name,
 ParentCategory: result.ParentCategory,
 Level:          result.Level,
 IsTab:          result.IsTab,
 Sort:           result.Sort,
 }, nil
 }
 
 | 
| 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
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 
 | package biz
 import (
 "context"
 "github.com/go-kratos/kratos/v2/log"
 )
 
 type Category struct {
 ID               int32
 Name             string
 ParentCategoryID int32
 SubCategory      []*Category
 Level            int32
 IsTab            bool
 Sort             int32
 }
 
 type CategoryInfo struct {
 ID             int32
 Name           string
 ParentCategory int32
 Level          int32
 IsTab          bool
 Sort           int32
 }
 
 type CategoryRepo interface {
 AddCategory(context.Context, *CategoryInfo) (*CategoryInfo, error)
 }
 
 type CategoryUsecase struct {
 repo CategoryRepo
 log  *log.Helper
 }
 
 func NewCategoryUsecase(repo CategoryRepo, logger log.Logger) *CategoryUsecase {
 return &CategoryUsecase{repo: repo, log: log.NewHelper(logger)}
 }
 
 func (c *CategoryUsecase) CreateCategory(ctx context.Context, r *CategoryInfo) (*CategoryInfo, error) {
 cateInfo, err := c.repo.AddCategory(ctx, r)
 if err != nil {
 return nil, err
 }
 return cateInfo, nil
 }
 
 | 
| 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
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 
 | package data
 
 import (
 "context"
 "errors"
 "fmt"
 "github.com/go-kratos/kratos/v2/log"
 "goods/internal/biz"
 "gorm.io/gorm"
 "time"
 )
 
 
 type Category struct {
 ID               int32          `gorm:"primarykey;type:int" json:"id"`
 Name             string         `gorm:"type:varchar(50);not null;comment:分类名称" json:"name"`
 ParentCategoryID int32          `json:"parent_id"`
 ParentCategory   *Category      `json:"-"`
 SubCategory      []*Category    `gorm:"foreignKey:ParentCategoryID;references:ID" json:"sub_category"`
 Level            int32          `gorm:"column:level;default:1;not null;type:int;comment:分类的级别" json:"level"`
 IsTab            bool           `gorm:"comment:是否显示;default:false" json:"is_tab"`
 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 CategoryRepo struct {
 data *Data
 log  *log.Helper
 }
 
 
 func NewCategoryRepo(data *Data, logger log.Logger) biz.CategoryRepo {
 return &CategoryRepo{
 data: data,
 log:  log.NewHelper(logger),
 }
 }
 
 
 func (r *CategoryRepo) AddCategory(ctx context.Context, req *biz.CategoryInfo) (*biz.CategoryInfo, error) {
 cMap := map[string]interface{}{}
 cMap["name"] = req.Name
 cMap["level"] = req.Level
 cMap["is_tab"] = req.IsTab
 cMap["sort"] = req.Sort
 cMap["add_time"] = time.Now()
 cMap["update_time"] = time.Now()
 
 
 if req.Level != 1 {
 var categories Category
 if res := r.data.db.First(&categories, req.ParentCategory); res.RowsAffected == 0 {
 return nil, errors.New("商品分类不存在")
 }
 cMap["parent_category_id"] = req.ParentCategory
 }
 
 result := r.data.db.Model(&Category{}).Create(&cMap)
 if result.Error != nil {
 return nil, result.Error
 }
 var value int32
 value, ok := cMap["parent_category_id"].(int32)
 if !ok {
 value = 0
 }
 res := &biz.CategoryInfo{
 Name:           cMap["name"].(string),
 ParentCategory: value,
 Level:          cMap["level"].(int32),
 IsTab:          cMap["is_tab"].(bool),
 Sort:           cMap["sort"].(int32),
 }
 return res, nil
 }
 
 | 
测试创建分类
注:这里有个很重要的点,修改 config 配置的时候,需要指定数据库,这里商品服务是独立,故不能跟用户服务用同一个库,此处用的是一个全新的 shop_goods 库。consul 和 trace 的配置保持一致,但主入口 main 文件中的服务名称要修改一下。
如果你用的是 goland 编辑器这里提供一个测试 rpc 接口的方法,就不用再编写指定的测试文件了。
编辑器打开 goods.proto 文件,看到如图:

点击这按钮你会跳到一个全新的文件,如图:

注意记得把请求的 IP 和 Port 修改成自己的,然后点击绿色的按钮,测试一下吧。
结束语
OK 此篇文章就写到这里了,虽然有很多点都没写,但我相信,你肯定会把没写的流程补上,比如如何把 biz 层的 Usecase 注入到 Goods 服务中,如何编写 data 层的数据库连接之类的。
感谢您的耐心阅读,动动手指点个赞吧。
关注我获取更新
