本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/待办事宜/FlintHub_1.1.7_方案.md。
FlintHub 1.1.7 方案与落地记录(2026-10-05)
状态:站长已确认,四项任务全部落地,核心包已出(
release/core/flinthub-core-1.1.7.zip)。核查基线:站点根
D:/phpstudy_pro/WWW,PHP 8.2.9,站点根version.php=V1.1.4(正常,见 §三)。本文档原为「仅核查与规划」,现按实际落地更新;未采纳项单独标注。
〇、站长确认结论(原待确认 6 项)
| # | 事项 | 确认结果 |
|---|---|---|
| 1 | 任务一修复方案 | A + B(A 自愈 + B 一键复位) |
| 2 | 备案占位图 | flinthub-badge.svg(站点根已有,不新增文件) |
| 3 | 公安盾牌图标 | 填写信息后自动配图标(用内置字体图标,无需上传图) |
| 4 | 备案信息显示位置 | 与原徽标同位置;ICP + 公安同一行(margin-left:14px 间隔) |
| 5 | nginx-*.conf 的 server_name 提示改进 | 不并入(本次不做) |
| 6 | index.php SITE_URL 兼容补丁 | 并入 1.1.7(原样保留) |
| 7 | (新增) 后台「密钥仍用默认值」告警 | 加「不再提示」按钮(全局关闭) |
中途修正:后台「基本信息」页里,备案字段最初插在「站点域名」与「站点部署路径」之间,把这对相邻的部署项拆开 → 已把两个备案
form-group整体移到「站点部署路径」之后。
一、升级页「删除备份」被拦 —— 属实,已修(A + B)
1.1 原拦截点(与「升级锁」无关)
app/Controllers/Admin/UpdateController.php(原 :759-763):拦的是进度文件 data/runtime/update_progress.json,不是 data/lock/update.lock(:738 注释明确「⚠️ 不要求升级锁」)。
1.2 为什么会被永久拦住
| 环节 | 说明 |
|---|---|
done 何时为 true | Updater::apply() 只在 ok && offset>=count(ops) && version_written 时置 true → 任何错误或没跑完,done 恒 false |
| 进度何时被清 | clearProgress() 只在 apply 完全成功分支调用 |
| 有无超时自愈 | 原无(对比升级锁有 LOCK_TTL = 1800) |
⇒ apply 中途失败/中断(超时、进程被杀、批次报错)→ 进度残留 done=false → 「删除备份」被永久拦住。
1.3 实际落地
A 半 —— 抽出单一真相源(app/Helpers/Updater.php,clearProgress() 之后新增):
/**
* 「apply 是否正在跑」—— 供后台「删除备份」等操作判定(1.1.7)。
* 判据:`update_progress.json` 为 `step=apply && done=false`,**且 `at` 新鲜**(口径同 LOCK_TTL)。
*/
public static function applyInProgress(): bool
{
$p = self::readProgress();
if (!is_array($p) || (string) ($p['step'] ?? '') !== 'apply' || !empty($p['done'])) {
return false;
}
$at = (int) ($p['at'] ?? 0);
return ($at > 0) && ((time() - $at) <= self::LOCK_TTL);
}UpdateController::deleteBackup() 的拦截改为调用它(只读判定):
if (Updater::applyInProgress()) {
$this->json(['ok' => false, 'error' => '正在应用升级,不能删除备份(请等升级跑完或回滚后再删)'], 200);
return;
}B 半 —— 「重置升级状态」按钮(UpdateController::resetState() + 路由 + 前端):
public function resetState()
{
Csrf::verifyOrDie($_POST['csrf'] ?? '');
$had = Updater::readProgress();
Updater::clearProgress(); // 清进度残留
$lock = Updater::lockStatus();
$unlocked = false;
if (!empty($lock['locked']) && !empty($lock['stale'])) {
$unlocked = Updater::releaseLock($this->lockOwner()); // 只放陈旧锁
}
AuditLog::log('update_state_reset', 'core', 0, '…');
$this->json(['ok' => true, 'had_progress' => is_array($had), 'unlocked' => $unlocked,
'lock' => Updater::lockStatus(), 'progress' => Updater::readProgress()], 200);
}- 路由:
index.php内/admin/update/reset-state→POST。 - 前端:
app/Views/admin/update.php回滚区新增按钮(window.confirm→ POST → 刷新状态)。 - i18n:
admin.update_reset_state_btn/_resetting/_reset_state_hint/_confirm/_ok。
1.4 验证
- 探针
fh_117_apply_in_progress_test.php14/14 PASS:边界†1799 / †1800 / †1801、中断 1 天残留、done=true、缺at、step=fetch、JSON 损坏、clearProgress()、lockStatus()含stale。 - 进度文件字节级还原(sha256
0d5ea53c…前后一致,零残留)。
顺带:
update.php:322-323原有注释说明「报错走backupErr、渲染在本区块内」——提示可见性此前已修过,未再动。
二、ICP / 公安备案 —— 已实现
2.1 落地方式(站长口径)
- 不新增图片资源:两个字段都空 → 保持原徽标
flinthub-badge.svg;任一有值 → 显示备案文字。 - 公安图标:用内置字体图标
$this->icon('shield', 13)(TemplateCompiler::icon()的'shield' => ),无需上传盾牌图。 - ICP + 公安同一行,间距
margin-left:14px。
2.2 改动点(实际)
| # | 文件 | 改动 |
|---|---|---|
| 1 | app/Views/_components/footer_beian.php | 新建(核心交付物,见下) |
| 2 | app/Views/layouts/main.php:409-410 | 前台页脚改调组件(badgeClass='mn-footer-badge') |
| 3 | assets/themes/Xenforo/layouts/main.php:431-432 | 同步(该目录不进核心包,见 §三.3) |
| 4 | app/Controllers/Admin/SettingsController.php | 白名单加 icp_number / police_number,并做去标签 + 60 字截断清洗 |
| 5 | app/Views/admin/settings_basic.php | 两个 form-group,位于「站点域名 → 站点部署路径」之后 |
| 6 | lang/{zh,en,zh_tw}.php | 各 +6 键(admin.icp_number{,_placeholder,_hint}、admin.police_number{,_placeholder,_hint}) |
2.3 组件逻辑(_components/footer_beian.php)
$badgeClass = $badgeClass ?? 'mn-footer-badge';
$badgeStyle = $badgeStyle ?? 'height:20px;width:138px;';
$icp = \trim((string) \app\Helpers\Settings::get('icp_number', ''));
$police = \trim((string) \app\Helpers\Settings::get('police_number', ''));
// 皆空 → 输出原徽标(?v 由 filemtime 派生);否则输出备案文字- 公安链接:
https://beian.mps.gov.cn/#/query/webSearch?code=<纯数字>(从备案号里preg_replace('/\D+/','')提取)。 - 全部输出走
$this->e()转义。 - ⚠️
include($tpl, $extra)的$extra只给extract()、不写回$this->data→ 组件内必须读局部变量,不能用$this->getData()。
2.4 验证
- 组件探针 22/22 PASS:皆空→徽标(class/style);仅 ICP;仅公安→
data-icon="shield"+code=…;两者→mn-beian单行 +margin-left:14px;XSS 转义;Xenforo 空 style 分支;纯空白→回徽标。零写入。 - HTTP 侧「移走组件 → 复现 → 恢复」在未被 PageCache 缓存的
/login上验证:50,223 B → 50,114 B → 50,223 B(109 B = 组件输出体积)→ 证明include真实生效。
> ⚠️ 首页 / 与 /forum 有磁盘缓存,直接冒烟会假阴性。
三、1.1.7 打包 —— 已出包
3.1 版本号机制(澄清)
- 站点根
version.php=V1.1.4属正常:打包器「站点根 version.php 不随建包改动」(cli/release_build.php:1139),包内version.php由--version生成(:1148)。 - ⇒ 打 1.1.7 无需改站点根
version.php,只传--version=1.1.7。
3.2 实际命令(★不用 --base)
cd /d/phpstudy_pro/WWW
. .workbuddy-ai/tmp/fh_key_env.sh # 导出 FH_RELEASE_KEY / _PASS(口令只从文件读)
"$PHP" cli/release_build.php --type=core --version=1.1.7 --dry-run --list # 预演
"$PHP" cli/release_build.php --type=core --version=1.1.7 --sign --key-id=flinthub-2026 # 正式
# 出包后必做:
"$PHP" -r 'is_file("data/runtime/update_check.json") && unlink("data/runtime/update_check.json");'
"$PHP" .workbuddy-ai/tmp/fh_pkg_vs_site.php # 必须 316/316 逐字节一致⚠️
--base是半成品:它只改 manifest 元数据(base_version/full)与改动说明文案,并不裁剪包内容 → 会产出「自称增量、实为全量」的误导性说明。故本次不加,与历史包(1.1.4~1.1.6 均full=true)保持一致。
3.3 产物与验收(2026-10-05 实出)
| 项 | 值 |
|---|---|
| 包 | release/core/flinthub-core-1.1.7.zip,1,853,151 B,sha256 1a52143c1ba1e6d2… |
| 清单 | release/core/manifest-1.1.7.json,317 条目,sha256 3e00f782b43c9098… |
| 签名 | RS256 / key_id=flinthub-2026 / 256 B,站点端自验通过 |
| latest.json | core → 1.1.7(全量包,base_version=null) |
| 门禁 | 9 项全过(#1 i18n 1539 键零差异、键序一致;#2 PHP 8.0/8.2 双版本 194 文件全过;#8 签名链路 PASS) |
| 包 ↔ 站点 | fh_pkg_vs_site.php:316/316 逐字节一致,0 偏差 0 缺失 |
| zip 结构 | 360 条目(318 文件 + 42 目录);data/、protected/ 各仅 .htaccess + web.config;无 config.php / plugins/ / assets/themes/ / assets/uploads/ |
包内文件数:纳入 316 = 1.1.6 的 315 个非 version.php 文件 + 新增 app/Views/_components/footer_beian.php;再加生成的 version.php → manifest 317 条。
3.4 1.1.7 包内容清单
index.php(SITE_URL 补丁 + 2 新路由)· Updater.php(applyInProgress) · UpdateController.php(resetState + deleteBackup) · DashboardController.php(dismissKeyWarn) · SettingsController.php(备案) · settings_basic.php · admin/index.php · admin/update.php · layouts/main.php · _components/footer_beian.php(新) · lang/{zh,en,zh_tw}.php
⚠️
assets/themes/Xenforo/不在核心包内(Updater::ASSETS_EXCLUDES),且核心包不含plugins/。Xenforo 主题的页脚同步改动不随核心包分发 —— 站长已明确「Xenforo 那个别管」。
3.5 发布后续(待办)
- 上传
release/到官网(/release/须公网可读,禁deny all)。 - 出包后清
data/runtime/update_check.json(已清,6h 缓存)。 - 同步
release/plugins/index.json:本次无插件改动 → 不动。
四、(新增) 后台「密钥仍在使用源码内默认值」告警 —— 已加「不再提示」
- 触发:
DashboardController.php读MAIL_PASS_KEY_IS_DEFAULT/CRON_KEY_IS_DEFAULT(定义在config.php,不进包)。 - 落地:设置项
key_warn_dismissed('1'= 全局关闭): index():$keyWarnOff = (Settings::get('key_warn_dismissed','0') === '1')→ 两个告警位与之取反。dismissKeyWarn():Csrf::verifyOrDie→setValue('key_warn_dismissed','1')→Settings::buildCache()→AuditLog→ PRG 重定向回/admin。- 视图
admin/index.php:告警条下加 POST 表单按钮(admin.key_warn_dismiss)。 - 路由:
index.php内/admin/dismiss-key-warn→POST。 - ⚠️ 上线新设置项必须清一次
settings_cache.php(buildCache()已在保存时调用)。
五、静态校验汇总
| 项 | 结果 |
|---|---|
php -l | 13 个改动文件全绿 |
php_var_cjk_check.php | 全 OK(无「全角标点紧跟 $var」隐患) |
fh_i18n_check.php | 55 PASS / 0 FAIL(核心三语各 1539 键、集合零差异、键序一致) |
| 路由注册 | POST /admin/dismiss-key-warn → 302、POST /admin/update/reset-state → 302(对照 /admin/nope-xyz → 404) |
| 清理 | 临时探针 2 个已删;冒烟生成的 forum_0_1_e1l180.html 已删;无 .fhbak 残留 |
未做(低优先,可补):任务一 B(resetState)与任务四(dismissKeyWarn)的端到端 HTTP 业务验证(路由层 302 已验)。