UpgradeLogic.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | likeshop100%开源免费商用商城系统
  4. // +----------------------------------------------------------------------
  5. // | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
  6. // | 开源版本可自由商用,可去除界面版权logo
  7. // | 商业版本务必购买商业授权,以免引起法律纠纷
  8. // | 禁止对系统程序代码以任何目的,任何形式的再发布
  9. // | gitee下载:https://gitee.com/likeshop_gitee
  10. // | github下载:https://github.com/likeshop-github
  11. // | 访问官网:https://www.likeshop.cn
  12. // | 访问社区:https://home.likeshop.cn
  13. // | 访问手册:http://doc.likeshop.cn
  14. // | 微信公众号:likeshop技术社区
  15. // | likeshop团队 版权所有 拥有最终解释权
  16. // +----------------------------------------------------------------------
  17. // | author: likeshopTeam
  18. // +----------------------------------------------------------------------
  19. namespace app\adminapi\logic\settings\system;
  20. use app\common\logic\BaseLogic;
  21. use think\facade\Cache;
  22. use think\facade\Db;
  23. use think\facade\Log;
  24. use Requests;
  25. /**
  26. * 升级逻辑
  27. * Class UpgradeLogic
  28. * @package app\adminapi\logic\settings\system
  29. */
  30. class UpgradeLogic extends BaseLogic
  31. {
  32. const BASE_URL = 'https://server.likeshop.cn';
  33. /**
  34. * @notes 格式化列表数据
  35. * @param $lists
  36. * @return array
  37. * @author 段誉
  38. * @date 2021/8/14 17:18
  39. */
  40. public static function formatLists($lists, $pageNo)
  41. {
  42. $localData = local_version();
  43. $localVersion = $localData['version'];
  44. foreach ($lists as $k => $item) {
  45. //版本描述
  46. $lists[$k]['version_str'] = '';
  47. $lists[$k]['able_update'] = 0;
  48. if ($localVersion == $item['version_no']) {
  49. $lists[$k]['version_str'] = '您的系统当前处于此版本';
  50. }
  51. if ($localVersion < $item['version_no']) {
  52. $lists[$k]['version_str'] = '系统可更新至此版本';
  53. $lists[$k]['able_update'] = 1;
  54. }
  55. //最新的版本号标志
  56. $lists[$k]['new_version'] = 0;
  57. //注意,是否需要重新发布描述
  58. $lists[$k]['notice'] = [];
  59. if ($item['uniapp_publish'] == 1) {
  60. $lists[$k]['notice'][] = '更新至当前版本后需重新发布前端商城';
  61. }
  62. if ($item['pc_admin_publish'] == 1) {
  63. $lists[$k]['notice'][] = '更新至当前版本后需重新发布PC管理后台';
  64. }
  65. if ($item['pc_shop_publish'] == 1) {
  66. $lists[$k]['notice'][] = '更新至当前版本后需重新发布PC商城端';
  67. }
  68. //处理更新内容信息
  69. $contents = $item['update_content'];
  70. $add = [];
  71. $optimize = [];
  72. $repair = [];
  73. $contentDesc = [];
  74. if (!empty($contents)) {
  75. foreach ($contents as $content) {
  76. if ($content['type'] == 1) {
  77. $add[] = '新增:' . $content['update_function'];
  78. }
  79. if ($content['type'] == 2) {
  80. $optimize[] = '优化:' . $content['update_function'];
  81. }
  82. if ($content['type'] == 3) {
  83. $repair[] = '修复:' . $content['update_function'];
  84. }
  85. }
  86. $contentDesc = array_merge($add, $optimize, $repair);
  87. }
  88. $lists[$k]['add'] = $add;
  89. $lists[$k]['optimize'] = $optimize;
  90. $lists[$k]['repair'] = $repair;
  91. $lists[$k]['content_desc'] = $contentDesc;
  92. unset($lists[$k]['update_content']);
  93. }
  94. $lists[0]['new_version'] = ($pageNo == 1) ? 1 : 0;
  95. return $lists;
  96. }
  97. /**
  98. * @notes 更新操作
  99. * @param $params
  100. * @return bool|string
  101. * @author 段誉
  102. * @date 2021/8/14 17:19
  103. */
  104. public static function upgrade($params)
  105. {
  106. $openBasedir = ini_get('open_basedir');
  107. if(strpos($openBasedir, "server") !== false) {
  108. self::$error = '更新失败: 请临时关闭服务器本站点的跨域攻击设置,并重启 nginx、PHP,具体参考相关升级文档';
  109. return false;
  110. }
  111. // 授权验证
  112. $params['link'] = "package_link";
  113. $result = self::verify($params);
  114. if (!$result['has_permission']) {
  115. self::$error = !empty($result['msg']) ? $result['msg'] : '请先联系客服获取授权';
  116. // 写日志
  117. self::addlog($params['id'], $params['update_type'], false);
  118. return false;
  119. }
  120. // 本地更新包路径
  121. $localUpgradeDir = ROOT_PATH . '/upgrade/';
  122. // 本地更新临时文件
  123. $tempDir = ROOT_PATH . '/upgrade/temp/';
  124. // 更新成功或失败的标识
  125. $flag = true;
  126. Db::startTrans();
  127. try {
  128. // 远程下载链接
  129. $remoteUrl = $result['link'];
  130. if (!is_dir($localUpgradeDir)) {
  131. mkdir(iconv("UTF-8", "GBK", $localUpgradeDir), 0777, true);
  132. }
  133. //下载更新压缩包保存到本地
  134. $remoteData = self::downFile($remoteUrl, $localUpgradeDir);
  135. if (false === $remoteData) {
  136. throw new \Exception('获取文件错误');
  137. }
  138. //解压缩
  139. if (false === unzip($remoteData['save_path'], $tempDir)) {
  140. throw new \Exception('解压文件错误');
  141. }
  142. //更新sql->更新数据类型
  143. if (false === self::upgradeSql($tempDir . 'sql/data/')) {
  144. throw new \Exception('更新数据库数据失败');
  145. }
  146. //更新文件
  147. if (false === self::upgradeFile($tempDir . 'project/server/', self::getProjectPath())) {
  148. throw new \Exception('更新文件失败');
  149. }
  150. Db::commit();
  151. } catch (\Exception $e) {
  152. Db::rollback();
  153. self::$error = $e->getMessage();
  154. //错误日志
  155. $params['error'] = $e->getMessage();
  156. // 标识更新失败
  157. $flag = false;
  158. }
  159. if ($flag) {
  160. try {
  161. //更新sql->更新数据结构
  162. if (false === self::upgradeSql($tempDir . 'sql/structure/')) {
  163. throw new \Exception('更新数据库结构失败');
  164. }
  165. } catch (\Exception $e) {
  166. self::$error = $e->getMessage();
  167. //错误日志
  168. $params['error'] = $e->getMessage();
  169. // 标识更新失败
  170. $flag = false;
  171. }
  172. }
  173. //删除临时文件(压缩包不删除,删除解压的文件)
  174. if ($flag && false === del_target_dir($tempDir, true)) {
  175. Log::write('删除系统更新临时文件失败');
  176. }
  177. // 增加日志
  178. self::addlog($params['id'], $params['update_type'], $flag);
  179. return $flag;
  180. }
  181. /**
  182. * @notes 授权验证
  183. * @param $params
  184. * @author Tab
  185. * @date 2021/10/26 17:12
  186. */
  187. public static function verify($params)
  188. {
  189. $domain = $_SERVER['SERVER_NAME'];
  190. $remoteUrl = self::BASE_URL . "/api/version/verify?domain=".$domain."&product_id=3&type=2&version_id=".$params['id']."&link=".$params['link'];
  191. $result = Requests::get($remoteUrl);
  192. $result = json_decode($result->body, true);
  193. $result = $result['data'] ?? ['has_permission' => false, 'link' => '', 'msg' => ''];
  194. return $result;
  195. }
  196. /**
  197. * @notes 获取远程版本数据
  198. * @param null $pageNo
  199. * @param null $pageSize
  200. * @return array|mixed
  201. * @author 段誉
  202. * @date 2021/8/14 17:20
  203. */
  204. public static function getRemoteVersion($pageNo = null, $pageSize = null)
  205. {
  206. $cacheVersion = Cache::get('version_lists' . $pageNo);
  207. if (!empty($cacheVersion)) {
  208. return $cacheVersion;
  209. }
  210. if (empty($pageNo) || empty($pageSize)) {
  211. $remoteUrl = self::BASE_URL . "/api/version/lists?product_id=3&type=2&page=1";
  212. } else {
  213. $remoteUrl = self::BASE_URL . "/api/version/lists?product_id=3&type=2&page_no=$pageNo&page_size=$pageSize&page=1";
  214. }
  215. $result = Requests::get($remoteUrl);
  216. $result = json_decode($result->body, true);
  217. $result = $result['data'] ?? [];
  218. Cache::set('version_lists' . $pageNo, $result, 1800);
  219. return $result;
  220. }
  221. /**
  222. * @notes 更新包下载链接
  223. * @param $params
  224. * @return array|false
  225. * @author 段誉
  226. * @date 2022/3/25 17:50
  227. */
  228. public static function getPkgLine($params)
  229. {
  230. $map = [
  231. 1 => 'package_link', //一键更新类型 : 服務端更新包
  232. 2 => 'package_link', //服務端更新包
  233. 3 => 'pc_package_link', //pc端更新包
  234. 4 => 'uniapp_package_link', //uniapp更新包
  235. 5 => 'web_package_link', //后台前端更新包
  236. 6 => 'integral_package_link', //完整包
  237. 8 => 'kefu_package_link', //客服更新包
  238. ];
  239. $params['link'] = $map[$params['update_type']] ?? '未知类型';
  240. // 授权验证
  241. $result = self::verify($params);
  242. if (!$result['has_permission']) {
  243. self::$error = !empty($result['msg']) ? $result['msg'] : '请先联系客服获取授权';
  244. // 写日志
  245. self::addlog($params['id'], $params['update_type'], false);
  246. return false;
  247. }
  248. //增加日志记录
  249. self::addlog($params['id'], $params['update_type']);
  250. //更新包下载链接
  251. return ['line' => $result['link']];
  252. }
  253. /**
  254. * @notes 添加日志
  255. * @param $versionId //版本id
  256. * @param $updateType //更新类型
  257. * @param bool $status //更新状态
  258. * @return bool|\Requests_Response
  259. * @author 段誉
  260. * @date 2021/10/9 14:48
  261. */
  262. public static function addlog($versionId, $updateType, $status = true)
  263. {
  264. //版本信息
  265. $versionData = self::getVersionDataById($versionId);
  266. $domain = $_SERVER['SERVER_NAME'];
  267. try {
  268. $paramsData = [
  269. 'version_id' => $versionData['id'],
  270. 'version_no' => $versionData['version_no'],
  271. 'domain' => $domain,
  272. 'type' => 2,//付费版
  273. 'product_id' => 3,//单商户plus
  274. 'update_type' => $updateType,
  275. 'status' => $status ? 1 : 0,
  276. 'error' => empty(self::$error) ? '' : self::$error,
  277. ];
  278. $requestUrl = self::BASE_URL . '/api/version/log';
  279. $result = Requests::post($requestUrl, [], $paramsData);
  280. return $result;
  281. } catch (\Exception $e) {
  282. Log::write('更新日志:' . '更新失败' . $e->getMessage());
  283. return false;
  284. }
  285. }
  286. /**
  287. * @notes 通过版本记录id获取版本信息
  288. * @param $id
  289. * @return array
  290. * @author 段誉
  291. * @date 2021/10/9 11:40
  292. */
  293. public static function getVersionDataById($id)
  294. {
  295. $cacheVersion = self::getRemoteVersion()['lists'] ?? [];
  296. if (!empty($cacheVersion)) {
  297. $versionColumn = array_column($cacheVersion, null, 'id');
  298. if (!empty($versionColumn[$id])) {
  299. return $versionColumn[$id];
  300. }
  301. }
  302. return [];
  303. }
  304. /**
  305. * @notes 下载远程文件
  306. * @param $url
  307. * @param string $savePath
  308. * @return array|false
  309. * @author 段誉
  310. * @date 2021/8/14 17:20
  311. */
  312. public static function downFile($url, $savePath = './upgrade/')
  313. {
  314. $ch = curl_init();
  315. curl_setopt($ch, CURLOPT_URL, $url);
  316. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  317. curl_setopt($ch, CURLOPT_HEADER, TRUE);
  318. curl_setopt($ch, CURLOPT_NOBODY, FALSE);
  319. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  320. $response = curl_exec($ch);
  321. $header = '';
  322. $body = '';
  323. if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
  324. $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
  325. $header = substr($response, 0, $headerSize);
  326. $body = substr($response, $headerSize);
  327. }
  328. curl_close($ch);
  329. //文件名
  330. $fullName = basename($url);
  331. //文件保存完整路径
  332. $savePath = $savePath . $fullName;
  333. //创建目录并设置权限
  334. $basePath = dirname($savePath);
  335. if (!file_exists($basePath)) {
  336. @mkdir($basePath, 0777, true);
  337. @chmod($basePath, 0777);
  338. }
  339. if (file_put_contents($savePath, $body)) {
  340. return [
  341. 'save_path' => $savePath,
  342. 'file_name' => $fullName,
  343. ];
  344. }
  345. return false;
  346. }
  347. /**
  348. * @notes 获取项目路径
  349. * @return string
  350. * @author 段誉
  351. * @date 2021/8/14 17:20
  352. */
  353. public static function getProjectPath()
  354. {
  355. $path = dirname(ROOT_PATH);
  356. if(substr($path, -1) != '/') {
  357. $path = $path . '/';
  358. }
  359. return $path;
  360. }
  361. /**
  362. * @notes 更新sql
  363. * @param $dir
  364. * @return bool
  365. * @author 段誉
  366. * @date 2021/8/14 17:20
  367. */
  368. public static function upgradeSql($dir)
  369. {
  370. //没有sql文件时无需更新
  371. if (!file_exists($dir)) {
  372. return true;
  373. }
  374. //遍历指定目录下的指定后缀文件
  375. $sqlFiles = get_scandir($dir, '', 'sql');
  376. if (false === $sqlFiles) {
  377. return false;
  378. }
  379. //当前数据库前缀
  380. $sqlPrefix = config('database.connections.mysql.prefix');
  381. foreach ($sqlFiles as $k => $item) {
  382. if (get_extension($item) != 'sql') {
  383. continue;
  384. }
  385. $sqlContent = file_get_contents($dir . $item);
  386. if (empty($sqlContent)) {
  387. continue;
  388. }
  389. $sqls = explode(';', $sqlContent);
  390. //执行sql
  391. foreach ($sqls as $sql) {
  392. $sql = trim($sql);
  393. if (!empty($sql)) {
  394. $sql = str_replace('`ls_', '`' . $sqlPrefix, $sql) . ';';
  395. Db::execute($sql);
  396. }
  397. }
  398. }
  399. return true;
  400. }
  401. /**
  402. * @notes 更新文件
  403. * @param $tempFile
  404. * @param $oldFile
  405. * @return bool
  406. * @author 段誉
  407. * @date 2021/8/14 17:21
  408. */
  409. public static function upgradeFile($tempFile, $oldFile)
  410. {
  411. if (empty(trim($tempFile)) || empty(trim($oldFile))) {
  412. return false;
  413. }
  414. // 目录不存在就新建
  415. if (!is_dir($oldFile)) {
  416. mkdir($oldFile, 0777, true);
  417. }
  418. foreach (glob($tempFile . '*') as $fileName) {
  419. // 要处理的是目录时,递归处理文件目录。
  420. if (is_dir($fileName)) {
  421. self::upgradeFile($fileName . '/', $oldFile . basename($fileName) . '/');
  422. }
  423. // 要处理的是文件时,判断是否存在 或者 与原来文件不一致 则覆盖
  424. if (is_file($fileName)) {
  425. if (!file_exists($oldFile . basename($fileName))
  426. || md5(file_get_contents($fileName)) != md5(file_get_contents($oldFile . basename($fileName)))
  427. ) {
  428. copy($fileName, $oldFile . basename($fileName));
  429. }
  430. }
  431. }
  432. return true;
  433. }
  434. }