WiwibaoProductSync.php 24 KB

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