Laravel项目中基于Elasticsearch-PHP构建多语言商品索引咨询
这个场景我之前做过类似的,结合Laravel和elasticsearch-php实现多语言商品检索其实不难,核心是做好ES的索引映射设计和数据同步逻辑,下面一步步来:
一、先搞定Elasticsearch的索引映射设计
因为你的title字段是动态多语言的,语言键不固定,所以用**动态模板(dynamic_templates)**是最合适的——它能自动识别不同语言的字段并应用对应的分词器。
首先要确保你的ES已经安装了对应语言的分词插件:
- 中文:
analysis-ik(用于精准中文分词) - 日文:
analysis-kuromoji(用于日文分词) - 英文用ES自带的
english分析器即可
然后用elasticsearch-php创建索引,映射代码示例如下:
use Elastic\Elasticsearch\Client; // 假设你已经通过Laravel依赖注入拿到了ES客户端实例 $client = app(Client::class); $indexName = 'products'; // 先判断索引是否存在,不存在则创建 if (!$client->indices()->exists(['index' => $indexName])) { $client->indices()->create([ 'index' => $indexName, 'body' => [ 'settings' => [ 'number_of_shards' => 1, 'number_of_replicas' => 0, // 配置自定义语言分析器 'analysis' => [ 'analyzer' => [ 'zh_cn_analyzer' => [ 'type' => 'custom', 'tokenizer' => 'ik_smart' ], 'ja_jp_analyzer' => [ 'type' => 'custom', 'tokenizer' => 'kuromoji_tokenizer' ] ] ] ], 'mappings' => [ 'dynamic_templates' => [ // 中文标题字段映射 [ 'zh_cn_title' => [ 'path_match' => 'title.zh-CN', 'mapping' => [ 'type' => 'text', 'analyzer' => 'zh_cn_analyzer', 'fields' => [ 'keyword' => ['type' => 'keyword'] ] ] ] ], // 日文标题字段映射 [ 'ja_jp_title' => [ 'path_match' => 'title.ja-JP', 'mapping' => [ 'type' => 'text', 'analyzer' => 'ja_jp_analyzer', 'fields' => [ 'keyword' => ['type' => 'keyword'] ] ] ] ], // 英文标题字段映射 [ 'en_us_title' => [ 'path_match' => 'title.en-US', 'mapping' => [ 'type' => 'text', 'analyzer' => 'english', 'fields' => [ 'keyword' => ['type' => 'keyword'] ] ] ] ], // 其他未定义语言,用默认标准分析器兜底 [ 'other_language_titles' => [ 'match_mapping_type' => 'string', 'path_match' => 'title.*', 'mapping' => [ 'type' => 'text', 'analyzer' => 'standard', 'fields' => [ 'keyword' => ['type' => 'keyword'] ] ] ] ] ], 'properties' => [ 'id' => ['type' => 'keyword'], // title作为object类型,存放多语言键值对 'title' => ['type' => 'object'] ] ] ] ]); }
二、从Laravel的MySQL同步数据到Elasticsearch
数据同步分两种场景:批量导入现有数据,以及实时同步新增/修改的数据。
1. 批量导入现有数据
写一个Artisan命令一次性同步所有商品:
// 生成命令:php artisan make:command SyncProductsToEs namespace App\Console\Commands; use App\Models\Product; use Elastic\Elasticsearch\Client; use Illuminate\Console\Command; class SyncProductsToEs extends Command { protected $signature = 'products:sync-es'; protected $description = 'Sync all products from MySQL to Elasticsearch'; public function handle(Client $client) { $indexName = 'products'; $bar = $this->output->createProgressBar(Product::count()); // 分块处理,避免内存溢出 Product::chunk(100, function ($products) use ($client, $indexName, $bar) { $bulkParams = []; foreach ($products as $product) { $bulkParams[] = [ 'index' => [ '_index' => $indexName, '_id' => $product->id ] ]; $bulkParams[] = [ 'id' => $product->id, 'title' => json_decode($product->title, true) ]; $bar->advance(); } if (!empty($bulkParams)) { $client->bulk(['body' => $bulkParams]); } }); $bar->finish(); $this->info("\nAll products synced to Elasticsearch successfully!"); } }
运行命令:php artisan products:sync-es就能完成批量导入。
2. 实时同步新增/修改的数据
在Product模型里添加事件监听,自动同步数据:
namespace App\Models; use Elastic\Elasticsearch\Client; use Illuminate\Database\Eloquent\Model; class Product extends Model { // 自动把title JSON转成数组 protected $casts = ['title' => 'array']; protected static function booted() { // 保存/更新时同步到ES static::saved(function (Product $product) { $client = app(Client::class); $client->index([ '_index' => 'products', '_id' => $product->id, 'body' => [ 'id' => $product->id, 'title' => $product->title ] ]); }); // 删除时从ES移除 static::deleted(function (Product $product) { $client = app(Client::class); $client->delete([ '_index' => 'products', '_id' => $product->id ]); }); } }
如果商品数量大,建议把同步逻辑放到队列里,避免阻塞请求。
三、实现多语言检索
现在可以根据不同场景实现检索逻辑:
1. 指定单一语言检索
比如用户当前语言是中文,就精准检索中文标题字段:
$client = app(Client::class); $indexName = 'products'; $keyword = '丰田'; $lang = 'zh-CN'; $response = $client->search([ 'index' => $indexName, 'body' => [ 'query' => [ 'match' => [ "title.$lang" => $keyword ] ] ] ]); // 处理返回结果 foreach ($response['hits']['hits'] as $hit) { echo "商品ID:{$hit['_id']},标题:{$hit['_source']['title'][$lang]}\n"; }
2. 多语言全局检索(无指定语言)
用multi_match匹配所有语言的标题字段:
$response = $client->search([ 'index' => $indexName, 'body' => [ 'query' => [ 'multi_match' => [ 'query' => $keyword, 'fields' => ['title.*'] ] ] ] ]);
3. 按语言优先级加权检索
优先返回用户当前语言的匹配结果,通过boost提升字段权重:
$response = $client->search([ 'index' => $indexName, 'body' => [ 'query' => [ 'bool' => [ 'should' => [ ['match' => ["title.zh-CN" => ['query' => $keyword, 'boost' => 3]]], ['match' => ["title.en-US" => ['query' => $keyword, 'boost' => 2]]], ['match' => ["title.ja-JP" => ['query' => $keyword, 'boost' => 1]]] ] ] ] ] ]);
一些注意事项
- 确保Laravel里的ES客户端配置正确:在
config/services.php中添加elasticsearch的连接信息,通过依赖注入获取客户端实例。 - 分词插件必须提前安装,否则中文、日文的检索效果会大打折扣。
- 批量同步大数量数据时,调整
chunk的大小避免内存溢出。 - 高并发场景下,用队列处理实时同步逻辑,避免影响主请求响应速度。
内容的提问来源于stack exchange,提问作者DengSihan




