19、商品微服务-srv层实现

目录

  • 前言
  • 一、需求分析
    • 1 - 数据库实体分析
    • 2 - 商品微服务接口分析
    • 3 - 商品服务表结构设计
    • 4 - 生成表结构与数据导入
  • 二、启动商品服务
    • 1 - proto接口
    • 2 - handle中实现接口
    • 3 - nacos配置
    • 4 - 修改yaml配置
    • 5 - 读取Tags和本机host
  • 三、品牌、商品分类、轮播接口
    • 1 - 接口测试
    • 2 - 品牌列表查询实现
    • 3 - 品牌新建、删除、更新接口
    • 4 - 轮播图的CRUD
    • 5 - 商品分类列表接口
    • 6 - 商品分类子分类接口
    • 7 - 商品新建、删除和更新接口
    • 8 - 品牌分类相关接口
  • 四、商品接口
    • 1 - 商品列表接口实现
    • 2 - 批量获取商品、获取商品详情
    • 3 - 新增、修改、更新商品
  • 五、完整源码

前言

  • 之前我们已经通过grpc从零开始到实现了用户的微服务,接来我们将实现商品微服务

一、需求分析

1 - 数据库实体分析

19、商品微服务-srv层实现_第1张图片

2 - 商品微服务接口分析

19、商品微服务-srv层实现_第2张图片

3 - 商品服务表结构设计

  • goods_srv/model/base.go:公共的base字段;自定义的GormList类型
package model

import (
	"database/sql/driver"
	"encoding/json"
	"gorm.io/gorm"
	"time"
)

type BaseModel struct {
	ID        int32          `gorm:"primarykey;type:int" json:"id"` //为什么使用int32, bigint
	CreatedAt time.Time      `gorm:"column:add_time" json:"-"`
	UpdatedAt time.Time      `gorm:"column:update_time" json:"-"`
	DeletedAt gorm.DeletedAt `json:"-"`
	IsDeleted bool           `json:"-"`
}

// GormList 自定义gorm类型
type GormList []string

func (g GormList) Value() (driver.Value, error) {
	return json.Marshal(g)
}

// Scan 实现 sql.Scanner 接口,Scan 将 value 扫描至 Jsonb
func (g *GormList) Scan(value interface{}) error {
	return json.Unmarshal(value.([]byte), &g)
}

  • goods_srv/model/goods.go:商品表结构类型定义
package model

// Category
//类型, 这个字段是否能为null, 这个字段应该设置为可以为null还是设置为空, 0
//实际开发过程中,尽量设置为不为null
//https://zhuanlan.zhihu.com/p/73997266
//这些类型我们使用int32还是int,可以减少proto的类型转换,proto没有int类型
type Category struct {
	BaseModel
	Name             string      `gorm:"type:varchar(20);not null" json:"name"`
	ParentCategoryID int32       `json:"parent"` // 数据库存储的外键ID
	ParentCategory   *Category   `json:"-"`      // gorm中外键自己指向自己,需要使用指针
	SubCategory      []*Category `gorm:"foreignKey:ParentCategoryID;references:ID" json:"sub_category"`
	Level            int32       `gorm:"type:int;not null;default:1;comment:'1为1级类目,2为2级...'" json:"level"`
	IsTab            bool        `gorm:"default:false;not null;comment:'能否展示在Tab栏'" json:"is_tab"`
}

type Brands struct {
	BaseModel
	Name string `gorm:"type:varchar(20);not null;comment:'品牌名称'"`
	Logo string `gorm:"type:varchar(200);default:'';not null;comment:'品牌图标'"`
}

// GoodsCategoryBrand 商品和品牌的对应关系
// CategoryID和BrandsID使用相同的index,gorm就会建成联合的索引
type GoodsCategoryBrand struct {
	BaseModel
	CategoryID int32 `gorm:"type:int;index:idx_category_brand,unique"` // 商品外键
	Category   Category

	BrandsID int32 `gorm:"type:int;index:idx_category_brand,unique"` // 品牌外键
	Brands   Brands
}

// TableName 自定义生成的表名
// 为了让gorm生成GoodsCategoryBrand表的时候不生成下划线的方式
// 如goods_category_brand
func (GoodsCategoryBrand) TableName() string {
	return "goodscategorybrand"
}

// Banner 轮播图
type Banner struct {
	BaseModel
	Image string `gorm:"type:varchar(200);not null"`
	Url   string `gorm:"type:varchar(200);not null"`
	Index int32  `gorm:"type:int;default:1;not null"`
}

// Goods 商品表结构
type Goods struct {
	BaseModel

	CategoryID int32 `gorm:"type:int;not null"`
	Category   Category
	BrandsID   int32 `gorm:"type:int;not null"`
	Brands     Brands

	OnSale   bool `gorm:"default:false;not null;comment:'是否上架'"`
	ShipFree bool `gorm:"default:false;not null;comment:'是否免运费'"`
	IsNew    bool `gorm:"default:false;not null;comment:'是否新品'"`
	IsHot    bool `gorm:"default:false;not null;comment:'是否热卖商品'"`

	Name            string   `gorm:"type:varchar(50);not null"`
	GoodsSn         string   `gorm:"type:varchar(50);not null;comment:'商家的内部编号'"`
	ClickNum        int32    `gorm:"type:int;default:0;not null;comment:'点击数'"`
	SoldNum         int32    `gorm:"type:int;default:0;not null;comment:'销售量'"`
	FavNum          int32    `gorm:"type:int;default:0;not null;comment:'收藏数'"`
	MarketPrice     float32  `gorm:"not null;comment:'商品价格'"`
	ShopPrice       float32  `gorm:"not null;comment:'实际价格'"`
	GoodsBrief      string   `gorm:"type:varchar(100);not null;comment:'商品简介'"`
	Images          GormList `gorm:"type:varchar(1000);not null;comment:'商品图片'"`
	DescImages      GormList `gorm:"type:varchar(1000);not null;comment:'商品图片'"`
	GoodsFrontImage string   `gorm:"type:varchar(200);not null;comment:'商品展示图'"`
}

4 - 生成表结构与数据导入

  • 新建数据库:mxshop_goods_srv
    19、商品微服务-srv层实现_第3张图片
  • goods_srv/model/main/main.go:ip和数据库账号密码自行修改
package main

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
	"log"
	"nd/goods_srv/model"
	"os"
	"time"
)

