Commit 15e63baff6d2af1954047809de30dcfad04159d8
1 parent
97a5d59d
索引文档修改
Showing
5 changed files
with
956 additions
and
122 deletions
Show diff stats
.gitignore
| @@ -0,0 +1,346 @@ | @@ -0,0 +1,346 @@ | ||
| 1 | +# 商品导入模板说明文档 | ||
| 2 | + | ||
| 3 | +本文档完整描述了商品导入Excel模板的结构、字段说明和示例数据。 | ||
| 4 | + | ||
| 5 | +## 字段列表 | ||
| 6 | + | ||
| 7 | +| 列号 | 字段名称 | 字段说明 | | ||
| 8 | +|------|----------|----------| | ||
| 9 | +| 1 | 商品ID | (此行导入时不可删除 )商品 ID 是系统生成的唯一标识符,新增商品无需填写 | | ||
| 10 | +| 2 | 创建时间 | 创建时间的时区为当前店铺设置的时区 | | ||
| 11 | +| 3 | 商品标题* | 最多255字符(同一商品的子款式标题与商品标题需一致,且中间请勿插入其他商品) | | ||
| 12 | +| 4 | 商品属性* | 1.多款式商品:<br>同一个商品的首行标题填入「M」(商品主体);<br>同一个商品的非首行标题填入「P」(子款式);<br>2.单一款式商品(Single):填入「S」 | | ||
| 13 | +| 5 | 商品副标题 | 最多600字符 | | ||
| 14 | +| 6 | 商品描述 | 上传不带样式,若需要样式,则输入html代码 | | ||
| 15 | +| 7 | SEO标题 | 最多5000字符 | | ||
| 16 | +| 8 | SEO描述 | 最多5000字符 | | ||
| 17 | +| 9 | SEO URL Handle | 最多支持输入255字符<br>(SEO URL handle只对SEO URL的「URL参数」部分进行更改,即“products/”后的内容,如:products/「URL参数」<br>) | | ||
| 18 | +| 10 | SEO URL 重定向 | 创建URL重定向,访问修改前链接可跳转到修改后的新链接页面<br>「Y」:TRUE <br>「N」:FALSE | | ||
| 19 | +| 11 | SEO关键词 | 多个关键词请用「英文逗号」隔开 | | ||
| 20 | +| 12 | 商品上架 | 「Y」:上架状态<br>「N」:下架状态<br>默认为「N」 | | ||
| 21 | +| 13 | 需要物流 | 「Y」:需要<br>「N」:不需要<br>默认为「Y」 | | ||
| 22 | +| 14 | 商品收税 | 「Y」:需要<br>「N」:不需要<br>默认为「N」 | | ||
| 23 | +| 15 | 商品spu | 最多100字符 | | ||
| 24 | +| 16 | 启用虚拟销量 | 「Y」:启用<br>「N」:不启用<br>默认为「N」 | | ||
| 25 | +| 17 | 虚拟销量值 | 最多输入六位数的自然数 | | ||
| 26 | +| 18 | 跟踪库存 | 「Y」:跟踪<br>「N」:不跟踪<br>默认为「N」 | | ||
| 27 | +| 19 | 库存规则* | 若跟踪库存为Y,则该项必填:<br>填入「1」表示:库存为0允许购买<br>填入「2」表示:库存为0不允许购买<br>填入「3」表示:库存为0自动下架 | | ||
| 28 | +| 20 | 专辑名称 | 请填入已创建的手动专辑名称;<br>多个专辑请用「英文逗号」隔开 | | ||
| 29 | +| 21 | 标签 | 最多输入250个标签,每个不得超过500字符,多个标签请用「英文逗号」隔开 | | ||
| 30 | +| 22 | 供应商名称 | 最多20字符 | | ||
| 31 | +| 23 | 供应商URL | 请输入供应商URL | | ||
| 32 | +| 24 | 款式1 | 最多255字符 | | ||
| 33 | +| 25 | 款式2 | 最多255字符 | | ||
| 34 | +| 26 | 款式3 | 最多255字符 | | ||
| 35 | +| 27 | 商品售价* | 最多输入9位正整数,2位小数 | | ||
| 36 | +| 28 | 商品原价 | 最多输入9位正整数,2位小数 | | ||
| 37 | +| 29 | 成本价 | 最多输入9位正整数,2位小数 | | ||
| 38 | +| 30 | 商品SKU | 最多255字符 | | ||
| 39 | +| 31 | 商品重量 | 最多输入10位正整数,2位小数 | | ||
| 40 | +| 32 | 重量单位 | 可选单位有:kg, lb, g, oz;<br>默认为kg | | ||
| 41 | +| 33 | 商品条形码 | 最多100字符 | | ||
| 42 | +| 34 | 商品库存 | 只支持导入默认地点库存,如需导入其他地点的库存,请到【库存列表】导入。最多输入9位整数 | | ||
| 43 | +| 35 | 尺寸信息 | 按长宽高顺序填写,用「英文逗号」隔开,单位默认为英寸 | | ||
| 44 | +| 36 | 原产地国别 | 请填写国家代码 | | ||
| 45 | +| 37 | HS(协调制度)代码 | 6-12纯数字 | | ||
| 46 | +| 38 | 商品图片* | 商品属性为M(商品主体)与S(单一款式商品)的行可填入商品图URL:<br>1.若商品主图字段有值,则填入的图片URL为商品副图(可选填)<br>2.若商品主图字段为空,则必须至少填入一个图片URL,且首个URL为商品主图URL,其他为商品副图<br>3.填多个URL可用「英文逗号」隔开<br><br>商品属性为P(子款式)的行可填入子款式图URL:<br>1.仅支持填入一个URL作为子款式图片,填入多个时默认导入第一个;若不填入,则默认不需要图片<br>2.同一商品的部分子款式上传图片,则其余款式也需要上传,否则默认填入商品主图<br> | | ||
| 47 | +| 39 | 商品备注 | 最多输入500个字 | | ||
| 48 | +| 40 | 款式备注 | 最多输入20个字 | | ||
| 49 | +| 41 | 商品主图 | 1.只需商品属性为M(商品主体)与S(单一款式商品)的行填写(可选填)<br>2.仅能填入一个图片URL作为商品主图,填入多个时默认只导入第一个 | | ||
| 50 | +| 42 | 如需导入元字段, 请在此行增添【元字段-命名空间和密钥】作为表头,例:元字段-test.111 | 1. 请确认导入的元字段在店铺后台已创建;<br>2. 只需商品属性为M(商品主体)与S(单一款式商品)的行填写元字段值(可选填)<br>3.各类型元字段填写规范详见帮助文档:https://helpcenter.shoplazza.com/hc/zh-cn/articles/37520605874457 | | ||
| 51 | + | ||
| 52 | +## 示例数据 | ||
| 53 | + | ||
| 54 | +模板中包含以下示例数据,展示了多款式商品(M+P)和单一款式商品(S)的格式: | ||
| 55 | + | ||
| 56 | +### 商品示例:Legendary Whitetails Men's Buck Camp Flannel Shirt | ||
| 57 | + | ||
| 58 | +#### 行 4 - 商品属性: M | ||
| 59 | + | ||
| 60 | +**基本信息:** | ||
| 61 | +- 商品标题*: `Legendary Whitetails Men's Buck Camp Flannel Shirt` | ||
| 62 | +- 商品属性*: `M` | ||
| 63 | +- 商品副标题: `100% Cotton | ||
| 64 | +We recommend ordering a size down if you prefer a closer fit | ||
| 65 | +Purchase from Amazon seller Legendary Whitetails to ensure you receive an authentic Legendary Whitetails branded Buck Camp F...` | ||
| 66 | +- 商品描述: `<p>A hunter’s wardrobe is not complete without a great flannel. Our exclusive plaids are made from 100% cotton soft brushed flannel. Featuring double pleat back for ease of movement and contr...` | ||
| 67 | + | ||
| 68 | +**SEO信息:** | ||
| 69 | +- SEO标题: `Legendary Whitetails Men's Buck Camp Flannel Shirt` | ||
| 70 | +- SEO描述: `Featuring double pleat back for ease of movement and contrasting corduroy lined collar and cuffs for a great look and lasting durability.` | ||
| 71 | +- SEO URL Handle: `products/maternity-lace-cardigan-dress-photo-shoot_3a09` | ||
| 72 | +- SEO关键词: `新品,热卖,爆款` | ||
| 73 | + | ||
| 74 | +**状态设置:** | ||
| 75 | +- 商品上架: `Y` | ||
| 76 | +- 需要物流: `Y` | ||
| 77 | +- 商品收税: `Y` | ||
| 78 | +- 启用虚拟销量: `N` | ||
| 79 | +- 虚拟销量值: `100` | ||
| 80 | +- 跟踪库存: `Y` | ||
| 81 | +- 库存规则*: `1` | ||
| 82 | + | ||
| 83 | +**分类信息:** | ||
| 84 | +- 专辑名称: `衬衣,热卖` | ||
| 85 | +- 标签: `新品,热卖,爆款` | ||
| 86 | +- 供应商名称: `Amazon` | ||
| 87 | +- 供应商URL: `https://www.amazon.com/Legendary-Whitetails-Buck-Flannels-Large/dp/B01KTUMBOI/ref=sr_1_1?s=fashion-mens-intl-ship&ie=UTF8&qid=1543038722&sr=1-1` | ||
| 88 | + | ||
| 89 | +**款式信息:** | ||
| 90 | +- 款式1: `SIZE` | ||
| 91 | +- 款式2: `COLOR` | ||
| 92 | + | ||
| 93 | +**其他信息:** | ||
| 94 | +- 商品图片*: `https://m.media-amazon.com/images/S/aplus-seller-content-images-us-east-1/ATVPDKIKX0DER/A1ABYS4IVXNT9X/27b061cf-ccd1-4c4c-82fd-6519a84b60c3.jpg` | ||
| 95 | + | ||
| 96 | +#### 行 5 - 商品属性: P | ||
| 97 | + | ||
| 98 | +**基本信息:** | ||
| 99 | +- 商品标题*: `Legendary Whitetails Men's Buck Camp Flannel Shirt` | ||
| 100 | +- 商品属性*: `P` | ||
| 101 | + | ||
| 102 | +**款式信息:** | ||
| 103 | +- 款式1: `S` | ||
| 104 | +- 款式2: `red` | ||
| 105 | + | ||
| 106 | +**价格库存:** | ||
| 107 | +- 商品售价*: `88.99` | ||
| 108 | +- 商品原价: `149.99` | ||
| 109 | +- 商品SKU: `LW-TS-RD-S1` | ||
| 110 | +- 商品重量: `0.2` | ||
| 111 | +- 重量单位: `kg` | ||
| 112 | +- 商品库存: `100` | ||
| 113 | + | ||
| 114 | +#### 行 6 - 商品属性: P | ||
| 115 | + | ||
| 116 | +**基本信息:** | ||
| 117 | +- 商品标题*: `Legendary Whitetails Men's Buck Camp Flannel Shirt` | ||
| 118 | +- 商品属性*: `P` | ||
| 119 | + | ||
| 120 | +**款式信息:** | ||
| 121 | +- 款式1: `S` | ||
| 122 | +- 款式2: `black` | ||
| 123 | + | ||
| 124 | +**价格库存:** | ||
| 125 | +- 商品售价*: `88.99` | ||
| 126 | +- 商品原价: `149.99` | ||
| 127 | +- 商品SKU: `LW-TS-BK-S1` | ||
| 128 | +- 商品重量: `0.2` | ||
| 129 | +- 重量单位: `kg` | ||
| 130 | +- 商品库存: `100` | ||
| 131 | + | ||
| 132 | +#### 行 7 - 商品属性: P | ||
| 133 | + | ||
| 134 | +**基本信息:** | ||
| 135 | +- 商品标题*: `Legendary Whitetails Men's Buck Camp Flannel Shirt` | ||
| 136 | +- 商品属性*: `P` | ||
| 137 | + | ||
| 138 | +**款式信息:** | ||
| 139 | +- 款式1: `S` | ||
| 140 | +- 款式2: `army` | ||
| 141 | + | ||
| 142 | +**价格库存:** | ||
| 143 | +- 商品售价*: `88.99` | ||
| 144 | +- 商品原价: `149.99` | ||
| 145 | +- 商品SKU: `LW-TS-AM-S1` | ||
| 146 | +- 商品重量: `0.2` | ||
| 147 | +- 重量单位: `kg` | ||
| 148 | +- 商品库存: `100` | ||
| 149 | + | ||
| 150 | +#### 行 8 - 商品属性: P | ||
| 151 | + | ||
| 152 | +**基本信息:** | ||
| 153 | +- 商品标题*: `Legendary Whitetails Men's Buck Camp Flannel Shirt` | ||
| 154 | +- 商品属性*: `P` | ||
| 155 | + | ||
| 156 | +**款式信息:** | ||
| 157 | +- 款式1: `L` | ||
| 158 | +- 款式2: `red` | ||
| 159 | + | ||
| 160 | +**价格库存:** | ||
| 161 | +- 商品售价*: `88.99` | ||
| 162 | +- 商品原价: `149.99` | ||
| 163 | +- 商品SKU: `LW-TS-RD-M1` | ||
| 164 | +- 商品重量: `0.2` | ||
| 165 | +- 重量单位: `kg` | ||
| 166 | +- 商品库存: `100` | ||
| 167 | + | ||
| 168 | +#### 行 9 - 商品属性: P | ||
| 169 | + | ||
| 170 | +**基本信息:** | ||
| 171 | +- 商品标题*: `Legendary Whitetails Men's Buck Camp Flannel Shirt` | ||
| 172 | +- 商品属性*: `P` | ||
| 173 | + | ||
| 174 | +**款式信息:** | ||
| 175 | +- 款式1: `L` | ||
| 176 | +- 款式2: `black` | ||
| 177 | + | ||
| 178 | +**价格库存:** | ||
| 179 | +- 商品售价*: `88.99` | ||
| 180 | +- 商品原价: `149.99` | ||
| 181 | +- 商品SKU: `LW-TS-BK-M1` | ||
| 182 | +- 商品重量: `0.2` | ||
| 183 | +- 重量单位: `kg` | ||
| 184 | +- 商品库存: `100` | ||
| 185 | + | ||
| 186 | +#### 行 10 - 商品属性: P | ||
| 187 | + | ||
| 188 | +**基本信息:** | ||
| 189 | +- 商品标题*: `Legendary Whitetails Men's Buck Camp Flannel Shirt` | ||
| 190 | +- 商品属性*: `P` | ||
| 191 | + | ||
| 192 | +**款式信息:** | ||
| 193 | +- 款式1: `L` | ||
| 194 | +- 款式2: `army` | ||
| 195 | + | ||
| 196 | +**价格库存:** | ||
| 197 | +- 商品售价*: `88.99` | ||
| 198 | +- 商品原价: `149.99` | ||
| 199 | +- 商品SKU: `LW-TS-AM-M1` | ||
| 200 | +- 商品重量: `0.2` | ||
| 201 | +- 重量单位: `kg` | ||
| 202 | +- 商品库存: `100` | ||
| 203 | + | ||
| 204 | +#### 行 11 - 商品属性: P | ||
| 205 | + | ||
| 206 | +**基本信息:** | ||
| 207 | +- 商品标题*: `Legendary Whitetails Men's Buck Camp Flannel Shirt` | ||
| 208 | +- 商品属性*: `P` | ||
| 209 | + | ||
| 210 | +**款式信息:** | ||
| 211 | +- 款式1: `XL` | ||
| 212 | +- 款式2: `red` | ||
| 213 | + | ||
| 214 | +**价格库存:** | ||
| 215 | +- 商品售价*: `88.99` | ||
| 216 | +- 商品原价: `149.99` | ||
| 217 | +- 商品SKU: `LW-TS-RD-L1` | ||
| 218 | +- 商品重量: `0.2` | ||
| 219 | +- 重量单位: `kg` | ||
| 220 | +- 商品库存: `100` | ||
| 221 | + | ||
| 222 | +#### 行 12 - 商品属性: P | ||
| 223 | + | ||
| 224 | +**基本信息:** | ||
| 225 | +- 商品标题*: `Legendary Whitetails Men's Buck Camp Flannel Shirt` | ||
| 226 | +- 商品属性*: `P` | ||
| 227 | + | ||
| 228 | +**款式信息:** | ||
| 229 | +- 款式1: `XL` | ||
| 230 | +- 款式2: `black` | ||
| 231 | + | ||
| 232 | +**价格库存:** | ||
| 233 | +- 商品售价*: `88.99` | ||
| 234 | +- 商品原价: `149.99` | ||
| 235 | +- 商品SKU: `LW-TS-BK-L1` | ||
| 236 | +- 商品重量: `0.2` | ||
| 237 | +- 重量单位: `kg` | ||
| 238 | +- 商品库存: `100` | ||
| 239 | + | ||
| 240 | +#### 行 13 - 商品属性: P | ||
| 241 | + | ||
| 242 | +**基本信息:** | ||
| 243 | +- 商品标题*: `Legendary Whitetails Men's Buck Camp Flannel Shirt` | ||
| 244 | +- 商品属性*: `P` | ||
| 245 | + | ||
| 246 | +**款式信息:** | ||
| 247 | +- 款式1: `XL` | ||
| 248 | +- 款式2: `army` | ||
| 249 | + | ||
| 250 | +**价格库存:** | ||
| 251 | +- 商品售价*: `88.99` | ||
| 252 | +- 商品原价: `149.99` | ||
| 253 | +- 商品SKU: `LW-TS-AM-L1` | ||
| 254 | +- 商品重量: `0.2` | ||
| 255 | +- 重量单位: `kg` | ||
| 256 | +- 商品库存: `100` | ||
| 257 | + | ||
| 258 | +--- | ||
| 259 | + | ||
| 260 | +### 商品示例:Jabra Move Wireless Stereo Headphones | ||
| 261 | + | ||
| 262 | +#### 行 14 - 商品属性: S | ||
| 263 | + | ||
| 264 | +**基本信息:** | ||
| 265 | +- 商品标题*: `Jabra Move Wireless Stereo Headphones` | ||
| 266 | +- 商品属性*: `S` | ||
| 267 | +- 商品副标题: `Listen to your tunes and never miss a call | ||
| 268 | +Ultra-lightweight and adjustable headband fits all head-types | ||
| 269 | +Durable stainless steel headband and dirt resistant fabric for life on the move.40mm Dynamic...` | ||
| 270 | +- 商品描述: `Clean Scandinavian design meets crisp digital sound. Get a perfect fit with the ultra-light, comfortable headband. Choose from four modern colors: Cayenne (Red), Cobalt (Blue), Coal (Black) and Gold.` | ||
| 271 | + | ||
| 272 | +**SEO信息:** | ||
| 273 | +- SEO URL Handle: `products/maternity-rainbow-stripe-skinny-bodycon-dress_5027` | ||
| 274 | + | ||
| 275 | +**状态设置:** | ||
| 276 | +- 商品上架: `Y` | ||
| 277 | +- 需要物流: `Y` | ||
| 278 | +- 商品收税: `N` | ||
| 279 | +- 启用虚拟销量: `N` | ||
| 280 | +- 虚拟销量值: `100` | ||
| 281 | +- 跟踪库存: `N` | ||
| 282 | +- 库存规则*: `2` | ||
| 283 | + | ||
| 284 | +**分类信息:** | ||
| 285 | +- 专辑名称: `头戴式耳机` | ||
| 286 | +- 标签: `耳机,头戴式,爆款` | ||
| 287 | +- 供应商名称: `Amazon` | ||
| 288 | +- 供应商URL: `https://www.amazon.com/Jabra-Move-Wireless-Stereo-Headphones/dp/B00MR8Z28S/ref=br_asw_pdt-2?pf_rd_m=ATVPDKIKX0DER&pf_rd_s=&pf_rd_r=8NAPN9R973J85P2KDX4G&pf_rd_t=36701&pf_rd_p=a08731ea-e1c2-4f7f-a56e...` | ||
| 289 | + | ||
| 290 | +**价格库存:** | ||
| 291 | +- 商品售价*: `49.99` | ||
| 292 | +- 商品原价: `99.99` | ||
| 293 | +- 商品SKU: `B00MR8Z28S` | ||
| 294 | +- 商品重量: `0.3` | ||
| 295 | +- 重量单位: `lb` | ||
| 296 | +- 商品库存: `500` | ||
| 297 | + | ||
| 298 | +**其他信息:** | ||
| 299 | +- 商品图片*: `https://images-na.ssl-images-amazon.com/images/I/416k5ZUd6lL.jpg` | ||
| 300 | + | ||
| 301 | +--- | ||
| 302 | + | ||
| 303 | + | ||
| 304 | +## 重要说明 | ||
| 305 | + | ||
| 306 | +### 商品属性字段说明 | ||
| 307 | + | ||
| 308 | +商品属性字段是必填项,用于标识商品类型: | ||
| 309 | + | ||
| 310 | +- **M (商品主体)**: 多款式商品的首行,填写商品主体信息 | ||
| 311 | + - 需要填写:商品标题、描述、SEO信息、分类等商品主体信息 | ||
| 312 | + - 不需要填写:价格、库存、SKU等子款式信息 | ||
| 313 | + | ||
| 314 | +- **P (子款式)**: 多款式商品的非首行,填写子款式信息 | ||
| 315 | + - 需要填写:商品标题(与M行一致)、款式值、价格、库存、SKU等 | ||
| 316 | + - 不需要填写:商品描述、SEO信息等商品主体信息 | ||
| 317 | + | ||
| 318 | +- **S (单一款式商品)**: 单一款式商品,一行包含所有信息 | ||
| 319 | + - 需要填写:所有商品信息,包括标题、描述、价格、库存等 | ||
| 320 | + | ||
| 321 | +### 字段填写规则 | ||
| 322 | + | ||
| 323 | +1. **商品ID**: 系统自动生成,新增商品无需填写 | ||
| 324 | +2. **商品标题***: 必填,最多255字符。同一商品的子款式标题必须与商品主体标题一致 | ||
| 325 | +3. **商品属性***: 必填,只能填写 M、P 或 S | ||
| 326 | +4. **商品售价***: 必填,最多9位正整数,2位小数 | ||
| 327 | +5. **商品SKU**: 必填,最多255字符 | ||
| 328 | +6. **商品图片***: 必填(M和S类型),可填写多个图片URL,用逗号分隔 | ||
| 329 | +7. **库存规则***: 当跟踪库存为Y时必填,可选值:1(库存为0允许购买)、2(库存为0不允许购买)、3(库存为0自动下架) | ||
| 330 | + | ||
| 331 | +### 数据格式要求 | ||
| 332 | + | ||
| 333 | +- 日期时间格式:`YYYY-MM-DD HH:MM:SS` | ||
| 334 | +- 布尔值:使用 `Y` 或 `N` | ||
| 335 | +- 多个值:使用英文逗号分隔 | ||
| 336 | +- HTML内容:商品描述支持HTML代码 | ||
| 337 | +- URL格式:图片URL和供应商URL需为完整URL | ||
| 338 | + | ||
| 339 | +### 注意事项 | ||
| 340 | + | ||
| 341 | +1. 同一商品的子款式行必须紧跟在商品主体行之后,中间不能插入其他商品 | ||
| 342 | +2. 商品标题在所有子款式行中必须保持一致 | ||
| 343 | +3. 图片URL必须是可访问的完整URL | ||
| 344 | +4. 价格、库存等数值字段不能包含非数字字符(除小数点外) | ||
| 345 | +5. 导入前请确保所有必填字段都已正确填写 | ||
| 346 | + |
docs/索引字段说明.md
| @@ -16,10 +16,127 @@ | @@ -16,10 +16,127 @@ | ||
| 16 | - 返回的结果格式约定为店匠系列的 SPU/SKU嵌套结构。 | 16 | - 返回的结果格式约定为店匠系列的 SPU/SKU嵌套结构。 |
| 17 | - 支撑 facet/过滤/排序业务需求:用户可以选择任何一个 keyword 或 HKText 类型的字段做筛选、聚合;也可以选择任何一个数值型字段做 Range 过滤或排序。 | 17 | - 支撑 facet/过滤/排序业务需求:用户可以选择任何一个 keyword 或 HKText 类型的字段做筛选、聚合;也可以选择任何一个数值型字段做 Range 过滤或排序。 |
| 18 | 18 | ||
| 19 | -## 数据源调研 | 19 | +## 关键字段 |
| 20 | 20 | ||
| 21 | -店匠的商品结构: | 21 | +参考1:spu表 & sku表、数据源《商品导入模板》 |
| 22 | +### SPU表(shoplazza_product_spu) | ||
| 23 | + | ||
| 24 | +主要字段: | ||
| 25 | +- `id`: BIGINT - 主键ID | ||
| 26 | +- `tenant_id`: BIGINT - 租户ID | ||
| 27 | +- `handle`: VARCHAR(255) - URL handle | ||
| 28 | +- `title`: VARCHAR(512) - 商品标题 | ||
| 29 | +- `brief`: VARCHAR(512) - 商品简介 | ||
| 30 | +- `description`: TEXT - 商品描述 | ||
| 31 | +- `vendor`: VARCHAR(255) - 供应商/品牌 | ||
| 32 | +- `category`: VARCHAR(255) - 类目 | ||
| 33 | +- `tags`: VARCHAR(1024) - 标签 | ||
| 34 | +- `seo_title`: VARCHAR(512) - SEO标题 | ||
| 35 | +- `seo_description`: TEXT - SEO描述 | ||
| 36 | +- `seo_keywords`: VARCHAR(1024) - SEO关键词 | ||
| 37 | +- `image_src`: VARCHAR(500) - 图片URL | ||
| 38 | +- `create_time`: DATETIME - 创建时间 | ||
| 39 | +- `update_time`: DATETIME - 更新时间 | ||
| 40 | +- `shoplazza_created_at`: DATETIME - 店匠创建时间 | ||
| 41 | +- `shoplazza_updated_at`: DATETIME - 店匠更新时间 | ||
| 42 | + | ||
| 43 | +spu表全部字段 | ||
| 44 | +"Field" "Type" "Null" "Key" "Default" "Extra" | ||
| 45 | +"id" "bigint(20)" "NO" "PRI" "auto_increment" | ||
| 46 | +"shop_id" "bigint(20)" "NO" "MUL" "" | ||
| 47 | +"shoplazza_id" "varchar(64)" "NO" "" "" | ||
| 48 | +"handle" "varchar(255)" "YES" "MUL" "" | ||
| 49 | +"title" "varchar(500)" "NO" "" "" | ||
| 50 | +"brief" "varchar(1000)" "YES" "" "" | ||
| 51 | +"description" "text" "YES" "" "" | ||
| 52 | +"spu" "varchar(100)" "YES" "" "" | ||
| 53 | +"vendor" "varchar(255)" "YES" "" "" | ||
| 54 | +"vendor_url" "varchar(500)" "YES" "" "" | ||
| 55 | +"seo_title" "varchar(500)" "YES" "" "" | ||
| 56 | +"seo_description" "text" "YES" "" "" | ||
| 57 | +"seo_keywords" "text" "YES" "" "" | ||
| 58 | +"image_src" "varchar(500)" "YES" "" "" | ||
| 59 | +"image_width" "int(11)" "YES" "" "" | ||
| 60 | +"image_height" "int(11)" "YES" "" "" | ||
| 61 | +"image_path" "varchar(255)" "YES" "" "" | ||
| 62 | +"image_alt" "varchar(500)" "YES" "" "" | ||
| 63 | +"inventory_policy" "varchar(50)" "YES" "" "" | ||
| 64 | +"inventory_quantity" "int(11)" "YES" "" "0" "" | ||
| 65 | +"inventory_tracking" "tinyint(1)" "YES" "" "0" "" | ||
| 66 | +"published" "tinyint(1)" "YES" "" "0" "" | ||
| 67 | +"published_at" "datetime" "YES" "MUL" "" | ||
| 68 | +"requires_shipping" "tinyint(1)" "YES" "" "1" "" | ||
| 69 | +"taxable" "tinyint(1)" "YES" "" "0" "" | ||
| 70 | +"fake_sales" "int(11)" "YES" "" "0" "" | ||
| 71 | +"display_fake_sales" "tinyint(1)" "YES" "" "0" "" | ||
| 72 | +"mixed_wholesale" "tinyint(1)" "YES" "" "0" "" | ||
| 73 | +"need_variant_image" "tinyint(1)" "YES" "" "0" "" | ||
| 74 | +"has_only_default_variant" "tinyint(1)" "YES" "" "0" "" | ||
| 75 | +"tags" "text" "YES" "" "" | ||
| 76 | +"note" "text" "YES" "" "" | ||
| 77 | +"category" "varchar(255)" "YES" "" "" | ||
| 78 | +"shoplazza_created_at" "datetime" "YES" "" "" | ||
| 79 | +"shoplazza_updated_at" "datetime" "YES" "MUL" "" | ||
| 80 | +"tenant_id" "bigint(20)" "NO" "MUL" "" | ||
| 81 | +"creator" "varchar(64)" "YES" "" "" "" | ||
| 82 | +"create_time" "datetime" "NO" "" "CURRENT_TIMESTAMP" "" | ||
| 83 | +"updater" "varchar(64)" "YES" "" "" "" | ||
| 84 | +"update_time" "datetime" "NO" "" "CURRENT_TIMESTAMP" "on update CURRENT_TIMESTAMP" | ||
| 85 | +"deleted" "bit(1)" "NO" "" "b'0'" "" | ||
| 22 | 86 | ||
| 87 | + | ||
| 88 | +### SKU表(shoplazza_product_sku) | ||
| 89 | + | ||
| 90 | +主要字段: | ||
| 91 | +- `id`: BIGINT - 主键ID(对应variant_id) | ||
| 92 | +- `spu_id`: BIGINT - SPU ID(关联字段) | ||
| 93 | +- `title`: VARCHAR(500) - 变体标题 | ||
| 94 | +- `price`: DECIMAL(10,2) - 价格 | ||
| 95 | +- `compare_at_price`: DECIMAL(10,2) - 原价 | ||
| 96 | +- `sku`: VARCHAR(100) - SKU编码 | ||
| 97 | +- `inventory_quantity`: INT(11) - 库存数量 | ||
| 98 | +- `option1`: VARCHAR(255) - 选项1 | ||
| 99 | +- `option2`: VARCHAR(255) - 选项2 | ||
| 100 | +- `option3`: VARCHAR(255) - 选项3 | ||
| 101 | + | ||
| 102 | +sku全部字段 | ||
| 103 | +"Field" "Type" "Null" "Key" "Default" "Extra" | ||
| 104 | +"id" "bigint(20)" "NO" "PRI" "auto_increment" | ||
| 105 | +"spu_id" "bigint(20)" "NO" "MUL" "" | ||
| 106 | +"shop_id" "bigint(20)" "NO" "MUL" "" | ||
| 107 | +"shoplazza_id" "varchar(64)" "NO" "" "" | ||
| 108 | +"shoplazza_product_id" "varchar(64)" "NO" "MUL" "" | ||
| 109 | +"shoplazza_image_id" "varchar(64)" "YES" "" "" | ||
| 110 | +"title" "varchar(500)" "YES" "" "" | ||
| 111 | +"sku" "varchar(100)" "YES" "MUL" "" | ||
| 112 | +"barcode" "varchar(100)" "YES" "" "" | ||
| 113 | +"position" "int(11)" "YES" "" "0" "" | ||
| 114 | +"price" "decimal(10,2)" "YES" "" "" | ||
| 115 | +"compare_at_price" "decimal(10,2)" "YES" "" "" | ||
| 116 | +"cost_price" "decimal(10,2)" "YES" "" "" | ||
| 117 | +"option1" "varchar(255)" "YES" "" "" | ||
| 118 | +"option2" "varchar(255)" "YES" "" "" | ||
| 119 | +"option3" "varchar(255)" "YES" "" "" | ||
| 120 | +"inventory_quantity" "int(11)" "YES" "" "0" "" | ||
| 121 | +"weight" "decimal(10,2)" "YES" "" "" | ||
| 122 | +"weight_unit" "varchar(10)" "YES" "" "" | ||
| 123 | +"image_src" "varchar(500)" "YES" "" "" | ||
| 124 | +"wholesale_price" "json" "YES" "" "" | ||
| 125 | +"note" "text" "YES" "" "" | ||
| 126 | +"extend" "json" "YES" "" "" | ||
| 127 | +"shoplazza_created_at" "datetime" "YES" "" "" | ||
| 128 | +"shoplazza_updated_at" "datetime" "YES" "" "" | ||
| 129 | +"tenant_id" "bigint(20)" "NO" "MUL" "" | ||
| 130 | +"creator" "varchar(64)" "YES" "" "" "" | ||
| 131 | +"create_time" "datetime" "NO" "" "CURRENT_TIMESTAMP" "" | ||
| 132 | +"updater" "varchar(64)" "YES" "" "" "" | ||
| 133 | +"update_time" "datetime" "NO" "" "CURRENT_TIMESTAMP" "on update CURRENT_TIMESTAMP" | ||
| 134 | +"deleted" "bit(1)" "NO" "" "b'0'" "" | ||
| 135 | + | ||
| 136 | +参考2:波哥 索引《店匠指南》 -> 商品详解 | ||
| 137 | + | ||
| 138 | + | ||
| 139 | +参考3: 店匠的商品结构 - 民丰: | ||
| 23 | 固定字段: | 140 | 固定字段: |
| 24 | 1、必填:品名 | 141 | 1、必填:品名 |
| 25 | 2、非必填:副标题、类目、专辑、标签、供应商、市场 | 142 | 2、非必填:副标题、类目、专辑、标签、供应商、市场 |
| @@ -33,7 +150,110 @@ | @@ -33,7 +150,110 @@ | ||
| 33 | 150 | ||
| 34 | 商品信息:品名、颜色、尺码,基本上就这三个信息 | 151 | 商品信息:品名、颜色、尺码,基本上就这三个信息 |
| 35 | 152 | ||
| 36 | -spu/sku表: | 153 | + |
| 154 | +### 分类 | ||
| 155 | +最多三级分类,商品可以指向任何级别的分类 | ||
| 156 | +Field Type | ||
| 157 | +category varchar(255) | ||
| 158 | +category_id bigint(20) | ||
| 159 | +category_google_id bigint(20) | ||
| 160 | +category_level int(11) | ||
| 161 | +category_path varchar(500) | ||
| 162 | + | ||
| 163 | + | ||
| 164 | +### 属性 | ||
| 165 | +1. 组织ES输入数据的时候,需要为sku拼接spu的 option1 option2 option3,作为属性名称(比如“颜色”),sku的 option1 option2 option3 作为属性值(比如“白色”) | ||
| 166 | +2. 有以下方案: TODO 可以选择其中一种,或者2用于填充3用于搜索: | ||
| 167 | +1)铺平展开,只支持三个 | ||
| 168 | +attr1 | ||
| 169 | +attr2 | ||
| 170 | +attr3 | ||
| 171 | +option1 | ||
| 172 | +option2 | ||
| 173 | +option3 | ||
| 174 | +2)nested,支持多个,动态。 查询性能低 | ||
| 175 | +"specifications": { | ||
| 176 | + "type": "nested", | ||
| 177 | + "properties": { | ||
| 178 | + "name": { "type": "keyword" }, // "颜色", "容量" | ||
| 179 | + "value": { "type": "keyword" } // "白色", "256GB" | ||
| 180 | + } | ||
| 181 | +}, | ||
| 182 | +3)平铺展开。写入时从 specifications 提取并填充这些字段,查询性能高。 | ||
| 183 | +"properties": { | ||
| 184 | + "color": { "type": "keyword" }, | ||
| 185 | + "capacity": { "type": "keyword" }, | ||
| 186 | + "network": { "type": "keyword" }, | ||
| 187 | + "edition": { "type": "keyword" } | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +### status | ||
| 191 | +1. 商品下架等状态 | ||
| 192 | +2. 无库存,是用status记录,提升查询效率 | ||
| 193 | + | ||
| 194 | +### 多语言 | ||
| 195 | +索引:中英文(两套),如果商品资料是中文,则我们系统使用 谷歌翻译(和平台使用的翻译工具对齐) 自动翻译为英文。 | ||
| 196 | +检索:将非中英文,翻译成英文后,再检索英文。 | ||
| 197 | + | ||
| 198 | +## 分面 | ||
| 199 | +1. 分类 | ||
| 200 | +2. 标签。这个是个扁平的结构,不是像属性那样k-v-pair的 | ||
| 201 | +不用考虑属性。这个 option1 2 3不能放在外面筛选。他的定位是spu内部的东西,不是外部用于筛选商品的。 即使外面要有那种 动态筛选 比如搜手机出品牌、款式 这种, 不是对应的这个字段,会是另外的字段。 | ||
| 202 | + | ||
| 203 | + | ||
| 204 | +### 字段预处理 | ||
| 205 | +需要用「英文逗号」隔开,作为list输入的字段: | ||
| 206 | +SEO关键词 | ||
| 207 | +专辑名称 | ||
| 208 | +标签 | ||
| 209 | +尺寸信息 | ||
| 210 | + | ||
| 211 | + | ||
| 212 | +## SPU 与 SKU 的协同设计 | ||
| 213 | + | ||
| 214 | +以下方案: | ||
| 215 | +1. sku为索引单位。使用 collapse 按 spu_id 折叠 | ||
| 216 | +需要考虑大量的字段冗余 | ||
| 217 | + | ||
| 218 | +2. spu为单位。 sku的title作为 spu 的sku_titles 属性。 | ||
| 219 | + 除了title, brielf description seo相关 cate tags vendor所有影响相关性的字段都在spu。 sku只有一个title。所以,可以以spu为单位,sku的title作为spu的一个字段,以list形式灌入,假设一个spu有三个sku,那么这个sku_titles字段有三个值,打分的时候按max取得打分,并且我们可以得到这三个sku的title匹配的得分,因此好决定sku的排序。 | ||
| 220 | + | ||
| 221 | +3. sku 作为nested | ||
| 222 | + | ||
| 223 | + | ||
| 224 | + | ||
| 225 | +参考 [](https://blog.csdn.net/csdn_tom_168/article/details/150432666) | ||
| 226 | +方案一:独立索引 | ||
| 227 | +spu-catalog-*:用于品牌、类目、商品介绍等宏观搜索。 | ||
| 228 | +sku-catalog-*:用于具体规格搜索、下单。 | ||
| 229 | +优势:职责分离,查询更高效。 | ||
| 230 | + | ||
| 231 | +方案二:联合查询 | ||
| 232 | +GET /spu-read,sku-read/_search | ||
| 233 | +适用于“模糊搜索 → 跳转详情页”的场景。 | ||
| 234 | + | ||
| 235 | +方案三:父子文档(不推荐) | ||
| 236 | +join 类型维护 SPU-SKU 关系。 | ||
| 237 | +性能差,维护复杂,不适用于高并发搜索场景。 | ||
| 238 | + | ||
| 239 | + | ||
| 240 | +## rank - 相关性 | ||
| 241 | + | ||
| 242 | + | ||
| 243 | +## rank - 提权 | ||
| 244 | + | ||
| 245 | +function_score 提升相关性 | ||
| 246 | +```json | ||
| 247 | +"function_score": { | ||
| 248 | + "functions": [ | ||
| 249 | + { "field_value_factor": { "field": "sales_count", "factor": 0.001, "modifier": "log1p" } }, | ||
| 250 | + { "gauss": { "listed_at": { "scale": "30d" } } } | ||
| 251 | + ], | ||
| 252 | + "boost_mode": "multiply" | ||
| 253 | +} | ||
| 254 | +``` | ||
| 255 | + | ||
| 256 | + | ||
| 37 | 257 | ||
| 38 | 258 | ||
| 39 | ## 索引基本信息 | 259 | ## 索引基本信息 |
| @@ -42,6 +262,17 @@ spu/sku表: | @@ -42,6 +262,17 @@ spu/sku表: | ||
| 42 | - **索引级别**: SPU级别(商品级别) | 262 | - **索引级别**: SPU级别(商品级别) |
| 43 | - **数据结构**: SPU文档包含嵌套的skus数组 | 263 | - **数据结构**: SPU文档包含嵌套的skus数组 |
| 44 | 264 | ||
| 265 | + | ||
| 266 | +## 分片与副本设置 | ||
| 267 | +```json | ||
| 268 | +# 分片数: 根据cpu数量和sku数量决定。 | ||
| 269 | +"settings": { | ||
| 270 | + "number_of_shards": 8, | ||
| 271 | + "number_of_replicas": 1, | ||
| 272 | + "refresh_interval": "15s" | ||
| 273 | +} | ||
| 274 | +``` | ||
| 275 | + | ||
| 45 | ## 索引类型与处理说明 | 276 | ## 索引类型与处理说明 |
| 46 | 277 | ||
| 47 | ### 文本字段 | 278 | ### 文本字段 |
| @@ -186,7 +417,7 @@ spu/sku表: | @@ -186,7 +417,7 @@ spu/sku表: | ||
| 186 | | 索引字段名 | ES字段类型 | 是否索引 | 数据来源表 | 表中字段名 | 表中字段类型 | Boost权重 | 是否返回 | 数据预处理 | 说明 | | 417 | | 索引字段名 | ES字段类型 | 是否索引 | 数据来源表 | 表中字段名 | 表中字段类型 | Boost权重 | 是否返回 | 数据预处理 | 说明 | |
| 187 | |-----------|-----------|---------|-----------|-----------|-------------|-----------|---------|-------------|------| | 418 | |-----------|-----------|---------|-----------|-----------|-------------|-----------|---------|-------------|------| |
| 188 | | vendor | HKText | 是 | SPU表 | vendor | VARCHAR(255) | 1.5 | 是 | | 供应商/品牌,HKText字段自动提供 `vendor.keyword` 用于过滤、聚合 | | 419 | | vendor | HKText | 是 | SPU表 | vendor | VARCHAR(255) | 1.5 | 是 | | 供应商/品牌,HKText字段自动提供 `vendor.keyword` 用于过滤、聚合 | |
| 189 | -| tags | HKText | 是 | SPU表 | tags | VARCHAR(1024) | 1.0 | 是 | | 标签字段,支持模糊搜索;使用 `tags.keyword` 进行精确过滤 | | 420 | +| tags | HKText | 是 | SPU表 | tags | VARCHAR(1024) | 1.0 | 是 | 按逗号分割为list | 标签字段,支持模糊搜索;使用 `tags.keyword` 进行精确过滤 | |
| 190 | | category | HKText | 是 | SPU表 | category | VARCHAR(255) | 1.5 | 是 | | 类目字段,使用 `category.keyword` 进行过滤/分面 | | 421 | | category | HKText | 是 | SPU表 | category | VARCHAR(255) | 1.5 | 是 | | 类目字段,使用 `category.keyword` 进行过滤/分面 | |
| 191 | 422 | ||
| 192 | ### 价格字段 | 423 | ### 价格字段 |
| @@ -306,124 +537,6 @@ spu/sku表: | @@ -306,124 +537,6 @@ spu/sku表: | ||
| 306 | 4. **向量搜索**: title_embedding字段用于语义搜索,需要配合文本查询使用 | 537 | 4. **向量搜索**: title_embedding字段用于语义搜索,需要配合文本查询使用 |
| 307 | 5. **Boost权重**: 不同字段的boost权重影响搜索结果的相关性排序 | 538 | 5. **Boost权重**: 不同字段的boost权重影响搜索结果的相关性排序 |
| 308 | 539 | ||
| 309 | -## 数据来源表结构 | ||
| 310 | - | ||
| 311 | -### SPU表(shoplazza_product_spu) | ||
| 312 | - | ||
| 313 | -主要字段: | ||
| 314 | -- `id`: BIGINT - 主键ID | ||
| 315 | -- `tenant_id`: BIGINT - 租户ID | ||
| 316 | -- `handle`: VARCHAR(255) - URL handle | ||
| 317 | -- `title`: VARCHAR(512) - 商品标题 | ||
| 318 | -- `brief`: VARCHAR(512) - 商品简介 | ||
| 319 | -- `description`: TEXT - 商品描述 | ||
| 320 | -- `vendor`: VARCHAR(255) - 供应商/品牌 | ||
| 321 | -- `category`: VARCHAR(255) - 类目 | ||
| 322 | -- `tags`: VARCHAR(1024) - 标签 | ||
| 323 | -- `seo_title`: VARCHAR(512) - SEO标题 | ||
| 324 | -- `seo_description`: TEXT - SEO描述 | ||
| 325 | -- `seo_keywords`: VARCHAR(1024) - SEO关键词 | ||
| 326 | -- `image_src`: VARCHAR(500) - 图片URL | ||
| 327 | -- `create_time`: DATETIME - 创建时间 | ||
| 328 | -- `update_time`: DATETIME - 更新时间 | ||
| 329 | -- `shoplazza_created_at`: DATETIME - 店匠创建时间 | ||
| 330 | -- `shoplazza_updated_at`: DATETIME - 店匠更新时间 | ||
| 331 | - | ||
| 332 | -spu表全部字段 | ||
| 333 | -"Field" "Type" "Null" "Key" "Default" "Extra" | ||
| 334 | -"id" "bigint(20)" "NO" "PRI" "auto_increment" | ||
| 335 | -"shop_id" "bigint(20)" "NO" "MUL" "" | ||
| 336 | -"shoplazza_id" "varchar(64)" "NO" "" "" | ||
| 337 | -"handle" "varchar(255)" "YES" "MUL" "" | ||
| 338 | -"title" "varchar(500)" "NO" "" "" | ||
| 339 | -"brief" "varchar(1000)" "YES" "" "" | ||
| 340 | -"description" "text" "YES" "" "" | ||
| 341 | -"spu" "varchar(100)" "YES" "" "" | ||
| 342 | -"vendor" "varchar(255)" "YES" "" "" | ||
| 343 | -"vendor_url" "varchar(500)" "YES" "" "" | ||
| 344 | -"seo_title" "varchar(500)" "YES" "" "" | ||
| 345 | -"seo_description" "text" "YES" "" "" | ||
| 346 | -"seo_keywords" "text" "YES" "" "" | ||
| 347 | -"image_src" "varchar(500)" "YES" "" "" | ||
| 348 | -"image_width" "int(11)" "YES" "" "" | ||
| 349 | -"image_height" "int(11)" "YES" "" "" | ||
| 350 | -"image_path" "varchar(255)" "YES" "" "" | ||
| 351 | -"image_alt" "varchar(500)" "YES" "" "" | ||
| 352 | -"inventory_policy" "varchar(50)" "YES" "" "" | ||
| 353 | -"inventory_quantity" "int(11)" "YES" "" "0" "" | ||
| 354 | -"inventory_tracking" "tinyint(1)" "YES" "" "0" "" | ||
| 355 | -"published" "tinyint(1)" "YES" "" "0" "" | ||
| 356 | -"published_at" "datetime" "YES" "MUL" "" | ||
| 357 | -"requires_shipping" "tinyint(1)" "YES" "" "1" "" | ||
| 358 | -"taxable" "tinyint(1)" "YES" "" "0" "" | ||
| 359 | -"fake_sales" "int(11)" "YES" "" "0" "" | ||
| 360 | -"display_fake_sales" "tinyint(1)" "YES" "" "0" "" | ||
| 361 | -"mixed_wholesale" "tinyint(1)" "YES" "" "0" "" | ||
| 362 | -"need_variant_image" "tinyint(1)" "YES" "" "0" "" | ||
| 363 | -"has_only_default_variant" "tinyint(1)" "YES" "" "0" "" | ||
| 364 | -"tags" "text" "YES" "" "" | ||
| 365 | -"note" "text" "YES" "" "" | ||
| 366 | -"category" "varchar(255)" "YES" "" "" | ||
| 367 | -"shoplazza_created_at" "datetime" "YES" "" "" | ||
| 368 | -"shoplazza_updated_at" "datetime" "YES" "MUL" "" | ||
| 369 | -"tenant_id" "bigint(20)" "NO" "MUL" "" | ||
| 370 | -"creator" "varchar(64)" "YES" "" "" "" | ||
| 371 | -"create_time" "datetime" "NO" "" "CURRENT_TIMESTAMP" "" | ||
| 372 | -"updater" "varchar(64)" "YES" "" "" "" | ||
| 373 | -"update_time" "datetime" "NO" "" "CURRENT_TIMESTAMP" "on update CURRENT_TIMESTAMP" | ||
| 374 | -"deleted" "bit(1)" "NO" "" "b'0'" "" | ||
| 375 | - | ||
| 376 | - | ||
| 377 | - | ||
| 378 | - | ||
| 379 | -### SKU表(shoplazza_product_sku) | ||
| 380 | - | ||
| 381 | -主要字段: | ||
| 382 | -- `id`: BIGINT - 主键ID(对应variant_id) | ||
| 383 | -- `spu_id`: BIGINT - SPU ID(关联字段) | ||
| 384 | -- `title`: VARCHAR(500) - 变体标题 | ||
| 385 | -- `price`: DECIMAL(10,2) - 价格 | ||
| 386 | -- `compare_at_price`: DECIMAL(10,2) - 原价 | ||
| 387 | -- `sku`: VARCHAR(100) - SKU编码 | ||
| 388 | -- `inventory_quantity`: INT(11) - 库存数量 | ||
| 389 | -- `option1`: VARCHAR(255) - 选项1 | ||
| 390 | -- `option2`: VARCHAR(255) - 选项2 | ||
| 391 | -- `option3`: VARCHAR(255) - 选项3 | ||
| 392 | - | ||
| 393 | -sku全部字段 | ||
| 394 | -"Field" "Type" "Null" "Key" "Default" "Extra" | ||
| 395 | -"id" "bigint(20)" "NO" "PRI" "auto_increment" | ||
| 396 | -"spu_id" "bigint(20)" "NO" "MUL" "" | ||
| 397 | -"shop_id" "bigint(20)" "NO" "MUL" "" | ||
| 398 | -"shoplazza_id" "varchar(64)" "NO" "" "" | ||
| 399 | -"shoplazza_product_id" "varchar(64)" "NO" "MUL" "" | ||
| 400 | -"shoplazza_image_id" "varchar(64)" "YES" "" "" | ||
| 401 | -"title" "varchar(500)" "YES" "" "" | ||
| 402 | -"sku" "varchar(100)" "YES" "MUL" "" | ||
| 403 | -"barcode" "varchar(100)" "YES" "" "" | ||
| 404 | -"position" "int(11)" "YES" "" "0" "" | ||
| 405 | -"price" "decimal(10,2)" "YES" "" "" | ||
| 406 | -"compare_at_price" "decimal(10,2)" "YES" "" "" | ||
| 407 | -"cost_price" "decimal(10,2)" "YES" "" "" | ||
| 408 | -"option1" "varchar(255)" "YES" "" "" | ||
| 409 | -"option2" "varchar(255)" "YES" "" "" | ||
| 410 | -"option3" "varchar(255)" "YES" "" "" | ||
| 411 | -"inventory_quantity" "int(11)" "YES" "" "0" "" | ||
| 412 | -"weight" "decimal(10,2)" "YES" "" "" | ||
| 413 | -"weight_unit" "varchar(10)" "YES" "" "" | ||
| 414 | -"image_src" "varchar(500)" "YES" "" "" | ||
| 415 | -"wholesale_price" "json" "YES" "" "" | ||
| 416 | -"note" "text" "YES" "" "" | ||
| 417 | -"extend" "json" "YES" "" "" | ||
| 418 | -"shoplazza_created_at" "datetime" "YES" "" "" | ||
| 419 | -"shoplazza_updated_at" "datetime" "YES" "" "" | ||
| 420 | -"tenant_id" "bigint(20)" "NO" "MUL" "" | ||
| 421 | -"creator" "varchar(64)" "YES" "" "" "" | ||
| 422 | -"create_time" "datetime" "NO" "" "CURRENT_TIMESTAMP" "" | ||
| 423 | -"updater" "varchar(64)" "YES" "" "" "" | ||
| 424 | -"update_time" "datetime" "NO" "" "CURRENT_TIMESTAMP" "on update CURRENT_TIMESTAMP" | ||
| 425 | -"deleted" "bit(1)" "NO" "" "b'0'" "" | ||
| 426 | - | ||
| 427 | 540 | ||
| 428 | ## TODO | 541 | ## TODO |
| 429 | 多语言问题。 | 542 | 多语言问题。 |
| @@ -0,0 +1,355 @@ | @@ -0,0 +1,355 @@ | ||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +""" | ||
| 3 | +Convert CSV data to Excel import template. | ||
| 4 | + | ||
| 5 | +Reads CSV file (goods_with_pic.5years_congku.csv.shuf.1w) and generates Excel file | ||
| 6 | +based on the template format (商品导入模板.xlsx). | ||
| 7 | + | ||
| 8 | +Each CSV row corresponds to 1 SPU and 1 SKU, which will be exported as a single | ||
| 9 | +S (Single variant) row in the Excel template. | ||
| 10 | +""" | ||
| 11 | + | ||
| 12 | +import sys | ||
| 13 | +import os | ||
| 14 | +import csv | ||
| 15 | +import random | ||
| 16 | +import argparse | ||
| 17 | +import re | ||
| 18 | +from pathlib import Path | ||
| 19 | +from datetime import datetime, timedelta | ||
| 20 | +import pandas as pd | ||
| 21 | +from openpyxl import load_workbook | ||
| 22 | +from openpyxl.styles import Font, Alignment | ||
| 23 | +from openpyxl.utils import get_column_letter | ||
| 24 | + | ||
| 25 | +# Add parent directory to path | ||
| 26 | +sys.path.insert(0, str(Path(__file__).parent.parent)) | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +def clean_value(value): | ||
| 30 | + """ | ||
| 31 | + Clean and normalize value. | ||
| 32 | + | ||
| 33 | + Args: | ||
| 34 | + value: Value to clean | ||
| 35 | + | ||
| 36 | + Returns: | ||
| 37 | + Cleaned string value | ||
| 38 | + """ | ||
| 39 | + if value is None: | ||
| 40 | + return '' | ||
| 41 | + value = str(value).strip() | ||
| 42 | + # Remove surrounding quotes | ||
| 43 | + if value.startswith('"') and value.endswith('"'): | ||
| 44 | + value = value[1:-1] | ||
| 45 | + return value | ||
| 46 | + | ||
| 47 | + | ||
| 48 | +def parse_csv_row(row: dict) -> dict: | ||
| 49 | + """ | ||
| 50 | + Parse CSV row and extract fields. | ||
| 51 | + | ||
| 52 | + Args: | ||
| 53 | + row: CSV row dictionary | ||
| 54 | + | ||
| 55 | + Returns: | ||
| 56 | + Parsed data dictionary | ||
| 57 | + """ | ||
| 58 | + return { | ||
| 59 | + 'skuId': clean_value(row.get('skuId', '')), | ||
| 60 | + 'name': clean_value(row.get('name', '')), | ||
| 61 | + 'name_pinyin': clean_value(row.get('name_pinyin', '')), | ||
| 62 | + 'create_time': clean_value(row.get('create_time', '')), | ||
| 63 | + 'ruSkuName': clean_value(row.get('ruSkuName', '')), | ||
| 64 | + 'enSpuName': clean_value(row.get('enSpuName', '')), | ||
| 65 | + 'categoryName': clean_value(row.get('categoryName', '')), | ||
| 66 | + 'supplierName': clean_value(row.get('supplierName', '')), | ||
| 67 | + 'brandName': clean_value(row.get('brandName', '')), | ||
| 68 | + 'file_id': clean_value(row.get('file_id', '')), | ||
| 69 | + 'days_since_last_update': clean_value(row.get('days_since_last_update', '')), | ||
| 70 | + 'id': clean_value(row.get('id', '')), | ||
| 71 | + 'imageUrl': clean_value(row.get('imageUrl', '')) | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + | ||
| 75 | +def generate_handle(title: str) -> str: | ||
| 76 | + """ | ||
| 77 | + Generate URL-friendly handle from title. | ||
| 78 | + | ||
| 79 | + Args: | ||
| 80 | + title: Product title | ||
| 81 | + | ||
| 82 | + Returns: | ||
| 83 | + URL-friendly handle (ASCII only) | ||
| 84 | + """ | ||
| 85 | + # Convert to lowercase | ||
| 86 | + handle = title.lower() | ||
| 87 | + | ||
| 88 | + # Remove non-ASCII characters, keep only letters, numbers, spaces, and hyphens | ||
| 89 | + handle = re.sub(r'[^a-z0-9\s-]', '', handle) | ||
| 90 | + | ||
| 91 | + # Replace spaces and multiple hyphens with single hyphen | ||
| 92 | + handle = re.sub(r'[-\s]+', '-', handle) | ||
| 93 | + handle = handle.strip('-') | ||
| 94 | + | ||
| 95 | + # Limit length | ||
| 96 | + if len(handle) > 255: | ||
| 97 | + handle = handle[:255] | ||
| 98 | + | ||
| 99 | + return handle or 'product' | ||
| 100 | + | ||
| 101 | + | ||
| 102 | +def read_csv_file(csv_file: str) -> list: | ||
| 103 | + """ | ||
| 104 | + Read CSV file and return list of parsed rows. | ||
| 105 | + | ||
| 106 | + Args: | ||
| 107 | + csv_file: Path to CSV file | ||
| 108 | + | ||
| 109 | + Returns: | ||
| 110 | + List of parsed CSV data dictionaries | ||
| 111 | + """ | ||
| 112 | + csv_data_list = [] | ||
| 113 | + | ||
| 114 | + with open(csv_file, 'r', encoding='utf-8') as f: | ||
| 115 | + reader = csv.DictReader(f) | ||
| 116 | + for row in reader: | ||
| 117 | + parsed = parse_csv_row(row) | ||
| 118 | + csv_data_list.append(parsed) | ||
| 119 | + | ||
| 120 | + return csv_data_list | ||
| 121 | + | ||
| 122 | + | ||
| 123 | +def csv_to_excel_row(csv_data: dict) -> dict: | ||
| 124 | + """ | ||
| 125 | + Convert CSV data row to Excel template row. | ||
| 126 | + | ||
| 127 | + Each CSV row represents a single product with one variant (S type in Excel). | ||
| 128 | + | ||
| 129 | + Args: | ||
| 130 | + csv_data: Parsed CSV row data | ||
| 131 | + | ||
| 132 | + Returns: | ||
| 133 | + Dictionary mapping Excel column names to values | ||
| 134 | + """ | ||
| 135 | + # Parse create_time | ||
| 136 | + try: | ||
| 137 | + created_at = datetime.strptime(csv_data['create_time'], '%Y-%m-%d %H:%M:%S') | ||
| 138 | + create_time_str = created_at.strftime('%Y-%m-%d %H:%M:%S') | ||
| 139 | + except: | ||
| 140 | + created_at = datetime.now() - timedelta(days=random.randint(1, 365)) | ||
| 141 | + create_time_str = created_at.strftime('%Y-%m-%d %H:%M:%S') | ||
| 142 | + | ||
| 143 | + # Generate title - use name or enSpuName | ||
| 144 | + title = csv_data['name'] or csv_data['enSpuName'] or 'Product' | ||
| 145 | + | ||
| 146 | + # Generate handle - prefer enSpuName, then name_pinyin, then title | ||
| 147 | + handle_source = csv_data['enSpuName'] or csv_data['name_pinyin'] or title | ||
| 148 | + handle = generate_handle(handle_source) | ||
| 149 | + if handle and not handle.startswith('products/'): | ||
| 150 | + handle = f'products/{handle}' | ||
| 151 | + | ||
| 152 | + # Generate SEO fields | ||
| 153 | + seo_title = f"{title} - {csv_data['categoryName']}" if csv_data['categoryName'] else title | ||
| 154 | + seo_description = f"购买{csv_data['brandName']}{title}" if csv_data['brandName'] else title | ||
| 155 | + seo_keywords_parts = [title] | ||
| 156 | + if csv_data['categoryName']: | ||
| 157 | + seo_keywords_parts.append(csv_data['categoryName']) | ||
| 158 | + if csv_data['brandName']: | ||
| 159 | + seo_keywords_parts.append(csv_data['brandName']) | ||
| 160 | + seo_keywords = ','.join(seo_keywords_parts) | ||
| 161 | + | ||
| 162 | + # Generate tags from category and brand | ||
| 163 | + tags_parts = [] | ||
| 164 | + if csv_data['categoryName']: | ||
| 165 | + tags_parts.append(csv_data['categoryName']) | ||
| 166 | + if csv_data['brandName']: | ||
| 167 | + tags_parts.append(csv_data['brandName']) | ||
| 168 | + tags = ','.join(tags_parts) if tags_parts else '' | ||
| 169 | + | ||
| 170 | + # Generate prices (similar to import_tenant2_csv.py) | ||
| 171 | + price = round(random.uniform(50, 500), 2) | ||
| 172 | + compare_at_price = round(price * random.uniform(1.2, 1.5), 2) | ||
| 173 | + cost_price = round(price * 0.6, 2) | ||
| 174 | + | ||
| 175 | + # Generate random stock | ||
| 176 | + inventory_quantity = random.randint(0, 100) | ||
| 177 | + | ||
| 178 | + # Generate random weight | ||
| 179 | + weight = round(random.uniform(0.1, 5.0), 2) | ||
| 180 | + weight_unit = 'kg' | ||
| 181 | + | ||
| 182 | + # Use ruSkuName as SKU title, fallback to name | ||
| 183 | + sku_title = csv_data['ruSkuName'] or csv_data['name'] or 'SKU' | ||
| 184 | + | ||
| 185 | + # Use skuId as SKU code | ||
| 186 | + sku_code = csv_data['skuId'] or '' | ||
| 187 | + | ||
| 188 | + # Generate barcode | ||
| 189 | + try: | ||
| 190 | + sku_id = int(csv_data['skuId']) | ||
| 191 | + barcode = f"BAR{sku_id:08d}" | ||
| 192 | + except: | ||
| 193 | + barcode = '' | ||
| 194 | + | ||
| 195 | + # Build description | ||
| 196 | + description = f"<p>{csv_data['name']}</p>" if csv_data['name'] else '' | ||
| 197 | + | ||
| 198 | + # Build brief (subtitle) | ||
| 199 | + brief = csv_data['name'] or '' | ||
| 200 | + | ||
| 201 | + # Excel row data (mapping to Excel template columns) | ||
| 202 | + excel_row = { | ||
| 203 | + '商品ID': '', # Empty for new products | ||
| 204 | + '创建时间': create_time_str, | ||
| 205 | + '商品标题*': title, | ||
| 206 | + '商品属性*': 'S', # Single variant product | ||
| 207 | + '商品副标题': brief, | ||
| 208 | + '商品描述': description, | ||
| 209 | + 'SEO标题': seo_title, | ||
| 210 | + 'SEO描述': seo_description, | ||
| 211 | + 'SEO URL Handle': handle, | ||
| 212 | + 'SEO URL 重定向': 'N', # Default to N | ||
| 213 | + 'SEO关键词': seo_keywords, | ||
| 214 | + '商品上架': 'Y', # Published by default | ||
| 215 | + '需要物流': 'Y', # Requires shipping | ||
| 216 | + '商品收税': 'N', # Not taxable by default | ||
| 217 | + '商品spu': '', # Empty | ||
| 218 | + '启用虚拟销量': 'N', # No fake sales | ||
| 219 | + '虚拟销量值': '', # Empty | ||
| 220 | + '跟踪库存': 'Y', # Track inventory | ||
| 221 | + '库存规则*': '1', # Allow purchase when stock is 0 | ||
| 222 | + '专辑名称': csv_data['categoryName'] or '', # Category as album | ||
| 223 | + '标签': tags, | ||
| 224 | + '供应商名称': csv_data['supplierName'] or '', | ||
| 225 | + '供应商URL': '', # Empty | ||
| 226 | + '款式1': '', # Not used for S type | ||
| 227 | + '款式2': '', # Not used for S type | ||
| 228 | + '款式3': '', # Not used for S type | ||
| 229 | + '商品售价*': price, | ||
| 230 | + '商品原价': compare_at_price, | ||
| 231 | + '成本价': cost_price, | ||
| 232 | + '商品SKU': sku_code, | ||
| 233 | + '商品重量': weight, | ||
| 234 | + '重量单位': weight_unit, | ||
| 235 | + '商品条形码': barcode, | ||
| 236 | + '商品库存': inventory_quantity, | ||
| 237 | + '尺寸信息': '', # Empty | ||
| 238 | + '原产地国别': '', # Empty | ||
| 239 | + 'HS(协调制度)代码': '', # Empty | ||
| 240 | + '商品图片*': csv_data['imageUrl'] or '', # Image URL | ||
| 241 | + '商品备注': '', # Empty | ||
| 242 | + '款式备注': '', # Empty | ||
| 243 | + '商品主图': csv_data['imageUrl'] or '', # Main image URL | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + return excel_row | ||
| 247 | + | ||
| 248 | + | ||
| 249 | +def create_excel_from_template(template_file: str, output_file: str, csv_data_list: list): | ||
| 250 | + """ | ||
| 251 | + Create Excel file from template and fill with CSV data. | ||
| 252 | + | ||
| 253 | + Args: | ||
| 254 | + template_file: Path to Excel template file | ||
| 255 | + output_file: Path to output Excel file | ||
| 256 | + csv_data_list: List of parsed CSV data dictionaries | ||
| 257 | + """ | ||
| 258 | + # Load template | ||
| 259 | + wb = load_workbook(template_file) | ||
| 260 | + ws = wb.active # Use the active sheet (Sheet4) | ||
| 261 | + | ||
| 262 | + # Find header row (row 2, index 1) | ||
| 263 | + header_row_idx = 2 # Row 2 in Excel (1-based, but header is at index 1 in pandas) | ||
| 264 | + | ||
| 265 | + # Get column mapping from header row | ||
| 266 | + column_mapping = {} | ||
| 267 | + for col_idx in range(1, ws.max_column + 1): | ||
| 268 | + cell_value = ws.cell(row=header_row_idx, column=col_idx).value | ||
| 269 | + if cell_value: | ||
| 270 | + column_mapping[cell_value] = col_idx | ||
| 271 | + | ||
| 272 | + # Start writing data from row 4 (after header and instructions) | ||
| 273 | + data_start_row = 4 # Row 4 in Excel (1-based) | ||
| 274 | + | ||
| 275 | + # Clear existing data rows (from row 4 onwards, but keep header and instructions) | ||
| 276 | + # Find the last row with data in the template | ||
| 277 | + last_template_row = ws.max_row | ||
| 278 | + if last_template_row >= data_start_row: | ||
| 279 | + # Clear data rows (keep header and instruction rows) | ||
| 280 | + for row in range(data_start_row, last_template_row + 1): | ||
| 281 | + for col in range(1, ws.max_column + 1): | ||
| 282 | + ws.cell(row=row, column=col).value = None | ||
| 283 | + | ||
| 284 | + # Convert CSV data to Excel rows | ||
| 285 | + for row_idx, csv_data in enumerate(csv_data_list): | ||
| 286 | + excel_row = csv_to_excel_row(csv_data) | ||
| 287 | + excel_row_num = data_start_row + row_idx | ||
| 288 | + | ||
| 289 | + # Write each field to corresponding column | ||
| 290 | + for field_name, col_idx in column_mapping.items(): | ||
| 291 | + if field_name in excel_row: | ||
| 292 | + cell = ws.cell(row=excel_row_num, column=col_idx) | ||
| 293 | + value = excel_row[field_name] | ||
| 294 | + cell.value = value | ||
| 295 | + | ||
| 296 | + # Set alignment for text fields | ||
| 297 | + if isinstance(value, str): | ||
| 298 | + cell.alignment = Alignment(vertical='top', wrap_text=True) | ||
| 299 | + elif isinstance(value, (int, float)): | ||
| 300 | + cell.alignment = Alignment(vertical='top') | ||
| 301 | + | ||
| 302 | + # Save workbook | ||
| 303 | + wb.save(output_file) | ||
| 304 | + print(f"Excel file created: {output_file}") | ||
| 305 | + print(f" - Total rows: {len(csv_data_list)}") | ||
| 306 | + | ||
| 307 | + | ||
| 308 | +def main(): | ||
| 309 | + parser = argparse.ArgumentParser(description='Convert CSV data to Excel import template') | ||
| 310 | + parser.add_argument('--csv-file', | ||
| 311 | + default='data/customer1/goods_with_pic.5years_congku.csv.shuf.1w', | ||
| 312 | + help='CSV file path (default: data/customer1/goods_with_pic.5years_congku.csv.shuf.1w)') | ||
| 313 | + parser.add_argument('--template', | ||
| 314 | + default='docs/商品导入模板.xlsx', | ||
| 315 | + help='Excel template file path (default: docs/商品导入模板.xlsx)') | ||
| 316 | + parser.add_argument('--output', | ||
| 317 | + default='商品导入数据.xlsx', | ||
| 318 | + help='Output Excel file path (default: 商品导入数据.xlsx)') | ||
| 319 | + parser.add_argument('--limit', | ||
| 320 | + type=int, | ||
| 321 | + default=None, | ||
| 322 | + help='Limit number of rows to process (default: all)') | ||
| 323 | + | ||
| 324 | + args = parser.parse_args() | ||
| 325 | + | ||
| 326 | + # Check if files exist | ||
| 327 | + if not os.path.exists(args.csv_file): | ||
| 328 | + print(f"Error: CSV file not found: {args.csv_file}") | ||
| 329 | + sys.exit(1) | ||
| 330 | + | ||
| 331 | + if not os.path.exists(args.template): | ||
| 332 | + print(f"Error: Template file not found: {args.template}") | ||
| 333 | + sys.exit(1) | ||
| 334 | + | ||
| 335 | + # Read CSV file | ||
| 336 | + print(f"Reading CSV file: {args.csv_file}") | ||
| 337 | + csv_data_list = read_csv_file(args.csv_file) | ||
| 338 | + print(f"Read {len(csv_data_list)} rows from CSV") | ||
| 339 | + | ||
| 340 | + # Limit rows if specified | ||
| 341 | + if args.limit: | ||
| 342 | + csv_data_list = csv_data_list[:args.limit] | ||
| 343 | + print(f"Limited to {len(csv_data_list)} rows") | ||
| 344 | + | ||
| 345 | + # Create Excel file | ||
| 346 | + print(f"Creating Excel file from template: {args.template}") | ||
| 347 | + print(f"Output file: {args.output}") | ||
| 348 | + create_excel_from_template(args.template, args.output, csv_data_list) | ||
| 349 | + | ||
| 350 | + print(f"\nDone! Generated {len(csv_data_list)} product rows in Excel file.") | ||
| 351 | + | ||
| 352 | + | ||
| 353 | +if __name__ == '__main__': | ||
| 354 | + main() | ||
| 355 | + |
| @@ -0,0 +1,18 @@ | @@ -0,0 +1,18 @@ | ||
| 1 | +# 激活环境 | ||
| 2 | +source /home/tw/miniconda3/etc/profile.d/conda.sh | ||
| 3 | +conda activate searchengine | ||
| 4 | + | ||
| 5 | +# # 基本使用(生成所有数据) | ||
| 6 | +# python scripts/csv_to_excel.py | ||
| 7 | + | ||
| 8 | +# # 指定输出文件 | ||
| 9 | +# python scripts/csv_to_excel.py --output tenant3_imports.xlsx | ||
| 10 | + | ||
| 11 | +# # 限制处理行数(用于测试) | ||
| 12 | +# python scripts/csv_to_excel.py --limit 100 | ||
| 13 | + | ||
| 14 | +# 指定CSV文件和模板文件 | ||
| 15 | +python scripts/csv_to_excel.py \ | ||
| 16 | + --csv-file data/customer1/goods_with_pic.5years_congku.csv.shuf.1w \ | ||
| 17 | + --template docs/商品导入模板.xlsx \ | ||
| 18 | + --output tenant3_imports.xlsx | ||
| 0 | \ No newline at end of file | 19 | \ No newline at end of file |