最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang使用elastic庫來實現(xiàn)前后端模糊搜索功能

 更新時間:2025年11月14日 09:07:00   作者:Sumery  
這篇文章主要為大家詳細(xì)介紹了如何使用Golang和TypeScript的elastic庫來實現(xiàn)前端和后端交互的模糊搜索功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下

介紹

使用go的elastic庫來實現(xiàn)前后端模糊搜索功能的示例

工具

后端

  • connectrpc.com/connect:與前端通信
  • connectrpc.com/cors:解決瀏覽器跨域
  • google.golang.org/protobuf: API定義
  • buf:生成go的api
  • sqlc:與Postgres數(shù)據(jù)庫交互
  • github.com/elastic/go-elasticsearch/v9:go的es交互工具

前端

  • react-ts
  • vite
  • @connectrpc/connect: 與后端通信
  • @connectrpc/connect-web: 與后端通信

快速入門

先決條件

  • go >=1.25.1
  • node.js >=22
  • docker >= 18
  • postgres >= 12
  • elastic-search >= 8
  • elastic-kibana >= 8
  • pgsync
  • buf
  • redis

實現(xiàn)思路

設(shè)計數(shù)據(jù)結(jié)構(gòu), 以一個經(jīng)典的電商商品舉例:

CREATE DATABASE ecommerce;
CREATE SCHEMA product;
SET search_path to product;

CREATE TYPE product_status AS ENUM (
    'draft',-- 草稿
    'pending_review', -- 待審核
    'active', -- 上架
    'inactive', -- 下架
    'deleted' -- 刪除,進(jìn)入回收站,無法搜索,30天后物理刪除
    );

-- 商品表
CREATE TABLE product.products
(
    id            bigserial primary key,
    name          varchar(255)   not null,
    description   text, -- 商品描述
    price         decimal(10, 2) not null,
    status        product_status not null default 'draft',
    merchant_id   uuid           not null,
    category_id   int            not null,
    category_name VARCHAR(100)   not null,
    cover_image   text           not null,
    attributes    jsonb          not null default '{}',
    sales_count   int            not null default 0,
    rating_score  decimal(2, 1)  not null default 0.0,
    created_at    timestamptz    not null default now(),
    updated_at    timestamptz    not null default now(),
    deleted_at    timestamptz    not null default now()
);

-- 商品圖片表
CREATE TABLE product.images
(
    id         BIGSERIAL PRIMARY KEY,
    product_id BIGINT       NOT NULL REFERENCES product.products (id) ON DELETE CASCADE,
    url        VARCHAR(500) NOT NULL,
    type       VARCHAR(20)  NOT NULL DEFAULT 'detail', -- cover/detail
    sort_order INTEGER               DEFAULT 0,
    alt_text   TEXT,
    created_at TIMESTAMPTZ           DEFAULT NOW(),

    -- 約束和索引
    UNIQUE (product_id, sort_order),
    CHECK (type IN ('cover', 'detail'))
);

proto數(shù)據(jù)結(jié)構(gòu):

syntax = "proto3";

package api.product.v1;
option go_package = "github.com/sunmery/elastic-example/api/product/v1;productv1";
import "google/protobuf/timestamp.proto";

service ProductService {
  rpc GetProduct(GetProductRequest) returns(GetProductResponse){}
}

message GetProductRequest {
  string index = 1;
  string name = 2;
}

message GetProductResponse{
  repeated Product products = 1;
}


// 定義 ProductImage 消息 (對應(yīng) Go 結(jié)構(gòu)體中的 ProductImage)
message ProductImage {
  // url 字段,對應(yīng) Go 結(jié)構(gòu)體中的 url 字段
  string url = 1;
  // type 字段,例如 "cover" (封面) 或 "detail" (詳情頁)
  string type = 2;
  // sort_order 字段,用于排序
  int32 sort_order = 3;
  // alt_text 字段,用于圖片替代文本
  string alt_text = 4;
}

// 定義 ProductAttribute 消息 (對應(yīng) Go 結(jié)構(gòu)體中的 ProductAttribute)
message ProductAttribute {
  // key 字段,屬性名 (例如 "顏色", "尺寸")
  string key = 1;
  // value 字段,屬性值 (例如 "紅色", "L")
  string value = 2;
}


