Files
FamilyBook/index.php
T
laoyang ecafa110f2 阶段 0 起步:图标与环境自检脚手架
- assets/icons 全套图标:SVG 源文件 + maskable + 12 档 PNG
- favicon.ico / apple-touch-icon.png / site.webmanifest
- index.php 环境自检页(PHP 版本 / 必需扩展 / 配置 / 数据库连接),
  详情页默认不打印连接错误原文,避免泄露数据库账号名
- config.example.php:数据库与 app_key 的配置模板(config.php 不入库)
- README 补充项目定位与六阶段路线图;.gitignore 沿用既有约定
2026-09-29 23:48:45 +08:00

142 lines
5.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* FamilyBook · 家书 —— 阶段 0 引导页
*
* 这一页不是首页。它的职责是:部署之后先看这里,确认
* 「PHP 版本 / 扩展 / 配置文件 / 数据库连接」四件事都通了,再往上盖功能。
* 正式首页做好之后会替换掉它。
*
* ⚠️ 线上请把 config.php 里的 debug 设为 false:
* 连接报错时,详情里可能含数据库账号名,不该公开展示。
*/
declare(strict_types=1);
define('FB_ROOT', __DIR__);
$configFile = FB_ROOT . '/config.php';
$hasConfig = is_file($configFile);
$config = $hasConfig ? require $configFile : null;
$debug = is_array($config) && !empty($config['debug']);
/**
* 静态资源带版本号(mtime + size)。
* 不带版本号的话,文件换了浏览器还在用旧缓存 —— 详见踩坑记录。
*/
function fb_asset(string $rel): string
{
$p = FB_ROOT . $rel;
if (!is_file($p)) {
return $rel;
}
return $rel . '?v=' . substr(md5(filemtime($p) . filesize($p)), 0, 8);
}
$checks = [];
// 1. PHP 版本:以 PHP 7.4 为底线(线上同时存在 7.4 与 8.1 两套)
$checks['PHP 版本'] = [
'ok' => PHP_VERSION_ID >= 70400,
'val' => PHP_VERSION . '(需 7.4 以上)',
];
// 2. 必需扩展
$needExt = ['pdo_mysql' => '数据库', 'mbstring' => '中文处理', 'openssl' => '敏感字段加密', 'json' => '配置与接口', 'gd' => '图片裁剪'];
foreach ($needExt as $ext => $why) {
$ok = extension_loaded($ext);
$checks['扩展 ' . $ext] = ['ok' => $ok, 'val' => $ok ? '已加载 · ' . $why : '缺失 · ' . $why];
}
// 3. 配置文件
$checks['配置文件 config.php'] = [
'ok' => $hasConfig,
'val' => $hasConfig ? '已就位' : '未找到(请复制 config.example.php 后填写)',
];
// 4. 数据库连接(只有配置齐全才去连)
if (is_array($config) && !empty($config['db'])) {
$d = $config['db'];
try {
$dsn = sprintf(
'mysql:host=%s;port=%d;dbname=%s;charset=%s',
$d['host'], $d['port'], $d['name'], $d['charset']
);
$pdo = new PDO($dsn, $d['user'], $d['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
$ver = $pdo->query('SELECT VERSION()')->fetchColumn();
$checks['数据库连接'] = ['ok' => true, 'val' => 'MySQL ' . $ver . ' · 库 ' . $d['name']];
} catch (Throwable $e) {
$checks['数据库连接'] = [
'ok' => false,
'val' => $debug ? $e->getMessage() : '连接失败(把 config.php 的 debug 设为 true 可看详情)',
];
}
}
$allOk = true;
foreach ($checks as $c) {
$allOk = $allOk && $c['ok'];
}
$siteName = is_array($config) && !empty($config['site_name']) ? $config['site_name'] : '家书';
?><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= htmlspecialchars($siteName, ENT_QUOTES, 'UTF-8') ?> · 环境自检</title>
<link rel="icon" href="/favicon.ico">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
<style>
:root { --paper:#F2E9D8; --cover:#A84A28; --cover2:#C96F45; --ink:#6E3820; --white:#FDFBF5; }
* { box-sizing:border-box; }
body { margin:0; padding:40px 20px; background:var(--paper); color:var(--ink);
font:15px/1.7 -apple-system,"PingFang SC","Microsoft YaHei",sans-serif; }
.wrap { max-width:560px; margin:0 auto; }
.head { display:flex; align-items:center; gap:14px; margin-bottom:6px; }
.head img { width:64px; height:64px; border-radius:14px; }
h1 { font-size:22px; margin:0; font-weight:600; }
.sub { color:#8a6a52; font-size:13px; margin:0 0 24px 78px; }
ul { list-style:none; margin:0; padding:0; background:var(--white);
border-radius:12px; overflow:hidden; }
li { display:flex; align-items:baseline; gap:10px; padding:12px 16px;
border-bottom:1px solid rgba(110,56,32,.08); }
li:last-child { border-bottom:0; }
.dot { width:9px; height:9px; border-radius:50%; flex:0 0 auto; margin-top:8px; }
.ok { background:#6f9e5c; } .bad { background:#c0533f; }
.k { font-weight:600; flex:0 0 auto; }
.v { color:#7a5a42; font-size:13px; word-break:break-all; }
.banner { padding:10px 16px; border-radius:10px; margin-bottom:18px; font-size:14px; }
.pass { background:#e6f0dc; color:#3d6b1f; }
.fail { background:#f8ddda; color:#a1341f; }
</style>
</head>
<body>
<div class="wrap">
<div class="head">
<img src="<?= htmlspecialchars(fb_asset('/assets/icons/icon-192.png'), ENT_QUOTES, 'UTF-8') ?>" alt="">
<div>
<h1><?= htmlspecialchars($siteName, ENT_QUOTES, 'UTF-8') ?> · 环境自检</h1>
</div>
</div>
<p class="sub">FamilyBook —— 一本用岁月写成的家谱</p>
<div class="banner <?= $allOk ? 'pass' : 'fail' ?>">
<?= $allOk ? '全部通过,可以开始盖功能了。' : '还有未通过项,处理后请刷新这一页。' ?>
</div>
<ul>
<?php foreach ($checks as $name => $c): ?>
<li>
<span class="dot <?= $c['ok'] ? 'ok' : 'bad' ?>"></span>
<span class="k"><?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?></span>
<span class="v"><?= htmlspecialchars((string)$c['val'], ENT_QUOTES, 'UTF-8') ?></span>
</li>
<?php endforeach; ?>
</ul>
</div>
</body>
</html>