func main() {
	dsn := "root:jiushi@tcp(192.168.124.51:3306)/mxshop_goods_srv?charset=utf8mb4&parseTime=True&loc=Local"

	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
		logger.Config{
			SlowThreshold: time.Second, // 慢 SQL 阈值
			LogLevel:      logger.Info, // Log level
			Colorful:      true,        // 禁用彩色打印
		},
	)

	// 全局模式
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true,
		},
		Logger: newLogger,
	})
	if err != nil {
		panic(err)
	}

	_ = db.AutoMigrate(&model.Category{},
		&model.Brands{}, &model.GoodsCategoryBrand{}, &model.Banner{}, &model.Goods{})
}

19、商品微服务-srv层实现_第4张图片


二、启动商品服务

1 - proto接口

  • goods_srv/proto/goods.proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = ".;proto";

service Goods{
  //商品接口
  rpc GoodsList(GoodsFilterRequest) returns(GoodsListResponse);
  //现在用户提交订单有多个商品,你得批量查询商品的信息吧
  rpc BatchGetGoods(BatchGoodsIdInfo) returns(GoodsListResponse); //批量获取商品信息
  rpc CreateGoods(CreateGoodsInfo) returns (GoodsInfoResponse);
  rpc DeleteGoods(DeleteGoodsInfo) returns (google.protobuf.Empty);
  rpc UpdateGoods(CreateGoodsInfo) returns (google.protobuf.Empty);
  rpc GetGoodsDetail(GoodInfoRequest) returns(GoodsInfoResponse);

  //商品分类
  rpc GetAllCategorysList(google.protobuf.Empty) returns(CategoryListResponse); //获取所有的分类
  //获取子分类
  rpc GetSubCategory(CategoryListRequest) returns(SubCategoryListResponse);
  rpc CreateCategory(CategoryInfoRequest) returns(CategoryInfoResponse); //新建分类信息
  rpc DeleteCategory(DeleteCategoryRequest) returns(google.protobuf.Empty); //删除分类
  rpc UpdateCategory(CategoryInfoRequest) returns(google.protobuf.Empty); //修改分类信息

  //品牌和轮播图
  rpc BrandList(BrandFilterRequest) returns(BrandListResponse); //
  rpc CreateBrand(BrandRequest) returns(BrandInfoResponse); //新建品牌信息
  rpc DeleteBrand(BrandRequest) returns(google.protobuf.Empty); //删除品牌
  rpc UpdateBrand(BrandRequest) returns(google.protobuf.Empty); //修改品牌信息

  //轮播图
  rpc BannerList(google.protobuf.Empty) returns(BannerListResponse); //获取轮播列表信息
  rpc CreateBanner(BannerRequest) returns(BannerResponse); //添加banner图
  rpc DeleteBanner(BannerRequest) returns(google.protobuf.Empty); //删除轮播图
  rpc UpdateBanner(BannerRequest) returns(google.protobuf.Empty); //修改轮播图

  //品牌分类
  rpc CategoryBrandList(CategoryBrandFilterRequest) returns(CategoryBrandListResponse); //获取轮播列表信息
  //通过category获取brands
  rpc GetCategoryBrandList(CategoryInfoRequest) returns(BrandListResponse);
  rpc CreateCategoryBrand(CategoryBrandRequest) returns(CategoryBrandResponse); //添加banner图
  rpc DeleteCategoryBrand(CategoryBrandRequest) returns(google.protobuf.Empty); //删除轮播图
  rpc UpdateCategoryBrand(CategoryBrandRequest) returns(google.protobuf.Empty); //修改轮播图
}

message CategoryListRequest {
  int32 id = 1;
  int32 level = 2;
}

message CategoryInfoRequest {
  int32 id = 1;
  string name = 2;
  int32 parentCategory = 3;
  int32 level = 4;
  bool isTab = 5;
}

message DeleteCategoryRequest {
  int32 id = 1;
}

message QueryCategoryRequest {
  int32 id = 1;
  string name = 2;
}

message CategoryInfoResponse {
  int32 id = 1;
  string name = 2;
  int32 parentCategory = 3;
  int32 level = 4;
  bool isTab = 5;
}

message CategoryListResponse {
  int32 total = 1;
  repeated CategoryInfoResponse data = 2;
  string jsonData = 3;
}

message SubCategoryListResponse {
  int32 total = 1;
  CategoryInfoResponse info = 2;
  repeated CategoryInfoResponse subCategorys = 3;
}

message CategoryBrandFilterRequest  {
  int32 pages = 1;
  int32 pagePerNums = 2;
}

message FilterRequest  {
  int32 pages = 1;
  int32 pagePerNums = 2;
}

message CategoryBrandRequest{
  int32 id = 1;
  int32 categoryId = 2;
  int32 brandId = 3;
}
message CategoryBrandResponse{
  int32 id = 1;
  BrandInfoResponse brand = 2;
  CategoryInfoResponse category = 3;
}

message BannerRequest {
  int32 id = 1;
  int32 index = 2;
  string image = 3;
  string url = 4;
}

message BannerResponse {
  int32 id = 1;
  int32 index = 2;
  string image = 3;
  string url = 4;
}

message BrandFilterRequest {
  int32 pages = 1;
  int32 pagePerNums = 2;
}

message BrandRequest {
  int32 id = 1;
  string name = 2;
  string logo = 3;
}

message BrandInfoResponse {
  int32 id = 1;
  string name = 2;
  string logo = 3;
}

message BrandListResponse {
  int32 total = 1;
  repeated BrandInfoResponse data = 2;
}

message BannerListResponse {
  int32 total = 1;
  repeated BannerResponse data = 2;
}

message CategoryBrandListResponse {
  int32 total = 1;
  repeated CategoryBrandResponse data = 2;
}



message BatchGoodsIdInfo {
  repeated int32 id = 1;
}


message DeleteGoodsInfo {
  int32 id = 1;
}

message CategoryBriefInfoResponse {
  int32 id = 1;
  string name = 2;
}

message CategoryFilterRequest {
  int32 id = 1;
  bool  isTab = 2;
}

message GoodInfoRequest {
  int32 id = 1;
}

message CreateGoodsInfo {
  int32 id = 1;
  string name = 2;
  string goodsSn = 3;
  int32 stocks = 7; //库存,
  float marketPrice = 8;
  float shopPrice = 9;
  string goodsBrief = 10;
  string goodsDesc = 11;
  bool shipFree = 12;
  repeated string images = 13;
  repeated string descImages = 14;
  string goodsFrontImage = 15;
  bool isNew = 16;
  bool isHot = 17;
  bool onSale = 18;
  int32 categoryId = 19;
  int32 brandId = 20;
}

