用户 2026-10-03 的十条,逐条落点: 1. 天气改为**个人设置里填城市**(每人一份):新增 app/Geo.php,内置 197 个 地级市坐标(离线、逐条核对,生成脚本 _tooling/_fb_geo_gen.py)。填坐标仍 作为内置表没有时的出口;老的家庭级坐标保留为兜底,升级当天天气不会凭空消失。 2. 所有"找个人"的地方**输入即筛选**:新增 assets/js/filter.js(纯前端显隐, 不发请求、回车不刷新)+ app/PersonQuery.php 把服务端判据收敛成一份 (名册 / 挑人 / 高级搜索共用,避免三处实现漂移)。 3. 名册列表:有昵称就不重复显示本名、行变矮、补齐手机/阴历阳历生日(带年份)/ 邮箱/工作单位;「清除」与「搜索」同款同尺寸。 4/5. 日程:撤掉左侧日历本栏改满宽,功能(显隐/颜色/导入/默认视图)全收进 右侧 ☰;**色块高度改为按起止时长**(旧实现一律 30 分钟,三小时的会跟 十五分钟的同高);点日历空白处即建日程,开始时间=点的那一格。 6/10. 资源:搜索提到最上;按"最近改动/最新新增"排序,默认只显示最新 10 条并 给「显示全部」出口。 7. 任务:看板的**列=用户自定义的看板**(不再是写死的待办/进行中), 拖动/左右键=换看板(改 board_id,不动 status)。 8. 财务:概览在上账目在下、记一笔改右侧按钮、账本设置(共享/改名/新建/删除) 收进 ☰、默认账本名「xxx的账本」、"挂在谁名下"默认自己且用标准选人框; **新增盘点**(新增 ledger_entry_snapshots 只增不改的历史表,旧值不被清空) + 上面画金额变化折线图(app/chart.php 服务端画 SVG,零依赖)。 9. 纪念日:手动添加的与名册生日派生的**分两块**、手动的在上;右侧设置可增删 分类(家庭级,删分类不强改已用的历史)。 本轮另外修掉/加固的四类问题(都由新增的静态或运行时守卫兜住): - index.php 少写 2 个 use 导入(Pinyin / UserSetting)→ 名册与个人设置 返回 200 但正文是 Fatal;顺带修了一处早已存在的 Attachment 未导入。 → 新增 _fb_import_check.py(1f 段)。 - 一次不精确替换弄丢了 app/Calendar.php 开头的 `<?php`,整份源码被当 HTML 吐给浏览器,而 `php -l` 报"无语法错误"(纯文本输出确实合法)。 → 新增 _fb_php_open_check.py(1e 段)。 - 模板片段 unset 了宿主模板的变量(账本片段 unset($canWrite))→ 正文里一片 Undefined variable 而 HTTP 仍 200。同 _datetime.php 早年 $t 的坑。 → 新增 _fb_fragment_scope_check.py(1g 段)+ _fb_batch_e.py 的 26 页无告警巡检。 - 窄屏下页头动作区不换行 → 周视图横向溢出且按钮被挤成三种高度(用户提过三次 "按钮要同高")→ .page-head .actions 加 flex-wrap。 验证战绩(全绿): e2e 773/0 · 第 5 轮专项 91/0 · 结构升级 126/0 · 几何 428/0 · 移动端 684/0 · 交互 97/0 · 家谱 26/0 · 按钮 60/0;三轮反向验证各 3/3 抓到,还原后复绿。 线上:schema v14、指纹一致(9df6c9fccc6796db)、snapshots 表已建、197 城、 8 个关键文件 md5 与本地逐字节一致、app/ 403 无源码泄漏、错误日志无新增。
116 lines
4.7 KiB
PHP
116 lines
4.7 KiB
PHP
<?php
|
||
/**
|
||
* 应用引导:配置加载、会话、自动加载、全局辅助函数。
|
||
* 每个页面/入口只需 require 这一个文件。
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
// ---------------- 自动加载(避免「新增类忘了 require」这类事故)----------------
|
||
spl_autoload_register(function ($class) {
|
||
if (strpos($class, 'App\\') !== 0) {
|
||
return;
|
||
}
|
||
// App\Storage\OssDriver → app/Storage/OssDriver.php(子命名空间靠这段映射)
|
||
$rel = str_replace('\\', '/', substr($class, 4));
|
||
$file = __DIR__ . '/' . $rel . '.php';
|
||
if (is_file($file)) {
|
||
require $file;
|
||
}
|
||
});
|
||
|
||
define('FB_ROOT', dirname(__DIR__));
|
||
define('FB_APP', __DIR__);
|
||
define('FB_UPLOADS', FB_ROOT . '/uploads');
|
||
|
||
// ---------------- 配置 ----------------
|
||
$configFile = FB_ROOT . '/config.php';
|
||
if (!is_file($configFile)) {
|
||
http_response_code(500);
|
||
echo '<!doctype html><meta charset="utf-8"><title>家书 · 未配置</title>'
|
||
. '<div style="max-width:520px;margin:12vh auto;padding:28px 32px;'
|
||
. 'background:#fff;border:1px solid rgba(47,36,30,.1);border-radius:16px;'
|
||
. 'font:15px/1.8 -apple-system,\'PingFang SC\',\'Microsoft YaHei\',sans-serif;color:#2F241E">'
|
||
. '<h1 style="font-size:18px;margin:0 0 12px">还差一步:没有找到 config.php</h1>'
|
||
. '<p style="color:#7C6A5B;margin:0 0 16px">'
|
||
. '请复制同目录下的 <code>config.example.php</code> 为 <code>config.php</code>,'
|
||
. '填好数据库连接与 app_key 后再访问。</p>'
|
||
. '</div>';
|
||
exit;
|
||
}
|
||
|
||
$GLOBALS['config'] = require $configFile;
|
||
|
||
// ---------------- 错误报告(线上不显示错误,避免泄露路径与库信息)----------------
|
||
$debug = !empty($GLOBALS['config']['debug']);
|
||
error_reporting(E_ALL);
|
||
ini_set('display_errors', $debug ? '1' : '0');
|
||
ini_set('log_errors', '1');
|
||
|
||
/**
|
||
* 把错误日志指到**我们够得着的地方**(v24)。
|
||
*
|
||
* 为什么需要:线上出 500 时最该看的就是错误日志,而宝塔的 `/www/wwwlogs`
|
||
* 既不在部署账号的读权限内,也不在 `open_basedir` 允许的路径里
|
||
* (本站是 `/www/wwwroot/…:/tmp/`)。这一轮真遇到了:用户报 500,
|
||
* 能查的全查了(逐文件哈希、两账号 32 次真实渲染、边界输入、DNS/证书/缓存)
|
||
* 全绿,唯独拿不到"当时哪一行炸了"。
|
||
*
|
||
* 做法:**只需指定 `error_log`** —— PHP 本来就会把 Fatal error(含文件:行号与
|
||
* 调用栈)写进去,不需要自己注册 shutdown 钩子。
|
||
* (实测:先前那版加了 `register_shutdown_function` 反而多余,
|
||
* 钩子自身一旦出错还会**再抛一个 Fatal**,把真正的原因埋掉。)
|
||
*
|
||
* 落点:优先站点根的 `db/`(敏感配置所在地,不入 Web 根、不进仓库),
|
||
* 没有就回落 `sys_get_temp_dir()`(线上 open_basedir 含 `/tmp/`,一定可写)。
|
||
* 两处都不可写就保持原样(记日志不该把站点拖垮)。
|
||
*
|
||
* ⚠️ 只记 PHP 自己的错误文本,**不记**请求体/变量值(可能含口令)。
|
||
*/
|
||
{
|
||
$fbLogFile = '';
|
||
foreach ([__DIR__ . '/../db', sys_get_temp_dir()] as $fbDir) {
|
||
if (is_dir($fbDir) && is_writable($fbDir)) {
|
||
$fbLogFile = rtrim($fbDir, '/\\') . '/familybook-error.log';
|
||
break;
|
||
}
|
||
}
|
||
if ($fbLogFile !== '') {
|
||
ini_set('log_errors', '1');
|
||
ini_set('error_log', $fbLogFile);
|
||
}
|
||
}
|
||
|
||
// ---------------- 时区:日期是家书的核心数据,一开始就必须明确 ----------------
|
||
date_default_timezone_set($GLOBALS['config']['timezone'] ?? 'Asia/Shanghai');
|
||
|
||
// ---------------- 安全响应头 ----------------
|
||
// Referrer-Policy: 跨域取图不带 Referer —— 这一条能兜住云存储防盗链
|
||
// 导致的「图片不显示、但地址栏直接打开没问题」那类怪异现象。
|
||
if (!headers_sent()) {
|
||
header('Referrer-Policy: same-origin');
|
||
header('X-Content-Type-Options: nosniff');
|
||
}
|
||
|
||
// ---------------- 会话 ----------------
|
||
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|
||
|| (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');
|
||
|
||
session_name('fbsid');
|
||
session_set_cookie_params([
|
||
'lifetime' => 0,
|
||
'path' => '/',
|
||
'secure' => $isHttps,
|
||
'httponly' => true,
|
||
'samesite' => 'Lax',
|
||
]);
|
||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||
session_start();
|
||
}
|
||
|
||
require FB_APP . '/helpers.php';
|
||
// 全局函数库(不是类,不吃自动加载):折线图等纯渲染工具。
|
||
// ⚠️ 与 helpers.php 一样在**每个请求**都加载,所以里面只能放函数定义,
|
||
// 不要放查询/副作用(那会让每个请求都多跑一次)。
|
||
require FB_APP . '/chart.php';
|