九 商品微服务goods-srv增加ES操作

九.商品微服务操作ES

1.安装golang 中的ES包
 go get github.com/olivere/elastic/v7
2.实现match查询
package main

import (
   "context"
   "fmt"
   "github.com/olivere/elastic/v7"
)

func main() {
   //初始化一个链接
   host := "http://127.0.0.1:9200"
   client, err := elastic.NewClient(elastic.SetURL(host), elastic.SetSniff(false))
   if err != nil {
      panic(err)
   }

   q := elastic.NewMatchQuery("address", "street")
   result, _ := client.Search().Index("user").Query(q).Do(context.Background())
   if err != nil {
      panic(err)
   }
   total := result.Hits.TotalHits.Value
   fmt.Printf("搜结结果数量:%d\n", total)
   for _, value := range result.Hits.Hits {
      if jsonData, err := value.Source.MarshalJSON(); err == nil {
         fmt.Println(string(jsonData))
      } else {
         panic(err)
      }

   }
}
2.将ES中的对象转化为struct
package main

import (
   "context"
   "encoding/json"
   "fmt"
   "github.com/olivere/elastic/v7"
)

type Account struct {
   AccountNumber int32  `json:"account_number"`
   Balance       int32  `json:"balance"`
   Firstname     string `json:"firstname"`
   Lastname      string `json:"lastname"`
}

func main() {
   //初始化一个链接
   host := "http://127.0.0.1:9200"
   client, err := elastic.NewClient(elastic.SetURL(host), elastic.SetSniff(false))
   if err != nil {
      panic(err)
   }

   q := elastic.NewMatchQuery("address", "street")
   result, _ := client.Search().Index("user").Query(q).Do(context.Background())
   if err != nil {
      panic(err)
   }
   total := result.Hits.TotalHits.Value
   fmt.Printf("搜结结果数量:%d\n", total)
   for _, value := range result.Hits.Hits {
      account := Account{}
      _ = json.Unmarshal(value.Source, &account)
      fmt.Println(account)
   }
}
3.保存数据到ES
package main

import (
   "context"
   "fmt"
   "github.com/olivere/elastic/v7"
   "log"
   "os"
)

type Account struct {
   AccountNumber int32  `json:"account_number"`
   Balance       int32  `json:"balance"`
   Firstname     string `json:"firstname"`
   Lastname      string `json:"lastname"`
}

func main() {
   //初始化一个链接
   host := "http://127.0.0.1:9200"
   logger := log.New(os.Stdout, "mxshop", log.LstdFlags)
   client, err := elastic.NewClient(elastic.SetURL(host),
      elastic.SetSniff(false), elastic.SetTraceLog(logger))
   if err != nil {
      panic(err)
   }

   account := Account{AccountNumber: 15468, Balance: 2345, Firstname: "li", Lastname: "yongcan"}

   // Add a document
   put1, err := client.Index().
      Index("myuser").
      BodyJson(account).
      Do(context.Background())
   if err != nil {
      panic(err)
   }
   fmt.Printf("indexed myuser %s to index %s, type %s", put1.Id, put1.Index, put1.Type)
}
4.建立商品对应的struct和mapping

在goods-srv/model目录下建立es_goods.go

package model

type EsGoods struct {
	ID int32 `json:"id"`
	CategoryID int32 `json:"category_id"`
	BrandsID int32 `json:"brands_id"`
	OnSale bool  `json:"on_sale"`
	ShipFree bool  `json:"ship_free"`
	IsNew bool `json:"is_new"`
	IsHot bool `json:"is_hot"`

	Name  string `json:"name"`
	ClickNum int32  `json:"click_num"`
	SoldNum int32 `json:"sold_num"`
	FavNum int32 `json:"fav_num"`
	MarketPrice float32  `json:"market_price"`
	GoodsBrief string `json:"goods_brief"`
	ShopPrice float32 `json:"shop_price"`
}

func (EsGoods) GetIndexName() string {
	return "goods"
}