message GoodsReduceRequest {
  int32 GoodsId = 1;
  int32 nums = 2;
}

message BatchCategoryInfoRequest {
  repeated int32 id = 1;
  int32 goodsNums = 2;
  int32 brandNums = 3;
}

message GoodsFilterRequest  {
  int32 priceMin = 1;
  int32 priceMax = 2;
  bool  isHot = 3;
  bool  isNew = 4;
  bool  isTab = 5;
  int32 topCategory = 6;
  int32 pages = 7;
  int32 pagePerNums = 8;
  string keyWords = 9;
  int32 brand = 10;
}


message GoodsInfoResponse {
  int32 id = 1;
  int32 categoryId = 2;
  string name = 3;
  string goodsSn = 4;
  int32 clickNum = 5;
  int32 soldNum = 6;
  int32 favNum = 7;
  float marketPrice = 9;
  float shopPrice = 10;
  string goodsBrief = 11;
  string goodsDesc = 12;
  bool shipFree = 13;
  repeated string images = 14;
  repeated string descImages = 15;
  string goodsFrontImage = 16;
  bool isNew = 17;
  bool isHot = 18;
  bool onSale = 19;
  int64 addTime = 20;
  CategoryBriefInfoResponse category = 21;
  BrandInfoResponse brand = 22;
}

message GoodsListResponse {
  int32 total = 1;
  repeated GoodsInfoResponse data = 2;
}
  • 生成pb文件protoc --go_out=. --go_opt=paths=import --go-grpc_out=. --go-grpc_opt=paths=import *.proto
    19、商品微服务-srv层实现_第5张图片

2 - handle中实现接口

  • goods_srv/handler/handle_goods.go这里我们可以先不用实现,使用proto.UnimplementedGoodsServer
package handler

import (
	"context"
	"google.golang.org/protobuf/types/known/emptypb"
	"nd/goods_srv/proto"
)

type GoodsServer struct {
	proto.UnimplementedGoodsServer
}

3 - nacos配置

  • 添加命名空间goods
    19、商品微服务-srv层实现_第6张图片
  • 克隆之前user_srv的配置到goods_srv中
    19、商品微服务-srv层实现_第7张图片
  • 编辑goods_srv.json的配置:将Tags配置也放入,以及本机的Host也加入配置

19、商品微服务-srv层实现_第8张图片

4 - 修改yaml配置

  • 修改命名空间id与dataid
//goods_srv/config_debug.yaml
host: '192.168.124.51'
port: 8848
namespace: 'b2f00e3b-4e22-4e87-8129-9c02b5f1220d'
user: 'nacos'
password: 'nacos'
dataid: 'goods_srv.json'
group: 'dev'

//goods_srv/config_pro.yaml
host: '192.168.124.51'
port: 8848
namespace: 'b2f00e3b-4e22-4e87-8129-9c02b5f1220d'
user: 'nacos'
password: 'nacos'
dataid: 'goods_srv.json'
group: 'pro'

5 - 读取Tags和本机host

  • goods_srv/config/config.go:ServerConfig中添加Tags
package config

type MysqlConfig struct {
	Host     string `mapstructure:"host" json:"host"`
	Port     int    `mapstructure:"port" json:"port"`
	Name     string `mapstructure:"db" json:"db"`
	User     string `mapstructure:"user" json:"user"`
	Password string `mapstructure:"password" json:"password"`
}

type ConsulConfig struct {
	Host string `mapstructure:"host" json:"host"`
	Port int    `mapstructure:"port" json:"port"`
}

type ServerConfig struct {
	Name       string       `mapstructure:"name" json:"name"`
	Tags       []string     `mapstructure:"tags" json:"tags"`
	MysqlInfo  MysqlConfig  `mapstructure:"mysql" json:"mysql"`
	ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"`
}

type NacosConfig struct {
	Host      string `mapstructure:"host"`
	Port      uint64 `mapstructure:"port"`
	Namespace string `mapstructure:"namespace"`
	User      string `mapstructure:"user"`
	Password  string `mapstructure:"password"`
	DataId    string `mapstructure:"dataid"`
	Group     string `mapstructure:"group"`
}

  • goods_srv/main.go
    • 注册时使用:registration.Tags = global.ServerConfig.Tags
    • 健康检查与服务注册的host使用:global.ServerConfig.Host
package main

import (
	"flag"
	"fmt"
	"github.com/hashicorp/consul/api"
	"github.com/satori/go.uuid"
	"go.uber.org/zap"
	"google.golang.org/grpc/health"
	"google.golang.org/grpc/health/grpc_health_v1"
	"nd/goods_srv/global"
	"nd/goods_srv/handler"
	"nd/goods_srv/initialize"
	"nd/goods_srv/proto"
	"nd/goods_srv/utils"
	"net"
	"os"
	"os/signal"
	"syscall"

	"google.golang.org/grpc"
)

func main() {
	IP := flag.String("ip", "0.0.0.0", "ip地址")
	Port := flag.Int("port", 0, "端口号") // 这个修改为0,如果我们从命令行带参数启动的话就不会为0

	//初始化
	initialize.InitLogger()
	initialize.InitConfig()
	initialize.InitDB()
	zap.S().Info(global.ServerConfig)

	flag.Parse()
	zap.S().Info("ip: ", *IP)
	if *Port == 0 {
		*Port, _ = utils.GetFreePort()
	}
	zap.S().Info("port: ", *Port)

	server := grpc.NewServer()
	proto.RegisterGoodsServer(server, &handler.GoodsServer{})
	lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
	if err != nil {
		panic("failed to listen:" + err.Error())
	}

	//注册服务健康检查
	grpc_health_v1.RegisterHealthServer(server, health.NewServer())

	//服务注册
	cfg := api.DefaultConfig()
	cfg.Address = fmt.Sprintf("%s:%d", global.ServerConfig.ConsulInfo.Host,
		global.ServerConfig.ConsulInfo.Port)

	client, err := api.NewClient(cfg)
	if err != nil {
		panic(err)
	}
	//生成对应的检查对象
	check := &api.AgentServiceCheck{
		GRPC:                           fmt.Sprintf("%s:%d", global.ServerConfig.Host, *Port),
		Timeout:                        "5s",
		Interval:                       "5s",
		DeregisterCriticalServiceAfter: "15s",
	}

	//生成注册对象
	registration := new(api.AgentServiceRegistration)
	registration.Name = global.ServerConfig.Name
	serviceID := fmt.Sprintf("%s", uuid.NewV4())
	registration.ID = serviceID
	registration.Port = *Port
	registration.Tags = global.ServerConfig.Tags
	registration.Address = global.ServerConfig.Host
	registration.Check = check

	err = client.Agent().ServiceRegister(registration)
	if err != nil {
		panic(err)
	}

	go func() {
		err = server.Serve(lis)
		if err != nil {
			panic("failed to start grpc:" + err.Error())
		}
	}()

	//接收终止信号
	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	if err = client.Agent().ServiceDeregister(serviceID); err != nil {
		zap.S().Info("注销失败")
	}
	zap.S().Info("注销成功")
}

