[开发]FlintHub 修复计划 2026-08-20

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

本文由 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 解压 OOMapp/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数据库异常泄露路径/SQLapp/Core/Database.php L96-101✅ 存在RuntimeException 消息含 $e->getMessage()(SQLite 报错可含物理路径)与完整 SQL
P1-4ShardRouter 除零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-1CSRF 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 记录引用已不存在的文件(服务器文件丢失但记录失效)。

修复方案(单文件内完成,不改外部调用契约的返回结构):

  1. 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'。
  1. file() L83-85 接收返回值:'rejected' → @unlink($destination) 回滚已落盘文件,返回 ['success' => false, 'error' => '图片尺寸超出限制'](前端收到失败,调用方不写 DB 记录);'skip' / 'ok' → 维持成功路径。
  2. 现有调用方(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 的双写模式):

  1. 若 $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)。
  1. 若 $table === 'blogs'(或未知表名):维持原单条 UPDATE(白名单校验表名)。
  2. 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 整体非原子(先读后写),并发窗口内相互覆盖计数。

修复方案:

  1. writeIpRecords() 改为 fopen($file, 'c+b') + flock($fh, LOCK_EX | LOCK_NB):
  • 立即失败(拿不到锁)→ 降级放行:不阻塞、不写文件,直接返回(限流器宁可漏放不可卡死,符合审计要求);
  • 拿到锁 → ftruncate + fwrite + fflush + flock(LOCK_UN) + fclose。
  1. 读取侧保持 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|)命中完整管线,绕过静态缓存造成缓存穿透。

修复方案:

  1. $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(视为游客命中缓存)。
  2. 登录判断改为 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.phpP0-1a、P0-1b
第 2 批app/Controllers/Admin/BaseController.phpP0-2(联动 Thread::update 模式)
第 3 批app/Helpers/RateLimiter.phpP0-3
第 4 批index.phpP1-1
第 5 批app/init.phpP1-2
第 6 批app/Core/Database.phpP1-3
第 7 批app/SplitDB/ShardRouter.phpP1-4(防御)
第 8 批plugins/code_repo/RepoController.phpP1-5
第 9 批app/Helpers/Csrf.phpP2-1(含调用点确认)
第 10 批app/Helpers/Auth.php + app/Controllers/Admin/BaseController.phpP2-2(上限需先确认)
第 11 批app/SplitDB/ExternStorage.phpP2-3
第 12 批app/Helpers/Plugin.phpP2-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
1app/Helpers/Upload.phpmakeThumbnail 返回 'ok'/'skip'/'rejected' 三态;解码前估算 宽×高×4 字节,>32MB 直接放弃生成缩略图(保原图不 crash);像素超限不再内部 unlink,改由 file() 收到 rejected 后删除已落盘原图并返回 图片尺寸超出限制(失败即回滚)✅
2app/Controllers/Admin/BaseController.phpbatchMoveIds:threads 走新私有方法 batchMoveThreads(main_index.bucket_path 分桶 → 逐桶事务 UPDATE topic + topic_index 单条同步 + 缓存失效);blogs 等非分片表维持单条 UPDATE;ids 统一 intval。顺带修复同文件二次验证超时限幅(max(60, min($raw, 604800))),与 Auth.php 保持一致(用户决策:上限 7 天)✅
3app/Helpers/RateLimiter.phpwriteIpRecords 改 fopen('c+b') + flock(LOCK_EXLOCK_NB),抢锁失败/打不开文件直接放弃写入(降级放行,不阻塞)
4index.php$sid 增加正则 /^[a-zA-Z0-9,-]{20,128}$/ 校验(非法即跳过会话查询);strpos 改 `preg_match('/(?:^;)user_id\
5app/init.php维护模式白名单拆为精确匹配(/admin /login /register /logout)+ 前缀匹配(/admin/ /api/),畸形路径不再放行✅
6app/Core/Database.phpcatch \PDOException 后 error_log 真实错误与 SQL,对外抛通用 数据库操作失败,请稍后重试(previous 保留原异常)✅
7plugins/code_repo/RepoController.phpContent-Disposition 增加 filename*=UTF-8'' + rawurlencode,filename 回退 addcslashes 转义引号/反斜杠✅
8app/Helpers/Auth.phprequireAdmin 超时 max(60, min($raw, 604800))(60 秒 ~ 7 天,用户决策)✅
9app/Helpers/Csrf.php + ProfileController.php + ThreadController.php新增 Csrf::consume()(unset csrf/csrf_time);仅两处调用:ProfileController 修改密码成功分支、ThreadController::delete 删除主题与删除回帖两个成功分支(3 个文件)✅
10app/SplitDB/ShardRouter.phpbucketForWrite 显式 $delta = $size - $prev,$delta <= 0 记日志并回落旧哈希路由(防 DivisionByZeroError 兜底)✅

执行说明:

  1. P2-2 用户决策为上限 7 天(604800)、下限 60 秒,已按此落实,且 BaseController.php 与 Auth.php 两处同步修改(BaseController 在 #2 批次内一并完成,未额外新增批次)。
  2. P2-1 严格按用户指令:consume() 仅在修改密码(ProfileController::change_password)与删除帖子(ThreadController::delete,含主题/回帖两个分支)成功后调用,未改动其他接口,避免影响并发 AJAX。
  3. 全部修改使用编辑工具完成(UTF-8 无 BOM),未使用 sed/重定向等 cmd 命令改文件;php -l 通过为唯一校验手段,未做运行时回归(如需上线请先手动冒烟:上传大图、后台批量移动、改密后旧 Token 重放应 403 等)。
  4. 待办提醒:flinthub1.0.0/ 安装包副本未同步本批次修复,如需分发请另行同步。

七、测试与安装包同步记录(2026-08-20 追加)

1. 冒烟测试(cli/smoke_*.php,全部使用临时数据目录,不触碰线上数据)

脚本结果说明
smoke_frontend_flow.php21/21 ✅发帖→列表→详情→回帖→搜索→投票→标签全链路
smoke_extern_storage.php10/10 ✅外置正文写入/重写/删除/目录隔离
smoke_db_factory.php10/10 ✅LRU 连接池 + PRAGMA
smoke_post.php21/21 ✅回复分片/extern/软删/硬删
smoke_id_generator.php9/9 ✅全局 ID 并发无重复
smoke_install.php34/34 ✅建库/种子/install.php 完整性
smoke_database.php10/13 ⚠️3 项失败均为计数断言未计入种子分类(Schema.php L211-213 内置 id=1「默认版块」):fetchAll 期望 2 条实际 3 条、事务后计数期望 3 实际 4。Database.php 本批次仅改动 catch 分支,不涉及断言路径 → 测试脚本过时,非回归
smoke_model.php8/13 ⚠️5 项失败同为种子分类(sort_order=1 与测试数据冲突)导致计数/排序断言不符;Model.php 本批次未修改 → 非回归
smoke_business_models.php18/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 保留在根目录供复测,如需分发请一并决定去留。

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 3 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录