func (EsGoods) GetMapping() string {
	goodsMapping := `
	{
		"mappings" : {
			"properties" : {
				"brands_id" : {
					"type" : "integer"
				},
				"category_id" : {
					"type" : "integer"
				},
				"click_num" : {
					"type" : "integer"
				},
				"fav_num" : {
					"type" : "integer"
				},
				"id" : {
					"type" : "integer"
				},
				"is_hot" : {
					"type" : "boolean"
				},
				"is_new" : {
					"type" : "boolean"
				},
				"market_price" : {
					"type" : "float"
				},
				"name" : {
					"type" : "text",
					"analyzer":"ik_max_word"
				},
				"goods_brief" : {
					"type" : "text",
					"analyzer":"ik_max_word"
				},
				"on_sale" : {
					"type" : "boolean"
				},
				"ship_free" : {
					"type" : "boolean"
				},
				"shop_price" : {
					"type" : "float"
				},
				"sold_num" : {
					"type" : "long"
				}
			}
		}
	}`
	return goodsMapping
}
5.初始化过程新建index和mapping

在goods-srv/global/global.go增加ES的全局变量

package global

import (
   "github.com/olivere/elastic/v7"
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
   "gorm.io/gorm/logger"
   "gorm.io/gorm/schema"
   "log"
   "mxshop_srvs/goods_srv/config"
   "os"
   "time"
)

var (
   DB           *gorm.DB
   ServerConfig config.ServerConfig
   NacosConfig  config.NacosConfig
   //新增的
   EsClient     *elastic.Client
)

在goods-srv/config目录增加ES的映射结构体

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 EsConfig 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"`
   //新增的
   EsInfo     EsConfig     `mapstructure:"es" json:"es"`
}

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"`
}

在nacos上配置ES

{
  "name": "goods-srv",
  "tags":["imooc", "bobby", "goods", "srv"],
  "mysql": {
    "host": "127.0.0.1",
    "port": 3306,
    "user": "root",
    "password": "123456",
    "db": "mxshop_goods_srv"
  },
  "consul": {
    "host": "127.0.0.1",
    "port": 8500
  },
  //新增的
  "es": {
    "host": "127.0.0.1",
    "port": 9200
  }
}

在goods-srv/initialize目录增加ES的初始化文件es.go

package initialize

import (
   "context"
   "fmt"
   "github.com/olivere/elastic/v7"
   "log"
   "mxshop_srvs/goods_srv/global"
   "mxshop_srvs/goods_srv/model"
   "os"
)

func InitEs() {
   //初始化连接
   host := fmt.Sprintf("http://%s:%d", global.ServerConfig.EsInfo.Host, global.ServerConfig.EsInfo.Port)
   logger := log.New(os.Stdout, "mxshop", log.LstdFlags)
   var err error
   global.EsClient, err = elastic.NewClient(elastic.SetURL(host), elastic.SetSniff(false),
      elastic.SetTraceLog(logger))
   if err != nil {
      panic(err)
   }

   //新建mapping和index
   exists, err := global.EsClient.IndexExists(model.EsGoods{}.GetIndexName()).Do(context.Background())
   if err != nil {
      panic(err)
   }
   if !exists {
      _, err = global.EsClient.CreateIndex(model.EsGoods{}.GetIndexName()).BodyString(model.EsGoods{}.GetMapping()).Do(context.Background())
      if err != nil {
         panic(err)
      }
   }
}

在goods-srv/main.go增加初始化

package main

import (
   "flag"
   "fmt"
   "github.com/hashicorp/consul/api"
   "go.uber.org/zap"
   "google.golang.org/grpc"
   "mxshop_srvs/goods_srv/global"
   "mxshop_srvs/goods_srv/handler"
   "os"
   "os/signal"
   "syscall"

   "github.com/satori/go.uuid"
   "google.golang.org/grpc/health"
   "google.golang.org/grpc/health/grpc_health_v1"
   "mxshop_srvs/goods_srv/initialize"
   "mxshop_srvs/goods_srv/proto"
   "net"
)

