> /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;
}
}
}