<?php
/**
* @author 龙虾 <15108148@qq.com>
* @version 2.0 2020-08-25
* @copyright 睿谷信息 <www.rgxx.com>
*/
namespace app\admin\controller;
use think\Controller;
use app\admin\model\Upgrade as UpgradeModel;
use Request;
use Db;

class Upgrade extends Base
{
    // 记录列表
    public function index()
    {
        $list = UpgradeModel::order('ver desc')->select()->toArray();
        return $this->fetch('',compact('list'));
    }

    // 外部请求
    public function dataRequest($url,$https=true,$method='get',$data=null)
    {
        if (trim($url) == '') {
            return false;
        }
        //初始化curl
        $ch = curl_init($url);
        //字符串不直接输出，进行一个变量的存储
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        //https请求
        if ($https === true) {
            //确保https请求能够请求成功
            curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
            curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
        }
        //post请求
        if ($method == 'post') {
            curl_setopt($ch,CURLOPT_POST,true);
            curl_setopt($ch,CURLOPT_POSTFIELDS,$data);
        }
        //发送请求
        $str = curl_exec($ch);
        $aStatus = curl_getinfo($ch);
        //关闭连接
        curl_close($ch);
        if(intval($aStatus["http_code"])==200){
            // json数据处理
            return json_decode($str);
            // return $str;
        }else{
            return false;
        }
    }

    // 检测是否有新版本
    public function check_version(){
        // 打开远程版本记录文件比对本地记录文件
        // 设定目录
        $server_dir = 'http://upgrade.rgxx.com/public/update/up_log.txt';
        $local_dir = env("root_path") . 'public'.DS.'update'.DS.'ver.txt';
        $cache_dir = env("root_path") . 'public'.DS.'update'.DS.'cache';
        // 初始化
        if(file_exists($cache_dir)) {
            $del_res = $this->deldir($cache_dir);
            if (empty($del_res)) {
                $result = [
                    'code'=>406,
                    'msg'=>'在线升级初始化失败，请重试',
                    'data'=>''
                ];
                return json($result);
            }
        }
        // 获取版本记录文件
        $server = rg_getfile($server_dir);
        if ($server === false) {
            $result= [
                'code'=>406,
                'msg'=>'服务器版本记录文件获取失败',
                'data'=>''
            ];
        }
        // 最新版本
        $server = explode(",", $server);
        $last_version = end($server);
        // 本地版本
        $local = rg_getfile($local_dir);
        if ($local === false) {
            $result= [
                'code'=>406,
                'msg'=>'本地版本记录文件获取失败',
                'data'=>''
            ];
        } else {
            // 比较版本
            $data = [
                'last_version' =>$last_version,
            ];
            if (intval($last_version) > intval($local)) {
                $result= [
                    'code'=>200,
                    'msg'=>'服务器有新版本',
                    'data'=>$data
                ];
            } else {
                $result= [
                    'code'=>204,
                    'msg'=>'已经是最新版本',
                    'data'=>$data
                ];
            }
        }
        return json($result);
    }

    /**
     * 解压缩
     * @param $file 要解压的文件
     * @param $todir 要存放的目录
     * @return str 包含所有文件及目录的数组
     */
    public function deal_zip($file,$todir)
    {
        if (trim($file) == '') {
            return 406;
        }
        if (trim($todir) == '') {
            return 406;
        }
        $zip = new \ZipArchive;
        // 中文文件名要使用ANSI编码的文件格式
        if ($zip->open($file) === TRUE) {
            //提取全部文件
            $zip->extractTo($todir);
            $zip->close();
            $result = 200;
        } else {
            $result = 406;
        }
        return $result;
    }

    /**
     * 遍历当前目录不包含下级目录
     * @param $dir 要遍历的目录
     * @param $file 要过滤的文件
     * @return str 包含所有文件及目录的数组
     */
    public function scan_dir($dir,$file='')
    {
        if (trim($dir) == '') {
            return false;
        }
        $file_arr = scandir($dir);
        $new_arr = [];
        foreach($file_arr as $item){

            if($item!=".." && $item !="." && $item != $file){
                $new_arr[] = $item;
            }
        }
        return $new_arr;
    }

