kratos 框架商城微服务实战之商品服务 (六)

本文最后更新于:2 个月前

这篇开始咱们就要编写商品服务了。一个电商的商品设计是比较复杂的,咱们这里不过多的深究表是否合理,是否漏写之类的问题,主要是为了搞明白 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
// service/goods/internal/service/category.go

package service

import (
"context"
"encoding/json"
v1 "goods/api/goods/v1"
"goods/internal/biz"
"google.golang.org/protobuf/types/known/emptypb"
)

// CreateCategory 创建分类
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
}
  • 修改 biz 目录下的 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
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
}
  • 修改 data 目录下的 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
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"
)

// Category 商品分类表
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
}

// NewCategoryRepo .
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 层的数据库连接之类的。

感谢您的耐心阅读,动动手指点个赞吧。

关注我获取更新