* @Copyright (c) 2020~2023 https://www.aieok.com All rights reserved.
* 升级包规定的目录结构
* xxx_版本号.zip(如:xxx_1.0.0.zip)
* |
* |————runtime
* | |
* | |___update.sql(更新脚本) //create table test(id init(11)); create table test2(id init(11));
* | |___rockback.sql(回滚脚本) //drop table test; //drop table test2;
* | |___remove.txt // clear清除目录和文件
* |
* |____php
*
*/
namespace app\admin\controller\system;
use app\common\controller\AdminController;
use think\facade\View;
use think\facade\Request;
use think\facade\Db;
use taoler\com\Api;
use taoler\com\Str;
use taoler\com\Files;
use think\facade\Config;
use think\facade\Log;
use app\common\lib\SqlFile;
use app\common\lib\Zip;
use taoser\SetArr;
class Upgrade extends AdminController
{
protected $root_dir = "../"; //站点代码的根目录
protected $backup_dir = "../runtime/update/backup_dir/"; //备份目录
protected $upload_dir = "../runtime/update/upload_dir/"; //升级包目录
protected $sys_version_num; //当前系统的版本
protected $sys_version = '';
protected $pn = '';
protected $sys = '';
public function __construct()
{
parent::initialize();
$this->sys_version = Config::get('taoler.version');
$this->pn = Config::get('taoler.appname');
$this->sys = $this->getSystem();
}
/** 升级界面 */
public function index()
{ //字符隐藏
$key = Str::func_substr_replace($this->sys['key']);
$sys_base = [
'key' => $key,
'upcheck_url' => $this->sys['upcheck_url'],
'upgrade_url' => $this->sys['upgrade_url'],
];
View::assign('ver_num',$sys_base);
return View::fetch();
}
//设置key
public function key()
{
$data = Request::only(['key']);
if(empty($data['key'])){
return json(['code'=>0,'msg'=>'请填写正确的key']);
}
$res = Db::name('system')->cache('system')->update(['key'=>$data['key'],'id'=>1]);
if($res){
$res = ['code'=>0,'msg'=>'保存成功'];
} else {
$res = ['code'=>-1,'msg'=>'保存失败'];
}
return json($res);
}
//修改key
public function keyedit()
{
$key = Db::name('system')->field('key,upcheck_url,upgrade_url')->find(1);
if(Request::isAjax()){
$data = Request::only(['key','upcheck_url','upgrade_url']);
if(empty($data['key'])){
return json(['code'=>-1,'msg'=>'请正确填写申请到的key']);
}
$res = Db::name('system')->cache('system')->update(['key'=>$data['key'],'upcheck_url'=>$data['upcheck_url'],'upgrade_url'=>$data['upgrade_url'],'id'=>1]);
if($res){
$res = ['code'=>0,'msg'=>'修改成功'];
} else {
$res = ['code'=>-1,'msg'=>'修改失败'];
}
return json($res);
}
View::assign('key',$key);
return View::fetch();
}
//升级前的版本检测
public function check()
{
$cy = Api::urlPost($this->sys['base_url'],['u'=>$this->sys['domain']]);
if($cy->code == 0 && $cy->level !== $this->sys['clevel']){
Db::name('system')->cache('system')->update(['clevel'=>$cy->level,'id'=>1]);
}
$versions = Api::urlPost($this->sys['upcheck_url'],['pn'=>$this->pn,'ver'=>$this->sys_version]);
// 判断服务器状态
$version_code = $versions->code;
if($version_code == -1){
$res = json(['code'=>$version_code,'msg'=>$versions->msg]);
}
if($version_code == 1){
$res = json(['code'=>$version_code,'msg'=>$versions->msg,'data'=>['version'=>$versions->version,'upnum'=>$versions->up_num,'info'=>$versions->info]]);
}
if($version_code == 0){
$res = json(['code'=>$version_code,'msg'=>$versions->msg]);
}
return $res;
}
/**
* 在线更新
* 1.提交接口2.下载数据包3.备份4升级
*/
public function upload()
{
$versions = Api::urlPost($this->sys['upgrade_url'],['url'=>$this->sys['domain'],'key'=>$this->sys['key'],'pn'=>$this->pn,'ver'=>$this->sys_version]);
Log::channel('update')->info('update:{type} {progress} {msg}',['type'=>'check','progress'=>'0%','msg'=>'===>升级检测开始===>']);
//判断服务器状态
$version_code = $versions->code;
if($version_code == -1){
Log::channel('update')->info('update:{type} {progress} {msg}',['type'=>'check eroor','progress'=>'5%','msg'=>'===>服务器链接失败===>']);
return json(['code'=>$version_code,'msg'=>$versions->msg]);
}
$version_num = $versions->version;
$file_url = $versions->src;
//判断远程文件是否可用存在
$header = get_headers($file_url, true);
if(!isset($header[0]) && (strpos($header[0], '200') || strpos($header[0], '304'))){
return json(['code'=>-1,'msg'=>'获取远程文件失败']);
}
$upload_dir = Files::getDirPath($this->upload_dir); //拼接升级文件暂存路径
Files::mkdirs($upload_dir);
$package_file = $upload_dir.'taoler_'.$version_num.'.zip'; //本地zip文件
$cpfile = copy($file_url,$package_file); //把远程文件放入本地
if(!$cpfile) {
return json(['code'=>-1,'msg'=>'下载升级文件失败']);
}
//记录下日志
Log::channel('update')->info('update:{type} {progress} {msg}',['type'=>'success','progress'=>'20%','msg'=>'上传升级包'.$version_num.'成功!']);
//升级前备份代码
$ex = array('.git','.idea','runtime','data','addons','config','extend','mysql','public','vendor','view'); // 排除备份目录
$this->backFile($this->root_dir,$this->backup_dir,$ex);
//执行升级
$updateRes = $this->execute_update($package_file);
$upDate = $updateRes->getData();
if($upDate['code'] == -1){
return json(['code'=>-1,'msg'=>$upDate['msg']]);
}
//清除
Files::delDirAndFile($this->upload_dir);
Files::delDirAndFile($this->backup_dir);
//清除废弃目录和文件
$delFiles = '../runtime/remove.txt';
if(file_exists($delFiles)){
$str = file_get_contents($delFiles); //将整个文件内容读入到一个字符串中
$str = str_replace("\r\n",",",$str);
$delArr = explode(',',$str);
foreach($delArr as $v){
if(is_dir($v)){
//删除文件夹
Files::delDirAndFile($v.'/',true);
} else {
//删除文件
if(file_exists($v)){
unlink($v);
}
}
}
unlink($delFiles);
}
//更新系统的版本号了
//更新php的版本号了(应该跟svn/git的版本号一致)
//更新数据库的版本号了(应该跟svn/git的版本号一致)
$value = [
'version' => $version_num
];
$res = SetArr::name('taoler')->edit($value);
if(!$res){
return json(['code'=>-1,'msg'=>'代码更新成功,但版本写入失败']);
}
Log::channel('update')->info('update:{type} {progress} {msg}',['type'=>'success','progress'=>'100%','msg'=>'升级成功!']);
//清理缓存
$this->clearSysCache();
return json(['code'=>0,'msg'=>'升级成功']);
}
/**
* 升级执行
* @param string $package_file
* @return \think\response\Json
* @throws \Exception
*/
private function execute_update(string $package_file)
{
// 1.解压 zip文件有密码的话需要解密
$zip = new Zip;
$zipDir = strstr($package_file, '.zip',true); //返回文件名后缀前的字符串
$zipPath = Files::getDirPath($zipDir); //转换为带/的路径 压缩文件解压到的路径
$unzip_res = $zip->unzip($package_file,$zipPath);
if(!$unzip_res) {
return json(['code'=>-1,'msg'=>'解压失败']);
}
//解压成功,得到文件夹
//$package_name = str_replace('.zip','',$package_file);
Log::channel('update')->info('update:{type} {progress} {msg}',['type'=>'success','progress'=>'50%','msg'=>'升级文件解压成功!']);
// 2.升级sql操作
$upSql = $zipPath.'runtime/update.sql';
if(file_exists($upSql)) {
try{
SqlFile::dbExecute($upSql);
} catch (\Exception $e){
return json(['code'=>-1,'msg'=>$e->getMessage()]);
}
//删除sql语句
unlink($upSql);
}
// 3.升级PHP
if(is_dir($zipPath)) {
//升级前的写入文件权限检查
$allUpdateFiles = Files::getAllFile($zipPath);
if (empty($allUpdateFiles)) return json(['code' => -1, 'msg' => '无可更新文件。']);
$checkString = '';
foreach ($allUpdateFiles as $updateFile) {
$coverFile = ltrim(str_replace($zipPath, '', $updateFile), DIRECTORY_SEPARATOR);
$dirPath = dirname('../'.$coverFile);
if (file_exists('../'.$coverFile)) {
if (!is_writable('../'.$coverFile)) $checkString .= $coverFile . ' [' . '无写入权限' . ']
';
} else {
if (!is_dir($dirPath)) @mkdir($dirPath, 0777, true);
if (!is_writable($dirPath)) $checkString .= $dirPath . ' [' . '无写入权限' . ']
';
}
}
if (!empty($checkString)) return json(['code' => -1, 'msg' => $checkString]);
$cpRes = Files::copyDirs($zipPath,$this->root_dir);
$cpData = $cpRes->getData();
//更新失败
if($cpData['code'] == -1) {
//数据库回滚
/*
if(file_exists($this->upload_dir.'/'.$package_file.'/mysql/mysql_rockback.sql'))
{
$this->database_operation($this->upload_dir.'/'.$package_file.'/mysql/mysql_rockback.sql');
}
*/
//php代码回滚 升级前备份的代码
Files::copydirs($this->backup_dir, $this->root_dir);
return json(['code'=>-1,'msg'=>$cpData['msg']]);
}
Log::channel('update')->info('update:{type} {progress} {msg}',['type'=>'success','progress'=>'70%','msg'=>'升级文件执行成功!']);
}
return json(['code'=>0,'msg'=>'升级文件执行成功']);
}
/**
* 手动处理升级包上传
*/
public function uploadZip()
{
$files = request()->file('file');
$mime = $files->getMime();
if($mime !== 'application/zip'){
return json(['code'=>-1,'msg'=>'文件类型不对']);
}
$name = $files->getOriginalName();
//校验后缀
$ext = pathinfo($name,PATHINFO_EXTENSION); //文件后缀
if($ext != 'zip')
{
return json(['code'=>-1,'msg'=>'上传文件格式不对']);
}
//对比版本号
$fname = pathinfo($name,PATHINFO_FILENAME); //无后缀文件名
$version_num = array_pop(explode('_',$fname));
$verRes = version_compare($version_num,$this->sys_version,'>');
if(!$verRes){
return json(['code'=>-1,'msg'=>'不能降级,请选择正确版本']);
}
$upDir = $this->upload_dir.$fname;
//$mv = $files->move('/../tmp/web/upload_dir',$version_num);
$mfile = move_uploaded_file($files,$upDir);
if(!$mfile)
{
return json(['code'=>0,'msg'=>'上传文件失败']);
}
//升级前备份代码
$ex = array('.git','.idea','runtime','data','addons','config','extend'); // 排除备份文件夹
$this->backFile($this->root_dir,$this->backup_dir,$ex);
//执行升级
$upres = $this->execute_update($mfile);
//更新版本
//Db::name('system')->update(['sys_version_num'=>$version_num,'id'=>1]);
$value = [
'version' => $version_num
];
$res = SetArr::name('taoler')->edit($value);
if($res == false){
return json(['code'=>-1,'msg'=>'代码更新成功,但版本写入失败']);
}
return json(['code'=>0,'msg'=>'升级成功']);
}
/**
* 备份
* @param string $dir
* @param string $backDir
* @param array $ex
* @return \think\response\Json
*/
public function backFile(string $dir,string $backDir,array $ex)
{
$backRes = Files::copydirs($dir, $backDir, $ex);
$backData = $backRes->getData();
if($backData['code'] == -1){
Log::channel('update')->info('update:{type} {progress} {msg}',['type'=>'error','progress'=>'25%','msg'=>'备份失败!']);
return json(['code'=>-1,'msg'=>$backRes['msg']]);
}
Log::channel('update')->info('update:{type} {progress} {msg}',['type'=>'success','progress'=>'30%','msg'=>'执行文件备份成功!']);
}
}