func main() {
   IP := flag.String("ip", "0.0.0.0", "ip地址")
   Port := flag.Int("port", 50052, "端口")

   //初始化
   initialize.InitLogger()
   initialize.InitConfig()
   initialize.InitDB()
    //新增的
   initialize.InitEs()

   zap.S().Info(global.ServerConfig.ConsulInfo)

   flag.Parse()
   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("192.168.0.4:%d", *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 = []string{"imooc", "bobby", "goods", "srv"}
   registration.Address = "192.168.0.4"
   registration.Check = check
   //1. 如何启动两个服务
   //2. 即使我能够通过终端启动两个服务,但是注册到consul中的时候也会被覆盖
   err = client.Agent().ServiceRegister(registration)
   if err != nil {
      panic(err)
   }
   err = server.Serve(lis)
   if err != nil {
      panic("failed to start")
   }

   //接收终止信号
   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("注销成功")
}
6.mysql中的商品数据同步到es中
package main

import (
   "context"
   "github.com/olivere/elastic/v7"
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
   "gorm.io/gorm/logger"
   "gorm.io/gorm/schema"
   "log"
   "mxshop_srvs/goods_srv/global"
   "mxshop_srvs/goods_srv/model"
   "os"
   "strconv"
   "time"
)

func Mysql2Es() {
   dsn := "root:123456@tcp(127.0.0.1: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)
   }

   host := "http://127.0.0.1:9200"
   logger := log.New(os.Stdout, "mxshop", log.LstdFlags)
   global.EsClient, err = elastic.NewClient(elastic.SetURL(host), elastic.SetSniff(false),
      elastic.SetTraceLog(logger))
   if err != nil {
      panic(err)
   }

   var goods []model.Goods
   db.Find(&goods)
   for _, g := range goods {
      esModel := model.EsGoods{
         ID:          g.ID,
         CategoryID:  g.CategoryID,
         BrandsID:    g.BrandsID,
         OnSale:      g.OnSale,
         ShipFree:    g.ShipFree,
         IsNew:       g.IsNew,
         IsHot:       g.IsHot,
         Name:        g.Name,
         ClickNum:    g.ClickNum,
         SoldNum:     g.SoldNum,
         FavNum:      g.FavNum,
         MarketPrice: g.MarketPrice,
         GoodsBrief:  g.GoodsBrief,
         ShopPrice:   g.ShopPrice,
      }

      _, err = global.EsClient.Index().Index(esModel.GetIndexName()).BodyJson(esModel).Id(strconv.Itoa(int(g.ID))).Do(context.Background())
      if err != nil {
         panic(err)
      }
      //强调一下 一定要将docker启动es的java_ops的内存设置大一些 否则运行过程中会出现 bad request错误
   }
}

func main() {
   Mysql2Es()
}
7.如何通过mysql和es协作完成商品的查询

在goods-srv/handle/goods.go进行修改

