install.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. <?php
  2. ini_set('display_errors', 'On');
  3. ini_set('display_startup_errors', 1);
  4. error_reporting(E_ALL);
  5. use think\facade\Db;
  6. require __DIR__ . '/../vendor/autoload.php';
  7. require __DIR__ . '/../vendor/topthink/framework/src/helper.php';
  8. define('DS', DIRECTORY_SEPARATOR);
  9. define('ROOT_PATH', __DIR__ . DS . '..' . DS);
  10. define('INSTALL_PATH', ROOT_PATH . 'config' . DS . 'install' . DS);
  11. define('CONFIG_PATH', ROOT_PATH . 'config' . DS);
  12. $currentHost = ($_SERVER['SERVER_PORT'] == 443 ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . '/';
  13. function isReadWrite($file)
  14. {
  15. if (DIRECTORY_SEPARATOR == '\\') {
  16. return true;
  17. }
  18. if (DIRECTORY_SEPARATOR == '/' && @ ini_get("safe_mode") === false) {
  19. return is_writable($file);
  20. }
  21. if (!is_file($file) || ($fp = @fopen($file, "r+")) === false) {
  22. return false;
  23. }
  24. fclose($fp);
  25. return true;
  26. }
  27. $errorInfo = null;
  28. if (is_file(INSTALL_PATH . 'lock' . DS . 'install.lock')) {
  29. $errorInfo = '已安装系统,如需重新安装请删除文件:/config/install/lock/install.lock';
  30. } elseif (!isReadWrite(ROOT_PATH . 'config' . DS)) {
  31. $errorInfo = ROOT_PATH . 'config' . DS . ':读写权限不足';
  32. } elseif (!isReadWrite(ROOT_PATH . 'runtime' . DS)) {
  33. $errorInfo = ROOT_PATH . 'runtime' . DS . ':读写权限不足';
  34. } elseif (!isReadWrite(ROOT_PATH . 'public' . DS)) {
  35. $errorInfo = ROOT_PATH . 'public' . DS . ':读写权限不足';
  36. } elseif (!checkPhpVersion('7.1.0')) {
  37. $errorInfo = 'PHP版本不能小于7.1.0';
  38. } elseif (!extension_loaded("PDO")) {
  39. $errorInfo = '当前未开启PDO,无法进行安装';
  40. }
  41. // POST请求
  42. if (isAjax()) {
  43. $post = $_POST;
  44. $cover = $post['cover'] == 1 ? true : false;
  45. $database = $post['database'];
  46. $hostname = $post['hostname'];
  47. $hostport = $post['hostport'];
  48. $dbUsername = $post['db_username'];
  49. $dbPassword = $post['db_password'];
  50. $prefix = $post['prefix'];
  51. $adminUrl = $post['admin_url'];
  52. $username = $post['username'];
  53. $password = $post['password'];
  54. // 参数验证
  55. $validateError = null;
  56. // 判断是否有特殊字符
  57. $check = preg_match('/[0-9a-zA-Z]+$/', $adminUrl, $matches);
  58. if (!$check) {
  59. $validateError = '后台地址不能含有特殊字符, 只能包含字母或数字。';
  60. $data = [
  61. 'code' => 0,
  62. 'msg' => $validateError,
  63. ];
  64. die(json_encode($data));
  65. }
  66. if (strlen($adminUrl) < 2) {
  67. $validateError = '后台的地址不能小于2位数';
  68. } elseif (strlen($password) < 5) {
  69. $validateError = '管理员密码不能小于5位数';
  70. } elseif (strlen($username) < 4) {
  71. $validateError = '管理员账号不能小于4位数';
  72. }
  73. if (!empty($validateError)) {
  74. $data = [
  75. 'code' => 0,
  76. 'msg' => $validateError,
  77. ];
  78. die(json_encode($data));
  79. }
  80. // DB类初始化
  81. $config = [
  82. 'type' => 'mysql',
  83. 'hostname' => $hostname,
  84. 'username' => $dbUsername,
  85. 'password' => $dbPassword,
  86. 'hostport' => $hostport,
  87. 'charset' => 'utf8',
  88. 'prefix' => $prefix,
  89. 'debug' => true,
  90. ];
  91. Db::setConfig([
  92. 'default' => 'mysql',
  93. 'connections' => [
  94. 'mysql' => $config,
  95. 'install' => array_merge($config, ['database' => $database]),
  96. ],
  97. ]);
  98. // 检测数据库连接
  99. if (!checkConnect()) {
  100. $data = [
  101. 'code' => 0,
  102. 'msg' => '数据库连接失败',
  103. ];
  104. die(json_encode($data));
  105. }
  106. // 检测数据库是否存在
  107. if (!$cover && checkDatabase($database)) {
  108. $data = [
  109. 'code' => 0,
  110. 'msg' => '数据库已存在,请选择覆盖安装或者修改数据库名',
  111. ];
  112. die(json_encode($data));
  113. }
  114. // 创建数据库
  115. createDatabase($database);
  116. // 导入sql语句等等
  117. $install = install($username, $password, array_merge($config, ['database' => $database]), $adminUrl);
  118. if ($install !== true) {
  119. $data = [
  120. 'code' => 0,
  121. 'msg' => '系统安装失败:' . $install,
  122. ];
  123. die(json_encode($data));
  124. }
  125. $data = [
  126. 'code' => 1,
  127. 'msg' => '系统安装成功,正在跳转登录页面',
  128. 'url' => $adminUrl,
  129. ];
  130. die(json_encode($data));
  131. }
  132. function isAjax()
  133. {
  134. if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
  135. return true;
  136. } else {
  137. return false;
  138. }
  139. }
  140. function isPost()
  141. {
  142. return ($_SERVER['REQUEST_METHOD'] == 'POST' && checkurlHash($GLOBALS['verify'])
  143. && (empty($_SERVER['HTTP_REFERER']) || preg_replace("~https?:\/\/([^\:\/]+).*~i", "\\1", $_SERVER['HTTP_REFERER']) == preg_replace("~([^\:]+).*~", "\\1", $_SERVER['HTTP_HOST']))) ? 1 : 0;
  144. }
  145. function checkPhpVersion($version)
  146. {
  147. $php_version = explode('-', phpversion());
  148. $check = strnatcasecmp($php_version[0], $version) >= 0 ? true : false;
  149. return $check;
  150. }
  151. function checkConnect()
  152. {
  153. try {
  154. Db::query("select version()");
  155. } catch (\Exception $e) {
  156. return false;
  157. }
  158. return true;
  159. }
  160. function checkDatabase($database)
  161. {
  162. $check = Db::query("SELECT * FROM information_schema.schemata WHERE schema_name='{$database}'");
  163. if (empty($check)) {
  164. return false;
  165. } else {
  166. return true;
  167. }
  168. }
  169. function createDatabase($database)
  170. {
  171. try {
  172. Db::execute("CREATE DATABASE IF NOT EXISTS `{$database}` DEFAULT CHARACTER SET utf8");
  173. } catch (\Exception $e) {
  174. return false;
  175. }
  176. return true;
  177. }
  178. function parseSql($sql = '', $to, $from)
  179. {
  180. list($pure_sql, $comment) = [[], false];
  181. $sql = explode("\n", trim(str_replace(["\r\n", "\r"], "\n", $sql)));
  182. foreach ($sql as $key => $line) {
  183. if ($line == '') {
  184. continue;
  185. }
  186. if (preg_match("/^(#|--)/", $line)) {
  187. continue;
  188. }
  189. if (preg_match("/^\/\*(.*?)\*\//", $line)) {
  190. continue;
  191. }
  192. if (substr($line, 0, 2) == '/*') {
  193. $comment = true;
  194. continue;
  195. }
  196. if (substr($line, -2) == '*/') {
  197. $comment = false;
  198. continue;
  199. }
  200. if ($comment) {
  201. continue;
  202. }
  203. if ($from != '') {
  204. $line = str_replace('`' . $from, '`' . $to, $line);
  205. }
  206. if ($line == 'BEGIN;' || $line == 'COMMIT;') {
  207. continue;
  208. }
  209. array_push($pure_sql, $line);
  210. }
  211. //$pure_sql = implode($pure_sql, "\n");
  212. $pure_sql = implode("\n",$pure_sql);
  213. $pure_sql = explode(";\n", $pure_sql);
  214. return $pure_sql;
  215. }
  216. function install($username, $password, $config, $adminUrl)
  217. {
  218. $sqlPath = file_get_contents(INSTALL_PATH . 'sql' . DS . 'install.sql');
  219. $sqlArray = parseSql($sqlPath, $config['prefix'], 'ea_');
  220. Db::startTrans();
  221. try {
  222. foreach ($sqlArray as $vo) {
  223. Db::connect('install')->execute($vo);
  224. }
  225. Db::connect('install')
  226. ->name('system_admin')
  227. ->where('id', 1)
  228. ->delete();
  229. Db::connect('install')
  230. ->name('system_admin')
  231. ->insert([
  232. 'id' => 1,
  233. 'username' => $username,
  234. 'head_img' => '/static/admin/images/head.jpg',
  235. 'password' => password($password),
  236. 'create_time' => time(),
  237. ]);
  238. // 处理安装文件
  239. !is_dir(INSTALL_PATH) && @mkdir(INSTALL_PATH);
  240. !is_dir(INSTALL_PATH . 'lock' . DS) && @mkdir(INSTALL_PATH . 'lock' . DS);
  241. @file_put_contents(INSTALL_PATH . 'lock' . DS . 'install.lock', date('Y-m-d H:i:s'));
  242. @file_put_contents(CONFIG_PATH . 'app.php', getAppConfig($adminUrl));
  243. @file_put_contents(CONFIG_PATH . 'database.php', getDatabaseConfig($config));
  244. Db::commit();
  245. } catch (\Exception $e) {
  246. Db::rollback();
  247. return $e->getMessage();
  248. }
  249. return true;
  250. }
  251. function password($value)
  252. {
  253. $value = sha1('blog_') . md5($value) . md5('_encrypt') . sha1($value);
  254. return sha1($value);
  255. }
  256. function getAppConfig($admin)
  257. {
  258. $config = <<<EOT
  259. <?php
  260. // +----------------------------------------------------------------------
  261. // | 应用设置
  262. // +----------------------------------------------------------------------
  263. use think\\facade\Env;
  264. return [
  265. // 应用地址
  266. 'app_host' => Env::get('app.host', ''),
  267. // 应用的命名空间
  268. 'app_namespace' => '',
  269. // 是否启用路由
  270. 'with_route' => true,
  271. // 是否启用事件
  272. 'with_event' => true,
  273. // 开启应用快速访问
  274. 'app_express' => true,
  275. // 默认应用
  276. 'default_app' => 'index',
  277. // 默认时区
  278. 'default_timezone' => 'Asia/Shanghai',
  279. // 应用映射(自动多应用模式有效)
  280. 'app_map' => [
  281. Env::get('easyadmin.admin', '{$admin}') => 'admin',
  282. ],
  283. // 后台别名
  284. 'admin_alias_name' => Env::get('easyadmin.admin', '{$admin}'),
  285. // 域名绑定(自动多应用模式有效)
  286. 'domain_bind' => [],
  287. // 禁止URL访问的应用列表(自动多应用模式有效)
  288. 'deny_app_list' => ['common'],
  289. // 异常页面的模板文件
  290. 'exception_tmpl' => Env::get('app_debug') == 1 ? app()->getThinkPath() . 'tpl/think_exception.tpl' : app()->getBasePath() . 'common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'think_exception.tpl',
  291. // 跳转页面的成功模板文件
  292. 'dispatch_success_tmpl' => app()->getBasePath() . 'common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'dispatch_jump.tpl',
  293. // 跳转页面的失败模板文件
  294. 'dispatch_error_tmpl' => app()->getBasePath() . 'common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'dispatch_jump.tpl',
  295. // 错误显示信息,非调试模式有效
  296. 'error_message' => '页面错误!请稍后再试~',
  297. // 显示错误信息
  298. 'show_error_msg' => false,
  299. // 静态资源上传到OSS前缀
  300. 'oss_static_prefix' => Env::get('easyadmin.oss_static_prefix', 'static_easyadmin'),
  301. ];
  302. EOT;
  303. return $config;
  304. }
  305. function getDatabaseConfig($data)
  306. {
  307. $config = <<<EOT
  308. <?php
  309. use think\\facade\Env;
  310. return [
  311. // 默认使用的数据库连接配置
  312. 'default' => Env::get('database.driver', 'mysql'),
  313. // 自定义时间查询规则
  314. 'time_query_rule' => [],
  315. // 自动写入时间戳字段
  316. // true为自动识别类型 false关闭
  317. // 字符串则明确指定时间字段类型 支持 int timestamp datetime date
  318. 'auto_timestamp' => true,
  319. // 时间字段取出后的默认时间格式
  320. 'datetime_format' => 'Y-m-d H:i:s',
  321. // 数据库连接配置信息
  322. 'connections' => [
  323. 'mysql' => [
  324. // 数据库类型
  325. 'type' => Env::get('database.type', 'mysql'),
  326. // 服务器地址
  327. 'hostname' => Env::get('database.hostname', '{$data['hostname']}'),
  328. // 数据库名
  329. 'database' => Env::get('database.database', '{$data['database']}'),
  330. // 用户名
  331. 'username' => Env::get('database.username', '{$data['username']}'),
  332. // 密码
  333. 'password' => Env::get('database.password', '{$data['password']}'),
  334. // 端口
  335. 'hostport' => Env::get('database.hostport', '{$data['hostport']}'),
  336. // 数据库连接参数
  337. 'params' => [],
  338. // 数据库编码默认采用utf8
  339. 'charset' => Env::get('database.charset', 'utf8'),
  340. // 数据库表前缀
  341. 'prefix' => Env::get('database.prefix', '{$data['prefix']}'),
  342. // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
  343. 'deploy' => 0,
  344. // 数据库读写是否分离 主从式有效
  345. 'rw_separate' => false,
  346. // 读写分离后 主服务器数量
  347. 'master_num' => 1,
  348. // 指定从服务器序号
  349. 'slave_no' => '',
  350. // 是否严格检查字段是否存在
  351. 'fields_strict' => true,
  352. // 是否需要断线重连
  353. 'break_reconnect' => false,
  354. // 监听SQL
  355. 'trigger_sql' => true,
  356. // 开启字段缓存
  357. 'fields_cache' => false,
  358. // 字段缓存路径
  359. 'schema_cache_path' => app()->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR,
  360. ],
  361. // 更多的数据库配置信息
  362. ],
  363. ];
  364. EOT;
  365. return $config;
  366. }
  367. ?>
  368. <!DOCTYPE html>
  369. <html>
  370. <head>
  371. <meta charset="utf-8">
  372. <title>安装EasyAdmin后台程序</title>
  373. <meta name="renderer" content="webkit">
  374. <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  375. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  376. <link rel="stylesheet" href="static/plugs/layui-v2.5.6/css/layui.css?v=<?php echo time() ?>" media="all">
  377. <link rel="stylesheet" href="static/common/css/insatll.css?v=<?php echo time() ?>" media="all">
  378. </head>
  379. <body>
  380. <h1><img src="static/common/images/logo-1.png"></h1>
  381. <h2>安装EasyAdmin后台系统</h2>
  382. <div class="content">
  383. <p class="desc">
  384. 使用过程中遇到任何问题可参考
  385. <a href="http://easyadmin.99php.cn/docs" target="_blank">文档教程</a>
  386. <a href="https://jq.qq.com/?_wv=1027&k=5IHJawE">QQ交流群</a>
  387. </p>
  388. <form class="layui-form layui-form-pane" action="">
  389. <?php if ($errorInfo): ?>
  390. <div class="error">
  391. <?php echo $errorInfo; ?>
  392. </div>
  393. <?php endif; ?>
  394. <div class="bg">
  395. <div class="layui-form-item">
  396. <label class="layui-form-label">数据库地址</label>
  397. <div class="layui-input-block">
  398. <input class="layui-input" name="hostname" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据库地址" placeholder="请输入数据库地址" value="host.docker.internal">
  399. </div>
  400. </div>
  401. <div class="layui-form-item">
  402. <label class="layui-form-label">数据库端口</label>
  403. <div class="layui-input-block">
  404. <input class="layui-input" name="hostport" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据库端口" placeholder="请输入数据库端口" value="3306">
  405. </div>
  406. </div>
  407. <div class="layui-form-item">
  408. <label class="layui-form-label">数据库名称</label>
  409. <div class="layui-input-block">
  410. <input class="layui-input" name="database" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据库名称" placeholder="请输入数据库名称" value="easyadmin">
  411. </div>
  412. </div>
  413. <div class="layui-form-item">
  414. <label class="layui-form-label">数据表前缀</label>
  415. <div class="layui-input-block">
  416. <input class="layui-input" name="prefix" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据表前缀" placeholder="请输入数据表前缀" value="ea_">
  417. </div>
  418. </div>
  419. <div class="layui-form-item">
  420. <label class="layui-form-label">数据库账号</label>
  421. <div class="layui-input-block">
  422. <input class="layui-input" name="db_username" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据库账号" placeholder="请输入数据库账号" value="root">
  423. </div>
  424. </div>
  425. <div class="layui-form-item">
  426. <label class="layui-form-label">数据库密码</label>
  427. <div class="layui-input-block">
  428. <input type="password" class="layui-input" name="db_password" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据库密码" placeholder="请输入数据库密码">
  429. </div>
  430. </div>
  431. <div class="layui-form-item">
  432. <label class="layui-form-label">覆盖数据库</label>
  433. <div class="layui-input-block" style="text-align: left">
  434. <input type="radio" name="cover" value="1" title="覆盖">
  435. <input type="radio" name="cover" value="0" title="不覆盖" checked>
  436. </div>
  437. </div>
  438. </div>
  439. <div class="bg">
  440. <div class="layui-form-item">
  441. <label class="layui-form-label">后台的地址</label>
  442. <div class="layui-input-block">
  443. <input class="layui-input" id="admin_url" name="admin_url" autocomplete="off" lay-verify="required" lay-reqtext="请输入后台的地址" placeholder="为了后台安全,不建议将后台路径设置为admin" value="admin">
  444. <span class="tips">后台登录地址: <?php echo $currentHost; ?><span id="admin_name">admin</span></span>
  445. </div>
  446. </div>
  447. <div class="layui-form-item">
  448. <label class="layui-form-label">管理员账号</label>
  449. <div class="layui-input-block">
  450. <input class="layui-input" name="username" autocomplete="off" lay-verify="required" lay-reqtext="请输入管理员账号" placeholder="请输入管理员账号" value="admin">
  451. </div>
  452. </div>
  453. <div class="layui-form-item">
  454. <label class="layui-form-label">管理员密码</label>
  455. <div class="layui-input-block">
  456. <input type="password" class="layui-input" name="password" autocomplete="off" lay-verify="required" lay-reqtext="请输入管理员密码" placeholder="请输入管理员密码">
  457. </div>
  458. </div>
  459. </div>
  460. <div class="layui-form-item">
  461. <button class="layui-btn layui-btn-normal <?php echo $errorInfo ? 'layui-btn-disabled' : '' ?>" lay-submit="" lay-filter="install">确定安装</button>
  462. </div>
  463. </form>
  464. </div>
  465. <script src="static/plugs/layui-v2.5.6/layui.js?v=<?php echo time() ?>" charset="utf-8"></script>
  466. <script>
  467. layui.use(['form', 'layer'], function () {
  468. var $ = layui.jquery,
  469. form = layui.form,
  470. layer = layui.layer;
  471. $("#admin_url").bind("input propertychange", function () {
  472. var val = $(this).val();
  473. $("#admin_name").text(val);
  474. });
  475. form.on('submit(install)', function (data) {
  476. if ($(this).hasClass('layui-btn-disabled')) {
  477. return false;
  478. }
  479. var _data = data.field;
  480. var loading = layer.msg('正在安装...', {
  481. icon: 16,
  482. shade: 0.2,
  483. time: false
  484. });
  485. $.ajax({
  486. url: window.location.href,
  487. type: 'post',
  488. contentType: "application/x-www-form-urlencoded; charset=UTF-8",
  489. dataType: "json",
  490. data: _data,
  491. timeout: 60000,
  492. success: function (data) {
  493. layer.close(loading);
  494. if (data.code === 1) {
  495. layer.msg(data.msg, {icon: 1}, function () {
  496. window.location.href = location.protocol + '//' + location.host + '/' + data.url;
  497. });
  498. } else {
  499. layer.msg(data.msg, {icon: 2});
  500. }
  501. },
  502. error: function (xhr, textstatus, thrown) {
  503. layer.close(loading);
  504. layer.msg('Status:' + xhr.status + ',' + xhr.statusText + ',请稍后再试!', {icon: 2});
  505. return false;
  506. }
  507. });
  508. return false;
  509. });
  510. });
  511. </script>
  512. </body>
  513. </html>