跳到主要内容
奇点CMS v0.4.70 · 官方网站 登录 / 注册 在线激活

奇点CMS 版本升级说明

1. 备份

чЙИцЬмхНЗч║зшп┤цШО.md 文档版本 v0.4.70 274 KB 更新于 2026-10-16
提示 该文档体积较大,本页仅渲染前 160 KB 内容;完整内容请查看交付包内 docs/чЙИцЬмхНЗч║зшп┤цШО.md。

奇点CMS 版本升级说明

本文档随交付包发布,记录通用升级流程与各版本的配置/依赖变更。

★ 维护约定:任何版本新增或变更扩展、依赖、服务器配置、数据表结构时, 必须同步更新 includes/SystemRequirements.php 注册表、docs/安装配置说明.md、 以及本文档的"版本依赖变更历史",打包时缺一不可(见 docs/打包说明.md)。


一、通用升级流程

1. 备份
   - 全站文件快照(宝塔:网站 → 备份,或 zip -qry 到 /www/backup/site/)
   - 数据库备份(面板 → 数据库 → 备份)
2. 覆盖升级
   - 解压新版本包覆盖站点目录
   - 严禁覆盖的运行时文件(新包中本就不含,覆盖前也请确认):
     config/generated.php、install.lock、data/ 全部文件、uploads/ 用户内容、.user.ini
3. 执行本版迁移脚本(见下方变更历史,均为幂等 SQL,可安全重复执行)
4. 配置变更(如本版要求新增 nginx location、ini 项等,见变更历史)
5. 验证
   - 后台 → 系统 → 部署检测:全部通过
   - 前台抽查:首页 / 小说频道 / 短视频 / 短剧播放 / 登录注册
6. 交付记录:登记升级时间与版本号

回滚方案:恢复步骤 1 的文件快照与数据库备份即可(迁移脚本均向后兼容,通常无需回滚数据)。


二、版本依赖变更历史

v0.4.70(将当前线上全局配色同步为站点默认值)

