[开发]FlintHub 增量包改动总览 update/1 至 update/99

👑Lv.11 元老 🌞 骨干会员
2026-10-24 00:05:45

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:合并篇(见正文内 merged_from 列表)。

FlintHub 增量包改动总览(update/1 ~ update/99 · 2026-08-07 ~ 2026-10-02)

本篇由 38 份源文件汇总去重而成:35 份包说明(32 份编号增量包文件——29 个编号中 update/3、/12、/13 在 8 月 SoSite 期与 9 月 FlintHub 期各有一份——+ 08-07 基础整改包 + 1.1.0-beta1/beta2 两个全量包)+ 3 份附录/汇总文档(update/9 附带的 Hooks 目录、update/26 附带的社区更新公告、WWW/docs/升级说明.md)。按发布日期排列,保留每包改了什么,去掉逐包的安装/备份步骤重复说明。

编号约定:update/N 的目录号 N = version.php 版本末位(如 update/22 → V1.0.22)。注意 update/3、/12、/13 在 8 月 SoSite 期与 9 月 FlintHub 期各有一个同名包,内容不同,表中以"〔SoSite 期〕/版本号"区分。

汇总口径

  • 每包只提取"改了什么",逐包的备份步骤、覆盖顺序、回归清单、MD5/SHA256 校验表均不收录(需要时查各源文件)。
  • "类型"与"关键改动"为读原文后归纳,不照抄标题;同一包内多个并列改动用 <br> 分隔,便于表格阅读。
  • 版本号以各包 version.php 为准;站点根版本滞后于包内目标版本的情况(update/33~40 集中出现)在对应行注明。

一、总览

时间跨度阶段 / 版本区间包数主要方向
2026-08-07 ~ 08-11SoSite 阶段(整改包 + 8 个编号包,无 version.php)9代码整改与孤儿文件清理、安全加固(XSS/RCE/路径穿越/越权)、附件权限体系、通知中心与勋章中心两个首批大插件
2026-09-15 ~ 09-20V1.0.2 ~ V1.0.2511统计增量与查询次数优化、PHP 8.4/8.5 兼容、可视化编辑器增强、史上最大安全修复 update/20、附件体系改造、SEO 插件增强
2026-09-21 ~ 09-30V1.0.26 ~ V1.0.4612用户报障修复、楼中楼通知与深链、插件审计整改(P0+P1+P2)、云存储插件、六插件完整包、右栏改造、登录回跳五层贯通、SplitDB 核查整改、清理性能优化
2026-10-01 ~ 10-02V1.1.0(beta1/beta2)+ V1.0.993内置在线升级系统落地(RSA 签名清单 + 备份回滚),核心与插件分开发布,增量包转入留档

