> /tmp/alibaba_today_import.log 2>&1 * * 分润设置(写死): * - extension_type: 1(开启分润) * - concession_pri: 售价 × 15%(商家让利金额) * - extension_one: 15(SKU级分润比例) * - commission: 保持原样(不修改) * * 原价计算规则(根据SKU售价阶梯加价): * 1-20元 => 固定加5元 * 21-50元 => 固定加20元 * 51-120元 => 固定加40元 * 121-200元 => 固定加45元 * 201元以上 => 固定加50元 */ class AlibabaTodayImport extends Command { protected function configure() { $this->setName('alibaba:today-import') ->setDescription('从alibaba_import_goods获取今日创建的商品,调用importStore接口正式入库') ->addOption('mer_id', null, \think\console\input\Option::VALUE_OPTIONAL, '商户ID', '3447') ->addOption('date', null, \think\console\input\Option::VALUE_OPTIONAL, '指定日期 (Y-m-d),默认今天', '') ->addOption('batch', null, \think\console\input\Option::VALUE_OPTIONAL, '每批处理数量', '50') ->addOption('skip', null, \think\console\input\Option::VALUE_OPTIONAL, '跳过前N条(断点续传)', '0') ->addOption('limit', null, \think\console\input\Option::VALUE_OPTIONAL, '限制处理条数,不设置则全部处理', '0'); } protected function execute(Input $input, Output $output) { // 解除时间限制 set_time_limit(0); $output->writeln('========================================'); $output->writeln(' 1688今日商品自动入库 开始'); $output->writeln('========================================'); // 1. 解析参数 $merId = (int)$input->getOption('mer_id'); $date = $input->getOption('date'); $batch = (int)$input->getOption('batch'); $skip = (int)$input->getOption('skip'); $limit = (int)$input->getOption('limit'); if ($batch < 1) $batch = 50; if ($skip < 0) $skip = 0; if ($limit < 0) $limit = 0; if (empty($date)) { $date = date('Y-m-d'); } // ============================================================ // 分润配置(写死) // ============================================================ $profitConfig = [ 'extension_type' => 1, // 1=开启分润 'concession_rate' => 0.15, // 商家让利比例15%(concession_pri = 售价 × 15%) 'extension_one' => 15, // SKU级分润比例15% ]; $output->writeln("商户ID: {$merId}"); $output->writeln("查询日期: {$date}"); $output->writeln("每批数量: {$batch}"); $output->writeln("跳过前N条: {$skip}"); $output->writeln("限制条数: " . ($limit > 0 ? $limit : '不限制')); $output->writeln('分润设置:'); $output->writeln(" extension_type: {$profitConfig['extension_type']} (开启分润)"); $output->writeln(" concession_rate: {$profitConfig['concession_rate']} (售价×{$profitConfig['concession_rate']})"); $output->writeln(" extension_one: {$profitConfig['extension_one']}%"); // 2. 查询今日创建的 alibaba_import_goods 记录(只查ID,不查全字段,节省内存) $todayStart = $date . ' 00:00:00'; $todayEnd = $date . ' 23:59:59'; $output->write('正在查询今日待入库商品... '); $query = Db::name('alibaba_import_goods') ->where('status', 1) ->where('create_time', '>=', $todayStart) ->where('create_time', '<=', $todayEnd) ->order('id', 'ASC'); // 如果设置了 limit,则限制查询数量 if ($limit > 0) { $query->limit($limit); } $allIds = $query->column('id'); $total = count($allIds); $output->writeln("共 {$total} 条"); if ($total === 0) { $output->writeln("{$date} 没有需要入库的商品"); $output->writeln('========================================'); $output->writeln(' 1688今日商品自动入库 结束 (无商品)'); $output->writeln('========================================'); return; } // 3. 应用 skip 跳过已处理的 if ($skip > 0) { $allIds = array_slice($allIds, $skip); $output->writeln("跳过前 {$skip} 条,剩余 " . count($allIds) . " 条"); } // 4. 分批处理 /** @var ProductService $productService */ $productService = app()->make(ProductService::class); $chunks = array_chunk($allIds, $batch); $totalChunks = count($chunks); $output->writeln("共分 {$totalChunks} 批执行"); $output->writeln(''); $globalSuccess = 0; $globalFail = 0; $globalProfitSet = 0; $startTime = time(); foreach ($chunks as $chunkIndex => $idBatch) { $batchStartTime = time(); $batchNum = $chunkIndex + 1; $processedCount = $skip + ($chunkIndex * $batch) + 1; $output->writeln("--- 第 {$batchNum}/{$totalChunks} 批 (已处理 {$processedCount}/{$total}) ---"); foreach ($idBatch as $importId) { $output->write(" [{$processedCount}/{$total}] import_goods_id={$importId} ... "); try { // 4a. 调用原始入库方法 $result = $productService->importToStoreProduct($importId, $merId); if ($result['code'] === 200) { $productId = $result['product_id']; $globalSuccess++; // 4b. 组装数据,调用 ProductRepository::update() 更新原价和分润 try { self::updateProductData($productId, $merId, $profitConfig); $globalProfitSet++; $output->write("[原价+分润已更新] "); } catch (\Throwable $updateE) { $output->write("[更新失败: {$updateE->getMessage()}] "); } $output->writeln("成功 => product_id={$productId}"); } else { $globalFail++; $output->writeln("跳过: {$result['msg']}"); } } catch (\Throwable $e) { $globalFail++; $output->writeln("异常: {$e->getMessage()}"); } $processedCount++; } // 每批结束后输出当前进度和耗时 $batchElapsed = time() - $batchStartTime; $totalElapsed = time() - $startTime; $output->writeln(" 本批耗时: {$batchElapsed}s | 累计耗时: {$totalElapsed}s | 累计成功: {$globalSuccess} | 累计失败: {$globalFail} | 更新: {$globalProfitSet}"); $output->writeln(''); // 每批之间短暂休眠,避免数据库压力过大 if ($chunkIndex < $totalChunks - 1) { $output->writeln(" 等待 1 秒后继续下一批..."); sleep(1); } } // 5. 输出汇总结果 $totalElapsed = time() - $startTime; $output->writeln(''); $output->writeln('========================================'); $output->writeln(' 入库完成'); $output->writeln('========================================'); $output->writeln("总耗时: {$totalElapsed}s"); $output->writeln("总处理: {$total} 条"); $output->writeln("成功: {$globalSuccess} 条"); $output->writeln("失败: {$globalFail} 条"); $output->writeln("已更新原价+分润: {$globalProfitSet} 条"); if ($globalFail > 0) { $output->writeln(''); $output->writeln('提示: 失败的商品可以查看 alibaba 日志通道获取详细信息'); $output->writeln(" 重跑时可加 --skip={$skip} 跳过已处理的商品"); } $output->writeln('========================================'); $output->writeln(' 1688今日商品自动入库 结束'); $output->writeln('========================================'); } /** * 组装数据并调用 ProductRepository::update() 更新商品 * * 从数据库查询商品完整信息,组装成 update 接口所需的格式, * 修改 ot_price(按阶梯计算)和 extension_one(15), * 然后调用 ProductRepository::update() 执行更新。 * * @param int $productId * @param int $merId * @param array $profitConfig * @throws \Throwable */ protected static function updateProductData(int $productId, int $merId, array $profitConfig): void { // 1. 查询 store_product 表获取商品基本信息 $product = Db::name('store_product') ->where('product_id', $productId) ->find(); if (!$product) { throw new \RuntimeException("商品不存在 product_id={$productId}"); } // 2. 查询 store_product_attr_value 表获取所有SKU $skuValues = Db::name('store_product_attr_value') ->where('product_id', $productId) ->select() ->toArray(); if (empty($skuValues)) { throw new \RuntimeException("商品SKU数据为空 product_id={$productId}"); } // 3. 查询 store_product_attr 表获取规格属性 $attrList = Db::name('store_product_attr') ->where('product_id', $productId) ->select() ->toArray(); // 4. 查询 store_product_content 表获取商品详情 $content = Db::name('store_product_content') ->where('product_id', $productId) ->value('content'); // 5. 组装 attrValue 数据(参考 detail 接口返回格式) $attrValue = []; $skuPrices = []; foreach ($skuValues as $sku) { $salePrice = (float)$sku['price']; $skuPrices[] = $salePrice; // 根据售价区间计算原价 $otPrice = self::calcOtPrice($salePrice); // detail 字段(规格明细) $detail = []; if (!empty($sku['detail'])) { $detail = is_string($sku['detail']) ? json_decode($sku['detail'], true) : $sku['detail']; } $attrValue[] = [ 'product_id' => $productId, 'detail' => $detail, 'sku' => $sku['sku'] ?? '', 'stock' => $sku['stock'] ?? 0, 'sales' => $sku['sales'] ?? 0, 'image' => $sku['image'] ?? '', 'bar_code' => $sku['bar_code'] ?? '', 'cost' => (string)($sku['cost'] ?? 0), 'ot_price' => (string)$otPrice, 'price' => (string)($sku['price'] ?? 0), 'volume' => (string)($sku['volume'] ?? 0), 'weight' => (string)($sku['weight'] ?? 0), 'type' => $sku['type'] ?? 0, 'extension_one' => (string)$profitConfig['extension_one'], 'extension_two' => $sku['extension_two'] ?? null, 'unique' => $sku['unique'] ?? '', 'dacang_price' => (string)($sku['dacang_price'] ?? 0), 'cost_price' => $sku['cost_price'] ?? null, 'gong_sku_id' => $sku['gong_sku_id'] ?? null, 'gong_mer_profit' => (string)($sku['gong_mer_profit'] ?? 0), 'gong_pension' => $sku['gong_pension'] ?? null, 'gong_market_price'=> (string)($sku['gong_market_price'] ?? 0), 'plate_mer_profit' => (string)($sku['plate_mer_profit'] ?? 0), 'pension_num' => (string)($sku['pension_num'] ?? 0), 'share_num' => (string)($sku['share_num'] ?? 0), 'stock_num' => (string)($sku['stock_num'] ?? 0), 'shares_num' => (string)($sku['shares_num'] ?? 0), 'is_usable' => $sku['is_usable'] ?? 0, 'sku_id' => $sku['sku_id'] ?? '', 'spec_id' => $sku['spec_id'] ?? '', 'value0' => $sku['value0'] ?? ($sku['sku'] ?? ''), 'dacang_cost_price'=> (string)($sku['dacang_cost_price'] ?? 0), 'no_dacang_cost_price'=> (string)($sku['no_dacang_cost_price'] ?? 0), ]; } // 6. 组装 attr 数据(规格属性) $attr = []; foreach ($attrList as $attrItem) { $detail = !empty($attrItem['attr_values']) ? explode('-!-', $attrItem['attr_values']) : []; $attr[] = [ 'value' => $attrItem['attr_name'], 'detail' => $detail, ]; } // 7. 计算商品级价格 $minPrice = !empty($skuPrices) ? min($skuPrices) : 0; $maxOtPrice = self::calcOtPrice($minPrice); // 取所有SKU中最大的ot_price foreach ($skuPrices as $p) { $ot = self::calcOtPrice($p); if ($ot > $maxOtPrice) { $maxOtPrice = $ot; } } // 8. 组装完整数据(参考 detail 接口返回 + update 接口所需字段) $sliderImages = !empty($product['slider_image']) ? (is_string($product['slider_image']) ? explode(',', $product['slider_image']) : $product['slider_image']) : []; $updateData = [ 'volunteer' => $product['volunteer'] ?? 0, 'image' => $product['image'] ?? '', 'slider_image' => $sliderImages, 'store_name' => $product['store_name'] ?? '', 'store_info' => $product['store_info'] ?? '', 'keyword' => $product['keyword'] ?? '', 'brand_id' => $product['brand_id'] ?? null, 'cate_id' => $product['cate_id'] ?? 0, 'mer_cate_id' => [], // 商户分类,保持空数组 'unit_name' => $product['unit_name'] ?? '件', 'sort' => $product['sort'] ?? 0, 'is_good' => $product['is_good'] ?? 0, 'temp_id' => $product['temp_id'] ?? 0, 'attr' => $attr, 'content' => $content ?? '', 'spec_type' => $product['spec_type'] ?? 1, 'extension_type' => $profitConfig['extension_type'], 'give_coupon_ids' => [], 'is_gift_bag' => $product['is_gift_bag'] ?? 0, 'type' => $product['type'] ?? 1, 'commission' => $product['commission'] ?? '', 'pension' => (string)($product['pension'] ?? '0.00'), 'pension_is' => $product['pension_is'] ?? 0, 'share_is' => $product['share_is'] ?? 0, 'ficti' => $product['ficti'] ?? 0, 'icon_type' => !empty($product['icon_type']) ? (is_string($product['icon_type']) ? json_decode($product['icon_type'], true) : $product['icon_type']) : [], 'attrValue' => $attrValue, ]; // 9. 调用 ProductRepository::update() 执行更新 /** @var ProductRepository $productRepository */ $productRepository = app()->make(ProductRepository::class); $productRepository->update($productId, $updateData, $merId); } /** * 根据售价计算原价(阶梯加价) * * @param float $salePrice SKU售价 * @return float 计算后的原价 */ protected static function calcOtPrice(float $salePrice): float { if ($salePrice <= 0) { return 0; } if ($salePrice >= 1 && $salePrice <= 20) { return $salePrice + 5; } elseif ($salePrice >= 21 && $salePrice <= 50) { return $salePrice + 20; } elseif ($salePrice >= 51 && $salePrice <= 120) { return $salePrice + 40; } elseif ($salePrice >= 121 && $salePrice <= 200) { return $salePrice + 45; } else { // 201元以上(含201-300及超过300),统一加50元 return $salePrice + 50; } } }