找回密码
 注册

QQ登录

只需一步,快速开始

查看: 2187|回复: 0

Go-kratos 框架商城微服务实战之商品服务 (八)

[复制链接]
发表于 2022-3-21 14:40:48 | 显示全部楼层 |阅读模式
大家好,今天咱们继续完善商品服务里的商品规格模块。

众所周知,一个电商的商品设计是比较复杂的,咱们这里不过多的深究商品设计的每个表是否合理,是否漏写之类的问题,主要是为了搞明白 kratos 的使用和微服务相关的调用关系。当然我真正的编写时也会尽可能的让此项目的商品设计合理一些。但大量的表设计呀,重复性的 curd 就不会在文章中体现了,具体的代码参看 GitHub[1] 上的源码。当然你觉得不合理的地方,欢迎给项目提 PR。
商品规格参数

商品参数,也有人管它们叫商品规格参数,信息如下图所示,一般可以分为规格分组、规格属性及属性值。这些特殊的规格参数,会影响商品 SKU 的信息,  我们选择不同的颜色、版本等规格,会影响我们 SKU 的记录,也就是对应的销售价格和商品的库存量。

Go-kratos 框架商城微服务实战之商品服务 (八)w1.jpg

商品属性参数信息如下图所示,一般可以分为分组、属性及属性值。这些信息基本不影响商品 SKU,只是作为商品的一些参数信息展示。

Go-kratos 框架商城微服务实战之商品服务 (八)w2.jpg

咱们这里为了方便商品的管理,使得数据更加有规律,实现更好的弹性设计,各自设置为一个模块。然后每个单独的模块都会跟上一篇文章中创建的商品类型进行关联。在创建一个具体的商品的时候,更好的使用商品类型下的商品规格以及商品属性信息。
编写代码

设计商品规格表

    data 层新增 specifications.go 文件
package data

import (
"context"
"errors"
"goods/internal/biz"
"goods/internal/domain"
"time"

"github.com/go-kratos/kratos/v2/log"
"gorm.io/gorm"
)

// SpecificationsAttr 规格参数信息表
type SpecificationsAttr struct {
ID        int64          `gorm:"primarykey;type:int" json:"id"`
TypeID    int64          `gorm:"index:type_id;type:int;comment:商品类型ID;not null"`
Name      string         `gorm:"type:varchar(250);not null;comment:规格参数名称" json:"name"`
Sort      int32          `gorm:"comment:规格排序;default:99;not null;type:int" json:"sort"`
Status    bool           `gorm:"comment:参数状态;default:false" json:"status"`
IsSKU     bool           `gorm:"comment:是否通用的SKU持有;default:false" json:"is_sku"`
IsSelect  bool           `gorm:"comment:是否可查询;default:false" json:"is_select"`
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"`
}