func (s *GoodsServer) GoodsList(ctx context.Context, req *proto.GoodsFilterRequest) (*proto.GoodsListResponse, error) {
   //使用es的目的是搜索出商品的id来,通过id拿到具体的字段信息是通过mysql来完成
   //我们使用es是用来做搜索的, 是否应该将所有的mysql字段全部在es中保存一份
   //es用来做搜索,这个时候我们一般只把搜索和过滤的字段信息保存到es中
   //es可以用来当做mysql使用, 但是实际上mysql和es之间是互补的关系, 一般mysql用来做存储使用,es用来做搜索使用
   //es想要提高性能, 就要将es的内存设置的够大, 1k 2k

   //关键词搜索、查询新品、查询热门商品、通过价格区间筛选, 通过商品分类筛选
   goodsListResponse := &proto.GoodsListResponse{}

   q := elastic.NewBoolQuery()
   localDB := global.DB.Model(&model.Goods{})
   if req.KeyWords != "" {
      //搜索
      //localDB = localDB.Where("name LIKE ?", "%"+req.KeyWords+"%")
      q = q.Must(elastic.NewMultiMatchQuery(req.KeyWords, "name", "goods_brief"))
   }
   if req.IsHot {
      localDB = localDB.Where(model.Goods{IsHot: true})
      q = q.Filter(elastic.NewTermQuery("is_hot", req.IsHot))
   }
   if req.IsNew {
      q = q.Filter(elastic.NewTermQuery("is_new", req.IsNew))
   }
   if req.PriceMin > 0 {
      q = q.Filter(elastic.NewRangeQuery("shop_price").Gte(req.PriceMin))
   }
   if req.PriceMax > 0 {
      q = q.Filter(elastic.NewRangeQuery("shop_price").Lte(req.PriceMax))
   }
   if req.Brand > 0 {
      q = q.Filter(elastic.NewTermQuery("brands_id", req.Brand))
   }

   //通过category去查询商品
   var subQuery string
   categoryIds := make([]interface{}, 0)
   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)
      }
      type Result struct {
         ID int32
      }
      var results []Result
      global.DB.Model(model.Category{}).Raw(subQuery).Scan(&results)
      for _, re := range results {
         categoryIds = append(categoryIds, re.ID)
      }
      //生成terms查询
      q = q.Filter(elastic.NewTermsQuery("category_id", categoryIds...))
   }

   //分页
   if req.Pages == 0 {
      req.Pages = 1
   }

   switch {
   case req.PagePerNums > 100:
      req.PagePerNums = 100
   case req.PagePerNums <= 0:
      req.PagePerNums = 10
   }
   result, err := global.EsClient.Search().Index(model.EsGoods{}.GetIndexName()).Query(q).From(int(req.Pages)).Size(int(req.PagePerNums)).Do(context.Background())
   if err != nil {
      return nil, err
   }

   goodsIds := make([]int32, 0)

   goodsListResponse.Total = int32(result.Hits.TotalHits.Value)
   for _, value := range result.Hits.Hits {
      goods := model.EsGoods{}
      _ = json.Unmarshal(value.Source, &goods)
      goodsIds = append(goodsIds, goods.ID)
   }
   //查询id在某个数组中的值
   var goods []model.Goods
   re := localDB.Preload("Category").Preload("Brands").Find(&goods, goodsIds)
   if re.Error != nil {
      return nil, re.Error
   }
   for _, good := range goods {
      goodsInfoResponse := ModelToResponse(good)
      goodsListResponse.Data = append(goodsListResponse.Data, &goodsInfoResponse)
   }
   return goodsListResponse, nil
}
8.确保商品添加到es中的事务一致性

在创建商品的时候,利用钩子创建商品的时候,同时创建ES,并且创建的时候要注意事务的使用,在创建品商和更新商品的时候要注意事务。在goods-srv/model/goods.go增加相应的代码

package model

import (
   "context"
   "gorm.io/gorm"
   "mxshop_srvs/goods_srv/global"
   "strconv"
)

// Category 类型, 这个字段是否能为null, 这个字段应该设置为可以为null还是设置为空, 0
//实际开发过程中 尽量设置为不为null
//https://zhuanlan.zhihu.com/p/73997266
//这些类型我们使用int32还是int
type Category struct {
   BaseModel
   Name             string      `gorm:"type:varchar(20);not null" json:"name"`
   ParentCategoryID int32       `json:"parent"`
   ParentCategory   *Category   `json:"-"`
   SubCategory      []*Category `gorm:"foreignKey:ParentCategoryID;references:ID" json:"sub_category"`
   Level            int32       `gorm:"type:int;not null;default:1" json:"level"`
   IsTab            bool        `gorm:"default:false;not null" json:"is_tab"`
}

type Brands struct {
   BaseModel
   Name string `gorm:"type:varchar(20);not null"`
   Logo string `gorm:"type:varchar(200);default:'';not null"`
}

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
}