// 定義 Product 消息 (對應(yīng) Go 結(jié)構(gòu)體中的 Product)
message Product {
  // 基礎(chǔ)信息
  string id = 1;
  string name = 2;
  // repeated 對應(yīng) Go 中的切片 ([]string)
//  repeated string name_suggest = 3;
  string name_suggest = 3;
  string description = 4;

  // 價格和狀態(tài)
  double price = 5;
  string status = 6;

  // 分類和商家信息
  string merchant_id = 7;
  int32 category_id = 8; // Go 的 int 對應(yīng) Protobuf 的 int32
  string category_name = 9;

  // 圖片和屬性 (嵌套消息)
  // repeated 對應(yīng) Go 中的結(jié)構(gòu)體切片 ([]ProductImage)
  repeated ProductImage images = 10;
  string cover_image = 11;
  // repeated 對應(yīng) Go 中的結(jié)構(gòu)體切片 ([]ProductAttribute)
  map<string,string> attributes = 12;

  // 統(tǒng)計信息
  int32 sales_count = 13;
  double rating_score = 14;

  // 時間戳 (使用標(biāo)準(zhǔn) Protobuf 類型)
  google.protobuf.Timestamp created_at = 15;
  google.protobuf.Timestamp updated_at = 16;
}

go的數(shù)據(jù)結(jié)構(gòu):

package biz

import "time"

// ProductImage 商品圖片結(jié)構(gòu)
type ProductImage struct {
	URL       string `json:"url"`
	Type      string `json:"type"` // cover/detail
	SortOrder int    `json:"sort_order"`
	AltText   string `json:"alt_text,omitempty"`
}

// ProductAttribute 商品屬性結(jié)構(gòu)
type ProductAttribute struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

// Product 商品主結(jié)構(gòu)
type Product struct {
	ProductId    int64             `json:"product_id"`
	ProductName  string            `json:"product_name"`
	NameSuggest  string            `json:"name_suggest,omitempty"` // 用于搜索建議
	Description  string            `json:"description,omitempty"`
	Price        float64           `json:"price"`
	Status       string            `json:"status"` // 上架/下架
	MerchantID   string            `json:"merchant_id"`
	CategoryID   int               `json:"category_id"`
	CategoryName string            `json:"category_name"`
	Images       []ProductImage    `json:"images,omitempty"`
	CoverImage   string            `json:"cover_image,omitempty"`
	Attributes   map[string]string `json:"attributes,omitempty"`
	SalesCount   int               `json:"sales_count"`
	RatingScore  float64           `json:"rating_score"`
	CreatedAt    time.Time         `json:"created_at"`
	UpdatedAt    time.Time         `json:"updated_at"`
}

構(gòu)建一個Web服務(wù)來給前端提供路由

func main() {
	es := InitES()

	Producter := NewProductServer(es)

	mux := http.NewServeMux()
	path, handler := productv1connect.NewProductServiceHandler(
		Producter,
		// Validation via Protovalidate is almost always recommended
		connect.WithInterceptors(validate.NewInterceptor()),
	)
	mux.Handle(path, handler)

	// CORS 配置
	corsHandler := cors.New(cors.Options{
		AllowedOrigins:   []string{"*"},
		AllowedMethods:   connectcors.AllowedMethods(),
		AllowedHeaders:   connectcors.AllowedHeaders(),
		ExposedHeaders:   connectcors.ExposedHeaders(),
		MaxAge:           7200,
		AllowCredentials: false,
	})

	// 創(chuàng)建處理器鏈:監(jiān)控中間件 -> CORS -> HTTP/2
	handlerChain := corsHandler.Handler(mux)

	p := new(http.Protocols)
	p.SetHTTP1(true)
	// Use h2c so we can serve HTTP/2 without TLS.
	p.SetUnencryptedHTTP2(true)
	s := http.Server{
		Addr:      "localhost:8080",
		Handler:      h2c.NewHandler(handlerChain, &http2.Server{}),
		Protocols: p,
	}
	s.ListenAndServe()
}

提供搜索功能:為了能夠讓用戶在搜索時提供更好的范圍,就需要從多個字段提供值來擴(kuò)大匹配的范圍。例如用戶想搜索一個商品名為"iPhone 18"時,數(shù)據(jù)庫存儲了一個name值,如果只從name字段去匹配,如果不引入es,可以寫一個sql為匹配前綴,那么可以取到,那么當(dāng)用戶搜索"手機(jī)"時,數(shù)據(jù)庫并不會返回該條目,因為它的name不包含手機(jī),而它也許只出現(xiàn)在description商品的介紹或者該商品的分類手機(jī)類目里,那么就可以很方便的使用es提供的功能來實現(xiàn):

