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


    前言

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

    一、需求分析

    1 - 数据库实体分析

    在这里插入图片描述

    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)
    }
    
    
    • 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
    • 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:'商品展示图'"`
    }
    
    
    • 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

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

    • 新建数据库:mxshop_goods_srv
      在这里插入图片描述
    • 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{})
    }
    
    
    • 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

    在这里插入图片描述


    二、启动商品服务

    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;
    }
    
    • 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
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 生成pb文件protoc --go_out=. --go_opt=paths=import --go-grpc_out=. --go-grpc_opt=paths=import *.proto
      在这里插入图片描述

    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
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3 - nacos配置

    • 添加命名空间goods
      在这里插入图片描述
    • 克隆之前user_srv的配置到goods_srv中
      在这里插入图片描述
    • 编辑goods_srv.json的配置:将Tags配置也放入,以及本机的Host也加入配置

    在这里插入图片描述

    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'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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"`
    }
    
    
    • 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
    • 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("注销成功")
    }
    
    
    • 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
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    在这里插入图片描述


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

    1 - 接口测试

    • goods_srv/main.go:为了让测试的时候端口不会变化,我们在main中修改port为50058
    func main() {
    	IP := flag.String("ip", "0.0.0.0", "ip地址")
    	Port := flag.Int("port", 50058, "端口号")
    	//...省略
    
    • 1
    • 2
    • 3
    • 4
    • 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()
    }
    
    
    • 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
    • 因为之前使用的是proto.UnimplementedGoodsServer:我们期望的返回是rpc接口未定义
      在这里插入图片描述

    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)
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 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)
    
    
    • 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
    • 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()
    }
    
    
    • 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

    在这里插入图片描述

    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
    }
    
    
    • 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

    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
    }
    
    
    • 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

    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
    }
    
    • 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
    • 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()
    }
    
    
    • 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

    在这里插入图片描述

    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
    }
    
    • 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
    • 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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    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
    }
    
    • 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

    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
    }
    
    
    • 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
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 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()
    }
    
    
    • 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

    在这里插入图片描述


    四、商品接口

    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
    }
    
    
    • 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
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 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()
    }
    
    
    • 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
    • 为了方便测试,我们先注释掉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:'商品展示图'"`
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 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()
    }
    
    
    • 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

    在这里插入图片描述

    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
    }
    
    
    • 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
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    五、完整源码

    • 完整源码下载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配置集导入文件
  • 相关阅读:
    掌握这3点,企业就能规避收款业务中的合规风险
    【Linux】userdel 命令使用
    从图灵机到量子计算机,计算机可以解决所有问题吗?
    接口测试需求分析
    第13届蓝桥杯Scratch省赛真题集锦
    SQLite R*Tree 模块(三十三)
    EasyGBS如何解决对讲功能使用异常?
    小红拿到了一个仅由大小写字母组成的长度为n的字符串,她希望把前k个字母变成大写,后n- k个字母变成小写,你能帮帮她吗?
    SpringBoot中localeResolver使用教程
    超强满血不收费的AI绘图教程来了(在线Stable Diffusion一键即用)
  • 原文地址:https://blog.csdn.net/qq23001186/article/details/126088192