• 30、ES集成到项目中


    一、go语言中使用ES

    1 - 使用第三方库

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"github.com/olivere/elastic/v7"
    )
    
    func main() {
    	//初始化一个连接
    	host := "http://192.168.124.51:9200"
    	// 这里必须将sniff设置为false,因为使用olivere/elastic连接elasticsearch时,发现连接地址命名输入的时候是地址
    	// 但是连接时会自动转换成内网地址或者docker中的ip地址,导致服务连接不上
    	_, err := elastic.NewClient(elastic.SetURL(host), elastic.SetSniff(false))
    	if err != nil {
    		panic(err)
    	}
    	q := elastic.NewMatchQuery("address", "street")
    	src, err := q.Source()
    	if err != nil {
    		panic(err)
    	}
    	data, err := json.Marshal(src)
    	got := string(data)
    	fmt.Println(got)
    }
    
    
    • 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

    在这里插入图片描述

    2 - 解析出查询结果

    package main
    
    import (
    	"context"
    	"fmt"
    	"github.com/olivere/elastic/v7"
    )
    
    func main() {
    	//初始化一个连接
    	host := "http://192.168.124.51:9200"
    	// 这里必须将sniff设置为false,因为使用olivere/elastic连接elasticsearch时,发现连接地址命名输入的时候是地址
    	// 但是连接时会自动转换成内网地址或者docker中的ip地址,导致服务连接不上
    	client, err := elastic.NewClient(elastic.SetURL(host), elastic.SetSniff(false))
    	if err != nil {
    		panic(err)
    	}
    	q := elastic.NewMatchQuery("address", "street")
    	result, err := 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)
    		}
    	}
    }
    
    
    • 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

    在这里插入图片描述

    3 - es对象转换为struct

    • 定义struct并使用go自带的json进行转换
    package main
    
    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	"github.com/olivere/elastic/v7"
    )
    
    type Account struct {
    	AccountNum int32  `json:"account_number"`
    	FirstName  string `json:"firstname"`
    }
    
    func main() {
    	//初始化一个连接
    	host := "http://192.168.124.51:9200"
    	// 这里必须将sniff设置为false,因为使用olivere/elastic连接elasticsearch时,发现连接地址命名输入的时候是地址
    	// 但是连接时会自动转换成内网地址或者docker中的ip地址,导致服务连接不上
    	client, err := elastic.NewClient(elastic.SetURL(host), elastic.SetSniff(false))
    	if err != nil {
    		panic(err)
    	}
    	q := elastic.NewMatchQuery("address", "street")
    	result, err := 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)
    	}
    }
    
    
    • 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

    在这里插入图片描述

    4 - 向es中添加数据

    • 添加数据同时开启es的日志打印
    package main
    
    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	"github.com/olivere/elastic/v7"
    	"log"
    	"os"
    )
    
    type Account struct {
    	AccountNum int32  `json:"account_number"`
    	FirstName  string `json:"firstname"`
    }
    
    func main() {
    	//初始化一个连接
    	host := "http://192.168.124.51:9200"
    	logger := log.New(os.Stdout, "mxshop", log.LstdFlags)
    	// 这里必须将sniff设置为false,因为使用olivere/elastic连接elasticsearch时,发现连接地址命名输入的时候是地址
    	// 但是连接时会自动转换成内网地址或者docker中的ip地址,导致服务连接不上
    	client, err := elastic.NewClient(elastic.SetURL(host), elastic.SetSniff(false),
    		elastic.SetTraceLog(logger))
    	if err != nil {
    		panic(err)
    	}
    	q := elastic.NewMatchQuery("address", "street")
    	result, err := 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)
    	}
    
    	account := Account{AccountNum: 15468, FirstName: "immooc bobby"}
    	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 \n", put1.Id, put1.Index, put1.Type)
    }
    
    
    • 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

    在这里插入图片描述

    5 - 新建mapping

    package main
    
    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	"github.com/olivere/elastic/v7"
    	"log"
    	"os"
    )
    
    const goodsMapping = `{
    	"mappings" : {
    		"properties" : {
    			"name" : {
    				"type" : "text",
    				"analyzer":"ik_max_word"
    			},
    			"id" : {
    				"type" : "integer"
    			}
    		}
    	}
    }`
    
    type Account struct {
    	AccountNum int32  `json:"account_number"`
    	FirstName  string `json:"firstname"`
    }
    
    func main() {
    	//初始化一个连接
    	host := "http://192.168.124.51:9200"
    	logger := log.New(os.Stdout, "mxshop", log.LstdFlags)
    	// 这里必须将sniff设置为false,因为使用olivere/elastic连接elasticsearch时,发现连接地址命名输入的时候是地址
    	// 但是连接时会自动转换成内网地址或者docker中的ip地址,导致服务连接不上
    	client, err := elastic.NewClient(elastic.SetURL(host), elastic.SetSniff(false),
    		elastic.SetTraceLog(logger))
    	if err != nil {
    		panic(err)
    	}
    	q := elastic.NewMatchQuery("address", "street")
    	result, err := 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)
    	}
    
    	//account := Account{AccountNum: 15468, FirstName: "immooc bobby"}
    	//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 \n", put1.Id, put1.Index, put1.Type)
    
    	createIndex, err := client.CreateIndex("mygoods").BodyString(goodsMapping).Do(context.Background())
    	if err != nil {
    		panic(err)
    	}
    	if !createIndex.Acknowledged {
    
    	}
    }
    
    
    • 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

    在这里插入图片描述
    在这里插入图片描述


    二、项目中集成ES

    1 - 集成es接口分析

    • 对于商品操作来说:需要同步操作es的接口
      • 搜索、添加、更新、删除:这些都需要将数据同步到es的数据中
    • 将es的集成在srv层还是web层:应该将es集成到srv层,因为有可能出现mysql保存成功了,但是es保存失败了,这时候如果是在web层无法完成数据的回滚,这样就省去了微服务的事务问题
    • 使用es的目的:主要目的是搜索出商品的id来,通过id拿到具体的字段信息是通过mysql来完成的
    • 是否需要将所有的mysql字段在es中保存一份:实际开发中,我们一般只把搜索和用来过滤的字段信息保存到es中
    • es也可以来当做mysql使用
      • 但是实际上mysql和es之间是互补的关系;
      • mysql是关系数据库,es是文档数据库,各有各的优缺点;
      • 一般mysql用来做存储使用,es用来做搜索使用
    • es的性能提升
      • 想要提升es的性能,需要将es的内存设置的够大;
      • 但是实际上es可以设置的内存也是有上限的,所以我们不在es中保存不必要的字段,可以提高es的内存使用率,同样的内存可以载入的文档数量就会多一些,从另外的角度上来说也可以提高es的性能

    2 - 建立商品对应的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
    }
    
    
    • 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

    3 - nacos新增es配置

    {
      "name": "goods_srv",
      "host": "192.168.124.9",
      "tags": ["imooc", "bobby", "goods", "srv"],
      "mysql": {
        "host": "192.168.124.51",
        "port": 3306,
        "user": "root",
        "password": "jiushi",
        "db": "mxshop_goods_srv"
      },
      "consul": {
        "host": "192.168.124.51",
        "port": 8500
      },
      "es": {
        "host": "192.168.124.51",
        "port": 9200
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • goods_srv/config/config.go:新增EsConfig结构体
    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"`
    	Host       string       `mapstructure:"host" json:"host"`
    	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"`
    }
    
    
    • 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/global/global.go:添加EsClient对象
    package global
    
    import (
    	"github.com/olivere/elastic/v7"
    	"gorm.io/gorm"
    	"nd/goods_srv/config"
    )
    
    var (
    	DB           *gorm.DB
    	ServerConfig config.ServerConfig
    	NacosConfig  config.NacosConfig
    	EsClient     *elastic.Client
    )
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    4 - 初始化es

    • goods_srv/initialize/init_es.go
    package initialize
    
    import (
    	"context"
    	"fmt"
    	"github.com/olivere/elastic/v7"
    	"log"
    	"nd/goods_srv/global"
    	"nd/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 { // 不存在的时候才需要新建mapping
    		_, err = global.EsClient.CreateIndex(model.EsGoods{}.GetIndexName()).BodyString(model.EsGoods{}.GetMapping()).Do(context.Background())
    		if err != nil {
    			panic(err)
    		}
    	}
    }
    
    
    • 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
    • goods_srv/main.go:添加es的初始化调用
    func main() {
    	IP := flag.String("ip", "0.0.0.0", "ip地址")
    	Port := flag.Int("port", 50058, "端口号") // 这个修改为0,如果我们从命令行带参数启动的话就不会为0
    
    	//初始化
    	initialize.InitLogger()
    	initialize.InitConfig()
    	initialize.InitDB()
    	initialize.InitEs()
    	zap.S().Info(global.ServerConfig)
    	//省略。。。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 启动查看
      在这里插入图片描述
      在这里插入图片描述

    5 - 同步已经的mysql数据到es中

    • goods_srv/model/main/main.go
    package main
    
    import (
    	"context"
    	"fmt"
    	"github.com/olivere/elastic/v7"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"gorm.io/gorm/schema"
    	"log"
    	"nd/goods_srv/global"
    	"nd/goods_srv/initialize"
    	"nd/goods_srv/model"
    	"os"
    	"strconv"
    	"time"
    )
    
    func main() {
    	/*	initialize.InitConfig()
    		dsn := fmt.Sprintf("root:jiushi@tcp(%s:3306)/mxshop_goods_srv?charset=utf8mb4&parseTime=True&loc=Local", global.ServerConfig.MysqlInfo.Host)
    
    		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{})*/
    	Mysql2Es()
    }
    
    // Mysql2Es 将之前mysql的goods数据保存到es中
    func Mysql2Es() {
    	initialize.InitConfig()
    	dsn := fmt.Sprintf("root:jiushi@tcp(%s:3306)/mxshop_goods_srv?charset=utf8mb4&parseTime=True&loc=Local", global.ServerConfig.MysqlInfo.Host)
    
    	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 := fmt.Sprintf("http://%s:%d", global.ServerConfig.EsInfo.Host, global.ServerConfig.EsInfo.Port)
    	loggerEs := log.New(os.Stdout, "mxshop", log.LstdFlags)
    	global.EsClient, err = elastic.NewClient(elastic.SetURL(host), elastic.SetSniff(false),
    		elastic.SetTraceLog(loggerEs))
    	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错误
    	}
    }
    
    
    • 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

    在这里插入图片描述
    在这里插入图片描述


    三、goods接口集成es查询

    1 - GoodsList中集成es

    • goods_srv/handler/handle_goods.go
    func (s *GoodsServer) GoodsList(ctx context.Context, req *proto.GoodsFilterRequest) (*proto.GoodsListResponse, error) {
    	//关键词搜索、查询新品、查询热门商品、通过价格区间筛选, 通过商品分类筛选
    	goodsListResponse := &proto.GoodsListResponse{}
    
    	//match bool 复合查询
    	q := elastic.NewBoolQuery()
    	localDB := global.DB.Model(model.Goods{})
    	if 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去查询商品
    	// 子查询 嵌套 子查询
    	// 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
    	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
    }
    
    
    • 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

    2 - CreateGoods集成es

    • 为了降低耦合性:我们使用gorm的钩子函数,这样就可以不用修改CreateGoods的逻辑
    • goods_srv/model/goods.go:添加钩子函数
    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
    }
    
    • 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/handler/handle_goods.go:需要添加上事务处理
    func (s *GoodsServer) CreateGoods(ctx context.Context, req *proto.CreateGoodsInfo) (*proto.GoodsInfoResponse, error) {
    	//省略。。。
    	//srv之间互相调用了
    	tx := global.DB.Begin()
    	result := tx.Save(&goods) // Save的时候会自动调用钩子函数,所以在这里添加事务处理
    	if result.Error != nil {
    		tx.Rollback()
    		return nil, result.Error
    	}
    	tx.Commit()
    	return &proto.GoodsInfoResponse{
    		Id: goods.ID,
    	}, nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • YApi测试添加商品:需要开启goods_web服务、goods_srv服务
      在这里插入图片描述
    • kibana查询35的商品:添加成功
      在这里插入图片描述

    3 - 商品更新与商品删除

    • goods_srv/model/goods.go:商品更新与删除同样使用gorm的钩子函数来实现
    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
    }
    
    • 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
    • goods_srv/handler/handle_goods.go
    // 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

    四、完整源码

    • 完整源码下载mxshop_srvsV11.0rar
    • 源码说明:(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配置集导入文件
      • other_import/nacos_config_export_inventory.zip:nacos的inventory的配置导入文件
      • other_import/nacos_config_export_orders.zip:nacos的orders的配置导入文件
      • other_import/nacos_config_export_userop.zip:nacos的userop的配置导入文件
  • 相关阅读:
    【UnityShader入门精要学习笔记】第十六章 Unity中的渲染优化技术 (上)
    计算机网络高频面试题
    Spring framework day 01:spring 整合数据源(连接池)
    人工智能核心基础 - 规划和概要
    网络请求-Android篇(Okhttp和Retrofit)
    排序算法:非比较排序(计数排序)
    【MySQL】 Java的JDBC编程
    5、Elasticsearch 索引文档的CRUD
    SQL多表设计--一对多(外键)
    今年暑假不AC(贪心思路)
  • 原文地址:https://blog.csdn.net/qq23001186/article/details/126331288