LikeShop 商品 SKU 体系深度改造:多规格属性扩展与批量导入开发

一、原生 SKU 数据表结构拆解

1.1 三表结构的核心模型

LikeShop 的商品体系采用经典的主表(SPU)+ 规格表 + SKU 表三层架构;三张表的分工如下:

表名职责关键字段
ls_goods商品基础信息(SPU)name、image、category_id、status、goods_type
ls_goods_spec规格名称与规格值定义spec_name、spec_value
ls_goods_itemSKU 组合的价格与库存goods_id、spec_value_ids、spec_value_str、sell_price、stock

ls_goods_spec 存储商品的可选规格,比如“颜色”这个规格下有“红色、蓝色”两个规格值,“尺寸”这个规格下有“S、M、L”三个规格值;ls_goods_item 则是规格值组合的笛卡尔积结果,每一条记录对应一个独立的 SKU,拥有自己的价格和库存。

1.2 ls_goods_item 的字段语义

ls_goods_item 是价格和库存的真正存储位置;核心字段包括:

字段名数据类型注释
goods_idint商品id
spec_value_idsvarchar多个规格id,逗号隔开
spec_value_strvarchar多个规格名称,逗号隔开
sell_pricedecimal销售价格
stockint库存

spec_value_ids 是 SKU 的唯一标识,由规格值 ID 按有序组合拼接而成;比如“蓝色,4G”对应“5,7”,“蓝色,6G”对应“5,8”;下单时用户选择的规格组合,计算通过 spec_value_ids 来定位具体的 SKU。

商品详情接口返回的数据结构清晰地印证了这个设计:

 json
 {
 "goods_spec": [
    {
       "name": "选择颜色",
       "spec_value": [
          {"id": 5, "value": "蓝色"},
          {"id": 6, "value": "白色"}
       ]
     },
     {
       "name": "选择版本",
       "spec_value": [
          {"id": 7, "value": "4G"},
          {"id": 8, "value": "6G"}
       ]
      }
    ],
    "goods_item": [
    {
       "id": 1,
       "spec_value_ids": "5,7",
       "spec_value_str": "蓝色,4G",
       "price": "2088.00",
       "stock": 1000
        }
     ]
   }

1.3 原生 SKU 体系的四个核心缺口

缺口一:规格数量受限。原生设计在 UI 层面通常只支持两级规格(如“颜色 + 尺寸”),当商品需要三级以上规格(如“颜色 + 尺寸 + 材质”)时,前端编辑器和后端计算逻辑都无法支撑;虽然 ls_goods_spec 表本身没有限制规格数量,但业务逻辑层对规格层级做了硬编码假设。

缺口二:规格属性不可自定义。原生的规格名和规格值依赖于预设模版或手动输入,缺乏“按商品分类预置规格属性”的能力;例如,服装分类应该自动带出“尺码、颜色、材质”三个规格维度,但原生系统无法实现这种分类级别的规格属性绑定。

缺口三:无批量导入能力。商品数量较多时,逐个手动添加商品的效率极低;原生系统没有提供 Excel 批量导入商品和 SKU 库存的功能,运营人员需要耗费大量时间在重复的表单填写上。

缺口四:SKU 组合的笛卡尔积计算在服务端硬编码。当规格数量动态变化时,SKU 组合的生成逻辑需要手动适配,缺乏通用的笛卡尔积计算引擎。

1.4 二开策略定位

遵循 LikeShop的“扩展优先”原则,SKU 体系改造采用以下策略:

  1. 不修改 ls_goods_item 的核心字段结构,通过扩展规格定义层来支持多级规格
  2. 新增分类规格属性表,实现“分类 → 规格属性模版 → 商品规格”的联动
  3. 批量导入采用 PhpSpreadsheet 流式读取 + 分批事务插入,避免内存溢出和超时
  4. SKU 笛卡尔积计算封装为独立 Service,支持任意数量的规格维度
二、多规格属性扩展实现