19、商品微服务-srv层实现_第9张图片


三、品牌、商品分类、轮播接口

1 - 接口测试

  • goods_srv/main.go:为了让测试的时候端口不会变化,我们在main中修改port为50058
func main() {
	IP := flag.String("ip", "0.0.0.0", "ip地址")
	Port := flag.Int("port", 50058, "端口号")
	//...省略
  • goods_srv/tests/test_brands.go:添加测试方法
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc/credentials/insecure"

	"google.golang.org/grpc"

	"nd/goods_srv/proto"
)

var brandClient proto.GoodsClient
var conn *grpc.ClientConn

func TestGetBrandList() {
	rsp, err := brandClient.BrandList(context.Background(), &proto.BrandFilterRequest{})
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.Total)
	for _, brand := range rsp.Data {
		fmt.Println(brand.Name)
	}
}

func Init() {
	var err error
	conn, err = grpc.Dial("127.0.0.1:50058", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		panic(err)
	}
	brandClient = proto.NewGoodsClient(conn)
}

func main() {
	Init()
	TestGetBrandList()

	conn.Close()
}

  • 因为之前使用的是proto.UnimplementedGoodsServer:我们期望的返回是rpc接口未定义
    19、商品微服务-srv层实现_第10张图片

2 - 品牌列表查询实现

  • goods_srv/handler/handle_base.go:添加分页的逻辑
package handler

import "gorm.io/gorm"

func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		if page == 0 {
			page = 1
		}

		switch {
		case pageSize > 100:
			pageSize = 100
		case pageSize <= 0:
			pageSize = 10
		}

		offset := (page - 1) * pageSize
		return db.Offset(offset).Limit(pageSize)
	}
}

  • goods_srv/handler/handle_brands.go:实现BrandList接口
package handler

import (
	"context"
	"nd/goods_srv/global"
	"nd/goods_srv/model"
	"nd/goods_srv/proto"
)

// BrandList 品牌和轮播图
func (s *GoodsServer) BrandList(ctx context.Context, req *proto.BrandFilterRequest) (*proto.BrandListResponse, error) {
	brandListResponse := proto.BrandListResponse{}

	var brands []model.Brands
	result := global.DB.Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&brands)
	if result.Error != nil {
		return nil, result.Error
	}

	var total int64
	global.DB.Model(&model.Brands{}).Count(&total)
	brandListResponse.Total = int32(total)

	var brandResponses []*proto.BrandInfoResponse
	for _, brand := range brands {
		brandResponses = append(brandResponses, &proto.BrandInfoResponse{
			Id:   brand.ID,
			Name: brand.Name,
			Logo: brand.Logo,
		})
	}
	brandListResponse.Data = brandResponses
	return &brandListResponse, nil
}

//CreateBrand(ctx context.Context, in *BrandRequest, opts ...grpc.CallOption) (*BrandInfoResponse, error)
//DeleteBrand(ctx context.Context, in *BrandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
//UpdateBrand(ctx context.Context, in *BrandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)

  • goods_srv/tests/test_brands.go:测试接口实现
package main

import (
	"context"
	"fmt"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	"nd/goods_srv/proto"
)

var brandClient proto.GoodsClient
var conn *grpc.ClientConn

func TestGetBrandList() {
	rsp, err := brandClient.BrandList(context.Background(), &proto.BrandFilterRequest{})
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.Total)
	for _, brand := range rsp.Data {
		fmt.Println(brand.Name)
	}
}

func Init() {
	var err error
	conn, err = grpc.Dial("127.0.0.1:50058", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		panic(err)
	}
	brandClient = proto.NewGoodsClient(conn)
}

func main() {
	Init()
	TestGetBrandList()

	conn.Close()
}

19、商品微服务-srv层实现_第11张图片

3 - 品牌新建、删除、更新接口

  • goods_srv/handler/handle_brands.go:实现CreateBrand、DeleteBrand、UpdateBrand
package handler

import (
	"context"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"nd/goods_srv/global"
	"nd/goods_srv/model"
	"nd/goods_srv/proto"
)

// BrandList 品牌和轮播图
func (s *GoodsServer) BrandList(ctx context.Context, req *proto.BrandFilterRequest) (*proto.BrandListResponse, error) {
	brandListResponse := proto.BrandListResponse{}

	var brands []model.Brands
	result := global.DB.Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&brands)
	if result.Error != nil {
		return nil, result.Error
	}

	var total int64
	global.DB.Model(&model.Brands{}).Count(&total)
	brandListResponse.Total = int32(total)

	var brandResponses []*proto.BrandInfoResponse
	for _, brand := range brands {
		brandResponses = append(brandResponses, &proto.BrandInfoResponse{
			Id:   brand.ID,
			Name: brand.Name,
			Logo: brand.Logo,
		})
	}
	brandListResponse.Data = brandResponses
	return &brandListResponse, nil
}

func (s *GoodsServer) CreateBrand(ctx context.Context, req *proto.BrandRequest) (*proto.BrandInfoResponse, error) {
	//新建品牌 需要先查询品牌是否已存在
	if result := global.DB.Where("name=?", req.Name).First(&model.Brands{}); result.RowsAffected == 1 {
		return nil, status.Errorf(codes.InvalidArgument, "品牌已存在")
	}
	brand := &model.Brands{
		Name: req.Name,
		Logo: req.Logo,
	}
	global.DB.Save(brand)
	return &proto.BrandInfoResponse{Id: brand.ID}, nil
}

func (s *GoodsServer) DeleteBrand(ctx context.Context, req *proto.BrandRequest) (*emptypb.Empty, error) {
	if result := global.DB.Delete(&model.Brands{}, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "品牌不存在")
	}
	return &emptypb.Empty{}, nil
}