// SpecificationsAttrValue 规格参数信息选项表
type SpecificationsAttrValue struct {
ID        int64          `gorm:"primarykey;type:int" json:"id"`
AttrId    int64          `gorm:"index:attr_id;type:int;comment:规格ID;not null"`
Value     string         `gorm:"type:varchar(250);not null;comment:规格参数信息值" json:"value"`
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 specificationRepo struct {
data *Data
log  *log.Helper
}

// NewSpecificationRepo .
func NewSpecificationRepo(data *Data, logger log.Logger) biz.SpecificationRepo {
return &specificationRepo{
  data: data,
  log:  log.NewHelper(logger),
}
}
    goods.proto 新增 rpc 方法
syntax = "proto3";

...

service Goods {

...

// 商品规格或属性的信息
rpc CreateGoodsSpecification(SpecificationRequest) returns(SpecificationResponse);
}

...

message SpecificationValue {
  int64 id = 1;
  int64 attrId = 2;
  string value = 3 [(validate.rules).string.min_len = 3];
  int32 sort = 4 [(validate.rules).string.min_len = 3];
}

message SpecificationRequest {
  int64 id = 1;
  int64 typeId = 2 [(validate.rules).string.min_len = 1];
  string name = 3 [(validate.rules).string.min_len = 3];
  int32 sort = 4 [(validate.rules).string.min_len = 1];
  bool status = 5;
  bool isSku = 6;
  bool isSelect = 7;
  repeated SpecificationValue specificationValue = 8;
}

message SpecificationResponse {
  int64 id = 1;
}

...

    service 层新增 specifications.go
package service

import (
"context"

v1 "goods/api/goods/v1"
"goods/internal/domain"
)

// CreateGoodsSpecification 创建商品规格版本
func (g *GoodsService) CreateGoodsSpecification(ctx context.Context, r *v1.SpecificationRequest) (*v1.SpecificationResponse, error) {
var value []*domain.SpecificationValue
// 组织规格参数值
if r.SpecificationValue != nil {
  for _, v := range r.SpecificationValue {
   res := &domain.SpecificationValue{
    Value: v.Value,
    Sort:  v.Sort,
   }
   value = append(value, res)
  }
}

id, err := g.s.CreateSpecification(ctx, &domain.Specification{
  TypeID:             r.TypeId,
  Name:               r.Name,
  Sort:               r.Sort,
  Status:             r.Status,
  IsSKU:              r.IsSku,
  IsSelect:           r.IsSelect,
  SpecificationValue: value,
})

if err != nil {
  return nil, err
}
return &v1.SpecificationResponse{
  Id: id,
}, nil
}

    domain 层新增 specifications.go

这里上一篇介绍的 domain 又出现,开始在 domain 编写一个逻辑吧
package domain

type Specification struct {
ID                 int64
TypeID             int64
Name               string
Sort               int32
Status             bool
IsSKU              bool
IsSelect           bool
SpecificationValue []*SpecificationValue
}

type SpecificationValue struct {
ID     int64
AttrId int64
Value  string
Sort   int32
}

// 新增判断类型不能为空的方法
func (b *Specification) IsTypeIDEmpty() bool {
return b.TypeID == 0
}

// 规格下面的参数不能为空的方法
func (b *Specification) IsValueEmpty() bool {
return b.SpecificationValue == nil
}

    biz 层新增 specifications.go
package biz

import (
"context"
"errors"
"goods/internal/domain"

"github.com/go-kratos/kratos/v2/log"
)

type SpecificationRepo interface {
CreateSpecification(context.Context, *domain.Specification) (int64, error)
CreateSpecificationValue(context.Context, int64, []*domain.SpecificationValue) error
}

type SpecificationUsecase struct {
repo  SpecificationRepo
gRepo GoodsTypeRepo // 加入商品类型的 repo,用来查询类型是否存在
tx    Transaction // 引入 gorm 事务
log   *log.Helper
}

func NewSpecificationUsecase(repo SpecificationRepo, type GoodsTypeRepo, tx Transaction,
logger log.Logger) *SpecificationUsecase {

return &SpecificationUsecase{
  repo:  repo,
  gRepo: type,
  tx:    tx,
  log:   log.NewHelper(logger),
}
}

// CreateSpecification 创建商品规格
func (s *SpecificationUsecase) CreateSpecification(ctx context.Context, r *domain.Specification) (int64, error) {
var (
  id  int64
  err error
)
  // domain 下编写的判断 typeid 的方法
if r.IsTypeIDEmpty() {
  return id, errors.New("请选择商品类型进行绑定")
}

  // domain 下编写的 value 的方法
if r.IsValueEmpty() {
  return id, errors.New("请填写商品规格下的参数")
}

// 去查询有没有这个类型
_, err = s.gRepo.IsExistsByID(ctx, r.TypeID)
if err != nil {
  return id, err
}

// 引入事务
err = s.tx.ExecTx(ctx, func(ctx context.Context) error {
  id, err = s.repo.CreateSpecification(ctx, r) // 插入规格
  if err != nil {
   return err
  }

  err = s.repo.CreateSpecificationValue(ctx, id, r.SpecificationValue) // 插入规格对应的值
  if err != nil {
   return err
  }
  return nil
})
return id, err
}

    处理 good_type 的判断逻辑
// biz 层
...
type GoodsTypeRepo interface {
  ...
IsExistsByID(context.Context, int64) (*domain.GoodsType, error)
}

...

// data 层
...

func (g *goodsTypeRepo) IsExistsByID(ctx context.Context, typeID int64) (*domain.GoodsType, error) {
var goodsType GoodsType
if res := g.data.db.First(&goodsType, typeID); res.RowsAffected == 0 {
  return nil, errors.New("商品类型不存在")
}

res := &domain.GoodsType{
  ID:        goodsType.ID,
  Name:      goodsType.Name,
  TypeCode:  goodsType.TypeCode,
  NameAlias: goodsType.NameAlias,
  IsVirtual: goodsType.IsVirtual,
  Desc:      goodsType.Desc,
  Sort:      goodsType.Sort,
}
return res, nil
}


    data 层 specifications.go 新增方法

    注意这里调用 repo 的方式,用的是 g.data.DB(ctx) 而不是之前的 g.data.db,这里是因为引入了 GORM MySQL 的事务,如果你对在 kratos 使用 GORM MySQL 的事务还不是很熟悉的话,请查看我之前写的一篇 kratos 中使用 GORM MySQL 的事务[2] 的文章。

...

func (g *specificationRepo) CreateSpecification(ctx context.Context, req *domain.Specification) (int64, error) {
s := &SpecificationsAttr{
  TypeID:    req.TypeID,
  Name:      req.Name,
  Sort:      req.Sort,
  Status:    req.Status,
  IsSKU:     req.IsSKU,
  IsSelect:  req.IsSelect,
  CreatedAt: time.Time{},
  UpdatedAt: time.Time{},
}
result := g.data.DB(ctx).Save(s)
return s.ID, result.Error
}

func (g *specificationRepo) CreateSpecificationValue(ctx context.Context, AttrId int64, req []*domain.SpecificationValue) error {
var value []*SpecificationsAttrValue
for _, v := range req {
  res := &SpecificationsAttrValue{
   AttrId:    AttrId,
   Value:     v.Value,
   Sort:      v.Sort,
   CreatedAt: time.Time{},
   UpdatedAt: time.Time{},
  }
  value = append(value, res)
}
result := g.data.DB(ctx).Create(&value)
return result.Error
}

测试

还是使用上一次介绍的工具,如图:

Go-kratos 框架商城微服务实战之商品服务 (八)w3.jpg

你可以少写参数或故意写错一些参数来验证,写的判断逻辑是否生效,这里就不演示了。
结束语

本篇只提供了一个商品规格参数的创建方法,其他方法没有在文章中体现,单元测试方法也没有编写,重复性的工作这里就不编写了,通过前几篇的文章,相信你可以自己完善剩余的方法。

下一篇开始编写本文中提到的商品属性,敬请期待。

Go-kratos 框架商城微服务实战之商品服务 (八)w4.jpg

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

[1]
GitHub: https://github.com/aliliin/kratos-shop
[2]
kratos 中使用 GORM MySQL 的事务: https://learnku.com/articles/65506
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|Archiver|手机版|小黑屋|广告网 ( 鄂ICP备20005464号-17 )

GMT+8, 2024-7-8 15:39

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表