func (p ProductServer) GetProduct(ctx context.Context, c *connect.Request[v1.GetProductRequest]) (*connect.Response[v1.GetProductResponse], error) {
	searchFidles := []string{
		"product_name",
		"categoryName",
		"description",
		"attributes.*",
	}

	res, err := p.es.Search().Index(c.Msg.Index).Request(&search.Request{
		Query: &types.Query{
			MultiMatch: &types.MultiMatchQuery{
				Query: c.Msg.Name,
				Fields: searchFidles,
			},
		},
	}).Do(ctx)
	if err != nil {
		return nil, err
	}

	var v1Products []*v1.Product // 存放 Protobuf 格式的商品列表
	for _, hit := range res.Hits.Hits {
		var bizProduct biz.Product
		if err := json.Unmarshal(hit.Source_, &bizProduct); err != nil {
			log.Printf("解析文檔失敗:%v", err)
			continue
		}
		v1Product := bizToV1Product(&bizProduct)
		v1Products = append(v1Products, v1Product)

		fmt.Printf("文檔ID: %d, 評分: %f\n", hit.Id_, *hit.Score_)
		fmt.Printf("商品名稱: %s, 價格: %.2f\n", bizProduct.ProductName, bizProduct.Price)
	}
	fmt.Printf("成功解析 %d 個商品\n", len(v1Products))

	return connect.NewResponse(&v1.GetProductResponse{Products: v1Products}), nil
}

前端負(fù)責(zé)展示從后端接收到的數(shù)據(jù)并進(jìn)行排版展示

import { createClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web";
import { ProductService, type Product } from "./api/product_pb";
import { useState } from "react";

const transport = createConnectTransport({
    baseUrl: "http://localhost:8080",
});
const client = createClient(ProductService, transport);

function App() {
    const [index, setIndex] = useState<string>('products');
    const [name, setName] = useState<string>('蘋果iPhone 15 Pro Max 256GB 原色鈦金屬');
    const [products, setProducts] = useState<Product[]>([]);

    const getDoc = async (index: string, name: string) => {
        try {
            const response = await client.getProduct({
                index,
                name
            });
            console.log("API Response:", response);

            // 檢查響應(yīng)結(jié)構(gòu)并設(shè)置 products
            if (response.products && Array.isArray(response.products)) {
                setProducts(response.products);
            } else {
                console.warn("響應(yīng)中沒有 products 數(shù)組:", response);
                setProducts([]);
            }
        } catch (error) {
            console.error("獲取產(chǎn)品失敗:", error);
            setProducts([]);
        }
    };

    return (
        <>
            <div style={{ padding: '20px' }}>
                <label style={{ display: 'block', marginBottom: '10px' }}>
                    索引名稱:
                    <input
                        type="text"
                        value={index}
                        onChange={(e) => setIndex(e.target.value)}
                        style={{ marginLeft: '10px', padding: '5px' }}
                    />
                </label>

                <label style={{ display: 'block', marginBottom: '10px' }}>
                    搜索關(guān)鍵詞:
                    <input
                        type="text"
                        value={name}
                        onChange={(e) => setName(e.target.value)}
                        style={{ marginLeft: '10px', padding: '5px' }}
                    />
                </label>

                <button
                    onClick={() => getDoc(index, name)}
                    style={{
                        padding: '8px 16px',
                        backgroundColor: '#007bff',
                        color: 'white',
                        border: 'none',
                        borderRadius: '4px',
                        cursor: 'pointer'
                    }}
                >
                    搜索產(chǎn)品
                </button>

                {/* 產(chǎn)品列表渲染 */}
                <div style={{ marginTop: '20px' }}>
                    <h3>搜索結(jié)果 ({products.length} 個產(chǎn)品):</h3>

                    {products.length === 0 ? (
                        <p>沒有找到產(chǎn)品</p>
                    ) : (
                        <ol>
                            {products.map((item:Product) => (
                                <li key={item.id} style={{ marginBottom: '15px', padding: '10px', border: '1px solid #ddd' }}>
                                    <p>產(chǎn)品名稱:{item.name}</p>
                                    <p>價格:{item.price}</p>
                                    <p>描述:{item.description}</p>
                                    <p>狀態(tài):{item.status}</p>
                                    <p>分類:{item.categoryName}</p>
                                </li>
                            ))}
                        </ol>
                    )}
                </div>
            </div>
        </>
    );
}

export default App;

運行

基礎(chǔ)設(shè)施

example.com替換為你的地址

password替換為更安全的密碼

elastic

docker compose -f infrastructure/elastic.yaml up -d

postgres

docker compose -f infrastructure/postgres/compose.yaml up -d

redis

docker compose -f infrastructure/redis/compose.yaml up -d

pgsync

docker compose -f infrastructure/pgsync/compose.yaml up -d

后端

example.com替換為你的地址

go mod tidy
go run .

前端

pnpm i
pnpm dev

測試

example.com替換為你的地址

elastic search:

export ELASTICSEARCH_URL="http://example.com:9200"
curl -X GET $ELASTICSEARCH_URL/products/_search -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
      "product_name": "蘋果"
    }
  },
  "size": 10 
}
'

后端:

