安装向导 + PHP 8.5 兼容修复

新增 install.php:全新部署时自动接管,用户只需上传文件、打开站点即进入
安装向导。安装页自己做三件事:
  - 建 db/ 与 uploads/{posters,tickets,photos,avatars}/,权限调到 775
  - 逐目录真写临时文件实测可写,不靠 is_writable 猜
  - 建库、建管理员、写 db/.installed 锁、自删本文件

配套改动:
  - config.php 把安装检查前置于建库之前,未安装一律 302 到 install.php。
    原实现无条件调用 initDB(),导致「打开首页」就等于建库并种下 admin/123456。
  - lib/db.php 的 initDB(\$seedDefaultAdmin = true);安装向导传 false,
    管理员由用户自己设,绝不种弱口令。新增 isInstalled()。
  - login.php / nav.php 接上 siteName(),站点名称不再只存不用。
  - lib/settings.php 新增 siteName(),缺省回退「电影日志」。

PHP 8.5 兼容:
  - 删掉 fetch_movie.php 与 lib/storage/http.php 的 curl_close()。
    8.5 起该函数 deprecated,调用会往输出插一行警告,把 JSON 响应污染成
    「警告 + JSON」,前端 JSON.parse 失败 → 电影名联想功能完全失灵。
  - config.php 的 display_errors 由 1 改为 0(生产环境不应向浏览器吐错误),
    错误仍写日志;需要调试时设 MOVIELOG_DEBUG=1 打开。

线上验证(movie.yangyq.net,PHP 8.5.12):
  未安装时 / 、/login.php 均 302 → install.php;
  安装页 200、体检 10/10 通过、自动建好 5 个目录;
  安装后 install.php 404、管理员登录成功、首页 200 无报错;
  联想接口带会话请求「流浪地球/星际穿越/霸王别姬」均返回纯 JSON(首字符 {)。
This commit is contained in:
2026-10-31 21:35:41 +08:00
parent ee256adc4b
commit 3a3c5890cc
8 changed files with 587 additions and 15 deletions
+42 -2
View File
@@ -6,8 +6,25 @@
// 所有页面统一通过 require_once 'config.php' 引入本文件。
// ============================================
// 错误报告策略。
//
// 关键:PHP 的警告/弃用提示是**直接写进响应体**的。对返回 JSON 的接口
// (fetch_movie.php、storage_api.php 等)来说,一条 Warning 插在 JSON 前面
// 就会让前端 JSON.parse 失败,表现为「功能静默失灵」——联想下拉空着、
// 存储测试报连接失败,都可能是这个原因,而接口本身其实是好的。
//
// 本项目踩过:PHP 8.5 把 curl_close() 标为 deprecated,一行提示就把
// 「电影名联想」整个搞挂了(接口返回的 JSON 完全正确,只是前面多了警告)。
//
// 因此:
// - 永远不把错误显示给浏览器(display_errors=0);
// - 仍然记录到日志(log_errors=1),排查时看 PHP 错误日志;
// - 需要临时调试时,设环境变量 MOVIELOG_DEBUG=1 即可把错误显示打开。
$__movielog_debug = getenv('MOVIELOG_DEBUG') === '1';
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('display_errors', $__movielog_debug ? '1' : '0');
ini_set('log_errors', '1');
unset($__movielog_debug);
session_start();
@@ -38,6 +55,27 @@ foreach ([POSTER_DIR, TICKET_DIR, PHOTO_DIR, AVATAR_DIR] as $dir) {
}
}
// ===== 安装检查(必须在加载任何业务模块、建库之前)=====
//
// 未安装时:把请求引到 install.php,并立刻退出 —— 绝不能继续往下走到 initDB()。
// 判据只看锁文件 db/.installed,由 install.php 装完时写入。
//
// 为什么不能「先 initDB() 再判断」:
// initDB() 会建库建表,并在 admins 为空时插入 admin/123456。
// 那样「打开任意页面」这个动作就等于完成了一半安装,还会把弱口令暴露出去
// (FeedLite 踩过同一个坑:访问动作本身建库 → 「未安装」永远判不出来)。
//
// 例外:install.php 自身、以及 css/js 等静态资源不受此检查约束
// (install.php 不加载本文件;静态资源由 Web 服务器直接返回,不经过 PHP)。
if (!is_file(ROOT_DIR . '/db/.installed')) {
// 当前脚本名(兼容子目录部署)
$self = basename($_SERVER['SCRIPT_NAME'] ?? '');
if ($self !== 'install.php' && PHP_SAPI !== 'cli') {
header('Location: install.php');
exit;
}
}
// ===== 功能模块 =====
require_once ROOT_DIR . '/lib/db.php'; // 数据库连接与初始化
require_once ROOT_DIR . '/lib/settings.php'; // 系统设置读写
@@ -50,4 +88,6 @@ require_once ROOT_DIR . '/lib/reviews.php'; // 评价与评价下的讨论
require_once ROOT_DIR . '/lib/stats.php'; // 统计与年度报告
// 初始化数据库
initDB();
// 走到这里说明已装过(上面的检查已拦截未安装的情况),
// 因此 seedDefaultAdmin 传 false:老库本来就有人,空库也不该被塞一个 admin/123456。
initDB(false);