func (s *GoodsServer) UpdateBrand(ctx context.Context, req *proto.BrandRequest) (*emptypb.Empty, error) {
	brands := model.Brands{}
	if result := global.DB.First(&brands); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "品牌不存在")
	}
	if req.Name != "" {
		brands.Name = req.Name
	}
	if req.Logo != "" {
		brands.Logo = req.Logo
	}
	global.DB.Save(&brands)
	return &emptypb.Empty{}, nil
}

4 - 轮播图的CRUD

  • goods_srv/handler/handle_banner.go
package handler

import (
	"context"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"google.golang.org/protobuf/types/known/emptypb"

	"nd/goods_srv/global"
	"nd/goods_srv/model"
	"nd/goods_srv/proto"
)

// BannerList 轮播图,不需要分页
func (s *GoodsServer) BannerList(ctx context.Context, req *emptypb.Empty) (*proto.BannerListResponse, error) {
	bannerListResponse := proto.BannerListResponse{}

	var banners []model.Banner
	result := global.DB.Find(&banners)
	bannerListResponse.Total = int32(result.RowsAffected)

	var bannerReponses []*proto.BannerResponse
	for _, banner := range banners {
		bannerReponses = append(bannerReponses, &proto.BannerResponse{
			Id:    banner.ID,
			Image: banner.Image,
			Index: banner.Index,
			Url:   banner.Url,
		})
	}

	bannerListResponse.Data = bannerReponses

	return &bannerListResponse, nil
}

func (s *GoodsServer) CreateBanner(ctx context.Context, req *proto.BannerRequest) (*proto.BannerResponse, error) {
	banner := model.Banner{}
	banner.Image = req.Image
	banner.Index = req.Index
	banner.Url = req.Url

	global.DB.Save(&banner)
	return &proto.BannerResponse{Id: banner.ID}, nil
}

func (s *GoodsServer) DeleteBanner(ctx context.Context, req *proto.BannerRequest) (*emptypb.Empty, error) {
	if result := global.DB.Delete(&model.Banner{}, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "轮播图不存在")
	}
	return &emptypb.Empty{}, nil
}

func (s *GoodsServer) UpdateBanner(ctx context.Context, req *proto.BannerRequest) (*emptypb.Empty, error) {
	var banner model.Banner

	if result := global.DB.First(&banner, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "轮播图不存在")
	}

	if req.Url != "" {
		banner.Url = req.Url
	}
	if req.Image != "" {
		banner.Image = req.Image
	}
	if req.Index != 0 {
		banner.Index = req.Index
	}

	global.DB.Save(&banner)
	return &emptypb.Empty{}, nil
}

5 - 商品分类列表接口

  • goods_srv/handler/handle_category.go
// GetAllCategorysList 商品分类
func (s *GoodsServer) GetAllCategorysList(context.Context, *emptypb.Empty) (*proto.CategoryListResponse, error) {
	/* 获取分类的时候构造好返回的json对象,供前端使用
		所以在CategoryListResponse结构中专门定义了JsonData用于返回给前端使用
		为什么在srv层来实现,因为srv层有gorm,而web层没有gorm并不与数据库交互
		如果在web层实现,没有gorm处理起来会比较复杂,所以建议放在srv层来实现
	[
		{
			"id":xxx,
			"name":"",
			"level":1,
			"is_tab":false,
			"parent":13xxx,
			"sub_category":[
				"id":xxx,
				"name":"",
				"level":1,
				"is_tab":false,
				"sub_category":[]
			]
		}
	]
	*/
	var categorys []model.Category
	// SubCategory      []*Category `gorm:"foreignKey:ParentCategoryID;references:ID" json:"sub_category"`
	// 配置指明了外键后,可以使用Preload预加载,来把品牌的子分类也取出来
	global.DB.Where(&model.Category{Level: 1}).Preload("SubCategory.SubCategory").Find(&categorys)
	b, _ := json.Marshal(&categorys)
	return &proto.CategoryListResponse{JsonData: string(b)}, nil
}
  • goods_srv/tests/category/test_category.go:添加测试方法
package main

import (
	"context"
	"fmt"
	"github.com/golang/protobuf/ptypes/empty"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"nd/goods_srv/tests"

	"nd/goods_srv/proto"
)

var brandClient proto.GoodsClient
var conn *grpc.ClientConn

func TestGetCategoryList() {
	rsp, err := brandClient.GetAllCategorysList(context.Background(), &empty.Empty{})
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.Total)
	fmt.Println(rsp.JsonData)
}

func Init() {
	var err error
	conn, err = grpc.Dial(tests.TargetAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		panic(err)
	}
	brandClient = proto.NewGoodsClient(conn)
}

func main() {
	Init()
	TestGetCategoryList()
	//TestGetSubCategoryList()

	conn.Close()
}

19、商品微服务-srv层实现_第12张图片

6 - 商品分类子分类接口

  • goods_srv/handler/handle_category.go
// GetSubCategory 获取子分类
func (s *GoodsServer) GetSubCategory(ctx context.Context, req *proto.CategoryListRequest) (*proto.SubCategoryListResponse, error) {
	categoryListResponse := proto.SubCategoryListResponse{}

	var category model.Category
	if result := global.DB.First(&category, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品分类不存在")
	}

	categoryListResponse.Info = &proto.CategoryInfoResponse{
		Id:             category.ID,
		Name:           category.Name,
		Level:          category.Level,
		IsTab:          category.IsTab,
		ParentCategory: category.ParentCategoryID,
	}

	var subCategorys []model.Category
	var subCategoryResponse []*proto.CategoryInfoResponse
	//preloads := "SubCategory"
	//if category.Level == 1 {
	//	preloads = "SubCategory.SubCategory"
	//}
	global.DB.Where(&model.Category{ParentCategoryID: req.Id}).Find(&subCategorys)
	for _, subCategory := range subCategorys {
		subCategoryResponse = append(subCategoryResponse, &proto.CategoryInfoResponse{
			Id:             subCategory.ID,
			Name:           subCategory.Name,
			Level:          subCategory.Level,
			IsTab:          subCategory.IsTab,
			ParentCategory: subCategory.ParentCategoryID,
		})
	}

	categoryListResponse.SubCategorys = subCategoryResponse
	return &categoryListResponse, nil
}
  • goods_srv/tests/category/test_category.go