本版无新增扩展、无依赖/服务器配置/数据表结构变更;仅把当前线上生效的主题配色同步为 站点默认值,使未来新页面与全新安装都默认采用该套配色(主色 #636363 等)。

项变更是否需要手动动作
admin/theme.php$themeDefaults 数组与 12 个表单 ?? '#旧值' 回退值同步为线上值否
includes/fn-core.phpthemeStyle() 16 个 getSetting(...) ?: '#旧值' 回退值与 themeHexToRgbTriplet() 默认主色同步为线上值(主色 #636363,RGB 99,99,99)否
assets/css/style.css:root 主题变量同步为线上值,并补上原先缺失的 --video-bg / --drama-bg 两个变量否
database.sql10 个 theme_* 初始安装值同步为线上值否
  • 依赖要求不变:PHP 8.0+、MySQL ≥ 5.7 或 MariaDB ≥ 10.2;本版未新增扩展、未改 nginx 配置。
  • 升级动作:仅覆盖文件即可,无 SQL 迁移、无配置变更;已装站点的线上主题值不受影响。
  • 验证口径:
  1. 全新安装后前台默认配色即为本次同步的值(主色 #636363、背景 #cccccc、文本 #000000 等);
  2. 后台「主题设置」默认值与表单回退值一致。

v0.4.69(新增 Token 卡密支付系统 + 授权码当天缓存 + 若干体验增强)

本版新增 token_cards 表(卡密充值/支付闭环)与授权码当天缓存,均自动生效; 无新增 PHP 扩展、无 nginx/服务器配置变更、无 MySQL 引擎要求变化。

项变更是否需要手动动作
includes/token-cards.php(新增)Token 卡密业务模块:token_cards_table_ready() 首次调用自动建表(幂等);生成卡密 QDC-XXXX-XXXX-XXXX-XXXX(字符集去掉易混淆的 0/O/1/I);事务 + 行锁 + status 二次校验防并发重复核销否(访问卡密页面即触发建表)
admin/token-cards.php / card-redeem.php / card-topup.php(新增)后台卡密管理(批量生成 1~500 张)、前台卡密核销端点、个人中心卡密直充否
config/config.php版本号 v0.4.68 → v0.4.69;新增 LICENSE_API_DEFAULT 常量(官网接口默认地址)否(覆盖文件即生效;已装站点的 license_api 设置不受影响)
includes/LicenseManager.php授权码当天文件缓存 data/.license_authcode_cache(减少回连官网);官网地址读取超时 5 秒 → 2 秒否(缓存文件运行时生成,属 data/ 保留目录)
includes/functions.php模块加载器引入 token-cards否
  • 依赖要求不变:PHP 8.0+、MySQL ≥ 5.7 或 MariaDB ≥ 10.2;本版未新增扩展、未改 nginx 配置。
  • 升级动作:仅覆盖文件即可;token_cards 表由代码首次访问自动创建,无手工 SQL。如需立即确认建表, 访问后台「卡密管理」或前台卡密核销端点一次即可(或按 UPGRADE-v0.4.69.md 手动执行 CREATE TABLE IF NOT EXISTS)。
  • 验证口径:
  1. 后台「系统 → 部署检测」全部通过;
  2. 数据库出现 token_cards 表(SHOW TABLES LIKE 'token_cards');
  3. 首页小说/动态封面显示「点击量」蒙版,动态封面约 15 秒自动刷新;
  4. 阅读器章节标题显示「(字数:N,费用:M)」,登录用户滑动进入付费章节、余额足够时自动扣费;
  5. 后台「卡密管理」可批量生成卡密;前台支付页选择卡密支付并核销后余额到账。

v0.4.68(交付前复核修复:MariaDB 全新安装被 WITH PARSER ngram 阻断)

复核结论:database.sql 的 agent_knowledge 表带 FULLTEXT ... WITH PARSER ngram, 该语法为 MySQL 专有;MariaDB 直接报 1128 Function 'ngram' is not defined, 建表失败后安装向导/CLI 的建表完整性校验中断,MariaDB 上无法完成全新安装。

项变更是否需要手动动作
install/index.php(Web 向导)新增 xdreamDbEngine() / xdreamAdaptSqlForEngine(),执行 database.sql 前按引擎省略 WITH PARSER xxx;executeSqlFile() 回传首条 SQL 错误并在"缺失 N 张表"提示中展示真实原因否(重装/全新安装自动生效)
install/auto-install.php(CLI)同上,并在建表前打印检测到的引擎与适配说明否
includes/SystemRequirements.php「知识库中文全文索引」按引擎判定:MySQL 要求 WITH PARSER ngram,MariaDB 只要求 FULLTEXT(title,content) 存在(不再给出 MariaDB 无法满足的修复建议)否(升级后检测页不再误报警告)
database.sql更新 MariaDB 兼容性注释:安装向导、auto-install、DbUpdater 三个入口均已自动适配否
docs/安装配置说明.md同步说明两引擎的索引形态差异与 LIKE 兜底否
  • 依赖要求不变:MySQL ≥ 5.7 或 MariaDB ≥ 10.2(MariaDB 上无 ngram 解析器,中文短词检索 命中率低于 MySQL,由 agent-ai.php / AgentKnowledge.php 的 LIKE 兜底,功能可用)
  • 升级动作:仅覆盖文件即可,无 SQL 迁移、无配置变更;已装站点如需在 MySQL 上补 ngram 解析器, 走后台首页「数据库结构检查/同步」(DbUpdater 会按引擎自动决定是否附加 WITH PARSER)
  • 验证口径:后台「系统 → 部署检测」→ 「知识库中文全文索引」在 MariaDB 上应显示"通过(默认分词)"; 全新安装(向导或 CLI)完成后 agent_knowledge 等 4 张知识库表必须存在

v0.4.64(官网新增用户系统 + 主站总览页「登录官方网站 / 获取授权验证」)

需求:"给官网增加一个用户系统用于管理授权网站管理,在主站后台总揽页增加一个登录官方网站的接口用于获取授权验证"。

一、官网:授权用户中心(客户自助管理授权网站)

页面能力
official/user/login.php登录 / 注册同页(支持邮箱或 11 位手机号;注册限流 10 次/小时;登录限流 12 次/5 分钟);从客户站点跳转时显示「你从客户站点后台跳转而来(域名 xxx)」提示
official/user/index.php我的授权:授权列表(套餐 / 绑定域名 / 到期 / 激活次数 / 换绑次数 / 状态徽标)、凭授权码认领、自助解绑换域名、自助续验、下载安装包入口、修改密码、登录记录
official/user/logout.php退出登录
  • 会话隔离:用户中心用 OFFUSERSESSID,与官网后台(OFSESSID)完全分开;
  • 数据模型:新增 off_users 表,并给 off_licenses 自动迁移 user_id 列(首次访问自动完成,无需重跑安装向导);
  • 归属规则:一个账号可管理多个授权;授权码未被他人认领时可自助认领;已被他人认领时明确提示联系客服;越权操作(解绑/续验他人授权)一律拒绝。

二、官网后台:用户管理(official/admin/users.php)

  • 列表 / 搜索(账号·称呼·公司)/ 分页;统计卡片:注册用户数、已归属授权数、未归属授权(待认领);
  • 用户详情:名下授权逐条列出,可解除归属;可把指定授权码指派给该用户;可重置密码;
  • 停用/启用、删除(删除时其授权只解除归属、不删除授权本身)。

三、主站后台总览页:官方网站 · 商业授权卡片

在总览页顶部新增卡片,四块信息 + 三个按钮:

信息块内容
官网连接正常 / 不可达 + 当前官网地址
授权状态已授权商用 / 授权未通过 / 未绑定在线授权(含套餐与到期日)
版本本站版本 vs 官网最新版(已最新 / 可更新)
用户中心「查看我的授权 →」直达官网用户中心
按钮作用
登录官方网站跳转 官网/user/login.php?from=cms&domain=<本站域名>,带上来源与域名,登录后即可管理本域名授权
获取授权验证提交到「商业授权」页执行 do=check:调官网 ping + releases 校验授权与最新版本
授权设置进入商业授权页(填官网地址/授权码、激活、续验、解绑)

新增 LicenseManager::officialLoginUrl() 与 officialHomeUrl() 供跳转使用。

四、验证

数据层与业务(21 项断言全通过):
  off_users 自动建表 ✓   off_licenses.user_id 自动迁移 ✓
  注册成功 ✓  重复注册被拒 ✓  非法账号被拒 ✓  弱密码被拒 ✓  登录成功 ✓  错误密码被拒 ✓
  ★ 认领授权成功 ✓   授权已归属 ✓   重复认领提示已在自己账号下 ✓   ★ 我的授权列表可读 ✓
  越权解绑被拒 ✓   授权可被激活 ✓   ★ 用户自助解绑成功(剩余换绑 1)✓   未绑域名续验给明确提示 ✓
  旧密码错误拒绝改密 ✓   两次不一致拒绝 ✓   ★ 改密成功 ✓   新密码可登录 ✓
  ★ officialLoginUrl 可生成且带 from=cms ✓   officialHomeUrl 可用 ✓
  (测试用户与授权已删除,库内无残留 ✓)

HTTP 真机验证:
  官网 /user/login.php HTTP 200 / 6956 B 无报错 ✓   ?from=cms&domain= 变体 200 / 7064 B ✓
  /user/index.php 未登录 302 跳登录 ✓(守卫生效)
  官网首页 30000 B,导航含「用户中心」✓,零报错 ✓
  官网后台「用户管理」HTTP 200 / 6326 B,含用户表 ✓
  ★ 主站总览页 34122 B:含「官方网站 · 商业授权」卡片、「登录官方网站」按钮、「获取授权验证」按钮、
    官网连接状态、链接指向 user/login.php,全部命中且零 PHP 报错 ✓

v0.4.63(主站配置官网接口地址 qdcms.cn + 主站↔官网 API 打通)

需求:"给主站配置官网接口地址'qdcms.cn',主站与官网的API相关功能"。

一、官网接口地址已配置

主站设置项 license_api 已写入 http://qdcms.cn。实测该域名已经在运行官网系统(v1.0.2):

接口结果
ping✓ {"ok":true,"name":"奇点CMS","version":"v1.0.2","plans":4}
plans✓ 4 个套餐(个人/商业/多站/体验)
plugins✓ 12 个插件
releases✗ unknown_action —— 该动作是 v1.0.5 才新增的,线上官网需升级

也就是说:域名与授权链路已经通了,只差把官网升级到新版(同时获得安装向导修复、首页主站介绍、CMS 下载中心与 releases 版本检查接口)。

二、主站↔官网 API 功能矩阵(4 条链路)

#主站位置调用的官网接口作用
1后台 →「商业授权」activate / verify / deactivate / ping在线激活、续验、解绑换域名
2后台 →「插件与模块」plugins展示官方插件市场(名称/分类/版本/价格/说明)
3后台 →「商业授权 → 官网连接与版本」(本次新增)ping + releases连接状态、官网版本、最新版对比、一键下载新版本
4前台悬浮助手 / 工作面板无(本地模型或云端模型)不依赖官网

三、本次新增内容

#文件说明
1official/api.php新增 action=releases:返回最新版本(版本号/体积/SHA256/发布时间/下载数)、下载地址、下载页地址、完整版本列表,并可按传入的 current 做版本对比(up_to_date);动作白名单与限流同步补充
2includes/LicenseManager.php新增 officialInfo():一次拿到官网 ping 信息 + 最新版本对比(独立缓存 15 分钟,避免每次开页面都请求官网)
3admin/license-online.php新增「官网连接与版本」面板:官网地址与接口 URL、连接状态(正常/不可达)、官网名称与版本、本站版本 vs 官网最新版(含"可更新"提示)、检查连接与更新按钮、官网下载页/下载最新版按钮、上次检查时间

四、验证 → 16 / 16 通过

A. 本地官网地址下:
   officialInfo 连接成功 ✓  官网名称+版本 ✓
   ★ releases 返回最新版与下载地址 ✓(v0.4.61 / /official/download.php?f=QDCMS-v0.4.61.zip)
   ★ 版本对比正确(本地官网 v0.4.61 vs 本站 v0.4.62 → up_to_date=true)✓
   ★ 插件市场接口可用(12 个插件)✓   releases 结构完整(历史版本 1 个)✓   ping 正常 ✓
B. 写入 http://qdcms.cn:
   设置写入成功 ✓   连接结果 ok=true("授权服务正常")✓
   不可达时返回结构化失败、不抛异常 ✓   ★ 字段完整(api/up_to_date 均在)✓   授权摘要可读 ✓
C. 后台渲染:商业授权页 HTTP 200 / 16858 B 无报错 ✓(含「官网连接与版本」面板、显示 qdcms.cn)
   插件与模块页 HTTP 200 / 27868 B 无报错 ✓
D. 远端实测:qdcms.cn 的 ping/plans/plugins 均正常返回 ✓(releases 需升级官网到 v1.0.5)

v0.4.62(官网新增 CMS 下载中心)

需求:"给官方网站增加一个CMS 下载功能"。

一、前台:CMS 下载中心(official/download.php)

  • 最新版卡片:版本号、体积、发布时间、下载次数、SHA256 校验值、通道(稳定/测试); 「立即下载」按钮 + 「查看安装文档」;右侧给出安装三步(环境确认 → 上传解压 → 运行安装向导);
  • 历史版本表:全部版本可回滚下载,显示版本/类型/体积/发布时间/下载次数/SHA256 前 16 位, 文件缺失的版本会明确标注「文件缺失」而不是给个坏链接;
  • 三栏说明:环境要求(PHP 8.0+/MySQL 8.0/可选 FFmpeg·Ollama)、授权说明(可自由下载评估,商用需购买授权并绑定域名)、升级与支持;
  • 导航新增「下载」入口,首页 Hero 新增「下载安装包」按钮。

二、下载安全

  • 下载走 download.php?f=<文件名>:只允许 off_releases 表中登记过的文件名(先 basename 再白名单正则), 实测 ../../config/generated.php、index.php、不存在的文件全部 404 拒绝;
  • 流式输出(256KB 分块)+ 正确 Content-Type: application/zip + Content-Disposition 文件名 + Content-Length + X-Package-Sha256 响应头(便于用户校验);
  • releases/ 目录自动放置 .htaccess(禁目录列表 + 禁 PHP 执行)与 index.html。

三、后台:下载管理(official/admin/downloads.php)

  • 上传 zip(≤300MB):自动计算体积与 SHA256、可从文件名解析版本号;同文件名则更新该条;
  • 可选设为最新版(前台优先展示)、通道(稳定/测试)、标题与更新说明;
  • 列表操作:编辑信息、设为最新、上架/下架、删除(同时删文件);统计卡片显示已上架版本数、累计下载、当前最新版、存储目录;
  • 「扫描本机安装包」:自动发现交付包目录里的 QDCMS-v*.zip 并复制登记,一步上架。

四、验证(实测)

① 建表:off_releases 自动创建(无需重跑安装向导)✓
② 登记:QDCMS-v0.4.61.zip(1.61 MB,sha256 1f9c38d5a40b75b3…)✓
③ 下载页:HTTP 200 / 10066 B,含最新版卡片与授权说明,零 PHP 报错 ✓
④ ★ 真实下载:HTTP 200 / 1,686,687 B / application/zip,
   下载文件 sha256 = 1f9c38d5a40b75b39bc00c7ca4344f52bc34e2abef146ba7757dd3d033fda63d
   与源包**完全一致** ✓
⑤ 目录穿越防护:../../config/generated.php → 404 ✓  index.php → 404 ✓  nonexist.zip → 404 ✓
⑥ 后台「下载管理」:HTTP 200 / 8019 B,含上传表单 + 版本列表 + 扫描按钮,零报错 ✓
⑦ 首页「下载安装包」按钮 ✓  导航「下载」入口 ✓  页面标题「CMS 下载 - 奇点CMS 官方网站」✓

五、打包说明

交付包内不含安装包本体(避免"包中套包"):official/releases/ 只保留说明文件; 部署后到官网后台「下载管理」上传,或点「扫描本机安装包」自动登记交付包目录中的 QDCMS-v*.zip。

v0.4.61(官网首页改用主站真实内容做介绍)

需求:"官方网站的首页介绍内用主站QDCMS相关内容做介绍"。

一、首页新增「主站介绍区块」(Hero 之后)

不再只讲功能名词,而是用主站自己的模块与运行数据说话:

  • 12 个模块卡片:每个卡片显示模块名、真实计数、真实前台入口文件名(如 video.php、moments.php、qa.php)、 真实后台位置(如「内容 → 短视频」「工作面板 → Agent」)与一句话说明;
  • 计数为 0 的模块(如小说、短剧:装了但还没内容)显示 「已就绪」,不露"0 部小说"这种尴尬数据;
  • 运行实况条:数据库表数、已装载模块数、媒体文件数、注册用户数,以及计数最高的 6 个模块;
  • 标注「统计时间 xxx,每 10 分钟自动刷新」。

实测(线上首页,与数据库逐项核对一致):

12 个模块卡片:
  小说阅读 已就绪   novels.php         后台:内容 → 小说管理
  章节内容 已就绪                      后台:内容 → 小说管理
  短视频   10 条   video.php          后台:内容 → 短视频
  短剧     已就绪   drama.php          后台:内容 → 短剧
  动态广场 17 条   moments.php        后台:内容 → 动态
  百科词条 3 条    encyclopedia.php   后台:内容 → 百科
  问答     8 条    qa.php             后台:内容 → 问答
  时政资讯 5 条    news.php           后台:内容 → 资讯
  即时通讯 13 条   messages.php       后台:系统 → 聊天设置
  AI 知识库 18 条  agent-ai-api.php   后台:工作面板 → Agent
  Token 计费 38 条                    后台:运营 → Token
  用户体系 3 条    login.php          后台:用户 → 用户管理
运行实况:数据库表 55 张 · 已装载模块 12 个 · 媒体文件 21 个 · 注册用户 3 位
          · 短视频 10 · 动态广场 17 · 百科词条 3
首页体积 21872 B → 29786 B,零 PHP 报错 ✓

二、配套改造

#文件说明
1official/includes/main-bridge.php(新增)主站桥接:读取主站数据库(独立部署用官网后台配置的 main_db_*;同机子目录部署自动复用上层主站 config/generated.php),输出模块清单与统计;结果缓存 10 分钟(不给主站库添压力);连接失败绝不报错
2official/includes/main-intro.php(新增)首页介绍区块(模块卡片 + 运行实况 + 降级文案),由 index.php 一行 require 引入
3official/admin/main-site.php(新增)官网后台「主站数据」页:启用开关、主机/端口/库名/账号/口令(口令留空=不修改)、保存后自动清缓存立即生效、清除配置;并列出"首页将展示什么"(模块 × 前台入口 × 后台位置 × 当前计数)
4official/admin/includes/layout.php后台导航加入「主站数据」
5official/index.phpHero 之后接入介绍区块(仅一行 require,不改动既有结构)

三、降级与容错验证

① 关闭主站数据(main_db_enabled=0)→ 首页 29786 → 24042 B,显示"主站数据暂不可读 + 功能清单"文字介绍,
   零 PHP 报错 ✓(不白屏、不报错)
② 恢复为自动模式 → 首页 29787 B,模块卡片与实况数据回归 ✓
③ 主站数据库不可达时同样走降级分支(连接 3 秒超时 + 10 分钟缓存,不影响首页打开速度)✓

v0.4.60(官网安装向导 + 修复 open_basedir 告警)

用户实测反馈:Linux 宝塔环境部署官网时报 Warning: is_file(): open_basedir restriction in effect. File(/www/wwwroot/config/generated.php) is not within the allowed path(s): (/www/wwwroot/qd/:/tmp/) 随后页面显示"官网数据库暂时不可用",并询问"官方网站是否有安装向导"。

一、这是真 bug(已修)

官网内核为了兼容"与 CMS 同机子目录部署",会去探测上层目录的 ../config/generated.php。 独立部署时上层目录通常在 open_basedir 之外 → is_file() 触发告警;且探测失败后代码 直接走到"数据库暂时不可用",既没说清原因、也没给安装入口。

修法:

  1. 新增 offSafeIsFile():先判断目标路径是否落在 open_basedir 允许列表内, 不在允许范围就根本不去访问(不再产生任何告警);所有对上层目录/密钥文件的探测统一走它;
  2. 数据库连不上时区分两种情况: · 尚未安装 → 渲染引导页「官网尚未完成安装 → 打开安装向导」(不再是干巴巴一句报错); · 已安装 → 仍提示数据库不可用,并指向 data/official.log。

二、新增图形安装向导(回答"是否有安装向导":原来只有命令行,现在有了)

浏览器打开 install.php 即进入向导,三步完成,无需命令行:

步骤内容
① 环境检测PHP 版本、必需/可选扩展(pdo_mysql/mbstring/json/openssl/curl/gd)、config 与 data 目录可写、session 可用、open_basedir 现状
② 数据库与管理员填主机/端口/库名/账号/口令 + 后台账号密码;库不存在自动创建;建 9 张表、14 项默认设置、12 个插件条目、首条公告;写入 config/generated.php(0600)
③ 完成显示官网/后台/接口入口与安全提醒;写入 data/.install.lock 自动锁定向导

其它:保留命令行安装(--db-name/--db-user/--db-pass/--admin/--pass,幂等,适合脚本化/批量部署); 支持网址参数一键安装;已安装后再访问显示"已完成安装",删除锁文件可重装,带 ?token= 可进入。

三、复刻用户环境实测(决定性验证)

环境:全新目录(无 config/generated.php)+ 上层目录放一个主站配置(复刻 /www/wwwroot/config/…)
      open_basedir 严格限制为「站点目录 + C:/Windows/Temp/」——与用户的 /www/wwwroot/qd/:/tmp/ 同构
① 首页:HTTP 503,显示"官网尚未完成安装 → 打开安装向导"
   ★ open_basedir Warning:False ✓(修复前为 True)
   ★ 直接甩"数据库暂时不可用":False ✓
② 向导第①步环境检测:HTTP 200,4626 B(含 open_basedir 检测行)✓
③ 向导第②步提交:
   [ok] 数据库不存在,已自动创建 ✓  [ok] 已建/校验 9 张表 ✓
   [ok] 已创建管理员 ✓  [ok] 默认设置新增 14 项 ✓  [ok] 插件条目新增 12 个 ✓
   [ok] 已写入首条公告 ✓  [ok] 已写入独立配置 config/generated.php ✓  [ok] 已写入安装锁 ✓
④ 安装后:首页 HTTP 200 / 21449 B ✓   API ping → ok=true, version=v1.0.2 ✓
⑤ 再次访问向导 → 显示"已完成安装"并锁定 ✓
⑥ 清理:停服务器 / 删测试库 / 删模拟目录 ✓

v0.4.59(官方网站支持独立打包与独立部署 + 一处空值加固)

需求:"官方网站独立打包"。

一、官网现在可以脱离主站独立部署

原来官网内核从主站 ../config/generated.php 读数据库口令,只能"同机子目录"部署; 本次改造后支持两种形态:

形态访问地址数据库配置来源
独立站点(新增)http://官网域名/official/config/generated.php(由 install.php 自动生成,或照抄 config/generated.example.php)
主站子目录(原有)http://主站域名/official/无独立配置时自动复用主站配置,零配置即可启动
  • install.php 支持命令行/网址传入数据库参数,并会自动建库、写独立配置(权限 0600): ``bash php install.php --db-host=127.0.0.1 --db-name=qdcms_official \ --db-user=root --db-pass=口令 --admin=admin --pass=后台密码``
  • 新增 README.md(部署与接口速查)、docs/官网部署说明.md(独立/子目录/反代/HTTPS/权限/排查/升级全套)、config/generated.example.php 配置模板;
  • 新增独立打包脚本 qdcms-release/pack-official.py,产出 QDCMS-Official-v1.0.1.zip (36 个文件 / 128 KB,不含数据库口令、签名密钥、日志、限流计数)。

二、独立部署实测(不是"应该能跑")

① 打包:36 个文件 / 128,277 字节;含 config/generated.php:False ✓;含 data 敏感文件:空 ✓
② 解压到临时目录 + 建全新库 qdcms_official_test(模拟客户拿到包的干净环境)
   install.php 退出码 0:9 张表 ✓ 管理员 ✓ 14 项设置 ✓ 12 个插件 ✓ 公告 ✓
   已生成独立配置 ✓ 已生成签名密钥 ✓
③ 用 PHP 内置服务器真机验证(14 个端点):
   /index.php 21449B ✓  /features.php 32864B ✓  /plugins.php 25401B ✓  /plugin.php?slug=ai-agent ✓
   /pricing.php ✓  /activate.php ✓  /changelog.php ✓  /news.php ✓  /docs.php ✓  /about.php ✓
   /admin/login.php ✓  /api.php?action=ping ✓  /api.php?action=plans ✓  /api.php?action=plugins ✓
   ping 返回:name=奇点CMS version=v1.0.1 plans=4 sign=64 位 ✓
   重复执行 install.php(幂等):退出码 0,管理员未重复创建 ✓
④ 清理:停临时服务器 ✓ 删除测试库(残留 False)✓ 删除临时目录 ✓

三、顺带修掉的一处真实缺陷

官网导航数组是 [标签, 链接] 元组形式,我此前插入的「公告中心」写成了关联数组 ['label'=>..,'url'=>..],导致该条 $item[1] 为 null → 在 strict_types 下 offUrl(null) 抛 TypeError → 官网首页整体 500(页面从 21872 B 掉到 3450 B)。 已修正为元组形式,并给内核 offUrl()/offAsset() 增加 ?string 空值容忍(防御同类问题)。 修复后官网 10 个前台页面 + API 全部复测 200 且零报错 ✓

v0.4.58(官方网站 + 商业授权激活系统 + 插件市场)

需求:"根据网站开发一个官方网站,包含商业授权激活等功能,以及CMS 所需业务的和插件等功能"。

一、交付形态

官网位于 站点子目录 official/(http://你的域名/official/),复用现有 nginx/PHP, 无需新增站点、无需重载 nginx;数据存放在独立数据库 qdcms_official,与客户站点数据完全隔离。

模块内容
官网前台首页(产品亮点/库内统计/推荐插件/最新更新/套餐概览)、功能介绍(8 大模块 + 后台入口路径)、插件市场(搜索/分类/分页/详情/安装步骤/下载)、授权方案(4 套餐 + 对照表 + 购买流程 + FAQ)、在线激活与授权查询/解绑、更新日志(分页 + 版本徽标)、公告中心、文档中心(在线渲染 docs/*.md)、关于我们 + 在线留言
官网后台仪表盘(授权/日志/到期预警)、授权码管理(批量生成/域名绑定/续期/吊销/恢复/重置换绑/导出 CSV)、激活日志(近 7 天统计、失败原因、导出)、订单管理(含一键发码、状态流转、统计)、插件管理(上下架/编辑/排序)、公告与更新、客户留言、站点设置 + 改密码(含管理员列表)
授权服务 APIofficial/api.php:activate / verify / deactivate / query / plans / plugins / ping;响应 HMAC-SHA256 签名、请求 ts+nonce 防重放、按 IP 限流
授权模型授权码 QDCMS-XXXX-XXXX-XXXX-XXXX,服务端保存域名绑定/有效期/激活次数/换绑次数/吊销状态;激活返回带签名令牌(7 天),客户端本地缓存状态,官网临时不可达不会立刻失效
CMS 侧接入后台新增「商业授权」页:填官网地址 + 授权码 → 在线激活 / 立即续验 / 解绑换域名 / 清除配置;isLicensed() 增加"在线授权有效"这一条合法路径(保留原授权码与局域网放行逻辑);checkRemote() 由空实现变为真实探测
插件体系off_plugins 表(slug/版本/分类/价格/依赖/安装步骤/下载量/评分/推荐位),官网提供市场展示与详情,CMS 侧保留原有模块化结构;已内置 12 个真实插件条目(AI 智能体增强、本地大模型、短剧、动态增强、IM 即时通讯、百科、问答、后台工作面板、官方支付、二维码、批量部署、静态瘦身)

二、端到端验证(授权全链路 40/40 通过)

A 段(官网侧 19 项全通过):
  ping/plans/plugins 接口 ✓  响应 64 位 HMAC 签名 ✓
  发码(格式校验)✓  ★首次激活成功 ✓  返回带签名令牌(含套餐/到期/域名)✓
  ★换域名激活被拒 domain_mismatch(并带出已绑定域名)✓
  ★重复 nonce 被拒(防重放)✓   过期时间戳被拒 ✓
  公开查询返回脱敏信息(QDCMS-57Z****WU52)✓
  续验成功 ✓  解绑成功(剩余换绑 4 次)✓  解绑后可在新域名激活 ✓
  不存在的授权码被拒 not_found ✓

B 段(CMS 侧 21 项全通过):
  ★CMS 在线激活成功 ✓  状态 active 且域名正确(剩余 364 天)✓  授权码脱敏展示 ✓
  ★onlineLicenseValid=true ✓  ★isLicensed=true(在线授权生效)✓  ★checkRemote 真实可用 ✓
  ★CLI 场景:缺少域名时给出可操作提示 ✓  显式传域名可激活成功 ✓
  手动续验 ✓  6 小时内重复续验走缓存(不打官网)✓
  未配置官网地址/官网不可达 → 提示清晰 ✓
  ★被吊销后续验失败并说明原因 ✓  ★到期后续验失败 ✓
  续验失败不清空本地状态(保留便于排查)✓
  测试授权码/日志已删除、配置已复位、库内零脏数据 ✓

官网页面全量回归:10 个前台页面 + 8 个后台页面 = 18 个页面全部 HTTP 200 且零 PHP 报错 ✓

三、开发中发现并修掉的 6 个真实缺陷

#缺陷后果修法
1admin/includes/layout.php 用 dirname(__DIR__) 找内核整个后台 Fatal(登录都进不去)改为上溯两级
2admin/login.php 同样路径写错登录页 Fatal改为 __DIR__ . '/includes/layout.php'
3admin/index.php 重复定义 offStatusTag()仪表盘 Cannot redeclare删除重复定义(统一放 layout)
4官网内核 e()/money()/fmtDate() 等与主站同名两者同进程加载时 Cannot redeclare全部加 function_exists 守卫,官网内核可安全嵌入
5LicenseManager::onlineConfig() 走 getSetting 静态缓存点"在线激活"提示成功、但状态卡仍显示未配置改为直连数据库读取
6getCurrentDomain() 只读 HTTP_HOSTCLI/计划任务下激活报"域名不能为空"增加回退链(SERVER_NAME→已绑定域名→site_url→SITE_URL),并支持显式传域名

另外:子代理还发现并补了一处业务安全漏洞 —— 已取消的订单仍可发码(UI 只是隐藏了按钮),已加服务端守卫。

四、使用步骤

  1. 官网:浏览器打开 http://你的域名/official/;后台 .../official/admin/(初始账号 admin,首次安装密码见安装输出,请立即在「站点设置」修改);
  2. 发码:官网后台 →「授权码管理 → 生成授权码」(或在「订单管理」里一键发码);
  3. 激活:客户站点后台 →「商业授权」→ 填官网地址(http://你的域名/official)与授权码 → 在线激活;
  4. 续验/换域名:同页面点「立即续验」「解绑换域名」;
  5. 插件市场:官网「插件市场」展示 12 个插件,可在后台维护上架/版本/价格/安装说明。

v0.4.57(交付包与说明文档的站点名称统一为「奇点CMS」)

需求:"打包说明站点名称改为『奇点CMS』"。

一、依据(你们自己的打包约定)

docs/打包说明.md 第 312 行原文:

站点名称 SITE_NAMEconfig/config.php奇点CMS打包的站点默认名称为"奇点CMS",交付包必须使用该通用名称;客户个性化站名由客户安装后自行修改,不得将任何客户特定站名带入交付包

生效版本:自 v0.3.80 起。

核对后发现:SITE_NAME 常量早已是「奇点CMS」,但包内仍有多处旧名残留,其中还有功能性缺陷:

#位置问题
1database.sql新装默认站点名竟写入 墨香书阁、页脚写入 Powered by QDCMS —— 客户装完看到的是旧名(真正的问题)
2includes/footer.php页脚兜底文案 Powered by X-Dream CMS
3admin/settings.phpSEO 标题占位提示 "如:墨香书阁 - 在线小说阅读平台"
4install/index.php安装向导标题 QDCMS 安装向导
5includes/OpsAgent.php / admin/ajax-workbench.phpAI 人设自称 QDCMS;OpsTools 体检报告标题 # QDCMS 站点体检报告
6assets/js/agent.jsAI 回复兜底版本名 X-Dream CMS
7文档标题 ×5、代码/脚本/SQL 头注释 ×60+仍为 X-Dream CMS

二、改动(共 60 个文件、84 处)

  • 新装默认值:站点名 / 页脚 → 「奇点CMS」;
  • 用户可见文案:安装向导标题、SEO 占位提示、AI 人设(后台助手/运维助手)、体检报告标题、AI 回复兜底版本名、对话导出标题、运维巡检日报标题、qdcms-ffmpeg-win.ps1 与各 JS 头注释;
  • 文档标题:README、打包说明、安装配置说明、Windows 部署说明、架构设计、版本升级说明、网站BUG修复规范、页面模块规范、聊天说明、Go 服务 README → 「奇点CMS」;
  • 明确不改(避免破坏功能与历史):
  • 技术标识:x-dream-go / x-dream-go.exe、qdcms-*.ps1 脚本名、QDCMS-v*.zip 包名正则、assets/images/qdcms-logo.png、数据库名、C:\ProgramData\QDCMS\reports 目录;
  • install/install.lock 内容(X-Dream CMS Installed,被安装锁逻辑使用);
  • 历史文档正文:docs/版本升级说明.md、docs/网站BUG修复.md 中的历史条目原文,以及 docs/打包说明.md 中记录"v0.3.79 及更早沿用旧名属已确认例外"的约定正文。

三、验证

包内品牌核对(13 项):
  config/config.php  SITE_NAME = 奇点CMS ✓
  database.sql       新装默认站点名 = 奇点CMS ✓   默认页脚 = Powered by 奇点CMS ✓
  includes/footer.php 兜底 = Powered by 奇点CMS ✓
  文档标题:README / 打包说明 / 安装配置说明 / Windows部署说明 / 版本升级说明 均为「奇点CMS」✓
  AI 人设:奇点CMS 的网站运维助手 ✓   奇点CMS 后台的助手 ✓
  体检报告标题:# 奇点CMS 站点体检报告 ✓    安装向导标题:奇点CMS 安装向导 ✓
语法:PHP 全部文件 php -l 通过 ✓   PowerShell 脚本 Parser 解析通过 ✓
剩余"用户可见旧名":0 处(仅保留打包约定文档中记录历史例外的正文)✓

v0.4.56(优化服务器资源调用、减少内存占用)

需求:"优化网站在网站服务器的资源调用,减少内存占用"。

一、先量后改(本机 7.73 GB 内存)

指标实测结论
物理内存已用 6.75 GB / 7.73 GB(87.2%),可用 1.01 GB;页面文件已用 13.65 GB内存确实紧张 ✓
PHP 单请求峰值内存4 MB,耗时 15~51ms,加载 24 个文件★ 站点代码本身很省(无需改代码)
php-cgi20 个进程 / 521 MB(父进程 nginx_server=宝塔进程池)★ 是进程池太大,不是负载
MySQLmax_connections=500 ✗、max_allowed_packet=1GB(my.ini 里竟写着 100G)✗、key_buffer_size=64M(本站全 InnoDB,用不上)✗、performance_schema=ON ✗★ 配置是"大服务器"模板
其他进程非服务器软件合计 3048 MB(含本 Agent 工具 1240MB、Edge 598MB、手机助手 275MB、Defender 272MB、vivo 199MB、Dell 166MB)用户侧可释放

二、已做的优化(MySQL,全部在线生效并持久化)

用 MySQL 8 的 SET PERSIST(同时写入 mysqld-auto.cnf,重启后依然有效):

参数原值现值说明
max_connections50080每个连接都会预留缓冲;本站并发极低
max_allowed_packet1 GB(my.ini 里写 100G)64 MB上传走 4MB 分片,64MB 绰绰有余;这项决定连接级提交内存上限
key_buffer_size64 MB16 MB该参数只服务 MyISAM,本站表全是 InnoDB
tmp_table_size64 MB32 MB并发查询时按查询累加
innodb_log_buffer_size32 MB16 MB—

performance_schema=OFF 已写入 my.ini(含备份),但它属于启动参数,需重启 MySQL 才生效 —— 生效后可再省 150~300 MB。

三、新增运维工具:服务器资源与内存

includes/OpsTools.php 新增 server_memory 工具(已注册进工具目录与"一键体检"), 在后台「工作面板 → ③ 运维工具」点一下即可看到:

  • 物理内存/页面文件占用(已用%、可用量)
  • PHP 进程池进程数与占用
  • MySQL 关键内存参数(连接上限、packet、key_buffer、tmp_table、performance_schema 状态)
  • 进程占用 Top 15
  • 非服务器软件占用合计(含进程名与体积)
  • 自动给出可执行的省内存建议(例如"进程池 20 个,建议降到 6~8,约省 144MB")

四、验证

$ OpsTools::run('server_memory')
  概要: 内存 6.75 GB / 7.73 GB(已用 87.2%,可用 1010 MB);PHP 进程池 20 个 521MB;MySQL 连接上限 80
  MySQL: buffer_pool=256MB, max_connections=80, max_allowed_packet=64MB,
         key_buffer=16MB, tmp_table=32MB, performance_schema=ON(待重启)
  建议 4 条:可用内存低于15% / PHP 进程池偏大 / performance_schema 待关 / 3048MB 非服务器软件可关闭

MySQL 新连接复核(会话值随新连接刷新,确认不是"只改了会话"): max_connections=80 ✓ max_allowed_packet=64MB ✓ tmp_table_size=32MB ✓ key_buffer_size=16MB ✓ innodb_log_buffer_size=16MB ✓

新增工具已并入"一键体检"清单,共 11 个工具。

五、还想再省内存?两条可选(都在你手上)

  1. 重启一次 MySQL(宝塔 → 数据库 → 重启),让 performance_schema=OFF 生效 → 再省 150~300 MB;
  2. 宝塔 → PHP 设置里把进程数降到 6~8(本站流量很低,20 个是浪费)→ 再省 约 144 MB;
  3. 不用时可关闭 Edge / 手机助手 / vivo 助手 / Dell 支持助手 等非服务器软件 → 最多可释放 约 1.8 GB。

v0.4.55(修复全站客户端加载缓慢)

现象:全站页面"打开后图片/内容慢慢才出来",尤其首页与动态广场。

一、先量后改(关键数据)

指标实测判断
服务端 TTFB首页 0.098s、其他页 0.04~0.11s服务端不慢 ✓
HTML 大小9.6~44 KB,gzip 后 6 KB正常 ✓
gzip(真实入口 65500 + 域名)style.css 26987→7039、HTML 38722→6151本来就是好的 ✓
静态资源缓存Cache-Control: max-age=2592000 + ETag正常 ✓
外部 CDN / 字体0 个无外链阻塞 ✓
首页图片6 张原图 = 886 KB,单张最大 403 KB★ 元凶

二、根因(两处叠加)

  1. 图片加速器正则漏掉了带前导斜杠的写法 ✗ includes/fn-core.php 的 imageAcceleratorBuffer() 只匹配 src="uploads/…", 而大量模板(首页、列表页)输出的是 src="/uploads/…" —— 于是这些图片完全绕过加速器, 既不压缩、也不走 30 天缓存、也不做懒加载,直接下发原图。
  2. 首页卡片代码直连原图 ✗ index.php 的热门动态卡片用 baseUrl() . 原图路径 直接输出 —— 实测单张 403 KB。

顺带确认:站内的 img-proxy.php 本身实现良好(磁盘缓存 + ETag + 30 天头), 同一张 394 KB 原图经它 400px 处理后只有 40.8 KB,命中缓存仅 7.6 ms —— 只是没被用上。

三、改动

#文件改动
1includes/fn-core.php加速器正则扩宽为接受 uploads/、/uploads/、http(s)://…/uploads/、//host/uploads/ 四种写法;已是代理 URL 的原样跳过(防重复代理);thumb_* 缩略图按卡片尺寸(400px)压缩而不是按原图尺寸重编码;补 decoding="async" 减少主线程解码阻塞
2index.php首页热门动态卡片改用 imgUrl($path, 400, 'cover'),直接走 400px 代理版本

四、效果(同一入口实测)

页面修复前修复后降幅
首页图片886 KB(6 张原图直连)206 KB(6 张走代理,单张 22~46 KB)−77%
动态广场每张缩略图 ~90 KB 直连走代理 ~44 KB 且懒加载−50%(加上懒加载首屏更低)
其他含图页面同样绕过加速器统一走代理同量级下降

五、验证 → 13 / 13 通过

A. 加速器正则:uploads/ ✓  /uploads/(修复点)✓  http://…/uploads/ ✓  //host/uploads/ ✓
   静态资源不动 ✓  已是代理 URL 原样返回(不嵌套)✓
B. 首页图片:6 张全部走代理 ✓  无 404 ✓  ★ 总量 206.2 KB < 300 KB(修复前 886 KB)✓
C. 全站回归:首页/动态/短视频/短剧/小说/问答/百科/资讯/登录/分类 共 10 个页面全部 HTTP 200、无 PHP 报错 ✓
D. 代理图片带 30 天缓存头 ✓  返回 image/jpeg ✓

v0.4.54(修复"运行环境下载不了")

现象:点「下载安装包」后一直转圈 / 任务永远"进行中",安装包始终是 0 字节。

一、诊断(有据)

证据结论
你那次点击的任务日志卡在 0 字节 25 秒后 curl: (56) Recv failure: Connection was reset
ollama.com/download/OllamaSetup.exe307 → GitHub Release(objects.githubusercontent.com)
实测下载速度ollama 官网直链 4.7 KB/s;GitHub 直链 27 KB/s;gh-proxy.com 镜像 46 KB/s;另两个镜像不可用
registry.ollama.ai(模型仓库)200 ✓ —— 模型拉取链路正常,只有安装包链路被卡

→ 700MB 按实测速度需 4 小时以上,且连接会被重置;这是网络链路问题,不是页面 bug。

二、同时修掉的 3 个真实代码缺陷

#缺陷后果修法
1任务脚本里写了 if exist X (echo 完成) else (echo 失败) >> log重定向只作用于 else 分支,且该写法语法中止 → 退出标记从未写入 → 任务永远"进行中"抽出 buildBatScript():顺序命令 + 每行独立重定向 + 末尾无条件写退出标记
2用 pclose(popen('start /B …')) 启动后台任务pclose 会等待子进程关闭继承的 stdout 管道 → 长时间下载会把 PHP/AJAX 请求挂住数小时(浏览器一直转圈)改用 PowerShell Start-Process -WindowStyle Hidden 真分离(回归测试:60 秒任务 0.96 秒返回)
3任务状态读取整份日志长下载日志数十 MB → 拖垮请求只读日志尾部 256KB + 僵尸判定(日志 5 分钟无变化即视为已结束)

三、本次新增能力

能力说明
多来源自动回退gh-proxy 镜像 → GitHub 直链 → ollama 官网 → ghproxy 镜像(按实测速度排序)
断点续传curl -C -,可反复点「继续 / 重试下载」,已下载部分不白费
自动重试--retry 3 --retry-all-errors,连接被重置会自动重连
数字签名校验下载后自动 Get-AuthenticodeSignature,必须 Valid 且签发者含 Ollama(第三方镜像的安全性由此兜底);页面新增「校验安装包」按钮
手动放置在别的网络环境下载 OllamaSetup.exe,放到服务器 data/model_jobs/ 后点「校验安装包」即可(最快的办法)
停止下载新增「停止下载」按钮(白名单式只结束 curl/powershell,保留已下载部分)
dryRundownloadInstaller(true) 只生成脚本不启动,便于自检与预览

四、验证 → 26 / 26 通过

A. 多源列表:4 个来源,首选为实测最快的镜像 ✓
B. 脚本生成:含 -C - / goto 多源回退 / --retry-all-errors / 签名校验 ✓
   ★ 不再有 if exist…else…>> 陷阱 ✓   ★ 退出标记无条件写入 ✓   dryRun 不产生真实任务 ✓
C. 断点续传:检测到部分文件时提示续传 ✓
D. 僵尸任务:日志 400 秒无变化 → 不再显示"进行中" ✓
E. 安装包校验:不存在/体积不足 均正确拒绝 ✓
F. 页面/端点:200 无报错、含继续下载+校验+停止+手动放置说明与实测网速提示 ✓
   verify / stop / status 端点均正常 ✓
回归:60 秒长任务 startJob 返回 0.96s(不再阻塞请求)✓

五、你要怎么做(两条路,任选)

  1. 推荐(最快):在手机/电脑(可开加速)下载 OllamaSetup.exe,拷到服务器 D:\wwwroot\qdcms\data\model_jobs\,回页面点「校验安装包」,通过后双击安装 → 点「启动服务」→ 再下载模型(模型仓库链路实测正常)。
  2. 让服务器自己下:已为你在后台启动了下载(可断点续传),页面会显示进度;数小时后若中断,回来再点一次「继续 / 重试下载」即可。

v0.4.53(把站内百科与问答接入知识库检索)

需求:"将站内的百科和问答接入知识库检索"。

一、之前的状况

项状况
知识库只有 8 条问答 + 7 条站点事实,百科词条完全没进去
百科表encyclopedia 有 3 条已发布词条(长城 / 人工智能 / 唐诗),Agent 完全不知道
片段表agent_knowledge_fragments 0 条 —— 代码里 fragment() 方法从未被调用,长内容的片段级检索等于没有
增量原来只按"标题哈希"去重,内容改了不会更新,标题改了还会重复

二、改动

#文件改动
1includes/AgentKnowledge.php① ingestFact() 支持 source_id 主键去重 + 内容变更自动 UPDATE + 长内容自动写片段(refreshFragments,先清后建幂等);② 新增 ingestEncyclopedia()(取标题+摘要+正文+标签,非已发布状态自动移除)、ingestQaQuestion()(题目+补充+已采纳优先的回答)、removeKnowledge()、syncEncyclopedia()、syncQa()、syncSiteContent()(一键同步,幂等);③ autoSeedSite() 现在一并同步百科 → 新装站点也"部署即用"
2admin/encyclopedia.php保存 / 新建 / 审核通过 → 自动入库;下架 → 自动移出知识库
3encyclopedia-edit.php(前台编辑)编辑 / 新建词条保存后自动入库
4includes/fn-qa.phpaddQaAnswer() 提交回答、acceptQaAnswer() 采纳回答 → 自动同步该问题到知识库
5admin/qa.php关闭问题 → 移出知识库;恢复问题 → 重新入库
6admin/workbench.php + ajax-workbench.php知识库卡片新增「同步百科与问答」按钮(新动作 kb_sync),执行后回显"扫描/新增/更新"明细

所有钩子都包在 try/catch 里 —— 知识库同步失败绝不影响百科/问答本身的保存。

三、验证 → 27 / 27 通过

A. 批量同步:百科扫描 3 → 入库 3;问答扫描 8 → 共 16 条;重复同步新增 0(幂等)✓
B. 检索:★「长城 防御工程」命中百科词条「长城」✓   片段表从 0 → 6 条(长城自身 2 条)✓   问答可命中 ✓
C. 增量更新(临时词条,全程用完即删):
   ★ 新建词条 → 立即能被检索到 ✓
   ★ 改内容 → 知识库同步更新且**不新增重复**(条数仍为 1,含新内容)✓
   ★ 下架 → 自动移出知识库,片段一并清理 ✓   临时数据已删 ✓
D. 问答钩子:临时问题+回答 → ★ 提交回答即自动入库 ✓ → ★ 可被检索命中 ✓ → 临时数据已清理 ✓
E. 真机端到端:问「长城是什么?」→ source=cloud
   答案:长城又称"万里长城",是中国古代规模浩大的军事防御工程,横跨15个省(区、市),
        总长度超过2.1万千米,1987年列入《世界遗产名录》。它始建于春秋战国,秦始皇连接增筑,
        明代修筑最完善。长城不只是城墙,还包括……      ← **完全来自百科知识库**
F. 面板/端点:含「同步百科与问答」按钮 ✓  来源分布展示 ✓  无 PHP 报错 ✓  kb_sync 幂等 ✓
数据卫生:无测试残留(知识库/百科/问答中的"自检"条目均为 0)✓

四、效果

  • 站内百科词条与问答现在都是 Agent 的事实来源:问「长城是什么」「怎么设置短视频单独价格」都能得到基于站内内容的回答;
  • 增量自动同步:后台改词条、审核通过、下架,前台提交/采纳回答 —— 知识库都会自动跟着变,无需手动维护;
  • 顺手修好片段级检索(此前 0 条数据形同虚设),长词条也能被精准命中。

v0.4.52(网站 Agent 知识库对接模型,实现"部署即用")

需求:"把网站Agent 知识库对接模型,实现部署即用"。

一、之前的两个断点

#问题后果
1知识库是空的(agent_knowledge 0 条,实测)模型再强也没有"本站事实"可依据,只能泛泛而谈
2模型完全没读知识库(AiFrontAgent 里搜不到任何 knowledge 相关代码;AgentProcessor 命中知识库时直接返回模板答案,不经模型)知识库与模型各干各的,谈不上"对接"

二、改动

#文件改动
1includes/AgentKnowledge.php新增 ingestFact()、autoSeedSite()(用站点自己的数据自动灌库:站内问答表 8 条问答 + 站点定位/栏目导览、Token 规则、上传限制(取真实常量)、靓号规则、认证、站内信、客服共 7 条事实;按 content_hash 去重,幂等可重复执行)、buildContext()(命中压缩成模型上下文)、contextFor()(一句话完成"检索 + 组上下文");并把原有 stats() 扩展为超集(保留旧字段)
2includes/AiFrontAgent.php回答前自动检索知识库,命中则把事实作为 system 上下文注入模型(RAG),并在结果里返回 kb_hits / kb_used;无命中或知识库为空时自动跳过 → 部署即用
3includes/AgentProcessor.php知识库命中时改由模型基于命中内容作答(skill=knowledge+model);模型不可用时静默回落原知识库答案(skill=knowledge)
4admin/workbench.php + ajax-workbench.php② 页签新增「知识库 ↔ 模型」卡片:条目数/累计命中/今日命中/来源分布 + 一键导入站点知识 + 「检索命中」自检(新动作 kb_seed / kb_stats / kb_probe)

三、验证 → 27 / 28 直接通过 + 1 项独立进程补验通过

A. 知识库内容:15 条(site_qa 8 + 站点事实 7)✓  统计字段完整 ✓  **灌库幂等**(二次执行 0 新增)✓
B. RAG 注入(桩模型查载荷):kb_hits=4 ✓  ★载荷含知识库原文"通用消费凭证" ✓  人设仍在 ✓
   用户问题在载荷 ✓  注入长度 974 字(受控)✓
C. 无命中时不注入:kb_used=false ✓  上下文无知识库字样 ✓
D. AgentProcessor:★命中时 skill=knowledge+model(模型按站点事实作答)✓
   独立进程补验:把接口地址改成不可达 → skill=knowledge(**回落知识库原答案**)✓
E. 真机端到端:问「看短视频要消耗多少 Token?」
   → source=cloud,1047ms/628 tokens
   → 答案:看短视频会消耗 Token,具体金额由系统按内容定价自动扣费,播放前页面会显示价格。想了解余额和扣费明细,
     可登录后进入「Token 记录」查看。如果 Token 不够,在个人中心充值即可。   ← **完全基于本站知识库事实**
F. 面板/端点:卡片与按钮 ✓  条目数展示 ✓  无 PHP 报错 ✓
   kb_stats total=15 ✓  kb_probe 命中 4 条/768 字上下文 ✓  kb_seed 重复执行 0 新增 ✓
数据卫生:知识库 15 → 15 未被污染 ✓  模型配置已还原为云端 ✓

本轮修掉一个自身缺陷:新增的 stats() 与既有同名方法重复声明导致致命错误 → 已改为删除重复实现、把既有 stats() 扩展成超集(保留原字段,避免影响其它调用方)。

四、效果

  • 部署即用:装上模型(云端或本地皆可)后,点一次「一键导入站点知识」,Agent 立刻能回答"本站怎么用/要花多少 Token/上传限制/靓号/认证/客服"等站点问题 —— 因为回答建立在知识库事实上,而不是模型自由发挥;
  • 知识库为空时也不报错,模型照常回答(自动跳过注入);
  • 知识库内容会随站内问答增长继续补充(原有 autoIngest 仍在工作)。

v0.4.51(本地模型运行环境 + 模型下载部署 + 部署后自动接入 Agent)

需求:"给网站增加一个本地模型运行环境和模型下载部署功能,植入模型下载页面,部署后自动接入网站Agent"。

一、选型(基于本机实测)

探测项结果结论
是否已装 Ollama未安装、11434 端口未监听需要提供安装与部署流程
内存 / CPU / GPU7.7 GB(可用 1.9 GB) / i3-1115G4 2 核 / Intel UHD 集显只能跑 0.5B~1.5B 小模型;清单已按此标注
磁盘 D:可用 111.8 GB空间充足
ollama.comHTTP 200可下载安装包 ✅
huggingface.co超时GGUF 直链不可用 → 走 Ollama 官方仓库 ✅

→ 因此运行环境选 Ollama:官网一键安装、自带模型仓库、并暴露 OpenAI 兼容端点 http://127.0.0.1:11434/v1 —— 本站既有 AiClient 零改动即可对接,这是"部署后自动接入"能成立的关键。

二、新增内容

#文件说明
1includes/LocalModel.php(新增)运行环境检测(可执行文件/版本/服务状态/已装模型/内存)、6 个候选模型清单(体积+内存建议)、后台异步任务引擎(安装包下载 / 模型拉取 / 启动服务)、进度解析、接入与回滚(写入 ai_* 配置并备份原云端配置)
2admin/local-model.php(新增)「本地模型」页面:状态总览 + 三步走(① 下载安装包并启动服务 ② 选模型下载部署(含进度条与日志实时刷新)③ 一键接入网站 Agent / 测试 / 恢复云端配置)
3admin/ajax-local-model.php(新增)10 个动作(status / download_installer / pull / job / job_clear / serve / activate / revert / test / delete),仅管理员 + CSRF
4admin/header.php左侧导航顶层新增「本地模型」(与「工作面板」并列)

接入后立即生效范围:后台「工作面板 → ①」显示本地端点;前台悬浮助手改用本地模型回答(AiFrontAgent 读同一份配置);原云端配置自动备份,可一键恢复。

三、验证 → 55 / 55 通过

A. 真实环境检测(本机当前未安装)
   状态结构完整 ✓  如实报告未安装/服务未运行 ✓  OpenAI 端点正确 ✓  读到内存 7.7GB ✓
   清单 6 项含体积与内存建议 ✓  首项为低配推荐 0.5B ✓  安装包为官方 URL ✓
B. 后台任务引擎(真实跑一个任务)
   任务启动 ✓  登记可跨请求查看 ✓  执行完毕 ✓  退出码 0 ✓  **进度解析到 100%** ✓
   日志落盘 ✓  日志尾部可读 ✓  任务可清除 ✓  parseProgress 取最大值/空日志安全 ✓
C. 拉取任务组装(假 exe,不产生真实下载)
   任务启动 ✓  批处理含 OLLAMA_HOST 与 pull 命令 ✓  **非法模型名被拒(防注入)** ✓
   失败任务如实以非 0 退出码结束(9009)✓
D. 接入 / 回滚
   未安装的模型接入被拒 ✓  接入成功 ✓  base_url 切到 11434/v1 ✓  模型名写入 ✓
   CA 包自动清空(本机 http 不需要)✓  前台 Agent 同步启用 ✓  云端配置自动备份 ✓
   一键恢复云端成功 ✓  isActivated() 语义正确 ✓
E. 页面与端点
   页面 HTTP 200、无 PHP 报错 ✓  含三步/6 个候选模型/全部按钮/导航入口 ✓
   端点 401/403/419 鉴权链路 ✓  status 返回环境与清单 ✓  非法模型名友好报错 ✓
   activate 未启动服务时明确提示 ✓  revert 无备份时友好提示 ✓

本轮修掉的 3 个真实缺陷(都是测试逼出来的):

  1. jobStatus() 用 getSetting 读任务键 → 启动任务后立刻查状态拿不到(进度条永远空白)→ 改为直连数据库读取;
  2. admin/local-model.php 用了 LicenseManager 却没 require → 页面 HTTP 500 → 已补;
  3. 批处理各命令输出没有重定向进日志 → 只有退出标记落盘,进度永远 0% → 现在每行自动追加 >> 日志 2>&1。

四、你要怎么用(三步,都在新页面里)

  1. 后台 →「本地模型」→ ① 下载安装包(约 700MB) → 下载完成后在服务器上双击运行完成安装 → 回到页面点「启动服务」;
  2. ② 在清单里选 qwen2.5:0.5b(约 397MB,最省内存,推荐) 点「下载部署」,页面会实时显示进度与日志;
  3. ③ 部署完成后点「接入网站 Agent」 → 立即生效(前台悬浮助手 + 后台工作面板同时切到本地模型); 想换回云端点「恢复云端配置」即可。

提醒:本机 7.7GB 内存、2 核 CPU,1.5B 模型单次回答约 5~20 秒;若追求速度,建议本地模型只作为云端不可用时的备份,云端仍用 DeepSeek(当前配置)。

v0.4.50(云端模型接入前台 Agent,实现真问答)

需求:"把云端模型 接入前端Agent 实现问答"。

一、以前前台 Agent 是"假 AI"

前台悬浮助手(assets/js/agent.js)的问答链路是: 站内搜索 → 有结果就套模板 ✓ → 没结果时调 agent-ai-api.php → MicroAI(规则/意图引擎,不是真模型) ✗ → 最后再兜底本地关键词。 即"回答"是模板拼接出来的,不会真正理解问题。

二、改动

#文件改动
1includes/AiFrontAgent.php(新增)前台模型问答:站点人设提示词、按用户/访客 IP 的每小时额度、全站每日预算保险丝、问题长度 300 字上限、max_tokens ≤ 600、超时 20s、每次调用写用量日志(时间/身份/字数/tokens/耗时/成败)、今日统计
2agent-ai-api.php优先走云端模型;额度用尽 / 未配置 / 调用失败 → 静默回落原规则引擎,用户侧无感知;响应新增 source(cloud/rule)、tokens、elapsed_ms、remaining,回落时附 cloud_note 便于后台排查
3assets/js/agent.js前端超时 5 秒 → 25 秒(真模型首字偶发 2~5 秒,原来会被提前掐断)
4admin/workbench.php + ajax-workbench.php② Agent 页签新增「前端 Agent 接入」:开关、每小时额度(登录/游客)、每日预算、前台人设提示词、今日用量统计(调用次数/tokens/平均耗时/失败数)

三、验证

A. 真机端到端(真实调用 DeepSeek)

★ 提问「用一句话介绍本站能做什么」→ source=cloud,tokens=139,760ms
   答案:墨香书阁是一个在线读书平台,你可以搜书、看书、收藏好书,还能管理自己的书架。
★ 把 base_url 临时改错(模拟断网)→ source=rule(自动回落规则引擎,用户侧仍然成功)
   并附 cloud_note:网络错误: Failed to connect to 127.0.0.1 port 9 ✓   之后 base_url 已还原 ✓

B. 额度与开关(独立子进程复测,等同于生产每请求新进程)

启用 + 游客额度 2  → 通过 2/3,模式 cloud,cloud,quota      ✓
游客额度 0        → 全部 quota(不开放云端)              ✓
关闭开关          → 全部 disabled(不调用模型)            ✓
登录用户额度 3     → 通过 3/4,模式 cloud,cloud,cloud,quota ✓

C. 单元(桩模型):答案透传 ✓、tokens/耗时记录 ✓、系统提示词含站点人设与指引要求 ✓、max_tokens 受限 ✓、extraContext 注入 ✓、模型报错返回结构化失败(调用方可回落)✓

D. 前端与面板:agent.js 超时 25000 ✓;面板含开关/额度/预算/人设/今日用量 ✓

所有测试写入的配置已重置回代码默认(启用、登录 20/小时、游客 5/小时、每日 500),测试额度计数已清理 ✓

四、费用与安全控制

  • 前台对任意访客开放,因此默认就是"有额度上限 + 有每日预算保险丝";
  • 游客默认 5 次/小时、登录用户 20 次/小时、全站默认 500 次/天;都可以在面板里改,设为 0 即关闭云端(仍可用规则引擎);
  • 每次调用都记 data/.ai_front_usage.log,面板可看今日用量,便于核对费用;
  • 关闭开关或额度用尽时自动回落,前台不会报错、不会白屏。

v0.4.49(云端模型配置成功后自动折叠配置区)

需求:"模型配置成功后折叠配置接口"。

一、改动(admin/workbench.php)

#内容
1① 页签的配置卡片改为可折叠:标题右侧一行状态摘要(模型 · 接口地址 · 密钥掩码,若开了自签名会额外标注)+「展开修改 / 收起」按钮
2已配置 → 默认折叠;未配置 → 始终展开(避免"看不到该填哪里"),并有 JS 守卫强制保证
3保存成功后自动收起;测试连通成功后自动收起(同时更新徽章与摘要);失败时保持展开,方便直接改配置
4手动展开/收起会记在本机(localStorage),下次打开按你的选择显示;未配置时仍强制展开
5折叠态卡片用浅底色区分,摘要里实时显示 模型 · Base URL · 密钥掩码

二、验证 → 22 / 22 通过

A. 已配置(当前状态)
   卡片带 wb-cfg collapsed ✓  折叠容器 wb-cfg-body ✓  摘要含模型/地址/密钥掩码 ✓
   按钮为「展开修改」✓  折叠 CSS 规则存在 ✓  页面无 PHP 报错 ✓
B. 未配置(用密钥快照→清空→渲染→还原,最终配置未被改动)
   卡片**不带** collapsed ✓  按钮为「收起」✓  摘要提示"尚未配置"✓  不显示密钥掩码 ✓  密钥已还原 ✓
C. JS 行为契约
   setCfgCollapsed / refreshCfgSummary 存在 ✓  localStorage 记忆 ✓
   保存成功自动收起 ✓  连通成功自动收起(失败保持展开)✓  未配置强制展开守卫 ✓  按钮可手动切换并记忆 ✓
D. 实时 HTTP 渲染:58,874 B,无报错 ✓

三、效果

配置好之后,① 页签只剩一行摘要(模型 · 接口 · 密钥 · 状态徽章),面板清爽不占地方; 需要改配置时点「展开修改」,改完保存(或测试连通成功)会再次自动收起。

v0.4.48(修复云端模型接口 TLS 证书校验失败)

报错原文:网络错误: SSL certificate problem: self signed certificate in certificate chain(HTTP 0)

一、根因:本机 PHP 完全没有 CA 根证书包

证据结果
php -i 的 curl.cainfo / openssl.cafile均为空
PHP 安装目录下 extras/ssl/cacert.pem、cacert.pem、系统 curl-ca-bundle.crt全部不存在
直接请求 https://api.deepseek.com/v1/models(用无效密钥做 TLS 探测)ssl_verify_result=19 + SSL certificate problem: self signed certificate in certificate chain
指定一份可用的 CA 包后重试ssl_verify_result=0、HTTP 401(TLS 已通,401 仅因探测密钥无效)

→ OpenSSL 没有信任库,任何 HTTPS 请求都会被判为"自签名证书链",与接口本身无关。

二、修复

#内容说明
1导出 Windows 系统根证书为 PEMCert:\LocalMachine\Root + Cert:\CurrentUser\Root + CA 四个存储 → 87 张根证书,151,635 B。同时兼容"杀软/公司代理做了 HTTPS 拦截"的情况(其拦截根证书正在系统存储里)
2data/cacert.pem(站点内副本)关键一步:宝塔给本站设了 open_basedir = D:/wwwroot/qdcms/;...,PHP 读不到 PHP 安装目录下的证书包 → 把证书包放进站点目录内即可稳定生效(无需重启 PHP)
3includes/AiClient.php新增 discoverCaBundle():优先站点内 data/cacert.pem → ini 配置 → PHP 目录 → 系统路径;证书类错误自动带 CA 重试一次;仍失败则输出中文排查指引(含已尝试路径与两条出路);新增 ai_cainfo(显式证书路径)与 ai_allow_insecure(允许自签名,默认关)
4admin/workbench.php / ajax-workbench.php面板新增「CA 根证书包路径」(留空=自动使用站点内证书包,实时显示实际路径)与「允许自签名证书(有风险)」开关;保存/状态接口同步支持
5php.ini(站点外)curl.cainfo / openssl.cafile 指向证书包(原为注释状态);重启 PHP 后全局对所有 cURL 生效(面板不依赖它,已即时可用)

三、验证 → 11 / 11 通过

A. 自动发现:Web 语境(open_basedir 限制下)命中站点内 data/cacert.pem(87 张)✓  默认不关闭校验 ✓
B. 证书错误 → 自动带 CA 重试成功 ✓  重试时校验仍开启(cainfo=站点内包, insecure=false)✓
C. 无可用 CA 时输出中文排查指引(含两条出路)✓
D. 面板端到端(真实调用你配置的 DeepSeek 接口):
   ★「测试连通」→ 连通正常(模型 deepseek-flash,799ms):ok   ← 你遇到的报错已消失
   ★ 对话试跑 → 正常
   status 返回站点内 CA 包路径 ✓

四、说明与后续

  • 现在无需任何操作即可用(面板即时生效);
  • 若希望全站所有 HTTPS 调用(不止模型接口)都用上 CA 包:到宝塔面板重启一次 PHP,curl.cainfo 即全局生效;
  • 若将来接口用自签名证书:勾选「允许自签名证书(有风险)」即可,但会关闭服务器证书校验,仅在确知风险时使用。

v0.4.47(工作面板对话框页增加可操作实用工具)

需求:"工作面板对话框页增加可操作实用工具"。

一、对话框页新增三组实用工具

组位置内容
快捷指令运维智能体对话框上方6 个一键提问按钮:做一次体检 / 磁盘空间 / 最近报错 / 授权状态 / Go 服务 / 上传活跃度(点击即发送)
对话工具运维智能体 + Agent 两个对话框清空对话、复制最后回复、导出对话 .md(零依赖实现,兼容无 clipboard 的老内核)
可执行实用工具运维智能体对话框下方6 个真正会改变站点状态的操作按钮(见下表),点击后二次确认 + 结果回显 + 审计记录

二、6 个可执行操作(includes/OpsActions.php)

操作作用安全设计
清理临时分片目录删除 uploads/tmp_chunks 下超过 2 小时的分片残留活动时间取内部文件 mtime(兼容 Windows 目录 mtime 不可靠),进行中的上传不误删
清理过期会话文件删除 PHP session 目录下超过 7 天的 sess_*只按年龄阈值删,活跃会话不动(实测保留 140 个活跃会话)
优化数据表对 8 张核心表执行 OPTIMIZE TABLE只针对白名单表名,无任何前端传入的 SQL
重置 OPcache清空字节码缓存,改完代码立即生效未启用 OPcache 时明确说明而非报错
重置授权告警计数清除 data/.license_warnings,解除停机页锁定先备份再删;本次验证后已还原线上原值(保持"10123.vip 继续拦截")
扫描孤儿上传(仅预览)列出 uploads 中未被数据库引用的文件只列不删;实测发现 5 个孤儿文件(57.7 KB)

三、安全边界(本次重点验证)

  1. 模型无法调用写操作 —— 写操作与只读工具(OpsTools)严格分离:把 clean_tmp_chunks 交给只读注册表会返回"未知工具",智能体的工具清单里也不含任何写操作;
  2. 必须二次确认 —— 前端 confirm + 服务端 confirm=1 双重校验;未确认时拒绝执行且零副作用(已用测试夹具验证目录仍在);
  3. 白名单 —— 不接受任意动作名、路径、命令、SQL;
  4. 审计日志 —— 每次执行写 data/.ops_actions.log(时间/操作者/动作/结果/IP),面板内可查最近 10 条;不含密钥等敏感信息;
  5. 越权防护 —— 端点仅管理员(401/403)+ CSRF(419),普通用户执行写操作返回 403。

四、验证 → 33 / 34 通过(唯一未过项为测试自身的计数口径:页面里 .wb-chip 出现 7 次=6 按钮 + 1 处 JS 选择器)

A. 安全边界:白名单 6 项 / 未知操作被拒 / 未确认被拒且无副作用 / 模型不可调用写操作(3 项断言)✓
B. 真实执行:临时分片"删过期 1 个、保留进行中 1 个"✓  会话"删过期 1 个、保留活跃 140 个"✓
             优化 8 张表 ✓  OPcache 重置成功 ✓  孤儿扫描 5 个(只列不删)✓
             授权告警重置(原 13/8)→ 校验删除与备份 → **已还原原值** ✓
C. 审计:6 条记录、含操作者、无敏感字段 ✓
D. 端点/页面:ops_actions 返回 6 操作+审计 ✓  无 confirm 拒绝 ✓  确认后执行 ✓  普通用户 403 ✓
             页面含 6 个操作按钮、快捷指令、两处对话工具、审计区,且无 PHP 报错 ✓
业务数据:核心表行数前后一致 ✓

五、怎么用

打开「工作面板 → ③ 网站运维工具智能体」:

  • 点 快捷指令 一键提问;点右侧 清空/复制/导出 管理对话;
  • 需要动手时点下方 可执行实用工具(悬停可看说明与预计耗时),确认后立即执行并回显结果;
  • 「最近操作审计」实时显示谁在什么时候做了什么。

v0.4.46(后台新增「工作面板」:云端模型接口 + Agent 智能体 + 网站运维工具智能体)

需求:"在网站后台增加一个工作面板,内含云端模型调用接口以及 Agent 智能体,顺便写入网站运维工具智能体"。

一、入口

后台左侧导航顶层新增「工作面板」(紧随「总览」,admin/workbench.php),三个页签:

页签能力
① 云端模型调用接口配置 Base URL / API Key / 模型名 / 超时 / 温度;保存(密钥留空=不修改,页面只显示掩码)、测试连通(真实调用一次并给出结论)、对话试跑
② Agent 智能体编辑系统提示词(人设/规则)、工具调用最大步数;保存 + 对话试跑
③ 网站运维工具智能体10 个只读运维工具(可逐个运行)+ 自然语言问答 + 一键体检(Markdown 报告,可复制/下载)

二、新增文件与实现要点

文件说明
includes/AiClient.phpOpenAI 兼容客户端:任何兼容端点(DeepSeek/通义/智谱/Moonshot/OpenRouter/自建 vLLM、Ollama)只填 base_url+key+model 即可;传输层可注入(便于离线单测);错误按 401/403/404/429/非 JSON/空内容分类给中文提示;密钥仅存服务端并掩码显示
includes/OpsTools.php10 个只读运维工具:站点连通性、磁盘与上传占用、数据库统计、PHP 运行环境、错误日志、授权状态、Go 服务心跳、待处理积压、上传活跃度、安全体检。统一返回 {ok,level,title,summary,data,hints,ms};一键体检生成 Markdown 报告
includes/OpsAgent.php运维工具智能体:模型模式(把工具清单交给模型,模型以 JSON 请求工具 → 服务端执行 → 回喂 → 收口,最多 N 步)+ 规则模式(无密钥也能用:关键词命中工具);模型失败自动降级;容忍模型输出的 markdown 代码块/杂字符
admin/workbench.php三页签面板(原生 PHP + 内联 JS,无外部依赖)
admin/ajax-workbench.php9 个动作:status / model_save / model_test / model_chat / agent_save / agent_chat / ops_run / ops_chat / ops_checkup;仅管理员(401/403)+ CSRF(419)
admin/header.php顶层导航新增「工作面板」

安全设计:运维工具全部只读(不写库、不删文件、不改配置);不接受任意 SQL/命令/路径;密钥不回传前端。

三、验证 → A~C 57 项 + D/E/F 22 项,全绿

A. AiClient(桩传输,不打真实网络)
   endpoint 拼接 / 已含路径不重复拼接 / 请求体(model,messages,stream=false,temperature) / Bearer 鉴权头
   响应解析(choices[0].message.content + usage) / 密钥掩码 / 未配置提示
   错误映射:401 密钥无效、404 地址或模型名不存在、429 限流、非 JSON 响应、空内容、传输异常被捕获
   消息规范化 / 连通性测试 / saveSettings 校验(非法地址拒绝、超时夹到 300、温度夹到 2) / 配置已还原
B. OpsTools(真实数据、只读)
   10 个工具全部运行成功:磁盘使用 5.3%(118GB/剩余 111.78GB)、uploads 84.2MB、PHP 8.0.28 扩展齐备、
   授权告警 13/8、Go 服务心跳 3 秒前(端口 8080 HTTP 200)、最近 24h 上传 64 个文件、安全体检发现 2 项
   体检报告 Markdown 生成 / 未知工具被拒 / **核心表行数前后一致(只读性得到验证)**
C. OpsAgent
   规则模式:磁盘→disk_usage、报错→recent_errors、授权→license_status、上传→uploads_recent 全部命中
   「做一次站点体检」→ 直接跑 7 项体检(跳过模型)
   模型模式:桩模型请求工具 → 执行 disk_usage → 回喂 → `{"final":...}` 收口(共 2 次模型调用)
   容错:非法 JSON 自动纠正重试;步数用尽优雅收口;模型失败自动降级规则模式
D/E. 页面与端点
   管理员 200、三页签、10 个工具卡片、导航入口、页面无 PHP 报错;普通用户 302 跳登录
   端点:未登录 401 / 普通用户 403 / CSRF 错误 419 / 未知动作 404
   status 返回 10 个工具;ops_run 成功;ops_checkup 返回报告(level=error,正常 5 注意 1 异常 1);
   model_test 未配置时给出明确中文提示(不 500)
F. 本轮修掉的真实缺陷
   ① go_service 曾显示"心跳 497677 小时前"——Go 服务写的是 epoch 字段,原代码读了 ISO 字符串 ts;
      已改为 epoch 优先 + 兼容毫秒/ISO/文件 mtime,现正确显示"心跳 3 秒前"
   ② recent_errors 的 5xx 统计曾在 CSRF token 里误匹配,**假阳性 243/300**;现只认状态码字段 → 0/512
   ③ 访问日志分支不再重复列出 *.error.log;磁盘信息增加 PowerShell 兜底(原账户下 disk_free_space 不可用)

四、怎么用

  1. 后台左侧「工作面板」→ 页签①填 Base URL / API Key / 模型名 → 保存 → 测试连通;
  2. 页签②按需改系统提示词与步数;
  3. 页签③直接问"磁盘还剩多少?""最近有报错吗?""做一次站点体检",或点工具卡片上的运行;
  4. 未配置模型也能用:运维工具智能体会走关键词规则模式,体检报告照常生成。

v0.4.45(PC 端个人主页增加「退出登录」按钮)

需求:"给 PC 端个人主页增加一个退出按钮"。

一、为什么之前 PC 端退不了

全站唯一的「退出登录」入口在 includes/header.php 的移动端下拉菜单里 (<a href="logout.php" class="nav-link-dropdown">退出登录</a>,容器 .main-nav-dropdown 仅在窄屏显示) → PC 端打开个人主页时确实没有任何退出按钮。

二、改动(仅 profile.php)

#内容
1顶部名片操作区新增按钮:编辑资料 → 管理后台(管理员)→ 退出登录,跳 logout.php(沿用原有退出逻辑,未新造接口)
2新按钮加二次确认:确定要退出登录吗?,避免误点直接掉线
3样式 .profile-logout-btn:危险色描边,默认透明底、悬停填充红色,与「编辑资料」明确区分;操作区本身已有 flex-wrap: wrap,窄屏自动换行不挤压

三、验证 → 15 / 15 通过

渲染检查(登录态真实请求 profile.php)
  HTTP 200 ✓  操作区存在 ✓  含「退出登录」→ logout.php ✓  带二次确认 ✓
  使用 profile-logout-btn 样式 ✓  原「编辑资料」保留 ✓  页面含样式规则 ✓
  顺序正确(编辑资料 → 管理后台 → 退出登录)✓  窄屏可换行 ✓
退出流程(独立测试会话,不影响任何人登录态)
  logout.php → 302 → index.php ✓
  退出后再访问 profile.php → 302 → login.php(会话已失效)✓
  服务端会话数据已清空/销毁 ✓
未登录访问个人主页 → 跳登录页 ✓

四、效果

  • PC 端个人主页一眼可见「退出登录」,点一下(确认后)即退出并回到首页;
  • 移动端行为不变(下拉菜单入口仍可用);未登录用户看不到该按钮(个人主页会先跳登录页)。

v0.4.44(授权告警看门狗:在"锁定"之前先提醒你)

背景:你选择保持拦截 10123.vip(不加入白名单)。该域名的访问会持续计入 "5 天 8 次"计数,达到 8 次后用未授权域名访问的访客会看到 403 停机页。

一、本次新增

#交付物说明
1qdcms-license-watch.ps1(新增,站点根目录 + 交付包)监控 data/.license_warnings 计数,按 0~2 / 3~4 / 5~6 / 7 / ≥8 分级输出,退出码 0/1/2/3/4 便于接计划任务;只提醒不自动清除(保持授权策略不被削弱);-Clear 可人工复位(先自动备份);日志写入 data/.license_watch.log
2措辞与说明修正明确"锁定只影响未授权域名访客;自有域名与内网/本机访问始终正常""

二、实测结论(本次核验)

访问方式结果
10123.vip(未授权域名,端口 65500)HTTP 403 + 停机维护页 ← 符合作者拦截意图
10123.xn--fiqs8s(授权域名,端口 65500)HTTP 200 正常完整页面
192.168.0.100(局域网 IP)HTTP 200 正常完整页面
127.0.0.1(本机)HTTP 200 正常完整页面

当前计数状态:9 / 8(窗口起始 2026-10-10 20:32:50,约 5 天后自动重置) → 未授权域名访客会看到停机页(你的选择),你自己的域名与内网访问不受任何影响。

三、日常用法

# 查看当前状态(退出码 0/1/2/3/4 分级)
powershell -ExecutionPolicy Bypass -File qdcms-license-watch.ps1 -ListDomains

# 确认是误访/爬虫后,人工复位计数(会自动备份原值)
powershell -ExecutionPolicy Bypass -File qdcms-license-watch.ps1 -Clear

v0.4.43(商业授权:增加本地局域网 IP)

需求:"给网站域名商业授权增加本地局域网 ip 地址"。

一、改动

#位置改动
1data/.license_allow(授权白名单)显式加入 192.168.0.100、192.168.0.(前缀,覆盖 192.168.0.1~254,应对 DHCP 变动)、127.0.0.1、localhost
2includes/LicenseManager.phpisLocalHost() 支持 IP 前缀形式的白名单条目(如 192.168.0.)。仅当整串确为合法 IPv4 时才做前缀匹配,因此 192.168.0.100.evil.com 这类主机不会被误放行
3nginx vhost 10123.xn--fiqs8s.confserver_name 追加内网网段正则 ~^192\.168\.\d+\.\d+$、~^10\.…$、~^172\.(16-31)\.…$ —— 否则换一台内网设备(如 192.168.0.101)时请求会落到宝塔默认站(只返回 612 B 页面)。nginx -t 校验通过,需在宝塔点一次「重载配置」生效
4data/.license_warnings(运维修复)原计数 268 / 8 且域名为 10123.vip。按现有守卫逻辑,count>=8 会对所有访客输出停机页 —— 属于"一次误访即全站锁死"的高危状态。已备份后重置为干净状态(备份:D:\BtSoft\backup\qdcms-license\license_warnings.bak)

说明:授权状态本身未变 —— 绑定域名仍为 10123.xn--fiqs8s(10123.中国),本次只是扩充白名单。

二、验证(真实 HTTP + 判定矩阵)→ 31 / 32 通过(唯一未过项见下)

放行矩阵(14/14 通过):192.168.0.100、192.168.0.100:65500、192.168.0.1/254、192.168.5.9、
                        127.0.0.1、127.0.0.1:80、10.0.0.7、172.16.5.9、172.31.255.1、169.254.1.1、
                        localhost、localhost:65500、::1
拦截矩阵(6/6 通过):10123.vip、evil.com、192.168.0.100.evil.com、192.16.0.100、11.0.0.1、172.32.0.1
checkDomain:192.168.0.100 / 127.0.0.1 / localhost → 有效(本地或白名单访问)
             10123.vip → 仍无效(**授权守卫未被削弱**)
真实 HTTP:Host 192.168.0.100 / 192.168.0.100:65500 / 127.0.0.1 → 200 + 完整首页 38,722 B + 无告警
           **内网访问没有产生任何告警计数** ✓(关键验收)
           陌生域名(10123.vip:65500)→ 正常计数并弹窗(守卫仍生效),验证后已复位计数
未过项:Host 192.168.5.9 在**改动前**的旧 vhost 下仍落到默认站(612 B)—— 本次已通过 nginx 正则
        server_name 解决,待重载配置后生效

三、你需要做的

  1. 无需重启 PHP;授权白名单改动立即生效。
  2. 让「整个局域网网段」都能直接访问:宝塔 → 网站 → 设置 → 重载配置(或 Nginx 重载)。仅此一步需要你点。

v0.4.42(修复聊天发送视频后气泡消失 / 回显缓慢或看不到)

现象:聊天里发送视频,上传成功后气泡消失,视频要过一会儿才回来(有时一直不出现)。

一、根因:接口响应没返回新消息 id

环节问题
includes/fn-message.php sendChatMessage()插入后只 return true,不返回自增 id
ajax-chat-send.php 响应 message{}只有 sender/content/media 等字段,没有 id
assets/im-plugin/modules/chat.js 发送回调需要 real.id 才做"原地替换乐观气泡";缺 id 时走 else 分支 → 直接 removeChild 删除气泡,然后等 4 秒轮询把消息拉回来

→ 于是出现:"上传 100% → 气泡消失 → 视频过几秒才回来(轮询错过就一直不回来)"。 实测服务端处理仅 0.65 秒,慢的完全是前端等轮询。

二、修复

#文件改动
1includes/fn-message.phpsendChatMessage() 改用 db()->insert() 并返回自增 id(拿不到时回退 true,旧调用方不受影响)
2ajax-chat-send.php响应 message 补 id、is_read、file_size、sender_avatar(id 兜底再查 LAST_INSERT_ID())
3assets/im-plugin/modules/chat.js兜底不再删气泡:响应缺 id 时保留乐观气泡并显示"已发送,正在同步…",同时重置加载锁并主动 _loadMessages() 拉回真实消息

三、验证

A. 真实发送聊天视频(真实 HTTP + FFmpeg 视频)→ 全通过

发送耗时 0.65s,响应带 "id":287 ✓
数据库存在该消息 ✓  media_type=video ✓  收发方正确 ✓
库中 media_url 与响应一致 ✓   视频文件落盘且 HTTP 可访问 ✓   封面存在 ✓
测试消息与文件已清理 ✓

B. 前端分支(Node + DOM/XHR 桩,跑真实 chat.js)→ 12 / 12 通过

带 id:气泡被**原地替换**(outerHTML)✓  未删除气泡 ✓  替换内容含真实地址与 data-msg-id=287 ✓
        不触发多余的整体拉取 ✓
缺 id:**不删气泡** ✓  气泡仍在消息区 ✓  显示"已发送,正在同步…" ✓  主动拉回真实消息 ✓
失败响应:气泡保留(可重试)✓

四、效果

  • 发送视频/图片成功后,气泡当场变成真实消息(含真实地址与 data-msg-id),无需等 4 秒轮询;
  • 即使接口异常缺 id,气泡也不再消失,而是自动补拉真实消息;
  • 顺带修正:data-msg-id 之前是 undefined(缺 id 导致),现在正确 → 媒体缓存/播放进度记录恢复正常。

v0.4.41(优化聊天插件"发送方视频气泡过宽")

需求:"优化聊天插件发送方视频气泡显示过宽的问题"。

一、根因(三点叠加)

#问题后果
1视频只约束 max-width: 240px / max-height: 180px,其余交给 <video> 的固有尺寸竖屏手机视频(如 1080×1920)在聊天里仍占很大一块;配合 12px 文字内边距,绿气泡比视频还宽一圈
2媒体气泡沿用文字内边距 padding: 8px 12px气泡四周多出 24px 空白,观感"过宽"
3气泡是普通 block、包裹层没有 min-width: 0当"时间/已读"那一行比视频宽时,气泡会被横向撑开(这就是"发送方视频气泡过宽"最直接的来源)

二、修复

#文件改动
1assets/im-plugin/modules/chat.js消息结构:包裹层加 .msg-body;媒体消息(图片/视频,含流式地址与付费墙分支)气泡加 .media 类 → 内边距 4px;文案消息完全不受影响
2assets/im-plugin/styles/im-modules.css① .msg-body { min-width: 0 }(允许收缩,不再撑破 80% 上限);② .msg-bubble { width: fit-content }(气泡紧贴内容,不再被时间行撑宽);③ .msg-bubble.media { padding: 4px; line-height: 0 };④ 视频尺寸改为 max-width: 200px; max-height: 260px(横屏 200×112、竖屏 146×260、方形 200×200);⑤ 图片同步为 200×200;⑥ 新增 @media (max-width: 480px):视频 168×220、图片 168×168
3assets/js/tab-card.jsIM_BUILD 递增(20261002-10 → 20261002-11)强制刷新插件资源缓存

尺寸推算(修复后)

视频形态修复前气泡宽修复后气泡宽
竖屏 1080×1920240 + 24 = 264px146 + 8 = 154px
横屏 1920×1080240 + 24 = 264px200 + 8 = 208px
小屏(≤480px)竖屏264px132px(124 + 8)

三、验证(Node + DOM 桩,真实渲染函数 + 真实 CSS)→ 30 / 30 通过

发送方视频:气泡带 .media、包裹层带 .msg-body、video 无内联 width:100%  ✓
接收方视频 / 流式地址分支 / 付费墙分支:同样带 .media,已读回执与付费按钮保留 ✓
图片消息:带 .media ✓     文案消息:不带 .media(内边距不变)✓
CSS:media 内边距 4px ✓  msg-body min-width:0 ✓  fit-content ✓  视频 200×260 ✓
     旧 240px 规则已移除 ✓  图片 200×200 ✓  小屏 168×220 / 168×168 ✓  花括号配平 ✓
尺寸推算:竖屏 146×260 ✓  横屏 200×112 ✓  方形 200×200 ✓  小屏竖屏 124×220 ✓

四、效果

  • 发送方(也含接收方)视频气泡只比视频大 8px,不再有 24px 空白;
  • 竖屏视频宽度约为屏幕的 40%(旧实现约 73%),聊天区观感与微信一致;
  • 小屏再自动收一档;文案消息、已读回执、付费按钮、播放进度缓存等行为全部不变。

v0.4.40(新增视频分片上传:彻底规避 504 网关超时)

现象:手机经域名上传短视频时报 服务器返回异常(HTTP 504)。

一、定位:504 不是本机 nginx 打的

证据结论
本机 nginx 访问日志 504 计数 = 0504 来自链路更前面的网关/代理层
18:43:04 那次上传在本机被记为 499(客户端主动断开)前面那层先超时并向浏览器返回 504,然后断开与本站的连接 —— 两边现象完全吻合
本站 fastcgi_read_timeout = 900s(上传走 FastCGI)、proxy_read_timeout 60s 只作用于 SSE 反代本机不是瓶颈;域名前置那层超时值我无法从本站修改
该次上传最终成功(18:43:08 发布 id=21,日志 已转换为 MP4:video/quicktime(remux))服务端处理仅约 23 秒 → 拖垮请求的是单请求上传时长(手机弱网上传几分钟)

根因:单请求上传大文件要持续几十秒到数分钟,链路上任意一层网关超时都会 504。 治本方案:把大文件切成 ≤4MB 分片,每个请求都在几秒内完成 → 免疫任何网关超时。

二、本次改动

#文件改动
1ajax-upload-chunk.php(新增)分片端点:登录 + CSRF 校验;upload_id(32hex)/index/total(1~500)/name/chunk(≤4MB);逐片落盘、到齐后流式合并为 merged.bin 并返回 token;含 200MB 总量上限、2 小时过期自动清理(GC 以内部文件时间为准,兼容 Windows 目录 mtime 不可靠)
2post-video.php支持 upload_token 发布:分片合并完成后只发表单字段(不再携带文件)→ 秒级返回;发布后自动清理分片目录
3includes/fn-upload.php新增 moveUploadedOrLocal():真实上传走 move_uploaded_file,分片合并文件走 rename/copy(仅允许 uploads/tmp_chunks/ 内的文件,防止被当作任意文件搬运)
4includes/fn-moments.php改用该助手;finfo 返回 application/octet-stream 等泛型类型时按扩展名判定(仍走同一白名单,安全不变)
5assets/js/upload-progress.js新增 QdUpload.uploadChunked():4MB 分片、串行上传、单片失败自动重试 2 次、逐片汇总进度
6post-video.php 内联脚本>4MB 自动走分片(进度条显示"分片 x/y"),≤4MB 仍走原来的单请求;分片失败时保留可重试状态

三、验证

A. 分片端点端到端(真实 HTTP + 真实 FFmpeg 视频) → 全通过:

3 片逐片上传 → received 1/2/3 → done:true + token
合并文件 sha256 与源文件**完全一致** ✓   分片文件已自动清理 ✓
凭 upload_token 发布 → {"success":true,"moment_id":"22"} ✓
落盘 uploads/moments/2026/10/moment_v_*.mp4(大小与源一致)✓   临时目录已清理 ✓
边界:非法 upload_id / CSRF 错误 / 越界 index / 单片 >4MB  → 全部正确拒绝 ✓
GC:把过期目录的内部文件时间设为 3 小时前 → 下一次请求自动清除 ✓

B. 渲染与语法:页面含分片分支与 upload_token、仍正确引入进度脚本、内联 JS node --check 通过 ✓ C. 数据卫生:测试动态/媒体/分类/临时目录全部清理(moments 17 条 = 用户真实发布 ✓)

四、效果

  • 手机经域名上传 → 每片几秒完成,不会再出现 504;进度条按分片推进(第 x/y 片);
  • 分片失败自动重试 2 次;全部分片合并后才进入"服务器处理中(转码/截图)"阶段;
  • 小文件(≤4MB)行为与之前完全一致,无额外请求。

五、附:可选的更彻底做法

若希望连"上传"这一步都完全不经过域名链路,可让手机在局域网直接访问 http://192.168.0.100/post-video.php(同 WiFi 时最快,且完全绕开前置网关)。

v0.4.39(修复聊天插件"靓号特效"不显示)

需求:"修复聊天插件靓号特效显示失败的问题"。

一、根因:特效样式进不了插件的 Shadow DOM

现象说明
全站页面里靓号有"七彩顺时针流光描边"定义在 assets/css/style.css:.vip-active(conic-gradient 彩虹环 + mask 挖空)与 .pretty-number(渐变流动文字)
聊天插件里完全没有特效插件渲染在 Shadow DOM 内,页面 CSS 无法穿透;而插件的 5 个样式表里一条相关规则都没有(实测 vip-active/glow/流光/pretty 命中 0)
插件 JS 也没加类chat.js / friends.js / messages.js 渲染头像时从不加 vip-active,号码也没套 .pretty-number

→ 结论:不是数据问题,是插件缺样式 + 缺类名。

二、修复内容

#文件改动
1assets/im-plugin/styles/im-pretty.css(新增,3,584 B)把同一套特效搬进 Shadow DOM:.vip-active 七彩流光描边(含 mask-clip: content-box + mask-composite: exclude 长属性写法,避免简写导致描边消失)、.pretty-number 渐变流动文字;插件头像较小,描边取 2px;另加 @supports 回退(无 mask-composite 的老内核退化为彩色描边光晕)
2assets/im-plugin/core/im-sdk.js① cssFiles 登记新样式表;② 新增 window.IMPretty 助手:is(n)(判定 10000~999999,与站内 numberText() 规则一致)、cls(n, base)(靓号追加 vip-active)、num(n)(输出 .pretty-number 号码 HTML)
3assets/js/tab-card.jsIM_PREFETCH_FILES 同步登记;IM_BUILD 递增(20261002-9 → 20261002-10)强制刷新插件资源缓存
4modules/chat.js联系人头像加 vip-active、标题带流光号码;聊天头部号码用流光文字;消息头像(对方用 sender_user_number、自己用 AGENT_CONFIG.userNumber)加 vip-active
5modules/friends.js好友项 / 好友申请 / 搜索结果的头像加 vip-active,ID 行号码套流光
6modules/messages.js站内信列表头像加 vip-active,发送者号码套流光

三、验证(Node + DOM 桩,用真实 IMPretty 与真实渲染函数)→ 39 / 39 通过

判定规则:10000 / 123456 / 999999 → 靓号 ✓    9999 / 1000000 / 0 / null → 非靓号 ✓
         563474767、403742444(九位)→ 非靓号 ✓(与站内 numberText 规则一致)
聊天联系人:靓号头像 <div class="im-avatar-placeholder vip-active">站</div> + 流光号码 ✓
           九位普通号 → 无 vip-active/pretty-number ✓
消息头像:对方靓号 ✓  自己(AGENT_CONFIG.userNumber=10000)✓  普通号 ✗(无特效)✓
好友项 / 搜索结果 / 站内信:靓号带特效 ✓,普通号与系统消息不带 ✓
插件样式表:含 conic-gradient、@keyframes imVipRainbowSpin、mask 长属性、花括号配平 12/12 ✓
资源登记:im-sdk.js cssFiles ✓  tab-card.js 预取列表 ✓  IM_BUILD 已递增 ✓

四、顺带发现(需要你确认)

站内对"靓号"的判定是 10000 ~ 999999(includes/fn-user.php 的 numberText() / userDisplayName())。 当前三个账号的号码是:

账号号码是否靓号(按站内规则)
origin(站长)10000✅ 是(全站与插件都会显示流光特效)
okay563474767(九位)❌ 否 —— 属于"普通号"
upon403742444(九位)❌ 否 —— 属于"普通号"

所以这三个账号里只有站长会看到特效;若你希望其它账号也有特效,需要把它们的号码换成 10000~999999 区间的靓号(后台「靓号管理」/号池分配),这与本次修复无关。

v0.4.38(修复:短视频上传一直卡在"上传中")

需求:"修复短视频上传时一直卡在上传状态"。

一、根因:上传进度脚本从未被引入页面(v0.4.34 的漏插)

排查链条:

  1. 访问日志里 17:42 之后没有任何 POST /post-video.php → 说明浏览器根本没发出上传请求;
  2. C:\Temp(PHP 上传临时目录)无进行中的临时文件 → 也不是"正在上传";
  3. 服务端渲染结果里,upload-progress.js 只出现在HTML 注释里, 没有独立的 <script src="...upload-progress.js"> 标签;
  4. 于是页面执行到 QdUpload.submit(...) → ReferenceError(QdUpload is not defined), 而按钮在此前一行已被置为 发布中... → 界面永远停在"上传中",请求从未发出。

漏插原因(我方缺陷):v0.4.34 的补丁里,插入脚本标签前用了 if 'upload-progress.js' not in t: 作为守卫,而同一补丁早前已插入的注释里恰好含这串文字 → 整段插入被静默跳过;随后的自检又用 'upload-progress.js' in t 判断(同样被注释命中)→ 假通过。

二、修复内容

#文件改动
1post-video.php补上 <script src="assets/js/upload-progress.js?v=…"></script>(位于内联脚本之前)
2post-video.php兜底提交:window.QdUpload 缺失或 submit 不是函数时,自动改用 fetch 提交(同样的 JSON 处理与跳转)→ 即使进度组件加载失败也能正常发布
3post-video.php提交整体 try/catch:任何前端异常都会恢复按钮 + 显示原因,绝不停在"发布中"
4post-video.phpMutationObserver 增加存在性判断(旧 X5/IE 内核无此 API,原写法会抛错)
5drama-upload.php / drama-update.php用严格正则复检独立 <script src> 标签(两页均已具备,无需改动)

三、验证

A. 提交路径回归测试(test-post-video-submit.js,直接跑服务端渲染出的内联脚本)→ 15 / 15 通过:

场景 1 QdUpload 可用        → 调 QdUpload.submit、按钮禁用、未走 fetch           ✅
场景 2 QdUpload 缺失(本次故障原形)→ 自动回退 fetch、POST 成功并跳转播放页        ✅ ← 关键
场景 3 QdUpload.submit 抛异常 → 捕获、按钮恢复"发布"、显示"提交异常:boom"        ✅
场景 4 兜底 fetch 业务失败   → 按钮恢复 + 显示"视频大小不能超过 50MB"             ✅
场景 5 兜底 fetch 网络异常   → 按钮恢复 + 显示"网络错误: …"                      ✅
场景 6 submit 非函数         → 同样走兜底                                        ✅

B. 线上验证(严格正则匹配真实标签,不再被注释骗过)→ 11 / 11 通过:

检查结果
短视频上传 / 短剧上传 / 短剧编辑 三页HTTP 200,独立 <script src> 标签 + 进度条容器 + QdUpload 调用 全部 ✓
真实上传回归(小 MKV){"success":true,"moment_id":"20"},落盘 moment_v_*.mp4(自动转封装)✓
测试数据清理moments 16 / moment_media 20 / short_drama 0 / 无自检分类 ✓(与修复前一致)

四、经验教训与流程改进

本次自检之所以"假通过",是用子串匹配判断"页面已引入脚本",而注释里也含同样文字。 现已统一改为严格标签正则 /<script[^>]+src="[^"]*upload-progress\.js[^"]*"[^>]*><\/script>/, 并对三个上传页逐页复检(本次验证 B 部分即用此法)。

v0.4.37(修复:从好友列表进入聊天窗口不同步聊天历史)

需求:"修复聊天插件好友列表进入聊天窗口没有同步聊天历史记录"。

一、根因(两个叠加缺陷)

#缺陷后果
1_loadMessages() 用单一布尔锁 if (this._loadingMessages) return;切回聊天 Tab 时 refresh() 会先给上一个会话发起加载(锁被占用)→ 紧接着 openChatWith(新好友) 的历史请求被直接 return 挡掉 → 新会话消息区永远空白
2响应不校验归属上一个会话的在途响应回来后,会把它的消息渲染进新会话的窗口(串话),或用空结果覆盖
3模块初始化是异步的(loadJsFiles 回调里才 new IMChatModule() / 暴露 window._imChatModule),而好友模块点击时同步直调 window._imChatModule.openChatWith(...)聊天模块尚未就绪时该分支被 if (... && window._imChatModule) 静默跳过 → 点了没反应/没有历史

二、修复内容

#文件改动
1assets/im-plugin/modules/chat.js_loadMessages() 改为 "同一会话才挡重复请求"(_loadingPartnerId 比对),换会话立刻放行;每次请求带令牌 _loadToken,响应回来先校验令牌与会话 id,过期响应直接丢弃
2同上_openChat() 进入新会话时重置加载状态并递增令牌(让上一个会话的在途响应失效)
3同上openChatWith() 在模块尚未挂载时把请求记入 _pendingOpen,init() 完成后自动补打开(消除异步初始化竞态,靓号也一并保留)
4assets/im-plugin/core/im-sdk.js新增插件级 IMPlugin.openChatWith(userId, nickname, avatar, userNumber):先 switchTab('chat'),再轮询等待聊天模块就绪(最多约 4 秒)后打开
5assets/im-plugin/modules/friends.js好友列表"💬 发消息"改用插件级 API(保留旧路径兜底)

三、验证(Node + DOM/网络桩,驱动真实模块代码)

qdcms-release/test-chat-history-sync.js → 17 / 17 通过:

场景 1(原缺陷复现点):A 会话加载中 → 立刻打开 B
  [PASS] A 的历史请求已发出 → [1]
  [PASS] 切到 B 后 B 的历史请求**仍然发出**(修复核心) → 请求序列 chat_with=[1,2]
场景 2:过期响应不得串话
  [PASS] B 的消息已渲染(2 条)        [PASS] A 的过期响应被丢弃(B 的 2 条仍在)
  [PASS] 加载标志未卡死
场景 3:_openChat 重置状态
  [PASS] 重置加载标志 / 递增令牌(1 → 3)/ 新会话请求已发出(chat_with=5)
  [PASS] 新会话消息已渲染 / 旧会话响应仍被丢弃
场景 4:模块未挂载竞态
  [PASS] 未挂载时记录待打开、不发请求 → init 后自动打开并补发历史请求(chat_with=7)、靓号带入

反向验证(把第 1 条改回旧的"单一布尔锁"再跑):立即出现 [FAIL] 切到 B 后 B 的历史请求仍然发出 → 请求序列 chat_with=[1]、 [FAIL] B 的消息已渲染 → 渲染 0 条 —— 与用户描述的现象完全一致,证明测试确实覆盖该缺陷。

线上核对:chat.js(同会话去重字段 / 请求令牌 / 过期丢弃 / 未挂载挂起)、im-sdk.js(插件级 openChatWith)、friends.js(改用插件 API)6 / 6 关键改动就位;三个资源 HTTP 200;首页 200 ✅

四、效果

  • 从好友列表点「💬 发消息」→ 一定会切到聊天窗口并加载该好友的完整历史(即使聊天模块还在加载中);
  • 快速在多个会话间切换 → 不会串话(旧响应被丢弃)、不会卡在"加载中";
  • 修复同时覆盖了"发送视频后返回列表再进入"的历史卡死路径(_loadingMessages 不再残留)。

v0.4.36(修复聊天插件"靓号失效")

需求:"修复聊天插件靓号失效问题"。

一、排查结论:服务端全对,问题在插件前端"没把靓号传/显示出来"

环节实测
用户靓号数据users.user_number 正常:站长 10000、okay 563474767、upon 403742444 ✅
ajax-chat-contacts.php(联系人)返回 user_number ✅(403742444 / 10000)
ajax-chat-history.php(聊天记录)返回 sender_user_number ✅(563474767)
api-friend.php(好友列表)返回 user_number ✅(10000 / 403742444)
friends.js 点"发消息"调 openChatWith(id, name, avatar) —— 把靓号丢掉了 ❌ → 聊天头部退回显示普通用户 id
chat.js 的 openChatWith参数只有 3 个,且 user_number: '' 写死为空 ❌
messages.js(站内信)完全没用接口已返回的 sender_user_number ❌ → 站内信里看不到靓号

→ 结论:"靓号失效"= 前端接线缺陷(显示回落成普通 id),不是号码数据或接口问题。

二、修复内容

#文件改动
1ajax-user-card.php(新增)按 user_id 返回昵称/头像/靓号;登录 + CSRF 校验;role=9 统一对外显示(不暴露用户名);头像与其它接口同样处理
2assets/im-plugin/modules/chat.js① openChatWith(userId, nickname, avatar, userNumber) 支持第 4 个参数;② 聊天窗口若拿不到号码,自动向 ajax-user-card.php 补取一次并即时刷新头部(失败静默,不影响聊天)
3assets/im-plugin/modules/friends.js好友列表"发消息"按钮把 friend.user_number 一起传入
4assets/im-plugin/modules/messages.js站内信列表显示 发送者 · ID: 靓号,详情显示 来自: 发送者(ID: 靓号)(系统消息无号码时不显示)
5includes/footer.phpAGENT_CONFIG 新增 userNumber(当前用户靓号),供插件/页面显示"我的号码"

三、验证

测试结果
ajax-user-card.php 四情形未登录被拒 ✅ / CSRF 错被拒 ✅ / 用户不存在 ✅ / 正常返回(站长 10000、okay 563474767)✅
头像基址与 ajax-chat-contacts.php 同为根相对路径 ✅
friends.jsopenChatWith(... friend.user_number ...) 已生效 ✅
chat.js 兜底存在补取调用、成功后更新 _chatWith.user_number 与 .partner-status、失败静默 ✅
messages.js列表 senderLabel、详情 fromText 均含靓号 ✅
页面下发个人资料页 AGENT_CONFIG.userNumber = 563474767(user2 靓号)✅
静态资源三个模块 HTTP 200 ✅
数据3 位用户靓号完好(10000 / 563474767 / 403742444)✅
合计18 / 18 断言通过(另含 4 项接口实测)

四、效果

  • 插件里点好友"发消息" → 聊天头部显示 ID: 403742444(此前显示 ID: 3);
  • 从其它入口打开聊天(未带号码)→ 自动补取后同样显示靓号;
  • 站内信列表/详情显示发送者靓号;
  • 页面脚本可读 window.AGENT_CONFIG.userNumber 显示"我的靓号"。

v0.4.35(个人资料页「站内信 / 好友 / 聊天」改为打开新版 IM 插件浮层)

需求:"修复个人资料页站内信,好友,聊天三个链接点击后进入老聊天插件,没有进入新聊天插件"。

一、问题定位

项目实际情况
个人资料页三个入口messages.php、messages.php?tab=friends、messages.php?tab=chat —— 都是整页跳转
新版聊天插件assets/im-plugin/(window.IMPlugin,浮层容器,含 messages / chat / friends 三个模块),由 tab-card.js 按需加载 im-sdk.js
结论三个链接进的是旧的全页聊天界面(messages.php),没有走新版浮层插件 → 与用户描述一致

二、修复方案:新增 assets/im-plugin/core/im-links.js

一个全站通用的链接接管脚本(事件委托,一次监听全局生效):

能力说明
接管规则任何带 `data-im-open="messages\friends\chat"的元素,点击 →IMPlugin.open(tab)` 打开新版浮层
按需加载若 im-sdk.js 尚未加载 → 自动注入(基础路径由自身 src 推导,兼容子目录部署),加载完成即打开
失败回退加载失败或 4 秒超时 → 自动回退到原有 href 跳转(不会卡住)
保留原生行为ctrl/cmd/shift/alt + 点击、中键、无 JS 环境 → 一律放行,仍可新窗口打开
渐进增强模板里 href 全部保留,老页面 messages.php 仍可直接访问

三、接入点

文件改动
assets/im-plugin/core/im-links.js新增(4,962 B)
includes/footer.php在 tab-card.js 之后引入该脚本(全站生效)
profile.php3 个快捷入口(站内信/好友/聊天)+ 2 个统计入口(好友/未读消息)加 data-im-open
includes/header.php导航栏「站内信」加 data-im-open="messages"

四、验证

测试结果
im-links.js 行为(Node + DOM 桩,test-im-links.js)17 / 17 通过:点击接管、子元素委托、插件就绪直接打开、按需注入 im-sdk、加载成功自动打开、加载失败回退、4 秒超时回退、ctrl/中键放行、普通链接不受影响、IMLinks.open() 直调
线上个人资料页(真实登录会话)HTTP 200;data-im-open="messages" 3 处(快捷入口+统计+导航)、friends 2 处、chat 1 处;im-links.js 已引入;href 保留 ✅
首页导航「站内信」已接管,im-links.js 已引入 ✅
静态资源im-links.js HTTP 200(4,962 B)、im-sdk.js HTTP 200(26,253 B)✅
回退路径messages.php、?tab=friends、?tab=chat 三个地址仍返回 200(老界面未被破坏)✅

说明:Tab 标识与插件模块一致(messages / chat / friends,默认进入 chat), 与站内浮动聊天按钮(tab-card)打开的是同一个浮层插件。

v0.4.34(短视频 / 短剧上传页新增真实上传进度条)

需求:"给短视频和短剧上传页面增加上传进度条显示"。

一、为什么不能沿用 fetch

fetch() 无法获取"已上传字节数"(只能等到响应返回),浏览器里唯一能给真实上传进度的是 XMLHttpRequest 的 xhr.upload.onprogress。因此本次新增通用组件并改造提交方式。

二、新增:assets/js/upload-progress.js(通用上传进度条)

能力说明
真实进度按已传字节计算百分比(不是假动画)
数据信息已传 / 总量、实时速度(KB/s、MB/s)、预计剩余时间
三阶段状态① 上传中(蓝紫渐变)→ ② 服务器处理中(橙色动画,转码/截图期间;避免用户以为卡死)→ ③ 成功(绿色)/ 失败(红色)
失败可读服务端 JSON 的 error 原样显示;非 JSON 响应提示"服务器返回异常(HTTP xxx)";区分网络中断 / 超时 / 取消
交互保护上传期间禁用提交按钮(防重复提交),失败后自动恢复可重试
零侵入只依赖一个容器元素(#uploadProgress);若缺失组件会自动创建并插到表单后;样式由组件自身注入

用法:

QdUpload.attach(formEl, { box: '#uploadProgress', button: btn, timeout: 1800000,
                          onSuccess: function (d) { location.href = d.redirect; } });

三、三个页面接入

页面改动
post-video.php(短视频)fetch → QdUpload.submit()(保留原 FormData 组装与错误提示;失败原因同时写入原错误框)
drama-upload.php(短剧上传)传统表单 POST → XHR + JSON 分支:新增 $isAjax 判定、门禁/成功/错误三处 JSON 返回;表单接入 QdUpload.attach()
drama-update.php(短剧编辑 / 追加剧集)同上(两个表单各自独立进度条:#uploadProgressEdit、#uploadProgressAdd)

踩坑记录(已修):这两个短剧页在文件顶部就 require header.php,整页 HTML 已进入输出缓冲; 若直接 echo json_encode(...),响应会变成「HTML + JSON」导致前端 JSON.parse 失败。 因此新增 dramaUploadJson() 助手:先 ob_end_clean() 清空缓冲再输出 JSON 并 exit。

顺带统一:短剧剧集(上传与追加)也接入 convertVideoToMp4() —— MOV/MKV/AVI 等容器会自动转封装/转码为 MP4, 与短视频动态保持一致(此前短剧允许 mkv/mov 却原样落盘,浏览器可能无法播放)。

四、验证

测试结果
进度条组件行为(Node + DOM/XHR 桩,test-upload-progress.js)23 / 23 通过:50% 进度、3.00 MB / 8.00 MB | 6.59 MB/s | 剩余约 1 秒、处理中状态、成功/失败/网络/超时、按钮恢复、attach 拦截
post-video.php / drama-upload.php 页面渲染HTTP 200,含进度条容器与 JS 引用 ✅
drama-update.php(临时短剧 + 所有者会话)HTTP 200,两个容器 + 2 处 QdUpload.attach ✅(验证后临时数据已删除)
短剧 AJAX JSON 分支{"success":false,"error":"请至少上传一集视频"}、CSRF 失败 JSON、非 XHR 仍是 HTML(传统流程不受影响)✅
真实上传回归(AJAX + MKV){"success":true,"moment_id":"19"},落盘 moment_v_*.mp4(已转 MP4)✅
数据卫生测试动态/短剧/分类/会话文件全部清理;moments 16、moment_media 20、short_drama 0(与测试前一致)✅

本次改动不需要重启 PHP / 重载 nginx(纯 PHP + JS)。 但 v0.4.33 的 15 分钟超时配置仍需你在宝塔执行一次「PHP 8.0 重启 + Nginx 重载配置」才生效。

v0.4.33(短视频上传:支持 MOV/MKV/AVI 等自动转 MP4;超时对齐 15 分钟)

需求:"检测网站短视频上传报错"。结论:上传功能本身正常(14 次提交:200 ×10 / 499 ×3 / 400 ×1), 报错来自格式与体积限制以及弱网超时,本次针对性修复。

一、体检结论

项目结果
上传端点 POST /post-video.php200 ×10、499(客户端主动断开)×3、400 ×1 —— 无 500/502/413 ✅
已成功入库moments 今日新增 13 条,moment_media 记录齐全 ✅
最大成功视频4.41 MB(10-02);今日的 1.35~2.39 MB ✅
PHP 限制upload_max_filesize 500M / post_max_size 600M / max_execution_time 300 ✅
nginxclient_max_body_size 600m(与 PHP 对齐)✅
业务限制(真正卡点)单视频 50MB(MOMENT_VIDEO_MAX_SIZE)、仅 mp4/webm、前端 5 分钟超时 ⚠️

报错成因(按出现概率):

  1. 格式不支持:只接受 video/mp4、video/webm → 手机下载来的 MKV / MOV / AVI / TS 会被拒: xxx.mkv 不是支持的视频格式(仅支持 MP4 / WebM);
  2. 体积超限:超过 50MB → xxx 超过 50MB 限制(而 php/nginx 允许到 500M/600M,限制在业务层);
  3. 弱网超时:前端 AbortController 5 分钟 + PHP/FastCGI 300s → 手机上传大文件超时中断(日志里的 499)。

二、修复内容

#改动说明
1新增 convertVideoToMp4()(includes/fn-upload.php)先用 -c copy -movflags +faststart 无损转封装(MKV/MOV/TS/FLV 内含 H.264 时通常数秒完成);失败再回退 libx264 + aac 转码
2放宽视频白名单(includes/fn-moments.php)新增 video/quicktime(mov)、video/x-matroska(mkv)、video/x-msvideo(avi)、video/x-m4v、video/mp2t(ts)、video/x-flv、video/x-ms-wmv、video/3gpp、video/mpeg、video/ogg
3上传后自动转 MP4 落盘非 mp4/webm 的容器自动转成 .mp4 后发布(转封装失败则保留原文件并交给后台压缩流程重试);file_size 记录实际落盘大小
4前端放宽可选类型accept 与类型正则同步扩展(含 .mov/.mkv/.avi/.ts/...),提示文案改为支持的完整列表
5超时对齐 15 分钟前端 AbortController 300s → 900s;php.ini max_execution_time/max_input_time 300 → 900;nginx fastcgi_send/read_timeout 300 → 900
6上传期间显示已用时间按钮文案变为"发布中… 已用 N 秒(请勿关闭页面)",减少误判卡死而重复提交(此前的 499 多为此因)
7nginx fastcgi_request_buffering off大文件请求体直接流给 PHP,避免 nginx 先整包落盘再转发(省一半磁盘 I/O)

三、验证(真实端到端)

用 ffmpeg 生成 2 秒 H.264+AAC 测试视频 → 转封装为 .mkv → 通过真实登录会话上传:

POST /post-video.php(media[]=vtest_upload.mkv;type=video/x-matroska)
→ {"success":true,"moment_id":"18","redirect":"video-play.php?id=18&index=0"}   ✅

落盘:uploads/moments/2026/10/moment_v_1791626149_9548.mp4(30,579 B,扩展名已是 .mp4)✅
ffprobe:h264,video / aac,audio; duration=2.001;format=mov,mp4,m4a,3gp,3g2,mj2 ✅
同目录残留 .mkv:0 个 ✅   封面已生成:uploads/covers/moments/thumb_1791626150_5795.jpg ✅

测试数据已全部清理:moment 18、moment_media 记录、视频文件、封面、测试分类均已删除, moments 恢复 16 条 / moment_media 20 条,上传目录无残留、无遗留 ffmpeg 进程 ✅

四、需要用户操作(生效用)

  1. 宝塔 → 软件商店 → PHP 8.0 → 重启(max_execution_time/max_input_time = 900 生效);
  2. 宝塔 → 软件商店 → Nginx → 重载配置(fastcgi_*_timeout = 900 与 fastcgi_request_buffering off 生效)。

可选项:若希望放宽单视频 50MB 限制,改 includes/fn-moments.php 第 22 行 define('MOMENT_VIDEO_MAX_SIZE', 50 * 1024 * 1024) 即可(php/nginx 已允许到 500M/600M); 但手机弱网下上传体积越大越易失败,建议同时考虑保持 50MB 并提示用户压缩。

v0.4.32(错误日志体检:清理历史噪音、补齐 favicon/robots、日志分流与 PHP 日志落地)

需求:"检测网站错误日志"。范围:10123.xn--fiqs8s.error.log(2596 行 / 793 KB,覆盖 10-02 ~ 10-10) 与访问日志近 5000 条。

一、体检结论:站点健康,无致命错误

类别条数结论
PHP Fatal / Parse error0✅ 无致命错误
502 / 504 / FastCGI 错误0✅ PHP-FPM 正常
数据库错误0(MySQL error log 亦为空)✅ 正常
/im-stream 上游提前关闭458⚠️ 良性:客户端换令牌/切页导致 SSE 主动断开(见下)
uploads/agent-icons/agent-default.png 缺失248✅ 已解决(文件 10-02 20:54 补齐,此后 0 条)
assets/images/default-avatar.png 缺失17✅ 已解决(同上)
robots.txt 缺失124(今日 16)⚠️ 仍在发生 → 本次补齐
favicon.ico 缺失52(今日 1)⚠️ 仍在发生 → 本次补齐
扫描器探测(.env / .git / wp-* / 各类 shell)~100✅ 站点正确返回 404,无需处理

404 占比:近 5000 条访问中 404 有 2458 条(49%),绝大多数是自动化漏洞扫描(/wp-content/...、/.env、/chosen.php、/1.php 等), 属正常现象;403 共 336 条,其中 210 条是 GET /(带 WP 风格参数)—— 站点安全层正常拦截。

二、/im-stream 458 条的成因(已确认非故障)

  • nginx 侧 SSE 代理配置完全正确(proxy_buffering off、proxy_read_timeout 3600s、gzip off 等);
  • Go 侧 im_sse.go 亦正确(text/event-stream、X-Accel-Buffering: no、20 秒心跳、Flush()、客户端断开即 return);
  • 实证:ajax-im-token.php 今日调用 242 次、中位间隔 11 秒,每次换新令牌都会关闭旧事件流 → nginx 便记录一条 "upstream prematurely closed"。属于 SSE 正常重连的副产物。

处理:在该 location 内把这类日志分流到独立文件并只记 crit,主错误日志从此清爽:

location = /im-stream {
    error_log D:/BtSoft/wwwlogs/im-stream.error.log crit;
    ...
}

三、本次修复

#项目内容
1robots.txt新增(569 B):放行正常抓取,屏蔽 /admin/ /data/ /includes/ /install/ /vendor/ 等敏感路径,并拒绝 Semrush/Ahrefs/MJ12 等扫描型爬虫
2favicon.ico由站点 logo 生成多尺寸 ICO(16/32/48/64,12,431 B,魔数 00 00 01 00、4 张图像),另生成 apple-touch-icon-180/192.png
3nginx 日志分流/im-stream 的良性错误写入 D:/BtSoft/wwwlogs/im-stream.error.log(crit 级),主日志不再被淹没
4PHP 错误日志落地php.ini 的 error_log = syslog → error_log = D:/BtSoft/php/80/logs/php_errors.log(Windows 下 syslog 等于丢弃,PHP 报错此前无处可查)

四、验证

检查结果
nginx -tsyntax is ok / test is successful ✅
/robots.txt(Host=127.0.0.1 / 192.168.0.100 / 域名入口 :65500)HTTP 200(569 B)✅
/favicon.icoHTTP 200(12,431 B,有效 ICO,4 尺寸)✅
/assets/images/apple-touch-icon-180.pngHTTP 200 ✅
首页 / receipt-check.phpHTTP 200 ✅(38.5 KB / 13.8 KB)
备份D:\BtSoft\backup\qdcms-logfix-20261010-174609(nginx 配置、php.ini 均已备份)

五、需要用户点击生效的两项

  1. 宝塔面板 → 网站 → 设置 → 配置文件 → 保存/重载(或 软件商店 → Nginx → 重载配置) —— 让 /im-stream 日志分流生效(nginx -s reload 需管理员权限,命令行被拒 Access is denied);
  2. 宝塔 → 软件商店 → PHP 8.0 → 重启 —— 让 error_log 指向文件生效(此后 PHP 报错会写入 D:/BtSoft/php/80/logs/php_errors.log)。

另可选(生产建议):把 display_errors 关掉,避免报错信息直接显示给访客(与上面同一次重启即可生效)。

v0.4.31(修复手机脚本 ReferenceError:screenOnOk 未定义;新增脚本端到端桩测试)

用户报错:错误: ReferenceError: "screenOnOk" 未定义。 性质:v2.8 引入的作用域缺陷(我方 bug),已修复并补上自动测试防止复发。

一、根因

v2.8 我把"屏幕常亮"的变量声明写在了 main() 函数内部:

function main() {
    var screenOnOk = false;     // ← 只属于 main 的作用域
    ...
}
function sendHeartbeat() {
    ... screen: screenOnOk ? 1 : 0   // ← 另一个函数里引用 → ReferenceError
}

而 wakeLockOk / foregroundOk / stableModeOk / notifSeen / lastHbAt 都是模块级声明, 唯独 screenOnOk 漏在了函数内 → 每次发心跳就抛 ReferenceError, 脚本启动后立刻中断(心跳发出前就报错),照旧收不到回执。

二、修复(v2.8.1)

  • 把 var screenOnOk = false; 移到模块级(与其它状态变量并列),main() 内只做赋值;
  • 静态检查:sendHeartbeat() 引用的 6 个状态变量全部在顶层声明 ✓;
  • node --check 通过 ✓。

三、新增:脚本端到端桩测试(qdcms-release/test-listener.js)

用 Node + AutoX.js API 桩(http / events / device / $settings / storages / files / Packages / context) 把手机脚本完整跑一遍,无需真机即可捕获"只在手机上才暴露"的错误:

检查项结果
脚本启动无异常(无 ReferenceError)✅
已开启通知监听 / 唤醒锁 / 屏幕常亮✅
心跳报文含 ver/screen/wake/notif/gap 且带双鉴权头✅
模拟"微信支付 收款到账 10.00 元" → 正确解析金额并上报 receipt✅
非收款通知 → 回传通知样本✅
非微信通知 → 忽略✅
合计15 / 15 通过

反向验证(把 bug 精确还原后再跑):测试立即报 [FAIL] 脚本启动无异常 → screenOnOk is not defined(5 项失败,退出码 1) —— 与用户在手机上看到的报错完全一致,证明该测试确实能拦住此类缺陷。

v0.4.30(手机脚本 v2.8:内置屏幕常亮保活)

需求:"再次检测手机脚本"。检测结论:脚本功能全正常(ver=2.7、wake=1、心跳按 1.7~3.1 分钟稳定上报), 唯独 fg=0 / stable=0 —— AutoX.js 的「前台服务 / 稳定模式」在该机型上无法通过脚本设置生效 (重启脚本后依然为 0)。既然 API 开关不生效,v2.8 直接内置最确定有效的兜底。

一、本次检测(03:29:56 心跳)

{"type":"heartbeat","device":"收款手机","ver":"2.7","notif":0,"gap":0,"wake":1,"fg":0,"stable":0}
项目结果
脚本版本v2.7 ✅
唤醒锁wake=1 ✅
前台服务 / 稳定模式fg=0 / stable=0 ⚠️(重启后仍为 0 → 脚本设置不生效)
心跳节奏03:25→03:28(3.1 分)、03:28→03:29(1.7 分)✅(手机被使用时正常)
回执0 条

二、v2.8 改动

改动说明
内置屏幕常亮(device.keepScreenOn(),配置项 KEEP_SCREEN_ON)脚本运行期间屏幕不灭 → 进程始终处于前台活动状态,OriginOS/Android 不会清理它;配合充电器即等效于彻底解决"进程被杀"
心跳新增 screen 字段服务端可远程确认屏幕常亮是否生效
使用说明补充明确提示"请把收款手机插上充电器"

为什么用它:PARTIAL_WAKE_LOCK 只能防"CPU 休眠",防不住系统杀进程; 而"屏幕常亮 + 充电"会让 App 处于前台活跃状态,是该机型上最可靠的保活方式。

三、验证标准

换 v2.8 后(插上充电器),自检页心跳报文应为:

{"type":"heartbeat","device":"收款手机","ver":"2.8","notif":N,"gap":2~4,"wake":1,"fg":0/1,"stable":0/1,"screen":1}

screen:1 且 gap 稳定在 2~4 分钟 → 保活到位;随后收款即可产生回执并自动入账。

v0.4.29(手机脚本 v2.7:稳定模式保活;实测"进程被反复杀掉")

需求:"检测手机脚本"。本次诊断出关键性质变化:不是"冻结",而是进程被系统反复杀掉。

一、实测(03:25:08 心跳报文)

{"type":"heartbeat","device":"收款手机","ver":"2.6","notif":0,"gap":0,"wake":1,"fg":0}
字段值判断
ver2.6✅ 已换到 v2.6
wake1✅ 唤醒锁成功
fg0⚠️ 前台服务始终没开(唯一拦路项)
gap0⚠️ 每次心跳都是 0 = 脚本刚重启,说明进程被反复杀掉

心跳间隔:02:27~02:28 → 02:37 → 02:49 → 03:06 → 03:11 → 03:25(间隔 4~17 分钟且 gap=0)。 → Android 16 + OriginOS 在持续清理 AutoX.js 后台进程;脚本活不过 3 分钟心跳周期,自然收不到收款通知。

二、v2.7 改动

改动说明
开启 AutoX.js「稳定模式」($settings.setEnabled('stable_mode', true))官方推荐的保活开关:进程被杀后可自动恢复;这是该机型最有效的自救手段
稳定模式开启后复检读取 $settings.getEnabled('stable_mode') 确认是否真的生效
心跳新增 stable 字段服务端可远程确认保活是否到位
日志汇总心跳日志一次显示:通知数 / 间隔 / 唤醒锁 / 前台服务 / 稳定模式

三、手机上必须打开的两个开关(否则一定会被杀)

  1. AutoX.js → 设置 → 打开「稳定模式」+「前台服务」;并在 系统 → 通知 → 允许 AutoX.js 发送通知 (前台服务依赖通知权限,缺它就永远是 fg:0);
  2. Vivo/OriginOS:设置 → 电池 → 后台耗电管理 → AutoX.js → 允许后台高耗电; 应用自启动允许;多任务界面加锁;关闭「睡眠模式 / 超级省电」。

兜底方案(任一即可,效果立竿见影):

  • 收款手机长期插电 + 屏幕常亮(开发者选项 →「充电时屏幕不休眠」);
  • 或换一台不装其他应用的旧手机专跑本脚本(白名单更干净);
  • 或改用站点内置的官方支付网关(支付宝/微信 APIv3),不需要手机即可自动到账。

四、验证标准

换 v2.7 后,自检页心跳报文应为:

{"type":"heartbeat","device":"收款手机","ver":"2.7","notif":N,"gap":2~4,"wake":1,"fg":1,"stable":1}

fg:1 且 stable:1 且 gap 稳定在 2~4 分钟 → 保活到位,之后收款才能收到回执。

v0.4.28(手机脚本 v2.6:前台服务复检与指引;v2.5 状态实测)

需求:"检测手机脚本"。本次从服务端直接读到手机脚本的自检数据(v2.5 起心跳携带诊断字段)。

一、手机脚本实测状态(03:11:03 心跳报文)

{"type":"heartbeat","device":"收款手机","ver":"2.5","notif":0,"gap":0,"wake":1,"fg":0}
字段值判断
ver2.5✅ 手机上已是新脚本
wake1✅ 部分唤醒锁获取成功(防冻结关键项已生效)
fg0⚠️ 前台服务未开启 —— 仍会被系统挂起的主因
notif0尚无微信通知(脚本重启后未收款)
gap0该次为启动后首次心跳

心跳时间线:02:27~02:28(启动)、02:37、02:49、03:06:51、03:11:03; 03:11 之后(至 03:17)无新心跳 → 仍需解决前台服务/白名单,否则会继续被冻结。

二、v2.6 改动

改动说明
前台服务开启后复检读取 $settings.getEnabled('foreground_service') 确认真实生效状态,避免"以为开了"
未生效时给出明确指引日志提示:AutoX.js → 设置 → 打开「前台服务」;并在系统里允许 AutoX.js 发送通知(前台服务依赖通知权限)
心跳继续携带诊断ver/notif/gap/wake/fg,服务端自检页可见

三、下一步(用户操作)

  1. 换 v2.6 脚本运行;日志若出现「前台服务未生效」→ 按提示在 AutoX.js 内开启并允许通知权限;
  2. Vivo/OriginOS:设置 → 电池 → 后台耗电管理 → AutoX.js → 允许后台高耗电;自启动允许;多任务加锁;
  3. 刷新自检页确认心跳报文变为 "ver":"2.6","wake":1,"fg":1,且 gap 稳定在 2~4 分钟(不再被冻结);
  4. 然后在脚本运行期间收款一笔(例如与订单 #17 金额一致的 8.00 元)→ 站点应出现回执并自动入账。

v0.4.27(手机脚本 v2.5:心跳携带诊断信息,可远程判断脚本状态)

需求:"检测手机脚本状态"。 结论:脚本在运行但仍被系统冻结 —— 心跳间隔 9.3 → 12.6 → 16.9 分钟(设定 3 分钟), 且回执与"通知样本"均为 0 → 息屏期间通知仍被漏掉。

一、本次检测证据(心跳间隔分析)

相邻心跳间隔判断
02:27:16 → 02:27:50 → 02:28:030.6 / 0.2 分钟正常(脚本刚启动)
02:28:03 → 02:37:229.3 分钟疑似冻结
02:37:22 → 02:49:5812.6 分钟疑似冻结
02:49:58 → 03:06:5116.9 分钟疑似冻结(间隔反而变长)

心跳只在"手机被使用/亮屏"时出现 → JS 定时器与通知回调仍被系统挂起, 说明 v2.4 的前台服务/唤醒锁在该机型上未生效(或手机上尚未换成 v2.4)。

二、v2.5 的改进:把手机状态"上报"到服务端

心跳报文新增诊断字段(会完整显示在站点自检页的「请求体」里):

字段含义
ver脚本版本(服务端可核对手机跑的是哪一版)
notif本次运行捕获到的微信通知条数(为 0 = 通知根本没进脚本)
gap距上次心跳的分钟数(>7 分钟即被冻结过)
wake / fg部分唤醒锁 / 前台服务是否获取成功(1=成功)

实测:心跳带 ver=2.5,notif=3,gap=17,wake=1,fg=1 → HTTP 200,诊断字段完整落库 ✓

三、结论与下一步

  1. 换 v2.5 脚本后,先看自检页的「请求体」:
  • wake:0 或 fg:0 → 唤醒锁/前台服务没拿到(多为系统权限限制);
  • ver 不是 2.5 → 手机上还是旧脚本;
  • notif:0 且收款后仍为 0 → 通知未进脚本(权限/微信通知被关)。
  1. 必须放开 Vivo/OriginOS 后台限制(否则唤醒锁也会被系统回收): 设置 → 电池 → 后台耗电管理 → AutoX.js → 允许后台高耗电;应用自启动允许;多任务加锁; AutoX.js 内开启前台服务;关闭睡眠模式/超级省电。
  2. 最省心的替代方案:让收款手机长期插电并保持屏幕常亮 (开发者选项 → "充电时屏幕不休眠"),这样 JS 不会被挂起;或改用站点内置的官方支付网关(无需手机)。

v0.4.26(手机脚本 v2.4:前台服务 + 唤醒锁,根治"息屏漏收款通知")

场景:连续两次"收款 10 元,动态码页无动静"。 根因找到了:手机脚本被 Android 息屏冻结(Doze),收款通知正好落在冻结期被漏掉。

一、决定性证据:心跳间隔异常

时间事件间隔
02:27:16 / 02:27:50 / 02:28:03心跳(脚本刚启动)~40s
02:37:22心跳9 分 19 秒
02:49:58心跳12 分 36 秒
02:49:58 之后无任何请求(排查时已 9 分钟)—

脚本设定的是每 3 分钟一次心跳,实际却是 9~12 分钟 → 典型的 Doze/后台冻结: 进程还活着,但 JS 定时器与通知回调被系统挂起,冻结期间到达的微信收款通知会直接丢失 ✗。 两次收款(很可能发生在 02:3x、02:4x)正好落在这些"冻结窗口"里。

同时:站点侧没有任何 receipt 请求、也没有通知样本 → 证明通知根本没进脚本(不是关键词问题)。

二、v2.4 的修复

#改动作用
1开启前台服务($settings.setEnabled('foreground_service', true))通知栏常驻进程,显著降低被冻结/清理的概率
2获取部分唤醒锁(PowerManager.PARTIAL_WAKE_LOCK)息屏后 CPU 仍运行,定时器与通知回调不被挂起
3冻结检测告警心跳间隔 > 2 倍设定值时,日志明确打印"脚本曾被系统冻结,期间的收款通知会漏掉,请检查前台服务/电池白名单"
4退出时释放唤醒锁避免耗电残留

三、仍需在手机上确认的系统设置(Vivo / OriginOS 尤其重要)

  1. 设置 → 电池 → 后台耗电管理 → AutoX.js → 选「允许后台高耗电」;
  2. 设置 → 应用与权限 → 权限管理 → 自启动 → AutoX.js 允许;
  3. 多任务界面下拉 AutoX.js 卡片 → 加锁(防一键清理);
  4. AutoX.js 内 → 设置 → 开启「前台服务」;
  5. 关闭系统的「睡眠模式/超级省电」。

四、验证方式

换 v2.4 脚本后,日志应出现:

已开启前台服务(通知栏常驻,防系统冻结)
已获取部分唤醒锁(息屏后脚本仍运行)
[心跳] 已上报,站点应答 {"ok":true,...}        ← 应每 3 分钟稳定出现

之后收款一笔(建议 10.00 元,与订单 #7 金额一致)→ 站点应在数秒内出现回执并自动入账; 若仍出现"脚本曾被系统冻结"告警,说明系统设置还没放开,按上面第 3 节逐项检查。

长期建议:若你的收款渠道支持官方接口(支付宝/微信 APIv3),可以用站点已内置的官方支付网关, 完全不需要手机即可自动到账(后台「在线支付订单/支付设置」里配置)。

v0.4.25(远程诊断:未识别的微信通知原文可回传站点)

场景:实收 10 元,动态码页无动静。 排查:手机脚本在运行(02:49:58 心跳正常,设备名"收款手机"),但没有任何 receipt 请求 → 说明"收款通知没有被脚本捕获/识别"。此前只能靠手机日志判断,本版打通远程诊断。

一、本次证据

检查结果
手机心跳02:49:58 正常(设备名"收款手机")→ 脚本在运行
receipt 请求0 条(自 02:04 起从未出现过)
回执表0 条
待审核订单10.00 元订单 #7 仍在(回执一到即自动入账)
浏览器轮询每 5 秒 GET /payment-receipt.php?order_id=7 → 200 ✓(页面本身正常)

→ 结论:服务端与页面都正常,卡在"手机没把这条收款通知报上来"。

二、新增能力(本版)

#内容说明
1端点支持 type=notice(诊断专用)只写排障日志:记录通知原文(前 200 字符)、原因、设备;绝不触碰订单/余额/回执;每小时最多 60 条
2手机脚本 v2.3新增 REPORT_NOTICES(默认开):凡被判定为"非收款"的微信通知,把原文回传到站点诊断日志(本地 10 秒限流)
3诊断日志展示自检页「最近上报尝试」会显示这些样本,标题为「通知样本(未识别为收款)…」

三、验证

用例结果
POST {type:'notice', text:'微信支付 收款到账 10.00 元(样本)'}HTTP 200 ✓,日志记为「通知样本(未识别为收款):测试样本」+ 原文 ✓
心跳HTTP 200 ✓(原有链路不受影响)
订单 #7 / 用户 3 余额 / 回执数均未变化 ✓(诊断通道零副作用)

四、下一步(用户操作)

  1. 换成 v2.3 脚本运行;
  2. 在脚本运行期间再收一笔(脚本看不到启动之前的历史通知);
  3. 结果判读:
  • 诊断日志出现「通知样本…」→ 通知到了手机但关键词没匹配,把原文发来即可校准(我已能看到);
  • 诊断日志连样本都没有 → 通知根本没进脚本(通知使用权未授予 / 微信通知被关闭);
  • 出现 receipt 记录 → 已正常,将自动入账。

v0.4.24(手机脚本 v2.2:通知使用权自检 + 通知捕获统计)

场景:用户实收 10 元,但站点没有回执。 排查结论:手机脚本完全没有上报"回执"(排障日志里只有心跳,没有一条 receipt); 且手机心跳自 02:37:22 后中断 → 脚本未在运行 / 未捕获到收款通知。 本版给手机脚本加上"通知侧"的自检与统计,让这类问题一眼可判。

一、本次排查证据

检查结果
回执表 wxpay_receipts0 条
上报尝试日志最后一条是 02:37:22 心跳(V2232A 200);没有任何 receipt 类型请求
待审核订单存在 10.00 元订单(#7,用户 3,1000 TKN)→ 回执到达即会唯一匹配并自动入账
自动入账开关已开启(上限 1000 元)
设备在线最后一次心跳 02:37:22,距排查时(02:46)已 9 分钟 → 脚本大概率已停止

→ 结论:服务端没问题,卡在"手机没把这条收款通知报上来"。

二、手机脚本 v2.2 的加固

#改动作用
1启动自检通知使用权events.observeNotification() 失败时,日志明确提示"请到 设置 → 应用 → 特殊权限 → 通知使用权 允许 AutoX.js"(否则脚本看似在跑却永远收不到通知)
2通知捕获统计每 30 分钟记录一次「本次运行已捕获微信通知 N 条」;收款后仍为 0 就说明通知根本没到脚本(权限/微信通知被关)
3未识别提示非收款通知打印"如这其实是收款通知,请把本行原文发给开发者校准关键词"
4通知编号日志改为 [微信通知 #N] 原文,便于对照计数

三、需要用户配合的动作

  1. 用 v2.2 脚本覆盖手机上现有脚本,重新运行;
  2. 日志必须出现 已开启微信通知监听 —— 若出现 无法开启通知监听,先授予通知使用权;
  3. 保持脚本运行(AutoX.js 开前台服务、系统关闭电池优化、锁定后台任务);
  4. 再收一笔款 → 刷新自检页看「最近上报尝试」是否出现 receipt 记录;
  5. 若日志里出现 [微信通知 #N] ... 但脚本判定为"非收款通知",把该行原文发来即可校准关键词。

v0.4.23(修复动态码页轮询:刷新可见 + 修掉 2 个 JS 缺陷)

现象:动态码页写着"页面每 5 秒自动刷新",但看起来并没有刷新。 结论:轮询一直在正常请求(nginx 日志每 5 秒一条 GET /payment-receipt.php?order_id=N 200), 问题出在前端脚本——数据没变化时画面文字也不变,而且脚本里有两处缺陷。

一、实测(先确认"到底有没有刷新")

02:37:46 / 02:37:51 / 02:37:56 / 02:38:01 …  每 5 秒一条
127.0.0.1 GET /payment-receipt.php?order_id=7 → 200

响应体(gzip 后 228 字节,解压后 355 字节):

{"ok":true,"enabled":true,"order_id":7,"receipt":null,"device_online":true,
 "device_last_seen":"2026-10-03 02:37:22","device_name":"V2232A","server_time":"…"}

→ 接口正常、轮询正常;但页面没有任何"刷新过"的视觉反馈,数据不变时看起来就像卡住了。

二、修复的三件事

#缺陷影响修复
1刷新不可见数据没变化时页面文字不变,用户以为没刷新新增独立提示条(每 1 秒更新):「最后更新:X 秒前(每 5 秒自动刷新)|服务器时间 …」
2clearInterval(pollTimer) 变量名不存在(该作用域里是 timer)走到"已自动入账"分支时抛 ReferenceError,状态不更新改用 stop()(正确停表)
3替换 catch 时漏掉收尾 });整段轮询脚本语法错误 → 完全不执行已补回;并用 node --check 校验内联脚本语法

另外:刷新失败不再静默吞掉 —— 连续 3 个周期(15 秒)没成功刷新时,提示条会变红显示"…——刷新异常,正在重试…"; 控制台也会打印 [wxReceipt] 刷新失败:…,便于排查登录过期 / 网络问题。

三、验证

内联 JS 语法:node --check → 全部通过 ✓(修复前报 missing ) after argument list)

轮询行为(Node + DOM 桩,qdcms-release/test-polling.js)→ 9 / 9 通过:

[PASS] 轮询脚本加载无异常(无 pollTimer 之类的 ReferenceError)
[PASS] 状态文字显示"尚未收到回执"
[PASS] 状态文字显示真机在线与设备名(监听设备在线(V2232A…))
[PASS] 已插入"最后更新"提示条元素
[PASS] 存在 1 秒刷新提示定时器
[PASS] 提示条显示"最后更新:2 秒前(每 5 秒自动刷新)|服务器时间 …"
[PASS] 刷新失败时提示条可见(刷新异常,正在重试)
[PASS] 收到已入账回执时显示"已自动入账"
[PASS] 已到账后停止轮询(clearInterval 被调用)

线上页面:HTTP 200,页面已含 wxReceiptTick / 「最后更新」提示条 ✓

四、请做一次强制刷新

浏览器可能缓存了旧页面脚本,请按 Ctrl + F5(手机:下拉刷新/清缓存)后再看动态码页 —— 页面底部会出现 "最后更新:X 秒前(每 5 秒自动刷新)",数字每秒跳动,即代表实时刷新正常。

v0.4.22(修复:动态码页"监听设备"显示成测试名)

现象:个人支付动态码页刷新后,"监听设备"一直显示 模拟真机心跳(而不是手机真机)。 性质:显示逻辑缺陷 + 开发排障数据污染,已修正并清理。

一、原因

动态码页轮询 payment-receipt.php?order_id=N,其中 device_name/device_last_seen/device_online 直接取 settings.wxpay_receipt_last_device / last_seen —— 而这两个 key 是可变的: 任何一次心跳都会覆盖它,包括开发者排障时发的测试心跳(本例就是 模拟真机心跳) 和自检页的"一键模拟上报"。

二、修复

#改动说明
1新增 wxreceipt_current_device()在线状态改为取上报尝试日志里最近一条"来自非回环地址"的记录(即真实设备);只有在查不到时才回退 settings
2payment-receipt.php GET 应答改用该函数动态码页显示的设备名/时间/在线判定从此只反映真机,自检与测试再也污染不了
3清理历史污染删除开发排障期间产生的本机测试尝试记录;last_device/last_seen 回填为手机真机最近一次心跳

三、验证(线上实测)

修复前:动态码页显示 模拟真机心跳(开发者测试名),last_seen=02:31:33

修复后(payment-receipt.php?order_id=7 实际应答):

{"ok":true,"enabled":true,"order_id":7,"order_status":0,"amount":"10.00",
 "device_online":true,"device_last_seen":"2026-10-03 02:28:03","device_name":"V2232A", ...}
检查结果
动态码页数据源 device_nameV2232A(手机真机名)✓
device_onlinetrue ✓
自检页「监听设备(真机)」在线 · V2232A ✓
排障日志残留仅保留手机真机 3 条;本机测试记录已清理 ✓

说明:设备"在线"窗口为 10 分钟。手机脚本每 3 分钟发一次心跳; 若超过 10 分钟没有新心跳,页面会(正确地)显示"离线"——这可以帮助判断脚本是否还在运行。

v0.4.21(手机上报已打通;修正"服务器自检"覆盖真机在线状态的问题)

性质:修复 + 排障收尾。需求:"是否收到回执"。 结论:手机上报通道已完全打通(手机心跳三次全部 HTTP 200);尚未收到"回执" —— 回执需要真实微信收款到账通知被手机捕获。

一、本次证据(手机已成功上报)

192.168.0.104 POST /payment-receipt.php 200  02:27:16   ← 手机(okhttp/4.10.0)心跳成功
192.168.0.104 POST /payment-receipt.php 200  02:27:50
192.168.0.104 POST /payment-receipt.php 200  02:28:03

排障日志同步记录(wxpay_receipt_attempts):

id来源结果原因Content-Type请求体
23–25192.168.0.104200心跳(设备在线标记已刷新)application/json; charset=utf-8{"type":"heartbeat","device":"V2232A"}

→ v2.1 脚本的"显式 JSON body"生效,400 已消失、鉴权通过 ✓ → 回执(receipt)条数为 0:说明还没有微信收款通知被手机捕获(不是通道问题)

二、本次修复:服务器自检不再抢占"监听设备"

自检页「一键模拟上报」是服务器自己发的,但它也刷新了"监听设备/最后心跳", 于是页面显示成"服务器自检",把真机是否在线盖掉了。

改动说明
回执端点识别 selftest 标记请求体带 selftest: true 时不刷新 last_seen/last_device
自检页模拟上报带 selftest: true自检只验证链路,不再影响设备在线状态
自检页文案「监听设备」→「监听设备(真机)」,避免歧义

三、验证

用例结果
触发一次自检(ping)last_device 保持不变 ✓(不再被覆盖)
模拟一次真机心跳last_device 正常更新为设备名、last_seen 刷新 ✓
自检页HTTP 200 ✓

四、要"收到回执"需要做什么

需要有真实的微信收款到账通知:

  1. 保持手机脚本运行(现在心跳正常,说明在跑);
  2. 用另一个微信扫你的收款码付一笔(建议 0.01 元,或与某笔待审核订单金额完全相同的数额);
  3. 刷新自检页 → 「近 24 小时回执」应变为 1,且「最近上报尝试」会出现一条回执记录;
  4. 金额与某笔待审订单唯一匹配时,自动入账会立即生效(后台「收款码解析」可看到记录)。

若付了款仍无回执:把手机 DEBUG: true 日志里的 [微信通知] ... 几行发来,据此校准 INCLUDE / ANCHOR 关键词(微信"收款到账"通知的实际文案各地略有差异)。

v0.4.20(排障补全:记录 Content-Type + 请求体密钥脱敏 + 手机脚本 v2.1)

性质:排障能力补全 + 手机脚本加固。场景:手机上报已从 401(鉴权)前进到 400(请求体), 需要看清"手机到底发了什么"。

一、本次「再次检测」结论

检查结果
自检页「模拟上报」02:08:27、02:09:05 两次点击 → HTTP 200 ✓(last_seen 随之刷新,监听状态=在线)
手机脚本上报自 02:04:06 之后没有任何请求(这期间只有服务器自测与模拟上报)
回执0 条

→ 服务端一切正常;需要手机脚本再运行一次才能拿到新的排障数据。

二、改动

#改动说明
1尝试日志新增 content_type 列记录设备请求的 Content-Type。若显示 application/x-www-form-urlencoded,即可确认手机把 JSON 当表单发送了(服务端 json_decode 失败 → 400)
2请求体日志密钥脱敏body_head 写入前把站点密钥替换为 ***,避免密钥落进退障日志
3手机脚本 v2.1改用 http.request(... {method:'POST', contentType:'application/json; charset=utf-8', body: ...}) 显式声明 JSON body;http.request 不可用时自动回退 http.post;仍保持"签名头 + 令牌头"双鉴权与 401 自动回退

三、验证

用例结果
JSON + 正确签名HTTP 200,日志 content_type=application/json ✓
表单编码请求HTTP 401(无鉴权头),日志记录 content_type 与 body_head ✓
请求体里带密钥日志存为 "token":"***" ✓(密钥不外泄)
手机脚本 v2.1node --check 通过 ✓

v0.4.19(排障增强:上报尝试日志覆盖全部分支,含请求体头部)

性质:排障能力补全。需求:"检测模拟上报是否成功 / 依然没有回执"。

一、模拟上报结论:成功

手机在 02:03:40、02:04:09、02:05:29 三次点击自检页「模拟一次上报」,全部返回 HTTP 200, 服务端 wxpay_receipt_last_seen 随之刷新、last_device = "服务器自检"。 → 服务端端点、密钥、路径、回退逻辑全部正常;问题只在手机脚本如何构造请求。

二、新发现:手机上报从 401 变成 400(鉴权已通过)

手机脚本在 02:04:05–02:04:06 的三次上报返回 HTTP 400(此前是 401)。 说明鉴权已通过(v2 脚本的令牌头生效),现在卡在请求体参数上。

三、本次改动

改动说明
尝试日志覆盖全部早退分支心跳成功、JSON 非法、体积超限、type 非法、receipt_id 非法、amount 非法、限流、IP 白名单、未启用、鉴权失败——每一条都落库
新增 body_head 列记录请求体前 200 字符,直接看到手机实际发了什么(是空、被截断、还是字段名不对)
老表自动升级检测到 wxpay_receipt_attempts 缺列时自动 ALTER TABLE 补 body_head

四、验证

用例结果
心跳(正确签名)HTTP 200,日志记 心跳(设备在线标记已刷新) ✓
非 JSON 请求体HTTP 400,日志记 请求体不是合法 JSON 对象 + body_head=not-json ✓
type 非法HTTP 400,日志记 type 非法:nope + body_head={"type":"nope"} ✓
amount 非法HTTP 400,日志记 amount 非法:abc ✓
鉴权失败HTTP 401,日志记原因 + has_sign/sign_len/has_token ✓

下一步:手机脚本再运行一次,自检页「最近上报尝试」会直接显示 400 的具体原因与请求体开头, 据此一眼定位(空体 / 字段名不对 / 编码问题)。

v0.4.18(排障能力 + 手机上报鉴权兼容:定位到"手机 401 鉴权失败")

性质:缺陷修复 + 排障增强。需求:"依然没有回执 / 检测手机回执失败的原因"。 本次拿到决定性证据:手机已经在连服务器了(自检页 HTTP 200), 上报请求也到达了端点,但被 401 鉴权拒绝——失败点从"网络"收敛到"鉴权"。

一、关键证据(nginx 日志)

192.168.0.104  GET  /receipt-check.php  200   ← 手机(Android 16 / Vivo 浏览器)能访问服务器
192.168.0.104  POST /payment-receipt.php 401  ← 手机脚本(okhttp/4.10.0)上报被拒
192.168.0.104  POST /payment-receipt.php 401
192.168.0.104  POST /payment-receipt.php 401

二、修复内容

#改动说明
1回执端点支持"签名失败 → 明文令牌回退"安卓运行环境(AutoX.js / okhttp)可能对请求体重编码,使设备端 HMAC 与服务端 hash_hmac 字节不一致;此时若同时带了 X-WXPay-Token 且正确,则按令牌模式放行。仍拒绝无密钥的伪造请求
2新增上报尝试日志表 wxpay_receipt_attempts每次设备请求记录:IP、UA、结果码、原因、是否带签名头/签名长度/是否带令牌头/请求体长度、设备名;保留最近 300 条
3自检页新增三块① 最近上报尝试(含"怎么读"对照:401+签名长度正常=密钥不一致;401+无签名头=脚本版本问题;403=IP 白名单;503=未启用)② 设备密钥对照(掩码)(与手机日志 dbb0****1b02 直接对比)③ 一键模拟上报按钮(服务器代发心跳,验证端点链路)
4新增手机脚本 v2:install/wxpay-receipt-listener.js① 每次上报同时发送签名头与令牌头;② 收到 401 自动改用"仅令牌头"重试;③ 启动自检并把失败原因翻译成中文(401=密钥不符 / 503=未启用 / 连接失败=地址或网络);④ SERVER/TOKEN 已预填

三、验证

用例结果
错误签名 + 正确令牌HTTP 200 ✓(回退放行)
无签名 + 正确令牌HTTP 200 ✓
错误签名 + 错误令牌HTTP 401 ✓(仍然拒绝伪造)

原始文件:docs/чЙИцЬмхНЗч║зшп┤цШО.md · 出于安全考虑,站点不直接暴露 docs/ 目录下的原始文件(其中含内部资料),请在交付包内查看全文。

全部文档
站点公告 官网试运行中,如需购买或试用请通过「联系我们」留言。