$now_version,'web_api_url' => Yii::getAlias('@apiUrl'),'sign' => ''];
//$result = CurlHelper::post($this->service_url,$post);
$result = self::sendRequest($this->service_url,$post,'POST');
return $result;
}
/**
* 升级版本
* @param array $data
* @throws
* @return bool
*/
public static function upgrade($params)
{
ini_set('memory_limit',-1);
$data = $params['upgrade'];
$now_version = $params['now_version'];
$log = self::addLog($now_version,$data);
// 备份插件文件
//self::backup(); //todo
$sqlFiles = [];
$addons_arr = [];
foreach($data as $key => $value){
$addons_name = $key;
$download_url = $value['download_url'] ?? '';
if(!$download_url){
continue;
}
$tmpFile = self::download($download_url);
try {
if(strpos($addons_name,'mian') === false){
if(in_array($addons_name, ['h5', 'salary-h5'])){
//h5更新
$dir = self::getH5TmpDir($addons_name);
}else{
//插件更新
$dir = Yii::getAlias('@addons').DIRECTORY_SEPARATOR;
}
}else{
//主包更新
$addons_name = 'mian';
$dir = self::getWorkDir();
}
// 解压插件
self::unzip($tmpFile,$dir);
if($addons_name == 'h5'){
self::handleH5();
}
if($addons_name == 'salary-h5'){
self::handleSalaryH5();
}
} catch (Exception $e) {
throw new Exception($download_url.','.$e->getMessage());
} finally {
// 移除临时文件
@unlink($tmpFile);
}
if($value['is_install']){
//需要导入sql
if($addons_name == 'mian'){
$sqlFiles[] = self::getWorkDir() . 'install.sql';
}else{
$sqlFiles[] = Yii::getAlias('@addons').DIRECTORY_SEPARATOR.$addons_name.DIRECTORY_SEPARATOR.'install.sql';
}
}
if(!in_array($addons_name,['mian','h5'])){
//插件更新配置
$addons_arr[] = $addons_name;
//Addons::install($addons_name);
}
}
//更新文件所有者
// $shell = "chown www:www -R ".\Yii::getAlias('@webpath');
// exec($shell, $result, $status);
//判断是否安装mycat,如果按照 则不能执行以下操作
$dbConfig = \Yii::$app->getComponents()['db'];
$dsn = $dbConfig['dsn'];
preg_match('|port=(.*?);|',$dsn,$match);
$port = $match[1] ?? '';
$status = 1;
if($port == 3306){
//mysql 端口
if($sqlFiles){
// 导入
foreach($sqlFiles as $sql){
$res = self::importsql($sql);
if(!$res) $status = -1;
}
}
$result = self::migrate();
$status = $result === false ? -1 : $status;
}else{
Yii::$app->custom->logs('数据库未处理,数据迁移迁移未处理,'.implode(',',$sqlFiles) );
self::$static_error = '请联系技术专员处理数据库';
}
//更新完成:释放锁
self::unlockUpgrade();
//更新记录状态
self::updateLogStatus($log,$status);
//异步处理更新插件配置
self::asynchAddonsConfig($addons_arr);
//重启服务
self::restartService();
return true;
}
/**
* 获取远程服务器
* @return string
*/
protected static function getServerUrl()
{
return Yii::getAlias('@serverUrl');
}
/**
* 获取备份目录
*/
public static function getBackupDir()
{
$dir = Yii::$app->getRuntimePath().DIRECTORY_SEPARATOR.'bak'.DIRECTORY_SEPARATOR;
if (!is_dir($dir)) {
@mkdir($dir, 0755, true);
}
return $dir;
}
/**
* 获取项目备份地址
* @return string
*/
public static function getWorkBackupDir(){
//$dir = Yii::getAlias('@webpath').DIRECTORY_SEPARATOR.'../';
$dir = Yii::$app->getRuntimePath().DIRECTORY_SEPARATOR.'bak_up'.DIRECTORY_SEPARATOR;
if (!is_dir($dir)) {
@mkdir($dir, 0755, true);
}
return $dir;
}
/**
* 获取项目根目录
*/
public static function getWorkDir()
{
$dir = Yii::getAlias('@webpath').DIRECTORY_SEPARATOR;
return $dir;
}
/**
* 远程下载更新包
* @param string $url 下载地址
* @return string
* @throws
*/
public static function download($url)
{
$tempDir = self::getBackupDir();
$tmpFile = $tempDir . date('Y_m_d_H_i_s') . ".zip";
try {
$client = self::getClient();
$response = $client->get($url);
$statusCode = $response->getStatusCode();
if($statusCode != 200) throw new Exception("query result code error:".$statusCode.',url='.$url);
$body = $response->getBody();
$content = $body->getContents();
} catch (TransferException $e) {
throw new Exception("package download failed:".$url);
}
if ($write = fopen($tmpFile, 'w')) {
fwrite($write, $content);
fclose($write);
return $tmpFile;
}
throw new Exception("No permission to write temporary files");
}
/**
* 解压压缩包
*
* @param string $name 压缩包名称
* @return string
* @throws Exception
*/
public static function unzip($file,$dir)
{
if (!$file || !file_exists($file)) {
throw new Exception('Invalid parameters');
}
// 打开插件压缩包
$zip = new ZipFile();
try {
$zip->openFile($file);
} catch (ZipException $e) {
$zip->close();
throw new Exception('Unable to open the zip file');
}
// if (!is_dir($dir)) {
// @mkdir($dir, 0755);
// }
// 解压插件压缩包
try {
$zip->extractTo($dir);
} catch (ZipException $e) {
throw new Exception('Unable to extract the file:'.$e->getMessage().' ,dir:'.$dir);
} finally {
$zip->close();
}
return $dir;
}
/**
* 获取请求对象
* @return Client
*/
public static function getClient()
{
$options = [
//'base_uri' => self::getServerUrl(),
'timeout' => 30,
'connect_timeout' => 30,
'verify' => false,
'http_errors' => false,
'headers' => [
'X-REQUESTED-WITH' => 'XMLHttpRequest',
//'Referer' => dirname(request()->root(true)),
'User-Agent' => 'qimall',
]
];
static $client;
if (empty($client)) {
$client = new Client($options);
}
return $client;
}
/**
* 发送请求
* @return array
* @throws Exception
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public static function sendRequest($url, $params = [], $method = 'POST')
{
$json = [];
try {
$client = self::getClient();
$options = strtoupper($method) == 'POST' ? ['form_params' => $params] : ['query' => $params];
$response = $client->request($method, $url, $options);
$body = $response->getBody();
$content = $body->getContents();
$json = (array)json_decode($content, true);
} catch (TransferException $e) {
throw new Exception('Network error');
} catch (\Exception $e) {
throw new Exception('Unknown data format');
}
return $json;
}
/**
* 备份项目
* @return bool
* @throws Exception
*/
public static function backup()
{
//$workDir = self::getWorkDir();
$backupDir = self::getWorkBackupDir();
$file = $backupDir . 'qimall-backup-' . date("YmdHis") . '.zip';
$zipFile = new ZipFile();
try {
$zipFile
//->addDirRecursive($workDir)
->addDirRecursive(Yii::getAlias('@common'),'common')
->addDirRecursive(Yii::getAlias('@frontend'),'frontend')
->addDirRecursive(Yii::getAlias('@api'),'api')
->addDirRecursive(Yii::getAlias('@backend'),'backend')
->addDirRecursive(Yii::getAlias('@console'),'console')
->addDirRecursive(Yii::getAlias('@addons'),'addons')
->addDirRecursive(Yii::getAlias('@services'),'services')
->saveAsFile($file)
->close();
} catch (ZipException $e) {
} finally {
$zipFile->close();
}
return true;
}
/**
* 备份H5文件夹
* @return bool
*/
public static function backupH5()
{
$backupDir = self::getWorkBackupDir();
$file = $backupDir . 'h5-backup-' . date("YmdHis") . '.zip';
$zipFile = new ZipFile();
try {
$zipFile
->addDirRecursive(self::getWorkDir().'h5'.DIRECTORY_SEPARATOR)
->saveAsFile($file)
->close();
} catch (ZipException $e) {
} finally {
$zipFile->close();
}
return true;
}
/**
* 执行SQL
* @param $fileName
*/
public static function importsql($fileName)
{
//$fileName = is_null($fileName) ? 'qimall.sql' : $fileName;
//$sqlFile = self::getWorkDir() . $fileName;
$sqlFile = $fileName;
$result = true;
if (is_file($sqlFile)) {
$lines = file($sqlFile);
$templine = '';
foreach ($lines as $line) {
if (substr($line, 0, 2) == '--' || $line == '' || substr($line, 0, 2) == '/*') {
continue;
}
$templine .= $line;
if (substr(trim($line), -1, 1) == ';') {
//$templine = str_ireplace('__PREFIX__', config('database.prefix'), $templine);
$templine = str_ireplace('INSERT INTO ', 'INSERT IGNORE INTO ', $templine);
try {
Yii::$app->db->createCommand($templine)->execute();
} catch (Exception $e) {
echo $e->getMessage();
$result = false;
}
$templine = '';
}
}
}
return $result;
}
/**
* 执行数据迁移
*/
public static function migrate()
{
//执行yii migrate
//exec('sh /www/wwwroot/qimall/run_migrate.sh', $result, $status);
$sh = self::getWorkDir().'run_migrate.sh';
if(file_exists($sh)){
//执行yii migrate
exec('sh '.$sh, $result, $status);
if( $status ){
echo "yii migrate执行失败";
print_r( $result );
return false;
}else{
echo "yii migrate成功执行, 结果如下
";
print_r( $result );
return true;
}
}
}
/**
* 重启队列服务:重启supervisorctl
*/
public static function restartService()
{
//判断是否docker部署
if (is_dir('/www/wwwroot/qimall')) {
$is_docker_deploy = \Yii::$app->params['is_docker_deploy'] ?? false;
if($is_docker_deploy){
$restart_docker_file = self::getWorkDir().'consume_docker.txt';
if(!file_exists($restart_docker_file)){
fopen($restart_docker_file, "w");
chown($restart_docker_file,'www');
}
file_put_contents($restart_docker_file,1);
}else{
//需要加上全路径
$shell = "sudo /www/server/panel/pyenv/bin/supervisorctl restart rabbitmq-consume:*";
exec($shell, $result, $status);
if( $status ){
echo "shell命令{$shell}执行失败";
} else {
echo "shell命令{$shell}成功执行, 结果如下
";
print_r( $result );
}
}
} else {
// 此部署方式的文件夹路径为data/wwwroot/qimall
$swoole_consume_num = \Yii::$app->params['swoole_consume_num'] ?? 1;
if($swoole_consume_num > 1){
for($i = 1;$i<= $swoole_consume_num;$i++){
$shell = "sudo systemctl restart swoole_consume_run@".$i;
exec($shell, $result, $status);
if( $status ){
echo "shell命令{$shell}执行失败";
} else {
echo "shell命令{$shell}成功执行, 结果如下
";
print_r( $result );
}
}
}else{
$shell = "sudo systemctl restart swoole_consume_run";
exec($shell, $result, $status);
if( $status ){
echo "shell命令{$shell}执行失败";
} else {
echo "shell命令{$shell}成功执行, 结果如下
";
print_r( $result );
}
}
}
}
/**
* 更新加锁
* @return bool
* @throws Exception
*/
public static function lockUpgrade()
{
$lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_VERSION_UPGRADE);
$identification = 'system';
if (!LockHelper::lock($lock_key, $identification, 1200, 1)){
throw new Exception('系统更新中,请等待...');
}
return true;
}
/**
* 版本更新完成释放锁
* @return bool
*/
public static function unlockUpgrade()
{
$lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_VERSION_UPGRADE);
$identification = 'system';
LockHelper::uLock($lock_key,$identification);
return true;
}
/**
* 添加记录
* @param $now_version
* @param $new_version
* @return int
*/
public static function addLog($now_version,$new_version)
{
preg_match('|mian:(.*?),|is',$now_version,$match);
$mian_version = $match[1] ?? '';
$addons_version = str_replace('mian:'.$mian_version.',','',$now_version);
$upgrade_addons_version = [];
$new_mian_version = '';
foreach($new_version as $key => $value){
if(strpos($key,'mian') === false){
$upgrade_addons_version[] = $key.':'.$value['version'];
}else{
$new_mian_version = $value['version'];
}
}
$params = [
'mian_version' => $mian_version,
'addons_version' => $addons_version,
'upgrade_mian_version' => $new_mian_version,
'upgrade_addons_version' => implode(',',$upgrade_addons_version),
'upgrade_params' => json_encode($new_version),
'status' => 0
];
$result = SystemVersionLog::setData($params);
return $result;
}
/**
* 更新记录状态
* @param $log
* @param $status
* @return mixed
*/
public static function updateLogStatus($log,$status)
{
$log->status = $status;
return $log->save();
}
/**
* 获取h5临时目录
*/
public static function getH5TmpDir($dirName = 'h5')
{
$dirName .= '_tmp';
$dir = self::getWorkDir(). $dirName .DIRECTORY_SEPARATOR;
if (!is_dir($dir)) {
@mkdir($dir, 0755, true);
}
return $dir;
}
/**
* @param $tmpDirName string 备份目录名
* @param $targetDirName string 目标目录名
*
* @return bool
*/
public static function handleH5($tmpDirName = 'h5', $targetDirName = 'h5')
{
//1. 压缩原来H5目录
// self::backupH5();
$h5_tmp_dir = self::getH5TmpDir($tmpDirName);
$tmp_js_dir = $h5_tmp_dir.'static/js/';
if (!is_dir($tmp_js_dir)) {
echo $tmpDirName . '_tmp/static/js/ 目录不存在';
return false;
}
$apiUrl = Yii::getAlias('@apiUrl') ?? '';
$attachurl = Yii::getAlias('@attachurl') ?? '';
if(!$apiUrl || !$attachurl){
return false;
}
//apiUrl attachurl 去除前面的https:// , http://
$apiUrl = str_replace('https://','',$apiUrl);
$apiUrl = str_replace('http://','',$apiUrl);
$attachurl = str_replace('https://','',$attachurl);
$attachurl = str_replace('http://','',$attachurl);
// 地图密钥
$config = Yii::$app->services->setting->platform->get('map');
$qq_map_key = $config['qq_jsapi_key_h5'] ? $config['qq_jsapi_key_h5'] : 'TSOBZ-PHCWT-MPBX5-LZLSP-4WSW6-UIFVJ';
$mall = Mall::find()->where(['=','status',StatusEnum::ENABLED])->andWhere(['=','is_recycle',StatusEnum::DISABLED])->orderBy('id asc')->asArray()->one();
$search = ['---sign---','---api---','---static---', 'TSOBZ-PHCWT-MPBX5-LZLSP-4WSW6-UIFVJ'];
$replace = [
$mall['mall_sign'] ?? '',
$apiUrl,
$attachurl,
$qq_map_key
];
//2.读取h5_tmp 目录:/static/js/ , 替换js文件
$js_list = [];
$resource = opendir($tmp_js_dir);
while ($file = readdir($resource))
{
//排除根目录
if ($file != ".." && $file != ".")
{
$con = file_get_contents($tmp_js_dir.$file);
$con = str_replace($search,$replace,$con);
file_put_contents($tmp_js_dir.$file,$con);
//根目录下的文件
$js_list[] = $file;
}
}
closedir($resource);
$h5_all_files = self::getFileList($h5_tmp_dir,'');
foreach($h5_all_files as $key => $file){
$source = $tmpDirName . '_tmp'.'/'.$file;
$target = $targetDirName .'/'.$file;
self::copyFile(self::getWorkDir(),$source,$target);
//删除h5_tmp
@unlink(self::getWorkDir() . '/' . $source);
}
return true;
}
/**
* 处理发薪猫h5包
*
* @return void
*/
public static function handleSalaryH5()
{
$salaryDir = self::getWorkDir() . 'salary';
// 如果已经存在salary目录,考虑旧目录不是nobody情况,修改目录归属为nobody
if (is_dir($salaryDir)) {
exec('sudo chown -R nobody:nobody ' . $salaryDir);
}
self::handleH5('salary-h5', 'salary');
// 生成发薪猫h5 Vhosts文件
try {
self::makeSalaryVhosts();
}catch (Exception $e) {
echo PHP_EOL . $e->getMessage();
}
}
/**
* 生成发薪猫h5 Vhosts文件
*
* @return void
*/
public static function makeSalaryVhosts()
{
$deployEnv = self::getDeployEnv();
// 如果是宝塔部署,不生成Vhosts文件
if ($deployEnv === self::DEPLOY_ENV_BT) {
return;
}
$workDir = self::getWorkDir();
if (!is_dir($workDir . 'salary')) {
return;
}
$salaryHost = parse_url(Yii::$app->services->setting->platform->get('system.cat_h5_url') ?: '')['host'] ?? '';
if (!$salaryHost) {
throw new RuntimeException('获取发薪猫域名失败');
}
// 检测发薪猫域名证书是否上传,不存在则不生成Vhosts文件
if (!self::isExistsSalaryCert($salaryHost, $deployEnv)) {
throw new RuntimeException('未检测到发薪猫域名证书');
}
$h5Host = parse_url(Yii::$app->services->setting->platform->get('system.h5_url') ?: '')['host'] ?? '';
if (!$h5Host) {
throw new RuntimeException('获取h5域名失败');
}
// nginx 配置文件目录
$vhostsDir = self::getVhostsPath($deployEnv);
if (!$vhostsDir) {
throw new RuntimeException('获取nginx 配置文件目录失败');
}
$salaryConfFile = $vhostsDir . $salaryHost . '.conf';
// 检查是否存在此文件,已存在则不再生成
if (file_exists($salaryConfFile)) {
return;
}
$h5ConfFile = $vhostsDir . $h5Host . '.conf';
if (!file_exists($h5ConfFile)) {
throw new RuntimeException('获取h5 vhosts conf 文件失败');
}
// 复制主商城h5Vhosts文件
exec(sprintf('sudo cp %s %s', $h5ConfFile, $salaryConfFile));
// 检查复制情况
if (!file_exists($salaryConfFile)) {
throw new RuntimeException('复制h5 vhosts conf 文件失败');
}
// 替换域名
exec(sprintf('sudo sed -i "s/%s/%s/g" %s', $h5Host, $salaryHost, $salaryConfFile));
// 替换指向目录
exec(sprintf('sudo sed -i "s|%sh5|%ssalary|g" %s', $workDir, $workDir, $salaryConfFile));
// 热重启nginx
self::restartNginx($deployEnv);
}
/**
* 获取部署环境
*
* @return string
*/
public static function getDeployEnv(): string
{
if (is_dir('/www/wwwroot/qimall')) {
if (Yii::$app->params['is_docker_deploy'] ?? false) {
return self::DEPLOY_ENV_DOCKER;
}
return self::DEPLOY_ENV_BT;
}
return self::DEPLOY_ENV_DEFAULT;
}
/**
* 根据部署环境重启nginx(如果是宝塔则不重启)
*
* @param string $deployEnv
*
* @return void
*/
public static function restartNginx(string $deployEnv)
{
switch ($deployEnv) {
case self::DEPLOY_ENV_DEFAULT:
exec('sudo /application/nginx/sbin/nginx -s reload');
break;
case self::DEPLOY_ENV_DOCKER:
exec('sudo docker restart nginx');
break;
default:
break;
}
}
/**
* 根据部署环境获取nginx vhosts文件路径
*
* @param string $deployEnv
*
* @return string
*/
public static function getVhostsPath(string $deployEnv): string
{
switch ($deployEnv) {
case self::DEPLOY_ENV_DEFAULT:
return '/application/nginx/conf/vhosts/';
case self::DEPLOY_ENV_DOCKER:
return '/data/confs/nginx/conf.d/';
default:
return '';
}
}
/**
* 检测是否存在发薪猫证书
*
* @param string $deployEnv 部署环境
*
* @return string
*/
public static function getSshPath(string $deployEnv): string
{
switch ($deployEnv) {
case self::DEPLOY_ENV_DEFAULT:
return '/application/nginx/conf/ssl/';
case self::DEPLOY_ENV_DOCKER:
return '/data/confs/nginx/ssl/';
default:
return '';
}
}
/**
* 检测是否存在发薪猫证书
*
* @param string $host
* @param string $deployEnv
*
* @return bool
*/
public static function isExistsSalaryCert(string $host, string $deployEnv): bool
{
$sshBasePath = self::getSshPath($deployEnv) . $host;
return file_exists($sshBasePath . '.key') && file_exists($sshBasePath . '.pem');
}
private static function getFileList($root, $basePath = '')
{
$files = [];
$handle = opendir($root);
while (($path = readdir($handle)) !== false) {
if ($path === '.git' || $path === '.svn' || $path === '.' || $path === '..') {
continue;
}
$fullPath = "$root/$path";
$relativePath = $basePath === '' ? $path : "$basePath/$path";
if (is_dir($fullPath)) {
$files = array_merge($files, self::getFileList($fullPath, $relativePath));
} else {
$files[] = $relativePath;
}
}
closedir($handle);
return $files;
}
private static function copyFile($root, $source, $target)
{
if (!is_file($root . '/' . $source)) {
echo " skip $target ($source not exist)\n";
return true;
}
if (is_file($root . '/' . $target)) {
file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source));
return true;
}
//echo " generate $target\n";
@mkdir(dirname($root . '/' . $target), 0777, true);
file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source));
return true;
}
/**
* 异步更新插件配置
* @param $addons_arr
* @throws Exception
*/
private static function asynchAddonsConfig($addons_arr)
{
if(!$addons_arr) return ;
Yii::$app->services->rabbitMq->delay(10, $addons_arr, self::class, 'updateAddonsConfig');
}
/**
* 更新插件配置
* @param $addons_arr
* @return bool
*/
public static function updateAddonsConfig($addons_arr)
{
if(!$addons_arr) return true;
try{
foreach($addons_arr as $addons_name){
$res = Addons::install($addons_name);
if(!$res){
Yii::$app->custom->logs('updateAddonsConfig error:'.Addons::getStaticError());
}
}
}catch(Exception $e){
Yii::$app->custom->logs('updateAddonsConfig Exception:'.$e->getMessage());
return false;
}
return true;
}
/**
* 从服务器下载更新代码
* @Author: lun
* @DateTime: 2023/5/18 0018 9:45
* @Copyright: copyright (c) 2021 广东七件事集团
* @param $data
* @param $slbId
* @return bool
* @throws Exception
*/
public static function slbUpgrade($data, $slbId)
{
foreach($data as $key => $value){
$addons_name = $key;
$download_url = $value['download_url'] ?? '';
if(!$download_url){
continue;
}
$tmpFile = self::download($download_url);
try {
if(strpos($addons_name,'mian') === false){
if(in_array($addons_name, ['h5', 'salary-h5'])){
//h5更新
$dir = self::getH5TmpDir($addons_name);
}else{
//插件更新
$dir = Yii::getAlias('@addons').DIRECTORY_SEPARATOR;
}
}else{
//主包更新
$addons_name = 'mian';
$dir = self::getWorkDir();
}
// 解压插件
self::unzip($tmpFile,$dir);
if($addons_name == 'h5'){
self::handleH5();
}
if($addons_name == 'salary-h5'){
self::handleSalaryH5();
}
} catch (Exception $e) {
SystemVersionSlb::setStatus($slbId, -1);
throw new Exception('从服务器更新失败:'.$e->getMessage());
} finally {
// 移除临时文件
@unlink($tmpFile);
}
}
// 更新状态
SystemVersionSlb::setStatus($slbId, 1);
return true;
}
/**
* 强制降级H5版本
* @return bool
* @throws Exception
*/
public static function forceDowngradeH5()
{
$h5_version_file = self::getWorkDir() . 'h5/version';
if (!file_exists($h5_version_file)) {
throw new Exception('h5版本文件不存在');
}
file_put_contents($h5_version_file, '1.0.0');
return true;
}
}