QBCMS 低版本站点根目录冗余文件清理工具 使用说明
（适用当前版本 ≤ V1.4.0；工具版本：v1.4.2 配套）

====================================================================
一、这个工具解决什么问题
====================================================================
修复前的 app/Core/Updater.php 里 fileIterator($dir) 递归时以「当前子目录」为基准计算相对
路径，导致两种情况在站点根目录留下一批「与子目录内文件同名同内容的孤立副本」：

  1）低版本站点直接升级到最新版（v1.4.2）过程中升级失败并自动回滚；
  2）升级成功后，若在后台对那次升级的备份执行手动回滚。

已实测：按该缺陷算法，一次升级的镜像文件（约 293 个）会全部被写到站点根目录，且原位还原数
为 0；2026-10-23、2026-10-24 两次现场清理合计 217 个此类文件均符合同一特征。
官方包 v1.4.0 / v1.4.1 / v1.4.2 已修复该缺陷，但老站此前留下的副本需要单独清理，本工具即
用于此。

本工具只清理「站点根目录一级的冗余副本」，不动任何子目录内文件、不动站点版本、不动数据库、
不动 config.php。

====================================================================
二、使用步骤
====================================================================
第 1 步：上传解压
  把本压缩包里的三个文件解压到站点根目录（与 config.php 同一层）：
    cleanstrays.php     本工具页面
    ref.json            判定参照清单（官方包嵌套文件清单，勿删勿改）
    使用说明.txt        本文件
  确认站点根目录里能直接看到 config.php。

第 2 步：打开工具页面，点击按钮执行清理
  浏览器访问：https://你的域名/cleanstrays.php
  页面先做「只读体检」，不修改任何文件，列出三类清单：
    ① 确认误落 —— 根目录文件与子目录内同名文件内容一致（或与官方包内同名嵌套条目一致），
       默认勾选；分区顶部另有【全选】【全不选】两个按钮，仅作用于本分区，便于批量调整；
    ② 疑似误落 —— 名字出现在子目录或官方包嵌套清单中、但内容不一致，默认不勾选，
       请人工确认后再勾选（【全选 / 全不选】不会影响本分区）；
    ③ 未识别 —— 与官方包和子目录都对不上，属站点自有文件，本工具不动它们。
   注：本站版本若高于 v1.4.0（超出适用范围），「确认误落」候选同样默认勾选，页面会给出醒目提示，
   如需一键取消可点【全不选】；执行前请逐项核对。
  核对清单与「保护范围」后，直接点击页末按钮：
    【执行清理所选文件并删除本工具】
  不需要再拼接或复制粘贴任何地址；直接用地址访问（GET）不会执行任何清理动作。

第 3 步：查看结果
  页面会给出：已清理文件明细、备份目录路径、工具自身三个文件的删除结果、清理后的站点根目录
  清单。清理成功后本工具的三个文件（cleanstrays.php、ref.json、使用说明.txt）会被自动删除，
  无需再手工删文件。

====================================================================
三、安全与可回滚
====================================================================
1）唯一执行入口是页面按钮：必须是 POST 提交 + 页面下发的一次性口令（口令绑定本站
   config.php 内容、站点版本与脚本自身路径），并要求请求来源与本站同源；直接访问带参数的
   地址不会执行任何改动。
2）只处理站点根目录「一级文件」，绝不递归删除；下列升级器保护/排除范围一律不删：
   config.php、uploads/、data/、cache/、static/、install/、composer.phar、.git/；
   官方包登记的根目录合法文件（index.php、config.php、captcha.php、web.config、.htaccess、
   README.md）同样默认保留。
3）删除前先备份：data/backup/cleanstrays_<时间戳>/files/（同名保存）+ manifest.txt
   （记录大小、时间戳、md5、判定层级、对应的正确路径）。任一项删除失败，已删除项会自动
   从备份回滚，页面给出逐项回滚结果。
4）幂等：清理成功后在 data/ 写入幂等标记 data/cleanstrays_lowver.done；再次访问会提示已
   执行过，如确需重跑需点击「重新体检并清理」按钮。
5）还原方式：把 data/backup/cleanstrays_<时间戳>/files/ 下的文件复制回站点根目录即可。