func (GoodsCategoryBrand) TableName() string {
   return "goodscategorybrand"
}

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"`
}

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"`
   ShipFree bool `gorm:"default:false;not null"`
   IsNew    bool `gorm:"default:false;not null"`
   IsHot    bool `gorm:"default:false;not null"`

   Name            string   `gorm:"type:varchar(50);not null"`
   GoodsSn         string   `gorm:"type:varchar(50);not null"`
   ClickNum        int32    `gorm:"type:int;default:0;not null"`
   SoldNum         int32    `gorm:"type:int;default:0;not null"`
   FavNum          int32    `gorm:"type:int;default:0;not null"`
   MarketPrice     float32  `gorm:"not null"`
   ShopPrice       float32  `gorm:"not null"`
   GoodsBrief      string   `gorm:"type:varchar(100);not null"`
   Images          GormList `gorm:"type:varchar(1000);not null"`
   DescImages      GormList `gorm:"type:varchar(1000);not null"`
   GoodsFrontImage string   `gorm:"type:varchar(200);not null"`
}

// 新增加的
func (g *Goods) AfterCreate(tx *gorm.DB) (err error) {
   esModel := EsGoods{
      ID:          g.ID,
      CategoryID:  g.CategoryID,
      BrandsID:    g.BrandsID,
      OnSale:      g.OnSale,
      ShipFree:    g.ShipFree,
      IsNew:       g.IsNew,
      IsHot:       g.IsHot,
      Name:        g.Name,
      ClickNum:    g.ClickNum,
      SoldNum:     g.SoldNum,
      FavNum:      g.FavNum,
      MarketPrice: g.MarketPrice,
      GoodsBrief:  g.GoodsBrief,
      ShopPrice:   g.ShopPrice,
   }

   _, err = global.EsClient.Index().Index(esModel.GetIndexName()).BodyJson(esModel).Id(strconv.Itoa(int(g.ID))).Do(context.Background())
   if err != nil {
      return err
   }
   return nil
}
// 新增加的
func (g *Goods) AfterUpdate(tx *gorm.DB) (err error) {
   esModel := EsGoods{
      ID:          g.ID,
      CategoryID:  g.CategoryID,
      BrandsID:    g.BrandsID,
      OnSale:      g.OnSale,
      ShipFree:    g.ShipFree,
      IsNew:       g.IsNew,
      IsHot:       g.IsHot,
      Name:        g.Name,
      ClickNum:    g.ClickNum,
      SoldNum:     g.SoldNum,
      FavNum:      g.FavNum,
      MarketPrice: g.MarketPrice,
      GoodsBrief:  g.GoodsBrief,
      ShopPrice:   g.ShopPrice,
   }

   _, err = global.EsClient.Update().Index(esModel.GetIndexName()).
      Doc(esModel).Id(strconv.Itoa(int(g.ID))).Do(context.Background())
   if err != nil {
      return err
   }
   return nil
}
// 新增加的
func (g *Goods) AfterDelete(tx *gorm.DB) (err error) {
   _, err = global.EsClient.Delete().Index(EsGoods{}.GetIndexName()).Id(strconv.Itoa(int(g.ID))).Do(context.Background())
   if err != nil {
      return err
   }
   return nil
}
    esModel := model.EsGoods{
         ID:          g.ID,
         CategoryID:  g.CategoryID,
         BrandsID:    g.BrandsID,
         OnSale:      g.OnSale,
         ShipFree:    g.ShipFree,
         IsNew:       g.IsNew,
         IsHot:       g.IsHot,
         Name:        g.Name,
         ClickNum:    g.ClickNum,
         SoldNum:     g.SoldNum,
         FavNum:      g.FavNum,
         MarketPrice: g.MarketPrice,
         GoodsBrief:  g.GoodsBrief,
         ShopPrice:   g.ShopPrice,
      }

      _, err = global.EsClient.Index().Index(esModel.GetIndexName()).BodyJson(esModel).Id(strconv.Itoa(int(g.ID))).Do(context.Background())
      if err != nil {
         panic(err)
      }
      //强调一下 一定要将docker启动es的java_ops的内存设置大一些 否则运行过程中会出现 bad request错误
   }
}

func main() {
   Mysql2Es()
}
7.如何通过mysql和es协作完成商品的查询

在goods-srv/handle/goods.go进行修改

