Install.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | Description: 安装
  4. // +----------------------------------------------------------------------
  5. // | Author: xiekunyu | raingad@foxmail.com
  6. // +----------------------------------------------------------------------
  7. namespace app\index\controller;
  8. use think\facade\Request;
  9. use think\facade\Db;
  10. use think\facade\View;
  11. use think\facade\Config;
  12. use Env;
  13. class Install
  14. {
  15. // private $count = 100;
  16. // private $now = 0;
  17. protected $status=1;
  18. public function _initialize()
  19. {
  20. /*防止跨域*/
  21. header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']);
  22. header('Access-Control-Allow-Credentials: true');
  23. header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
  24. header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, authKey, sessionId");
  25. }
  26. /**
  27. * [index 安装步骤]
  28. * @author Michael_xu
  29. * @param
  30. */
  31. public function index()
  32. {
  33. $protocol = strpos(strtolower($_SERVER['SERVER_PROTOCOL']), 'https') === false ? 'http' : 'https';
  34. if (file_exists(PACKAGE_PATH . "install.lock")) {
  35. echo "<meta http-equiv='content-type' content='text/html; charset=UTF-8'> <script>alert('请勿重复安装!');location.href='".$protocol."://".$_SERVER["HTTP_HOST"]."';</script>";
  36. die();
  37. }
  38. if (!file_exists(PUBLIC_PATH . "sql/database.sql")) {
  39. echo "<meta http-equiv='content-type' content='text/html; charset=UTF-8'> <script>alert('缺少必要的数据库文件!');location.href='".$protocol."://".$_SERVER["HTTP_HOST"]."';</script>";
  40. die();
  41. }
  42. return View::fetch('index');
  43. }
  44. // 检测环境配置和文件夹读写权限
  45. public function getEnv()
  46. {
  47. $data = [];
  48. $data['env'] = self::checkEnv();
  49. $data['dir'] = self::checkDir();
  50. $data['version'] = $this->version();
  51. $data['status'] = $this->status;
  52. return success('',$data);
  53. }
  54. //版本
  55. public function version()
  56. {
  57. $res = include(CONF_PATH.'app.php');
  58. $data=[
  59. 'VERSION'=>$res['app_version'],
  60. 'RELEASE'=>$res['app_release'],
  61. ];
  62. return $data ? : array('VERSION' => '0.5.18','RELEASE' => '20210518');
  63. }
  64. // 检查数据库
  65. public function checkDatabase(){
  66. if (file_exists(PACKAGE_PATH . "install.lock")) {
  67. return warning('请勿重复安装!');
  68. }
  69. if (!file_exists(PUBLIC_PATH . "sql/database.sql")) {
  70. return warning('缺少必要的数据库文件!');
  71. }
  72. $temp = request()->param();
  73. $db_config = $temp['form'];
  74. $db_config['type'] = 'mysql';
  75. if (empty($db_config['hostname'])) {
  76. return warning('请填写数据库主机!');
  77. }
  78. if (empty($db_config['hostport'])) {
  79. return warning('请填写数据库端口!');
  80. }
  81. if (preg_match('/[^0-9]/', $db_config['hostport'])) {
  82. return warning('数据库端口只能是数字!');
  83. }
  84. if (empty($db_config['database'])) {
  85. return warning('请填写数据库名!');
  86. }
  87. if (empty($db_config['username'])) {
  88. return warning('请填写数据库用户名!');
  89. }
  90. if (empty($db_config['password'])) {
  91. return warning('请填写数据库密码!');
  92. }
  93. if (empty($db_config['prefix'])) {
  94. return warning('请填写表前缀!');
  95. }
  96. if (empty($db_config['redishost'])) {
  97. return warning('请填写redis主机地址!');
  98. }
  99. if (empty($db_config['redisport'])) {
  100. return warning('请填写redis端口!');
  101. }
  102. if (preg_match('/[^a-z0-9_]/i', $db_config['prefix'])) {
  103. return warning('表前缀只能包含数字、字母和下划线!');
  104. }
  105. // 创建数据库配置文件
  106. self::mkDatabase($db_config);
  107. // 检测数据库连接
  108. try{
  109. $conn=mysqli_connect($db_config['hostname'], $db_config['username'], $db_config['password']);
  110. // 检测连接
  111. if ($conn->connect_error) {
  112. return warning("连接失败: " . $conn->connect_error);
  113. }
  114. // 创建数据库
  115. $sql = "CREATE DATABASE IF NOT EXISTS `".$db_config['database']."` default collate utf8_general_ci ";
  116. if ($conn->query($sql) === TRUE) {
  117. return success('数据库连接成功',['status'=>1]);
  118. } else{
  119. return warning('没有找到您填写的数据库名且无法创建!请检查连接账号是否有创建数据库的权限!');
  120. }
  121. }catch(\Exception $e){
  122. return warning('数据库连接失败,请检查数据库配置!');
  123. }
  124. }
  125. // 执行安装
  126. public function install(){
  127. $db_config=Config::get('database.connections.mysql');
  128. $sql = file_get_contents( PUBLIC_PATH . "sql/database.sql");
  129. $sqlList = parse_sql($sql, 0, ['yu_' => $db_config['prefix']]);
  130. $install_count=0;
  131. if ($sqlList) {
  132. $sqlList = array_filter($sqlList);
  133. $install_count = count($sqlList);
  134. foreach ($sqlList as $k=>$v) {
  135. try {
  136. $temp_sql = $v.';';
  137. Db::query($temp_sql);
  138. } catch(\Exception $e) {
  139. touch(PACKAGE_PATH . "install.lock");
  140. return error('数据库sql安装出错,请操作数据库手动导入sql文件'.$e->getMessage());
  141. }
  142. }
  143. }
  144. touch(PACKAGE_PATH . "install.lock");
  145. return success('安装成功',['status'=>$this->status],$install_count);
  146. }
  147. //ajax 进度条
  148. public function progress()
  149. {
  150. $data['length'] = session('install_count');
  151. $data['now'] = session('install_now');
  152. return success('',$data);
  153. }
  154. //添加database.php文件
  155. private function mkDatabase(array $data)
  156. {
  157. $code = <<<INFO
  158. APP_DEBUG = true
  159. [APP]
  160. DEFAULT_TIMEZONE = Asia/Shanghai
  161. ID = a1b2c3d4e5f
  162. SECRET = GHJKUG123456sdfghjkl
  163. API_STATUS = true
  164. # 安卓包名,如果上架了市场,根据市场ID跳转市场
  165. ANDRIOD_APPID =
  166. #安卓下载地址,如果未设置会检测根目录是否有app.apk
  167. ANDRIOD_WEBCLIP =https://emoji.raingad.com/file/raingad.apk
  168. #APPSTORE市场ID
  169. IOS_APPID =
  170. #IOS下载地址,如果没有市场的ID则使用下载地址
  171. IOS_WEBCLIP =
  172. #windows下载地址
  173. WIN_WEBCLIP =
  174. #mac下载地址
  175. MAC_WEBCLIP =
  176. [DATABASE]
  177. TYPE = {$data['type']}
  178. HOSTNAME = {$data['hostname']}
  179. DATABASE = {$data['database']}
  180. USERNAME = {$data['username']}
  181. PASSWORD = {$data['password']}
  182. HOSTPORT = {$data['hostport']}
  183. CHARSET = utf8
  184. DEBUG = true
  185. prefix = {$data['prefix']}
  186. [LANG]
  187. default_lang = zh-cn
  188. [REDIS]
  189. HOST = {$data['redishost']}
  190. PORT = {$data['redisport']}
  191. PASSWORD ={$data['redispass']}
  192. [AES]
  193. TOKEN_KEY = tHTi8USApxsdfnhTM
  194. LOGIN_KEY = t2fe6HMnmssswDVi2
  195. #聊天内容加密,如果不加密则留空,一旦加密就不能修改,如果修改了需要清空所有聊天记录
  196. CHAT_KEY =
  197. [JWT]
  198. SECRET = 17b190c0d612321f94f57325ae5a8b4c
  199. TTL = 2592000
  200. [WORKER]
  201. NAME = businessWorker
  202. PORT = 8282
  203. # 根据自己的核心数而配置
  204. COUNT = 1
  205. START_PORT = 2300
  206. REGISTER_ADDRESS =127.0.0.1:1236
  207. lAN_IP = 127.0.0.1
  208. # 分部署部署只需要启动一个gateway,其他的gateway只需要配置register_address即可
  209. REGISTER_DEPLOY = true
  210. #配置预览功能,本系统主要使用第三方的预览工具,比如永中云转换,自带预览系统
  211. [PREVIEW]
  212. # 自带预览系统URL,主要用于预览媒体文件,已内置,必须要有最后的/斜杠
  213. own=
  214. # 永中云文件预览,主要用于文档预览,必须要有最后的/斜杠
  215. yzdcs=
  216. # 永中云api code
  217. keycode=17444844212312
  218. [UNIPUSH]
  219. # unipush的云函数转url地址,主要用于推送
  220. URL=
  221. # unipush直接推送通知栏还是app接收后再创建通知栏
  222. IS_FORCE=false
  223. # 配置对象储存,主要用于聊天文件储存,可以通过后台进行配置
  224. [FILESYSTEM]
  225. driver=local
  226. aliyun_accessId=false
  227. aliyun_accessSecret=false
  228. aliyun_bucket=false
  229. aliyun_endpoint=false
  230. aliyun_url=false
  231. qiniu_accessKey=false
  232. qiniu_secretKey=false
  233. qiniu_bucket=false
  234. qiniu_url=false
  235. qcloud_region=false
  236. qcloud_appId=false
  237. qcloud_secretId=false
  238. qcloud_secretKey=false
  239. qcloud_bucket=false
  240. qcloud_cdn=false
  241. INFO;
  242. @file_put_contents( root_path().'.env', $code);
  243. $database=env('database.database');
  244. // 判断写入是否成功
  245. if (empty($database) || $database != $data['database']) {
  246. return warning('[.env]数据库配置写入失败!');
  247. }
  248. return true;
  249. }
  250. //添加database.php文件
  251. private function mkDatabase1(array $data)
  252. {
  253. $code = <<<INFO
  254. <?php
  255. return [
  256. // 自定义时间查询规则
  257. 'time_query_rule' => [],
  258. // 自动写入时间戳字段
  259. // true为自动识别类型 false关闭
  260. // 字符串则明确指定时间字段类型 支持 int timestamp datetime date
  261. 'auto_timestamp' => true,
  262. // 时间字段取出后的默认时间格式
  263. 'datetime_format' => 'Y-m-d H:i:s',
  264. 'default' => '{$data['type']}',
  265. 'connections' => [
  266. 'mysql' => [
  267. // 数据库类型
  268. 'type' =>env('database.type', '{$data['type']}'),
  269. // 服务器地址
  270. 'hostname' => env('database.hostname','{$data['hostname']}'),
  271. // 数据库名
  272. 'database' => env('database.database','{$data['database']}'),
  273. // 用户名
  274. 'username' => env('database.username','{$data['username']}'),
  275. // 密码
  276. 'password' => env('database.password','{$data['password']}'),
  277. // 端口
  278. 'hostport' => env('database.hostport','{$data['hostport']}'),
  279. // 数据库连接参数
  280. 'params' => [],
  281. // 数据库编码默认采用utf8
  282. 'charset' => env('database.charset', 'utf8'),
  283. // 数据库表前缀
  284. 'prefix' => env('database.prefix', '{$data['prefix']}'),
  285. // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
  286. 'deploy' => 0,
  287. // 数据库读写是否分离 主从式有效
  288. 'rw_separate' => false,
  289. // 读写分离后 主服务器数量
  290. 'master_num' => 1,
  291. // 指定从服务器序号
  292. 'slave_no' => '',
  293. // 是否严格检查字段是否存在
  294. 'fields_strict' => true,
  295. // 是否需要断线重连
  296. 'break_reconnect' => false,
  297. // 监听SQL
  298. 'trigger_sql' => env('app_debug', true),
  299. // 开启字段缓存
  300. 'fields_cache' => false,
  301. // 字段缓存路径
  302. 'schema_cache_path' => app()->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR,
  303. ]
  304. ]
  305. ];
  306. INFO;
  307. file_put_contents( CONF_PATH.'database.php', $code);
  308. // 判断写入是否成功
  309. $config = include CONF_PATH.'database.php';
  310. if (empty($config['database']) || $config['database'] != $data['database']) {
  311. return warning('[config/database.php]数据库配置写入失败!');
  312. }
  313. return true;
  314. }
  315. //检查目录权限
  316. public function check_dir_iswritable($dir_path){
  317. $dir_path=str_replace( '\\','/',$dir_path);
  318. $is_writale=1;
  319. if (!is_dir($dir_path)) {
  320. $is_writale=0;
  321. return $is_writale;
  322. } else {
  323. $file_hd=@fopen($dir_path.'/test.txt','w');
  324. if (!$file_hd) {
  325. @fclose($file_hd);
  326. @unlink($dir_path.'/test.txt');
  327. $is_writale=0;
  328. return $is_writale;
  329. }
  330. $dir_hd = opendir($dir_path);
  331. while (false !== ($file=readdir($dir_hd))) {
  332. if ($file != "." && $file != "..") {
  333. if (is_file($dir_path.'/'.$file)) {
  334. //文件不可写,直接返回
  335. if (!is_writable($dir_path.'/'.$file)) {
  336. return 0;
  337. }
  338. } else {
  339. $file_hd2=@fopen($dir_path.'/'.$file.'/test.txt','w');
  340. if (!$file_hd2) {
  341. @fclose($file_hd2);
  342. @unlink($dir_path.'/'.$file.'/test.txt');
  343. $is_writale=0;
  344. return $is_writale;
  345. }
  346. //递归
  347. $is_writale=$this->check_dir_iswritable($dir_path.'/'.$file);
  348. }
  349. }
  350. }
  351. }
  352. return $is_writale;
  353. }
  354. /**
  355. * 环境检测
  356. * @return array
  357. */
  358. private function checkEnv()
  359. {
  360. // $items = [
  361. // 'os' => ['操作系统', PHP_OS, '类Unix', 'ok'],
  362. // 'php' => ['PHP版本', PHP_VERSION, '7.3 ( <em style="color: #888; font-size: 12px;">>= 7.0</em> )', 'ok','性能更佳'],
  363. // 'gd' => ['gd', '开启', '开启', 'ok'],
  364. // 'openssl' => ['openssl', '开启', '开启', 'ok'],
  365. // 'pdo' => ['pdo', '开启', '开启', 'ok'],
  366. // ];
  367. $items = [
  368. ['name'=>'操作系统','alias'=>'os','value'=>PHP_OS,'status'=> 'ok','description'=>"操作系统需要类Unix"],
  369. ['name'=>'PHP版本','alias'=>'version','value'=> PHP_VERSION, 'status'=>'ok','description'=>"PHP版本必须大于7.0"],
  370. ['name'=>'gd库','alias'=>'gd', 'value'=>'开启', 'status'=>'ok','description'=>"开启GD库"],
  371. ['name'=>'pdo','alias'=>'pdo', 'value'=>'开启', 'status'=>'ok','description'=>"PDO扩展"],
  372. ['name'=>'openssl','alias'=>'openssl', 'value'=>'开启', 'status'=>'ok','description'=>"OPENSSL扩展"],
  373. ['name'=>'pcntl','alias'=>'pcntl', 'value'=>'开启', 'status'=>'ok','description'=>"pcntl扩展,消息推送必须开启"],
  374. ['name'=>'posix','alias'=>'posix', 'value'=>'开启', 'status'=>'ok','description'=>"posix扩展,消息推送必须开启"],
  375. ['name'=>'event','alias'=>'event', 'value'=>'开启', 'status'=>'ok','description'=>"event扩展(可选安装),处理消息推送高并发"],
  376. ];
  377. foreach($items as $k=>$v){
  378. $status='ok';
  379. switch($v['alias']){
  380. case 'php':
  381. if (substr($v['value'],0,3) < '7.0') {
  382. $status='no';
  383. $this->status=0;
  384. }
  385. break;
  386. case 'gd':
  387. if (!extension_loaded('gd')) {
  388. $items[$k]['value'] = '未开启';
  389. $status='no';
  390. $this->status=0;
  391. }
  392. break;
  393. case 'openssl':
  394. if (!extension_loaded('openssl')) {
  395. $items[$k]['value'] = '未开启';
  396. $status='no';
  397. $this->status=0;
  398. }
  399. break;
  400. case 'pdo':
  401. if (!extension_loaded('pdo')) {
  402. $this->status=0;
  403. $items[$k]['value'] = '未开启';
  404. $status='no';
  405. }
  406. break;
  407. case 'pcntl':
  408. if (PHP_OS === 'Linux') {
  409. if (!extension_loaded('pcntl')) {
  410. $items[$k]['value'] = '未开启';
  411. $status='no';
  412. }
  413. } else {
  414. $items[$k]['value'] = 'win无需开启';
  415. }
  416. break;
  417. case 'posix':
  418. if (PHP_OS === 'Linux') {
  419. if (!extension_loaded('posix')) {
  420. $this->status=0;
  421. $items[$k]['value'] = '未开启';
  422. $status='no';
  423. }
  424. } else {
  425. $items[$k]['value'] = 'win无需开启';
  426. }
  427. break;
  428. case 'event':
  429. if (PHP_OS === 'Linux') {
  430. if (!extension_loaded('event')) {
  431. $items[$k]['value'] = '未开启';
  432. $status='no';
  433. }
  434. } else {
  435. $items[$k]['value'] = 'win无需开启';
  436. }
  437. break;
  438. }
  439. $items[$k]['status'] = $status;
  440. }
  441. return $items;
  442. }
  443. /**
  444. * 目录权限检查
  445. * @return array
  446. */
  447. private function checkDir()
  448. {
  449. $items = [
  450. ['dir', root_path().'app', 'app', '读写', '读写', 'ok'],
  451. ['dir', root_path().'extend', 'extend', '读写', '读写', 'ok'],
  452. ['dir', root_path().'runtime', './temp', '读写', '读写', 'ok'],
  453. ['dir', root_path().'public', './upload', '读写', '读写', 'ok'],
  454. ['file', root_path().'config', 'config', '读写', '读写', 'ok'],
  455. ];
  456. $items = [
  457. ['path'=>root_path().'app', 'dir'=>'app', 'value'=>'读写', 'type'=>'dir','status'=>'ok'],
  458. ['path'=>root_path().'extend', 'dir'=>'extend', 'value'=>'读写', 'type'=>'dir','status'=>'ok'],
  459. ['path'=> root_path().'runtime', 'dir'=>'runtime', 'value'=>'读写', 'type'=>'dir','status'=>'ok'],
  460. ['path'=>root_path().'public', 'dir'=>'public', 'value'=>'读写', 'type'=>'dir','status'=>'ok'],
  461. ['path'=>root_path().'config', 'dir'=>'config', 'value'=>'读写', 'type'=>'file','status'=>'ok'],
  462. ];
  463. $status=1;
  464. foreach ($items as $k=>$v) {
  465. if ($v['type'] == 'dir') {// 文件夹
  466. if (!is_writable($v['path'])) {
  467. if (is_dir($v['path'])) {
  468. $items[$k]['value'] = '不可写';
  469. $items[$k]['status'] = 'no';
  470. } else {
  471. $items[$k]['value'] = '不存在';
  472. $items[$k]['status'] = 'no';
  473. }
  474. $this->status=0;
  475. }
  476. } else {// 文件
  477. if (!is_writable($v['path'])) {
  478. $items[$k]['value'] = '不可写';
  479. $items[$k]['status'] = 'no';
  480. $this->status=0;
  481. }
  482. }
  483. }
  484. return $items;
  485. }
  486. /**
  487. * 验证序列号
  488. * @param
  489. * @return
  490. */
  491. public function checkCodeOld($username) {
  492. $encryption = md5($username);
  493. $substr = substr($username, strlen($username)-6);
  494. $subArr = str_split($substr, 1);
  495. $code = '';
  496. for ($i = 0; $i <= 5; $i++) {
  497. $code .= $encryption[$subArr[$i]];
  498. }
  499. return $code;
  500. }
  501. //写入license文件
  502. private function mkLicense($wkcode)
  503. {
  504. file_put_contents( CONF_PATH.'license.dat', $wkcode);
  505. // 判断写入是否成功
  506. // $config = include CONF_PATH.'license.dat';
  507. // if (empty($config)) {
  508. // return resultArray(['error' => 'license配置写入失败!']);
  509. // }
  510. return true;
  511. }
  512. }