update/1 说明:无 version.php 与改动说明,包内 20 个文件(app/Helpers/Plugin.php、install.php、nginx-baota.conf、17 个 plugins/*/plugin.json)与 flinthub1.0.0.zip 逐字节一致,视为 V1.0.0 发行基线,本身不构成一次升级(据 WWW/docs/升级说明.md)。

8 月批次署名 SoSite(V5.0.0 阶段,以"覆盖部署说明"形式交付);9 月起改署名 FlintHub 并启用 V1.0.x 版本号体系(以"改动说明"形式交付)。

分阶段读法

  • 8 月(9 个包):以"整改 + 加固"为主——先把孤儿文件、内联样式、模板悬空引用清理干净,再逐项补安全洞(XSS、RCE、路径穿越、越权上传),同时把附件权限体系和通知中心、勋章中心两个基础设施插件立起来。这一阶段的包普遍不含 version.php,交付物以"文件清单 + 回归验证清单"为主。
  • 9 月上中旬(11 个包):转入 FlintHub V1.0.x 编号体系,重心转向性能与兼容性——统计改增量、查询次数收敛、PHP 8.4/8.5 兼容、编辑器增强;update/20 是这一阶段的顶点,一次交付 A/B/C/D 四批共 96 个文件的集中修复。
  • 9 月下旬(12 个包):从"修核心"扩展到"修插件 + 修数据层"——插件审计整改(P0+P1+P2 全量)、缤纷云存储、六插件完整包、楼中楼通知与深链、右栏签到解耦、登录回跳五层贯通、SplitDB 核查整改、孤儿附件清理性能优化,绝大多数包都带 version.php 并明确"覆盖后必须清页面缓存"的部署口径。
  • 10 月初(3 个包):1.1.0 落地内置在线升级系统,核心与插件分开发布;update/99 刻意把版本号停在 V1.0.99 以实测升级链路,两个 beta 全量包则给出"只含核心"的纯净包形态。

贯穿全程的部署共性(各包重复出现,此处统一说明)

  • config.php 是安装生成物、含站点本地参数,从 update/20 起一律不进包,需按各包说明手工合并新增段落。
  • 改了 CSS/JS 的包依赖两级破缓存:TemplateCompiler::asset() 的 filemtime() ?v= + sw.js 缓存名版本派生;只含 PHP 的包则连 sw.js 都不含。
  • 改了导航钩子、CSS 或 plugins_cache.json 的包,部署后普遍要求后台「维护 → 清理缓存」——data/runtime/pages/*.html 是永久缓存、不按 TTL 过期。
  • 涉及插件钩子/插件列表变化的包,plugins/plugins_cache.json 必须一起覆盖,否则新钩子不分发、后台入口不出现。
  • 增量包表达不了"删除文件",需要删除的包(update/20 的幽灵路由、update/28 的两个文件、update/39 的一个钩子文件等)都在说明里给出手工步骤。

二、改动总表

按发布日期排序;"关键改动"为读原文后归纳,保留模块名/方法名。来源路径相对 D:\phpstudy_pro\。

包号日期类型关键改动来源文件
整改更新包(8月12日备份根目录)08-07代码整改删除 9 个孤儿/死文件(app/Views/admin/settings.php 孤儿视图、schema_mysql/sqlite.sql 遗留建库脚本、blog/forum/pages/layout.css 零引用死文件)<br>新增通用 404 视图 app/Views/errors/404.php,修复 user_profile 悬空引用<br>前后台 16 个视图内联样式收敛为 mn-* / admin.css 工具类,移除 forum.css / pages.css / layout.css 引用<br>Helpers / Models 共 13 个零调用方法加 @deprecated 标注(纯注释)8月12日的备份/update/README-部署说明.md
update/3〔SoSite 期〕08-08安全修复Segmenter 分词过滤单字 token,防单字检索拖垮数据库<br>Thread.php / Blog.php 的 batchDelete 改动态占位符;附件删除前文件名白名单校验 + 孤儿清理跳过符号链接(防路径穿越)<br>TemplateCompiler 的 {if} 表达式禁止函数调用(防模板 RCE),保留 isset/empty 白名单<br>Permission 最小权限:权限体系已启用但用户组未配置时不再默认可见全站版块<br>editor.js 粘贴内容立即净化后插入,消除 200ms 恶意 HTML 渲染窗口8月12日的备份/update/3/README-部署说明.md
update/508-09功能前端库改名:alpine.min.js→sostate.js、htmx.min.js→soajax.js,主布局与安装页引用同步<br>后台基本信息新增"启用 Gzip 压缩输出"开关(enable_gzip,默认关闭)<br>app/init.php 在 enable_gzip=1 时 ob_start('ob_gzhandler'),按 Accept-Encoding 协商,只压文本类不影响二进制8月12日的备份/update/5/README-部署说明.md
update/708-09功能后台"功能开关"新增"开启邮箱验证"(email_verify_enabled,默认开启)<br>AuthController 注册/登录按开关走验证链路,关闭时注册即生效、未验证可登录<br>用户编辑页新增邮箱验证状态徽标 + "标记为已验证"手动通过(User::markEmailVerified())8月12日的备份/update/7/README-部署说明.md
update/908-09功能/权限category_permissions 新增 can_attach 列,后台版块权限矩阵新增"附件"列,逐用户组控制上传<br>新增 GET /attachment/{id} 下载入口,权限跟随版块 can_view,替代原 uploads 静态直链<br>下载前触发 attachment_download_before 钩子,预留积分/付费下载扩展<br>附带 sosite-plugin-dev/references/02-hooks-catalog.md 全量钩子目录(随包登记新钩子与 trigger 点)8月12日的备份/update/9/README-部署说明.md;同包 02-hooks-catalog.md
update/1108-11插件新增通知中心插件 plugins/notifications:聚合回复 / 点赞 / 积分变动 / 注册欢迎 / 审核结果五类,PC 顶栏 + 移动端双入口、未读徽标、进页自动已读<br>核心新增 vote_after / points_award_after / points_deduct_after 三个钩子<br>content_review 的 approve()/reject() 新增 review_result_after 广播,与自有通知表双写双保险<br>新表 notifications 由 Plugin::activate() 幂等创建8月12日的备份/update/11/README-部署说明.md
update/12〔SoSite 期〕08-11体验优化左栏 nav_plugin_links 钩子调用传入 __nav_uri,插件页面入口高亮 mn-cat-active<br>覆盖签到 / 骰子 / 图标 / 猜题 / 红包 5 个入口,路由前缀匹配天然覆盖子路由<br>移动端不传参天然跳过,行为不变8月12日的备份/update/12/README-部署说明.md
update/13〔SoSite 期〕08-11体验优化每日签到 / 积分红包 / 个人主页去掉 600~720px 限宽,占满 PC 中栏<br>modern.css 的 html 补 overflow-y:auto; scrollbar-gutter:stable,长短页切换宽度不跳动<br>详情页右栏发帖人信息栏按钮文案"个人中心"→"用户主页"8月12日的备份/update/13/README-部署说明.md
update/1408-11插件新增勋章中心插件 plugins/medal:后台定义勋章、手动颁发/回收、3 种自动规则(发帖≥N / 回帖≥N / 注册满 N 天 / 积分≥N)<br>用户可佩戴 1-3 枚,展示于帖子楼层 / 右栏 / 个人主页,controller_view_before 批量注入避免 N+1<br>新表 medals / user_medals / user_medal_wears 激活时幂等创建8月12日的备份/update/14/README-部署说明.md

| update/2 | 09-15 | 性能/机制 | Post / Thread 的 insert() 同步分支由 runtimeBuild() 改 runtimeIncr() 增量累加;Settings::runtimeIncr() 首次调用时惰性注册 flushCounters(防 CLI/cron 入口增量丢失)<br>reply_index 补 status 索引:新装内置 + marker 门控的存量站自动补丁<br>cli/worker.php / cron_trigger.php stats 合并节流 + 每日计数器校准兜底<br>部署红线:queue_mode 保持 cron,切勿改为 sync | WWW/update/2/改动说明.md |

| update/3(V1.0.3) | 09-15 | 兼容性 | PHP 8.4 起 session_set_save_handler() 传独立回调弃用:6 个闭包迁入新增 app/Core/SessionHandler.php(实现 SessionHandlerInterface)<br>删除已废弃的 curl_close() / imagedestroy() / finfo_close() 调用共 11 处,ViewCounter 补可空类型、CoverTemplate 补多字节前置判断<br>刻意不实现 SessionUpdateTimestampHandlerInterface,保持会话固定攻击防护判定路径不变;TTL 1800 秒与原实现一致<br>共 7 个文件、均为"删一个调用/补一个字符"级;已在 PHP 8.4.25 / 8.5.10 真机验证,向下兼容 8.0+ | WWW/update/3/改动说明.md |

| update/10 | 09-17 | 功能 | 编辑器快捷键体系:Ctrl+B/I/U/K、Ctrl+Shift+7/8/9、Ctrl+Enter 提交、Esc 退全屏;中文输入法组合期(isComposing/229)放行<br>新增代码块命令(<pre><code class="language-*">)+ pre>code 复制按钮(clipboard.writeText 优先、execCommand 回退)+ 快捷键帮助面板<br>删除线双向互通:htmlToMarkdown 注册自定义 ~~ 规则,MD_HIDDEN_CMDS 移除 strikeThrough<br>新增 _t() i18n 英文回退;修复复制按钮泄漏;sw.js CACHE_VERSION v1.0.1→v1.0.2 | WWW/update/10/改动说明.md |

| update/12(V1.0.12) | 09-17 | 功能/插件机制 | 新增通用侧栏钩子 sidebar_above_friend_links(PC right_sidebar.php 与移动端 mobile_sidebar.php 双端触发,友链始终垫底)<br>Plugin.php 新增 sort_order 稳定排序(sortPlugins())+ 插件编辑页"排序权重"在线编辑<br>修复"禁用友链插件后前台仍显示空面板":显隐判断加 Plugin::isActivated('friend_links')<br>申请友链入口图标 / 配色 / 留白优化(FA 系统图标 + 文字加深) | WWW/update/12/改动说明.md |

| update/13(V1.0.13) | 09-17 | 缺陷修复 | 修复网页粘贴带入的内联 color 落入 <pre> 代码块的"深底深字"不可读(实测 #1e1e2e 底 + #334155 字)<br>Content::sanitizeCssStyle() 增加代码块模式、新增 inCodeBlock() 判定,代码块内剥离 color / background-color<br>改在渲染期净化层执行(ThreadController → Content::formatPostContent()),存量帖立即生效、无需重新编辑、不改已存内容 | WWW/update/13/改动说明.md |

| update/18 | 09-17 | 性能优化 | 新增请求级缓存 app/Helpers/UserPrefs.php(一次查询取 theme / night_theme_auto / hide_right_sidebar / home_mode 等 6 字段);Theme 三处 + ThemeController::readPref() 改读它<br>Settings::reconcileWithDb() 复用已加载全量 KV + 60s 对账节流;新增 getUnreadBadgeCounts() 一条 UNION ALL 合并通知+私信未读数<br>Thread::decorate() 同请求同 uid/cid 只查一次;Points::getLevelConfig() 改一次性载入整表(文件缓存 + 1 小时 TTL)<br>优化前实测(登录态)首页 24 / 论坛 23 / 详情 29 / 主题设置 21 次查询,本包逐项收敛 | WWW/update/18/改动说明.md |

| update/20 | 09-18 | 安全修复+机制修复(四批,体量最大) | A 批 8 项安全止血:存储型 XSS(净化器删 raw-text 元素)、分页反射型 XSS、附件上传越权、后台封禁无效、$_GET 数组参数 500、APCu 日志刷屏、密钥治理(代码侧)、幽灵路由<br>B 批 7 组机制修复:计数器漂移三因同修、跨库写路径原子性(ExternStorage/Merger 锁与缓存)、后台审计补全、插件设置写入权限下沉、RateLimiter 原子化、权限默认拒绝、改密吊销记住登录、CSRF 时效等<br>C 批 9 项契约断裂专项:权限矩阵回跳丢版块、插件分类静默丢弃、自动扩容阈值被改写、博客封面无法移除、搜索用户标签空白、搜索高亮双重转义、分页丢筛选参数、私信收发件人恒为空、重置密码页误收起表单<br>D 批 D1~D9 批量硬化:模板表达式校验绕过封堵、BASE_PATH 家族收敛、script/CSS 上下文口径、服务端长度校验、LIKE ESCAPE 修复搜索漏检、通知聚合计数、sw.js 缓存名改由站点版本号派生<br>共 96 个代码文件(含根目录 version.php、sw.js、install.php),约 17 万字 | WWW/update/20/改动说明.md |

| update/22 | 09-20 | 功能改造 | 附件体系改造:Upload 单一收敛 API(url()/path()/thumbUrl()/formatSize()),全类型按月分目录 YYYY-MM/,缩略图双档(thumb_ 400×300 / md_ 800×600)<br>新增 AttachmentController:版块权限校验 + ?s=thumb 直出 + 分级 Cache-Control + download_count 原子自增(attachments 新列自动补齐)<br>列表摘要图改读缩略图 + 摘要图网格规范化;正文图渲染期分组;新增 lightbox.js 灯箱(全页连读 + 预载 + 键盘/滑动 + 缩放)<br>C 线:后台回收站只读预览 + 彻底删除补审计日志(内容随后独立成 update/23)<br>附件卡 / 灯箱两个公共组件 + cli/migrate_uploads_by_month.php 迁移工具(本次不跑) | WWW/update/22/改动说明.md |

| update/23 | 09-20 | 功能 | Thread 新增 getDeletedById($id):镜像 getById() 但只接受 status=STATUS_DELETED 索引行并带出 deleted_at;唯一调用点在管理员校验之后,越权面收敛为 0<br>/thread/{id}?preview_deleted=1 管理员只读预览:主帖 + 全部回复 + 附件,跳过浏览量计数与惰性归档、强制 canReply=false、隐藏一切写入口<br>回收站列表标题链接改带 preview_deleted=1;getById() / show() 既有行为一字未改 | WWW/update/23/改动说明.md |

| update/24 | 09-20 | 功能 | 门户首页(site_mode=portal)新增第三种首页模式 cms(杂志式门户):头条大图 + 热读榜 + 图文网格 + 博客封面卡 + 版块栏目 + 标签云<br>Theme::getHomeMode() 站点默认 / 用户偏好 / 注释共 3 处白名单加 cms;HomeController 视图映射 home/index_cms + 游客前置缓存键 home_1_m<br>数据全部取自 HomeController::index() 已聚合变量,不新增任何查询;portal / community 既有行为一字未改 | WWW/update/24/改动说明.md |

| update/25 | 09-20 | 插件增强 | SEO 插件 A 档:noindex 指令、canonical 修正、Open Graph 基础标签与首图、description 摘要质量、sitemap 转义与 lastmod<br>B 档批 1:JSON-LD 结构化数据(DiscussionForumPosting / Article / WebSite);批 2:Twitter Card、BreadcrumbList 面包屑、作者权威信号、文章元数据 meta<br>批 3:llms.txt(AI 爬虫内容索引,文件缓存 TTL 60min)+ data/ 目录防护文件 + robots.txt 注释声明<br>插件版本 1.2.0→1.5.0;controller_view_before 的 stash() 暂存已加载行,省掉一次重查询 | WWW/update/25/改动说明.md |

| update/26 | 09-21 | 缺陷修复(用户报障) | 退出登录 403:Csrf::verify() 校验通过后滑动续期 csrf_time;AuthController::logout() 由 verifyOrDie() 改 verify() + 审计 + 降级放行<br>编辑器粘贴后光标消失:insertAtCursor() 插入前捕获 lastNode,连带修复外链图片本地化永久失效<br>工具栏插入的代码块无复制按钮:afterEdit() 收编 attachCopyButtons()<br>删除线双向不通:抽 STRIKE_TAGS 常量 + isSelectionInAnyTag(),覆盖 <s>/<del>/<strike><br>通知删除后铃铛徽标残留:新增 Notification::invalidateUnreadCache(),接入 add() / markAllRead() / deleteByIds()<br>附带面向用户的《社区更新公告》(6 条口径一致的说明) | WWW/update/26/改动说明.md;同目录 社区更新公告.md |

| update/27 | 09-21 | 功能 | 楼中楼通知:Notification::notifyFloorReply()(约 75 行),楼层回复 / 被 @ / 通知楼主三类开关可配(默认楼主关)<br>三段式深链:通知链接带 ?pid= 经 Post::pageOf() 换算页码;楼层容器加 id="post-{pid}" 锚点;楼中楼列表支持 ?focus=<id> 定位高亮<br>floor_reply 补后台设置页(新增 AdminController + admin_route_register 路由 + 插件三语 11 键),assets/script.js 整体重写(196 行)<br>核心 8 文件 + 插件 17 文件;fr_replies 表结构未动 | WWW/update/27/改动说明.md |

| update/28 | 09-21 | 安全/规范整改(插件审计) | 按《插件审计报告》(2026-09-21,28 插件 / 390 PHP 文件)一次修完 P0(2) + P1(121) + P2(119),共 24 个整改项<br>User.php 只加不改:getProfileById()(受限列集合,不含 password / email_verify_token)与 setStatus(),收敛插件侧 13 处对 users 表直读直写<br>invite P0:beginTransaction() + catch 内 inTransaction() 守卫,负数 award() 改 Points::deduct() 失败回滚;插件建表统一 Plugin::ensureSchema()<br>15 个插件 CSS 主题变量化;icon_generator 71 键 ×3 语 i18n 前缀改名(plugin.icon_gen.→plugin.icon_generator.);重建 plugins_cache.json 移除 post_favorite 死钩子<br>64 个文件(44 PHP / 3 JS / 15 CSS / 2 JSON)+ 2 个必须手工删除的文件 + version.php V1.0.28 | WWW/update/28/改动说明.md |

| update/29 | 09-22 | 插件(云存储) | 新增缤纷云 Bitiful S4 对象存储插件:附件本地落盘后镜像到公开桶,前台 URL 改写为 CDN 地址,未同步文件自动回退本地<br>S3Client(纯函数 SigV4 签名 + 6 个 API)+ SecretBox(AES-256-GCM,主密钥四来源解析)+ SyncQueue(1115 行:入队/原子取批/退避失败/排空)+ UrlRewriter(白名单文件缓存 + 六道闸门)<br>核心仅 5 处"只加不改语义":Upload::url()/thumbUrl() 过滤钩子 + upload_after 事件钩子 + Plugin::http() 任意 HTTP 方法与 headers 返回<br>8 钩子 + 3 权限(route:admin/system:settings/net:fetch);默认未启用,后台停用即整体回退本地 | WWW/update/29/改动说明.md |

| update/30 | 09-22 | 插件完整包 | 完整收录积分商城 / 摇骰子 / 猜题对决 / 问答社区 / 信任等级 / 第三方登录六个插件的全部代码 + 6 份说明.md(139 项)<br>修复 3 个插件目录名含下划线导致核心生命周期回调静默失效(class_alias 兼容别名:points_mall / quiz_duel / third_party_login)<br>第三方登录 3 处运行期缺陷:CSS 未定义变量(夜间可读性)、短信登录成功后误报"验证码错误"、init_after 每请求 6 条 DDL<br>127 项原样收录、实际改动 12 项;不含插件 data/ 独立库与建表戳,覆盖不碰服务器插件数据 | WWW/update/30/改动说明.md |

| update/33 | 09-23 | 缺陷修复 | 限流 429 按当前记录算真实剩余等待秒数(RateLimiter::waitFromRecords():需过期 N-max+1 条,取升序第 N-max 条时间戳),不再报整窗口值<br>Retry-After 头、页面提示、JSON retry_after、登录 IP 锁定倒计时四处口径统一<br>三语 lang 去掉写死"15 分钟",改 {seconds} 占位符;version.php V1.0.31→V1.0.33(跳过 1.0.32) | WWW/update/33/改动说明.md |

| update/35 | 09-26 | 缺陷修复 | 后台备份列表操作列"删除"按钮整块不可见、"恢复"被切一半:.admin-table td:nth-child(4) 描述列限宽规则按列序硬编码,误伤操作列<br>修法一:admin-table-actions 定向豁免限宽(max-width:none; overflow:visible);修法二:恢复确认框默认收起、点一次才展开(x-data="{armed:false}")<br>diff 仅 +6/−3 与 +11 行;后台页不走 PageCache,无需清缓存 | WWW/update/35/改动说明.md |

| update/38 | 09-27 | 缺陷修复 | /tag/{id} 分页是死代码:版块权限过滤后用"本页条数"覆盖"全站总数",ceil(total/perPage) 恒为 1,pagination() 直接返回空串;连带"共 N 帖"标题失真<br>Tag::getThreads() 新增第 4 参 $allowedCategoryIds:全量取回→过滤→排序(抽 sortThreads())→切片,按过滤后真实总数返回 total<br>/tags 标签云补分页:getAllWithCount() 支持 limit/offset,新增 countAll() / maxThreadCount(),视图挂 pagination() | WWW/update/38/改动说明.md |

| update/39 | 09-27 | 功能/解耦 | 核心右栏删除硬编码 include 签到模板与 #checkinHint 块——未装插件时页面显示键名(plugin.daily_checkin.title)而非空白<br>用户栏"签到"位改为发帖按钮,签到插件改右栏周视图面板(周一→周日 7 格),钩子由 controller_view_before 改挂 right_sidebar_after 自取数据<br>新增用户栏内通用钩子 right_sidebar_user_actions_above(.mn-user-quick 2 列网格),与 right_sidebar_after(整块面板)位置粒度不同、不可互替<br>建议手工删除 daily_checkin/hook/controller_view_before.php(增量包表达不了删除,不删也无功能影响) | WWW/update/39/改动说明.md |

| update/40 | 09-28 | 缺陷修复(五层贯通) | 新增唯一回跳守卫 Auth::sanitizeRedirectTarget()(只允许站内相对路径,堵开放重定向),配套 sanitizeQuery() / captureRedirectTarget() / loginUrl()<br>Csrf::verifyOrDie() 未登录改跳登录并记录来源(不再是 403 死胡同);回帖 POST 改"先 requireLogin 后 CSRF"<br>核心 / 视图 / API / JS / 插件五批 27 项:ApiController 401 附 login_url、auth.js 新增 htmx:responseError 处理器、editor.js 新增 redirectOnAuthError()(原生 XHR 不触发 htmx 事件)<br>后台补齐 admin_redirect_after_verify 唯一消费点;12 个插件视图登录链接统一改 $this->loginUrl() | WWW/update/40/改动说明.md |

| update/45 | 09-29 | 安全/数据修复(核查整改) | 对账《核查结果和修复建议报告》P0/P1/P2/P3 共 20 项,18 项实施、2 项因技术前提不成立暂缓<br>F-02 归档合并与编辑竞态(可静默丢失正文):ExternMerger::mergeGroup() 重构为全程同一 .lock 临界区<br>M-05 锁失败必须中止(不再退化为"尽力而为");Queue / QueueConsumer / ShardRouter / DBFactory / ViewCounter / Schema / SearchIndexStore 等 13 个 SplitDB 文件同批对齐<br>APP 系列:Permission 新增解释层,首页/论坛/搜索/个人中心控制器统一改用;editor.js / auth.js / fhajax.js 三处前端修复<br>当日追加修复"帖子详情页浏览量恒为 0"(Thread.php / ThreadController.php / ViewCounter.php 三个镜像追加进包) | WWW/update/45/改动说明.md |

| update/46 | 09-30 | 性能优化 | "清理孤儿附件"耗时从 ~3.2s 涨到 ~9.5s,微基准证明瓶颈是每个全新 SQLite 连接的首个查询(同连接重复 COUNT 仅 0.8~1.1ms),而非 I/O 总量<br>方案 A1:第 4 段 bucket 枚举改 data/extern 直扫;方案 A2:抽 scanPluginUploads() 并加插件库动态白名单<br>Settings.php 新增 6 个方法 + 2 个常量;维护页新增"重建插件扫描白名单"按钮<br>仅 6 个文件 + version.php V1.0.46,sha256 逐字节一致 | WWW/update/46/改动说明.md |

| update/99 | 10-02 | 功能(在线升级系统) | V1.1.0 主体新增后台「系统升级」页:检查更新 → 差异预览 → 环境预检 → 取包校验 → 备份 → 应用 → 回滚,全流程无需 SSH<br>分层 P0~P4:版本比对 / latest.json 解析、差异预览(新增/修改/删除/冲突)、9+1 项预检(含版本区间校验)、写盘前三道门禁、备份+双路径回滚+RSA-2048 清单签名(默认 fail-closed,公钥内置 app/Helpers/release_keys.php)<br>version.php 刻意写 V1.0.99(代码已是 1.1.0),用于实测"老站 → 手工覆盖 → 在线升级"完整链路<br>四次同步修复:新增"删除备份"端点(三层安全)、"备份"按钮取包守卫、apply() 进度游标 offset 漏数 version.php 导致"19/20 却已完成"、删除守卫过窄化 + 错误信息改渲染在「回滚」区<br>1.1.0 起改发全量包 release/core/flinthub-core-1.1.0.zip,本包仅作留档 | WWW/update/99/改动说明.md |

| flinthub1.1.0-beta1(全量包) | 10-01 | 全量包 | FlintHub 1.1.0-beta1 纯净包:312 文件,只含核心(app/ assets/ lang/ cli/ docs/),不含任何插件与第三方主题<br>主体改动同 V1.1.0 在线升级系统:清单 RSA-2048/RS256 签名、openssl_verify() 验签、fail-closed 拒收未签名清单<br>不含 plugins/、assets/themes/、assets/uploads/、config.php 与 data/、protected/ 实际内容<br>可全新安装(install.php)或覆盖升级现有站点 | _flinthub_beta_backup_20261002/core/flinthub1.1.0-beta1/改动说明.md |

| flinthub1.1.0-beta2(全量包) | 10-01 | 全量包 | 1.1.0-beta2 纯净包(312 文件),为全量包口径,逐项改动详见仓库 CHANGELOG.md<br>升级后建议:后台「维护 → 清理缓存」、本机先跑 php cli/update_probe.php 看差异、覆盖前备份 config.php 与 data/<br>不含 plugins/ 与 assets/themes/——覆盖升级不会动已有插件目录与第三方主题 | _flinthub_beta_backup_20261002/core/flinthub1.1.0-beta2/改动说明.md |

跨包交叉参考一:数据库结构变更汇总(全部走幂等补丁,无手工迁移脚本)

包号变更落地方式
update/2reply_index 补 status 索引SplitDB/Schema.php 新装内置 + marker 门控的存量站自动补丁
update/9category_permissions 新增 can_attach 列(默认 1,不破坏存量行为)Permission::ensureCanAttachColumn() 幂等,后台保存权限时触发
update/11新表 notifications(user_id/type/title/link/is_read 等)插件 Plugin::activate() 建表 + hook/init_after.php 懒建兜底
update/14新表 medals / user_medals / user_medal_wears(唯一键防重复颁发/佩戴)激活时 CREATE TABLE IF NOT EXISTS 双保险幂等执行
update/20notifications 增 agg_count 列;tags.name 补 UNIQUE 索引 uk_tags_name(先合并重复再建);PRAGMA user_version 建表版本标记均走 ensureColumnPatches 首次访问自动补齐
update/22attachments 增 download_count 列Schema.php 补丁清单同步,存量库首次请求自动 ALTER
update/27~28、30、45、46核心均无表结构变更—
update/29插件 bs_objects / bs_logs 两表Plugin::ensureSchema() 幂等建,老站不存在该插件、首次启用才建

跨包交叉参考二:新增插件钩子一览(钩子机制见 update/9 附带的 Hooks 目录)

包号新增钩子 / 扩展点用途
update/9attachment_download_before(附件下载输出前)积分/付费下载等下载前拦截,约定 $GLOBALS['attachment_download_error'] 非空即 403
update/11vote_after、points_award_after、points_deduct_after(核心)+ review_result_after(content_review 插件)通知中心聚合点赞、积分变动、审核结果
update/12sidebar_above_friend_links(PC + 移动端右栏)插件在友链面板上方追加内容面板
update/29upload_url / upload_thumb_url(过滤,引用传参)+ upload_after(事件,6 参数)云存储 URL 改写与同步队列的唯一入口
update/39right_sidebar_user_actions_above(用户栏内,2 列网格)在用户栏操作按钮上方放日常操作小按钮

跨包交叉参考三:version.php 与缓存机制演进

阶段机制
8 月各包无 version.php;部署后清 protected/cache/templates/ 模板编译缓存
update/10~18sw.js 的 CACHE_VERSION 手工递增(v1.0.1 → v1.0.2 → v1.0.3)
update/20 起sw.js 缓存名改由站点版本号派生(注册时带 ?v=<FLINTHUB_VERSION>)——升 version.php 即自动换缓存名、清旧缓存,老访客无需手动强刷
update/22 起CSS/JS 破缓存双保险:TemplateCompiler::asset() 用 filemtime() 生成 ?v= + SW 缓存名派生
update/25 起游客列表页 data/runtime/pages/*.html 为永久缓存、不按 TTL 过期,后台保存类操作需主动 PageCache::invalidate(),部署后普遍要求"维护 → 清理缓存"

跨包交叉参考四:新增 settings 配置键一览(均按"缺失即默认"处理,老库不补键也能工作)

包号配置键默认
update/5enable_gzip0(关闭,不改变现状)
update/7email_verify_enabled1(与原行为一致)
update/25seo_enable_og / seo_og_default_image / seo_enable_llms开 / 空(用站点图标兜底)/ 开
update/27floor_reply_notify_floor_author / _notify_mention / _notify_thread_owner / _auto_expand1 / 1 / 0 / 1
update/29bitiful_storage_* 共 13 个由 Plugin::DEFAULTS 种入,键存在且非空则不动
update/12插件级 sort_order(plugin.json 字段,非 settings 键)未声明按 0,对既有插件零影响

跨包交叉参考五:三语 i18n 字典规模变化(核心三语始终保持键集合一致、键序一致)

包号变化
update/10核心补编辑器新增 i18n key(简/繁/英三份同步)
update/20核心三语同步新增 5 键(各 1303 键,集合完全一致);C 批再同步 blog.cover_remove(各 1302 键)
update/27核心 1340 → 1343 键;floor_reply 插件 18 → 29 键
update/28插件补 1 键(plugin.code_audit.truncated,43 → 44 键);核心字典键集合零变化(1343 键)
update/29新增插件三语各 122 键(从 0 起);核心字典零变化
update/33三语 auth.lock_ip 去掉写死"15 分钟",改 {seconds} 占位
update/99核心三语各 1450 键(本包 +4 键,键序一致)

三、重点改动详述

3.1 update/20(09-18)——A/B/C/D 四批集中修复(体量最大)

改了什么问题

  • 上线前安全审计集中暴露四类问题:A 批是可被直接利用的安全漏洞;B 批是计数器漂移、跨库写路径原子性等机制缺陷;C 批是前端与后端契约断裂导致的功能失灵;D 批是全项目扫描出的批量硬化项。

怎么改的

  • A 批(8 项止血):Content.php 净化器整块删除 raw-text 元素 + 输出侧 CDATA 兜底(S-1 存储型 XSS);TemplateCompiler::pagination() 三出口统一转义 + SearchController::$type 加白名单(分页反射型 XSS);ApiController 的 /api/upload、/api/fetch-image 上传权限默认拒绝(越权);Auth::getCurrentUser() / getCurrentUserWithPassword() / RememberMe / AuthController 四处补封禁拦截;index.php 规整 $_GET 标量并删除 /admin/database/convert 幽灵路由;ViewCounter 新增 apcuUsable() 真实探测(APCu 日志刷屏);config.php 的 MAIL_PASS_KEY/CRON_KEY 支持环境变量与 protected/*_key.php 覆盖并在仪表盘渲染告警条。
  • B 批(7 组机制):计数器漂移三因同修——Post::softDelete/restore 下沉维护 reply_count + 幂等守卫,Thread::hardDelete/batchDelete 只递减未软删主题;ExternStorage/ExternMerger/ExternIdxRepairer 锁与缓存口径对齐(同一临界区 + 写完 apcu_delete);UserController/PluginController/GroupController/TrashedController/CategoryController/DatabaseController 共 7 处后台操作补审计日志(带 before 值);Setting::setValue() 用 debug_backtrace 反查 plugins/<name>/ 做请求级缓存,插件设置写入权限下沉;RateLimiter 把"读-判断-追加"收进同一 LOCK_EX 临界区、损坏 json 保守拒绝;Permission 无权限记录默认拒绝(仅整表为空时兜底放行);改密成功后 RememberMe::clearTokens();Vote 改条件写。
  • C 批(9 项契约断裂):GroupController 权限矩阵保存回跳携带 category_id;PluginController 编辑保存透传 category;DatabaseController 自动扩容阈值改"字段存在才写";blog/edit.php 补"移除现有封面"复选框;search/index.php 补用户结果卡片分支、3 处高亮改传原始文本去双重转义、分页 URL 补 time_range;blog/index.php 分页携带 archive;Message/MessageController 收发件箱别名对齐 sender_username/receiver_username;AuthController 的 tokenValid 不再被表单校验错误驱动。
  • D 批(D1~D9 批量硬化):TemplateCompiler::safeExpr() 由"只查第一个函数调用"改 preg_match_all 遍历全部调用(封堵 isset($x) && system("id") 绕过);新增 cssValue() 统一 CSS 上下文口径、JS 统一 json_encode + JSON_HEX_*;抽 currentPath() 收敛 9 处 REQUEST_URI 直读;新增 Content::lengthError() 统一服务端长度校验收敛 7 个写入点;SQLite LIKE 显式 ESCAPE '\' 修复含 %/_ 搜索漏检;notifications 增 agg_count 列;sw.js 缓存名改由站点版本号派生——顺带解决"老访客一直命中旧 CSS"。

影响范围

  • 96 个代码文件(A 批 22 + B 批 29 − 重叠 3 + C 批新增 5 + D 批持续追加),覆盖核心、三语字典(各 1300+ 键)、多个插件;config.php 因含站点本地参数被移出包外需手工合并;D 批为增量批次"进行中",后续多包(22/26/33/45)都建立在它建立的机制之上。

关键文件与位置

  • app/Helpers/Content.php(A1 净化器)、app/Core/TemplateCompiler.php(A2/A5 分页转义、D7 safeExpr() 全调用遍历)
  • app/Controllers/ApiController.php(A3 上传默认拒绝)、app/Helpers/Auth.php 与 RememberMe.php(A4 封禁拦截)
  • app/Helpers/Settings.php(B1 计数器/B5 插件写入校验)、app/Models/Setting.php(B5 debug_backtrace 反查插件来源)
  • app/SplitDB/ExternStorage.php / ExternMerger.php / ExternIdxRepairer.php(B3 锁与缓存口径对齐)、app/Helpers/RateLimiter.php(B6 原子化)
  • app/Core/Model.php(B7/M-5 列白名单)、app/Helpers/Csrf.php(时效判定)、index.php(A5 规整 $_GET / A8 幽灵路由)

遗留事项

  • D 批自述"增量批次、进行中、尚未冻结",后续由 update/22 起的各包继续衔接;B 批 M-10 的 points_log 唯一索引经评估不做——会造成回帖/摇骰/邀请码等正常重复业务写入失败。
  • config.php 因含站点本地参数被移出包外,新增段落需按第七节手工合并(后续多个包沿用同一约定)。

3.2 update/22 + update/23(09-20)——附件体系改造与回收站只读预览

改了什么问题

  • 附件路径此前由各控制器/助手自行拼字符串,头像、封面、正文图、附件各写一套;列表摘要图直接读原图浪费带宽;灯箱关闭按钮点击无反应;后台回收站点标题必然落到 /?msg=not_found,必须"恢复"后才能看内容。

怎么改的

  • app/Helpers/Upload.php 成为唯一收敛层:url()/path()/absPath()/thumbUrl()/formatSize()/makeRelPath(),file() 分月落盘(YYYY-MM/),makeThumbnail() 双档(thumb_ 400×300 / md_ 800×600);博客封面收敛到 covers/YYYY-MM/(新增 CoverTemplate.php),头像落 users.avatar 存相对路径。
  • 新增 AttachmentController:版块权限校验(改读 main_index.topic_index)、?s=thumb 缩略图直出、分级 Cache-Control、download_count 原子自增(attachments 新列走 ensureColumnPatches 自动补齐);路径全部走 Upload::path()。
  • 视图层新增 uploadUrl()/uploadThumb()/excerptThumbs() 三个入口和 attachment_card.php、image_lightbox.php 两个公共组件;Content 净化器 img 分支 src→md_、原图进 data-fullurl(幂等);新增 lightbox.js(全页连读 + 预载 + 键盘/滑动 + 缩放,closest() 判点击修掉 × 失效)。
  • update/23 把 22 的 C 线独立成包:Thread::getDeletedById() 与 getById() 完全同一条装饰路径,但只接受 status=STATUS_DELETED 索引行;ThreadController::show() 顶部加 ?preview_deleted=1 管理员预览分支(跳过浏览量计数与惰性归档标记、强制 canReply=false);视图加只读横幅并隐藏编辑/删除/引用/回复表单/点赞等全部写入口,分页链接带上 preview_deleted=1。

影响范围

  • update/22:48 个代码文件 + version.php + 9 个 md_ 缩略图;存量上传文件可由 cli/migrate_uploads_by_month.php 迁移(本次不跑)。update/23:7 个代码文件 + 三语各 3 键;软删数据本就完整保留(桶行、extern 正文、回复、附件原样在),"看不到"纯粹是读取入口被挡住,故预览功能不需要任何迁移或重建索引;getById() 作为全站读取入口保持原样,避免把"读到已删帖"的能力开放给所有调用点。

关键文件与位置

  • app/Helpers/Upload.php(唯一收敛层)、app/Controllers/AttachmentController.php(新增)、app/Helpers/CoverTemplate.php(新增,封面路径收敛 covers/YYYY-MM/)
  • app/Views/_components/attachment_card.php、image_lightbox.php(两个新增公共组件)、assets/js/lightbox.js(新增)、assets/js/thread.js(新增,事件委托)
  • update/23:app/Models/Thread.php(getDeletedById())、app/Controllers/ThreadController.php(预览分支)、app/Views/thread/show.php(只读横幅 + 隐藏写入口)、app/Views/admin/trashed.php(链接带 preview_deleted=1)

遗留事项

  • 旧上传文件的按月迁移工具 cli/migrate_uploads_by_month.php 本次不跑,按站点存量规模另行安排;正文图 md_ 回填由 cli/backfill_md_thumbs.php 幂等执行(生成后比 filesize() 大则回退原图)。

3.3 update/26(09-21)——用户报障修复(附社区更新公告)

改了什么问题

  • 站长实测反馈 + 《四问题核查与解决方案》列出五类用户可感知缺陷:退出登录 403、粘贴后光标消失、代码块无复制按钮、删除线不通、通知徽标残留。

怎么改的

  • 退出登录 403:根因是 CSRF 用"固定签发窗口"——Csrf::token() 只在超时轮换,verify() 却按 csrf_time + 1800s 判死,页面渲染满 30 分钟必 403;Token 又是会话级单值,任何页面渲染轮换都会废掉其他标签页的旧 Token。修法三件套:Csrf::verify() 校验通过后滑动续期 csrf_time;AuthController::logout() 由 Csrf::verifyOrDie() 改 verify() + 审计 + 降级放行(失效也能正常退出);Session 过期重建无 CSRF 的场景一并覆盖。
  • 粘贴后光标消失:insertAtCursor() 在 range.insertNode(frag) 之后才取 frag.lastChild——insertNode 会把 fragment 子节点全部搬入文档,此后 frag.lastChild 恒为 null,回退到 DocumentFragment 自身又导致 setStartAfter() 抛 InvalidNodeTypeError,异常发生在 removeAllRanges() 之后,选区已清未能重建。修法:插入前先捕获 lastNode;同源的 removeInlineFormatting()("清除格式"后光标丢失)一并修复;该异常原本还中断了 processExternalImages(),故"外链图片本地化永久失效"被连带修好。
  • 代码块无复制按钮:attachCopyButtons() 原本只有初始化与 MD/可视化切换两个调用点,工具栏 codeblock 命令走 afterEdit() 路径 → 新插的代码块没按钮。修法:afterEdit() 收编 attachCopyButtons()。
  • 删除线不通:COMMANDS.strikeThrough 用 toggleWrap('s') 只判 <s>,而 marked 产出 <del>、execCommand 产出 <strike> → 误判再套一层;updateBtnStates() 漏 <del> → 按钮不高亮。修法:抽 STRIKE_TAGS 常量 + 新增 isSelectionInAnyTag(),两处共用。
  • 通知徽标残留:未读徽标有 Session 级(30s TTL)+ Settings 请求级两层缓存,deleteByIds() 两层都没清。修法:新增 Notification::invalidateUnreadCache() 接入 add()/markAllRead()/deleteByIds(),通知页 POST 写操作后再兜底失效。

影响范围

  • 5 个代码文件 + version.php(V1.0.23→V1.0.26),全部为修改;未改任何 lang / CSS / 视图;随包附带面向用户的《社区更新公告》,把六条修复翻译成用户语言(含"清除格式"与"快捷键留待下一版"的明确取舍)。破缓存无需人工:asset() 的 filemtime ?v= + SW 缓存名派生两层自动生效。

关键文件与位置

  • assets/js/editor.js(4 处:insertAtCursor() 捕获 lastNode、removeInlineFormatting() 同源补漏、STRIKE_TAGS + isSelectionInAnyTag()、afterEdit() 收编 attachCopyButtons())
  • app/Helpers/Csrf.php(verify() 滑动续期)、app/Controllers/AuthController.php(logout() 降级放行 + 审计)
  • app/Helpers/Notification.php、app/Controllers/NotificationController.php(徽标缓存两层失效)

取舍说明

  • 本批明确不含"3.1 快捷键"与"3.4 i18n 英文回退"两项(用户明确本批不做),留待后续版本;按核查报告推荐方案执行,未重新审计、未扩大问题范围。

3.4 update/27(09-21)——楼中楼通知 + 三段式深链直达

改了什么问题

  • floor_reply(楼中楼)的写入路径是"独立库 fr_replies → 核心计数联动 → 清页面缓存",从头到尾不调 Notification::add()——在楼中楼被回复或被 @,对方完全没有感知;且通知链接一直是 /thread/{id} 裸链接落在第 1 页,目标楼层不在第 1 页时"点通知进去找不到刚才那条"。

怎么改的

  • Notification 新增 const TYPE_FLOOR_REPLY 与 notifyFloorReply()(约 75 行)+ 私有 floorReplyNotifyOwner();floor_reply 的 FrontController::reply() 保存 addReply() 返回的 $replyId 并调用它;三类通知(楼层回复 / 被 @ / 通知楼主)各配一个开关,默认楼主关,activate() 只写缺失键、老站不写也按默认兜底。
  • 三段式深链:① 通知链接带 ?pid=,ThreadController::show() 用新增的 Post::pageOf(int $threadId, int $postId, int $perPage = 20) 换算页码并夹到 [1, $totalPages];② 楼层容器加 id="post-{pid}" 锚点(此前整帖只有一个 post-body-{threadId},没有任何按楼层的锚点);③ 楼中楼列表支持 ?focus=<id>,条目加 id="fr-{rid}"、命中加 fr-focus 类(新增一条 CSS 规则)。
  • 插件补后台设置页:新增 AdminController + hook/admin_route_register.php(GET /floor-reply → index、POST /floor-reply/save → save)+ 表单视图,照 seo 插件范式补 getAllSettings()/saveSettings()/isEnabled();plugin.json 版本 1.0.0→1.1.1、icon 改核心图标表内的 chat、permissions 补 ["route:admin","system:settings"];assets/script.js 整体重写(196 行:deepPid/deepFr 解析、deepLink()、focusItem()、loading 防重复请求)。

影响范围

  • 核心 8 文件(含三语各 +3 键,1340→1343 键)+ 插件 17 文件(含三语各 +11 键,18→29 键);fr_replies 表结构与索引完全未动、无数据迁移;plugins/plugins_cache.json 必须一起覆盖(否则 admin_route_register 钩子不分发、后台入口不出现);部署后必须清缓存(游客列表页是永久缓存)。

关键文件与位置

  • 核心:app/Helpers/Notification.php(notifyFloorReply())、app/Models/Post.php(pageOf())、app/Controllers/ThreadController.php(?pid= 换算)、app/Views/thread/show.php(楼层锚点)、app/Views/notifications/index.php(图标/配色补 3 项)
  • 插件:plugins/floor_reply/FrontController.php(通知 + focus)、Plugin.php(设置范式 + pageOfReply())、AdminController.php 与 views/admin.php(新增)、assets/script.js(重写)、assets/style.css(+1 条 .fr-item.fr-focus 规则)

遗留事项

  • plugins/plugins_cache.json 重建后的 diff 确认仅 floor_reply 变化、其余 27 个插件字节一致;回滚以 update/26 同名文件逐个覆盖即可。

3.5 update/28(09-21)——插件审计整改(P0 + P1 + P2 全量)

改了什么问题

  • 《插件审计报告》(2026-09-21,28 个插件 / 390 个 PHP 文件)列出 P0 2 项、P1 121 项、P2 119 项:插件直读直写 users 表、activate() 里裸 $db->exec($ddl) 建表、CSS 硬编码色值不跟随主题、i18n 键前缀不统一、死钩子注册残留、每请求重复 DDL 等。

怎么改的

  • 核心只加一个文件的两个方法:User::getProfileById()(受限列集合,不含 password / email_verify_token)与 User::setStatus();插件侧 13 处直查直写 users(content_review、medal、daily_checkin 等)全部改走它们。
  • 插件建表统一 Plugin::ensureSchema()(ai_assistant 2 处、invite 3 处、medal 3 处、content_review 1 处);invite 的 P0 修事务:beginTransaction() + catch 内 inTransaction() 守卫,负数 award() 改 Points::deduct() 且失败回滚。
  • 15 个插件的 assets/style.css 主题变量化(如 icon_generator 69 处,含 17 处硬编码主色 → var(--mn-primary));icon_generator 的 71 键 ×3 语 i18n 前缀 plugin.icon_gen. → plugin.icon_generator.(键序不变);plugins_cache.json 重建,移除 post_favorite 死钩子 nav_pc_user_links。
  • 另修:draft_saver 内联 <script> 改 <meta> 传用户 ID、edit_info 重写为"会话守卫 + 惰性建表"、forum_required_tag 内联 style 改自定义属性注入、content_review 的 IN ({$in}) 改占位符。

影响范围

  • 64 个文件(44 PHP / 3 JS / 15 CSS / 2 JSON)+ 2 个必须手工删除的文件(增量包表达不了删除);核心字典键集合零变化(1343 键三语零差异);部署后必须后台清理缓存(改了导航钩子与 CSS,游客页是永久缓存);回滚用 update/27 及更早包同名文件逐个覆盖。

关键文件与位置

  • 核心:app/Models/User.php(getProfileById() / setStatus() 两个新方法)、plugins/plugins_cache.json(重建,移除死钩子)
  • 代表性插件:plugins/invite/InviteController.php(P0 事务 + Points::deduct)、plugins/daily_checkin/CheckinController.php(直写改 Points::award())、plugins/content_review/(3 处)、plugins/draft_saver/(meta 传参 + draft.js 读取)、plugins/icon_generator/*(71 键 ×3 语前缀改名,共 213 处)

遗留事项

  • 两处文件删除增量包表达不了,须按部署步骤手工执行;plugins_cache.json 不覆盖则 post_favorite 的死钩子注册仍留在缓存里。

3.6 update/29(09-22)——缤纷云存储插件(bitiful_storage)

改了什么问题

  • 附件全部存本地磁盘,IIS 服务器既费带宽又无 CDN 加速;若直接改造上传链路,一旦云侧故障全站附件不可用,需要一个"可随时整体关停"的云存储方案。

怎么改的

  • 形态是"本地落盘后镜像":Upload::file() 成功分支 return 前触发 upload_after 事件钩子(6 参数),插件把文件异步入 SyncQueue(1115 行:入队 / 原子取批 / 退避失败 / 排空 / 跨库可见性判定),再由 S3Client(634 行,纯函数 SigV4 签名与网络分离)镜像到缤纷云公开桶。
  • 前台 URL 改写:Upload::url() / thumbUrl() 末尾的过滤钩子(引用传参,thumbUrl 多带 prefix 档位语义)由 UrlRewriter 消费——白名单文件缓存 + 六道闸门 + 云端地址拼接;命中改 CDN 地址,未同步文件自动回退本地。
  • 密钥安全:SecretBox 用 AES-256-GCM 加解密,主密钥四来源解析(环境变量 → config.php → protected/bitiful_key.php → 首次自动生成);包内不含任何密钥,data/secret.key 由 activate() 首次生成。
  • 核心只加不改语义:Upload.php 3 处钩子 + Plugin.php 的 http() 支持任意 HTTP 方法(白名单 7 种)、返回值新增 status 与 PSR-7 风格 headers 键。

影响范围

  • 27 个文件(24 个新增插件文件 + 2 个核心 + plugins_cache.json),插件数 28 → 29;插件默认未启用(activated: false),需后台手工开启;最轻回滚 = 后台停用插件,URL 改写与队列立刻全部失效、前台回到本地地址;新增 bs_objects / bs_logs 两表由 ensureSchema() 幂等创建,老站首次启用才建。

关键文件与位置

  • plugins/bitiful_storage/:Plugin.php(531 行,主类)、S3Client.php(634 行,SigV4)、SecretBox.php(391 行,AES-256-GCM)、ConfigStore.php(471 行,13 个配置字段)、SyncQueue.php(1115 行,同步队列)、UrlRewriter.php(284 行,六道闸门)、AdminController.php(402 行,三个 Tab)+ 8 个 hook 文件 + 三语各 122 键
  • 核心:app/Helpers/Upload.php(3 处钩子)、app/Helpers/Plugin.php(http() 2 处增强)

遗留事项

  • 运行时产物刻意不进包:data/bitiful_storage.sqlite(队列台账)、data/secret.key(加密主密钥)、data/done_keys.json(改写白名单缓存);13 个 bitiful_storage_* settings 键由激活时按 Plugin::DEFAULTS 种入。

3.7 update/39(09-27)——右侧栏改造(签到解耦 + 发帖按钮 + 用户栏通用钩子)

改了什么问题

  • 核心右栏视图 right_sidebar.php 硬编码 include('plugins/daily_checkin/_sidebar') 和一段 #checkinHint:插件目录不存在时 TemplateCompiler::include() 静默返回(按钮位空一格),#checkinHint 又无 Plugin::isActivated() 门控,缺键时 I18n::get() 直接回显键名——未装插件的可见症状不是空白,是键名。
  • 用户栏操作区只有"签到 + 个人信息"两格,"签到"占着主位,却与社区最高频的"发帖"争位置;签到面板信息量极低(只有"今日已签/未签"一枚按钮)。

怎么改的

  • 删掉硬编码 include 与 #checkinHint 块;签到插件的钩子由 controller_view_before 改挂 right_sidebar_after——渲染钩子在闭包作用域拿不到视图数据,改为自取数据;views/_sidebar.php 由"今日签到按钮"重写为"周一 → 周日 7 格周视图"(钩子路径与 htmx 回包共用本模板)。
  • 用户栏"签到"位改为发帖按钮,个人信息 / 发帖按钮平均分布宽度;新增用户栏内通用钩子 right_sidebar_user_actions_above(.mn-user-quick 2 列网格)——与 right_sidebar_after(锚点在整个用户面板之后、只能塞整块 .mn-panel)位置和粒度都不同,不可互替。
  • daily_checkin 删死样式 .dc-checkin-hint、新增 dc-week-* 一组样式;三语各 +1 键 msg_tomorrow;CheckinController::buildAreaData() 增补本周查询与 week_days;建议手工删除已无消费方的 hook/controller_view_before.php。

影响范围

  • 包内 14 项 = 12 镜像 + version.php(V1.0.39)+ 改动说明,另有 1 项需手工删除;必须走后台「维护 → 清理缓存」(data/runtime/pages/*.html 是永久缓存、不按 TTL 过期,不清则老访客仍看旧右栏);签到记录与积分发放逻辑未改动。

关键文件与位置

  • app/Views/_components/right_sidebar.php(删硬编码 include 与 #checkinHint、加钩子容器)、assets/css/modern.css(.mn-user-quick,+7 行)
  • plugins/daily_checkin/views/_sidebar.php(重写为周视图)、CheckinController.php(buildAreaData() 补 week_days)、assets/style.css(删 .dc-checkin-hint、新增 dc-week-* 一组)
  • plugins/daily_checkin/hook/right_sidebar_after.php(新增,自取数据)、hook/controller_view_before.php(删除)

遗留事项

  • 站点根 version.php 保持 V1.0.31、由部署方覆盖升版——与 update/33/35/38 同属"包带目标版本、站点根滞后"的一类;plugins_cache.json 随包下发保证新钩子被注册。

3.8 update/40(09-28)——登录失效回跳修复(核心 / 视图 / 接口 / JS / 插件贯通)

改了什么问题

  • 会话过期后重新登录无法返回原页面;CSRF 校验失败且未登录时直接 403 白屏死胡同;API 401 不告诉前端登录地址(前端只能整页卡住);后台二次验证后不回跳来源页——"记录来源 → 跳登录 → 登录成功回跳"这条链路五处各自断掉。

怎么改的

  • Auth.php 新增唯一回跳守卫 sanitizeRedirectTarget()(只允许站内相对路径,堵开放重定向),配套 sanitizeQuery() / captureRedirectTarget() / loginUrl();改造 rememberRedirectAfter() / consumeRedirectAfter(),isAuthPath() 补 4 条认证路径;AuthController::login() 顶部调 captureRedirectTarget(),登录页加 Cache-Control: no-store。
  • Csrf::verifyOrDie() 未登录时改为跳登录并记录来源(不再是死胡同);ThreadController 回复 POST 由"先校验 CSRF 后 requireLogin"改为"先 requireLogin 后 CSRF"——否则未登录用户先撞上 403,永远到不了登录页。
  • API 层:ApiController 新增 captureApiRedirect() / apiLoginUrl(),jsonError() 在 401 时自动附 login_url,vote() 的 HX-Redirect 改用它,fetchImage() 401 由默认 400 纠正为 401;前端 auth.js 新增 document 级 htmx:responseError 处理器(401 且无 HX-Redirect 头时按 login_url 整页跳转);editor.js 新增 redirectOnAuthError()——原生 XHR 不触发 htmx 事件,/api/upload 与 /api/fetch-image 两处单独接。
  • 插件层 12 项:floor_reply / props / user_profile / red_packet / reply_reward / post_poll / qa / points_mall / third_party_login / suiyu 的视图登录链接统一改 $this->loginUrl()(钩子内无 $this,用 htmlspecialchars(Auth::loginUrl()));props 4 处POST 顺序修正;后台补齐 admin_redirect_after_verify 的唯一消费点。

影响范围

  • 27 项镜像,按批次 1 → 2 → 3 → 3.5 → 4 → 5 分组交付;纯代码覆盖,无需清页面缓存(未改视图 HTML 结构,只改链接 href 的取值方式);回跳来源只动 Session 键 redirect_after / admin_redirect_after_verify,不碰任何业务数据。

关键文件与位置

  • app/Helpers/Auth.php(唯一回跳守卫 + 4 个新方法)、app/Helpers/Csrf.php(verifyOrDie 改造)、app/Controllers/AuthController.php / ThreadController.php / ApiController.php
  • assets/js/auth.js(htmx:responseError 处理器)、assets/js/editor.js(redirectOnAuthError())、app/Core/TemplateCompiler.php(loginUrl() 视图助手)
  • 插件层 12 项:floor_reply / props / user_profile / red_packet / reply_reward / post_poll / qa / points_mall / third_party_login / suiyu 的视图与钩子

遗留事项

  • 站点根 version.php 由 V1.0.39 覆盖为 V1.0.40;此前 update/33→35→38→39 的版本号跳跃(31→33→35→38→39)均为各包自带目标版本所致,部署时切勿用旧包覆盖 version.php 造成版本倒退。

3.9 update/45(09-29)——核查报告整改(SplitDB / 访问控制 / 前端)

改了什么问题

  • 对 审计/assets_audit_report.html 与 审计/splitdb_audit_report.html 的逐条核查,收敛出 P0 / P1 / P2 / P3 共 20 项修复建议:F-02 归档合并与编辑竞态可静默丢失正文、M-05 锁失败退化为"尽力而为"继续执行、多个 SplitDB 组件的锁/缓存/版本标记口径不一、APP-01 权限判定多处重复实现等。

怎么改的

  • F-02:ExternMerger::mergeGroup() 重构为"先取 $idxPath.lock → glob 收集 .txt 清单 → 读 .idx → 追加 .bin → 写 .idx → 删 .txt",全程同一临界区、finally 统一解锁。原实现"glob 早于加锁 + 删 .txt 在锁外",并发编辑下会用旧快照覆盖 .idx 并删掉编辑刚写入的新 .txt,导致正文变空白且无任何日志。
  • M-05:ExternStorage 索引锁与 ExternMerger 组锁,fopen 或 flock(LOCK_EX|LOCK_NB)失败即记录错误并放弃本次读改写,不再降级执行;ExternIdxRepairer / ExternNamespaceMigrator / BucketAutoScaler / Queue / QueueConsumer / ShardRouter / DBFactory / ViewCounter / Schema 等 13 个 SplitDB 文件同批对齐。
  • APP 系列:Permission 新增解释层,HomeController / ForumController / SearchController / ProfileController / layouts/main.php 统一改用它;SearchIndexStore 修 APP-04;editor.js / auth.js 修 A-01/02/04,fhajax.js 仅关闭动态求值(CSP 未动)。
  • 2026-09-29 晚追加(不属于核查范围):"帖子详情页浏览量恒为 0"修复——Thread.php、ThreadController.php、ViewCounter.php 三个镜像追加进包(读取源对齐 + 本页即时体现 + inc() 返回值语义收紧,M-07 修复内容原样保留)。

影响范围

  • 26 个镜像 + version.php(V1.0.31→V1.0.45),全部 SHA256 逐字节一致、php -l 21 个 PHP 文件与 node --check 3 个 JS 全过;version.php 必须随包覆盖——sw.js 缓存名由站点版本号派生,不改版本号则浏览器继续命中旧 editor.js/auth.js/fhajax.js,前端修复不生效。

关键文件与位置

  • app/SplitDB/ 12 个文件(ExternStorage / ExternMerger / ExternNamespaceMigrator / ExternIdxRepairer / BucketAutoScaler / Queue / QueueConsumer / ShardRouter / DBFactory / ViewCounter / Schema / SearchIndexStore)+ index.php(A-02)
  • app/Helpers/Permission.php(新增解释层)、app/Helpers/Search.php、HomeController / ForumController / SearchController / ProfileController / ApiController、layouts/main.php
  • 追加进包的三个镜像:app/Models/Thread.php、app/Controllers/ThreadController.php、app/SplitDB/ViewCounter.php(覆盖第 11 项)

遗留事项

  • 20 项建议中 2 项因技术前提不成立暂缓(未实施);当日追加的"浏览量恒为 0"修复与原 11 项 M-07 内容并存于同一个 ViewCounter.php(inc() 返回值语义收紧,修复内容原样保留)。

3.10 update/99 与 1.1.0 beta(10-01 ~ 10-02)——内置在线升级系统落地

改了什么问题

  • 此前升级全靠手工覆盖增量包:版本号靠人记、缓存靠人清、回滚靠人事先备份,包与包之间的版本区间没有任何约束,漏覆盖 sw.js 就出现"老访客一直命中旧 CSS"这类问题。

怎么改的

  • V1.1.0 主体新增后台「系统升级」页,全流程:检查更新 → 差异预览(新增/修改/删除/冲突)→ 环境预检 → 取包并校验(sha256)→ 备份 → 应用 → 回滚,无需 SSH。分层设计 P0(版本比对 / latest.json 解析 / 频道)→ P1(差异预览)→ P2(预检 9 项 + 下载校验解压)→ P3(写盘前三道门禁:白名单 ⊆、路径大小写、防目录穿越)→ P4(备份 + 应用 + 双路径回滚)。
  • 安全:清单 manifest-<ver>.json 使用 RSA-2048 / RS256 签名,公钥内置 app/Helpers/release_keys.php,站点端 openssl_verify() 验签,默认 fail-closed(拒收未签名清单);preflight 第 10 项校验"版本区间"(累积增量包要求 base_version ≤ 当前版本 < 目标版本)。
  • update/99 的 version.php 刻意写 V1.0.99(代码已是 1.1.0):覆盖后站点自报 V1.0.99,后台就能看到"远端 1.1.0 可用",用来实测"老站 → 手工覆盖 → 在线升级"完整链路。
  • 同日四轮同步修复站长实测问题:① 新增"删除备份"端点(CSRF → safeBackupDir() 必须真实存在于 data/backup/ 之下 → 拒删正在使用的备份),后因"列表里往往只有一份谁也别想删"收窄为"仅 step=apply 且未完成时拦截"并自动清空悬空的 state.last_backup;② "备份"按钮加取包守卫(未取包置灰 + runBackup() 前置拦截);③ 修"进度 19/20 却已升级完成"——apply() 的 offset 漏数单独写的 version.php,done=true 时补到 total(未完成时绝不补);④ 错误信息从页面顶部横幅改渲染在「回滚」区内部(按钮在底部,原先"点了没反应")。
  • beta1 / beta2 是 1.1.0 的 312 文件纯净包:只含核心,不含 plugins/、assets/themes/、assets/uploads/、config.php 与 data/ 实际内容——1.1.0 起核心与插件分开发布。

影响范围

  • 1.1.0 起站点只发布全量包 release/core/flinthub-core-1.1.0.zip,update/ 增量包仅作留档(唯一例外:实测升级链路时随最新修复同步一次);"取消更新通道"(不再分稳定/测试版)、"更新源只读"(只能从官网拉取)等调整使 Updater 类成为后续维护的新入口。

关键文件与位置

  • app/Helpers/release_keys.php(内置 RSA-2048 公钥,openssl_verify() 验签 fail-closed)、后台「系统升级」页的控制器与视图、Updater 类(取包 / sha256 校验 / 备份 / 应用 / 双路径回滚)
  • index.php 与 cron_trigger.php 顶部 ini_set('display_errors','0')(防 warning 污染 JSON 响应);update/99 共 18 个文件(增量口径),1.1.0 正式全量为 release/core/flinthub-core-1.1.0.zip

遗留事项

  • 在线升级链路仍在实测迭代:三轮同步修复(删除守卫收窄、备份取包守卫、进度游标补齐)均来自站长实测反馈;正式版相对 beta 还取消了"稳定/测试"双通道、更新源改为只读(只能从官网拉取)。
  • 1.1.0 起核心与插件分开发布:核心全量包不含 plugins/,覆盖升级不会动已有插件目录;第三方主题 assets/themes/ 同样不受影响。

四、版本演进小结

从 2026-08-07 的 SoSite 整改包到 2026-10-02 的 V1.1.0,两个月的 35 个包呈现五条清晰脉络:

  1. 安全从"补洞"走向"机制化":8 月是单点加固——update/3〔SoSite 期〕 一次修掉单字检索拖库、路径穿越、模板 RCE、默认可见全站、粘贴净化窗口期五类洞;9 月 update/20 一次打出 A/B/C/D 四批,把"锁、审计、默认拒绝、服务端校验"固化为机制(RateLimiter 原子化、权限默认拒绝、Csrf 时效、Model 列白名单、后台审计补全);此后 update/28(插件审计,收敛插件对 users 表的直读直写)、update/45(SplitDB 锁失败必须中止、归档合并竞态)把"审计整改"变成常规动作;最终 update/99 / 1.1.0 用 RSA-2048 签名清单 + fail-closed 给发布链路收口。运维方式也从"人肉备份回滚"走到"签名验签 + 双路径回滚"。
  1. 性能优化沿"统计 → 查询 → I/O"递进:update/2 把站点统计从同步重建改增量累加(runtimeIncr + 惰性 flushCounters,并补 reply_index.status 索引);update/18 用请求级缓存收敛重复查询(UserPrefs 一次取 6 字段、UNION ALL 合并未读数、Thread::decorate() 按 uid/cid 去重、level_config 文件缓存);update/46 再证明"清理孤儿附件"的瓶颈是 SQLite 连接数而非 I/O 总量,改为 extern 直扫 + 插件库动态白名单。每一轮都先用微基准/查询计数定位,再动手。
  1. 插件生态从可用走向规范:update/9 随包沉淀 Hooks 目录(钩子机制、可用变量、路径安全、单钩子异常不影响页面);update/11 / update/14 落地通知中心、勋章中心两个首批大插件;update/25 把 SEO 插件从"基础技术 SEO"升级为"进阶 SEO + GEO"(JSON-LD / Twitter Card / 面包屑 / llms.txt);update/29 新增云存储、update/30 六插件完整包补齐文档;update/28 一次性整改 P0+P1+P2,确立"插件不直读 users 表、建表走 ensureSchema、CSS 必须主题变量化"的规范。通用钩子池持续扩容:attachment_download_before → vote_after/points_award_after/points_deduct_after/review_result_after → sidebar_above_friend_links → upload_url/upload_thumb_url/upload_after → right_sidebar_user_actions_above。
  1. 前台体验持续精细化:整改包清理 9 个孤儿文件、统一样式基线;update/10 / update/26 两轮打磨编辑器(快捷键与代码块复制、删除线双向互通、粘贴光标丢失、外链图片本地化);update/13 修代码块"深底深字";update/24 增加杂志式门户首页(cms 模式,不新增查询);update/39 右栏签到解耦改周视图、让出主位给发帖按钮;update/40 把"登录失效回跳"做到核心 / 视图 / 接口 / JS / 插件五层贯通,并堵掉开放重定向。
  1. 工程化与发布方式换代:update/20 起 sw.js 缓存名由站点版本号派生,破缓存从手工变自动;形成"目录号 = version.php 版本末位"的编号约定与"包带目标版本、站点根由部署方覆盖"的协作方式;1.1.0 起核心与插件分包发布,并内置在线升级系统(差异预览 / 取包校验 / 备份 / 双路径回滚 / RSA 签名),update/ 增量包体系完成历史使命转入留档——这份总览覆盖的两月,正是 FlintHub 从"手工覆盖补丁"走向"自带升级引擎"的完整过程。

汇总口径说明:总表覆盖 35 个包(29 个编号增量包 + 08-07 基础整改包 + 1.1.0-beta1/beta2 两个全量包);update/1 为 V1.0.0 发行基线(无代码变更,未列入总表);update/9 附带的 Hooks 目录、update/26 附带的社区更新公告已并入对应行;WWW/docs/升级说明.md 为 update/1~23 的总升级说明,用作交叉核对来源。


附录:本汇总的整理边界

收录但未单独展开的内容

  • 编号不连续,总表按实际存在的包排列:WWW/update/ 现存 24 个编号包(2、3、10、12、13、18、20、22~30、33、35、38~40、45、46、99),4~9、11、14~17、19、21、31、32、34、36、37、41~44、47~98 等号不存在;8 月 SoSite 期的 /3、/5、/7、/9、/11、/12、/13、/14 在《8月12日的备份》另一份快照里,其中 /3、/12、/13 与 WWW 同名包内容不同,表中以"〔SoSite 期〕/版本号"区分。
  • 各包的"覆盖前必须备份什么""回归验证清单""MD5/SHA256 校验表""回滚步骤"统一不收录,部署时以各源文件为准。
  • 各包内部的逐文件 diff 明细(改动行数、文件大小)只在对定位根因有帮助时保留(如 update/35 的 +6/−3 行、update/38 的行数变化)。

未收录的内容

  • 插件自身的 说明.md 文档更新(update/25~30 多个插件同步了文档,属文档维护,未计入关键改动)。
  • 待办事宜/、审计/、.workbuddy-ai/ 等开发过程产物;release/ 目录下的正式全量包构建产物(1.1.0 起启用,本汇总只覆盖到 beta 留档态)。
  • 服务器侧(IIS + 腾讯云函数 cron)的环境运维事项,各包仅在"部署关键点"里提及,本汇总不展开。
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 瀏覽 5 次 | 回覆 0 次

全部回覆 (0)

暫無回覆

請 登入