curl -v -X POST http://localhost:8080/api.product.v1.ProductService/GetProduct -H 'Content-Type: application/json' -d'
{
  "name": "手機(jī)",
  "index": "products"
}
'

前端:

注意事項

同步問題:pgsync設(shè)定了每20s從postgres數(shù)據(jù)庫,redis緩存獲取數(shù)據(jù)并同步到es,并非實時同步,請根據(jù)實際需求來設(shè)定合理時間。

es插件:pgync不支持ik等第三方分詞插件,所以在schema.json即使定義了也不會起作用,pgsync不會不識別

到此這篇關(guān)于Golang使用elastic庫來實現(xiàn)前后端模糊搜索功能的文章就介紹到這了,更多相關(guān)Golang模糊搜索內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • goland 設(shè)置注釋模板的過程圖文詳解

    goland 設(shè)置注釋模板的過程圖文詳解

    這篇文章主要介紹了goland 設(shè)置注釋模板的過程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2020-12-12
  • Swaggo零基礎(chǔ)入門教程

    Swaggo零基礎(chǔ)入門教程

    swagger是一套基于OpenAPI規(guī)范構(gòu)建的開源工具,使用RestApi。swagger-ui呈現(xiàn)出來的是一份可交互式的API文檔,可以直接在文檔頁面嘗試API的調(diào)用
    2023-01-01
  • Go語言標(biāo)準(zhǔn)輸入輸出庫的基本使用教程

    Go語言標(biāo)準(zhǔn)輸入輸出庫的基本使用教程

    輸入輸出在任何一門語言中都必須提供的一個功能,下面這篇文章主要給大家介紹了關(guān)于Go語言標(biāo)準(zhǔn)輸入輸出庫的基本使用,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-02-02
  • 深入理解Go語言中的閉包

    深入理解Go語言中的閉包

    Go函數(shù)是可以閉包的。閉包是一個函數(shù)值,他來自函數(shù)體外部的變量引用。 下面這篇文章通過一個demo來進(jìn)行深入的介紹了Go語言中閉包的相關(guān)資料,文中介紹的非常詳細(xì),需要的朋友可以參考下。
    2017-03-03
  • Go中select多路復(fù)用的實現(xiàn)示例

    Go中select多路復(fù)用的實現(xiàn)示例

    Go的select用于多通道通信,實現(xiàn)多路復(fù)用,支持隨機(jī)選擇、超時控制及非阻塞操作,建議合理使用以避免協(xié)程泄漏和死循環(huán),感興趣的可以了解一下
    2025-08-08
  • VSCode安裝go相關(guān)插件失敗的簡單解決方案

    VSCode安裝go相關(guān)插件失敗的簡單解決方案

    這篇文章主要給大家介紹了關(guān)于VSCode安裝go相關(guān)插件失敗的簡單解決方案,VSCode是我們開發(fā)go程序的常用工具,最近安裝的時候遇到了些問題,需要的朋友可以參考下
    2023-07-07
  • Golang如何快速刪除map所有元素

    Golang如何快速刪除map所有元素

    這篇文章主要介紹了Golang如何快速刪除map所有元素問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • go單體日志采集zincsearch方案實現(xiàn)

    go單體日志采集zincsearch方案實現(xiàn)

    這篇文章主要為大家介紹了go單體日志采集zincsearch方案實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • Golang基于Vault實現(xiàn)敏感數(shù)據(jù)加解密

    Golang基于Vault實現(xiàn)敏感數(shù)據(jù)加解密

    數(shù)據(jù)加密是主要的數(shù)據(jù)安全防護(hù)技術(shù)之一,敏感數(shù)據(jù)應(yīng)該加密存儲在數(shù)據(jù)庫中,降低泄露風(fēng)險,本文將介紹一下利用Vault實現(xiàn)敏感數(shù)據(jù)加解密的方法,需要的可以參考一下
    2023-07-07
  • Go?的入口函數(shù)和包初始化的使用

    Go?的入口函數(shù)和包初始化的使用

    本文主要介紹了Go?的入口函數(shù)和包初始化的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05

最新評論

容城县| 正蓝旗| 普兰店市| 东兰县| 南投县| 运城市| 那曲县| 鹤庆县| 鹤峰县| 连云港市| 博乐市| 扶绥县| 开封县| 通榆县| 惠来县| 门源| 平湖市| 河池市| 临高县| 盐津县| 馆陶县| 张家口市| 德安县| 澄江县| 宜宾市| 澎湖县| 阳城县| 贵南县| 黎平县| 甘洛县| 乐至县| 公安县| 台南县| 蒲城县| 库尔勒市| 阜平县| 广州市| 江陵县| 莫力| 玉山县| 秦皇岛市|