新增 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(首字符 {)。
94 lines
4.4 KiB
PHP
94 lines
4.4 KiB
PHP
<?php
|
||
// ============================================
|
||
// 文件: config.php
|
||
// 说明: 应用引导入口。负责错误报告、会话、路径常量、上传目录,
|
||
// 并加载 lib/ 下的各功能模块,最后初始化数据库。
|
||
// 所有页面统一通过 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', $__movielog_debug ? '1' : '0');
|
||
ini_set('log_errors', '1');
|
||
unset($__movielog_debug);
|
||
|
||
session_start();
|
||
|
||
// 跨域资源引用策略:本站页面加载对象存储(OSS / COS / 七牛)等第三方域名的图片时,
|
||
// 浏览器不发送 Referer,避免被云厂商的防盗链(bucket referer policy)拦成 403。
|
||
// 同源请求(本站自身的页面与接口)仍照常携带 Referer,不影响任何安全检查。
|
||
if (!headers_sent()) {
|
||
header('Referrer-Policy: same-origin');
|
||
}
|
||
|
||
// 项目根目录(lib/ 下的模块统一用它拼接绝对路径)
|
||
define('ROOT_DIR', __DIR__);
|
||
|
||
// 数据库路径
|
||
define('DB_PATH', ROOT_DIR . '/db/movies.db');
|
||
|
||
// 上传目录
|
||
define('UPLOAD_DIR', ROOT_DIR . '/uploads/');
|
||
define('POSTER_DIR', UPLOAD_DIR . 'posters/');
|
||
define('TICKET_DIR', UPLOAD_DIR . 'tickets/');
|
||
define('PHOTO_DIR', UPLOAD_DIR . 'photos/');
|
||
define('AVATAR_DIR', UPLOAD_DIR . 'avatars/');
|
||
|
||
// 创建上传目录
|
||
foreach ([POSTER_DIR, TICKET_DIR, PHOTO_DIR, AVATAR_DIR] as $dir) {
|
||
if (!is_dir($dir)) {
|
||
mkdir($dir, 0755, true);
|
||
}
|
||
}
|
||
|
||
// ===== 安装检查(必须在加载任何业务模块、建库之前)=====
|
||
//
|
||
// 未安装时:把请求引到 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'; // 系统设置读写
|
||
require_once ROOT_DIR . '/lib/storage.php'; // 对象存储门面(含各云厂商驱动)
|
||
require_once ROOT_DIR . '/lib/helpers.php'; // 通用工具函数
|
||
require_once ROOT_DIR . '/lib/theme.php'; // 界面配色主题(注册表 / 当前主题)
|
||
require_once ROOT_DIR . '/lib/auth.php'; // 登录校验与账号管理
|
||
require_once ROOT_DIR . '/lib/movies.php'; // 电影 / 影院 / 搜索
|
||
require_once ROOT_DIR . '/lib/reviews.php'; // 评价与评价下的讨论
|
||
require_once ROOT_DIR . '/lib/stats.php'; // 统计与年度报告
|
||
|
||
// 初始化数据库
|
||
// 走到这里说明已装过(上面的检查已拦截未安装的情况),
|
||
// 因此 seedDefaultAdmin 传 false:老库本来就有人,空库也不该被塞一个 admin/123456。
|
||
initDB(false);
|