From ce8f7dba19c7dcc46068a3c1cffa08a8ce953008 Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Thu, 8 May 2025 18:19:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=B0=E6=8D=AE=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/command/DataMigration.php | 488 ++++++++++++++++++++++++++++++---- 1 file changed, 431 insertions(+), 57 deletions(-) diff --git a/app/command/DataMigration.php b/app/command/DataMigration.php index 61d9918f..1b48d496 100644 --- a/app/command/DataMigration.php +++ b/app/command/DataMigration.php @@ -26,9 +26,12 @@ class DataMigration extends Command protected function execute(Input $input, Output $output) { + ini_set('pcre.backtrack_limit', 10000000); + ini_set('default_socket_timeout', 1); + // 指令输出 $this->println = function ($msg) use ($output) { - $output->writeln($msg); + $output->writeln($msg); }; try { @@ -38,15 +41,30 @@ class DataMigration extends Command // 迁移产品分类 // $this->productCategory(); + // 迁移产品属性 + // $this->migrateProductAttr(); + + // 迁移产品 + $this->migrateProduct(); + // 迁移文章 // $this->migrateArticle([ - // 1 => 2, - // 2 => 3, - // 33 => 4, - // 36 => 5, - // 16 => 7, - // 31 => 8, - // 32 => 9 + // 68 => 10, + // 69 => 11, + // 70 => 12, + // 71 => 13, + // 72 => 14, + // 73 => 15, + // 74 => 16, + // 75 => 17, + // 78 => 19, + // 79 => 20, + // 80 => 21, + // 81 => 22, + // 82 => 23, + // 83 => 24, + // 84 => 25, + // 85 => 26 // ]); // 迁移faq @@ -72,29 +90,31 @@ class DataMigration extends Command // $this->migrateVideoCategory(); // 迁移视频 - $this->migrateVideo([ - 0 => 0, - 1 => 1, - 2 => 2, - 3 => 3, - 4 => 4, - 5 => 5, - 6 => 6, - 7 => 7, - 8 => 8, - 11 => 0, - 36 => 9, - 37 => 10, - 38 => 11, - 39 => 12, - 40 => 13, - 41 => 14, - 42 => 15, - 43 => 16, - 60 => 17, - 62 => 18, - 63 => 19 - ]); + // $this->migrateVideo([ + // 0 => 0, + // 1 => 1, + // 2 => 2, + // 3 => 3, + // 4 => 4, + // 5 => 5, + // 6 => 6, + // 7 => 7, + // 8 => 8, + // 11 => 0, + // 36 => 9, + // 37 => 10, + // 38 => 11, + // 39 => 12, + // 40 => 13, + // 41 => 14, + // 42 => 15, + // 43 => 16, + // 60 => 17, + // 62 => 18, + // 63 => 19 + // ]); + + // $this->test(); $output->writeln('success'); } catch(\Throwable $th) { @@ -174,6 +194,319 @@ class DataMigration extends Command } } + // 迁移产品属性 + private function migrateProductAttr() + { + $products = Db::connect('old') + ->name('product') + ->where('country_code', 'in', ['ZH', 'US']) + ->where('product_attr', '<>', '') + ->order(['id' => 'asc']) + ->cursor(); + + $exists = []; + foreach ($products as $v) + { + $attrs = []; + $product_attr = json_decode($v['product_attr'], true); + foreach ($product_attr as $attr) { + if (!in_array($attr, ['颜色', 'Color']) && !in_array($attr, $exists)) { + $attrs[] = [ + 'language_id' => $v['country_code'] == 'ZH' ? 1 : 2, + 'attr_type' => 2, + 'attr_name' => $attr, + 'is_system' => 0 + ]; + $exists[] = $attr; + } + } + if (empty($attrs)) { + continue; + } + Db::name('product_attr')->insertAll($attrs); + } + } + + // 迁移产品 + private function migrateProduct() + { + $attrs_map = []; + $attrs_dict = Db::name('product_attr') + ->withoutField(['created_at', 'updated_at', 'deleted_at']) + ->select(); + foreach ($attrs_dict as $attr) + { + $code = $attr['language_id'] == 1 ? 'ZH' : 'US'; + if (!isset($attrs_map[$code])) { + $attrs_map[$code] = []; + } + $attrs_map[$code][$attr['attr_name']] = $attr['id']; + } + + $old_db = Db::connect('old'); + + $products = $old_db->name('product') + ->where('id', '=', 128) + ->where('country_code', 'in', ['ZH', 'US']) + ->order(['id' => 'asc']) + ->cursor(); + + $uploadMgr = new UploadMannager(); + foreach ($products as $v) + { + Db::startTrans(); + try { + // 处理封面图片 + $image = ''; + $image_ret = $uploadMgr->upload($uploadMgr->download($v['list_bk_img']), 'image', 'product'); + if ($image_ret['code'] == 0) { + $image = $image_ret['data']['path']; + } else { + $image = $image_ret['msg']; + } + + // 处理视频 + $video = ''; + $video_ret = $uploadMgr->upload($uploadMgr->download($v['videopath']), 'video', 'video'); + if ($video_ret['code'] == 0) { + $video = $video_ret['data']['path']; + } else { + $video = $video_ret['msg']; + } + + // 处理详情中图片 + $content = preg_replace_callback('/]*src=[\'"]([^\'"]*?)[\'"][^>]*>/is', function($matches) use ($uploadMgr) { + $file_path = ''; + try { + $ret = $uploadMgr->upload($uploadMgr->download($matches[1]), 'image'); + if ($ret['code'] == 0) { + $file_path = $ret['data']['path']; + } + } catch (\Throwable $th) { + $this->println($th->getMessage() . ':' . $th->getLine()); + return ''; + } + return ''; + }, $v['ld_md_content']); + + $item = [ + 'language_id' => $v['country_code'] == 'ZH' ? 1 : 2, + 'category_id' => '', + 'spu' => $v['brand_id'], + 'name' => $v['name'], + 'short_name' => $v['shortname'], + 'cover_image' => $image, + 'desc' => $v['description'], + 'video_img' => '', + 'video_url' => $video, + 'is_sale' => $v['is_onsale'], + 'is_new' => $v['isnew'], + 'is_hot' => $v['ishot'], + 'is_show' => $v['is_show'], + 'sort' => $v['sort'], + 'detail' => $content, + 'status' => $v['stat'] == -1 ? -1 : 1, + 'seo_title' => $v['seo_title'], + 'seo_keywords' => $v['seo_keyword'], + 'seo_desc' => $v['seo_description'], + 'created_at' => date('Y-m-d H:i:s', $v['createtime']), + 'updated_at' => date('Y-m-d H:i:s', $v['updatetime']), + 'deleted_at' => $v['stat'] == -1 ? date('Y-m-d H:i:s') : null, + ]; + // 保存产品数据 + $id = Db::name('product')->insertGetId($item); + + // 保存产品参数数据 + $prarms = []; + $views = unserialize($v['product_view']); + foreach ($views as $p) { + $prarms[] = [ + 'product_id' => $id, + 'name' => $p['desc_title'], + 'value' => $p['desc_desc'] + ]; + } + Db::name('product_params')->insertAll($prarms); + + // 保存sku数据 + $skus = []; + $sku_images = $old_db->name('product_image') + ->where('product_id', '=', $v['id']) + ->where('country_code', '=', $v['country_code']) + ->where('stat', '=', 0) + ->order(['id' => 'asc']) + ->select(); + $images = []; + $photo_album = []; + foreach ($sku_images as $im) { + $pkey = $im['sku']; + if (empty($pkey)) { + if (!empty($im['image_color'])) { + $pkey = md5($im['image_color']); + } else { + $pkey = 0; + } + } + if (!isset($images[$pkey])) { + $images[$pkey] = []; + $images[$pkey]['sku'] = $im['sku']; + } + + // 处理图册 + if (!empty($im['image_url'])) { + $photos = json_decode($im['image_url'], true); + if (empty($photos)) { + $photos[] = $im['image_url']; + } + foreach ($photos as $photo) { + $photos_ret = $uploadMgr->upload($uploadMgr->download($photo), 'image', 'product'); + if ($photos_ret['code'] == 0) { + if (!isset($photo_album[$pkey])) { + $photo_album[$pkey] = []; + } + $photo_album[$pkey][] = $photos_ret['data']['path']; + } + } + } + + // 处理属性 + $attrs = json_decode($im['image_color'], true); + if (!empty($attrs)) { + foreach ($attrs as $k => $at) { + if ($k != 'sort') { + $attr_value = $at; + if (in_array($k, ['颜色', 'Color'])) { + if ($k == 'Color') $k = '颜色'; + $attr_value = '/static/common/images/colors/' . $at . '.png'; + } + $images[$pkey]['attrs'] = [ + 'attr_id' => $attrs_map[$v['country_code']][$k], + 'attr_value' => $attr_value, + ]; + } + } + } else { + $attr_value = $im['image_color']; + if (empty($attr_value) || $attr_value == '[]') { + $images[$pkey]['attrs'] = []; + continue; + } + if (\think\helper\Str::endsWith($attr_value, ['.png', '.jpg', 'jpeg', '.gif'])) { + $photos_ret = $uploadMgr->upload($uploadMgr->download($attr_value), 'image', 'product'); + if ($photos_ret['code'] == 0) { + $attr_value = $photos_ret['data']['path']; + } + } + $attr_arr = [ + 'attr_id' => $attrs_map[$v['country_code']]['颜色'], + 'attr_value' => $attr_value, + ]; + if ( + empty($images[$pkey]['attrs']) || + (!empty($images[$pkey]['attrs']) && empty(array_intersect($images[$pkey]['attrs'], $attr_arr))) + ) { + + $images[$pkey]['attrs'][] = $attr_arr; + file_put_contents(runtime_path() . 'attrs.txt', json_encode($attr_arr)); + } + } + } + foreach ($images as $key => $image) { + $images[$key]['photo_album'] = $photo_album[$key]; + } + file_put_contents(runtime_path() . 'images.txt', json_encode($images, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + file_put_contents(runtime_path() . 'photo_album.txt', json_encode($photo_album, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + + $two_images = $old_db->name('product_two_img') + ->where('product_id', '=', $v['id']) + ->where('country_code', '=', $v['country_code']) + ->where('stat', '=', 0) + ->order(['id' => 'asc']) + ->select(); + foreach ($two_images as $ti) { + $tpkey = 0; + if (!empty($ti['image_color'])) { + $tpkey = md5($ti['image_color']); + } + if (!empty($ti['image_url'])) { + $main_image_ret = $uploadMgr->upload($uploadMgr->download($ti['image_url']), 'image', 'product'); + if ($main_image_ret['code'] == 0) { + $ti['image_url'] = $main_image_ret['data']['path']; + } + } + $skus[] = ['main_image' => $ti['image_url'], 'image_color' => $ti['image_color'], 'pkey' => $tpkey]; + } + if (!empty($skus)) { + $temp = []; + foreach ($skus as $idx => $sku) { + if (isset($images[$sku['pkey']])) { + $skus[$idx]['product_id'] = $id; + $skus[$idx]['sku'] = $images[$sku['pkey']]['sku']; + $skus[$idx]['photo_album'] = $images[$sku['pkey']]['photo_album']; + $skus[$idx]['attrs'] = $images[$sku['pkey']]['attrs']; + } else { + if (!empty($images[$idx])) { + $temp = $images[$idx]; + } + if (!empty($temp)) { + $skus[$idx]['product_id'] = $id; + $skus[$idx]['sku'] = $temp['sku']; + $skus[$idx]['photo_album'] = $temp['photo_album']; + $skus[$idx]['attrs'] = $temp['attrs']; + } + } + unset($skus[$idx]['pkey']); + unset($skus[$idx]['image_color']); + } + } else { + foreach ($images as $image) { + $skus[] = [ + 'product_id' => $id, + 'sku' => $image['sku'], + 'main_image' => '', + 'photo_album' => $image['photo_album'], + 'skus' => $image['attrs'] + ]; + } + } + file_put_contents(runtime_path() . 'skus.txt', json_encode($skus, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + // throw new \Exception("exit;"); + + foreach ($skus as $sku) { + $sku_id = Db::name('product_sku')->insertGetId([ + 'product_id' => $sku['product_id'], + 'sku' => $sku['sku'], + 'main_image' => $sku['main_image'], + 'photo_album' => json_encode($sku['photo_album']), + ]); + if (!empty($sku['attrs'])) { + foreach ($sku['attrs'] as $attr) { + if (!empty($sku_id)) { + Db::name('product_sku_attr')->insert([ + 'sku_id' => $sku_id, + 'attr_id' => $attr['attr_id'], + 'attr_value' => $attr['attr_value'] + ]); + } + } + } + } + Db::commit(); + $this->println(sprintf('迁移产品ID:%s => %s', $v['id'], $id)); + } catch (\Throwable $th) { + Db::rollback(); + file_put_contents( + runtime_path() . 'product_throwable.txt', + sprintf('【%s】 产品【%s】,迁移失败,错误【%s:%d】' . PHP_EOL, date('Y-h-d H:i:s'), $v['id'], $th->getMessage(), $th->getLine()) + ); + file_put_contents( + runtime_path() . 'product_throwable_details.txt', + (string)$th + ); + } + } + } + // 迁移文章 private function migrateArticle($category_map = []) { @@ -201,22 +534,19 @@ class DataMigration extends Command } // 处理详情中图片 - $content = $v['content']; - preg_match_all('//i', $content, $matches); - $content_images = []; - foreach ($matches[1] as $val) { + $content = preg_replace_callback('/]*src=[\'"]([^\'"]*?)[\'"][^>]*>/is', function ($matches) use ($uploadMgr) { + $file_path = ''; try { - $ret = $uploadMgr->upload($uploadMgr->download($val), 'image'); + $ret = $uploadMgr->upload($uploadMgr->download($matches[1]), 'image'); if ($ret['code'] == 0) { - $content_images[$val] = $ret['data']['path']; + $file_path = $ret['data']['path']; } } catch (\Throwable $th) { - continue; + $this->println($th->getMessage() . ':' . $th->getLine()); + return ''; } - } - foreach ($content_images as $key => $val) { - $content = str_replace($key, $val, $content); - } + return ''; + }, $v['content']); $item = [ 'language_id' => $v['country_code'] == 'ZH' ? 1 : 2, 'category_id' => $category_map[$v['cid']], @@ -234,12 +564,12 @@ class DataMigration extends Command 'seo_title' => $v['seo_title'], 'seo_keywords' => $v['seo_keyword'], 'seo_desc' => $v['seo_description'], - 'enabled' => $v['stat'], - 'release_time' => date('Y-m-d H:i:s', $v['createtime']) + 'release_time' => date('Y-m-d H:i:s', $v['createtime']), + 'deleted_at' => $v['stat'] == -1 ? date('Y-m-d H:i:s') : null ]; - Db::name('article')->insert($item); + $id = Db::name('article')->insertGetId($item); - $this->println('迁移文章ID:' . $v['id']); + $this->println(sprintf('迁移文章ID:%s => %s', $v['id'], $id)); } } @@ -422,7 +752,7 @@ class DataMigration extends Command class UploadMannager { const UPLOAD_BASE_API = 'http://dev.ow.f2b211.com'; - const DOWNLOAD_BASE_API = 'https://www.orico.com.cn'; + const DOWNLOAD_BASE_API = 'http://www.orico.com.cn'; const DOWNLOAD_TEMP_PATH = '/var/www/html/orico-official-website/public/migrate_temp_images'; private $username = 'admin'; private $password = 'Aa-1221'; @@ -453,20 +783,44 @@ class UploadMannager $url = self::DOWNLOAD_BASE_API . \think\helper\Str::substr($file_name, mb_strpos($file_name, $need) + mb_strlen($need)); } $file_name = '/' . \think\helper\Str::substr($url, mb_strpos($url, '://') + 3); - } else { + } + elseif (\think\helper\Str::startsWith($file_name, 'data:image/')) { + $idx = strpos($file_name, ';'); + $file_type = substr($file_name, 0, $idx); + $base64_image = preg_replace('#^data:image/\w+;base64,#i', '', $file_name); + $file_data = base64_decode($base64_image, true); + $file_path = self::DOWNLOAD_TEMP_PATH . '/uploads/' . date('Ymd') . '/' . uniqid() . '.' . substr($file_type, 11); + $dir = dirname($file_path); + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + if (!file_put_contents($file_path, $file_data, FILE_USE_INCLUDE_PATH)) { + throw new \Exception('转换base64图片失败'); + } + return $file_path; + } + else { $url = self::DOWNLOAD_BASE_API . str_replace(" ", "%20", $file_name); } $file_path = self::DOWNLOAD_TEMP_PATH . $file_name; - $opts = [ - 'http' => [ - 'method' => 'GET', - 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)' - ] - ]; - $context = stream_context_create($opts); - $file = file_get_contents($url, false, $context); - $dir = dirname($file_path); + if (file_exists($file_path)) { + return $file_path; + } + + // 使用file_get_contents下载 + // $opts = [ + // 'http' => [ + // 'method' => 'GET', + // 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)' + // ] + // ]; + // $context = stream_context_create($opts); + // $file = file_get_contents($url, false, $context); + + // 使用curl下载 + $file = $this->file_get_withcurl($url); + $dir = dirname($file_path); if (!is_dir($dir)) { mkdir($dir, 0777, true); } @@ -474,6 +828,26 @@ class UploadMannager return $file_path; } + // 使用curl下载图片 + private function file_get_withcurl($url) + { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // 连接超时 10 秒 + curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 传输超时 30 秒 + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 跟随重定向 + curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0'); // 模拟浏览器 UA + + $data = curl_exec($ch); + if ($data === false) { + error_log(sprintf('cURL Error: %s; URL: %s', curl_error($ch), $url)); + return false; + } + curl_close($ch); + + return $data; + } + // 上传图片 public function upload($file_path, $field_name, $module = 'unknown') { if (empty($file_path)) {