这篇开始咱们就要编写商品服务了。一个电商的商品设计是比较复杂的,咱们这里不过多的深究表是否合理,是否漏写之类的问题,主要是为了搞明白 kratos 的使用和微服务相关的调用关系。当然我真正的编写时也会尽可能的让此项目的商品设计合理一些。但大量的表设计呀,重复性的 curd 就不会再文章中体现了,具体参看 GitHub 上的源码,当然你觉得不合理的地方,也可以给项目提 PR。
文章写的不清晰的地方可通过 GitHub 源码进行查看, 也感谢您指出不足之处,欢迎大佬指教。
注:竖排 … 代码省略,为了保持文章的篇幅简洁,我会将一些不必要的代码使用竖排的 . 来代替,你在复制本文代码块的时候,切记不要将 . 也一同复制进去。
准备工作
由于前面几篇文章(第一篇和第四篇)都已经写过了,如何初始化一个 kratos 项目,并修改部分主要的文件,来变成自己的服务并编写业务。所以此篇文章就不做重复性的工作了,这篇编写一个商品分类的创建,让商品服务先存在。废话少说开始写
Goods 服务目录存放的位置
1 2 3 4 5 6
| // 整体的项目 目录结构如下 |-- kratos-shop |-- service |-- user // 原先的用户服务 grpc |-- goods // 新增的商品服务 grpc |-- shop // interface
|
编写业务
- 修改
goods
服务下的 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 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
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
|
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 }
|
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 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 }
|
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 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 层的数据库连接之类的。
感谢您的耐心阅读,动动手指点个赞吧。
关注我获取更新