商品批量导入
商品批量导入插件
插件编码:product_batch_import
插件目的
商品批量导入插件用于帮助 BeikeShop 商家通过 XLSX 表格批量创建或更新商品资料。插件支持单规格商品、多规格商品、中英文商品文案、中英文分类、中英文属性和中英文规格导入,适合新站商品初始化、供应商资料整理、批量上新和存量商品维护。
环境要求
- PHP >= 8.3.0
- BeikeShop >= v3.0.0.3
- 无需配置队列驱动
- 无需安装 Supervisor、无需常驻队列 Worker、无需开放
exec/shell_exec
功能亮点
- 多规格导入:同一个
product_code下多行 SKU 会合并为一个商品,每行代表一个规格 SKU。 - 中英文商品文案:支持名称、详情、SEO 标题、SEO 描述、SEO 关键词分别导入中文和英文。
- 国际化分类:支持
categories_zh_cn和categories_en,自动创建分类并写入多语言描述。 - 国际化属性:支持
attributes_zh_cn和attributes_en,自动创建属性组、属性和属性值的多语言描述。 - 国际化规格:支持
variants_zh_cn和variants_en,自动生成 BeikeShop 商品规格轴和 SKU 规格索引。 - 自动创建基础资料:导入数据中存在但数据库中缺失的分类、品牌、属性组、属性和值会自动创建。
- 图片自动入库:支持远程图片 URL 下载,也支持 XLSX + 图片 ZIP 的本地图片匹配导入。
- 长任务拆分 + 页面自动推进:提交任务后,页面通过 AJAX 分批同步处理商品组,无需后台 Worker,避免 HTTP 超时 502。
- 刷新自动续跑:中途刷新或关闭页面后,重新进入插件页面会自动检测并继续处理待处理/进行中的任务。
- 执行时间记录:任务完成后会显示耗时(秒)。
- 预览和报告:正式导入前可预览商品组、SKU 行数、错误信息;导入后可下载结果报告。
- 结构化日志:JSON Lines 格式日志,支持按日期/级别/任务 ID 筛选、搜索、详情弹窗。
操作手册
- 上传并安装插件。
- 启用插件,进入插件详情页。
- 点击“下载模板”,获取 XLSX 模板。
- 按模板填写商品数据。
- 多规格商品使用相同
product_code填写多行,每行一个 SKU。 - 中英文分类、属性、规格必须保持结构一致。
- 如使用远程图片,在
images字段填写图片 URL,多个图片用|分隔。 - 如使用本地图片,在
images字段填写 ZIP 内图片路径,并在执行导入时上传图片 ZIP。 - 先上传 XLSX 点击“预览”,确认错误和将自动创建的数据。
- 确认无误后点击“提交导入任务”。
- 提交后页面会自动按商品组处理导入任务,下方“导入任务记录”会实时刷新进度。请保持当前页面打开,直到任务完成。
- 任务完成后,可点击“结果”查看每条 SKU 的导入状态,点击“日志”查看该任务的完整日志。
核心字段
| 字段 | 必填 | 说明 |
|---|---|---|
product_code |
多规格建议必填 | 商品归组编码。同一编码的多行合并为一个商品。旧模板可不填,不填时每个 SKU 单独成组。 |
sku |
是 | SKU 编码,文件内不可重复。数据库已存在时更新其所属商品。 |
is_default |
否 | 默认 SKU 标记,支持 yes、true、1、是。同一商品只能有一行默认。 |
name_zh_cn / name_en |
是 | 中文/英文商品名称。旧字段 name 仍可作为兜底。 |
price |
是 | SKU 售价,必须为数字。 |
quantity |
否 | SKU 库存,必须为数字,默认 0。 |
brand_zh_cn / brand_en |
否 | 品牌名称。BeikeShop 品牌表只有单一 name,导入时用多语言候选名查找,创建时保存一个主名称。 |
categories_zh_cn / categories_en |
否 | 多语言分类路径。多分类用竖线分隔,层级用 > 分隔。 |
attributes_zh_cn / attributes_en |
否 | 多语言属性。格式:属性组:属性=属性值,多个属性之间用竖线连接。 |
variants_zh_cn / variants_en |
多规格必填 | 多语言规格组合。格式:规格名=规格值,多个规格之间用竖线连接。 |
images |
否 | 商品图 URL、图库路径或 ZIP 内文件路径,多个路径用竖线分隔。 |
content_zh_cn / content_en |
否 | 中文/英文商品详情。 |
model |
否 | SKU 型号。 |
origin_price |
否 | 划线价,必须为数字。 |
cost_price |
否 | 成本价,必须为数字。 |
weight |
否 | SKU 重量,必须为数字。 |
meta_title_zh_cn / meta_title_en |
否 | 中文/英文 SEO 标题。 |
meta_description_zh_cn / meta_description_en |
否 | 中文/英文 SEO 描述。 |
meta_keywords_zh_cn / meta_keywords_en |
否 | 中文/英文 SEO 关键词。 |
多规格示例
| product_code | sku | is_default | name_zh_cn | name_en | variants_zh_cn | variants_en | price | quantity |
|---|---|---|---|---|---|---|---|---|
| BAG-CANVAS | BAG-CANVAS-RED-M | yes | 帆布手提袋 | Canvas Tote | 颜色=红色;尺码=M | Color=Red; Size=M | 19.99 | 25 |
| BAG-CANVAS | BAG-CANVAS-RED-L | 帆布手提袋 | Canvas Tote | 颜色=红色;尺码=L | Color=Red; Size=L | 21.99 | 18 |
导入后会生成一个商品、两个 SKU、两个规格轴:颜色/Color、尺码/Size。
规格组合中的多个规格在实际 XLSX 中用竖线
|分隔,例如:
颜色=红色|尺码=MColor=Red|Size=M
国际化填写规则
- 商品文案:每个语言后缀列会写入对应 locale 的商品描述。
- 分类:
categories_zh_cn和categories_en的分类数量、层级数量必须一致。 - 属性:
attributes_zh_cn和attributes_en的条目数量必须一致。 - 规格:
variants_zh_cn和variants_en的规格轴数量和顺序必须一致。 - 兜底:如果只填旧字段
name、categories、attributes、variants,插件会把同一值写入所有启用语言。
自动创建规则
- 分类:按父级和任一语言名称查找,找不到时创建分类并写入多语言描述。
- 品牌:按
brand、brand_zh_cn、brand_en候选名查找,找不到时创建单名称品牌。 - 属性组:按任一语言名称查找,找不到时创建多语言描述。
- 属性:在属性组下按任一语言名称查找,找不到时创建多语言描述。
- 属性值:在属性下按任一语言名称查找,找不到时创建多语言描述。
- 规格:写入商品
variables字段,不创建独立规格基础表。
图片导入规则
- 远程图片:
images填写https://example.com/a.jpg。 - ZIP 图片:
images填写 ZIP 内路径,例如images/a.jpg。 - 多张图片:多个路径用竖线分隔,例如
a.jpg、b.jpg、https://example.com/c.jpg。 - 已有图库图片:以
image/catalog/开头的值会直接作为图库路径使用。 - 当前版本只导商品图,不做逐 SKU 图片。
- 单张远程图片下载超时 15 秒,失败会自动降级跳过,不影响整组商品。
异步处理机制
为避免 HTTP 请求超时导致页面 502,导入已改为 长任务拆分 + 页面 AJAX 自动推进:
- 点击“提交导入任务”后,插件先保存上传文件并创建任务记录;
- 页面通过
fetch提交表单并立即开始轮询/process/{id}; - 后端使用文件锁保证同一个任务不会被多个并发请求同时处理;
- 每个请求内只解析一次 XLSX,然后连续处理最多 10 个商品组(同时受 25 秒时间上限保护);
- 同一 ZIP 在单次请求内只解压一次;
- 处理完后返回最新进度,前端继续轮询,直到任务完成;
- “导入任务记录”卡片会实时刷新进度、状态、新建/更新/失败数量、耗时。
这种方式不需要队列驱动、不需要 Supervisor、不需要常驻 Worker、不需要开放 exec / shell_exec,安装插件即可使用。
请保持导入页面打开直到任务完成。如果中途刷新或关闭页面,处理会暂停;重新打开页面后,插件会自动检测“待处理”或“进行中”的任务并继续处理,无需再次提交。
使用 Laravel Queue / Supervisor(可选,高级用户)
如果希望任务在后台自动跑、不依赖浏览器,可以启用 database 队列驱动,并让 ProductImportDispatchJob 进入队列,再配合 Supervisor 常驻 Worker:
[program:beikeshop-import-queue]command=/www/server/php/83/bin/php artisan queue:work --queue=product_import --sleep=3 --tries=1 --memory=1024 --timeout=300directory=/www/wwwroot/coppable.com/beikeshop_v3.0.0.3user=wwwautostart=trueautorestart=true
启用队列模式前请确认:
.env中QUEUE_CONNECTION=database- 已创建
jobs表和failed_jobs表:php artisan queue:tablephp artisan queue:failed-tablephp artisan migrate
任务记录与日志
- 任务记录:
storage/app/product_import/records/{uuid}.json - 上传文件:
storage/app/product_import/uploads/{uuid}.xlsx/.zip - 导入日志:
storage/logs/product-import-YYYY-MM-DD.log(JSON Lines) - 报告文件:
storage/app/product_import/reports/ - 任务锁文件:
storage/app/product_import/locks/{uuid}.lock
页面提供结构化日志查看器:支持按日期/级别筛选、按任务 ID 过滤、关键词搜索、自动刷新、统计 badge、点击行查看完整 JSON 上下文,并可下载原始日志。
字段长度提示:
name、meta_title、meta_keywords最大 255 字符,meta_description最大 500 字符。超出时插件会在预览和导入结果中给出警告,并自动截断,不会导致导入失败。
常见问题
上传提示 code 编码不可用
插件编码固定为 product_batch_import。如果仍提示冲突,通常表示目标站点已有同 code 插件目录、数据库插件记录或上传平台记录。请检查:
- 是否已有
plugins/ProductImport或其他同 code 插件目录。 - 数据库
plugins表是否已有code = product_batch_import。 - 上传平台是否已登记过相同 code。
40 个产品为什么更新了 56 条?
页面统计的是商品组数量,不是产品概念数量:
- 如果
product_code列有值,同一个product_code下的所有 SKU 行合并为 1 个商品组。 - 如果
product_code为空,插件会按sku分组,每行都会变成 1 个商品组。 - 每个商品组处理成功后,
updated_count才会 +1。
所以“更新 56”说明实际处理了 56 个商品组。请检查 XLSX 里的 product_code 是否都已正确填写。
任务提交后进度一直为 0
- 确认导入页面保持打开,页面需要轮询队列才能推进任务。
- 检查浏览器 F12 控制台是否有 JS 报错。
- 检查
storage/logs/product-import-YYYY-MM-DD.log和storage/logs/laravel.log中的错误信息。
导入结束后页面还有 loading 遮罩
这是 BeikeShop 后台全局提交事件弹出的 layui-layer-loading,因为插件用 AJAX 拦截了表单提交、没有触发页面跳转,导致遮罩未被自动关闭。插件已在表单提交和完成后调用 layui.layer.closeAll('loading') 并清理遮罩 DOM。如果仍有残留,可在控制台执行:
layui.layer.closeAll('loading')
导入太慢怎么办?
插件已经做了以下提速:
- 每个请求批量处理最多 10 个商品组;
- 每个请求内只解析一次 XLSX;
- 同一 ZIP 在单次请求内只解压一次;
- 前端有进度时立即发起下一次轮询。
如果仍然很慢,通常是以下原因:
- 远程图片下载慢或超时:检查服务器网络,或改用 ZIP 本地图片。
- 商品组数量多、每个组 SKU 多:属于正常情况,耐心等待。
- 需要更高并发:可启用 Laravel Queue + Supervisor 后台常驻 Worker。
注意事项
- 正式导入前建议先备份数据库和图片目录。
- 大批量导入前建议先用少量数据测试模板格式。
- 远程图片下载依赖服务器网络环境。
- 品牌国际化受 BeikeShop 原生品牌表结构限制,无法按语言显示不同品牌名。
- 更新插件代码后,建议执行
php artisan cache:clear config:clear view:clear route:clear。