Слияние кода завершено, страница обновится автоматически
<?php
// +----------------------------------------------------------------------
// | Bwsaas
// +----------------------------------------------------------------------
// | Copyright (c) 2015~2020 http://www.buwangyun.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Gitee ( https://gitee.com/buwangyun/bwsaas )
// +----------------------------------------------------------------------
// | Author: buwangyun <hnlg666@163.com>
// +----------------------------------------------------------------------
// | Date: 2020-9-28 10:55:00
// +----------------------------------------------------------------------
namespace buwang\service;
use buwang\util\File;
use buwang\util\Sql;
use think\Exception;
use think\facade\Db;
use buwang\util\Http;
use buwang\exception\MiniappException;
use think\facade\Log;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use Alchemy\Zippy\Zippy;
use app\common\model\Miniapp;
use app\common\model\MiniappModule;
use app\manage\model\AuthGroup;
use app\manage\model\AuthGroupAccess;
use app\manage\model\AuthGroupNode;
use app\manage\model\AuthNode;
use app\common\model\MemberMiniapp;
use app\manage\model\Member;
use app\manage\model\Config;
use app\manage\model\ConfigTab;
use app\manage\model\ConfigGroup;
use app\manage\model\ConfigGroupData;
use buwang\traits\ErrorTrait;
/**
* 应用更新服务类
* Class PluginService
* @package buwang\service
*/
class MiniappUpdateService
{
use ErrorTrait;
protected $version = "1.0.0"; //初始化版本
protected $domain = ""; //云接口域名
protected $download_url = ""; //应用更新下载路由接口
protected $info_url = ""; //获取最新应用信息路由
protected $local_cofig = []; //原配置安装文件
protected $local_group_data = []; //原组合数据安装文件
protected $temp_path = '';//临时文件目录
protected $local_file = '';//本地包路径
protected $ds = '';//路径分割
protected $root_path = '';//根路径
public function __construct($data = [])
{
//初始化配置信息
$miniapp_config = config('miniapp'); //得到应用云配置
$miniapp_config = array_merge($miniapp_config, $data); //应用配置
$this->domain = $miniapp_config['domain'];
$this->download_url = $miniapp_config['download'] ?? '';
$this->info_url = $this->domain . $miniapp_config['info'];
$this->ds = DS;
$this->root_path = ROOT_PATH;
$this->temp_path = "{$this->root_path}{$this->ds}runtime{$this->ds}";
}
/**
* 检查并获取更新信息
*/
public function info($dir, $check_local = true)
{
//得到远程最新版本信息
$remote_version = $this->getRemoteVersion($dir);
//远程库是否有可供下载的文件
if (!$remote_version['data']['data'] || !$remote_version['data']['data']['have_app_file']) throw new MiniappException('云端暂无该应用的更新库,无需更新');
//得到当前应用配置
$local_version = MiniappService::getVersion($dir);//应用本地版本
$this->version = $local_version['version'];
if (empty($local_version)) throw new MiniappException('未找到应用配置');
//比较版本差异
if ($this->compareVersion($this->version, $remote_version['data']['data']['version']) >= 0) throw new MiniappException('当前已是最新版本,无需更新。');
//拼装返回数据
$remote_version['data']['data']['local_package'] = false;
//检测是否有本地包
if ($check_local) $remote_version['data']['data']['local_package'] = $this->checkLocalPackage($dir);
return $remote_version['data']['data'];
}
public function cover($dir)
{
//查询应用
$miniapp = Miniapp::where('dir', $dir)->find();
if (!$miniapp) throw new MiniappException('请先进行应用的安装');
//得到文件覆盖前本地原项目配置和组合配置文件
$this->local_cofig = MiniappService::getAppConfig($dir);//配置
$this->local_group_data = MiniappService::getAppData($dir);//组合数据
//TODO: 2021/5/6 变动,如果存在本地包,优先本地包
//①检测本地包是否存在
$remote_version_info = $this->checkLocalPackage($dir);
if ($remote_version_info) {
//②存在本地包获取本地包
$tempFile = $this->local_file;
} else {
//③不存在本地包获取远程包
//得到最新远程下载信息
$remote_version_info = $this->info($dir, false);
//获取远程文件
$version = $remote_version_info['version'];
$this->download_url = $remote_version_info['download_url'];
$tempDirectory = "{$this->temp_path}app_update_depository{$this->ds}{$dir}{$this->ds}{$version}";
$tempFile = "{$tempDirectory}{$this->ds}src.zip";//本地临时文件生成地址
$this->download($this->download_url, $tempFile);
}
//将最新项目替换掉当前项目
$zippy = Zippy::load();
$archive = $zippy->open($tempFile);
$cover_path = "{$this->root_path}{$this->ds}app{$this->ds}";
$archive->extract($cover_path); //执行项目文件覆盖
unset($archive);//释放占用内存
$this->coverStaticResource($dir);//执行覆静态资源盖
//执行应用数据增量更新
$this->updateDatabase($dir, true);
//调用应用钩子事件
event('MiniappUpdate', [$dir, $remote_version_info]);
return true;
}
/**
* 执行应用数据增量更新
*/
public function updateDatabase($dir, $trans = false)
{
if ($trans) {
Db::startTrans();
}
try {
//执行更新sql(如果存在update.sql的话)
// MiniappService::runSQL($dir, 'install', 'update');弃置
$this->exeUpdateSql($dir);//执行更新sql
//执行节点更新(如果存在节点)
//定义一个常量,菜单使用
define('MINIAPP_DIR', $dir);
$this->updateGroupNode($dir);
// 执行配置和组合数据更新(分类节点修复)
$this->updateConfigAndGroupData($dir);
if ($trans) {
Db::commit();
}
} catch (\Exception $e) {
if ($trans) {
Db::rollback();
}
throw new MiniappException($e->getMessage());
}
}
/**得到应用云端最新版本信息
* @param $dir
* @return bool|string
*/
public function getRemoteVersion($dir)
{
try {
$res = Http::getBwRequest($this->info_url, ['dir' => $dir]);
} catch (\Exception $e) {
throw new MiniappException($e->getMessage());
}
return $res;
}
/**版本比较 1 本地版本高于远程版本,0 本地版本 等于远程版本,-1 本地版本小于远程版本
* @param $local_version
* @param $remote_version
* @return bool
*/
public function compareVersion($local_version, $remote_version)
{
//正则成统一格式
$local_version = $this->versionFormat($local_version);
$remote_version = $this->versionFormat($remote_version);
//比较大小
return bccomp($local_version, $remote_version);
}
/**
* 版本号格式化
*/
public function versionFormat($versionString)
{
$versionString = preg_replace("([a-zA-Z:=\"\']+)", "", strtolower($versionString));
if (!$versionString) throw new MiniappException('版本号格式异常,请检查配置');
$normal_num = 2;//点个数
$point_num = substr_count($versionString, "."); //点数量
if ($point_num > $normal_num) throw new MiniappException('版本号格式异常,版本位数过多');
//补满小数点位数
for ($i = 1; $i <= ($normal_num - $point_num); $i++) {
$versionString .= '.00';
}
$versionString = explode('.', $versionString);//字符串分割数组
foreach ($versionString as &$string) {
$len = strlen($string);
if (strlen($string) > 2) throw new MiniappException('版本号格式异常,请检查配置');
$string = str_pad($string, 3 - $len, '0', STR_PAD_LEFT);
}
return implode("", $versionString);
}
public function download($url, $file)
{
if (!is_dir(dirname($file))) {
if (!make_dir(dirname($file))) {
Log::error(dirname($file) . '无法创建目录,请检查文件写入权限。');
throw new MiniappException('无法创建目录,请检查文件写入权限。');
}
}
$fp = fopen($file, 'w+');
if ($fp === false) {
Log::error('无法保存文件,请检查文件写入权限。');
throw new MiniappException('无法保存文件,请检查文件写入权限。');
}
try {
$client = new Client([
'verify' => false,
'stream' => true,
]);
$response = $client->get($url);
$body = $response->getBody();
while (!$body->eof()) {
fwrite($fp, $body->read(1024));
}
fclose($fp);
} catch (\Exception $e) {
throw new MiniappException('云端暂无更新文件,请联系官方客服或使用【本地包】进行更新!');
}
return $file;
}
/**
* 得到本地应用sql文件内容(字符串)
*/
public function getLocalSql(string $name = '', string $dir = 'install', string $sqlName = 'install')
{
$sql_file = base_path($name) . DS . "{$dir}" . DS . "{$sqlName}.sql";
if (!file_exists($sql_file)) return '';
$sql_statement = Sql::getSqlFromFile($sql_file);
if (!empty($sql_statement)) {
//修改表前缀
$table_prefix = config('database.connections.mysql.prefix');
if (!empty($table_prefix)) {
$sql_statement = str_replace('bw_', $table_prefix, $sql_statement);
}
}
return $sql_statement;
}
/**得到本地安装菜单文件
* @param $name
* @return array|mixed
*/
public function getLocalGroup($name)
{
$path = base_path($name) . DS . "install" . DS . "menu.php";
if (!file_exists($path)) return [];
$data = include $path; //得到角色组和菜单节点树
if (empty($data)) return [];
return $data;
}
/**更新菜单节点数据
* @param string $name
*/
public function updateGroupNode(string $name)
{
//得到安装节点数据
$data = $this->getLocalGroup($name);
if (!$data) return true;
//获取所有应用
$miniapps = array_flip(Miniapp::column('dir', 'id')); //以目录名为key 应用id为value的数组
//待插入应用功能
$modules = [];
foreach ($data as $item) {
//遍历最外层 = 角色组
//TODO 参数验证
//组装角色组数据
//角色记录
$searchParam = [
//角色唯一标识,不可重复
'group_name' => $name . '_' . $item['group_name'], //生成规则:应用目录名 + ‘_’+ 角色组名
//登陆类型 admin=总后台,member=租户后台
'scopes' => 'member', //目前应用属于租户,则角色组只可能是租户后台的,即member
'type' => 'app',//角色组类型:system=系统,app=应用,plugin=插件
'app_name' => $name,//应用名称 取目录名
];
$groupParam = $searchParam;
$groupParam['name'] = $item['name'];
$groupParam['remark'] = $item['remark'] ?: '';
//判断角色是否存在(标识,范围,应用名一致的角色组),不存在则作插入操作
$group = AuthGroup::where($searchParam)->find();
if (!$group) $group = AuthGroup::create($groupParam); //创建角色组
if (!$group) throw new Exception("{$item['name']}角色导入失败");
//应用模块记录 如果应用没有安装进表中则抛错
if (!isset($miniapps[$name])) throw new Exception("{$name}应用不存在");
//查询是否有该功能表,如果没有则插入
$module = MiniappModule::where('group_id', $group['id'])->where('miniapp_id', $miniapps[$name])->find();
if (!$module) {
$modules[] = [
'group_id' => $group['id'], //角色组id
'miniapp_id' => $miniapps[$name], //系统应用id
'name' => $item['name'], //角色名称
'type' => $item['module_type'] ?: '1', //是否是基础功能1:基础功能,2:附加功能
'price' => $item['module_price'] ?: 0, //模块价格
'desc' => $item['remark'] ?: '', //描述
];
}
//查询该角色拥有的全部节点
$node_ids = AuthGroupNode::where('group_id', $group['id'])->where('type', 'app')->column('node_id');
$node_ids = implode(",", $node_ids);
//删除该角色所有节点
if ($node_ids) AuthNode::whereRaw("id IN({$node_ids})")->where('type', 'app')->where('app_name', $name)->delete();
//删除该角色所有中间表数据
AuthGroupNode::where('group_id', $group['id'])->where('type', 'app')->delete();
//插入当前应用的节点
//如果角色有节点则插入节点
if (isset($item['nodes']) && !empty($item['nodes'])) MiniappService::importNodeData($name, $group, $item['nodes']); //目录名 ,角色组,菜单权限节点树
}
if ($modules) {
$miniappModule = new MiniappModule();
$miniappModule->saveAll($modules);
}
return true;
}
/**
* 更新配置和数据组(废弃,更新不会使用)
*/
public function updateConfigAndGroupDataAbandonment($dir, $trans = false)
{
//得到新版安装文件中的sql
$new_sql = $this->getLocalSql($dir, 'install', 'install');
//得到新增的配置和数据组(原版本差异比较后新增的配置和组合数据)
$new_data = $this->getNeedUpdataConfigAndData($dir);
//正则匹配新增的配置并执行初始化配置插入sql
$this->runConfigSql($new_sql, $new_data);
//对当前线上已经购买该应用的用户执行新增配置的插入(针对不会再触发购买事件的租户)
$this->updataMemberConfigAndGrounpData($dir, $new_data);
}
/**
* 更新配置和数据组修复分类id
*/
public function updateConfigAndGroupData($dir, $trans = false)
{
//跨应用调用model前调用此方法设置应用表前缀
set_miniapp_database_prefix($dir);
if ($trans) {
Db::startTrans();
}
try {
//得到所有应用配置分类
$config_tab_ids = ConfigTab::valiWhere('', null, $dir, false)->where('dir', $dir)->where('scopes', 'member')->column('id', 'tab_name');
//遍历配置分类,给租户插入配置数据
foreach ($config_tab_ids as $tab_name => $tab_id) {
//修正分类id参数
Config::valiWhere('', null, $dir, false)->where('tab_name', '=', $tab_name)->where('dir', $dir)->update(['tab_id' => $tab_id]);
}
$group_ids = ConfigGroup::valiWhere('', null, $dir)->where('dir', $dir)->where('scopes', 'member')->column('id', 'config_name');
//遍历数据组,给租户插入组合数据
foreach ($group_ids as $config_name => $group_id) {
//修正数据组id参数
ConfigGroupData::valiWhere('', null, $dir)->where('config_name', '=', $config_name)->where('dir', $dir)->update(['group_id' => $group_id]);
}
if ($trans) {
Db::commit();
}
} catch (\Exception $e) {
if ($trans) {
Db::rollback();
}
throw new MiniappException("更新配置和组合数据失败:" . $e->getMessage());
}
return true;
}
/**覆盖静态资源
* @param $dir
*/
public function coverStaticResource($dir)
{
//1.拷贝静态资源文件 app/demoApp/install/static/ => public/static/demoApp/
if (file_exists(root_path() . 'app' . DS . $dir . DS . 'install' . DS . 'static' . DS)) {
//拷贝模板到前台模板目录中去 public/static/demoApp/
File::copy_dir(root_path() . 'app' . DS . $dir . DS . 'install' . DS . 'static' . DS, root_path() . 'public' . DS . 'static' . DS . strtolower($dir) . DS);
}
}
/**得到需要更新的组合数据的配置
* @param $dir
*/
public function getNeedUpdataConfigAndData($dir)
{
//得到新增的配置和数据组
$new_config = MiniappService::getAppConfig($dir);//配置
$new_group_data = MiniappService::getAppData($dir);//组合数据
$update_config = $update_group_data = [];
//得到需要新增的配置
if ($new_config && isset($new_config['config_name']) && $new_config['config_name']) {
//遍历寻找差异
$configs = $new_config['config_name'];
foreach ($configs as $config_tab => $config) {
//如果旧系统不存在该配置分类,直接将整个分类配置放入更新中
if (!$this->local_cofig || !isset($this->local_cofig['config_name']) || !$this->local_cofig['config_name'] || !isset($this->local_cofig['config_name'][$config_tab]) || !$this->local_cofig['config_name'][$config_tab]) {
$update_config[$config_tab] = $config;
} else {
//如果存在,则比较该分类下配置的差集
$update_config[$config_tab] = array_diff($config, $this->local_cofig['config_name'][$config_tab]);
//如果不存在差异则不列入更新
if (!$update_config[$config_tab]) unset($update_config[$config_tab]);
}
}
}
//得到需要更新的数据组
if ($new_group_data && isset($new_group_data['config_name']) && $new_group_data['config_name']) {
//原版本不存在组合数据,则直接全部新增
if (!$this->local_group_data || !isset($this->local_group_data['config_name']) || !$this->local_group_data['config_name']) {
$update_group_data = $new_group_data['config_name'];
} else {
//得到需要新增的数据组
$update_group_data = array_diff($new_group_data['config_name'], $this->local_group_data['config_name']);
}
}
return compact('update_config', 'update_group_data');
}
/**正则匹配并执行对应配置和组合数据的初始化sql(方法废弃)
* @param $new_sql
* @param $new_data
*/
public function runConfigSql($new_sql, $new_data)
{
$update_sql = [];
$configs = $new_data['update_config']; //配置文件
$group_datas = $new_data['update_group_data']; //数据组
if ($configs || $group_datas) {
foreach ($new_sql as $sql) {
//正则插入语句匹配
if (preg_match("/^INSERT INTO.*/iUs", $sql)) {
//配置
if ($configs) {
foreach ($configs as $tab => $config) {
//配置分类sql插入
if (stripos($sql, "`bw_sys_config_tab`") && stripos($sql, "`tab_name` = '{$tab}'")) {
$update_sql[] = $sql;
}
//配置文件sql插入
if (stripos($sql, "`bw_sys_config`") && stripos($sql, "`tab_name` = '{$tab}'")) {
$update_sql[] = $sql;
}
}
}
//数据组
if ($group_datas) {
foreach ($group_datas as $group_data) {
//数据组sql插入
if (stripos($sql, "`bw_sys_config_group`") && stripos($sql, "`config_name` = '{$group_data}'")) {
$update_sql[] = $sql;
}
//数据组数据sql插入
if (stripos($sql, "`bw_sys_config_group_data`") && stripos($sql, "`config_name` = '{$group_data}'")) {
$update_sql[] = $sql;
}
}
}
}
}
//执行sql
if ($update_sql) {
//修改表前缀
$table_prefix = config('database.connections.mysql.prefix');
if (!empty($table_prefix)) {
$update_sql = str_replace('bw_', $table_prefix, $update_sql);
}
foreach ($update_sql as $value) {
try {
Db::execute($value);
} catch (\Exception $e) {
throw new MiniappException($e->getMessage());
}
}
}
}
return true;
}
/**对当前线上已经购买该应用的用户执行新增配置的插入(针对不会再触发购买事件的租户)
* @param $dir
* @param $new_data
*/
public function updataMemberConfigAndGrounpData($dir, $new_data, $trans = false)
{
$member_ids = $this->getMemberIdsByApp($dir);
$config = $new_data['update_config'];//需要插入的配置数据
$config_data = $new_data['update_group_data']; //需要插入的组合数据
$config_insert_list = [];//添加的配置数据
$group_data_insert_list = [];//添加的组合数据
$config_tabs = [];
$group_names = [];
//增量更新的配置项
$update_config = [];
//配置
if ($config) { //判断应用是否存在配置数据
//如果存在配置数据,进行租户插入逻辑
$config_names = $config;//得到所有的配置组 (二维数组 , 一维下标是配置组标识 ,二维是配置组下的每一个配置标识)
//检查配置分类是否有缺少
$config_tabs = array_keys($config_names); //得到所有配置分类标识数组
//判断 当前应用 安装配置文件中所记录的配置标识数量,是否与实际存在于数据库中的应用配置组数量一致 ,如果不一致,说明应用sql执行错误,数据库与实际应安装配置分类不一致。
$config_tab_ids = ConfigTab::where('tab_name', 'in', $config_tabs)->where('dir', 'null')->where('scopes', 'member')->column('id', 'tab_name');
if (count($config_tabs) != count($config_tab_ids)) throw new MiniappException("与版本更新期望参数不符:在bw_config.php中配置分类数量与实际安装参数不一致");
//遍历配置分类,给租户插入配置数据
foreach ($config_tab_ids as $tab_name => $tab_id) {
//检测该配置分类是否与配置文件中所写的分类一致
if (!$config_names[$tab_name]) throw new MiniappException("与版本更新期望参数不符:未在bw_config.php中{$tab_name}配置分类下写入配置");
//得到该分类下 应用安装sql 执行后插入的那些 初始化配置,并比对与配置文件中所写的那些配置是否一致
$config_list = Config::where('member_id', 0)->where('dir', 'null')->where('scopes', 'member')->where('config_name', 'in', $config_names[$tab_name])->select()->toArray();
if (count($config_names[$tab_name]) != count($config_list)) throw new MiniappException("与版本更新期望参数不符:在bw_config.php中{$tab_name}配置分类下实际配置数量与实际安装参数不一致");
$update_config = array_merge($update_config, $config_names[$tab_name]);
//组装插入数据
foreach ($config_list as $config_init) {
//组装要插入的 应用初始化配置数据(member_id =0) 和 每个租户的配置数据
unset($config_init['id']);
$config_init['member_id'] = 0; //初始化配置的member_id=0
$config_init['tab_id'] = $tab_id;
$config_init['dir'] = $dir;
//插入初始化数据
$config_insert_list[] = $config_init;
//给每个租户插入配置
foreach ($member_ids as $member_id) {
$config_init['member_id'] = $member_id;
$config_insert_list[] = $config_init;
}
}
}
}
//组合数据
if ($config_data) { //判断应用是否存在组合数据
//如果存在组合数据,进行租户插入逻辑
$group_names = $config_data;//得到所有的数据组标识数组 (一维数组 , value是数据组标识 )
//根据数据组标识,得到该应用所有 安装sql 执行后插入的那些 初始化组合数据,并比对与配置文件中所写的那些数据组是否一致
$group_ids = ConfigGroup::where('config_name', 'in', $group_names)->where('dir', 'null')->where('scopes', 'member')->column('id', 'config_name');
if (count($group_names) != count($group_ids)) throw new MiniappException("与版本更新期望参数不符:在bw_data.php中组合数据数量与实际安装参数不一致");
//遍历数据组,给租户插入组合数据
foreach ($group_ids as $config_name => $group_id) {
//得到该数据组的全部初始化组合数据(member_id=0的数据)
$group_data_list = ConfigGroupData::where('member_id', 0)->where('dir', 'null')->where('scopes', 'member')->where('config_name', '=', $config_name)->select()->toArray();
//组装插入数据
foreach ($group_data_list as $group_data) {
//组装要插入的 应用初始化组合数据(member_id =0) 和 每个租户的组合数据
unset($group_data['id']);
$group_data['member_id'] = 0; //初始化组合数据的member_id=0
$group_data['group_id'] = $group_id;
$group_data['dir'] = $dir;
//插入初始化组合数据
$group_data_insert_list[] = $group_data;
//给每个租户插入组合数据
foreach ($member_ids as $member_id) {
$group_data['member_id'] = $member_id;
$group_data_insert_list[] = $group_data;
}
}
}
}
if ($trans) {
Db::startTrans();
}
$res = $res1 = $res2 = $res3 = $res4 = true;
try {
//如果应用存在需要插入的配置分类
if ($config_insert_list) {
if ($config_tabs) {
//执行配置插入前,先删除安装sql插入的初始化配置(如果有),和该应用所有租户配置(如果有)
Config::where('tab_name', 'in', $config_tabs)->where('config_name', 'in', $update_config)->where('dir', $dir)->delete();
Config::where('tab_name', 'in', $config_tabs)->where('config_name', 'in', $update_config)->where('dir', 'null')->delete();
}
//执行配置插入
$configModel = new Config;
$res2 = $configModel->saveAll($config_insert_list);
if ($config_tabs) {
//更新配置分类dir标识为当前应用标识
ConfigTab::where('tab_name', 'in', $config_tabs)->where('dir', 'null')->update(['dir' => $dir]);
Config::where('tab_name', 'in', $config_tabs)->where('dir', 'null')->update(['dir' => $dir]);
}
}
//如果应用存在需要插入的组合数据
if ($group_data_insert_list) {
if ($group_names) {
//执行组合数据插入前,先删除安装sql插入的初始化组合数据(如果有),和该应用所有租户组合数据(如果有)
ConfigGroupData::where('config_name', 'in', $group_names)->where('dir', $dir)->delete();
ConfigGroupData::where('config_name', 'in', $group_names)->where('dir', 'null')->delete();
}
//执行组合数据插入
$groupDataModel = new ConfigGroupData;
$res4 = $groupDataModel->saveAll($group_data_insert_list);
if ($group_names) {
//更新数据组dir标识为当前应用标识
ConfigGroup::where('config_name', 'in', $group_names)->where('dir', 'null')->update(['dir' => $dir]);
ConfigGroupData::where('config_name', 'in', $group_names)->where('dir', 'null')->update(['dir' => $dir]);
}
}
$res = $res && $res1 && $res2 && $res3 && $res4 && true;
if ($res) {
if ($trans) {
Db::commit();
}
} else {
if ($trans) {
Db::rollback();
}
}
} catch (\Exception $e) {
if ($trans) {
Db::rollback();
}
throw new MiniappException($e->getMessage());
}
if (!$res) throw new MiniappException("更新配置和组合数据失败");
return $res;
}
/**
* 执行数据库脚本(通过sql语句集字符串)
* @param string $name 应用目录
* @param string $dir 数据库脚本所在目录,默认install
* @param string $sqlName 数据库脚本名称,默认install
* @return bool
* @throws Exception
*/
public function runSqlByStr(string $sql = '', string $dir = '', $have_member = false, $error = false)
{
if ($sql) {
// 分割语句
$sql_statement = Sql::parseSql($sql, false, []);
if (!empty($sql_statement)) {
//修改表前缀
$table_prefix = config('database.connections.mysql.prefix');
if (!empty($table_prefix)) {
$sql_statement = str_replace('bw_', $table_prefix, $sql_statement); //替换前缀 兼容老版本
$sql_statement = str_replace('__BWPREFIX__', $table_prefix, $sql_statement); //替换前缀
$sql_statement = str_replace('__BWNAME__', $dir, $sql_statement); //替换应用标识 __BWNAME__
}
foreach ($sql_statement as $value) {
try {
if ($have_member) {
//执行初始化数据
$statement = str_replace('__BWMEMBER__', 0, $value);
$statement = str_replace('__MEMBER_MINIAPP__', 0, $statement);
Db::execute($statement);
//得到已经购买的租户
$member_ids = $this->getMemberIdsByApp($dir);
if ($member_ids) {
//给每个租户执行初始化数据
foreach ($member_ids as $member_id) {
//得到购买的租户应用id
$memberMiniappId = MemberMiniapp::getMemberMiniappIdByDir($dir, $member_id);
if (!$memberMiniappId) throw new \Exception("更新失败:应用{$dir}对应的租户{$member_id}找不到购买数据");
$member_statement = str_replace('__BWMEMBER__', $member_id, $value);//替换执行
$member_statement = str_replace('__MEMBER_MINIAPP__', $memberMiniappId, $member_statement);
Db::execute($member_statement);
}
}
} else {
Db::execute($value);
}
} catch (\Exception $e) {
Log::error("{$dir}应用版本更新执行update.php--语句:[ {$value} ]执行失败,原因:" . $e->getMessage());
if ($error) throw new MiniappException($e->getMessage());
}
}
}
}
return true;
}
/**得到本地更新文件
* @param $name
* @return array|mixed
*/
public function getUpdateFile($name)
{
$path = base_path($name) . DS . "update" . DS . "update.php";
if (!file_exists($path)) return [];
$data = include $path; //得到角色组和菜单节点树
if (empty($data)) return [];
return $data;
}
/**通过应用标识查询当前已购应用到的租户id
* @param $name
*/
public function getMemberIdsByApp($dir)
{
//查询应用
$miniapp = Miniapp::where('dir', $dir)->find();
if (!$miniapp) throw new MiniappException('未找到应用安装记录,请先进行应用的安装');
//得到购买了应用的租户
$member_ids = MemberMiniapp::where('miniapp_id', $miniapp['id'])->column('member_id');
$member_ids_str = implode(",", $member_ids);
//过滤掉不存在的租户
if ($member_ids_str) $member_ids = Member::whereRaw("id IN({$member_ids_str})")->column('id');
return $member_ids;
}
/**解释执行更新sql
* @param $name
*/
public function exeUpdateSql($name)
{
//得到更新文件
$updateFile = $this->getUpdateFile($name);
if (!$updateFile) return true; //没有更新文件需要执行
$commonSql = $extendSql = '';
//得到可直接执行的sql
if (isset($updateFile['common'])) $commonSql = $updateFile['common'];
//得到必须给每个租户执行的sql
if (isset($updateFile['extend'])) $extendSql = $updateFile['extend'];
//执行sql
if ($commonSql) $this->runSqlByStr($commonSql, $name);
if ($extendSql) $this->runSqlByStr($extendSql, $name, true);
}
/** 检测是否存在本地更新包
* @param $localFile 本地文件地址
*/
public function checkLocalPackage($dir, $is_bool = false)
{
//本地包路径:项目根路径/[ 应用标识 ].zip
$this->local_file = "{$this->root_path}{$this->ds}public{$this->ds}{$dir}.zip";
//是否存在本地包文件
if (!file_exists($this->local_file)) return false; //不存在返回
if ($is_bool) return true;
//得到本地包版本信息
$remote_version = $this->getLocalPackgeVersion($this->local_file, "{$this->temp_path}app_update_depository{$this->ds}{$dir}{$this->ds}", $dir);
//本地包完整性检测
if (!$remote_version) throw new MiniappException('更新失败,本地包缺失版本文件');
$this->versionCheck($remote_version, $dir);
//返回
return $remote_version;
}
/**
* 得到本地包的
*/
public function getLocalPackgeVersion($localFile, $tempPath, $dir)
{
$version_file = "{$dir}/config/version.php";
$files = array($version_file); //直接压需要的一部分
$zip = new \ZipArchive;
$res = $zip->open($localFile);
if ($res === TRUE) {
$zip->extractTo($tempPath, $files);
$zip->close();
$miniapp_config = "{$tempPath}/{$files[0]}";
$config = [];
if (is_file($miniapp_config)) {
$config = include $miniapp_config;
}
@unlink($miniapp_config);
return $config;
} else {
throw new MiniappException('本地包未找到应用配置');
}
}
public function versionCheck($remote_version, $dir)
{
//得到当前应用配置
$local_version = MiniappService::getVersion($dir);//应用本地版本
$this->version = $local_version['version'];
if (empty($local_version)) throw new MiniappException('未找到应用配置');
//比较版本差异
if ($this->compareVersion($this->version, $remote_version['version']) >= 0) throw new MiniappException('更新包版本低于当前应用版本,无需更新。');
//返回
return $remote_version;
}
}
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )