feat: 数据迁移命令
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,6 +9,4 @@ Thumbs.db
|
|||||||
/vendor
|
/vendor
|
||||||
/.settings
|
/.settings
|
||||||
/.buildpath
|
/.buildpath
|
||||||
/.project
|
/.project
|
||||||
|
|
||||||
app/index/controller/DataMigration.php
|
|
||||||
334
app/command/DataMigration.php
Normal file
334
app/command/DataMigration.php
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
<?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)
|
||||||
|
{
|
||||||
|
// 指令输出
|
||||||
|
$this->println = function ($msg) use ($output) {
|
||||||
|
$output->writeln($msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 迁移tock产品分类
|
||||||
|
// $this->productTcoCategory();
|
||||||
|
|
||||||
|
// 迁移产品分类
|
||||||
|
// $this->productCategory();
|
||||||
|
|
||||||
|
// 迁移文章
|
||||||
|
$this->migrateArticle([
|
||||||
|
1 => 2,
|
||||||
|
2 => 3,
|
||||||
|
33 => 4,
|
||||||
|
36 => 5,
|
||||||
|
16 => 7,
|
||||||
|
31 => 8,
|
||||||
|
32 => 9
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 迁移faq
|
||||||
|
// $this->migrateFaq();
|
||||||
|
|
||||||
|
$output->writeln('success');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迁移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 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');
|
||||||
|
if ($ret['code'] == 0) {
|
||||||
|
$image = $ret['data']['path'];
|
||||||
|
} else {
|
||||||
|
$image = $ret['msg'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理详情中图片
|
||||||
|
$content = $v['content'];
|
||||||
|
preg_match_all('/<img.*?src=[\'"](.*?)[\'"].*?>/i', $content, $matches);
|
||||||
|
$content_images = [];
|
||||||
|
foreach ($matches[1] as $val) {
|
||||||
|
try {
|
||||||
|
$ret = $uploadMgr->upload($uploadMgr->download($val), 'image');
|
||||||
|
if ($ret['code'] == 0) {
|
||||||
|
$content_images[$val] = $ret['data']['path'];
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($content_images as $key => $val) {
|
||||||
|
$content = str_replace($key, $val, $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'],
|
||||||
|
'enabled' => $v['stat'],
|
||||||
|
'release_time' => date('Y-m-d H:i:s', $v['createtime'])
|
||||||
|
];
|
||||||
|
Db::name('article')->insert($item);
|
||||||
|
|
||||||
|
$this->println('迁移文章ID:' . $v['id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迁移FAQ
|
||||||
|
private function migrateFaq()
|
||||||
|
{
|
||||||
|
$faq = Db::connect('old')
|
||||||
|
->name('fq')
|
||||||
|
->where('id', '>=', 10)
|
||||||
|
->where('stat', '>=', 0)
|
||||||
|
->select();
|
||||||
|
|
||||||
|
$uploadMgr = new UploadMannager();
|
||||||
|
foreach ($faq as $key => $val) {
|
||||||
|
$image = '';
|
||||||
|
$ret = $uploadMgr->upload($uploadMgr->download($val['picture']), 'image');
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UploadMannager
|
||||||
|
{
|
||||||
|
const UPLOAD_BASE_API = 'http://dev.ow.f2b211.com';
|
||||||
|
const DOWNLOAD_BASE_API = 'https://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 = [];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// 登录获取token
|
||||||
|
$this->token = $this->getAuthorization();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载图片
|
||||||
|
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)) {
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
$url = self::DOWNLOAD_BASE_API . \think\helper\Str::substr($file_name, mb_strpos($file_name, $need) + mb_strlen($need));
|
||||||
|
} else {
|
||||||
|
$url = self::DOWNLOAD_BASE_API . $file_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_path = self::DOWNLOAD_TEMP_PATH . $file_name;
|
||||||
|
$file = file_get_contents($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) {
|
||||||
|
if (empty($file_path)) {
|
||||||
|
return ['code' => 0, 'msg' => 'file_path为空', 'data' => ['path' => '']];
|
||||||
|
}
|
||||||
|
$ch = curl_init(self::UPLOAD_BASE_API . '/admapi/v1/images/faq/upload');
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode($response, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录获取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'];
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/index/controller/Article.php
Normal file
26
app/index/controller/Article.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\index\controller;
|
||||||
|
|
||||||
|
use think\facade\View;
|
||||||
|
|
||||||
|
class Article extends Common
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 文件列表
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
|
||||||
|
return View::fetch('index');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文章详情
|
||||||
|
*/
|
||||||
|
public function detail()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,13 +12,24 @@ use think\facade\Route;
|
|||||||
|
|
||||||
Route::get('/', 'Index/index');
|
Route::get('/', 'Index/index');
|
||||||
|
|
||||||
|
// 产品相关路由
|
||||||
Route::group('product', function() {
|
Route::group('product', function() {
|
||||||
// 产品列表页
|
// 产品列表页
|
||||||
Route::get('index/:id', 'Product/index')->name('product_index');
|
Route::get('index/:id', 'Product/index')->name('product_index');
|
||||||
// 产品详情页
|
// 产品详情页
|
||||||
Route::get('detail/:id', 'Product/detail')->name('product_detail');
|
Route::get('detail/:id', 'Product/detail')->name('product_detail');
|
||||||
// 产品搜索页
|
// 产品搜索页
|
||||||
Route::get('search', 'Product/search')->name('product_search');
|
Route::get('search', 'Product/search');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 文章相关路由
|
||||||
|
Route::group('article', function() {
|
||||||
|
// 文章列表页
|
||||||
|
Route::get('index/:id', 'Article/index');
|
||||||
|
// 文章详情页
|
||||||
|
Route::get('detail/:id', 'Article/detail');
|
||||||
|
// 文章搜索页
|
||||||
|
Route::get('search', 'Article/search');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 数据迁移
|
// 数据迁移
|
||||||
|
|||||||
73
app/index/view/article/index.html
Normal file
73
app/index/view/article/index.html
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
{extend name="public/base" /}
|
||||||
|
{block name="style"}
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/index/css/category.css" />
|
||||||
|
{/block}
|
||||||
|
{block name="main"}
|
||||||
|
<div class="orico_Page_category">
|
||||||
|
<!--内容 -->
|
||||||
|
<div class="categoryMain">
|
||||||
|
<img src="categoryImg/eng-blog.jpg" class="categorybgimg" />
|
||||||
|
<!-- 切换-->
|
||||||
|
<div class="tabs">
|
||||||
|
<div class="tabitme on">News</div>
|
||||||
|
<div class="tabitme">Blogs</div>
|
||||||
|
</div>
|
||||||
|
<!--搜索-->
|
||||||
|
<div class="categorySearch">
|
||||||
|
<i class="search_icon"></i>
|
||||||
|
<input type="text" placeholder="Search" class="search" id="article-search-in" value="">
|
||||||
|
</div>
|
||||||
|
<!-- 切换内容-->
|
||||||
|
<div class="tabConten">
|
||||||
|
<div class="tbmain">
|
||||||
|
<div class="Contenitem">
|
||||||
|
<a>
|
||||||
|
<img src="categoryImg/t1.jpg" />
|
||||||
|
<h3>ORICO Highlights Next-Gen Portable Data Storage and Charging Solution at Global
|
||||||
|
Sources Mobile Electronics 2024 Fall</h3>
|
||||||
|
<p>Hong Kong AsiaWorld-Expo, October 18-21, 2024 - A long-time Global Sources Mobile
|
||||||
|
Electronics exhibitor, ORICO made a splash at the show with its cutting-edge
|
||||||
|
next-generation portable data storage and...</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="Contenitem">
|
||||||
|
<a>
|
||||||
|
<img src="categoryImg/t1.jpg" />
|
||||||
|
<h3>ORICO Highlights Next-Gen Portable Data Storage and Charging Solution at Global
|
||||||
|
Sources Mobile Electronics 2024 Fall</h3>
|
||||||
|
<p>Hong Kong AsiaWorld-Expo, October 18-21, 2024 - A long-time Global Sources Mobile
|
||||||
|
Electronics exhibitor, ORICO made a splash at the show with its cutting-edge
|
||||||
|
next-generation portable data storage and...</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="Contenitem">
|
||||||
|
<a>
|
||||||
|
<img src="categoryImg/t1.jpg" />
|
||||||
|
<h3>ORICO Highlights Next-Gen Portable Data Storage and Charging Solution at Global
|
||||||
|
Sources Mobile Electronics 2024 Fall</h3>
|
||||||
|
<p>Hong Kong AsiaWorld-Expo, October 18-21, 2024 - A long-time Global Sources Mobile
|
||||||
|
Electronics exhibitor, ORICO made a splash at the show with its cutting-edge
|
||||||
|
next-generation portable data storage and...</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 分页-->
|
||||||
|
<div class="Page">
|
||||||
|
<span class="p_page">
|
||||||
|
<a class="a_prev"></a>
|
||||||
|
<em class="num">
|
||||||
|
<a class="a_cur">1</a>
|
||||||
|
<a>2</a>
|
||||||
|
<a>3</a>
|
||||||
|
<a>4</a>
|
||||||
|
</em>
|
||||||
|
<a class="a_next"></a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/block}
|
||||||
|
{block name="script"}
|
||||||
|
|
||||||
|
{/block}
|
||||||
@@ -5,5 +5,6 @@
|
|||||||
return [
|
return [
|
||||||
// 指令定义
|
// 指令定义
|
||||||
'commands' => [
|
'commands' => [
|
||||||
|
'data:migrate' => \app\command\DataMigration::class,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
0
runtime/.gitignore
vendored
Normal file → Executable file
0
runtime/.gitignore
vendored
Normal file → Executable file
Reference in New Issue
Block a user