func (s *GoodsServer) GoodsList(ctx context.Context, req *proto.GoodsFilterRequest) (*proto.GoodsListResponse, error) {
   //使用es的目的是搜索出商品的id来,通过id拿到具体的字段信息是通过mysql来完成
   //我们使用es是用来做搜索的, 是否应该将所有的mysql字段全部在es中保存一份
   //es用来做搜索,这个时候我们一般只把搜索和过滤的字段信息保存到es中
   //es可以用来当做mysql使用, 但是实际上mysql和es之间是互补的关系, 一般mysql用来做存储使用,es用来做搜索使用
   //es想要提高性能, 就要将es的内存设置的够大, 1k 2k

   //关键词搜索、查询新品、查询热门商品、通过价格区间筛选, 通过商品分类筛选
   goodsListResponse := &proto.GoodsListResponse{}

   q := elastic.NewBoolQuery()
   localDB := global.DB.Model(&model.Goods{})
   if req.KeyWords != "" {
      //搜索
      //localDB = localDB.Where("name LIKE ?", "%"+req.KeyWords+"%")
      q = q.Must(elastic.NewMultiMatchQuery(req.KeyWords, "name", "goods_brief"))
   }
   if req.IsHot {
      localDB = localDB.Where(model.Goods{IsHot: true})
      q = q.Filter(elastic.NewTermQuery("is_hot", req.IsHot))
   }
   if req.IsNew {
      q = q.Filter(elastic.NewTermQuery("is_new", req.IsNew))
   }
   if req.PriceMin > 0 {
      q = q.Filter(elastic.NewRangeQuery("shop_price").Gte(req.PriceMin))
   }
   if req.PriceMax > 0 {
      q = q.Filter(elastic.NewRangeQuery("shop_price").Lte(req.PriceMax))
   }
   if req.Brand > 0 {
      q = q.Filter(elastic.NewTermQuery("brands_id", req.Brand))
   }

   //通过category去查询商品
   var subQuery string
   categoryIds := make([]interface{}, 0)
   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)
      }
      type Result struct {
         ID int32
      }
      var results []Result
      global.DB.Model(model.Category{}).Raw(subQuery).Scan(&results)
      for _, re := range results {
         categoryIds = append(categoryIds, re.ID)
      }
      //生成terms查询
      q = q.Filter(elastic.NewTermsQuery("category_id", categoryIds...))
   }

   //分页
   if req.Pages == 0 {
      req.Pages = 1
   }

   switch {
   case req.PagePerNums > 100:
      req.PagePerNums = 100
   case req.PagePerNums <= 0:
      req.PagePerNums = 10
   }
   result, err := global.EsClient.Search().Index(model.EsGoods{}.GetIndexName()).Query(q).From(int(req.Pages)).Size(int(req.PagePerNums)).Do(context.Background())
   if err != nil {
      return nil, err
   }

   goodsIds := make([]int32, 0)

   goodsListResponse.Total = int32(result.Hits.TotalHits.Value)
   for _, value := range result.Hits.Hits {
      goods := model.EsGoods{}
      _ = json.Unmarshal(value.Source, &goods)
      goodsIds = append(goodsIds, goods.ID)
   }
   //查询id在某个数组中的值
   var goods []model.Goods
   re := localDB.Preload("Category").Preload("Brands").Find(&goods, goodsIds)
   if re.Error != nil {
      return nil, re.Error
   }
   for _, good := range goods {
      goodsInfoResponse := ModelToResponse(good)
      goodsListResponse.Data = append(goodsListResponse.Data, &goodsInfoResponse)
   }
   return goodsListResponse, nil
}
8.确保商品添加到es中的事务一致性

在创建商品的时候,利用钩子创建商品的时候,同时创建ES,并且创建的时候要注意事务的使用,在创建品商和更新商品的时候要注意事务。在goods-srv/model/goods.go增加相应的代码

package model

import (
   "context"
   "gorm.io/gorm"
   "mxshop_srvs/goods_srv/global"
   "strconv"
)

