100, // 每页拉取数量 'max_pages' => 0, // 最大页数限制 'price_change_rate' => 0.1, // 价格变动比率阈值(10%) ]; protected function configure() { // 指令配置 $this->setName('wiwibaoproductsync') ->setDescription('the wiwibaoproductsync command'); } protected function execute(Input $input, Output $output) { $output->writeln('定时任务执行中...'); $this->syncGoodsFromGong(); $output->writeln('定时任务执行完成'); } /** * 同步微唯宝商品数据(主方法) */ public function syncGoodsFromGong() { try { set_time_limit(0); // 1. 批量拉取所有商品数据 $allProducts = $this->fetchAllProducts(); if (empty($allProducts)) { Log::info('未拉取到任何商品数据'); return 'success'; } // 2. 获取本地所有SPU ID $localSpuIds = Db::name('douhuomall')->column('spu_id'); $syncedSpuIds = []; // 3. 批量处理商品数据 foreach ($allProducts as $product) { try { $spuId = $this->processSingleProduct($product); if ($spuId) { $syncedSpuIds[] = $spuId; } } catch (\Exception $e) { Log::error("处理商品 {$product['goods_id']} 失败: " . $e->getMessage()); continue; } } // 4. 处理需要下架的商品(本地有但第三方已下架) $this->handleOfflineProducts($localSpuIds, $syncedSpuIds); return 'success'; } catch (\Exception $e) { $this->logError('商品同步失败', $e); return 'error'; } } /** * 批量拉取所有商品数据 */ protected function fetchAllProducts() { $allData = []; // 第一页 $firstPageData = GongApiRequest::get_goods_list(1, $this->config['page_size']); if (empty($firstPageData['list'])) { return []; } $allData = $firstPageData['list']; $totalPages = $firstPageData['pages'] ?? 1; if (!empty($this->config['max_pages'])) { $totalPages = min($totalPages, $this->config['max_pages']); } // 并行获取剩余页面 for ($page = 2; $page <= $totalPages; $page++) { $pageData = GongApiRequest::get_goods_list($page, $this->config['page_size']); if (!empty($pageData['list'])) { $allData = array_merge($allData, $pageData['list']); } } return $allData; } /** * 处理单个商品 */ protected function processSingleProduct($productData) { $goodsId = $productData['goods_id']; // 1. 获取商品详情 $goodsInfo = GongApiRequest::get_goods_info($goodsId); if (!$goodsInfo) { return null; } // 2. 处理图片信息 $imageData = $this->processProductImages($goodsId, $goodsInfo); // 3. 处理SKU信息 $skuInfo = $this->processSkuInfo($goodsInfo['sku_list'] ?? [], $goodsId); // 4. 构建保存数据 $saveData = $this->buildSaveData($goodsInfo, $skuInfo, $imageData); // 5. 保存到本地(使用原有的add方法逻辑) $spuId = $this->addToDouhuomall($saveData); // 6. 检查并处理已上架商品的价格变动 $this->checkAndProcessOnlineProduct($spuId, $saveData); return $spuId; } /** * 处理商品图片信息 */ protected function processProductImages($goodsId, $goodsInfo) { // 如果你需要保存图片到本地,可以在这里实现 // 否则直接返回URL return [ 'cover_url' => $goodsInfo['main_img'] ?? '', 'detail_images' => $goodsInfo['detail_img'] ?? [], 'detail_img_list' => $goodsInfo['detail_img_list'] ?? [] ]; } /** * 处理SKU信息 */ protected function processSkuInfo($skuList, $goodsId) { if (empty($skuList)) { return []; } $processedSkus = []; foreach ($skuList as $sku) { if (empty($sku['plat_price'])) { continue; } $processedSku = [ 'sku_id' => $sku['sku_id'] ?? 0, 'plat_price' => $sku['plat_price'], 'img_url' => $sku['img_url'], 'retail_price' => $sku['retail_price'] ?? 0, 'cost_price' => $this->calculateCostPrice($sku['plat_price']), 'market_price' => $this->calculateMarketPrice($sku['plat_price']), 'profit' => $this->calculateProfit($sku['plat_price']), 'mer_profit' => $this->calculateMerProfit($sku['plat_price']), 'yanglaojin' => '0.00', 'main_img' => $sku['main_img'] ?? '', 'attr' => !empty($sku['attr']) ? $sku['attr'] : [] ]; $processedSkus[] = array_merge($sku, $processedSku); } return $processedSkus; } /** * 构建保存数据 */ protected function buildSaveData($goodsInfo, $skuInfo, $imageData) { // 构建详情HTML $detailHtml = $this->buildDetailHtml($imageData['detail_images']); return [ 'spu_id' => $goodsInfo['goods_id'], 'skuId_info' => $skuInfo, 'sys_id' => '112', 'goods_id' => $goodsInfo['goods_id'], 'spuId_info' => array_merge( $goodsInfo, [ 'cover_url' => $imageData['cover_url'], 'detail' => $detailHtml, 'detail_img_list' => $imageData['detail_img_list'] ] ), 'sku_id' => $skuInfo[0]['sku_id'] ?? 0, 'status' => $goodsInfo['status'] ?? 0, 'cate_ids' => $goodsInfo['cate_ids'] ?? '', 'title' => $goodsInfo['spu_name'] ?? '', 'cxb_cate_id' => 0 ]; } /** * 构建详情HTML */ protected function buildDetailHtml($detailImages) { if (empty($detailImages)) { return ''; } $detailHtml = ''; foreach ((array)$detailImages as $imgUrl) { $detailHtml .= ""; } return $detailHtml; } /** * 保存到douhuomall表(基于原有add方法) */ protected function addToDouhuomall($data) { try { $findData = Db::name('douhuomall')->where('spu_id', $data['spu_id'])->find(); // 对SKU按成本价排序,获取最低成本价的SKU $skuInfo = $this->arrSort($data['skuId_info'], 'cost_price', SORT_ASC); // 计算价格字段 $data['cost_price'] = $skuInfo[0]['cost_price'] ?? 0; $data['market_price'] = $skuInfo[0]['market_price'] ?? 0; $data['ot_price'] = $skuInfo[0]['retail_price'] ?? 0; $data['mer_profit'] = $skuInfo[0]['mer_profit'] ?? 0; $data['pension'] = $skuInfo[0]['yanglaojin'] ?? 0; $data['profit'] = (empty($data['cost_price']) || empty($data['market_price'])) ? 0 : bcdiv((string)$data['market_price'], (string)$data['cost_price'], 2); $now = date('Y-m-d H:i:s'); if (empty($findData)) { // 新增 $insertData = [ 'spu_id' => $data['spu_id'], 'skuId_info' => json_encode($data['skuId_info'], JSON_UNESCAPED_UNICODE), 'sys_id' => $data['sys_id'], 'goods_id' => $data['goods_id'], 'spuId_info' => json_encode($data['spuId_info'], JSON_UNESCAPED_UNICODE), 'create_time' => $now, 'update_time' => $now, 'sku_id' => $data['sku_id'], 'status' => $data['status'], 'cate_ids' => $data['cate_ids'], 'title' => $data['title'], 'cost_price' => $data['cost_price'], 'market_price' => $data['market_price'], 'ot_price' => $data['ot_price'], 'profit' => $data['profit'], 'mer_profit' => $data['mer_profit'], 'pension' => $data['pension'], 'cxb_cate_id' => $data['cxb_cate_id'] ]; Db::name('douhuomall')->insert($insertData); } else { // 更新 // 检查是否需要恢复已删除商品的上架状态 $shouldRestore = $this->shouldRestoreProduct($findData, $data); if ($shouldRestore) { $this->restoreProduct($data['spu_id']); } $updateData = [ 'status' => $data['status'], 'update_time' => $now, 'cost_price' => $data['cost_price'], 'market_price' => $data['market_price'], 'ot_price' => $data['ot_price'], 'mer_profit' => $data['mer_profit'], 'pension' => $data['pension'], 'profit' => $data['profit'], 'skuId_info' => json_encode($data['skuId_info'], JSON_UNESCAPED_UNICODE), 'spuId_info' => json_encode($data['spuId_info'], JSON_UNESCAPED_UNICODE), 'title' => $data['title'], 'cate_ids' => $data['cate_ids'], 'cxb_cate_id' => $data['cxb_cate_id'] ]; Db::name('douhuomall') ->where('spu_id', $findData['spu_id']) ->update($updateData); } return $data['spu_id']; } catch (\Exception $e) { Log::error("保存商品到douhuomall失败: " . $e->getMessage()); throw $e; } } /** * 检查是否需要恢复已删除商品的上架状态 */ protected function shouldRestoreProduct($findData, $newData) { // 原有逻辑:如果货物属于已被删除状态,并且新更新进来的状态非删除状态 // 并且成本价、零售价、市场价、利润率没有变动的话 if ($findData['status'] == DouhuomallEnum::STATUS['Delete']['code'] && $newData['status'] !== DouhuomallEnum::STATUS['Delete']['code'] && $newData['cost_price'] == $findData['cost_price'] && $newData['ot_price'] == $findData['ot_price'] && $newData['market_price'] == $findData['market_price'] && $newData['profit'] == $findData['profit']) { // 检查是否有已下架的商品 $count = Db::name('store_product') ->where('spu_id', $newData['spu_id']) ->where('is_show', 0) ->where('is_status', 0) ->count(); return $count > 0; } return false; } /** * 恢复商品上架状态 */ protected function restoreProduct($spuId) { Db::name('store_product') ->where('spu_id', $spuId) ->update(['is_show' => 1]); Log::info("恢复商品上架状态: spu_id={$spuId}"); } /** * 检查并处理已上架商品 */ protected function checkAndProcessOnlineProduct($spuId, $syncData) { // 检查商品是否已上架 $product = Db::name('store_product') ->where('spu_id', $spuId) ->find(); if (!$product) { return; } // 获取本地商品价格信息 $localPrices = $this->getLocalProductPrices($product['product_id']); // 解析同步数据中的价格 $syncSkuInfo = $syncData['skuId_info']; $syncPrices = $this->extractPricesFromSkuInfo($syncSkuInfo); // 比较价格,判断是否需要下架 $priceChanged = $this->checkPriceChange($localPrices, $syncPrices); if ($priceChanged) { // 价格变动超过阈值,下架商品并记录 $this->offlineProductWithRecord($product, $syncData, $localPrices, $syncPrices); } else { // 价格未变动或变动很小,只更新图片 $this->updateProductImages($spuId); } } /** * 获取本地商品价格信息 */ protected function getLocalProductPrices($productId) { $product = Db::name('store_product') ->where('product_id', $productId) ->field('price, cost, ot_price') ->find(); // 获取SKU价格 $skus = Db::name('store_product_attr_value') ->where('product_id', $productId) ->field('sku, price, cost') ->select() ->toArray(); return [ 'product_price' => $product['price'] ?? 0, 'product_cost' => $product['cost'] ?? 0, 'product_ot_price' => $product['ot_price'] ?? 0, 'skus' => $skus ]; } /** * 从SKU信息中提取价格 */ protected function extractPricesFromSkuInfo($skuInfo) { $prices = []; foreach ($skuInfo as $sku) { $prices[] = [ 'sku_id' => $sku['sku_id'] ?? '', 'market_price' => $sku['market_price'] ?? 0, 'cost_price' => $sku['cost_price'] ?? 0, 'retail_price' => $sku['retail_price'] ?? 0 ]; } return $prices; } /** * 检查价格是否变动超过阈值 */ protected function checkPriceChange($localPrices, $syncPrices) { // 如果没有SKU,比较商品主价格 if (empty($localPrices['skus'])) { $localPrice = $localPrices['product_price']; $syncPrice = $syncPrices[0]['market_price'] ?? 0; if ($localPrice > 0 && $syncPrice > 0) { $changeRate = abs(($syncPrice - $localPrice) / $localPrice); return $changeRate > $this->config['price_change_rate']; } return false; } // 如果有SKU,匹配SKU进行比较 foreach ($localPrices['skus'] as $localSku) { foreach ($syncPrices as $syncSku) { // 尝试匹配SKU if ($this->matchSku($localSku, $syncSku)) { $localPrice = $localSku['price'] ?? 0; $syncPrice = $syncSku['market_price'] ?? 0; if ($localPrice > 0 && $syncPrice > 0) { $changeRate = abs(($syncPrice - $localPrice) / $localPrice); if ($changeRate > $this->config['price_change_rate']) { return true; } } } } } return false; } /** * 匹配SKU(根据业务逻辑实现) */ protected function matchSku($localSku, $syncSku) { // 根据SKU ID匹配 if (!empty($localSku['sku']) && !empty($syncSku['sku_id'])) { return $localSku['sku'] == $syncSku['sku_id']; } // 可以根据其他属性匹配,如规格等 return false; } /** * 下架商品并记录变动 */ protected function offlineProductWithRecord($product, $syncData, $localPrices, $syncPrices) { $productId = $product['product_id']; $spuId = $product['spu_id']; // 1. 收集变动前数据 $beforeData = [ 'product_id' => $productId, 'spu_id' => $spuId, 'title' => $product['store_name'] ?? '', 'status' => $product['is_show'] ?? 0, 'price_info' => $localPrices, 'images' => [ 'image' => $product['image'] ?? '', 'slider_image' => $product['slider_image'] ?? '' ], 'record_time' => date('Y-m-d H:i:s') ]; // 2. 下架商品 Db::name('store_product') ->where('product_id', $productId) ->update([ 'is_show' => 0, 'offline_time' => time(), 'offline_reason' => '第三方平台价格变动' ]); // 3. 收集变动后数据 $afterData = [ 'spu_id' => $spuId, 'sync_price_info' => $syncPrices, 'sync_title' => $syncData['title'], 'sync_status' => $syncData['status'], 'sync_time' => date('Y-m-d H:i:s') ]; // 4. 记录变动日志 $this->recordChangeLog( $productId, $spuId, 'price_change', '第三方平台价格变动超过阈值', $beforeData, $afterData ); Log::info("商品下架: product_id={$productId}, spu_id={$spuId}, 原因: 价格变动"); } /** * 更新商品图片(基于原有updata_product_img方法) */ protected function updateProductImages($spuId) { // 1. 验证该SPU是否已上架 $productId = Db::name('store_product') ->where('spu_id', $spuId) ->value('product_id'); if (!$productId) { return true; } // 2. 查找微唯宝商品 $data = Db::name('douhuomall') ->where('spu_id', $spuId) ->find(); if (!$data) { return false; } // 3. 解析商品信息 $spuIdInfo = json_decode($data['spuId_info'], true); // 4. 处理图片数据 $sliderImage = $spuIdInfo['detail_img_list'] ?? ''; $image = $spuIdInfo['cover_url'] ?? ''; try { $sliderImage = implode(',', json_decode($sliderImage, true)); } catch (\Exception $e) { $sliderImage = $image; } // 5. 更新产品图片数据 $updateData = [ 'image' => $image, 'slider_image' => $sliderImage, ]; Db::name('store_product') ->where('product_id', $productId) ->update($updateData); // 6. 更新商品详情 $detail = $spuIdInfo['detail'] ?? ''; if (!is_null(json_decode($detail))) { $detailList = json_decode($detail); $newDetail = ''; foreach ($detailList as $value) { $newDetail .= ""; } $detail = $newDetail; } Db::name('store_product_content') ->where('product_id', $productId) ->update(['content' => $detail]); return true; } /** * 处理需要下架的商品(第三方已下架) */ protected function handleOfflineProducts($localSpuIds, $syncedSpuIds) { $offlineSpuIds = array_diff($localSpuIds, $syncedSpuIds); if (empty($offlineSpuIds)) { return; } foreach ($offlineSpuIds as $spuId) { $this->offlineProductBySpuId($spuId, 'third_party_offline'); } Log::info("下架第三方已下架商品", ['count' => count($offlineSpuIds), 'spu_ids' => $offlineSpuIds]); } /** * 根据SPU ID下架商品 */ protected function offlineProductBySpuId($spuId, $reason) { // 查找已上架的商品 $products = Db::name('store_product') ->where('spu_id', $spuId) ->select(); foreach ($products as $product) { // 记录下架前数据 $beforeData = [ 'product_id' => $product['product_id'], 'spu_id' => $spuId, 'title' => $product['store_name'] ?? '', 'status' => $product['is_show'] ?? 0, 'price' => $product['price'] ?? 0, 'record_time' => date('Y-m-d H:i:s') ]; // 下架商品 Db::name('store_product') ->where('product_id', $product['product_id']) ->update([ 'is_show' => 0, 'offline_time' => time(), 'offline_reason' => $reason ]); // 记录日志 $this->recordChangeLog( $product['product_id'], $spuId, 'third_party_offline', $reason, $beforeData, [] ); } // 从douhuomall表中删除 Db::name('douhuomall')->where('spu_id', $spuId)->delete(); } /** * 记录变动日志 */ protected function recordChangeLog($productId, $spuId, $changeType, $reason, $beforeData, $afterData) { $logData = [ 'product_id' => $productId, 'spu_id' => $spuId, 'change_type' => $changeType, 'change_reason' => $reason, 'before_data' => json_encode($beforeData, JSON_UNESCAPED_UNICODE), 'after_data' => json_encode($afterData, JSON_UNESCAPED_UNICODE), 'change_time' => time(), 'create_time' => time() ]; Db::name('product_change_log')->insert($logData); } /** * 数组排序(从原有add方法中提取) */ protected function arrSort($array, $keys, $sort = SORT_DESC) { $keysValue = []; foreach ($array as $k => $v) { $keysValue[$k] = $v[$keys] ?? 0; } array_multisort($keysValue, $sort, $array); return $array; } /** * 计算成本价 */ protected function calculateCostPrice($platPrice) { return bcadd($platPrice, bcmul($platPrice, '0.03', 4), 2); } /** * 计算市场价 */ protected function calculateMarketPrice($platPrice) { $costPrice = $this->calculateCostPrice($platPrice); return bcadd($costPrice, bcmul($costPrice, '0.2', 4), 2); } /** * 计算利润率 */ protected function calculateProfit($platPrice) { $costPrice = $this->calculateCostPrice($platPrice); $marketPrice = $this->calculateMarketPrice($platPrice); return bcdiv($marketPrice, $costPrice, 2); } /** * 计算毛利 */ protected function calculateMerProfit($platPrice) { $costPrice = $this->calculateCostPrice($platPrice); $marketPrice = $this->calculateMarketPrice($platPrice); return bcsub($marketPrice, $costPrice, 2); } /** * 记录错误 */ protected function logError($message, \Exception $e) { $errorInfo = [ 'message' => $message, 'file' => $e->getFile(), 'line' => $e->getLine(), 'error' => $e->getMessage() ]; Log::error('商品同步错误', $errorInfo); Db::name('log')->insert([ 'data' => json_encode($errorInfo, JSON_UNESCAPED_UNICODE), 'create_time' => time() ]); } }