func TestGetSubCategoryList() {
	rsp, err := brandClient.GetSubCategory(context.Background(), &proto.CategoryListRequest{
		Id: 1001,
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.SubCategorys)
}

19、商品微服务-srv层实现_第13张图片

7 - 商品新建、删除和更新接口

  • goods_srv/handler/handle_category.go
func (s *GoodsServer) CreateCategory(ctx context.Context, req *proto.CategoryInfoRequest) (*proto.CategoryInfoResponse, error) {
	category := model.Category{}
	cMap := map[string]interface{}{}
	cMap["name"] = req.Name
	cMap["level"] = req.Level
	cMap["is_tab"] = req.IsTab
	if req.Level != 1 {
		//去查询父类目是否存在
		cMap["parent_category_id"] = req.ParentCategory
	}
	tx := global.DB.Model(&model.Category{}).Create(cMap)
	fmt.Println(tx)
	return &proto.CategoryInfoResponse{Id: int32(category.ID)}, nil
}

func (s *GoodsServer) DeleteCategory(ctx context.Context, req *proto.DeleteCategoryRequest) (*emptypb.Empty, error) {
	if result := global.DB.Delete(&model.Category{}, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品分类不存在")
	}
	return &emptypb.Empty{}, nil
}

func (s *GoodsServer) UpdateCategory(ctx context.Context, req *proto.CategoryInfoRequest) (*emptypb.Empty, error) {
	var category model.Category
	if result := global.DB.First(&category, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品分类不存在")
	}
	if req.Name != "" {
		category.Name = req.Name
	}
	if req.ParentCategory != 0 {
		category.ParentCategoryID = req.ParentCategory
	}
	if req.Level != 0 {
		category.Level = req.Level
	}
	if req.IsTab {
		category.IsTab = req.IsTab
	}
	global.DB.Save(&category)
	return &emptypb.Empty{}, nil
}

8 - 品牌分类相关接口

  • goods_srv/handler/handle_category_brand.go
package handler

import (
	"context"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"

	"nd/goods_srv/global"
	"nd/goods_srv/model"
	"nd/goods_srv/proto"
)

// CategoryBrandList 品牌分类
func (s *GoodsServer) CategoryBrandList(ctx context.Context, req *proto.CategoryBrandFilterRequest) (*proto.CategoryBrandListResponse, error) {
	var categoryBrands []model.GoodsCategoryBrand
	categoryBrandListResponse := proto.CategoryBrandListResponse{}

	var total int64
	global.DB.Model(&model.GoodsCategoryBrand{}).Count(&total)
	categoryBrandListResponse.Total = int32(total)

	global.DB.Preload("Category").Preload("Brands").Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&categoryBrands)

	var categoryResponses []*proto.CategoryBrandResponse
	for _, categoryBrand := range categoryBrands {
		categoryResponses = append(categoryResponses, &proto.CategoryBrandResponse{
			Category: &proto.CategoryInfoResponse{
				Id:             categoryBrand.Category.ID,
				Name:           categoryBrand.Category.Name,
				Level:          categoryBrand.Category.Level,
				IsTab:          categoryBrand.Category.IsTab,
				ParentCategory: categoryBrand.Category.ParentCategoryID,
			},
			Brand: &proto.BrandInfoResponse{
				Id:   categoryBrand.Brands.ID,
				Name: categoryBrand.Brands.Name,
				Logo: categoryBrand.Brands.Logo,
			},
		})
	}

	categoryBrandListResponse.Data = categoryResponses
	return &categoryBrandListResponse, nil
}

func (s *GoodsServer) GetCategoryBrandList(ctx context.Context, req *proto.CategoryInfoRequest) (*proto.BrandListResponse, error) {
	brandListResponse := proto.BrandListResponse{}

	var category model.Category
	if result := global.DB.Find(&category, req.Id).First(&category); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "商品分类不存在")
	}

	var categoryBrands []model.GoodsCategoryBrand
	if result := global.DB.Preload("Brands").Where(&model.GoodsCategoryBrand{CategoryID: req.Id}).Find(&categoryBrands); result.RowsAffected > 0 {
		brandListResponse.Total = int32(result.RowsAffected)
	}

	var brandInfoResponses []*proto.BrandInfoResponse
	for _, categoryBrand := range categoryBrands {
		brandInfoResponses = append(brandInfoResponses, &proto.BrandInfoResponse{
			Id:   categoryBrand.Brands.ID,
			Name: categoryBrand.Brands.Name,
			Logo: categoryBrand.Brands.Logo,
		})
	}

	brandListResponse.Data = brandInfoResponses

	return &brandListResponse, nil
}

func (s *GoodsServer) CreateCategoryBrand(ctx context.Context, req *proto.CategoryBrandRequest) (*proto.CategoryBrandResponse, error) {
	var category model.Category
	if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "商品分类不存在")
	}

	var brand model.Brands
	if result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "品牌不存在")
	}

	categoryBrand := model.GoodsCategoryBrand{
		CategoryID: req.CategoryId,
		BrandsID:   req.BrandId,
	}

	global.DB.Save(&categoryBrand)
	return &proto.CategoryBrandResponse{Id: categoryBrand.ID}, nil
}

func (s *GoodsServer) DeleteCategoryBrand(ctx context.Context, req *proto.CategoryBrandRequest) (*emptypb.Empty, error) {
	if result := global.DB.Delete(&model.GoodsCategoryBrand{}, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "品牌分类不存在")
	}
	return &emptypb.Empty{}, nil
}

func (s *GoodsServer) UpdateCategoryBrand(ctx context.Context, req *proto.CategoryBrandRequest) (*emptypb.Empty, error) {
	var categoryBrand model.GoodsCategoryBrand

	if result := global.DB.First(&categoryBrand, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "品牌分类不存在")
	}

	var category model.Category
	if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "商品分类不存在")
	}

	var brand model.Brands
	if result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "品牌不存在")
	}

	categoryBrand.CategoryID = req.CategoryId
	categoryBrand.BrandsID = req.BrandId

	global.DB.Save(&categoryBrand)

	return &emptypb.Empty{}, nil
}

  • goods_srv/tests/category_brand/test_category_brand.go:测试接口
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"nd/goods_srv/proto"
	"nd/goods_srv/tests"
)

var brandClient proto.GoodsClient
var conn *grpc.ClientConn

func TestGetCategoryBrandList() {
	rsp, err := brandClient.CategoryBrandList(context.Background(), &proto.CategoryBrandFilterRequest{})
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.Total)
	fmt.Println(rsp.Data)
}

