商品搜索
配置索引 Mapping
PUT goods
{
"mappings": {
"dynamic": false,
"properties": {
"goods_id": {
"type": "integer"
},
"store_id": {
"type": "integer"
},
"goods_name": {
"type" : "text",
"analyzer" : "ik_max_word",
"fields": {
"word": {
"type": "text",
"analyzer": "standard"
}
}
},
"cate_id": {
"type": "integer"
},
"brand_id": {
"type": "integer"
},
"closed": {
"type": "byte"
},
"is_deleted": {
"type": "byte"
},
"if_show_b2b": {
"type": "byte"
},
"wholesale_price": {
"type": "double"
},
"sale_region": {
"type": "integer"
},
"recommend_sort": {
"type": "integer"
},
"last_update": {
"type": "integer"
},
"cate_name": {
"type" : "text",
"analyzer" : "ik_max_word",
"fields": {
"word": {
"type": "text",
"analyzer": "standard"
}
}
},
"wholesale_sales": {
"type": "integer"
}
}
},
"settings": {
"analysis": {
"analyzer": {
"ik_max_word_html_strip": {
"tokenizer": "ik_max_word",
"char_filter": ["html_strip"]
}
}
}
}
}
Logstash 配置同步 Mysql 数据
为了方便管理,建议每个 Index 索引设置一个配置文件夹
# 创建索引goods配置目录
mkdir /logstash/config/goods
# 创建JDBC同步配置文件
touch /logstash/config/goods/jdbc.conf
# 创建JDBC同步配置SQL文件
touch /logstash/config/goods/jdbc.sql
input {
jdbc {
# mysql 数据库链接,db_fengche为数据库名 ?tinyInt1isBit=false 禁止整型转换为bool
jdbc_connection_string => "jdbc:mysql://192.168.9.201/db_fengche?tinyInt1isBit=false"
# 用户名和密码
jdbc_user => "db_fengche"
jdbc_password => "HbDZF63D5865BjjE"
# 驱动
jdbc_driver_library => "/logstash/logstash-core/lib/jars/mysql-connector-java-8.0.28.jar"
# 驱动类名
jdbc_driver_class => "com.mysql.jdbc.Driver"
jdbc_paging_enabled => "true"
jdbc_page_size => "50000"
# 启动追踪,如果为true,则需要指定 tracking_colum
use_column_value => true
# 指定追踪的字段
tracking_column => "last_update"
# 追踪字段的类型,目前只有数字(numeric)和时间类型(timestamp),默认是 numeric
tracking_column_type => "numeric"
# 记录最后一次运行的结果
record_last_run => true
# 上次运行结果的保存位置
last_run_metadata_path => "/logstash/config/goods/jdbc-position.txt"
# 执行的sql 文件路径+名称
statement_filepath => "/logstash/config/goods/jdbc.sql"
# 设置监听间隔 各字段含义(由左至右)分、时、天、月、年,全部为*默认含义为每分钟都更新
schedule => "* * * * *"
}
}
output {
elasticsearch {
# ES的IP地址及端口
hosts => ["192.168.9.220:9200"]
# 索引名称
index => "goods"
document_type => "_doc"
# 自增ID 需要关联的数据库中有有一个id字段,对应索引的id号
document_id => "%{goods_id}"
}
stdout {
# JSON格式输出
codec => json_lines
}
}
SELECT
g.goods_id,
g.store_id,
g.goods_name,
g.cate_id,
g.brand_id,
g.closed,
g.is_deleted,
g.if_show_b2b,
g.wholesale_price,
g.sale_region,
g.recommend_sort,
g.last_update,
gc.cate_name,
st.wholesale_sales
FROM
fc_goods AS g
INNER JOIN fc_gcategory AS gc
ON g.cate_id = gc.cate_id
LEFT JOIN fc_goods_statistics AS st
ON st.goods_id = g.goods_id
WHERE g.last_update > :sql_last_value
/logstash/bin/logstash -f /logstash/config/goods/jdbc.conf
PHP 项目开发
安装 Elasticsearch 插件
composer require elasticsearch/elasticsearch "elasticsearch/elasticsearch":"7.1.0"
控制器测试代码
基于 thinkphp6.0 框架
<?php
namespace app\controller;
use app\BaseController;
use think\facade\Request;
use app\logic\ElasticsearchLogic;
class Elasticsearch extends BaseController
{
public function get_goods_list()
{
$keyword = '车灯';
$where = array(
'closed' => 0,
'keyword' => $keyword,
'is_deleted' => 0,
'if_show_b2b' => 1,
'cate_id' => [],
'brand_id' => [],
'goods_id' => [],
'store_id' => []
);
$page_no = Request::post('page_no/d', 1);
$page_size = Request::post('page_size/d', 10);
$elasticsearchLogic = new ElasticsearchLogic();
$elasticsearchLogic->goodsSearch($where, build_limit($page_no, $page_size));
}
}
逻辑代码
根据关键字搜索商品名称进行关键字相关度搜索,结合商品上下架等匹配和排序,完成搜索。
<?php
namespace app\logic;
use \Elasticsearch\ClientBuilder;
class ElasticsearchLogic
{
public $client;
public function __construct()
{
$params = array(
'192.168.9.220:9200'
);
$this->client = ClientBuilder::create()->setHosts($params)->build();
}
public function goodsSearch(array $where = [], array $limit = [])
{
// 索引名称
$params['index'] = 'goods';
// 文档类型
$params['type'] = '_doc';
// 返回字段
$params['body']['_source'] = ["closed", "goods_name", "cate_name", "is_deleted", "if_show_b2b", "cate_id", "brand_id", "goods_id", "store_id", "wholesale_sales", "last_update"];
// 请求体
$params['body']['query'] = [];
// 关键字相关度匹配搜索
if ($where['keyword']) {
// should:选择性匹配,其中一项匹配即可,贡献算分
$should = [];
$keyword = $where['keyword'];
$should[] = $this->bulid_match('goods_name', $keyword);
$should[] = $this->bulid_match('goods_name.word', $keyword, 0.2);
$params['body']['query']['bool']['should'] = $should;
}
// filter:必须匹配,但是不贡献算分,高速缓存
$filter = [];
if (isset($where['closed'])) {
$filter[] = $this->bulid_match('closed', $where['closed']);
}
if (isset($where['is_deleted'])) {
$filter[] = $this->bulid_match('is_deleted', $where['is_deleted']);
}
if (isset($where['if_show_b2b'])) {
$filter[] = $this->bulid_match('if_show_b2b', $where['if_show_b2b']);
}
if (isset($where['cate_id']) && $where['cate_id']) {
$filter[] = $this->bulid_terms('cate_id', $where['cate_id']);
}
if (isset($where['brand_id']) && $where['brand_id']) {
$filter[] = $this->bulid_terms('brand_id', $where['brand_id']);
}
if (isset($where['goods_id']) && $where['goods_id']) {
$filter[] = $this->bulid_terms('goods_id', $where['goods_id']);
}
if (isset($where['store_id']) && $where['store_id']) {
$filter[] = $this->bulid_terms('store_id', $where['store_id']);
}
$params['body']['query']['bool']['filter'] = $filter;
$params['body']['sort'] = [["_score"=> ['order'=> 'desc']], ['wholesale_sales'=> ['order'=> 'desc']], ['last_update'=> ['order'=> 'desc']]];
$response = $this->client->search($params);
}
protected function bulid_match($queryName, $queryValue, $boost = 1)
{
$type = 'match';
if (is_array($queryValue)) {
$type = 'terms';
}
if ($boost === 1) {
return [$type => [$queryName => $queryValue]];
} else {
return [$type => [$queryName => ["query" => $queryValue, 'boost' => 0.2]]];
}
}
}