// Category 类型, 这个字段是否能为null, 这个字段应该设置为可以为null还是设置为空, 0
//实际开发过程中 尽量设置为不为null
//https://zhuanlan.zhihu.com/p/73997266
//这些类型我们使用int32还是int
type Category struct {
   BaseModel
   Name             string      `gorm:"type:varchar(20);not null" json:"name"`
   ParentCategoryID int32       `json:"parent"`
   ParentCategory   *Category   `json:"-"`
   SubCategory      []*Category `gorm:"foreignKey:ParentCategoryID;references:ID" json:"sub_category"`
   Level            int32       `gorm:"type:int;not null;default:1" json:"level"`
   IsTab            bool        `gorm:"default:false;not null" json:"is_tab"`
}

type Brands struct {
   BaseModel
   Name string `gorm:"type:varchar(20);not null"`
   Logo string `gorm:"type:varchar(200);default:'';not null"`
}

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
}

func (GoodsCategoryBrand) TableName() string {
   return "goodscategorybrand"
}

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"`
}

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"`
   ShipFree bool `gorm:"default:false;not null"`
   IsNew    bool `gorm:"default:false;not null"`
   IsHot    bool `gorm:"default:false;not null"`

   Name            string   `gorm:"type:varchar(50);not null"`
   GoodsSn         string   `gorm:"type:varchar(50);not null"`
   ClickNum        int32    `gorm:"type:int;default:0;not null"`
   SoldNum         int32    `gorm:"type:int;default:0;not null"`
   FavNum          int32    `gorm:"type:int;default:0;not null"`
   MarketPrice     float32  `gorm:"not null"`
   ShopPrice       float32  `gorm:"not null"`
   GoodsBrief      string   `gorm:"type:varchar(100);not null"`
   Images          GormList `gorm:"type:varchar(1000);not null"`
   DescImages      GormList `gorm:"type:varchar(1000);not null"`
   GoodsFrontImage string   `gorm:"type:varchar(200);not null"`
}

// 新增加的
func (g *Goods) AfterCreate(tx *gorm.DB) (err error) {
   esModel := EsGoods{
      ID:          g.ID,
      CategoryID:  g.CategoryID,
      BrandsID:    g.BrandsID,
      OnSale:      g.OnSale,
      ShipFree:    g.ShipFree,
      IsNew:       g.IsNew,
      IsHot:       g.IsHot,
      Name:        g.Name,
      ClickNum:    g.ClickNum,
      SoldNum:     g.SoldNum,
      FavNum:      g.FavNum,
      MarketPrice: g.MarketPrice,
      GoodsBrief:  g.GoodsBrief,
      ShopPrice:   g.ShopPrice,
   }

   _, err = global.EsClient.Index().Index(esModel.GetIndexName()).BodyJson(esModel).Id(strconv.Itoa(int(g.ID))).Do(context.Background())
   if err != nil {
      return err
   }
   return nil
}
// 新增加的
func (g *Goods) AfterUpdate(tx *gorm.DB) (err error) {
   esModel := EsGoods{
      ID:          g.ID,
      CategoryID:  g.CategoryID,
      BrandsID:    g.BrandsID,
      OnSale:      g.OnSale,
      ShipFree:    g.ShipFree,
      IsNew:       g.IsNew,
      IsHot:       g.IsHot,
      Name:        g.Name,
      ClickNum:    g.ClickNum,
      SoldNum:     g.SoldNum,
      FavNum:      g.FavNum,
      MarketPrice: g.MarketPrice,
      GoodsBrief:  g.GoodsBrief,
      ShopPrice:   g.ShopPrice,
   }

   _, err = global.EsClient.Update().Index(esModel.GetIndexName()).
      Doc(esModel).Id(strconv.Itoa(int(g.ID))).Do(context.Background())
   if err != nil {
      return err
   }
   return nil
}
// 新增加的
func (g *Goods) AfterDelete(tx *gorm.DB) (err error) {
   _, err = global.EsClient.Delete().Index(EsGoods{}.GetIndexName()).Id(strconv.Itoa(int(g.ID))).Do(context.Background())
   if err != nil {
      return err
   }
   return nil
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值