本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:暂存20260913/修复计划20260820.md。
FlintHub 修复计划 2026-08-20
依据「6 批次代码审计」整理出的 15 条修复指令(P0×3 / P1×5 / P2×4),逐项对照根目录正在运行的代码进行验证,并给出修复方案。
范围说明:本计划只针对根目录运行中的程序代码(
D:/phpstudy_pro/WWW)。flinthub1.0.0/、update/、plugins/code_repo/data/repos/内的副本不直接修改;根目录修复验证通过后,如需同步到安装包再另行处理。修复执行约束(用户指令):
1. 逐文件修复,禁止批量改动多个文件。
2. 所有文件为 UTF-8 无 BOM(含中文注释),一律用编辑工具修改,不用 sed/重定向等 cmd 命令,避免编码损坏。
3. 每个文件改完后用
php -l <file>做语法校验(若允许运行命令);不允许则人工复查并明说未验证。
一、验证结论总览
| 编号 | 问题 | 位置(实际) | 结论 | 影响 |
|---|---|---|---|---|
| P0-1a | 高清大图 GD 解压 OOM | app/Helpers/Upload.php L117-121, L156-222 | ✅ 存在 | 4999×4999 通过 5000 阈值,GD 解压分配约 100MB 位图,可致 PHP 进程 OOM |
| P0-1b | 超限删原图却返回成功 | app/Helpers/Upload.php L118-120, L83-85 | ✅ 存在 | unlink 原图后 file() 仍返回 success,文件丢失但前端显示成功 |
| P0-2 | 批量移动跨分片失效 | app/Controllers/Admin/BaseController.php L53-78(审计标注的 DatabaseController.php 中无此方法) | ✅ 存在(比审计描述更严重) | 单条 UPDATE threads ... 打在已退役的 business.sqlite.threads 表上,分片桶内数据实际完全未被更新 |
| P0-3 | 文件锁阻塞无降级 | app/Helpers/RateLimiter.php L77-83 | ✅ 存在 | 同 key 高并发排队等 LOCK_EX,读-改-写非原子,接口可卡死 |
| P1-1 | 前置缓存 strpos 判断登录 | index.php L28, L39 | ✅ 存在 | 会话数据中任意位置出现 `user_id |
| P1-2 | 维护模式前缀误放行 | app/init.php L180-184 | ✅ 存在 | /admin_test、/api_v2 等畸形路径被 strncmp 前缀匹配放行 |
| P1-3 | 数据库异常泄露路径/SQL | app/Core/Database.php L96-101 | ✅ 存在 | RuntimeException 消息含 $e->getMessage()(SQLite 报错可含物理路径)与完整 SQL |
| P1-4 | ShardRouter 除零 | app/SplitDB/ShardRouter.php L76-86 | ⚠️ 当前代码已内置防护 | L81 $prev < $size 已排除 $size-$prev===0,DivisionByZeroError 实际不可达;建议按审计补显式防御(降为 P2 级) |
| P1-5 | 下载文件名未编码 | plugins/code_repo/RepoController.php L130-132 | ✅ 存在 | filename="' . $name . '" 未编码;PHP header() 已拦截 CRLF 注入,但双引号等字符仍会破坏 Content-Disposition 语法 |
| P2-1 | CSRF Token 可无限重放 | app/Helpers/Csrf.php L23-29 | ✅ 存在 | verify() 只比对不消费(注释明示设计取舍);按审计增加 consume() 供敏感操作调用 |
| P2-2 | 二次验证超时无限幅 | app/Helpers/Auth.php L104-105;app/Controllers/Admin/BaseController.php L25-26 | ✅ 存在 | 仅 min($v, 2592000) 封顶,无下限;设为 0 秒即"刚登录就踢到验证页"死循环 |
| P2-3 | .idx 索引并发写竞争 | app/SplitDB/ExternStorage.php L72-108 | ✅ 存在 | invalidateArchiveEntry 对 .idx 读-改-写无锁;且缺少 write() 中的 Windows rename 重试逻辑 |
| P2-4 | 插件临时文件清理误删 | app/Helpers/Plugin.php L160-163, L204-207 | ✅ 存在 | glob('.tmp.') 无 filemtime 判断,可能删掉并发进程刚创建的临时文件 |
结论:12 项中 11 项确认存在需修复;P1-4 为"已有防护,建议加显式防御";另有 2 处审计位置修正(P0-2 实际在 BaseController.php;P1-1 的"32 位 Hex"校验与 PHP 默认 Session ID 格式不符,见下文)。
二、P0 必须优先修复
P0-1 Upload.php(app/Helpers/Upload.php)
现状问题 ① 高清大图 OOM:makeThumbnail() L117-121 仅检查 >5000 像素才拒绝;4999×4999 恰好通过,随后 GD imagecreatefromjpeg 需分配 4999×4999×4 ≈ 100MB 位图(远超常见 memory_limit 128M),直接 OOM 崩溃整个请求/进程。
现状问题 ② 删原图却返回成功:L118-120 超限时 @unlink($sourcePath) 删掉原图后直接 return;file() L83-85 忽略 makeThumbnail 返回值仍返回 success:true → 前端显示"上传成功",DB 记录引用已不存在的文件(服务器文件丢失但记录失效)。
修复方案(单文件内完成,不改外部调用契约的返回结构):
makeThumbnail()改返回三态字符串'ok' | 'skip' | 'rejected':
getimagesize成功后、解码前先估算:$estBytes = $info[0] $info[1] 4;(RGBA 位图内存,GD 另需缩放副本,按 4 字节已足够保守)——若$estBytes > 32 1024 1024→ 直接返回'skip'(不生成缩略图、不删原图、不 crash,原图照常保留、上传成功)。- 超过 5000 像素限制 → 返回
'rejected'(不再内部 unlink)。 - Imagick/GD 异常、非图片 →
'skip'。 - 正常生成 →
'ok'。
file()L83-85 接收返回值:'rejected'→@unlink($destination)回滚已落盘文件,返回['success' => false, 'error' => '图片尺寸超出限制'](前端收到失败,调用方不写 DB 记录);'skip'/'ok'→ 维持成功路径。- 现有调用方(ApiController/ProfileController/BlogController/ThreadController/Settings)均已按
$result['success']分支处理,无需改动。
验证:php -l app/Helpers/Upload.php;构造 4999×4999 图片上传确认返回失败且服务器不留文件、>32MB 预估图返回成功但无 thumb 文件。
P0-2 批量移动帖子跨分片(app/Controllers/Admin/BaseController.php)
位置修正:审计写的是 DatabaseController.php,实际方法 batchMoveIds() 在 BaseController.php L53-78(ThreadController/BlogController 共用)。
现状问题:L60-65 一条 UPDATE {$table} SET category_id = ? WHERE id IN (...) 打到 Database::getInstance() 的 business.sqlite 连接。但 threads 已分片:数据在 bucket/active/{季度}/{桶}.sqlite(由 main_index.bucket_path 定位),business.sqlite 的 threads 表已退役无数据(后台 ThreadController 注释明示)。因此该 UPDATE 对分片帖子完全不生效(静默失败),且就算生效也做不到跨库一条 UPDATE。blogs 表未分片(Blog.php 无 main_index/bucket 引用),单条 UPDATE 仍然有效。
修复方案(参考 Thread::update L320-384 的双写模式):
- 若
$table === 'threads':
- 用
Schema::mainIndexDb()一次查出所选 id 的id, bucket_path,按bucket_path把 id 分组; - 对每个桶:
Schema::bucketFromPath($bucket_path)取桶连接,beginTransaction()→UPDATE topic SET category_id=? WHERE id IN (该桶 ids)→commit()(每条事务包住,失败回滚该桶); - main_index 同步:
UPDATE topic_index SET category_id=? WHERE id IN (...); - 完成后照搬 Thread::update 的联动:
Category::refreshLatestSnapshot(新旧分类)、Category::invalidateCategoryStats()、PageCache::invalidate(),再跑$afterMove回调(Search::indexThread)。
- 若
$table === 'blogs'(或未知表名):维持原单条 UPDATE(白名单校验表名)。 - id 上限 500、
$ids统一转 int 防注入(沿用现有?参数绑定)。
验证:php -l app/Controllers/Admin/BaseController.php;后台勾选跨桶帖子批量移动后,查 main_index.topic_index.category_id 与对应桶 topic.category_id 均已变更、前台列表分类正确。
P0-3 RateLimiter 文件锁阻塞(app/Helpers/RateLimiter.php)
现状问题:writeIpRecords() L82 file_put_contents(..., LOCK_EX) 是阻塞式独占写锁。同一 IP+action 的 key 在极端高并发下,多个 PHP-FPM 进程排队等锁串行化;且 readIpRecords → 修改 → writeIpRecords 整体非原子(先读后写),并发窗口内相互覆盖计数。
修复方案:
writeIpRecords()改为fopen($file, 'c+b')+flock($fh, LOCK_EX | LOCK_NB):
- 立即失败(拿不到锁)→ 降级放行:不阻塞、不写文件,直接返回(限流器宁可漏放不可卡死,符合审计要求);
- 拿到锁 →
ftruncate+fwrite+fflush+flock(LOCK_UN)+fclose。
- 读取侧保持
file_get_contents(读不加锁可接受,最坏读到旧计数)。
验证:php -l app/Helpers/RateLimiter.php;高并发打同一 upload 接口确认不再出现接口卡死。
三、P1 修复(优先级次之)
P1-1 前置缓存登录判断(index.php L28、L39)
现状问题:
$sid直接取自 Cookie,无格式校验(L28);- L39
strpos((string)$sdata, 'user_id|')只要会话数据任意位置出现该字符串即判"已登录"→ 游客可借 Session 中可控内容(如某字段值恰含user_id|)命中完整管线,绕过静态缓存造成缓存穿透。
修复方案:
$sid校验:PHP 默认 Session ID 为 26 位、字符集[0-9a-v](base32),审计建议的"32 位 Hex"会误伤正常会话(所有带 Cookie 游客全部退回完整管线,纯性能回归)。建议用宽容正则/^[a-zA-Z0-9,-]{20,64}$/(覆盖 PHP 各 sid_length/sid_bits_per_character 配置),不合法直接$canFrontServe = true(视为游客命中缓存)。- 登录判断改为
preg_match严格匹配键边界:preg_match('/(?:^|;)user_id\|i:/', (string)$sdata)(PHP 序列化会话条目格式为键|类型:值,user_id作为键出现时必为行首或紧接;),消除值内包含user_id|的误判。
验证:php -l index.php;未登录带 Cookie 游客访问首页仍命中前置缓存;构造会话含 user_id| 字段不触发完整管线。
P1-2 维护模式白名单(app/init.php L180-184)
现状问题:$allowed = ['/admin', '/login', '/register', '/logout', '/api/'] 全部走 strncmp 前缀匹配 → /admin_test、/api_v2、/login_backdoor 等畸形路径全部放行。
修复方案:拆成两段判断:
$exactAllowed = ['/admin', '/login', '/register', '/logout'];
$prefixAllowed = ['/admin/', '/api/'];
$pass = in_array($path, $exactAllowed, true)
|| (bool)array_filter($prefixAllowed, fn($p) => strncmp($path, $p, strlen($p)) === 0);注意 /admin 精确 + /admin/ 前缀组合:/admin/database 命中前缀,/admin_test 两边都不命中。
验证:php -l app/init.php;维护模式开启后 /admin_test 应返回 503,/admin、/admin/database、/api/xxx 正常放行。
P1-3 数据库异常泄露(app/Core/Database.php L96-101)
现状问题:catch \PDOException 后抛 RuntimeException('DB Error: ' . $e->getMessage() . ' | SQL: ' . $sql, 0, $e) —— getMessage 对 SQLite 可能含物理绝对路径(如 unable to open database file: D:\...),且完整 SQL 语句随异常上抛,最终可能出现在错误页/响应中。
修复方案:
} catch (\PDOException $e) {
\error_log('[Database] ' . $e->getMessage() . ' | SQL: ' . $sql); // 真实信息只进日志
throw new \RuntimeException('数据库操作失败,请稍后重试', 0, $e); // 对外通用文案;previous 保留便于内部排查
}验证:php -l app/Core/Database.php;触发一条错误 SQL,确认响应体不含路径/SQL,error.log 含真实信息。
P1-4 ShardRouter 扩容除零(app/SplitDB/ShardRouter.php L76-86)
验证结论:当前代码 L81 条件 $prev > 0 && $prev < $size 已排除 $prev === $size,$size - $prev 不可能为 0(bucketSize() 也保证 $size >= 32),DivisionByZeroError 当前不可达。该项按审计降级为防御性加固(P2 级):
修复方案:进入扩容分支前显式计算 $delta = $size - $prev;,if ($delta <= 0) return $id % $size;(配置出错时回落旧哈希路由,防未来改动破坏防护)。
验证:php -l app/SplitDB/ShardRouter.php;构造 SPLITDB_PREV_BUCKET_SIZE === SPLITDB_BUCKET_SIZE 配置调用 bucketForWrite() 确认无异常。
P1-5 下载文件名未编码(plugins/code_repo/RepoController.php L130-132)
现状问题:Content-Disposition: attachment; filename="' . $name . '" —— basename() 可能含 " 等字符,破坏响应头引号配对(PHP header() 会拦截换行 CRLF 注入,但引号/非 ASCII 文件名仍会乱码或语法破坏)。
修复方案:L132 改为 RFC 5987 双写:
$name = basename($path);
\header('Content-Disposition: attachment; filename="' . addcslashes($name, '"\\') . '"; filename*=UTF-8\'\'' . rawurlencode($name));(中文名走 filename* 由浏览器正确解码,引号经 addcslashes 转义兜底。)
验证:php -l plugins/code_repo/RepoController.php;下载含中文/引号文件名的仓库文件确认响应头合法。
四、P2 修复(防御性优化)
P2-1 CSRF Token 一次性消费(app/Helpers/Csrf.php)
现状问题:verify() L23-29 只比对不消费,Token 在 Session 有效期内(30 分钟自动轮换前)可无限重放。注释明示"仅校验不轮换,防并发 AJAX 403"是有意设计——因此不能全局改 verify 为消费,按审计新增 consume() 供敏感操作显式调用。
修复方案:
/** 一次性消费:敏感操作(改密/删除等)成功后强制作废当前 Token */
public static function consume(): void
{
unset($_SESSION['csrf'], $_SESSION['csrf_time']);
}调用点(改密码、删帖/删帖、删用户、后台保存等敏感 POST 成功路径)逐个在修复时确认并注入,避免破坏并发 AJAX 页面(普通页面不调用)。
验证:php -l app/Helpers/Csrf.php;改密后旧 Token 再提交同表单应 403。
P2-2 二次验证超时无限幅(app/Helpers/Auth.php L104-105;app/Controllers/Admin/BaseController.php L25-26)
现状问题:两处都是 min($rawTimeout, 2592000) 只封顶。admin_verify_timeout 被误配为 0 时,$verified < time() - 0 恒真 → 管理员刚登录就被踢到 /admin/verify,死循环。
修复方案(两处同改,建议提取成 Settings 层单一取值函数或两处同步修改):
$raw = (int)\app\Helpers\Settings::get('admin_verify_timeout', '1800');
$verifyTimeout = \max(60, \min($raw, 86400));⚠️ 需确认的取舍:按审计把上限从 2592000(30 天,长任务模式)收窄到 86400(24 小时)会削弱"长任务模式"功能;下限 60 秒是本次必须修的核心(修死循环)。若保留长任务模式,可改 max(60, min($raw, 2592000)) —— 修复前请定夺。
验证:php -l 两文件;设置 admin_verify_timeout=0 后登录不再死循环(回落 60 秒)。
P2-3 ExternStorage .idx 并发写竞争(app/SplitDB/ExternStorage.php L72-108)
现状问题:invalidateArchiveEntry() 对同一批次 .idx:file_get_contents(无锁)→ 改 JSON → 临时文件 + rename。多进程并发编辑同一 .idx 时 read-modify-write 丢失更新(后写覆盖先写);且 L104 的 rename 缺少 write()(L43-50)那套 Windows 目标已存在时的删除重试。
修复方案:读-改-写全程加锁:
$fh = @fopen($idxPath, 'c+b');
if ($fh === false) return;
flock($fh, LOCK_EX); // 独占锁:串行化本批次 .idx 的读改写
$raw = stream_get_contents($fh); // 锁内读(不再 file_get_contents)
// ... 解析、unset($idx[$topicId]) ...
ftruncate($fh, 0); rewind($fh);
fwrite($fh, json_encode($idx, JSON_UNESCAPED_UNICODE));
fflush($fh); flock($fh, LOCK_UN); fclose($fh);(c+b 直接原地改写,取代临时文件+rename,天然规避 Windows rename 覆盖问题;APCu 清理逻辑保留。)
验证:php -l app/SplitDB/ExternStorage.php;并发编辑同一批次内两条正文,确认 .idx 两条都正确作废、无文件损坏。
P2-4 插件临时文件清理误删(app/Helpers/Plugin.php L160-163、L204-207)
现状问题:两处 glob($file . '.tmp.*') 清理对所有匹配文件直接 unlink,无时间判断。进程 A 清理时可能删掉进程 B 刚创建的 .tmp.xxxx(B 的随机后缀防不住 A 的 glob),导致 B 的 rename 失败/数据丢失。
修复方案(两处同改):
foreach (glob($file . '.tmp.*') as $stale) {
if (is_file($stale) && (time() - @filemtime($stale)) > 60) @unlink($stale); // 只清 60s 前残留
}验证:php -l app/Helpers/Plugin.php;并发激活两个插件确认无临时文件被误删。
五、建议执行顺序
| 批次 | 文件 | 对应问题 |
|---|---|---|
| 第 1 批 | app/Helpers/Upload.php | P0-1a、P0-1b |
| 第 2 批 | app/Controllers/Admin/BaseController.php | P0-2(联动 Thread::update 模式) |
| 第 3 批 | app/Helpers/RateLimiter.php | P0-3 |
| 第 4 批 | index.php | P1-1 |
| 第 5 批 | app/init.php | P1-2 |
| 第 6 批 | app/Core/Database.php | P1-3 |
| 第 7 批 | app/SplitDB/ShardRouter.php | P1-4(防御) |
| 第 8 批 | plugins/code_repo/RepoController.php | P1-5 |
| 第 9 批 | app/Helpers/Csrf.php | P2-1(含调用点确认) |
| 第 10 批 | app/Helpers/Auth.php + app/Controllers/Admin/BaseController.php | P2-2(上限需先确认) |
| 第 11 批 | app/SplitDB/ExternStorage.php | P2-3 |
| 第 12 批 | app/Helpers/Plugin.php | P2-4 |
执行纪律:每批只动一个文件(P2-2 涉及两个文件时先改 Auth.php 再改 BaseController.php),UTF-8 无 BOM,edit_file 修改,php -l 校验通过后再进下一批;修改前后对比 diff 复核。
六、修复执行记录(2026-08-20,已全部完成)
按用户 2026-08-20 指令执行:P0 立即修 → P1 接着修 → P2 按用户决策修。
校验环境:
D:\phpstudy_pro\Extensions\php\php8.2.9nts\php.exe -l,全部输出No syntax errors detected。
| # | 文件 | 修复内容(与计划差异) | php -l |
|---|---|---|---|
| 1 | app/Helpers/Upload.php | makeThumbnail 返回 'ok'/'skip'/'rejected' 三态;解码前估算 宽×高×4 字节,>32MB 直接放弃生成缩略图(保原图不 crash);像素超限不再内部 unlink,改由 file() 收到 rejected 后删除已落盘原图并返回 图片尺寸超出限制(失败即回滚) | ✅ |
| 2 | app/Controllers/Admin/BaseController.php | batchMoveIds:threads 走新私有方法 batchMoveThreads(main_index.bucket_path 分桶 → 逐桶事务 UPDATE topic + topic_index 单条同步 + 缓存失效);blogs 等非分片表维持单条 UPDATE;ids 统一 intval。顺带修复同文件二次验证超时限幅(max(60, min($raw, 604800))),与 Auth.php 保持一致(用户决策:上限 7 天) | ✅ |
| 3 | app/Helpers/RateLimiter.php | writeIpRecords 改 fopen('c+b') + flock(LOCK_EX | LOCK_NB),抢锁失败/打不开文件直接放弃写入(降级放行,不阻塞) |
| 4 | index.php | $sid 增加正则 /^[a-zA-Z0-9,-]{20,128}$/ 校验(非法即跳过会话查询);strpos 改 `preg_match('/(?:^ | ;)user_id\ |
| 5 | app/init.php | 维护模式白名单拆为精确匹配(/admin /login /register /logout)+ 前缀匹配(/admin/ /api/),畸形路径不再放行 | ✅ |
| 6 | app/Core/Database.php | catch \PDOException 后 error_log 真实错误与 SQL,对外抛通用 数据库操作失败,请稍后重试(previous 保留原异常) | ✅ |
| 7 | plugins/code_repo/RepoController.php | Content-Disposition 增加 filename*=UTF-8'' + rawurlencode,filename 回退 addcslashes 转义引号/反斜杠 | ✅ |
| 8 | app/Helpers/Auth.php | requireAdmin 超时 max(60, min($raw, 604800))(60 秒 ~ 7 天,用户决策) | ✅ |
| 9 | app/Helpers/Csrf.php + ProfileController.php + ThreadController.php | 新增 Csrf::consume()(unset csrf/csrf_time);仅两处调用:ProfileController 修改密码成功分支、ThreadController::delete 删除主题与删除回帖两个成功分支(3 个文件) | ✅ |
| 10 | app/SplitDB/ShardRouter.php | bucketForWrite 显式 $delta = $size - $prev,$delta <= 0 记日志并回落旧哈希路由(防 DivisionByZeroError 兜底) | ✅ |
执行说明:
- P2-2 用户决策为上限 7 天(604800)、下限 60 秒,已按此落实,且 BaseController.php 与 Auth.php 两处同步修改(BaseController 在 #2 批次内一并完成,未额外新增批次)。
- P2-1 严格按用户指令:consume() 仅在修改密码(ProfileController::change_password)与删除帖子(ThreadController::delete,含主题/回帖两个分支)成功后调用,未改动其他接口,避免影响并发 AJAX。
- 全部修改使用编辑工具完成(UTF-8 无 BOM),未使用 sed/重定向等 cmd 命令改文件;php -l 通过为唯一校验手段,未做运行时回归(如需上线请先手动冒烟:上传大图、后台批量移动、改密后旧 Token 重放应 403 等)。
- 待办提醒:
flinthub1.0.0/安装包副本未同步本批次修复,如需分发请另行同步。
七、测试与安装包同步记录(2026-08-20 追加)
1. 冒烟测试(cli/smoke_*.php,全部使用临时数据目录,不触碰线上数据)
| 脚本 | 结果 | 说明 |
|---|---|---|
| smoke_frontend_flow.php | 21/21 ✅ | 发帖→列表→详情→回帖→搜索→投票→标签全链路 |
| smoke_extern_storage.php | 10/10 ✅ | 外置正文写入/重写/删除/目录隔离 |
| smoke_db_factory.php | 10/10 ✅ | LRU 连接池 + PRAGMA |
| smoke_post.php | 21/21 ✅ | 回复分片/extern/软删/硬删 |
| smoke_id_generator.php | 9/9 ✅ | 全局 ID 并发无重复 |
| smoke_install.php | 34/34 ✅ | 建库/种子/install.php 完整性 |
| smoke_database.php | 10/13 ⚠️ | 3 项失败均为计数断言未计入种子分类(Schema.php L211-213 内置 id=1「默认版块」):fetchAll 期望 2 条实际 3 条、事务后计数期望 3 实际 4。Database.php 本批次仅改动 catch 分支,不涉及断言路径 → 测试脚本过时,非回归 |
| smoke_model.php | 8/13 ⚠️ | 5 项失败同为种子分类(sort_order=1 与测试数据冲突)导致计数/排序断言不符;Model.php 本批次未修改 → 非回归 |
| smoke_business_models.php | 18/21 ⚠️ | 3 项失败(allOrdered 排序/getThreads 首条/deleteWithReset 计数)同为种子分类参与计数排序所致;Category/User 模型本批次未修改 → 非回归 |
结论:所有涉及本批次改动文件的冒烟路径全部通过;8 项失败均集中在未修改文件(Model/Category/User/Schema 种子)的计数断言上,系种子功能引入后测试断言未同步更新的历史问题,建议后续单独修正 smoke_database/smoke_model/smoke_business_models 的断言基数。
2. 专项验证(cli/_verify_fixes_20260820.php,10/10 ✅)
| 验证点 | 结果 |
|---|---|
| ShardRouter 除零兜底(prev=size 时回落旧哈希路由、不抛异常) | ✅ |
| Csrf::consume 一次性消费(旧 Token 失效、token() 自动再生成) | ✅ |
| Upload 内存估算阈值(4999×4999>32MB 跳过;2800×2800≤32MB 可生成) | ✅ |
| RateLimiter flock(LOCK_EX\ | LOCK_NB) 锁占用时立即失败不阻塞 |
3. 安装包同步(flinthub1.0.0/)
已逐字节复制以下 12 个文件到安装包对应路径,全部 php -l 校验通过(No syntax errors detected):
| 安装包内文件 | 校验 |
|---|---|
| index.php | ✅ |
| app/Helpers/Upload.php、RateLimiter.php、Auth.php、Csrf.php | ✅ |
| app/Core/Database.php | ✅ |
| app/init.php | ✅ |
| app/Controllers/Admin/BaseController.php、ProfileController.php、ThreadController.php | ✅ |
| app/SplitDB/ShardRouter.php | ✅ |
| plugins/code_repo/RepoController.php | ✅ |
说明:flinthub1.0.0/cli 目录不含 smoke 脚本(仅根目录工作副本有),故冒烟在根目录代码上执行;同步文件为 copy /y 字节级复制(不改变 UTF-8 编码)。cli/_verify_fixes_20260820.php 保留在根目录供复测,如需分发请一并决定去留。