====================================================================
四、判定口径（页面会逐项标注）
====================================================================
  确认误落：根目录文件 md5 == 站点子目录内同名文件 md5；或根目录文件 md5 == 官方包中同名
            嵌套条目 md5。两者都说明它是「被写到根目录的副本」，删除不丢内容。
            （同名对照只取站点「子目录内」的文件；站点根目录一级文件不参与同名对照，
              避免根文件与自身同内容而被误判。）
  疑似误落：名字能在子目录或官方包嵌套清单中找到，但内容与同名文件不一致（可能是升级前后
            版本内容不同的旧副本，也可能是站点自建同名文件），需人工确认。
  未识别  ：既不是官方包登记的根目录文件，也不与任何子目录文件同名 —— 本工具不动。
  另有「需人工处理」告警：根目录合法文件名（如 index.php、captcha.php）的内容与某个包内嵌套
            条目一致，说明它可能已被误落副本覆盖，此时本工具不会改写，请用官方 v1.4.2 全量包
            对应文件覆盖恢复，或按官方渠道重新升级。注意 Windows 文件名不区分大小写，例如镜像
            文件 app/Core/Captcha.php 会覆盖站点根 captcha.php，此类情况也在本告警中列出。

====================================================================
五、常见问题
====================================================================
Q1 直接访问地址（或带参数的地址）为什么没有执行清理？
   本工具只有「页面按钮」这一条执行入口，GET 请求一律只做只读体检，这是为了避免被第三方
   页面或爬虫误触发。

Q2 提示「页面口令不正确」？
   请在本站域名下重新打开 cleanstrays.php，直接在页面上点击按钮；不要复制页面源码里的地址。

Q3 提示「删除失败（请检查文件权限）」？
   说明站点运行账号对这些根目录文件没有删除权限（同卷移动或跨目录复制可能导致 ACL 变化）。
   本次已自动回滚，未造成改动。请在主机面板/资源管理器中对报错文件重置权限（继承站点根目录
   的权限）后重试。

Q4 提示「data/ 目录不存在或不可写」？
   清理必须可回滚，因此要求能写入 data/ 目录创建备份。请先恢复 data/ 写权限后重试。

Q5 提示「本工具面向当前版本 ≤ V1.4.0 的站点，通常无需执行」？
   本站当前版本高于 V1.4.0。该缺陷只影响 V1.4.0 及以下版本直升 v1.4.2／升级失败回滚的场景；
   若确实曾受影响，仍可在页面执行——「确认误落」候选此时同样默认勾选，页面会给出醒目提示，
   如需全部取消可点【全不选】，再按需逐项勾选。

Q5b 「确认误落」的默认勾选为什么看起来没生效？
   该分区进入页面即默认全部勾选。若看到未勾选，请确认打开的是本版本工具页面并刷新一次；
   也可点分区顶部【全选】一键全选、【全不选】一键取消。若浏览器禁用了 JavaScript，
   【全选 / 全不选】按钮不可用，请手动逐个勾选，不影响执行。

Q6 执行后还想再检查一次？
   工具文件在清理成功后已自动删除，重新上传本压缩包后再访问即可（幂等标记会提示已执行过，
   确需重跑可点「重新体检并清理」）。

Q7 是否会改动站点文件内容或版本号？
   不会。本工具只删除站点根目录的冗余副本，不写任何子目录文件、不改 config.php、不改版本号、
   不访问数据库、不联网。

====================================================================
六、校验值
====================================================================
  cleanstrays.php    md5 = b296dadbf1f43beb990160d17af96b41
  ref.json           md5 = 117711a7a132b0979b78db1d75e945ac
  本工具包（zip）    md5 见发布目录的 v1.4.2-rootclean-md5.txt

  参照清单 ref.json 由以下官方产物生成，用于「同名嵌套条目」比对：
    qbcms-v1.2.0-full.zip、qbcms-v1.3.0-full.zip、qbcms-v1.4.0-full.zip、
    qbcms-v1.4.1-full.zip、qbcms-v1.4.2-full.zip，
    QBCMS_upgrade_1.3.0.zip、QBCMS_upgrade_1.4.0.zip、QBCMS_upgrade_1.4.1.zip、
    QBCMS_upgrade_1.4.2.zip，
    以及各版本官方「官网版」目录与发布树目录
    （qbcms-vX.Y.Z-官网版/、vX.Y.Z/，覆盖全量包未含的同一版本内容差异）。
  其中嵌套条目 basename 共 680 个（含各版本内容差异），本站 2026-10-23 现场清理出的 213 个
  误落文件名 100% 命中该清单。

  说明：同一版本存在「包内文件」与「目录树/官网版文件」两种内容变体（例：V1.3.0 的
  app/Core/View.php，包内为 fc9c708d…，树/官网版为 c946ee83…）。站点若由树或官网版安装，
  其升级残留副本只会命中后者；因此参照清单同时收录两类来源，避免个别残留被漏判为
  「疑似误落」而无法自动清理。