2.1 分类规格属性模版表

新建分类规格属性表,实现按商品分类预置规格维度的能力:

sql
CREATE TABLE `ls_category_spec_attr` (
  `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
  `category_id` int UNSIGNED NOT NULL COMMENT '分类ID',
  `attr_name` varchar(30) NOT NULL COMMENT '规格属性名称,如"颜色""尺寸""材质"',
  `attr_values` text COMMENT '预设规格值,JSON数组,如["红色","蓝色"]',
 `is_required` tinyint NOT NULL DEFAULT 0 COMMENT '是否必填:0-否,1-是',
 `sort` int NOT NULL DEFAULT 0 COMMENT '排序',
 `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
 `create_time` int UNSIGNED NOT NULL DEFAULT 0,
 `update_time` int UNSIGNED NOT NULL DEFAULT 0,
 PRIMARY KEY (`id`),
 KEY `idx_category_id` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分类规格属性模板表';

该表使得“服装”分类可以预置“尺码、颜色、材质”三个规格属性,“数码”分类可以预置“颜色、内存、版本”三个规格属性;发布商品选择分类后,系统自动带出该分类下的规格属性模版。

2.2 商品规格表的结构扩展

在 ls_goods_spec 表中新增字段,支持规格属性的扩展能力:

sql
ALTER TABLE `ls_goods_spec`
 ADD COLUMN `spec_type` tinyint NOT NULL DEFAULT 1 COMMENT '规格类型:1-普通规格,2-自定义规格' AFTER `spec_name`,
 ADD COLUMN `sort` int NOT NULL DEFAULT 0 COMMENT '规格排序' AFTER `spec_value`,
 ADD COLUMN `is_required` tinyint NOT NULL DEFAULT 0 COMMENT '是否必填:0-否,1-是' AFTER `sort`;

spec_type 标识该规格是来自分类模版(普通规格)还是商家自定义添加(自定义规格);sort 字段控制规格在商品详情页的展示顺序。

2.3 多规格笛卡尔积计算引擎

原生 SKU 组合的计算逻辑在服务端硬编码,只能处理固定数量的规格;改造后需要实现通用的笛卡尔积计算引擎,支持任意数量的规格维度:

php
class SkuGeneratorService
{
   /**
    * 生成多规格 SKU 组合
    * @param array $specGroups 规格分组,格式:
    *   [
    *     ['spec_name' => '颜色', 'spec_values' => [['id'=>5,'value'=>'蓝色'], ['id'=>6,'value'=>'白色']]],
    *     ['spec_name' => '尺寸', 'spec_values' => [['id'=>7,'value'=>'S'], ['id'=>8,'value'=>'M']]],
    *     ['spec_name' => '材质', 'spec_values' => [['id'=>9,'value'=>'棉'], ['id'=>10,'value'=>'涤纶']]]
    *   ]
    * @return array SKU 组合列表
    */
    public function generateSkus($specGroups)
    {
       if (empty($specGroups)) {
           return [];
       }

        // 提取每个规格分组的值列表
        $valueGroups = [];
        foreach ($specGroups as $group) {
           $values = array_column($group['spec_values'], 'id');
           if (empty($values)) {
               throw new Exception("规格「{$group['spec_name']}」没有配置规格值");
           }
           $valueGroups[] = $values;
       }

         // 计算笛卡尔积
         $combinations = $this->cartesianProduct($valueGroups);

        // 构建 SKU 数据结构
        $skus = [];
        foreach ($combinations as $combination) {
             $specValueIds = implode(',', $combination);
             $specValueStr = $this->buildSpecValueStr($specGroups, $combination);

             $skus[] = [
                'spec_value_ids' => $specValueIds,
                'spec_value_str' => $specValueStr,
                'sell_price'     => 0.00,
                'stock'          => 0,
            ];
         }

            return $skus;
          }

/**
 * 递归计算笛卡尔积
 */
private function cartesianProduct($arrays, $index = 0, $current = [])
{
    if ($index >= count($arrays)) {
        return [$current];
    }

    $result = [];
    foreach ($arrays[$index] as $value) {
        $next = $current;
        $next[] = $value;
        $result = array_merge($result, $this->cartesianProduct($arrays, $index + 1, $next));
    }
    return $result;
}

/**
 * 构建规格值展示字符串
 */
private function buildSpecValueStr($specGroups, $combination)
{
    $strs = [];
    foreach ($specGroups as $i => $group) {
        foreach ($group['spec_values'] as $value) {
            if ($value['id'] == $combination[$i]) {
                $strs[] = $value['value'];
                break;
            }
        }
    }
    return implode(',', $strs);
  }
}

2.4 多级规格的 SKU 匹配逻辑

当规格数量动态变化时,下单时的 SKU 匹配逻辑需要适配;原生通过 spec_value_ids 字段串匹配,改造后需要增加对任意数量规格的支持:

php
class SkuMatcherService
{
  /**
   * 根据用户选择的规格值匹配 SKU
   * @param int   $goodsId    商品ID
   * @param array $selectedValueIds 用户选择的规格值ID数组,如 [5, 7, 9]
   */
    public function matchSku($goodsId, $selectedValueIds)
   {
       // 排序保证顺序一致
        sort($selectedValueIds);
        $specValueIds = implode(',', $selectedValueIds);

        // 优先精确匹配
        $sku = GoodsItemModel::where('goods_id', $goodsId)
           ->where('spec_value_ids', $specValueIds)
           ->find();

       if ($sku) {
          return $sku;
       }

       // 降级:遍历该商品所有 SKU,做集合匹配(处理顺序不一致的情况)
        $allSkus = GoodsItemModel::where('goods_id', $goodsId)->select();
        foreach ($allSkus as $item) {
           $itemIds = explode(',', $item['spec_value_ids']);
           sort($itemIds);
           if ($itemIds === $selectedValueIds) {
               return $item;
          }
       }

        throw new Exception('未找到匹配的规格组合');
      }
   }

2.5 后台配置

在“商品管理 → 分类管理”中,为每个分类增加“规格属性模版”配置区块:

配置项类型说明
规格属性名称文本输入如“颜色”“尺寸”“材质”
预设规格值标签输入每个规格值回车确认,如“红色”“蓝色”
是否必填开关发布商品时该规格属性是否必须配置
排序数字输入控制规格属性的展示顺序

在商品发布页面,选择分类后自动带出该分类下的规格属性模版,商家可在此基础上增删规格值,也可添加自定义规格属性。

三、Excel 批量导入开发

3.1 批量导入的整体流程

批量导入的完整流程为:下载导入模版 → 填写商品数据 → 上传 Excel 文件 → 后端解析验证 → 分批写入数据库;LikeShop 原生支持列表导出功能,数据导出采用 PhpExcel 使数据更加直观;批量导入作为导出的逆向操作,需要复用已有的数据模型和验证规则。

3.2 导入模版设计

模版分为两个 Sheet:

Sheet1:商品基本信息(一行一个商品)

商品名称商品分类商品类型商品主图轮播图商品详情运费模板单位品牌
iPhone 15手机数码实物url1url1,url2

...

默认模板台Apple

Sheet2:SKU 规格信息(一行一个 SKU)

商品名称规格1名称规格1值规格2名称规格2值规格3名称规格3值销售价库存成本价
iPhone 15颜色黑色内存256G版本国行69991006500
iPhone 15颜色白色内存256G版本国行6999806500

两个 Sheet 通过“商品名称”关联;如果商品只有单一规格(无多规格),Sheet2 中的规格列留空即可。

3.3 PhpSpreadsheet 流式读取

使用 PhpSpreadsheet 实现 Excel 批量导入时,必须先安装依赖: composer require phpoffice/phpspreadsheet;TP6 项目必须确保 PHP ≥ 7.4,否则类加载会静默失败;在控制器顶部写全命名空间 use PhpOffice\PhpSpreadsheet\IOFactory,漏掉 PhpOffice\ 前缀,运行时必报“Class not found”。

php
class GoodsImportService
{
/**
 * 解析导入文件
 */
public function parseFile($filePath, $ext)
{
    // 1. 根据扩展名创建显式 Reader
    if ($ext === 'xlsx') {
        $reader = IOFactory::createReader('Xlsx');
    } else {
        $reader = IOFactory::createReader('Xls');
    }

    // 2. 关闭非数据解析,内存占用直降60%以上
    $reader->setReadDataOnly(true);

    // 3. 加载文件并获取工作表
    $spreadsheet = $reader->load($filePath);

    // 4. 解析商品基本信息 Sheet
    $goodsSheet = $spreadsheet->getSheet(0);
    $goodsList = $this->parseGoodsSheet($goodsSheet);

    // 5. 解析 SKU 规格 Sheet
    $skuSheet = $spreadsheet->getSheet(1);
    $skuList = $this->parseSkuSheet($skuSheet);

    // 6. 释放内存
    $spreadsheet->disconnectWorksheets();
    unset($spreadsheet);

    return ['goods' => $goodsList, 'skus' => $skuList];
}

/**
 * 解析商品基本信息 Sheet
 */
private function parseGoodsSheet($worksheet)
{
    $goodsList = [];
    $headers = [];

    // 读取表头(第一行)
    $headerRow = $worksheet->getRowIterator(1, 1)->current();
    $cellIterator = $headerRow->getCellIterator();
    $cellIterator->setIterateOnlyExistingCells(false);
    foreach ($cellIterator as $cell) {
        $headers[] = trim($cell->getFormattedValue());
    }

    // 读取数据行(从第二行开始)
    $rowIterator = $worksheet->getRowIterator(2);
    foreach ($rowIterator as $row) {
        $rowData = [];
        $cellIterator = $row->getCellIterator();
        $cellIterator->setIterateOnlyExistingCells(false);
        foreach ($cellIterator as $index => $cell) {
            if (isset($headers[$index])) {
                $rowData[$headers[$index]] = trim($cell->getFormattedValue());
            }
        }

        // 跳过空行
        if (empty(array_filter($rowData))) continue;

        $goodsList[] = $rowData;
    }

    return $goodsList;
}

/**
 * 解析 SKU 规格 Sheet
 */
private function parseSkuSheet($worksheet)
{
    // 与 parseGoodsSheet 逻辑类似,读取 SKU 行数据
    // 返回格式:[['商品名称'=>..., '规格1名称'=>..., '规格1值'=>..., ...], ...]
    return $this->parseGenericSheet($worksheet);
 }
}

3.4 数据验证与清洗

导入前的数据验证是保证数据质量的关键环节;参考 ThinkPHP 批量导入的最佳实践,需要先验证文件格式与大小,再清洗数据并映射字段:

php
class GoodsImportValidator
{
/**
 * 验证导入数据
 */
public function validate($goodsList, $skuList)
{
    $errors = [];
    $goodsNames = [];

    // 1. 验证商品基本信息
    foreach ($goodsList as $index => $goods) {
        $rowNum = $index + 2; // Excel 行号

        // 必填字段校验
        if (empty($goods['商品名称'])) {
            $errors[] = "第{$rowNum}行:商品名称不能为空";
            continue;
        }

        // 商品名称重复校验
        if (in_array($goods['商品名称'], $goodsNames)) {
            $errors[] = "第{$rowNum}行:商品名称「{$goods['商品名称']}」重复";
        }
        $goodsNames[] = $goods['商品名称'];

        // 分类存在性校验
        if (!empty($goods['商品分类'])) {
            $catId = CategoryModel::where('name', $goods['商品分类'])
                ->where('status', 1)
                ->value('id');
            if (!$catId) {
                $errors[] = "第{$rowNum}行:分类「{$goods['商品分类']}」不存在或已禁用";
            }
        }

        // 商品类型校验
        if (!in_array($goods['商品类型'], ['实物', '虚拟'])) {
            $errors[] = "第{$rowNum}行:商品类型只能为「实物」或「虚拟」";
        }
    }

    // 2. 验证 SKU 数据
    foreach ($skuList as $index => $sku) {
        $rowNum = $index + 2;

        if (empty($sku['商品名称'])) {
            $errors[] = "SKU第{$rowNum}行:商品名称不能为空";
            continue;
        }

        // 商品名称必须存在于商品列表中
        if (!in_array($sku['商品名称'], $goodsNames)) {
            $errors[] = "SKU第{$rowNum}行:商品「{$sku['商品名称']}」在商品信息中不存在";
        }

        // 价格和库存校验
        if (!is_numeric($sku['销售价']) || $sku['销售价'] < 0) {
            $errors[] = "SKU第{$rowNum}行:销售价格式不正确";
        }
        if (!is_numeric($sku['库存']) || $sku['库存'] < 0) {
            $errors[] = "SKU第{$rowNum}行:库存格式不正确";
        }
    }

    return $errors;
  }
}

3.5 分批事务写入

数据验证通过后,进入数据库写入阶段;大批量数据插入需要分批处理,每批 200-400 条,并在事务中执行:

php
class GoodsImportWriter
{
/**
 * 执行导入
 */
public function import($goodsList, $skuList)
{
    // 按商品名称分组 SKU
    $skuGroups = [];
    foreach ($skuList as $sku) {
        $skuGroups[$sku['商品名称']][] = $sku;
    }

    $successCount = 0;
    $batchSize = 50; // 每批处理 50 个商品

    Db::startTrans();
    try {
        foreach (array_chunk($goodsList, $batchSize) as $goodsBatch) {
            foreach ($goodsBatch as $goodsData) {
                // 1. 创建商品(SPU)
                $goodsId = $this->createGoods($goodsData);

                // 2. 创建规格和 SKU
                $skus = $skuGroups[$goodsData['商品名称']] ?? [];
                $this->createSkus($goodsId, $goodsData['商品名称'], $skus);

                $successCount++;
            }
        }

        Db::commit();
    } catch (\Exception $e) {
        Db::rollback();
        throw new Exception('导入失败:' . $e->getMessage());
    }

    return $successCount;
}

/**
 * 创建商品主记录
 */
private function createGoods($data)
{
    $catId = CategoryModel::where('name', $data['商品分类'])
        ->where('status', 1)
        ->value('id');

    return GoodsModel::create([
        'name'        => $data['商品名称'],
        'category_id' => $catId ?: 0,
        'goods_type'  => $data['商品类型'] == '实物' ? 'real' : 'virtual',
        'image'       => $data['商品主图'] ?? '',
        'carousel'    => $data['轮播图'] ?? '',
        'unit'        => $data['单位'] ?? '件',
        'status'      => 0, // 默认下架,审核通过后上架
        'create_time' => time(),
    ])->id;
}

/**
 * 创建 SKU 记录
 */
private function createSkus($goodsId, $goodsName, $skus)
{
    if (empty($skus)) {
        // 无规格商品,创建默认 SKU
        GoodsItemModel::create([
            'goods_id'      => $goodsId,
            'spec_value_ids'=> '',
            'spec_value_str'=> '',
            'sell_price'    => 0,
            'stock'         => 0,
            'create_time'   => time(),
        ]);
        return;
    }

    // 解析规格分组
    $specGroups = $this->parseSpecGroups($skus);

    // 生成规格定义
    $specService = new SpecService();
    $specService->saveSpecs($goodsId, $specGroups);

    // 创建 SKU
    foreach ($skus as $skuData) {
        $specValueIds = $this->buildSpecValueIds($specGroups, $skuData);
        $specValueStr = $this->buildSpecValueStr($skuData);

        GoodsItemModel::create([
            'goods_id'      => $goodsId,
            'spec_value_ids'=> $specValueIds,
            'spec_value_str'=> $specValueStr,
            'sell_price'    => $skuData['销售价'] ?? 0,
            'stock'         => $skuData['库存'] ?? 0,
            'create_time'   => time(),
        ]);
    }
}

/**
 * 从 SKU 数据中解析规格分组
 */
private function parseSpecGroups($skus)
{
    $groups = [];
    foreach ($skus as $sku) {
        for ($i = 1; $i <= 5; $i++) { // 支持最多5级规格
            $nameKey = "规格{$i}名称";
            $valueKey = "规格{$i}值";
            if (!empty($sku[$nameKey]) && !empty($sku[$valueKey])) {
                $groups[$sku[$nameKey]][$sku[$valueKey]] = true;
            }
        }
    }

    // 转换为标准格式
    $result = [];
    foreach ($groups as $name => $values) {
        $result[] = [
            'spec_name'   => $name,
            'spec_values' => array_keys($values),
        ];
    }
    return $result;
  }
}

3.6 导入结果反馈

导入完成后,向用户反馈导入结果:

php
public function getImportResult($successCount, $errors = [])

{ result=[′successcount′=>result = [ 'success_count' => successCount, 'error_count' => count(errors),′errors′=>errors), 'errors' => errors, ];

if ($successCount > 0 && empty($errors)) {
    $result['message'] = "成功导入 {$successCount} 个商品";
} elseif ($successCount > 0 && !empty($errors)) {
    $result['message'] = "部分导入成功:{$successCount} 个商品成功,"
        . count($errors) . " 条数据存在错误";
} else {
    $result['message'] = '导入失败,请检查数据格式';
}

return $result;
}

3.7 后台配置

在“商品管理 → 商品列表”页面增加“批量导入”入口:

功能说明
下载导入模板下载包含商品信息和 SKU 规格两个 Sheet 的 Excel 模板
上传导入文件支持 .xlsx 和 .xls 格式,文件大小限制 10MB
导入预览上传后先预览解析结果和验证错误,确认后再执行导入
导入日志记录每次导入的时间、操作人、成功/失败数量
四、SKU 库存批量更

4.1 库存批量更新模版

除了商品全量导入,商家还需要对已有商品的 SKU 库存进行批量更新;设计专用的库存更新模版:

商品ID商品名称SKU ID规格值库存成本价
15iPhone 151黑色,256G,国行2006500
15iPhone 152白色,256G,国行1506500

通过“SKU ID”精确定位,避免因规格值字符串匹配的歧义。

4.2 库存更新逻辑

php
class StockUpdateService
{
/**
 * 批量更新 SKU 库存
 */
public function batchUpdateStock($stockList)
{
    $updated = 0;
    $errors = [];

    Db::startTrans();
    try {
        foreach ($stockList as $index => $item) {
            $skuId = (int)$item['SKU ID'];
            $stock = (int)$item['库存'];

            if ($skuId <= 0) {
                $errors[] = "第" . ($index + 2) . "行:SKU ID 无效";
                continue;
            }

            if ($stock < 0) {
                $errors[] = "第" . ($index + 2) . "行:库存不能为负数";
                continue;
            }

            // 校验 SKU 是否存在
            $sku = GoodsItemModel::find($skuId);
            if (!$sku) {
                $errors[] = "第" . ($index + 2) . "行:SKU ID {$skuId} 不存在";
                continue;
            }

            // 更新库存
            GoodsItemModel::where('id', $skuId)->update([
                'stock'       => $stock,
                'update_time' => time(),
            ]);

            // 记录库存变动日志
            StockLogModel::create([
                'goods_id'   => $sku['goods_id'],
                'item_id'    => $skuId,
                'change_num' => $stock - $sku['stock'],
                'before_num' => $sku['stock'],
                'after_num'  => $stock,
                'remark'     => 'Excel批量导入更新',
                'create_time'=> time(),
            ]);

            $updated++;
        }

        Db::commit();
    } catch (\Exception $e) {
        Db::rollback();
        throw $e;
    }

    return ['updated' => $updated, 'errors' => $errors];
  }
}

4.3 库存预警联动

批量更新库存后,系统自动检查是否触发库存预警;当 stock 低于预警阈值时,写入预警记录:

php
private function checkStockWarning($skuId, $goodsId, $stock)
{
$warningThreshold = ConfigModel::get('stock_warning_threshold', 10);
if ($stock < $warningThreshold && $stock >= 0) {
    // 避免重复预警
    $exists = StockWarningModel::where('goods_id', $goodsId)
        ->where('item_id', $skuId)
        ->where('status', 0) // 未处理
        ->find();

    if (!$exists) {
        StockWarningModel::create([
            'goods_id'    => $goodsId,
            'item_id'     => $skuId,
            'stock'       => $stock,
            'threshold'   => $warningThreshold,
            'status'      => 0,
            'create_time' => time(),
        ]);

        MessageService::sendToShop($goodsId, '库存预警',
            "商品 SKU 库存仅剩 {$stock} 件,请及时补货");
      }
   }
} 
五、完整开发步骤总结

5.1 数据库变更清单

操作表名说明
CREATEls_category_spec_attr分类规格属性模板表
ALTERls_goods_spec新增 spec_type、sort、is_required 字段
CREATEls_stock_log库存变动日志表
CREATEls_stock_warning库存预警记录表

5.2 后端开发清单

模块文件/类工作内容
数据层CategorySpecAttrModel分类规格属性 Model
数据层GoodsSpecModel规格表 Model,新增字段映射
数据层StockLogModel / StockWarningModel库存日志和预警 Model
业务层SkuGeneratorService多规格笛卡尔积计算引擎
业务层SkuMatcherServiceSKU 匹配服务,支持任意数量规格
业务层GoodsImportServiceExcel 解析和导入主流程
业务层GoodsImportValidator导入数据验证和清洗
业务层GoodsImportWriter分批事务写入
业务层StockUpdateService库存批量更新和预警检查
后台接口GoodsImportController模板下载、文件上传、导入执行接口
后台接口StockController库存批量更新接口

5.3 后台前端开发清单

页面工作内容
分类管理 → 规格属性分类规格属性模板的增删改查,预设规格值配置
商品发布 → 规格设置选择分类后自动带出规格属性模板,支持添加自定义规格
商品列表 → 批量导入下载模板、上传文件、导入预览、执行导入
商品列表 → 批量库存更新下载库存模板、上传文件、更新预览、执行更新

5.4 开发注意事项

Excel 读取的内存控制:使用 PhpSpreadsheet 时,必须设置 setReadDataOnly(true) 跳过公式和样式解析,内存占用直降 60%以上;对于超过 1000 行的数据,建议使用行迭代器逐行读取,而非一次性加载全部数据到内存。

SKU 唯一性约束:ls_goods_item 表的 goods_id + spec_value_ids 组合应建立唯一索引,防止批量导入时产生重复的 SKU 记录;导入前先查询已存在的 SKU,已存在则执行更新而非插入。

事务边界的控制:批量导入时,每批商品的处理在独立事务中执行;如果某批数据出现问题,只需回滚该批次,不影响已成功导入的数据;批次大小建议设为 50 个商品,避免事务过大导致锁表时间过长。

规格数量的上限:虽然笛卡尔积计算引擎支持任意数量的规格,但从用户体验和性能角度考虑,建议限制为最多 5 级规格;超过 5 级规格时,SKU 数量会呈指数级增长,给前端渲染和库存管理带来巨大压力。

兼容性:ls_goods_spec 表新增字段全部设置默认值,保证存量商品数据不受影响;spec_type 默认为1(普通规则),存量规格记录的该字段值自动为 1;批量导入功能作为独立的后台模块开发,不修改原生的商品发布和编辑逻辑。

0
0
0
0
评论
未登录
暂无评论