初始化

This commit is contained in:
2026-08-08 18:28:49 +08:00
parent 9bef4420e0
commit f082037a6e
854 changed files with 217171 additions and 11 deletions
+153
View File
@@ -0,0 +1,153 @@
<?php
// 应用目录为当前目录
header('Content-Type: text/html; charset=utf-8');
// 禁止 CDN 和浏览器缓存动态页面(确保修改即时生效)
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
ini_set('default_charset', 'UTF-8');
// ===== Session 安全配置(必须在 session_start 之前) =====
ini_set('session.cookie_httponly', '1');
// 关闭 strict_mode — strict_mode=1 会导致:
// 浏览器带着一个已销毁的 Session Cookie 来时,PHP 静默创建新 Session
// 但补发的 Set-Cookie 只能在下次请求生效,造成当前请求 Session 为空
ini_set('session.use_strict_mode', '0');
ini_set('session.use_only_cookies', '1');
// P2-8 安全加固:会话 Cookie 统一使用 SameSite=Lax(降低 CSRF 风险)
// 说明:原 SameSite=None 仅用于跨站 iframe 场景;若前台工位确需嵌入第三方站点的 iframe,
// 可在此改回 'None',但这会重新引入 CSRF 风险,请谨慎评估。
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443);
if ($isHttps) {
ini_set('session.cookie_secure', '1');
}
ini_set('session.cookie_samesite', 'Lax');
// ===== 自动修复 session 存储目录(LNMP 重装后目录可能丢失) =====
$sessionPath = ini_get('session.save_path');
if ($sessionPath && !is_dir($sessionPath)) {
// php.ini 指定的 session 目录不存在,尝试创建
@mkdir($sessionPath, 0777, true);
}
// 如果创建失败或路径为空,回退到系统临时目录
if (!$sessionPath || !is_dir($sessionPath) || !is_writable($sessionPath)) {
$fallbackPath = sys_get_temp_dir() . '/php_sessions';
if (!is_dir($fallbackPath)) {
@mkdir($fallbackPath, 0777, true);
}
if (is_dir($fallbackPath) && is_writable($fallbackPath)) {
ini_set('session.save_path', $fallbackPath);
session_save_path($fallbackPath);
}
}
// ===== 处理浏览器带来的旧 Session Cookie(避免 Session 文件不存在导致数据丢失) =====
$cookieSid = $_COOKIE[session_name()] ?? null;
if ($cookieSid) {
// 检查对应的 session 文件是否存在
$sessionFile = ini_get('session.save_path') . '/sess_' . $cookieSid;
if (!file_exists($sessionFile)) {
// Session 文件已被销毁(如被 session_minimal.php 清除),
// 浏览器还带着旧 Cookie。先设置 session_id 让它复用旧 ID
// session_start() 会创建新文件(strict_mode=0 允许)
session_id($cookieSid);
}
}
session_start();
// ===== 补发 Session Cookie(兼容某些浏览器 Cookie 丢失场景) =====
// P2-11 安全加固:仅在「请求已携带会话 Cookie 且当前 Session ID 发生变化」
// (如登录/会话重生成)时补发,避免每个响应都重复发送 Set-Cookie。
// 全新会话由 session_start() 自动发送,此处不再补发,消除重复头。
$hadIncomingSid = isset($_COOKIE[session_name()]);
if ($hadIncomingSid && (!isset($_COOKIE[session_name()]) || $_COOKIE[session_name()] !== session_id())) {
$cookieParams = session_get_cookie_params();
setcookie(
session_name(),
session_id(),
[
'expires' => 0, // 会话 Cookie
'path' => $cookieParams['path'] ?: '/',
'domain' => $cookieParams['domain'] ?: '',
'secure' => $cookieParams['secure'],
'httponly' => true,
'samesite' => 'Lax',
]
);
}
// ===== Session 诊断日志(仅首次请求记录,避免日志泛滥) =====
if (empty($_SESSION['_diag_logged'])) {
$_SESSION['_diag_logged'] = 1;
$diagPath = ini_get('session.save_path');
$diagWritable = is_writable($diagPath) ? 'writable' : 'NOT_WRITABLE';
$diagSameSite = ini_get('session.cookie_samesite');
$diagSecure = ini_get('session.cookie_secure');
$diagHttpOnly = ini_get('session.cookie_httponly');
$sessHash = session_id() ? hash('sha256', session_id()) : 'none';
error_log("[SESSION_DIAG] path={$diagPath} {$diagWritable} samesite={$diagSameSite} secure={$diagSecure} httponly={$diagHttpOnly} session_hash={$sessHash}");
}
define('APP_PATH', __DIR__ . '/');
// 基础URL路径(自动检测子目录,无需手动配置)
// 例如:部署在 /mes/ 下则自动为 /mes,部署在根目录则为空
$scriptDir = dirname($_SERVER['SCRIPT_NAME']);
define('BASE_URL', ($scriptDir === '/' || $scriptDir === '\\') ? '' : $scriptDir);
// 从 .env 读取调试模式开关(生产环境设为 false)
$envDebug = getenv('APP_DEBUG');
if ($envDebug === false || $envDebug === '') {
// 环境变量未设置时,从 .env 文件读取
$envFile = __DIR__ . '/.env';
if (file_exists($envFile)) {
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || $line[0] === '#') continue;
if (strpos($line, '=') !== false) {
list($k, $v) = explode('=', $line, 2);
if (trim($k) === 'APP_DEBUG') {
$envDebug = trim($v);
break;
}
}
}
}
}
define('APP_DEBUG', ($envDebug === 'true' || $envDebug === '1'));
// 启用输出缓冲,防止 PHP 错误/警告污染 JSON/HTML 输出
ob_start();
// 加载全局辅助函数(必须在框架和视图之前加载)
require(APP_PATH . 'core/helper.php');
// 加载框架文件
require(APP_PATH . 'core/Core.php');
// 加载配置文件
$config = require(APP_PATH . 'config/config.php');
// ===== v3-P2 CSP:基于 nonce 的内容安全策略(严格模式) =====
// 每次请求生成独立 nonce 并注入 CSP 头;视图通过 csp_nonce() 为 <script>/<style> 打标记。
// 全站内联事件处理器(onclick 等)已改造为 addEventListener / 事件委托,故 script-src 不再保留 'unsafe-inline'。
// 第三方库均以外链 <script src> 加载(受 'self' 允许),不依赖内联脚本。
$cspNonce = base64_encode(random_bytes(16));
$GLOBALS['csp_nonce'] = $cspNonce;
$cspHeader = "default-src 'self'; "
. "script-src 'self' 'nonce-{$cspNonce}'; "
. "style-src 'self' 'unsafe-inline'; "
. "img-src 'self' data: blob:; "
. "font-src 'self'; "
. "connect-src 'self'; "
. "frame-ancestors 'self'; "
. "base-uri 'self'; "
. "form-action 'self'";
header("Content-Security-Policy: {$cspHeader}");
// 实例化框架类
(new core\Core($config))->run();