九.商品微服务操作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
}