func Init() {
	var err error
	conn, err = grpc.Dial(tests.TargetAddr, grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	brandClient = proto.NewGoodsClient(conn)
}

func main() {
	Init()
	TestGetCategoryBrandList()

	conn.Close()
}

19、商品微服务-srv层实现_第14张图片


四、商品接口

1 - 商品列表接口实现

  • goods_srv/handler/handle_goods.go:根据条件筛选查询商品
package handler

import (
	"context"
	"fmt"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"nd/goods_srv/global"
	"nd/goods_srv/model"
	"nd/goods_srv/proto"
)

type GoodsServer struct {
	proto.UnimplementedGoodsServer
}

func ModelToResponse(goods model.Goods) proto.GoodsInfoResponse {
	return proto.GoodsInfoResponse{
		Id:              goods.ID,
		CategoryId:      goods.CategoryID,
		Name:            goods.Name,
		GoodsSn:         goods.GoodsSn,
		ClickNum:        goods.ClickNum,
		SoldNum:         goods.SoldNum,
		FavNum:          goods.FavNum,
		MarketPrice:     goods.MarketPrice,
		ShopPrice:       goods.ShopPrice,
		GoodsBrief:      goods.GoodsBrief,
		ShipFree:        goods.ShipFree,
		GoodsFrontImage: goods.GoodsFrontImage,
		IsNew:           goods.IsNew,
		IsHot:           goods.IsHot,
		OnSale:          goods.OnSale,
		DescImages:      goods.DescImages,
		Images:          goods.Images,
		Category: &proto.CategoryBriefInfoResponse{
			Id:   goods.Category.ID,
			Name: goods.Category.Name,
		},
		Brand: &proto.BrandInfoResponse{
			Id:   goods.Brands.ID,
			Name: goods.Brands.Name,
			Logo: goods.Brands.Logo,
		},
	}
}

func (s *GoodsServer) GoodsList(ctx context.Context, req *proto.GoodsFilterRequest) (*proto.GoodsListResponse, error) {
	//关键词搜索、查询新品、查询热门商品、通过价格区间筛选, 通过商品分类筛选
	goodsListResponse := &proto.GoodsListResponse{}
	var goods []model.Goods
	// 不要修改全局的DB,而是使用局部的localDB
	localDB := global.DB.Model(model.Goods{})
	// 这里开始拼接查询语句
	if req.KeyWords != "" {
		localDB = localDB.Where("name LIKE ?", "%"+req.KeyWords+"%")
	}
	if req.IsHot {
		localDB = localDB.Where(model.Goods{IsHot: true})
	}
	if req.IsNew {
		localDB = localDB.Where(model.Goods{IsNew: true})
	}
	if req.PriceMin > 0 {
		localDB = localDB.Where("shop_price >= ?", req.PriceMin)
	}
	if req.PriceMax > 0 {
		localDB = localDB.Where("shop_price <= ?", req.PriceMax)
	}
	if req.Brand > 0 {
		localDB = localDB.Where("brand_id = ?", req.Brand)
	}

	//通过category去查询商品
	// 子查询 嵌套 子查询
	// SELECT * FROM goods WHERE category_id IN(SELECT id FROM category WHERE parent_category_id IN (SELECT id FROM category WHERE parent_category_id=1001))
	var subQuery string
	if req.TopCategory > 0 {
		var category model.Category
		if result := global.DB.First(&category, req.TopCategory); result.RowsAffected == 0 {
			return nil, status.Errorf(codes.NotFound, "商品分类不存在")
		}
		if category.Level == 1 {
			subQuery = fmt.Sprintf("select id from category where parent_category_id in (select id from category WHERE parent_category_id=%d)", req.TopCategory)
		} else if category.Level == 2 {
			subQuery = fmt.Sprintf("select id from category WHERE parent_category_id=%d", req.TopCategory)
		} else if category.Level == 3 {
			subQuery = fmt.Sprintf("select id from category WHERE id=%d", req.TopCategory)
		}
		localDB = localDB.Where(fmt.Sprintf("category_id in (%s)", subQuery))
	}

	var count int64
	localDB.Count(&count)
	goodsListResponse.Total = int32(count) //这个total的总数量一定要在分页之前完成,否则就是分页的数量了

	// 有外键需要使用 Preload
	result := localDB.Preload("Category").Preload("Brands").Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&goods)
	if result.Error != nil {
		return nil, result.Error
	}

	for _, good := range goods {
		goodsInfoResponse := ModelToResponse(good)
		goodsListResponse.Data = append(goodsListResponse.Data, &goodsInfoResponse)
	}
	return goodsListResponse, nil
}

  • goods_srv/tests/goods/test_goods.go:goods接口测试
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"nd/goods_srv/proto"
	"nd/goods_srv/tests"
)

var brandClient proto.GoodsClient
var conn *grpc.ClientConn

func Init() {
	var err error
	conn, err = grpc.Dial(tests.TargetAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		panic(err)
	}
	brandClient = proto.NewGoodsClient(conn)
}

func TestGetGoodsList() {
	rsp, err := brandClient.GoodsList(context.Background(), &proto.GoodsFilterRequest{
		//TopCategory: 2007,
		PriceMin: 10,
		KeyWords: "水",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.Total)
	for _, good := range rsp.Data {
		fmt.Println(good.Name, good.ShopPrice)
	}
}

func main() {
	Init()
	TestGetGoodsList()
	conn.Close()
}

  • 为了方便测试,我们先注释掉Images和DescImages
//goods_srv/handler/handle_goods.go

		IsHot:           goods.IsHot,
		OnSale:          goods.OnSale,
		//DescImages:      goods.DescImages,
		//Images:          goods.Images,
		Category: &proto.CategoryBriefInfoResponse{
			Id:   goods.Category.ID,
			Name: goods.Category.Name,
		},

//goods_srv/model/goods.go

	GoodsBrief  string  `gorm:"type:varchar(100);not null;comment:'商品简介'"`
	//Images          GormList `gorm:"type:varchar(1000);not null;comment:'商品图片'"`
	//DescImages      GormList `gorm:"type:varchar(1000);not null;comment:'商品图片'"`
	GoodsFrontImage string `gorm:"type:varchar(200);not null;comment:'商品展示图'"`

19、商品微服务-srv层实现_第15张图片

2 - 批量获取商品、获取商品详情

  • goods_srv/handler/handle_goods.go
// BatchGetGoods 现在用户提交订单有多个商品,你得批量查询商品的信息吧
func (s *GoodsServer) BatchGetGoods(ctx context.Context, req *proto.BatchGoodsIdInfo) (*proto.GoodsListResponse, error) {
	goodsListResponse := &proto.GoodsListResponse{}
	var goods []model.Goods

	//调用where并不会真正执行sql 只是用来生成sql的 当调用find, first才会去执行sql,
	result := global.DB.Where(req.Id).Find(&goods)
	for _, good := range goods {
		goodsInfoResponse := ModelToResponse(good)
		goodsListResponse.Data = append(goodsListResponse.Data, &goodsInfoResponse)
	}
	goodsListResponse.Total = int32(result.RowsAffected)
	return goodsListResponse, nil
}

func (s *GoodsServer) GetGoodsDetail(ctx context.Context, req *proto.GoodInfoRequest) (*proto.GoodsInfoResponse, error) {
	var goods model.Goods

	if result := global.DB.Preload("Category").Preload("Brands").First(&goods, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品不存在")
	}
	goodsInfoResponse := ModelToResponse(goods)
	return &goodsInfoResponse, nil
}
  • goods_srv/tests/goods/test_goods.go
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"nd/goods_srv/proto"
	"nd/goods_srv/tests"
)

