1052 lines
40 KiB
PHP
1052 lines
40 KiB
PHP
<?php
|
||
declare (strict_types = 1);
|
||
|
||
namespace app\command;
|
||
|
||
use think\console\Command;
|
||
use think\console\Input;
|
||
use think\console\input\Argument;
|
||
use think\console\input\Option;
|
||
use think\console\Output;
|
||
use think\facade\Db;
|
||
|
||
class DataMigration extends Command
|
||
{
|
||
protected $println;
|
||
private function println($msg) {
|
||
($this->println)($msg);
|
||
}
|
||
|
||
protected function configure()
|
||
{
|
||
// 指令配置
|
||
$this->setName('migrate')
|
||
->setDescription('执行数据迁移');
|
||
}
|
||
|
||
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);
|
||
};
|
||
|
||
try {
|
||
// 迁移tock产品分类
|
||
// $this->productTcoCategory();
|
||
|
||
// 迁移产品分类
|
||
// $this->productCategory();
|
||
|
||
// 迁移产品属性
|
||
// $this->migrateProductAttr();
|
||
|
||
// 迁移产品
|
||
// $this->migrateProduct();
|
||
|
||
// 迁移产品关联产品数据
|
||
$this->migrateProductRelated();
|
||
|
||
// 迁移文章
|
||
// $this->migrateArticle([
|
||
// 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
|
||
// $this->migrateFaq();
|
||
|
||
// 迁移附件下载
|
||
// $this->migrateAttachment([
|
||
// 1 => 1,
|
||
// 2 => 2,
|
||
// 3 => 3,
|
||
// 4 => 4,
|
||
// 5 => 5,
|
||
// 43 => 6,
|
||
// 22 => 8,
|
||
// 24 => 11,
|
||
// 25 => 12,
|
||
// 26 => 13,
|
||
// 44 => 9,
|
||
// 45 => 10
|
||
// ]);
|
||
|
||
// 迁移视频分类
|
||
// $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->test();
|
||
|
||
$output->writeln('success');
|
||
} catch(\Throwable $th) {
|
||
$output->writeln($th->getMessage() .':' . $th->getLine());
|
||
}
|
||
}
|
||
|
||
// 迁移tco产品分类
|
||
private function productTcoCategory()
|
||
{
|
||
$category = Db::connect('old')
|
||
->name('product_tco_category')
|
||
->select();
|
||
|
||
foreach ($category as $val) {
|
||
$item = [
|
||
'id' => $val['id'],
|
||
'language_id' => $val['country_code'] == 'ZH' ? 1 : 2,
|
||
'name' => $val['name'],
|
||
'tco_id' => $val['tco_id'],
|
||
'tco_pid' => $val['tco_pid'],
|
||
'tco_path' => $val['tco_path'],
|
||
'erp_id' => $val['erp_id'],
|
||
'erp_pid' => $val['erp_pid'],
|
||
'erp_path' => $val['erp_path'],
|
||
'erp_code' => $val['erp_code'],
|
||
'disabled' => $val['disabled'],
|
||
'sync_time' => $val['sync_time'],
|
||
];
|
||
Db::name('product_tco_category')->insert($item);
|
||
}
|
||
}
|
||
|
||
// 迁移产品分类
|
||
private function productCategory()
|
||
{
|
||
$tco_category = Db::connect('old')
|
||
->name('product_tco_category')
|
||
->select();
|
||
$tco_category_map = [];
|
||
foreach ($tco_category as $val) {
|
||
$key = sprintf("%s_%s", $val['category_id'], $val['country_code']);
|
||
if (isset($tco_category_map[$key])) {
|
||
$tco_category_map[$key] = [];
|
||
}
|
||
$tco_category_map[$key][] = $val['id'];
|
||
}
|
||
|
||
$category = Db::connect('old')
|
||
->name('product_category')
|
||
->select()
|
||
->toArray();
|
||
|
||
// 处理数据
|
||
$this->handlerProductCategory(array_to_tree($category, 0, 'pid', 1), $tco_category_map);
|
||
}
|
||
private function handlerProductCategory($category, $map) {
|
||
foreach ($category as $val) {
|
||
$key = sprintf("%s_%s", $val['id'], $val['country_code']);
|
||
$item = [
|
||
'id' => $val['id'],
|
||
'language_id' => $val['country_code'] == 'ZH' ? 1 : 2,
|
||
'unique_id' => uniqid('PRO_CATE_'),
|
||
'pid' => $val['pid'],
|
||
'name' => $val['name'],
|
||
'icon' => $val['icon'],
|
||
'desc' => $val['description'],
|
||
'related_tco_category' => isset($map[$key]) ? implode(',', $map[$key]) : '',
|
||
'sort' => $val['sort'] == 9999 ? 0 : $val['sort'],
|
||
'level' => $val['level'],
|
||
'is_show' => $val['isshow'],
|
||
];
|
||
Db::name('product_category')->insert($item);
|
||
if (isset($val['children'])) {
|
||
$this->handlerProductCategory($val['children'], $map);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 迁移产品属性
|
||
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');
|
||
|
||
$success_map = [];
|
||
$success_arr = include_once(runtime_path() . 'product_success.php');
|
||
foreach ($success_arr as $so) {
|
||
$success_map['p_' . $so['cod_product_id']] = $so;
|
||
}
|
||
|
||
$products = $old_db->name('product')
|
||
->where('country_code', 'in', ['ZH', 'US'])
|
||
->where('id', '>', 0)
|
||
->order(['id' => 'asc'])
|
||
->cursor();
|
||
|
||
$total = 0;
|
||
$uploadMgr = new UploadMannager();
|
||
foreach ($products as $v)
|
||
{
|
||
$start = microtime(true);
|
||
if (isset($success_map['p_' . $v['id']])) {
|
||
continue;
|
||
}
|
||
|
||
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 {
|
||
if ($image_ret['code'] != 400) {
|
||
$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 {
|
||
if ($video_ret['code'] != 400) {
|
||
$video = $video_ret['msg'];
|
||
}
|
||
}
|
||
|
||
// 处理详情中图片
|
||
$content = preg_replace_callback('/<img[^>]*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 '<img src="解析替换图片失败,请手动处理" alt="" />';
|
||
}
|
||
return '<img src="' . $file_path . '" alt="" />';
|
||
}, $v['ld_md_content']??'');
|
||
|
||
$item = [
|
||
'language_id' => $v['country_code'] == 'ZH' ? 1 : 2,
|
||
'category_id' => $v['cid'],
|
||
'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'] == 0 ? 1 : 0,
|
||
'sort' => $v['sort'] == 9999 ? 0 : $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' => $v['updatetime'] == 0 ? date('Y-m-d H:i:s', $v['createtime']) : 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);
|
||
|
||
// 保存产品参数数据
|
||
if (!empty($v['product_view'])) {
|
||
$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'];
|
||
$images[$pkey]['attrs'] = [];
|
||
}
|
||
|
||
// 处理图册
|
||
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'];
|
||
} else {
|
||
if ($photos_ret['code'] != 400) {
|
||
$photo_album[$pkey][] = json_encode($photos_ret);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理属性
|
||
$attrs = json_decode($im['image_color'], true);
|
||
if (!empty($attrs)) {
|
||
foreach ($attrs as $attr) {
|
||
foreach ($attr 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']) || !array_some($images[$pkey]['attrs'], function($k, $v) use($attr_arr) {
|
||
return $v == $attr_arr;
|
||
})
|
||
) {
|
||
$images[$pkey]['attrs'][] = $attr_arr;
|
||
}
|
||
}
|
||
}
|
||
if (!empty($photo_album)) {
|
||
foreach ($images as $key => $image) {
|
||
if (isset($photo_album[$key])) {
|
||
$images[$key]['photo_album'] = $photo_album[$key];
|
||
}
|
||
}
|
||
}
|
||
|
||
$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']) && $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'];
|
||
}
|
||
}
|
||
if (\think\helper\Str::endsWith($ti['image_color'], ['.png', '.jpg', 'jpeg', '.gif'])) {
|
||
$arrt_ret = $uploadMgr->upload($uploadMgr->download($ti['image_color']), 'image', 'product');
|
||
if ($arrt_ret['code'] == 0) {
|
||
$ti['image_color'] = $arrt_ret['data']['path'];
|
||
}
|
||
}
|
||
$skus[] = [
|
||
'main_image' => $ti['image_url'],
|
||
'attrs' => [[
|
||
'attr_id' => $attrs_map[$v['country_code']]['颜色'],
|
||
'attr_value' => $ti['image_color'],
|
||
]],
|
||
'pkey' => $tpkey
|
||
];
|
||
}
|
||
|
||
if (!empty($skus)) {
|
||
$temp = [];
|
||
$temp_images = [];
|
||
foreach ($skus as $idx => $sku) {
|
||
$skus[$idx]['product_id'] = $id;
|
||
if (isset($images[$sku['pkey']])) {
|
||
$skus[$idx]['sku'] = $images[$sku['pkey']]['sku']??null;
|
||
$skus[$idx]['photo_album'] = $images[$sku['pkey']]['photo_album']??null;
|
||
if (!empty($images[$sku['pkey']]['attrs'])) {
|
||
$skus[$idx]['attrs'] = $images[$sku['pkey']]['attrs'];
|
||
}
|
||
} else {
|
||
if (empty($temp_images)) {
|
||
$temp_images = array_values($images);
|
||
}
|
||
if (!empty($temp_images[$idx])) {
|
||
$temp = $temp_images[$idx];
|
||
}
|
||
if (!empty($temp)) {
|
||
$skus[$idx]['sku'] = $temp['sku']??null;
|
||
$skus[$idx]['photo_album'] = $temp['photo_album']??null;
|
||
if (!empty($temp['attrs'])) {
|
||
$skus[$idx]['attrs'] = $temp['attrs'];
|
||
}
|
||
}
|
||
}
|
||
unset($skus[$idx]['pkey']);
|
||
}
|
||
} else {
|
||
foreach ($images as $image) {
|
||
$skus[] = [
|
||
'product_id' => $id,
|
||
'sku' => $image['sku']??null,
|
||
'main_image' => '',
|
||
'photo_album' => $image['photo_album']??null,
|
||
'skus' => $image['attrs']
|
||
];
|
||
}
|
||
}
|
||
|
||
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' => !empty($sku['photo_album']) ? json_encode($sku['photo_album']) : null,
|
||
]);
|
||
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();
|
||
$total += 1;
|
||
file_put_contents(
|
||
runtime_path() . 'product_success.txt',
|
||
sprintf('["ow_product_id" => %d, "cod_product_id" => %d]'.PHP_EOL, $id, $v['id']),
|
||
FILE_APPEND
|
||
);
|
||
$this->println(sprintf('迁移产品ID:%s => %s 【耗时:%s】', $v['id'], 0, round(microtime(true) - $start, 2) . 's'));
|
||
} 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_APPEND
|
||
);
|
||
file_put_contents(
|
||
runtime_path() . 'product_throwable_details.txt',
|
||
(string)$th
|
||
);
|
||
}
|
||
}
|
||
|
||
$this->println(sprintf('迁移产品完成,共迁移 %s 条数据', $total));
|
||
}
|
||
|
||
// 迁移产品关联产品数据
|
||
private function migrateProductRelated()
|
||
{
|
||
$sources = include_once(runtime_path() . 'product_success.php');
|
||
$maps = [];
|
||
foreach ($sources as $so) {
|
||
$maps[$so['cod_product_id']] = $so;
|
||
}
|
||
|
||
$old_db = Db::connect('old');
|
||
$related = $old_db->name('product_related')
|
||
->where('country_code', 'in', ['ZH', 'US'])
|
||
->where('stat', '=', 0)
|
||
->cursor();
|
||
|
||
$data = [];
|
||
foreach ($related as $rl) {
|
||
if (empty($maps[$rl['product_id']]) || empty($maps[$rl['related_product_id']])) {
|
||
continue;
|
||
}
|
||
$data[] = [
|
||
'product_id' => $maps[$rl['product_id']]['ow_product_id'],
|
||
'related_product_id' => $maps[$rl['related_product_id']]['ow_product_id'],
|
||
'desc' => empty($rl['related_desc']) ? null : $rl['related_desc'],
|
||
'sort' => $rl['related_sort'] == 9999 ? 0 : $rl['related_sort']
|
||
];
|
||
}
|
||
Db::name('product_related')->insertAll($data);
|
||
}
|
||
|
||
// 迁移文章
|
||
private function migrateArticle($category_map = [])
|
||
{
|
||
$this->println('开始迁移文章......');
|
||
|
||
if (empty($category_map)) {
|
||
throw new \Exception('请确认分类ID');
|
||
}
|
||
|
||
$article = Db::connect('old')
|
||
->name('article')
|
||
->where('cid', 'in', array_keys($category_map))
|
||
->order(['id' => 'asc'])
|
||
->cursor();
|
||
|
||
$uploadMgr = new UploadMannager();
|
||
foreach ($article as $v) {
|
||
// 处理封面图片
|
||
$image = '';
|
||
$ret = $uploadMgr->upload($uploadMgr->download($v['picture']), 'image', 'article');
|
||
if ($ret['code'] == 0) {
|
||
$image = $ret['data']['path'];
|
||
} else {
|
||
$image = $ret['msg'];
|
||
}
|
||
|
||
// 处理详情中图片
|
||
$content = preg_replace_callback('/<img[^>]*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 '<img src="解析替换图片失败,请手动处理" alt="" />';
|
||
}
|
||
return '<img src="' . $file_path . '" alt="" />';
|
||
}, $v['content']);
|
||
$item = [
|
||
'language_id' => $v['country_code'] == 'ZH' ? 1 : 2,
|
||
'category_id' => $category_map[$v['cid']],
|
||
'title' => $v['name'],
|
||
'author' => $v['writer'],
|
||
'source' => $v['source'],
|
||
'image' => $image,
|
||
'desc' => $v['description'],
|
||
'recommend' => $v['recommend'],
|
||
'sort' => $v['sort'] == 9999 ? 0 : $v['sort'],
|
||
'link' => $v['jump_link'],
|
||
'content' => $content,
|
||
'view_count' => $v['viewcount'],
|
||
'praise_count' => $v['zancount'],
|
||
'seo_title' => $v['seo_title'],
|
||
'seo_keywords' => $v['seo_keyword'],
|
||
'seo_desc' => $v['seo_description'],
|
||
'release_time' => date('Y-m-d H:i:s', $v['createtime']),
|
||
'deleted_at' => $v['stat'] == -1 ? date('Y-m-d H:i:s') : null
|
||
];
|
||
$id = Db::name('article')->insertGetId($item);
|
||
|
||
$this->println(sprintf('迁移文章ID:%s => %s', $v['id'], $id));
|
||
}
|
||
}
|
||
|
||
// 迁移FAQ
|
||
private function migrateFaq()
|
||
{
|
||
$faq = Db::connect('old')
|
||
->name('fq')
|
||
->where('id', '>', 0)
|
||
->where('stat', '>=', 0)
|
||
->select();
|
||
|
||
$uploadMgr = new UploadMannager();
|
||
foreach ($faq as $key => $val) {
|
||
$image = '';
|
||
$ret = $uploadMgr->upload($uploadMgr->download($val['picture']), 'image', 'faq');
|
||
if ($ret['code'] == 0) {
|
||
$image = $ret['data']['path'];
|
||
} else {
|
||
$image = $ret['msg'];
|
||
}
|
||
$content = explode("\n", $val['content']);
|
||
$content = '<p>' . implode("</p><p>", $content) . '</p>';
|
||
$item = [
|
||
'language_id' => $val['country_code'] == 'ZH' ? 1 : 2,
|
||
'image' => $image,
|
||
'question' => $val['name'],
|
||
'answer' => $content,
|
||
'recommend' => $val['is_home'],
|
||
'sort' => $val['sort'] == 9999 ? 0 : $val['sort']
|
||
];
|
||
Db::name('faq')->insert($item);
|
||
}
|
||
}
|
||
|
||
// 迁移附件
|
||
private function migrateAttachment($category_map = [])
|
||
{
|
||
$attachment = Db::connect('old')
|
||
->name('download')
|
||
->where('id', '>', 0)
|
||
->where('country_code', 'in', ['ZH', 'US'])
|
||
->order(['id' => 'asc'])
|
||
->cursor();
|
||
|
||
$uploadMgr = new UploadMannager();
|
||
foreach ($attachment as $key => $val) {
|
||
$image = '';
|
||
$image_ret = $uploadMgr->upload($uploadMgr->download($val['picture']), 'image');
|
||
if ($image_ret['code'] == 0) {
|
||
$image = $image_ret['data']['path'];
|
||
} else {
|
||
$image = $image_ret['msg'];
|
||
}
|
||
$paths = explode(',', $val['downloadpath']);
|
||
$btns = explode(',', $val['downloadpath64']);
|
||
$attach = [];
|
||
foreach ($paths as $k => $path) {
|
||
$file_path = '';
|
||
if (\think\helper\Str::contains($path, "oos.oricogroup.com")) {
|
||
$file_path = $path;
|
||
} else {
|
||
$attach_ret = $uploadMgr->upload($uploadMgr->download($path), 'attachment');
|
||
if ($attach_ret['code'] == 0) {
|
||
$file_path = $attach_ret['data']['path'];
|
||
} else {
|
||
$file_path = $attach_ret['msg'];
|
||
}
|
||
}
|
||
|
||
$file_ext = '';
|
||
$last_sper_idx = strrpos($path, '.');
|
||
if ($last_sper_idx !== false) {
|
||
$file_ext = substr($path, $last_sper_idx + 1);
|
||
if (substr($path, $last_sper_idx - 3, $last_sper_idx) == 'tar') {
|
||
$file_ext = 'tar.' . $file_ext;
|
||
}
|
||
}
|
||
|
||
$attach[] = [
|
||
'file_path' => $file_path,
|
||
'file_ext' => $file_ext,
|
||
'btn_name' => $btns[$k],
|
||
];
|
||
}
|
||
$item = [
|
||
'language_id' => $val['country_code'] == 'ZH' ? 1 : 2,
|
||
'category_id' => $category_map[$val['cid']],
|
||
'name' => $val['name'],
|
||
'desc' => $val['description'],
|
||
'image' => $image,
|
||
'applicable_to' => $val['app_model'],
|
||
'support_platform' => $val['support_os'],
|
||
'attach' => json_encode($attach),
|
||
'sort' => $val['sort'] == 9999 ? 0 : $val['sort'],
|
||
'recommend' => $val['recommend'],
|
||
'seo_title' => $val['seo_title'],
|
||
'seo_keywords' => $val['seo_keyword'],
|
||
'seo_desc' => $val['seo_description'],
|
||
'deleted_at' => $val['stat'] == -1 ? date('Y-m-d H:i:s') : null
|
||
];
|
||
Db::name('attachment')->insert($item);
|
||
$this->println('迁移附件ID:' . $val['id']);
|
||
}
|
||
}
|
||
|
||
// 迁移视频
|
||
private function migrateVideoCategory()
|
||
{
|
||
$categorys = Db::connect('old')
|
||
->name('video_category')
|
||
->where('id', '>', 0)
|
||
->where('country_code', 'in', ['ZH', 'US'])
|
||
->order(['id' => 'asc'])
|
||
->cursor();
|
||
|
||
foreach ($categorys as $val) {
|
||
$item = [
|
||
'language_id' => $val['country_code'] == 'ZH' ? 1 : 2,
|
||
'name' => $val['name'],
|
||
'sort' => $val['sort'] == 9999 ? 0 : $val['sort'],
|
||
'is_show' => $val['isshow']
|
||
];
|
||
Db::name('video_category')->insert($item);
|
||
$this->println('迁移视频分类ID:' . $val['id']);
|
||
}
|
||
}
|
||
|
||
// 迁移视频
|
||
private function migrateVideo($category_map = [])
|
||
{
|
||
$videos = Db::connect('old')
|
||
->name('video')
|
||
->where('id', '>', 844)
|
||
->where('country_code', 'in', ['ZH', 'US'])
|
||
->order(['id' => 'asc'])
|
||
->cursor();
|
||
|
||
$uploadMgr = new UploadMannager();
|
||
foreach ($videos as $val) {
|
||
$image = '';
|
||
$image_ret = $uploadMgr->upload($uploadMgr->download($val['picture']), 'image', 'video');
|
||
if ($image_ret['code'] == 0) {
|
||
$image = $image_ret['data']['path'];
|
||
} else {
|
||
$image = $image_ret['msg'];
|
||
}
|
||
|
||
$video = '';
|
||
if (!empty($val['videourl'])) {
|
||
$video = $val['videourl'];
|
||
} else {
|
||
$video_ret = $uploadMgr->upload($uploadMgr->download($val['videopath']), 'video', 'video');
|
||
if ($video_ret['code'] == 0) {
|
||
$video = $video_ret['data']['path'];
|
||
} else {
|
||
$video = $video_ret['msg'];
|
||
}
|
||
}
|
||
$item = [
|
||
'language_id' => $val['country_code'] == 'ZH' ? 1 : 2,
|
||
'category_id' => $category_map[$val['cid']],
|
||
'name' => $val['name'],
|
||
'desc' => $val['description'],
|
||
'image' => $image,
|
||
'video' => $video,
|
||
'sort' => $val['sort'] == 9999 ? 0 : $val['sort'],
|
||
'recommend' => $val['recommend'],
|
||
'seo_title' => $val['seo_title'],
|
||
'seo_keywords' => $val['seo_keyword'],
|
||
'seo_desc' => $val['seo_description'],
|
||
'deleted_at' => $val['stat'] == -1 ? date('Y-m-d H:i:s') : null
|
||
];
|
||
Db::name('video')->insert($item);
|
||
$this->println('迁移视频ID:' . $val['id']);
|
||
}
|
||
}
|
||
}
|
||
|
||
class UploadMannager
|
||
{
|
||
const UPLOAD_BASE_API = 'http://dev.ow.f2b211.com';
|
||
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';
|
||
private $token = '';
|
||
private $retrys = [];
|
||
private $maps = [];
|
||
|
||
public function __construct()
|
||
{
|
||
// 登录获取token
|
||
$this->token = $this->getAuthorization();
|
||
$this->maps = include_once(runtime_path() . 'fiber_product_image_mapping.php');
|
||
}
|
||
|
||
// 下载图片
|
||
public function download($file_name)
|
||
{
|
||
if (empty($file_name)) {
|
||
return '';
|
||
}
|
||
$url = $file_name;
|
||
if (\think\helper\Str::startsWith($file_name, 'http')) {
|
||
$need = 'orico.com.cn';
|
||
if (!\think\helper\Str::contains($file_name, $need)) {
|
||
if (\think\helper\Str::contains($file_name, 'https')) {
|
||
// 避免https过期情况
|
||
$url = 'http://' . \think\helper\Str::substr($file_name, mb_strlen('https://'));
|
||
}
|
||
} else {
|
||
$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);
|
||
}
|
||
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;
|
||
if (file_exists($file_path)) {
|
||
if (!is_valid_image($file_path)) {
|
||
return '';
|
||
}
|
||
return $file_path;
|
||
}
|
||
|
||
// 使用curl下载
|
||
$file = file_get_withcurl($url);
|
||
$dir = dirname($file_path);
|
||
if (!is_dir($dir)) {
|
||
mkdir($dir, 0777, true);
|
||
}
|
||
file_put_contents($file_path, $file, FILE_USE_INCLUDE_PATH);
|
||
return $file_path;
|
||
}
|
||
|
||
// 上传图片
|
||
public function upload($file_path, $field_name, $module = 'unknown') {
|
||
if (empty($file_path)) {
|
||
return ['code' => 400, 'msg' => 'file_path为空', 'data' => ['path' => '']];
|
||
}
|
||
if (\think\helper\Str::startsWith($file_path, 'http')) {
|
||
return ['code' => 0, 'msg' => 'file_path为http', 'data' => ['path' => $file_path]];
|
||
}
|
||
if (isset($this->maps[$file_path])) {
|
||
return ['code' => 0, 'msg' => '成功', 'data' => ['path' => $this->maps[$file_path]]];
|
||
}
|
||
|
||
switch($field_name){
|
||
case 'image':
|
||
$url_path = "/admapi/v1/images/$module/upload";
|
||
break;
|
||
case 'attachment':
|
||
$url_path = '/admapi/v1/attachment/upload';
|
||
break;
|
||
case 'video':
|
||
$url_path = "/admapi/v1/video/$module/upload";
|
||
break;
|
||
default:
|
||
$url_path = "/admapi/v1/images/$module/upload";
|
||
break;
|
||
}
|
||
$ch = curl_init(self::UPLOAD_BASE_API . $url_path);
|
||
$post_data = [
|
||
$field_name => new \CURLFile($file_path)
|
||
];
|
||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||
'Authorization: Bearer ' . $this->token
|
||
]);
|
||
curl_setopt($ch, CURLOPT_POST, true);
|
||
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||
$response = curl_exec($ch);
|
||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
// 登录失效
|
||
if ($http_code == 401) {
|
||
$this->token = $this->getAuthorization();
|
||
if (!isset($this->retrys[$file_path])) {
|
||
$this->retrys[$file_path] = 0;
|
||
}
|
||
if ($this->retrys[$file_path] > 0) {
|
||
throw new \Exception('[' . $file_path . ']上传重试失败');
|
||
}
|
||
$this->retrys[$file_path] += 1;
|
||
return $this->upload($file_path, $field_name);
|
||
}
|
||
|
||
if ($http_code != 200 && $http_code != 401) {
|
||
print($file_path . PHP_EOL);
|
||
print($field_name . PHP_EOL);
|
||
print(self::UPLOAD_BASE_API . $url_path . PHP_EOL);
|
||
print($response . PHP_EOL);
|
||
print($http_code . PHP_EOL);
|
||
}
|
||
|
||
$ret = json_decode($response, true);
|
||
if (empty($ret)) {
|
||
throw new \Exception($response);
|
||
}
|
||
return $ret;
|
||
}
|
||
|
||
// 登录获取token
|
||
private function getAuthorization()
|
||
{
|
||
$ch = curl_init(self::UPLOAD_BASE_API . '/admapi/v1/user/login');
|
||
curl_setopt($ch, CURLOPT_POST, true);
|
||
curl_setopt($ch, CURLOPT_POSTFIELDS, [
|
||
'username' => $this->username,
|
||
'password' => md5($this->password)
|
||
]);
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||
$response = curl_exec($ch);
|
||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if ($http_code != 200) {
|
||
throw new \Exception('获取token失败');
|
||
}
|
||
|
||
$result = json_decode($response, true);
|
||
if ($result['code'] != 0) {
|
||
throw new \Exception($result['msg']);
|
||
}
|
||
|
||
return $result['data']['token'];
|
||
}
|
||
}
|
||
|
||
// 使用curl下载图片
|
||
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, 60); // 传输超时 30 秒
|
||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 跟随重定向
|
||
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0'); // 模拟浏览器 UA
|
||
|
||
$data = curl_exec($ch);
|
||
if ($data === false) {
|
||
file_put_contents(runtime_path() . 'file_get_withcurl.txt', sprintf('cURL Error: %s; URL: %s' . PHP_EOL, curl_error($ch), $url), FILE_APPEND);
|
||
return false;
|
||
}
|
||
curl_close($ch);
|
||
|
||
return $data;
|
||
}
|
||
|
||
// 检查图片是否有效
|
||
function is_valid_image($filepath)
|
||
{
|
||
try {
|
||
$image_size = getimagesize($filepath);
|
||
if ($image_size === false) {
|
||
return false;
|
||
}
|
||
[$width, $height] = $image_size;
|
||
return $width > 0 && $height > 0;
|
||
} catch (\Throwable $e) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 根据自定义函数检查数组
|
||
function array_some($array, callable $ca): bool
|
||
{
|
||
foreach ($array as $k => $v) {
|
||
if ($ca($k, $v)) return true;
|
||
}
|
||
|
||
return false;
|
||
}
|