UpgradeService.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  1. <?php
  2. namespace services\common;
  3. use common\enums\RedisKeyEnum;
  4. use common\enums\StatusEnum;
  5. use common\helpers\CurlHelper;
  6. use common\helpers\LockHelper;
  7. use common\models\backend\SystemVersionLog;
  8. use common\models\backend\SystemVersionSlb;
  9. use common\models\common\Addons;
  10. use common\models\mall\Mall;
  11. use RuntimeException;
  12. use Yii;
  13. use yii\data\Pagination;
  14. use yii\web\NotFoundHttpException;
  15. use common\components\Service;
  16. use GuzzleHttp\Client;
  17. use GuzzleHttp\Exception\TransferException;
  18. use GuzzleHttp\TransferStats;
  19. use Exception;
  20. use PhpZip\Exception\ZipException;
  21. use PhpZip\ZipFile;
  22. use common\traits\ErrorTrait;
  23. /**
  24. *
  25. * 版本更新服务
  26. * Class UpgradeService
  27. * @package services\common
  28. */
  29. class UpgradeService extends Service
  30. {
  31. use ErrorTrait;
  32. const DEPLOY_ENV_BT = 'bt';
  33. const DEPLOY_ENV_DOCKER = 'docker';
  34. const DEPLOY_ENV_DEFAULT = 'default';
  35. // public $service_url = 'https://testapi.qijianshigw.com/version-upgrade/upgrade';
  36. public $service_url = 'https://service-api.qijianshigw.com/version-upgrade/upgrade';
  37. /**
  38. * 获取新版本
  39. * @param $now_version
  40. * @return mixed
  41. * @throws
  42. */
  43. public function getNewVersion($now_version)
  44. {
  45. $post = ['system_info' => $now_version,'web_api_url' => Yii::getAlias('@apiUrl'),'sign' => ''];
  46. //$result = CurlHelper::post($this->service_url,$post);
  47. $result = self::sendRequest($this->service_url,$post,'POST');
  48. return $result;
  49. }
  50. /**
  51. * 升级版本
  52. * @param array $data
  53. * @throws
  54. * @return bool
  55. */
  56. public static function upgrade($params)
  57. {
  58. ini_set('memory_limit',-1);
  59. $data = $params['upgrade'];
  60. $now_version = $params['now_version'];
  61. $log = self::addLog($now_version,$data);
  62. // 备份插件文件
  63. //self::backup(); //todo
  64. $sqlFiles = [];
  65. $addons_arr = [];
  66. foreach($data as $key => $value){
  67. $addons_name = $key;
  68. $download_url = $value['download_url'] ?? '';
  69. if(!$download_url){
  70. continue;
  71. }
  72. $tmpFile = self::download($download_url);
  73. try {
  74. if(strpos($addons_name,'mian') === false){
  75. if(in_array($addons_name, ['h5', 'salary-h5'])){
  76. //h5更新
  77. $dir = self::getH5TmpDir($addons_name);
  78. }else{
  79. //插件更新
  80. $dir = Yii::getAlias('@addons').DIRECTORY_SEPARATOR;
  81. }
  82. }else{
  83. //主包更新
  84. $addons_name = 'mian';
  85. $dir = self::getWorkDir();
  86. }
  87. // 解压插件
  88. self::unzip($tmpFile,$dir);
  89. if($addons_name == 'h5'){
  90. self::handleH5();
  91. }
  92. if($addons_name == 'salary-h5'){
  93. self::handleSalaryH5();
  94. }
  95. } catch (Exception $e) {
  96. throw new Exception($download_url.','.$e->getMessage());
  97. } finally {
  98. // 移除临时文件
  99. @unlink($tmpFile);
  100. }
  101. if($value['is_install']){
  102. //需要导入sql
  103. if($addons_name == 'mian'){
  104. $sqlFiles[] = self::getWorkDir() . 'install.sql';
  105. }else{
  106. $sqlFiles[] = Yii::getAlias('@addons').DIRECTORY_SEPARATOR.$addons_name.DIRECTORY_SEPARATOR.'install.sql';
  107. }
  108. }
  109. if(!in_array($addons_name,['mian','h5'])){
  110. //插件更新配置
  111. $addons_arr[] = $addons_name;
  112. //Addons::install($addons_name);
  113. }
  114. }
  115. //更新文件所有者
  116. // $shell = "chown www:www -R ".\Yii::getAlias('@webpath');
  117. // exec($shell, $result, $status);
  118. //判断是否安装mycat,如果按照 则不能执行以下操作
  119. $dbConfig = \Yii::$app->getComponents()['db'];
  120. $dsn = $dbConfig['dsn'];
  121. preg_match('|port=(.*?);|',$dsn,$match);
  122. $port = $match[1] ?? '';
  123. $status = 1;
  124. if($port == 3306){
  125. //mysql 端口
  126. if($sqlFiles){
  127. // 导入
  128. foreach($sqlFiles as $sql){
  129. $res = self::importsql($sql);
  130. if(!$res) $status = -1;
  131. }
  132. }
  133. $result = self::migrate();
  134. $status = $result === false ? -1 : $status;
  135. }else{
  136. Yii::$app->custom->logs('数据库未处理,数据迁移迁移未处理,'.implode(',',$sqlFiles) );
  137. self::$static_error = '请联系技术专员处理数据库';
  138. }
  139. //更新完成:释放锁
  140. self::unlockUpgrade();
  141. //更新记录状态
  142. self::updateLogStatus($log,$status);
  143. //异步处理更新插件配置
  144. self::asynchAddonsConfig($addons_arr);
  145. //重启服务
  146. self::restartService();
  147. return true;
  148. }
  149. /**
  150. * 获取远程服务器
  151. * @return string
  152. */
  153. protected static function getServerUrl()
  154. {
  155. return Yii::getAlias('@serverUrl');
  156. }
  157. /**
  158. * 获取备份目录
  159. */
  160. public static function getBackupDir()
  161. {
  162. $dir = Yii::$app->getRuntimePath().DIRECTORY_SEPARATOR.'bak'.DIRECTORY_SEPARATOR;
  163. if (!is_dir($dir)) {
  164. @mkdir($dir, 0755, true);
  165. }
  166. return $dir;
  167. }
  168. /**
  169. * 获取项目备份地址
  170. * @return string
  171. */
  172. public static function getWorkBackupDir(){
  173. //$dir = Yii::getAlias('@webpath').DIRECTORY_SEPARATOR.'../';
  174. $dir = Yii::$app->getRuntimePath().DIRECTORY_SEPARATOR.'bak_up'.DIRECTORY_SEPARATOR;
  175. if (!is_dir($dir)) {
  176. @mkdir($dir, 0755, true);
  177. }
  178. return $dir;
  179. }
  180. /**
  181. * 获取项目根目录
  182. */
  183. public static function getWorkDir()
  184. {
  185. $dir = Yii::getAlias('@webpath').DIRECTORY_SEPARATOR;
  186. return $dir;
  187. }
  188. /**
  189. * 远程下载更新包
  190. * @param string $url 下载地址
  191. * @return string
  192. * @throws
  193. */
  194. public static function download($url)
  195. {
  196. $tempDir = self::getBackupDir();
  197. $tmpFile = $tempDir . date('Y_m_d_H_i_s') . ".zip";
  198. try {
  199. $client = self::getClient();
  200. $response = $client->get($url);
  201. $statusCode = $response->getStatusCode();
  202. if($statusCode != 200) throw new Exception("query result code error:".$statusCode.',url='.$url);
  203. $body = $response->getBody();
  204. $content = $body->getContents();
  205. } catch (TransferException $e) {
  206. throw new Exception("package download failed:".$url);
  207. }
  208. if ($write = fopen($tmpFile, 'w')) {
  209. fwrite($write, $content);
  210. fclose($write);
  211. return $tmpFile;
  212. }
  213. throw new Exception("No permission to write temporary files");
  214. }
  215. /**
  216. * 解压压缩包
  217. *
  218. * @param string $name 压缩包名称
  219. * @return string
  220. * @throws Exception
  221. */
  222. public static function unzip($file,$dir)
  223. {
  224. if (!$file || !file_exists($file)) {
  225. throw new Exception('Invalid parameters');
  226. }
  227. // 打开插件压缩包
  228. $zip = new ZipFile();
  229. try {
  230. $zip->openFile($file);
  231. } catch (ZipException $e) {
  232. $zip->close();
  233. throw new Exception('Unable to open the zip file');
  234. }
  235. // if (!is_dir($dir)) {
  236. // @mkdir($dir, 0755);
  237. // }
  238. // 解压插件压缩包
  239. try {
  240. $zip->extractTo($dir);
  241. } catch (ZipException $e) {
  242. throw new Exception('Unable to extract the file:'.$e->getMessage().' ,dir:'.$dir);
  243. } finally {
  244. $zip->close();
  245. }
  246. return $dir;
  247. }
  248. /**
  249. * 获取请求对象
  250. * @return Client
  251. */
  252. public static function getClient()
  253. {
  254. $options = [
  255. //'base_uri' => self::getServerUrl(),
  256. 'timeout' => 30,
  257. 'connect_timeout' => 30,
  258. 'verify' => false,
  259. 'http_errors' => false,
  260. 'headers' => [
  261. 'X-REQUESTED-WITH' => 'XMLHttpRequest',
  262. //'Referer' => dirname(request()->root(true)),
  263. 'User-Agent' => 'qimall',
  264. ]
  265. ];
  266. static $client;
  267. if (empty($client)) {
  268. $client = new Client($options);
  269. }
  270. return $client;
  271. }
  272. /**
  273. * 发送请求
  274. * @return array
  275. * @throws Exception
  276. * @throws \GuzzleHttp\Exception\GuzzleException
  277. */
  278. public static function sendRequest($url, $params = [], $method = 'POST')
  279. {
  280. $json = [];
  281. try {
  282. $client = self::getClient();
  283. $options = strtoupper($method) == 'POST' ? ['form_params' => $params] : ['query' => $params];
  284. $response = $client->request($method, $url, $options);
  285. $body = $response->getBody();
  286. $content = $body->getContents();
  287. $json = (array)json_decode($content, true);
  288. } catch (TransferException $e) {
  289. throw new Exception('Network error');
  290. } catch (\Exception $e) {
  291. throw new Exception('Unknown data format');
  292. }
  293. return $json;
  294. }
  295. /**
  296. * 备份项目
  297. * @return bool
  298. * @throws Exception
  299. */
  300. public static function backup()
  301. {
  302. //$workDir = self::getWorkDir();
  303. $backupDir = self::getWorkBackupDir();
  304. $file = $backupDir . 'qimall-backup-' . date("YmdHis") . '.zip';
  305. $zipFile = new ZipFile();
  306. try {
  307. $zipFile
  308. //->addDirRecursive($workDir)
  309. ->addDirRecursive(Yii::getAlias('@common'),'common')
  310. ->addDirRecursive(Yii::getAlias('@frontend'),'frontend')
  311. ->addDirRecursive(Yii::getAlias('@api'),'api')
  312. ->addDirRecursive(Yii::getAlias('@backend'),'backend')
  313. ->addDirRecursive(Yii::getAlias('@console'),'console')
  314. ->addDirRecursive(Yii::getAlias('@addons'),'addons')
  315. ->addDirRecursive(Yii::getAlias('@services'),'services')
  316. ->saveAsFile($file)
  317. ->close();
  318. } catch (ZipException $e) {
  319. } finally {
  320. $zipFile->close();
  321. }
  322. return true;
  323. }
  324. /**
  325. * 备份H5文件夹
  326. * @return bool
  327. */
  328. public static function backupH5()
  329. {
  330. $backupDir = self::getWorkBackupDir();
  331. $file = $backupDir . 'h5-backup-' . date("YmdHis") . '.zip';
  332. $zipFile = new ZipFile();
  333. try {
  334. $zipFile
  335. ->addDirRecursive(self::getWorkDir().'h5'.DIRECTORY_SEPARATOR)
  336. ->saveAsFile($file)
  337. ->close();
  338. } catch (ZipException $e) {
  339. } finally {
  340. $zipFile->close();
  341. }
  342. return true;
  343. }
  344. /**
  345. * 执行SQL
  346. * @param $fileName
  347. */
  348. public static function importsql($fileName)
  349. {
  350. //$fileName = is_null($fileName) ? 'qimall.sql' : $fileName;
  351. //$sqlFile = self::getWorkDir() . $fileName;
  352. $sqlFile = $fileName;
  353. $result = true;
  354. if (is_file($sqlFile)) {
  355. $lines = file($sqlFile);
  356. $templine = '';
  357. foreach ($lines as $line) {
  358. if (substr($line, 0, 2) == '--' || $line == '' || substr($line, 0, 2) == '/*') {
  359. continue;
  360. }
  361. $templine .= $line;
  362. if (substr(trim($line), -1, 1) == ';') {
  363. //$templine = str_ireplace('__PREFIX__', config('database.prefix'), $templine);
  364. $templine = str_ireplace('INSERT INTO ', 'INSERT IGNORE INTO ', $templine);
  365. try {
  366. Yii::$app->db->createCommand($templine)->execute();
  367. } catch (Exception $e) {
  368. echo $e->getMessage();
  369. $result = false;
  370. }
  371. $templine = '';
  372. }
  373. }
  374. }
  375. return $result;
  376. }
  377. /**
  378. * 执行数据迁移
  379. */
  380. public static function migrate()
  381. {
  382. //执行yii migrate
  383. //exec('sh /www/wwwroot/qimall/run_migrate.sh', $result, $status);
  384. $sh = self::getWorkDir().'run_migrate.sh';
  385. if(file_exists($sh)){
  386. //执行yii migrate
  387. exec('sh '.$sh, $result, $status);
  388. if( $status ){
  389. echo "yii migrate执行失败";
  390. print_r( $result );
  391. return false;
  392. }else{
  393. echo "yii migrate成功执行, 结果如下<hr>";
  394. print_r( $result );
  395. return true;
  396. }
  397. }
  398. }
  399. /**
  400. * 重启队列服务:重启supervisorctl
  401. */
  402. public static function restartService()
  403. {
  404. //判断是否docker部署
  405. if (is_dir('/www/wwwroot/qimall')) {
  406. $is_docker_deploy = \Yii::$app->params['is_docker_deploy'] ?? false;
  407. if($is_docker_deploy){
  408. $restart_docker_file = self::getWorkDir().'consume_docker.txt';
  409. if(!file_exists($restart_docker_file)){
  410. fopen($restart_docker_file, "w");
  411. chown($restart_docker_file,'www');
  412. }
  413. file_put_contents($restart_docker_file,1);
  414. }else{
  415. //需要加上全路径
  416. $shell = "sudo /www/server/panel/pyenv/bin/supervisorctl restart rabbitmq-consume:*";
  417. exec($shell, $result, $status);
  418. if( $status ){
  419. echo "shell命令{$shell}执行失败";
  420. } else {
  421. echo "shell命令{$shell}成功执行, 结果如下<hr>";
  422. print_r( $result );
  423. }
  424. }
  425. } else {
  426. // 此部署方式的文件夹路径为data/wwwroot/qimall
  427. $swoole_consume_num = \Yii::$app->params['swoole_consume_num'] ?? 1;
  428. if($swoole_consume_num > 1){
  429. for($i = 1;$i<= $swoole_consume_num;$i++){
  430. $shell = "sudo systemctl restart swoole_consume_run@".$i;
  431. exec($shell, $result, $status);
  432. if( $status ){
  433. echo "shell命令{$shell}执行失败";
  434. } else {
  435. echo "shell命令{$shell}成功执行, 结果如下<hr>";
  436. print_r( $result );
  437. }
  438. }
  439. }else{
  440. $shell = "sudo systemctl restart swoole_consume_run";
  441. exec($shell, $result, $status);
  442. if( $status ){
  443. echo "shell命令{$shell}执行失败";
  444. } else {
  445. echo "shell命令{$shell}成功执行, 结果如下<hr>";
  446. print_r( $result );
  447. }
  448. }
  449. }
  450. }
  451. /**
  452. * 更新加锁
  453. * @return bool
  454. * @throws Exception
  455. */
  456. public static function lockUpgrade()
  457. {
  458. $lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_VERSION_UPGRADE);
  459. $identification = 'system';
  460. if (!LockHelper::lock($lock_key, $identification, 1200, 1)){
  461. throw new Exception('系统更新中,请等待...');
  462. }
  463. return true;
  464. }
  465. /**
  466. * 版本更新完成释放锁
  467. * @return bool
  468. */
  469. public static function unlockUpgrade()
  470. {
  471. $lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_VERSION_UPGRADE);
  472. $identification = 'system';
  473. LockHelper::uLock($lock_key,$identification);
  474. return true;
  475. }
  476. /**
  477. * 添加记录
  478. * @param $now_version
  479. * @param $new_version
  480. * @return int
  481. */
  482. public static function addLog($now_version,$new_version)
  483. {
  484. preg_match('|mian:(.*?),|is',$now_version,$match);
  485. $mian_version = $match[1] ?? '';
  486. $addons_version = str_replace('mian:'.$mian_version.',','',$now_version);
  487. $upgrade_addons_version = [];
  488. $new_mian_version = '';
  489. foreach($new_version as $key => $value){
  490. if(strpos($key,'mian') === false){
  491. $upgrade_addons_version[] = $key.':'.$value['version'];
  492. }else{
  493. $new_mian_version = $value['version'];
  494. }
  495. }
  496. $params = [
  497. 'mian_version' => $mian_version,
  498. 'addons_version' => $addons_version,
  499. 'upgrade_mian_version' => $new_mian_version,
  500. 'upgrade_addons_version' => implode(',',$upgrade_addons_version),
  501. 'upgrade_params' => json_encode($new_version),
  502. 'status' => 0
  503. ];
  504. $result = SystemVersionLog::setData($params);
  505. return $result;
  506. }
  507. /**
  508. * 更新记录状态
  509. * @param $log
  510. * @param $status
  511. * @return mixed
  512. */
  513. public static function updateLogStatus($log,$status)
  514. {
  515. $log->status = $status;
  516. return $log->save();
  517. }
  518. /**
  519. * 获取h5临时目录
  520. */
  521. public static function getH5TmpDir($dirName = 'h5')
  522. {
  523. $dirName .= '_tmp';
  524. $dir = self::getWorkDir(). $dirName .DIRECTORY_SEPARATOR;
  525. if (!is_dir($dir)) {
  526. @mkdir($dir, 0755, true);
  527. }
  528. return $dir;
  529. }
  530. /**
  531. * @param $tmpDirName string 备份目录名
  532. * @param $targetDirName string 目标目录名
  533. *
  534. * @return bool
  535. */
  536. public static function handleH5($tmpDirName = 'h5', $targetDirName = 'h5')
  537. {
  538. //1. 压缩原来H5目录
  539. // self::backupH5();
  540. $h5_tmp_dir = self::getH5TmpDir($tmpDirName);
  541. $tmp_js_dir = $h5_tmp_dir.'static/js/';
  542. if (!is_dir($tmp_js_dir)) {
  543. echo $tmpDirName . '_tmp/static/js/ 目录不存在';
  544. return false;
  545. }
  546. $apiUrl = Yii::getAlias('@apiUrl') ?? '';
  547. $attachurl = Yii::getAlias('@attachurl') ?? '';
  548. if(!$apiUrl || !$attachurl){
  549. return false;
  550. }
  551. //apiUrl attachurl 去除前面的https:// , http://
  552. $apiUrl = str_replace('https://','',$apiUrl);
  553. $apiUrl = str_replace('http://','',$apiUrl);
  554. $attachurl = str_replace('https://','',$attachurl);
  555. $attachurl = str_replace('http://','',$attachurl);
  556. // 地图密钥
  557. $config = Yii::$app->services->setting->platform->get('map');
  558. $qq_map_key = $config['qq_jsapi_key_h5'] ? $config['qq_jsapi_key_h5'] : 'TSOBZ-PHCWT-MPBX5-LZLSP-4WSW6-UIFVJ';
  559. $mall = Mall::find()->where(['=','status',StatusEnum::ENABLED])->andWhere(['=','is_recycle',StatusEnum::DISABLED])->orderBy('id asc')->asArray()->one();
  560. $search = ['---sign---','---api---','---static---', 'TSOBZ-PHCWT-MPBX5-LZLSP-4WSW6-UIFVJ'];
  561. $replace = [
  562. $mall['mall_sign'] ?? '',
  563. $apiUrl,
  564. $attachurl,
  565. $qq_map_key
  566. ];
  567. //2.读取h5_tmp 目录:/static/js/ , 替换js文件
  568. $js_list = [];
  569. $resource = opendir($tmp_js_dir);
  570. while ($file = readdir($resource))
  571. {
  572. //排除根目录
  573. if ($file != ".." && $file != ".")
  574. {
  575. $con = file_get_contents($tmp_js_dir.$file);
  576. $con = str_replace($search,$replace,$con);
  577. file_put_contents($tmp_js_dir.$file,$con);
  578. //根目录下的文件
  579. $js_list[] = $file;
  580. }
  581. }
  582. closedir($resource);
  583. $h5_all_files = self::getFileList($h5_tmp_dir,'');
  584. foreach($h5_all_files as $key => $file){
  585. $source = $tmpDirName . '_tmp'.'/'.$file;
  586. $target = $targetDirName .'/'.$file;
  587. self::copyFile(self::getWorkDir(),$source,$target);
  588. //删除h5_tmp
  589. @unlink(self::getWorkDir() . '/' . $source);
  590. }
  591. return true;
  592. }
  593. /**
  594. * 处理发薪猫h5包
  595. *
  596. * @return void
  597. */
  598. public static function handleSalaryH5()
  599. {
  600. $salaryDir = self::getWorkDir() . 'salary';
  601. // 如果已经存在salary目录,考虑旧目录不是nobody情况,修改目录归属为nobody
  602. if (is_dir($salaryDir)) {
  603. exec('sudo chown -R nobody:nobody ' . $salaryDir);
  604. }
  605. self::handleH5('salary-h5', 'salary');
  606. // 生成发薪猫h5 Vhosts文件
  607. try {
  608. self::makeSalaryVhosts();
  609. }catch (Exception $e) {
  610. echo PHP_EOL . $e->getMessage();
  611. }
  612. }
  613. /**
  614. * 生成发薪猫h5 Vhosts文件
  615. *
  616. * @return void
  617. */
  618. public static function makeSalaryVhosts()
  619. {
  620. $deployEnv = self::getDeployEnv();
  621. // 如果是宝塔部署,不生成Vhosts文件
  622. if ($deployEnv === self::DEPLOY_ENV_BT) {
  623. return;
  624. }
  625. $workDir = self::getWorkDir();
  626. if (!is_dir($workDir . 'salary')) {
  627. return;
  628. }
  629. $salaryHost = parse_url(Yii::$app->services->setting->platform->get('system.cat_h5_url') ?: '')['host'] ?? '';
  630. if (!$salaryHost) {
  631. throw new RuntimeException('获取发薪猫域名失败');
  632. }
  633. // 检测发薪猫域名证书是否上传,不存在则不生成Vhosts文件
  634. if (!self::isExistsSalaryCert($salaryHost, $deployEnv)) {
  635. throw new RuntimeException('未检测到发薪猫域名证书');
  636. }
  637. $h5Host = parse_url(Yii::$app->services->setting->platform->get('system.h5_url') ?: '')['host'] ?? '';
  638. if (!$h5Host) {
  639. throw new RuntimeException('获取h5域名失败');
  640. }
  641. // nginx 配置文件目录
  642. $vhostsDir = self::getVhostsPath($deployEnv);
  643. if (!$vhostsDir) {
  644. throw new RuntimeException('获取nginx 配置文件目录失败');
  645. }
  646. $salaryConfFile = $vhostsDir . $salaryHost . '.conf';
  647. // 检查是否存在此文件,已存在则不再生成
  648. if (file_exists($salaryConfFile)) {
  649. return;
  650. }
  651. $h5ConfFile = $vhostsDir . $h5Host . '.conf';
  652. if (!file_exists($h5ConfFile)) {
  653. throw new RuntimeException('获取h5 vhosts conf 文件失败');
  654. }
  655. // 复制主商城h5Vhosts文件
  656. exec(sprintf('sudo cp %s %s', $h5ConfFile, $salaryConfFile));
  657. // 检查复制情况
  658. if (!file_exists($salaryConfFile)) {
  659. throw new RuntimeException('复制h5 vhosts conf 文件失败');
  660. }
  661. // 替换域名
  662. exec(sprintf('sudo sed -i "s/%s/%s/g" %s', $h5Host, $salaryHost, $salaryConfFile));
  663. // 替换指向目录
  664. exec(sprintf('sudo sed -i "s|%sh5|%ssalary|g" %s', $workDir, $workDir, $salaryConfFile));
  665. // 热重启nginx
  666. self::restartNginx($deployEnv);
  667. }
  668. /**
  669. * 获取部署环境
  670. *
  671. * @return string
  672. */
  673. public static function getDeployEnv(): string
  674. {
  675. if (is_dir('/www/wwwroot/qimall')) {
  676. if (Yii::$app->params['is_docker_deploy'] ?? false) {
  677. return self::DEPLOY_ENV_DOCKER;
  678. }
  679. return self::DEPLOY_ENV_BT;
  680. }
  681. return self::DEPLOY_ENV_DEFAULT;
  682. }
  683. /**
  684. * 根据部署环境重启nginx(如果是宝塔则不重启)
  685. *
  686. * @param string $deployEnv
  687. *
  688. * @return void
  689. */
  690. public static function restartNginx(string $deployEnv)
  691. {
  692. switch ($deployEnv) {
  693. case self::DEPLOY_ENV_DEFAULT:
  694. exec('sudo /application/nginx/sbin/nginx -s reload');
  695. break;
  696. case self::DEPLOY_ENV_DOCKER:
  697. exec('sudo docker restart nginx');
  698. break;
  699. default:
  700. break;
  701. }
  702. }
  703. /**
  704. * 根据部署环境获取nginx vhosts文件路径
  705. *
  706. * @param string $deployEnv
  707. *
  708. * @return string
  709. */
  710. public static function getVhostsPath(string $deployEnv): string
  711. {
  712. switch ($deployEnv) {
  713. case self::DEPLOY_ENV_DEFAULT:
  714. return '/application/nginx/conf/vhosts/';
  715. case self::DEPLOY_ENV_DOCKER:
  716. return '/data/confs/nginx/conf.d/';
  717. default:
  718. return '';
  719. }
  720. }
  721. /**
  722. * 检测是否存在发薪猫证书
  723. *
  724. * @param string $deployEnv 部署环境
  725. *
  726. * @return string
  727. */
  728. public static function getSshPath(string $deployEnv): string
  729. {
  730. switch ($deployEnv) {
  731. case self::DEPLOY_ENV_DEFAULT:
  732. return '/application/nginx/conf/ssl/';
  733. case self::DEPLOY_ENV_DOCKER:
  734. return '/data/confs/nginx/ssl/';
  735. default:
  736. return '';
  737. }
  738. }
  739. /**
  740. * 检测是否存在发薪猫证书
  741. *
  742. * @param string $host
  743. * @param string $deployEnv
  744. *
  745. * @return bool
  746. */
  747. public static function isExistsSalaryCert(string $host, string $deployEnv): bool
  748. {
  749. $sshBasePath = self::getSshPath($deployEnv) . $host;
  750. return file_exists($sshBasePath . '.key') && file_exists($sshBasePath . '.pem');
  751. }
  752. private static function getFileList($root, $basePath = '')
  753. {
  754. $files = [];
  755. $handle = opendir($root);
  756. while (($path = readdir($handle)) !== false) {
  757. if ($path === '.git' || $path === '.svn' || $path === '.' || $path === '..') {
  758. continue;
  759. }
  760. $fullPath = "$root/$path";
  761. $relativePath = $basePath === '' ? $path : "$basePath/$path";
  762. if (is_dir($fullPath)) {
  763. $files = array_merge($files, self::getFileList($fullPath, $relativePath));
  764. } else {
  765. $files[] = $relativePath;
  766. }
  767. }
  768. closedir($handle);
  769. return $files;
  770. }
  771. private static function copyFile($root, $source, $target)
  772. {
  773. if (!is_file($root . '/' . $source)) {
  774. echo " skip $target ($source not exist)\n";
  775. return true;
  776. }
  777. if (is_file($root . '/' . $target)) {
  778. file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source));
  779. return true;
  780. }
  781. //echo " generate $target\n";
  782. @mkdir(dirname($root . '/' . $target), 0777, true);
  783. file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source));
  784. return true;
  785. }
  786. /**
  787. * 异步更新插件配置
  788. * @param $addons_arr
  789. * @throws Exception
  790. */
  791. private static function asynchAddonsConfig($addons_arr)
  792. {
  793. if(!$addons_arr) return ;
  794. Yii::$app->services->rabbitMq->delay(10, $addons_arr, self::class, 'updateAddonsConfig');
  795. }
  796. /**
  797. * 更新插件配置
  798. * @param $addons_arr
  799. * @return bool
  800. */
  801. public static function updateAddonsConfig($addons_arr)
  802. {
  803. if(!$addons_arr) return true;
  804. try{
  805. foreach($addons_arr as $addons_name){
  806. $res = Addons::install($addons_name);
  807. if(!$res){
  808. Yii::$app->custom->logs('updateAddonsConfig error:'.Addons::getStaticError());
  809. }
  810. }
  811. }catch(Exception $e){
  812. Yii::$app->custom->logs('updateAddonsConfig Exception:'.$e->getMessage());
  813. return false;
  814. }
  815. return true;
  816. }
  817. /**
  818. * 从服务器下载更新代码
  819. * @Author: lun
  820. * @DateTime: 2023/5/18 0018 9:45
  821. * @Copyright: copyright (c) 2021 广东七件事集团
  822. * @param $data
  823. * @param $slbId
  824. * @return bool
  825. * @throws Exception
  826. */
  827. public static function slbUpgrade($data, $slbId)
  828. {
  829. foreach($data as $key => $value){
  830. $addons_name = $key;
  831. $download_url = $value['download_url'] ?? '';
  832. if(!$download_url){
  833. continue;
  834. }
  835. $tmpFile = self::download($download_url);
  836. try {
  837. if(strpos($addons_name,'mian') === false){
  838. if(in_array($addons_name, ['h5', 'salary-h5'])){
  839. //h5更新
  840. $dir = self::getH5TmpDir($addons_name);
  841. }else{
  842. //插件更新
  843. $dir = Yii::getAlias('@addons').DIRECTORY_SEPARATOR;
  844. }
  845. }else{
  846. //主包更新
  847. $addons_name = 'mian';
  848. $dir = self::getWorkDir();
  849. }
  850. // 解压插件
  851. self::unzip($tmpFile,$dir);
  852. if($addons_name == 'h5'){
  853. self::handleH5();
  854. }
  855. if($addons_name == 'salary-h5'){
  856. self::handleSalaryH5();
  857. }
  858. } catch (Exception $e) {
  859. SystemVersionSlb::setStatus($slbId, -1);
  860. throw new Exception('从服务器更新失败:'.$e->getMessage());
  861. } finally {
  862. // 移除临时文件
  863. @unlink($tmpFile);
  864. }
  865. }
  866. // 更新状态
  867. SystemVersionSlb::setStatus($slbId, 1);
  868. return true;
  869. }
  870. /**
  871. * 强制降级H5版本
  872. * @return bool
  873. * @throws Exception
  874. */
  875. public static function forceDowngradeH5()
  876. {
  877. $h5_version_file = self::getWorkDir() . 'h5/version';
  878. if (!file_exists($h5_version_file)) {
  879. throw new Exception('h5版本文件不存在');
  880. }
  881. file_put_contents($h5_version_file, '1.0.0');
  882. return true;
  883. }
  884. }