var brandClient proto.GoodsClient
var conn *grpc.ClientConn

func Init() {
	var err error
	conn, err = grpc.Dial(tests.TargetAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		panic(err)
	}
	brandClient = proto.NewGoodsClient(conn)
}

func TestGetGoodsList() {
	rsp, err := brandClient.GoodsList(context.Background(), &proto.GoodsFilterRequest{
		//TopCategory: 2007,
		PriceMin: 10,
		KeyWords: "水",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.Total)
	for _, good := range rsp.Data {
		fmt.Println(good.Name, good.ShopPrice)
	}
}

func TestBatchGetGoods() {
	rsp, err := brandClient.BatchGetGoods(context.Background(), &proto.BatchGoodsIdInfo{
		Id: []int32{29, 30, 31},
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.Total)
	for _, good := range rsp.Data {
		fmt.Println(good.Name, good.ShopPrice)
	}
}

func TestGetGoodsDetail() {
	rsp, err := brandClient.GetGoodsDetail(context.Background(), &proto.GoodInfoRequest{
		Id: 28,
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.Name)
	fmt.Println(rsp.ShopPrice)
	fmt.Println(rsp.MarketPrice)
}

func main() {
	Init()
	//TestGetGoodsList()
	TestBatchGetGoods()
	fmt.Println("----------------------------")
	TestGetGoodsDetail()
	conn.Close()
}

19、商品微服务-srv层实现_第16张图片

3 - 新增、修改、更新商品

  • goods_srv/handler/handle_goods.go
func (s *GoodsServer) CreateGoods(ctx context.Context, req *proto.CreateGoodsInfo) (*proto.GoodsInfoResponse, error) {
	var category model.Category
	if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "商品分类不存在")
	}

	var brand model.Brands
	if result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "品牌不存在")
	}
	//先检查redis中是否有这个token
	//防止同一个token的数据重复插入到数据库中,如果redis中没有这个token则放入redis
	//这里没有看到图片文件是如何上传, 在微服务中 普通的文件上传已经不再使用
	goods := model.Goods{
		Brands:      brand,
		BrandsID:    brand.ID,
		Category:    category,
		CategoryID:  category.ID,
		Name:        req.Name,
		GoodsSn:     req.GoodsSn,
		MarketPrice: req.MarketPrice,
		ShopPrice:   req.ShopPrice,
		GoodsBrief:  req.GoodsBrief,
		ShipFree:    req.ShipFree,
		//Images: req.Images,
		//DescImages: req.DescImages,
		GoodsFrontImage: req.GoodsFrontImage,
		IsNew:           req.IsNew,
		IsHot:           req.IsHot,
		OnSale:          req.OnSale,
	}
	//srv之间互相调用了
	tx := global.DB.Begin()
	result := tx.Save(&goods)
	if result.Error != nil {
		tx.Rollback()
		return nil, result.Error
	}
	tx.Commit()
	return &proto.GoodsInfoResponse{
		Id: goods.ID,
	}, nil
}

// DeleteGoods 逻辑删除
func (s *GoodsServer) DeleteGoods(ctx context.Context, req *proto.DeleteGoodsInfo) (*emptypb.Empty, error) {
	if result := global.DB.Delete(&model.Goods{BaseModel: model.BaseModel{ID: req.Id}}, req.Id); result.Error != nil {
		return nil, status.Errorf(codes.NotFound, "商品不存在")
	}
	return &emptypb.Empty{}, nil
}

func (s *GoodsServer) UpdateGoods(ctx context.Context, req *proto.CreateGoodsInfo) (*emptypb.Empty, error) {
	var goods model.Goods
	if result := global.DB.First(&goods, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品不存在")
	}
	var category model.Category
	if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "商品分类不存在")
	}
	var brand model.Brands
	if result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "品牌不存在")
	}
	goods.Brands = brand
	goods.BrandsID = brand.ID
	goods.Category = category
	goods.CategoryID = category.ID
	goods.Name = req.Name
	goods.GoodsSn = req.GoodsSn
	goods.MarketPrice = req.MarketPrice
	goods.ShopPrice = req.ShopPrice
	goods.GoodsBrief = req.GoodsBrief
	goods.ShipFree = req.ShipFree
	//goods.Images= req.Images
	//goods.DescImages= req.DescImages
	goods.GoodsFrontImage = req.GoodsFrontImage
	goods.IsNew = req.IsNew
	goods.IsHot = req.IsHot
	goods.OnSale = req.OnSale
	tx := global.DB.Begin()
	result := tx.Save(&goods)
	if result.Error != nil {
		tx.Rollback()
		return nil, result.Error
	}
	tx.Commit()
	return &emptypb.Empty{}, nil
}


五、完整源码

  • 完整源码下载:mxshop_srvsV8.2.rar
  • 源码说明:(nacos的ip配置自行修改,全局变量DEV_CONFIG设置:1=zsz,2=comp,3=home)
    • goods_srv/model/sql/mxshop_goods.sql:包含了建表语句
    • other_import/api.json:YApi的导入文件
    • other_import/nacos_config_export_user.zip:nacos的user配置集导入文件
    • other_import/nacos_config_export_goods.zip:nacos的goods配置集导入文件

你可能感兴趣的:(Go微服务实战-电商系统,微服务,架构,golang,consul)