    /**
     * 合并目录且只覆盖不一致的文件
     * @param $source 要合并的文件夹
     * @param $target 要合并的目的地
     * @return int 处理的文件数
     */
    public function copy_merge($source, $target) {
        if (trim($source) == '') {
            return false;
        }
        if (trim($target) == '') {
            return false;
        }
        // 路径处理
        $source = preg_replace ( '#/\\\\#', DIRECTORY_SEPARATOR, $source );
        $target = preg_replace ( '#\/#', DIRECTORY_SEPARATOR, $target );
        $source = rtrim ( $source, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
        $target = rtrim ( $target, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
        // 记录处理了多少文件
        $count = 0;
        // 如果目标目录不存在，则创建。
        if (! is_dir ( $target )) {
            mkdir ( $target, 0777, true );
            $count ++;
        }
        // 搜索目录下的所有文件
        foreach ( glob ( $source . '*' ) as $filename ) {
            if (is_dir ( $filename )) {
                // 如果是目录，递归合并子目录下的文件。
                $count += $this->copy_merge ( $filename, $target . basename ( $filename ) );
            } elseif (is_file ( $filename )) {
                // 如果是文件，判断当前文件与目标文件是否一样，不一样则拷贝覆盖。
                // 这里使用的是文件md5进行的一致性判断，可靠但性能低。
                if (! file_exists ( $target . basename ( $filename ) ) || md5 ( file_get_contents ( $filename ) ) != md5 ( file_get_contents ( $target . basename ( $filename ) ) )) {
                    copy ( $filename, $target . basename ( $filename ) );
                    $count ++;
                }
            }
        }
        // 返回处理了多少个文件
        return $count;
    }

    /**
     * 遍历删除文件
     * @param $dir 要删除的目录
     * @return bool 成功与否
     */
    public function deldir($dir) {
        if (trim($dir) == '') {
            return false;
        }
        return delete_dir($dir);
    }


    /**
     * 遍历执行sql文件
     * @param $dir 要执行的目录
     * @return bool 成功与否
     */
    public function carry_sql($dir){
        if (trim($dir) == '') {
            return false;
        }
        $sql_file_res = $this->scan_dir($dir);
        if (!empty($sql_file_res)) {
            if(strtolower(config('database.type') == 'sqlite')) {
                $ext_sql = '_sqlite.sql';
            } else {
                $ext_sql = '_mysql.sql';
            }
            foreach ($sql_file_res as $k => $v) {
                if (!empty(strstr($v,$ext_sql))) {
                    $sql_content = file_get_contents($dir.$v);
                    $sql_arr = explode(';', $sql_content);
                    //执行sql语句
                    foreach ($sql_arr as $vv) {
                        if (!empty($vv)) {
                            try{
                                Db::execute($vv.';');
                            } catch (\Exception $e) {
                                continue;
                            }
                           // $sql_res = Db::execute($vv.';');
                           // if (empty($sql_res)) {
                           //      return false;
                           // }
                        }
                    }
                }
            }
        }
        return true;
    }

    /**
     * 下载程序压缩包文件
     * @param $url 要下载的url
     * @param $save_dir 要存放的目录
     * @return res 成功返回下载信息 失败返回false
     */
    function down_file($url, $save_dir) {
        if (trim($url) == '') {
            return false;
        }
        if (trim($save_dir) == '') {
            return false;
        }
        if (0 !== strrpos($save_dir, '/')) {
            $save_dir.= '/';
        }
        $filename = basename($url);
        //创建保存目录
        if (!file_exists($save_dir) && !mkdir($save_dir, 0777, true)) {
            return false;
        }
        //开始下载
        $ch = curl_init();
        $timeout = 5;
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        $content = curl_exec($ch);
        $status = curl_getinfo($ch);
        curl_close($ch);
        // 判断执行结果
        if ($status['http_code'] ==200) {
            $size = strlen($content);
            //文件大小
            $fp2 = @fopen($save_dir . $filename , 'a');
            fwrite($fp2, $content);
            fclose($fp2);
            unset($content, $url);
            $res = [
                'status' =>$status['http_code'] ,
                'file_name' => $filename,
                'save_path' => $save_dir . $filename
            ];
        } else {
            $res = false;
        }
        return $res;
    }

    // 在线更新
    public function system_update(){
        // 有效期核验
        // 查询用户身份标识并请求接口
        // $Info = new Info();
        // $sn = $Info
        // ->where('id', 1)
        // ->field('sn')
        // ->find();
        // $data = [
        //     'sn'=>$sn->sn
        // ];
        $data = [];
        // 访问服务器判断有效期：
        // $res = $this -> dataRequest('http://upgrade.rgxx.com/upgrade/getlist',false,'post',$data);
        $res = true;
        // if ($res->data === false) {
        if ($res === false) {
            // 不在有效期
            $result = [
                'code'=>401,
                'msg'=>'已过服务有效期',
                'data'=>''
            ];
        } else {
            // 有效期内 开始更新
            // 设定目录
            // 根目录
            $base_dir = env("root_path");
            // 服务器更新路径
            $update_res = 'http://upgrade.rgxx.com/public/update/';
            // 本地更新路径
            $local_up_dir = $base_dir.'public'.DS.'update'.DS;
            // 本地缓存路径
            $path = $base_dir . 'public'.DS.'update'.DS.'cache';
            // 没有就创建
            if(!is_dir($path)){
                mkdir(iconv("UTF-8", "GBK", $path),0777,true);
            }
            // 设定缓存目录名称
            $cache_dir = $path.DS;
            // 初始化
            if(file_exists($cache_dir)) {
                $del_res = $this->deldir($cache_dir);
                if (empty($del_res)) {
                    $result = [
                        'code'=>406,
                        'msg'=>'在线升级初始化失败，请重试',
                        'data'=>''
                    ];
                    return json($result);
                }
            }
            // 看看需要下载几个版本的压缩包
            // 服务器更新日志存放路径
            $server = rg_getfile($update_res.'up_log.txt');
            if ($server === false) {
                $result = [
                    'code'=>406,
                    'msg'=>'服务器更新日志获取失败',
                    'data'=>''
                ];
            }else{
                // 版本记录
                $server = explode(",", $server);
                $local = rg_getfile($local_up_dir.'ver.txt');
                if ($local === false) {
                    $result = [
                        'code'=>406,
                        'msg'=>'本地更新日志获取失败',
                        'data'=>''
                    ];
                } else {
                    // 循环比较是否需要下载 更新
                    foreach ($server as $key => $value) {
                        if ($local < $value) {
                            // 获取更新信息
                            // 服务器各个程序包日志存放路径
                            $up_info = rg_getfile($update_res.$value.'/version.txt');
                            // 判断是否存在
                            if ($up_info === false) {
                                $result = [
                                    'code'=>406,
                                    'msg'=>'服务器更新包不存在',
                                    'data'=>''
                                ];
                            } else {
                                // 信息以json格式存储便于增减和取值 故解析json对象
                                $up_info = json_decode($up_info);
                                // 下载文件
                                $back = $this->down_file($up_info->download,$cache_dir);
                                if (empty($back)) {
                                    $result = [
                                        'code'=>406,
                                        'msg'=>'升级程序包下载失败',
                                        'data'=>''
                                    ];
                                } else {
                                    // 创建解压目录
                                    if(!is_dir($cache_dir . $value)){
                                        mkdir(iconv("UTF-8", "GBK", $cache_dir . $value),0777,true);
                                    }
                                    // 下载成功 解压缩
                                    $zip_res = $this->deal_zip($back['save_path'] ,$cache_dir . $value);
                                    // 判断解压是否成功
                                    if ($zip_res == 406) {
                                        $result = [
                                            'code'=>406,
                                            'msg'=>'文件解压缩失败',
                                            'data'=>''
                                        ];
                                    } else {
                                        // 开始更新数据库和文件
                                        // sql文件
                                        // 读取文件内容遍历执行sql
                                        $sql_res = $this->carry_sql($cache_dir . $value .DS.'sql'.DS);
                                        if ($sql_res === false) {
                                            $result = [
                                                'code'=>406,
                                                'msg'=>'sql文件写入失败',
                                                'data'=>''
                                            ];
                                        } else {
                                            // php文件合并 返回处理的文件数
                                            $file_up_res = $this->copy_merge($cache_dir . $value .DS.'program',$base_dir);
                                            if (empty($file_up_res)) {
                                                $result = [
                                                    'code'=>406,
                                                    'msg'=>'文件移动合并失败',
                                                    'data'=>''
                                                ];
                                            }else{
                                                // 更新完改写网站本地版号
                                                $write_res = file_put_contents($local_up_dir . 'ver.txt', $value);
                                                if (empty($write_res)) {
                                                    $result = [
                                                        'code'=>406,
                                                        'msg'=>'本地更新日志改写失败',
                                                        'data'=>''
                                                    ];
                                                }else{
                                                    $result = [
                                                        'code'=>200,
                                                        'msg'=>'在线升级已完成',
                                                        'data'=>''
                                                    ];
                                                    UpgradeModel::create(['ver' => $value,'desc'=>$up_info->desc,'files'=>$file_up_res],true);
                                                }
                                            }
                                        }
                                    }

                                }
                            }
                        }else{
                            $result = [
                                'code'=>406,
                                'msg'=>'本地已经是最新版',
                                'data'=>''
                            ];
                        }
                    }
                }
                $this->deldir($cache_dir);
            }
        }
        return json($result);
    }
}