WiwibaoProductSync.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. <?php
  2. declare (strict_types=1);
  3. namespace app\command;
  4. use app\common\enum\douhuomall\DouhuomallEnum;
  5. use app\traits\GongApiRequest;
  6. use think\console\Command;
  7. use think\console\Input;
  8. use think\console\Output;
  9. use think\facade\Db;
  10. use think\facade\Log;
  11. class WiwibaoProductSync extends Command
  12. {
  13. use GongApiRequest;
  14. // 配置参数
  15. protected $config = [
  16. 'page_size' => 100, // 每页拉取数量
  17. 'max_pages' => 0, // 最大页数限制
  18. 'price_change_rate' => 0.1, // 价格变动比率阈值(10%)
  19. ];
  20. protected function configure()
  21. {
  22. // 指令配置
  23. $this->setName('wiwibaoproductsync')
  24. ->setDescription('the wiwibaoproductsync command');
  25. }
  26. protected function execute(Input $input, Output $output)
  27. {
  28. $output->writeln('定时任务执行中...');
  29. $this->syncGoodsFromGong();
  30. $output->writeln('定时任务执行完成');
  31. }
  32. /**
  33. * 同步微唯宝商品数据(主方法)
  34. */
  35. public function syncGoodsFromGong()
  36. {
  37. try {
  38. set_time_limit(0);
  39. // 1. 批量拉取所有商品数据
  40. $allProducts = $this->fetchAllProducts();
  41. if (empty($allProducts)) {
  42. Log::info('未拉取到任何商品数据');
  43. return 'success';
  44. }
  45. // 2. 获取本地所有SPU ID
  46. $localSpuIds = Db::name('douhuomall')->column('spu_id');
  47. $syncedSpuIds = [];
  48. // 3. 批量处理商品数据
  49. foreach ($allProducts as $product) {
  50. try {
  51. $spuId = $this->processSingleProduct($product);
  52. if ($spuId) {
  53. $syncedSpuIds[] = $spuId;
  54. }
  55. } catch (\Exception $e) {
  56. Log::error("处理商品 {$product['goods_id']} 失败: " . $e->getMessage());
  57. continue;
  58. }
  59. }
  60. // 4. 处理需要下架的商品(本地有但第三方已下架)
  61. $this->handleOfflineProducts($localSpuIds, $syncedSpuIds);
  62. return 'success';
  63. } catch (\Exception $e) {
  64. $this->logError('商品同步失败', $e);
  65. return 'error';
  66. }
  67. }
  68. /**
  69. * 批量拉取所有商品数据
  70. */
  71. protected function fetchAllProducts()
  72. {
  73. $allData = [];
  74. // 第一页
  75. $firstPageData = GongApiRequest::get_goods_list(1, $this->config['page_size']);
  76. if (empty($firstPageData['list'])) {
  77. return [];
  78. }
  79. $allData = $firstPageData['list'];
  80. $totalPages = $firstPageData['pages'] ?? 1;
  81. if (!empty($this->config['max_pages'])) {
  82. $totalPages = min($totalPages, $this->config['max_pages']);
  83. }
  84. // 并行获取剩余页面
  85. for ($page = 2; $page <= $totalPages; $page++) {
  86. $pageData = GongApiRequest::get_goods_list($page, $this->config['page_size']);
  87. if (!empty($pageData['list'])) {
  88. $allData = array_merge($allData, $pageData['list']);
  89. }
  90. }
  91. return $allData;
  92. }
  93. /**
  94. * 处理单个商品
  95. */
  96. protected function processSingleProduct($productData)
  97. {
  98. $goodsId = $productData['goods_id'];
  99. // 1. 获取商品详情
  100. $goodsInfo = GongApiRequest::get_goods_info($goodsId);
  101. if (!$goodsInfo) {
  102. return null;
  103. }
  104. // 2. 处理图片信息
  105. $imageData = $this->processProductImages($goodsId, $goodsInfo);
  106. // 3. 处理SKU信息
  107. $skuInfo = $this->processSkuInfo($goodsInfo['sku_list'] ?? [], $goodsId);
  108. // 4. 构建保存数据
  109. $saveData = $this->buildSaveData($goodsInfo, $skuInfo, $imageData);
  110. // 5. 保存到本地(使用原有的add方法逻辑)
  111. $spuId = $this->addToDouhuomall($saveData);
  112. // 6. 检查并处理已上架商品的价格变动
  113. $this->checkAndProcessOnlineProduct($spuId, $saveData);
  114. return $spuId;
  115. }
  116. /**
  117. * 处理商品图片信息
  118. */
  119. protected function processProductImages($goodsId, $goodsInfo)
  120. {
  121. // 如果你需要保存图片到本地,可以在这里实现
  122. // 否则直接返回URL
  123. return [
  124. 'cover_url' => $goodsInfo['main_img'] ?? '',
  125. 'detail_images' => $goodsInfo['detail_img'] ?? [],
  126. 'detail_img_list' => $goodsInfo['detail_img_list'] ?? []
  127. ];
  128. }
  129. /**
  130. * 处理SKU信息
  131. */
  132. protected function processSkuInfo($skuList, $goodsId)
  133. {
  134. if (empty($skuList)) {
  135. return [];
  136. }
  137. $processedSkus = [];
  138. foreach ($skuList as $sku) {
  139. if (empty($sku['plat_price'])) {
  140. continue;
  141. }
  142. $processedSku = [
  143. 'sku_id' => $sku['sku_id'] ?? 0,
  144. 'plat_price' => $sku['plat_price'],
  145. 'img_url' => $sku['main_img'],
  146. 'retail_price' => $sku['retail_price'] ?? 0,
  147. 'cost_price' => $this->calculateCostPrice($sku['plat_price']),
  148. 'market_price' => $this->calculateMarketPrice($sku['plat_price']),
  149. 'profit' => $this->calculateProfit($sku['plat_price']),
  150. 'mer_profit' => $this->calculateMerProfit($sku['plat_price']),
  151. 'yanglaojin' => '0.00',
  152. 'main_img' => $sku['main_img'] ?? '',
  153. 'attr' => !empty($sku['attr']) ? $sku['attr'] : []
  154. ];
  155. $processedSkus[] = array_merge($sku, $processedSku);
  156. }
  157. return $processedSkus;
  158. }
  159. /**
  160. * 构建保存数据
  161. */
  162. protected function buildSaveData($goodsInfo, $skuInfo, $imageData)
  163. {
  164. // 构建详情HTML
  165. $detailHtml = $this->buildDetailHtml($imageData['detail_images']);
  166. return [
  167. 'spu_id' => $goodsInfo['goods_id'],
  168. 'skuId_info' => $skuInfo,
  169. 'sys_id' => '112',
  170. 'goods_id' => $goodsInfo['goods_id'],
  171. 'spuId_info' => array_merge(
  172. $goodsInfo,
  173. [
  174. 'cover_url' => $imageData['cover_url'],
  175. 'detail' => $detailHtml,
  176. 'detail_img_list' => $imageData['detail_img_list']
  177. ]
  178. ),
  179. 'sku_id' => $skuInfo[0]['sku_id'] ?? 0,
  180. 'status' => $goodsInfo['status'] ?? 0,
  181. 'cate_ids' => $goodsInfo['cate_ids'] ?? '',
  182. 'title' => $goodsInfo['spu_name'] ?? '',
  183. 'cxb_cate_id' => 0
  184. ];
  185. }
  186. /**
  187. * 构建详情HTML
  188. */
  189. protected function buildDetailHtml($detailImages)
  190. {
  191. if (empty($detailImages)) {
  192. return '';
  193. }
  194. $detailHtml = '';
  195. foreach ((array)$detailImages as $imgUrl) {
  196. $detailHtml .= "<img src='{$imgUrl}' referrerpolicy='no-referrer' />";
  197. }
  198. return $detailHtml;
  199. }
  200. /**
  201. * 保存到douhuomall表(基于原有add方法)
  202. */
  203. protected function addToDouhuomall($data)
  204. {
  205. try {
  206. $findData = Db::name('douhuomall')->where('spu_id', $data['spu_id'])->find();
  207. // 对SKU按成本价排序,获取最低成本价的SKU
  208. $skuInfo = $this->arrSort($data['skuId_info'], 'cost_price', SORT_ASC);
  209. // 计算价格字段
  210. $data['cost_price'] = $skuInfo[0]['cost_price'] ?? 0;
  211. $data['market_price'] = $skuInfo[0]['market_price'] ?? 0;
  212. $data['ot_price'] = $skuInfo[0]['retail_price'] ?? 0;
  213. $data['mer_profit'] = $skuInfo[0]['mer_profit'] ?? 0;
  214. $data['pension'] = $skuInfo[0]['yanglaojin'] ?? 0;
  215. $data['profit'] = (empty($data['cost_price']) || empty($data['market_price']))
  216. ? 0
  217. : bcdiv((string)$data['market_price'], (string)$data['cost_price'], 2);
  218. $now = date('Y-m-d H:i:s');
  219. if (empty($findData)) {
  220. // 新增
  221. $insertData = [
  222. 'spu_id' => $data['spu_id'],
  223. 'skuId_info' => json_encode($data['skuId_info'], JSON_UNESCAPED_UNICODE),
  224. 'sys_id' => $data['sys_id'],
  225. 'goods_id' => $data['goods_id'],
  226. 'spuId_info' => json_encode($data['spuId_info'], JSON_UNESCAPED_UNICODE),
  227. 'create_time' => $now,
  228. 'update_time' => $now,
  229. 'sku_id' => $data['sku_id'],
  230. 'status' => $data['status'],
  231. 'cate_ids' => $data['cate_ids'],
  232. 'title' => $data['title'],
  233. 'cost_price' => $data['cost_price'],
  234. 'market_price' => $data['market_price'],
  235. 'ot_price' => $data['ot_price'],
  236. 'profit' => $data['profit'],
  237. 'mer_profit' => $data['mer_profit'],
  238. 'pension' => $data['pension'],
  239. 'cxb_cate_id' => $data['cxb_cate_id']
  240. ];
  241. Db::name('douhuomall')->insert($insertData);
  242. } else {
  243. // 更新
  244. // 检查是否需要恢复已删除商品的上架状态
  245. $shouldRestore = $this->shouldRestoreProduct($findData, $data);
  246. if ($shouldRestore) {
  247. $this->restoreProduct($data['spu_id']);
  248. }
  249. $updateData = [
  250. 'status' => $data['status'],
  251. 'update_time' => $now,
  252. 'cost_price' => $data['cost_price'],
  253. 'market_price' => $data['market_price'],
  254. 'ot_price' => $data['ot_price'],
  255. 'mer_profit' => $data['mer_profit'],
  256. 'pension' => $data['pension'],
  257. 'profit' => $data['profit'],
  258. 'skuId_info' => json_encode($data['skuId_info'], JSON_UNESCAPED_UNICODE),
  259. 'spuId_info' => json_encode($data['spuId_info'], JSON_UNESCAPED_UNICODE),
  260. 'title' => $data['title'],
  261. 'cate_ids' => $data['cate_ids'],
  262. 'cxb_cate_id' => $data['cxb_cate_id']
  263. ];
  264. Db::name('douhuomall')
  265. ->where('spu_id', $findData['spu_id'])
  266. ->update($updateData);
  267. }
  268. return $data['spu_id'];
  269. } catch (\Exception $e) {
  270. Log::error("保存商品到douhuomall失败: " . $e->getMessage());
  271. throw $e;
  272. }
  273. }
  274. /**
  275. * 检查是否需要恢复已删除商品的上架状态
  276. */
  277. protected function shouldRestoreProduct($findData, $newData)
  278. {
  279. // 原有逻辑:如果货物属于已被删除状态,并且新更新进来的状态非删除状态
  280. // 并且成本价、零售价、市场价、利润率没有变动的话
  281. if ($findData['status'] == DouhuomallEnum::STATUS['Delete']['code']
  282. && $newData['status'] !== DouhuomallEnum::STATUS['Delete']['code']
  283. && $newData['cost_price'] == $findData['cost_price']
  284. && $newData['ot_price'] == $findData['ot_price']
  285. && $newData['market_price'] == $findData['market_price']
  286. && $newData['profit'] == $findData['profit']) {
  287. // 检查是否有已下架的商品
  288. $count = Db::name('store_product')
  289. ->where('spu_id', $newData['spu_id'])
  290. ->where('is_show', 0)
  291. ->where('is_status', 0)
  292. ->count();
  293. return $count > 0;
  294. }
  295. return false;
  296. }
  297. /**
  298. * 恢复商品上架状态
  299. */
  300. protected function restoreProduct($spuId)
  301. {
  302. Db::name('store_product')
  303. ->where('spu_id', $spuId)
  304. ->update([
  305. 'is_show' => 1,
  306. 'status' => 0
  307. ]);
  308. Log::info("恢复商品上架状态: spu_id={$spuId}");
  309. }
  310. /**
  311. * 检查并处理已上架商品
  312. */
  313. protected function checkAndProcessOnlineProduct($spuId, $syncData)
  314. {
  315. // 检查商品是否已上架
  316. $product = Db::name('store_product')
  317. ->where('spu_id', $spuId)
  318. ->find();
  319. if (!$product) {
  320. return;
  321. }
  322. // 获取本地商品价格信息
  323. $localPrices = $this->getLocalProductPrices($product['product_id']);
  324. // 解析同步数据中的价格
  325. $syncSkuInfo = $syncData['skuId_info'];
  326. $syncPrices = $this->extractPricesFromSkuInfo($syncSkuInfo);
  327. // 比较价格,判断是否需要下架
  328. $priceChanged = $this->checkPriceChange($localPrices, $syncPrices);
  329. if ($priceChanged) {
  330. // 价格变动超过阈值,下架商品并记录
  331. $this->offlineProductWithRecord($product, $syncData, $localPrices, $syncPrices);
  332. } else {
  333. // 价格未变动或变动很小,只更新图片
  334. $this->updateProductImages($spuId);
  335. }
  336. }
  337. /**
  338. * 获取本地商品价格信息
  339. */
  340. protected function getLocalProductPrices($productId)
  341. {
  342. $product = Db::name('store_product')
  343. ->where('product_id', $productId)
  344. ->field('price, cost, ot_price')
  345. ->find();
  346. // 获取SKU价格
  347. $skus = Db::name('store_product_attr_value')
  348. ->where('product_id', $productId)
  349. ->field('sku, price, cost')
  350. ->select()
  351. ->toArray();
  352. return [
  353. 'product_price' => $product['price'] ?? 0,
  354. 'product_cost' => $product['cost'] ?? 0,
  355. 'product_ot_price' => $product['ot_price'] ?? 0,
  356. 'skus' => $skus
  357. ];
  358. }
  359. /**
  360. * 从SKU信息中提取价格
  361. */
  362. protected function extractPricesFromSkuInfo($skuInfo)
  363. {
  364. $prices = [];
  365. foreach ($skuInfo as $sku) {
  366. $prices[] = [
  367. 'sku_id' => $sku['sku_id'] ?? '',
  368. 'market_price' => $sku['market_price'] ?? 0,
  369. 'cost_price' => $sku['cost_price'] ?? 0,
  370. 'retail_price' => $sku['retail_price'] ?? 0
  371. ];
  372. }
  373. return $prices;
  374. }
  375. /**
  376. * 检查价格是否变动超过阈值
  377. */
  378. protected function checkPriceChange($localPrices, $syncPrices)
  379. {
  380. // 如果没有SKU,比较商品主价格
  381. if (empty($localPrices['skus'])) {
  382. $localPrice = $localPrices['product_price'];
  383. $syncPrice = $syncPrices[0]['market_price'] ?? 0;
  384. if ($localPrice > 0 && $syncPrice > 0) {
  385. $changeRate = abs(($syncPrice - $localPrice) / $localPrice);
  386. return $changeRate > $this->config['price_change_rate'];
  387. }
  388. return false;
  389. }
  390. // 如果有SKU,匹配SKU进行比较
  391. foreach ($localPrices['skus'] as $localSku) {
  392. foreach ($syncPrices as $syncSku) {
  393. // 尝试匹配SKU
  394. if ($this->matchSku($localSku, $syncSku)) {
  395. $localPrice = $localSku['price'] ?? 0;
  396. $syncPrice = $syncSku['market_price'] ?? 0;
  397. if ($localPrice > 0 && $syncPrice > 0) {
  398. $changeRate = abs(($syncPrice - $localPrice) / $localPrice);
  399. if ($changeRate > $this->config['price_change_rate']) {
  400. return true;
  401. }
  402. }
  403. }
  404. }
  405. }
  406. return false;
  407. }
  408. /**
  409. * 匹配SKU(根据业务逻辑实现)
  410. */
  411. protected function matchSku($localSku, $syncSku)
  412. {
  413. // 根据SKU ID匹配
  414. if (!empty($localSku['sku']) && !empty($syncSku['sku_id'])) {
  415. return $localSku['sku'] == $syncSku['sku_id'];
  416. }
  417. // 可以根据其他属性匹配,如规格等
  418. return false;
  419. }
  420. /**
  421. * 下架商品并记录变动
  422. */
  423. protected function offlineProductWithRecord($product, $syncData, $localPrices, $syncPrices)
  424. {
  425. $productId = $product['product_id'];
  426. $spuId = $product['spu_id'];
  427. // 1. 收集变动前数据
  428. $beforeData = [
  429. 'product_id' => $productId,
  430. 'spu_id' => $spuId,
  431. 'title' => $product['store_name'] ?? '',
  432. 'status' => $product['is_show'] ?? 0,
  433. 'price_info' => $localPrices,
  434. 'images' => [
  435. 'image' => $product['image'] ?? '',
  436. 'slider_image' => $product['slider_image'] ?? ''
  437. ],
  438. 'record_time' => date('Y-m-d H:i:s')
  439. ];
  440. // 2. 下架商品
  441. Db::name('store_product')
  442. ->where('product_id', $productId)
  443. ->update([
  444. 'is_show' => 0,
  445. 'status' => 2,
  446. 'is_status' => 0
  447. ]);
  448. // 3. 收集变动后数据
  449. $afterData = [
  450. 'spu_id' => $spuId,
  451. 'sync_price_info' => $syncPrices,
  452. 'sync_title' => $syncData['title'],
  453. 'sync_status' => $syncData['status'],
  454. 'sync_time' => date('Y-m-d H:i:s')
  455. ];
  456. // 4. 记录变动日志
  457. $this->recordChangeLog(
  458. $productId,
  459. $spuId,
  460. 'price_change',
  461. '第三方平台价格变动超过阈值',
  462. $beforeData,
  463. $afterData
  464. );
  465. Log::info("商品下架: product_id={$productId}, spu_id={$spuId}, 原因: 价格变动");
  466. }
  467. /**
  468. * 更新商品图片(基于原有updata_product_img方法)
  469. */
  470. protected function updateProductImages($spuId)
  471. {
  472. // 1. 验证该SPU是否已上架
  473. $productId = Db::name('store_product')
  474. ->where('spu_id', $spuId)
  475. ->value('product_id');
  476. if (!$productId) {
  477. return true;
  478. }
  479. // 2. 查找微唯宝商品
  480. $data = Db::name('douhuomall')
  481. ->where('spu_id', $spuId)
  482. ->find();
  483. if (!$data) {
  484. return false;
  485. }
  486. // 3. 解析商品信息
  487. $spuIdInfo = json_decode($data['spuId_info'], true);
  488. // 4. 处理图片数据
  489. $sliderImage = $spuIdInfo['detail_img_list'] ?: '[]';
  490. $image = $spuIdInfo['cover_url'] ?? '';
  491. try {
  492. $sliderImage = implode(',', json_decode($sliderImage, true));
  493. } catch (\Exception $e) {
  494. $sliderImage = $image;
  495. }
  496. // 5. 更新产品图片数据
  497. $updateData = [
  498. 'image' => $image,
  499. 'slider_image' => $sliderImage,
  500. ];
  501. Db::name('store_product')
  502. ->where('product_id', $productId)
  503. ->update($updateData);
  504. // 6. 更新商品详情
  505. $detail = $spuIdInfo['detail'] ?? '';
  506. if (!is_null(json_decode($detail))) {
  507. $detailList = json_decode($detail);
  508. $newDetail = '';
  509. foreach ($detailList as $value) {
  510. $newDetail .= "<img src='{$value}' referrerpolicy='no-referrer' /></img>";
  511. }
  512. $detail = $newDetail;
  513. }
  514. Db::name('store_product_content')
  515. ->where('product_id', $productId)
  516. ->update(['content' => $detail]);
  517. return true;
  518. }
  519. /**
  520. * 处理需要下架的商品(第三方已下架)
  521. */
  522. protected function handleOfflineProducts($localSpuIds, $syncedSpuIds)
  523. {
  524. $offlineSpuIds = array_diff($localSpuIds, $syncedSpuIds);
  525. if (empty($offlineSpuIds)) {
  526. return;
  527. }
  528. foreach ($offlineSpuIds as $spuId) {
  529. $this->offlineProductBySpuId($spuId, 'third_party_offline');
  530. }
  531. Log::info("下架第三方已下架商品" . json_encode(['count' => count($offlineSpuIds), 'spu_ids' => $offlineSpuIds]));
  532. }
  533. /**
  534. * 根据SPU ID下架商品
  535. */
  536. protected function offlineProductBySpuId($spuId, $reason)
  537. {
  538. // 查找已上架的商品
  539. $products = Db::name('store_product')
  540. ->where('spu_id', $spuId)
  541. ->select();
  542. foreach ($products as $product) {
  543. // 记录下架前数据
  544. $beforeData = [
  545. 'product_id' => $product['product_id'],
  546. 'spu_id' => $spuId,
  547. 'title' => $product['store_name'] ?? '',
  548. 'status' => $product['is_show'] ?? 0,
  549. 'price' => $product['price'] ?? 0,
  550. 'record_time' => date('Y-m-d H:i:s')
  551. ];
  552. // 下架商品
  553. Db::name('store_product')
  554. ->where('product_id', $product['product_id'])
  555. ->update([
  556. 'is_show' => 0,
  557. 'status' => 2,
  558. 'is_status' => 0
  559. ]);
  560. // 记录日志
  561. $this->recordChangeLog(
  562. $product['product_id'],
  563. $spuId,
  564. 'third_party_offline',
  565. $reason,
  566. $beforeData,
  567. []
  568. );
  569. }
  570. // 从douhuomall表中删除
  571. Db::name('douhuomall')->where('spu_id', $spuId)->delete();
  572. }
  573. /**
  574. * 记录变动日志
  575. */
  576. protected function recordChangeLog($productId, $spuId, $changeType, $reason, $beforeData, $afterData)
  577. {
  578. $logData = [
  579. 'product_id' => $productId,
  580. 'spu_id' => $spuId,
  581. 'change_type' => $changeType,
  582. 'change_reason' => $reason,
  583. 'before_data' => json_encode($beforeData, JSON_UNESCAPED_UNICODE),
  584. 'after_data' => json_encode($afterData, JSON_UNESCAPED_UNICODE),
  585. 'change_time' => time(),
  586. 'create_time' => time()
  587. ];
  588. Db::name('product_change_log')->insert($logData);
  589. }
  590. /**
  591. * 数组排序(从原有add方法中提取)
  592. */
  593. protected function arrSort($array, $keys, $sort = SORT_DESC)
  594. {
  595. $keysValue = [];
  596. foreach ($array as $k => $v) {
  597. $keysValue[$k] = $v[$keys] ?? 0;
  598. }
  599. array_multisort($keysValue, $sort, $array);
  600. return $array;
  601. }
  602. /**
  603. * 计算成本价
  604. */
  605. protected function calculateCostPrice($platPrice)
  606. {
  607. return bcadd((string)$platPrice, bcmul((string)$platPrice, '0.03', 4), 2);
  608. }
  609. /**
  610. * 计算市场价
  611. */
  612. protected function calculateMarketPrice($platPrice)
  613. {
  614. $costPrice = $this->calculateCostPrice($platPrice);
  615. return bcadd($costPrice, bcmul($costPrice, '0.2', 4), 2);
  616. }
  617. /**
  618. * 计算利润率
  619. */
  620. protected function calculateProfit($platPrice)
  621. {
  622. $costPrice = $this->calculateCostPrice($platPrice);
  623. $marketPrice = $this->calculateMarketPrice($platPrice);
  624. return bcdiv($marketPrice, $costPrice, 2);
  625. }
  626. /**
  627. * 计算毛利
  628. */
  629. protected function calculateMerProfit($platPrice)
  630. {
  631. $costPrice = $this->calculateCostPrice($platPrice);
  632. $marketPrice = $this->calculateMarketPrice($platPrice);
  633. return bcsub($marketPrice, $costPrice, 2);
  634. }
  635. /**
  636. * 记录错误
  637. */
  638. protected function logError($message, \Exception $e)
  639. {
  640. $errorInfo = [
  641. 'message' => $message,
  642. 'file' => $e->getFile(),
  643. 'line' => $e->getLine(),
  644. 'error' => $e->getMessage()
  645. ];
  646. Log::error('商品同步错误' . json_encode($errorInfo, JSON_UNESCAPED_UNICODE));
  647. Db::name('log')->insert([
  648. 'data' => json_encode($errorInfo, JSON_UNESCAPED_UNICODE),
  649. 'time' => date('Y-m-d H:i:s')
  650. ]);
  651. }
  652. }