# MySQL 到 Elasticsearch 字段映射说明(业务版)
## 1. 概述
本文档说明 Elasticsearch 中的商品文档(Document)的各个字段是如何从 MySQL 数据库中的相关表获取和转换的。
### 1.1 数据源表
- **SPU 表** (`shoplazza_product_spu`): 商品基本信息
- **SKU 表** (`shoplazza_product_sku`): 商品变体信息(多款式)
- **Option 表** (`shoplazza_product_option`): 选项名称定义(如:颜色、尺寸)
- **类目表** (`product_category`): 类目信息(通过 SPU 表的 category_id 关联)
---
## 2. 基础字段映射
### 2.1 标识字段
| ES 字段 | 数据来源表 | 表中字段 | 转换说明 |
|---------|-----------|----------|----------|
| `tenant_id` | SPU 表 | `tenant_id` | 直接使用,转为字符串 |
| `spu_id` | SPU 表 | `id` | 直接使用,转为字符串 |
### 2.2 文本字段(多语言)
文本字段支持中英文双语,根据租户配置自动翻译。
| ES 字段 | 数据来源表 | 表中字段 | 转换说明 |
|---------|-----------|----------|----------|
| `title.zh` | SPU 表 | `title` | 如果主语言是中文,直接使用;否则翻译为中文 |
| `title.en` | SPU 表 | `title` | 如果主语言是英文,直接使用;否则翻译为英文 |
| `brief.zh` | SPU 表 | `brief` | 同上 |
| `brief.en` | SPU 表 | `brief` | 同上 |
| `description.zh` | SPU 表 | `description` | 同上 |
| `description.en` | SPU 表 | `description` | 同上 |
| `vendor.zh` | SPU 表 | `vendor` | 同上 |
| `vendor.en` | SPU 表 | `vendor` | 同上 |
**翻译规则:**
- 根据租户配置的 `primary_language` 确定主语言
- 如果配置了 `translate_to_en=true`,且主语言不是英文,则翻译为英文
- 如果配置了 `translate_to_zh=true`,且主语言不是中文,则翻译为中文
### 2.3 标签字段
| ES 字段 | 数据来源表 | 表中字段 | 转换说明 |
|---------|-----------|----------|----------|
| `tags` | SPU 表 | `tags` | 逗号分隔的字符串 → 数组
示例:`"标签1,标签2"` → `["标签1", "标签2"]` |
### 2.4 图片字段
| ES 字段 | 数据来源表 | 表中字段 | 转换说明 |
|---------|-----------|----------|----------|
| `image_url` | SPU 表 | `image_src` | |
### 2.5 销量字段
| ES 字段 | 数据来源表 | 表中字段 | 转换说明 |
|---------|-----------|----------|----------|
| `sales` | SPU 表 | `fake_sales` | 转为整数,如果为空则为 0 |
### 2.6 时间字段
| ES 字段 | 数据来源表 | 表中字段 | 转换说明 |
|---------|-----------|----------|----------|
| `create_time` | SPU 表 | `create_time` | 转为 ISO 格式字符串(如:`2024-01-01T00:00:00`) |
| `update_time` | SPU 表 | `update_time` | 转为 ISO 格式字符串 |
---
## 3. 类别(Category)字段映射
### 3.1 类别数据来源
类别信息主要来自 SPU 表的以下字段:
- `category_path`: 类目路径,逗号分隔的类目ID列表(如:`"1,2,3"`)
- `category`: 类目名称(备用字段)
- `category_id`: 当前类目ID
- `category_level`: 类目层级
### 3.2 类别映射流程
**步骤 1:解析 category_path**
从 SPU 表的 `category_path` 字段解析出类目ID列表:
```
category_path = "1,2,3"
→ category_ids = ["1", "2", "3"]
```
**步骤 2:ID 转名称**
通过预加载的类目映射表(从 SPU 表查询 `category_id` 和 `category` 字段构建),将ID转换为名称:
```
映射表:{"1": "电子产品", "2": "手机", "3": "iPhone"}
category_ids = ["1", "2", "3"]
→ category_names = ["电子产品", "手机", "iPhone"]
```
**步骤 3:构建 ES 字段**
| ES 字段 | 数据来源 | 转换说明 |
|---------|----------|----------|
| `category_path.zh` | 类目名称列表 | 用 `/` 连接:`"电子产品/手机/iPhone"` |
| `category1_name` | 类目名称列表[0] | 一级类目:`"电子产品"` |
| `category2_name` | 类目名称列表[1] | 二级类目:`"手机"` |
| `category3_name` | 类目名称列表[2] | 三级类目:`"iPhone"` |
| `category_id` | SPU 表 | `category_id` 转为字符串 |
| `category_level` | SPU 表 | `category_level` 转为整数 |
**备用处理:**
如果 `category_path` 为空,使用 `category` 字段作为备选:
- 如果 `category` 包含 `/`,按 `/` 分割为多级类目
- 否则,直接作为 `category1_name`
**数据质量检查:**
如果 `category_path` 中的类目ID在映射表中不存在,则不写入任何类目字段(视为没有类目)。
---
## 4. 多款式(SKU/Options)字段映射
### 4.1 SKU 嵌套结构
一个 SPU 可以有多个 SKU,每个 SKU 代表一个商品变体(如:红色-L码、蓝色-M码)。
**ES 中的 `skus` 字段结构:**
```json
{
"skus": [
{
"sku_id": "123",
"price": 99.99,
"compare_at_price": 129.99,
"sku_code": "SKU001",
"stock": 100,
"weight": 0.5,
"weight_unit": "kg",
"option1_value": "红色",
"option2_value": "L",
"option3_value": "棉",
"image_src": "https://..."
}
]
}
```
### 4.2 SKU 字段映射
| ES 字段 | 数据来源表 | 表中字段 | 转换说明 |
|---------|-----------|----------|----------|
| `skus[].sku_id` | SKU 表 | `id` | 转为字符串 |
| `skus[].price` | SKU 表 | `price` | 转为浮点数 |
| `skus[].compare_at_price` | SKU 表 | `compare_at_price` | 转为浮点数 |
| `skus[].sku_code` | SKU 表 | `sku` | 转为字符串 |
| `skus[].stock` | SKU 表 | `inventory_quantity` | 转为整数 |
| `skus[].weight` | SKU 表 | `weight` | 转为浮点数 |
| `skus[].weight_unit` | SKU 表 | `weight_unit` | 转为字符串 |
| `skus[].image_src` | SKU 表 | `image_src` | 转为字符串 |
| `skus[].option1_value` | SKU 表 | `option1` | 转为字符串 |
| `skus[].option2_value` | SKU 表 | `option2` | 转为字符串 |
| `skus[].option3_value` | SKU 表 | `option3` | 转为字符串 |
### 4.3 选项名称字段
选项名称来自 Option 表,按 `position` 字段排序(1、2、3 对应 option1、option2、option3)。
| ES 字段 | 数据来源表 | 表中字段 | 转换说明 |
|---------|-----------|----------|----------|
| `option1_name` | Option 表 | `name` (where position=1) | 第一个选项的名称(如:"颜色") |
| `option2_name` | Option 表 | `name` (where position=2) | 第二个选项的名称(如:"尺寸") |
| `option3_name` | Option 表 | `name` (where position=3) | 第三个选项的名称(如:"材质") |
**查询逻辑:**
```sql
SELECT position, name
FROM shoplazza_product_option
WHERE spu_id = ? AND deleted = 0
ORDER BY position
```
### 4.4 选项值字段
选项值来自所有 SKU 的 `option1`、`option2`、`option3` 字段,去重后形成列表。
| ES 字段 | 数据来源表 | 表中字段 | 转换说明 |
|---------|-----------|----------|----------|
| `option1_values` | SKU 表 | `option1` | 收集所有 SKU 的 option1 值,去重
示例:`["红色", "蓝色", "绿色"]` |
| `option2_values` | SKU 表 | `option2` | 收集所有 SKU 的 option2 值,去重
示例:`["S", "M", "L"]` |
| `option3_values` | SKU 表 | `option3` | 收集所有 SKU 的 option3 值,去重
示例:`["棉", "涤纶"]` |
**转换逻辑:**
```
遍历所有 SKU:
- 收集 option1 值 → option1_values 列表
- 收集 option2 值 → option2_values 列表
- 收集 option3 值 → option3_values 列表
去重后写入 ES
```
**注意:** 只有配置在 `searchable_option_dimensions` 中的选项才会被索引。
### 4.5 规格(Specifications)字段
规格字段用于支持按规格过滤和分面搜索,将 SKU 的选项值结构化存储。
**ES 中的 `specifications` 字段结构:**
```json
{
"specifications": [
{"sku_id": "123", "name": "颜色", "value": "红色"},
{"sku_id": "123", "name": "尺寸", "value": "L"},
{"sku_id": "124", "name": "颜色", "value": "蓝色"},
{"sku_id": "124", "name": "尺寸", "value": "M"}
]
}
```
**构建逻辑:**
1. 从 Option 表获取选项名称映射:
```
position=1 → name="颜色"
position=2 → name="尺寸"
position=3 → name="材质"
```
2. 遍历所有 SKU,为每个 SKU 的每个选项值构建规格记录:
```
SKU.id=123, option1="红色", option2="L"
→ {"sku_id": "123", "name": "颜色", "value": "红色"}
→ {"sku_id": "123", "name": "尺寸", "value": "L"}
```
| ES 字段 | 数据来源 | 转换说明 |
|---------|----------|----------|
| `specifications[].sku_id` | SKU 表 | `id` 转为字符串 |
| `specifications[].name` | Option 表 | `name`(根据 position 匹配) |
| `specifications[].value` | SKU 表 | `option1/2/3` 转为字符串 |
### 4.6 价格聚合字段
从所有 SKU 的价格中计算聚合值。
| ES 字段 | 数据来源 | 转换说明 |
|---------|----------|----------|
| `min_price` | SKU 表 | 所有 SKU 的 `price` 最小值 |
| `max_price` | SKU 表 | 所有 SKU 的 `price` 最大值 |
| `compare_at_price` | SKU 表 | 所有 SKU 的 `compare_at_price` 最大值 |
| `sku_prices` | SKU 表 | 所有 SKU 的 `price` 数组
示例:`[99.99, 99.99, 129.99]` |
**计算逻辑:**
```
遍历所有 SKU:
- 收集 price → prices 列表
- 收集 compare_at_price → compare_prices 列表
min_price = min(prices)
max_price = max(prices)
compare_at_price = max(compare_prices)
sku_prices = prices 列表
```
### 4.7 库存和重量聚合字段
| ES 字段 | 数据来源 | 转换说明 |
|---------|----------|----------|
| `total_inventory` | SKU 表 | 所有 SKU 的 `inventory_quantity` 求和 |
| `sku_weights` | SKU 表 | 所有 SKU 的 `weight` 数组
示例:`[500, 500, 600]` |
| `sku_weight_units` | SKU 表 | 所有 SKU 的 `weight_unit` 去重后的列表
示例:`["kg"]` |
**计算逻辑:**
```
遍历所有 SKU:
- 累加 inventory_quantity → total_inventory
- 收集 weight → sku_weights 列表
- 收集 weight_unit → sku_weight_units 列表
total_inventory = 所有 SKU 库存之和
sku_weights = 所有 SKU 重量数组
sku_weight_units = 去重后的重量单位列表
```
---
## 5. 向量字段映射
### 5.1 标题向量(title_embedding)
| ES 字段 | 数据来源 | 转换说明 |
|---------|----------|----------|
| `title_embedding` | SPU 表 `title` | 使用文本编码器(BGE)将标题转换为 1024 维向量
优先使用 `title.en`,如果没有则使用 `title.zh` |
**生成逻辑:**
```
如果启用向量搜索:
文本 = title.en 或 title.zh
向量 = 文本编码器.encode(文本)
title_embedding = 向量(1024 维浮点数组)
```
---
## 6. 完整字段映射表
### 6.1 字段来源汇总
| ES 字段 | 数据来源表 | 表中字段 | 说明 |
|---------|-----------|----------|------|
| **基础字段** |
| `tenant_id` | SPU | `tenant_id` | 租户ID |
| `spu_id` | SPU | `id` | 商品ID |
| `title.zh/en` | SPU | `title` | 标题(多语言) |
| `brief.zh/en` | SPU | `brief` | 简介(多语言) |
| `description.zh/en` | SPU | `description` | 描述(多语言) |
| `vendor.zh/en` | SPU | `vendor` | 品牌(多语言) |
| `tags` | SPU | `tags` | 标签数组 |
| `image_url` | SPU | `image_src` | 主图URL |
| `sales` | SPU | `fake_sales` | 销量 |
| `create_time` | SPU | `create_time` | 创建时间 |
| `update_time` | SPU | `update_time` | 更新时间 |
| **类别字段** |
| `category_path.zh` | SPU + 类目映射 | `category_path` → 类目名称 | 类目路径 |
| `category1_name` | SPU + 类目映射 | `category_path` → 类目名称[0] | 一级类目 |
| `category2_name` | SPU + 类目映射 | `category_path` → 类目名称[1] | 二级类目 |
| `category3_name` | SPU + 类目映射 | `category_path` → 类目名称[2] | 三级类目 |
| `category_id` | SPU | `category_id` | 类目ID |
| `category_level` | SPU | `category_level` | 类目层级 |
| **SKU 嵌套字段** |
| `skus[]` | SKU | 多个字段 | SKU 数组(见 4.2 节) |
| **选项字段** |
| `option1_name` | Option | `name` (position=1) | 选项1名称 |
| `option2_name` | Option | `name` (position=2) | 选项2名称 |
| `option3_name` | Option | `name` (position=3) | 选项3名称 |
| `option1_values` | SKU | `option1` | 选项1值列表 |
| `option2_values` | SKU | `option2` | 选项2值列表 |
| `option3_values` | SKU | `option3` | 选项3值列表 |
| **规格字段** |
| `specifications[]` | SKU + Option | `option1/2/3` + `name` | 规格数组 |
| **聚合字段** |
| `min_price` | SKU | `price` | 最低价格 |
| `max_price` | SKU | `price` | 最高价格 |
| `compare_at_price` | SKU | `compare_at_price` | 最高原价 |
| `sku_prices` | SKU | `price` | 所有价格数组 |
| `total_inventory` | SKU | `inventory_quantity` | 总库存 |
| `sku_weights` | SKU | `weight` | 所有重量数组 |
| `sku_weight_units` | SKU | `weight_unit` | 重量单位列表 |
| **向量字段** |
| `title_embedding` | SPU | `title` | 标题向量(1024维) |
---
## 7. 数据查询示例
### 7.1 查询 SPU 数据
```sql
SELECT
id, tenant_id, title, brief, description, vendor,
category, category_id, category_path, category_level,
tags, image_src, fake_sales, create_time, update_time
FROM shoplazza_product_spu
WHERE tenant_id = ? AND id = ? AND deleted = 0
```
### 7.2 查询 SKU 数据
```sql
SELECT
id, spu_id, price, compare_at_price, sku,
inventory_quantity, weight, weight_unit,
option1, option2, option3, image_src
FROM shoplazza_product_sku
WHERE tenant_id = ? AND spu_id = ? AND deleted = 0
```
### 7.3 查询 Option 数据
```sql
SELECT
position, name
FROM shoplazza_product_option
WHERE tenant_id = ? AND spu_id = ? AND deleted = 0
ORDER BY position
```
### 7.4 查询类目映射
```sql
SELECT DISTINCT
category_id, category
FROM shoplazza_product_spu
WHERE deleted = 0 AND category_id IS NOT NULL
```
---
## 8. ES 文档示例
```json
GET /search_products/_search
{
"size": 1,
"_source": "*",
"query": {
"bool": {
"filter": [
{ "term": {"_id" : 74174} },
{ "term": { "tenant_id": "162" } }
]
}
}
}
```
结果
```json
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0,
"hits": [
{
"_index": "search_products",
"_id": "74174",
"_score": 0,
"_source": {
"tenant_id": "162",
"spu_id": "74174",
"title.zh": "实色二阶碳纤维魔方 方形 2阶 塑料【英文包装】",
"title.en": "Solid Color 2nd Order Carbon Fiber Rubik's Cube Square 2 Steps Plastic [English Packaging",
"brief.zh": "实色二阶碳纤维魔方 方形 2阶 塑料【英文包装】",
"brief.en": "Solid Color 2nd Order Carbon Fiber Rubik's Cube Square 2 Steps Plastic [English Packaging",
"description.zh": "
实色二阶碳纤维魔方 方形 2阶 塑料【英文包装】
", "description.en": "Solid Color 2nd Order Carbon Fiber Rubik's Cube Square 2 Steps Plastic [English Packaging]