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

更新日志

版本发布逐版本列出变更说明,站点公告按置顶与时间倒序排列,共 93 条记录;升级前请先阅读对应版本的变更说明与升级步骤。

版本发布 93 条 站点公告 0 条
  • v0.4.14

    修复:授权域名守卫把"本机/局域网/中文域名"访问误判为违规并锁定

    随交付包发布

    性质:缺陷修复(安全守卫误伤运维访问)。无数据库变更;改动仅 includes/LicenseManager.php。

    查看完整变更

    性质:缺陷修复(安全守卫误伤运维访问)。无数据库变更;改动仅 includes/LicenseManager.php。 触发场景:在服务器本机或局域网打开站点、或用中文域名访问时,被站点自己的"域名授权守卫"记为违规。

    一、问题现象与根因

    现象:http://192.168.0.100/(局域网)、http://127.0.0.1/(本机)、http://10123.中国/(中文域名) 都会返回 403 + "停机维护"页面,而授权域名 10123.xn--fiqs8s 正常。

    根因(includes/LicenseManager.php + includes/header.php):

    // header.php 每个页面都会执行
    if (LicenseManager::isLicensed()) {
        $domainCheck = LicenseManager::checkDomain();      // 与授权域名逐字符比较
        if (!$domainCheck['valid']) {
            $warning = LicenseManager::recordWarning();     // 记一次违规(5 天窗口内累计)
            if ($warning['shutdown']) { echo renderShutdownPage(); exit; }   // ≥8 次 → 停机页
        }
    }

    三处设计缺陷叠加:

    #缺陷后果
    1getCurrentDomain() 直接返回原始 HTTP_HOST,不剥离端口、不做 IDN 归一化127.0.0.1:16500(宝塔反代)、10123.中国(中文形态)永远不等于绑定的 punycode 10123.xn--fiqs8s
    2本机/内网访问也计入违规管理员在本机调试、局域网访问,每个页面都记一次违规;累计 8 次后本机/局域网访问被自己的停机页锁死(线上实际计数已达 106)
    3违规域名无白名单机制自家其它域名(如 10123.vip)也被判违规

    实测线上违规计数文件:{"count":106,"domains":["127.0.0.1","10123.vip","127.0.0.1:16500","192.168.0.100","10123.中国","39.96.90.85"]} —— 全部是运维访问地址,没有一个是真正的盗版域名。

    二、修复内容

    改动说明
    新增 LicenseManager::normalizeHost()小写化、剥离端口(含 [::1]:8080)、非 ASCII 主机名用 idn_to_ascii() 转 punycode
    新增 LicenseManager::isLocalHost()放行且不计数:localhost / `*.local.lan.home.internal/::1/127./10./172.16-31./192.168./169.254.*`
    新增 data/.license_allow 白名单每行一个主机名或后缀(.example.com 支持),# 注释
    getBoundDomain() / getCurrentDomain()统一走 normalizeHost(),中文域名与 punycode 绑定可正确互相匹配
    checkDomain()命中本地/白名单 → 直接 valid(不进入违规计数)

    反盗版语义保留:外部未授权域名仍然被拦截并计数(8 次后对该主机显示停机页)。

    三、验证(线上实测)

    多 Host 访问(修复前 → 修复后):

    Host修复前修复后
    10123.xn--fiqs8s(授权域名)200200 ✓
    192.168.0.100(局域网)403 停机维护200 ✓
    127.0.0.1(本机)403200 ✓
    localhost—200 ✓
    127.0.0.1:16500(宝塔反代带端口)403200 ✓
    10123.中国(中文域名形态)403200 ✓

    守卫语义 8/8 通过(CLI 直测 LicenseManager):

    [PASS] 10123.xn--fiqs8s   放行(计数 +0)
    [PASS] 192.168.0.100      放行(计数 +0)
    [PASS] 127.0.0.1          放行(计数 +0)
    [PASS] localhost          放行(计数 +0)
    [PASS] 127.0.0.1:16500    放行(计数 +0)
    [PASS] 10123.中国          放行(计数 +0)      ← IDN 归一化生效
    [PASS] evil-domain.example.com  拦截(计数 +1)  ← 反盗版语义保留
    [PASS] 白名单 10123.vip    放行且不计数

    历史计数已归零(data/.license_warnings → count:0),并已备份原文件到 D:\BtSoft\backup\qdcms-license-20261003-013150。

    四、给你的使用建议

    • 本机 / 局域网 / 宝塔反代访问站点:现在不会再被记过或锁定 ✓
    • 若还有自家其它域名(如 10123.vip)需要正常访问,把它写进 data/.license_allow(一行一个)即可;
    • 该文件与计数文件都在 data/ 下,权限 0600,不随包分发。
  • v0.4.13

    内置二维码解码器 + 动态码生成器:部署即用

    随交付包发布

    性质:新能力(纯 PHP 实现)+ 修复"服务器未提供二维码渲染/解析能力"的降级状态。

    查看完整变更

    性质:新能力(纯 PHP 实现)+ 修复"服务器未提供二维码渲染/解析能力"的降级状态。 需求:"修复二维码解码器,写一个二维码解码器和动态码生成器入安装包内,实现部署即用"。 无数据库变更、无外部依赖(不需要 qrencode / zbarimg / Python)。

    一、问题背景

    此前两个能力都依赖外部命令行程序:

    能力原实现在 Windows/宝塔上的实际结果
    生成动态收款码findQrEncoder() → 只找 qrencode找不到 → 前端「支付码」页显示「服务器未提供二维码渲染能力,请按右侧信息手工支付」
    解析上传的收款码图片detectQrDecoder() → 只找 zbarimg / python zxing / pyzbar找不到 → 后台显示「未检测到二维码解码器」

    二、新增模块(包内自带,随包分发)

    文件说明
    includes/qr/QrEncoder.php纯 PHP 二维码生成器:数字/字母数字/字节模式,版本 1~40 自适应,纠错 L/M/Q/H,8 种掩码按标准罚分择优;PNG(GD)或 SVG 输出
    includes/qr/QrDecoder.php纯 PHP 二维码解码器:GD 读图 → Otsu 二值化 → 定位图形检测 → 仿射采样 → 格式/版本信息 BCH → 去交织 → Reed-Solomon 纠错(GF(256)/Berlekamp-Massey/Chien/Forney)→ 段解析;支持版本 1~10、四档纠错、任意 90° 旋转与缩放
    install/qr-selftest.php自检脚本(php install/qr-selftest.php)
    docs/二维码模块说明.md使用说明、能力边界与常见问题

    三、接入方式(系统内自动优先内置)

    • findQrEncoder() → 返回 'builtin';renderQrPngDataUri() 用 GD 现场渲染 PNG(无 GD 自动退化 SVG)
    • detectQrDecoder() → 返回 ['type' => 'builtin'];decodeQrImage() 用内置解码器解析;内置失败仍会回退外部解码器
    • 前台「支付码」页与后台「收款码与二维码解析」页无需改动,自动变为可用

    四、验证(自检 7/7 全通过)

    断言结果
    版本 1~40 × 四档纠错的容量表自洽160/160 通过
    ISO/IEC 18004 标准样例:"01234567"(v1-M)数据码字10 20 0C 56 61 80 EC 11 EC 11 EC 11 EC 11 EC 11 逐字节一致
    格式信息 BCH(M/mask0)101010000010010 与标准一致
    编码 → 解码往返(版本 1~12、四档纠错、多长度)18/18 通过
    四向旋转(0/90/180/270)4/4 通过
    真实载荷(支付宝 / 微信 / 中文备注)5/5 通过
    缩放/纠错/边距组合扫描96/96 通过

    线上端到端实测(写入线上环境后,用临时探针调用系统自身函数):

    findQrEncoder()   = builtin
    detectQrDecoder() = type=builtin
    renderQrPngDataUri() → data:image/png;base64,iVBORw0K…(真实 PNG)
    decodeQrImage(生成的二维码) → 与原文完全一致(往返成功)
    真实载荷 3 例(支付宝/微信/中文) → 全部解码成功
    后台「收款码解析」页显示「已就绪:内置纯 PHP 解码器」;"未检测到解码器"告警消失

    五、开发过程中发现并修复的实现缺陷(自检驱动,逐条记录)

    #缺陷影响
    1选版本时把位数当作码字数"01234567" 被编成版本 3 而非版本 1
    2格式信息 15 位摆放位置写反(两个副本都与标准不符)解码器读不到合法格式信息
    3解码采样偏移写成 k-3.5(应为 k-3.0)整体偏半个模块,采样落在模块边界
    4格式信息副本 2 的读取坐标与标准互换副本 2 永远校验失败
    5字符计数指示符位宽未随版本组变化(字节模式 v10+ 应为 16 位)版本 10 及以上数据头畸形,无法解析
    6定位图形纵向复核时,"下方浅色游程"起点用了最后一个深色行定位图形 0 命中,全图无法解析
    7定位图形聚类窗口过宽(3 个模块)且无条件合并真定位图形被邻近伪命中"吃掉"(实测 33×33 图真 TL 消失)
    8只按模块大小排序取前 3 个候选假阳性被当成真定位图形

    修复后另加两项鲁棒性措施:每模块 5 点多数表决采样、掩码回退重试(RS 自洽才认),最终缩放/纠错扫描 96/96 通过。

  • v0.4.12

    后台:收款码与二维码解析独立成页

    随交付包发布

    性质:后台结构整理(重构)。需求:"将后台 token 充值审核页面里二维码解析项目单独建立一个页面归纳"。

    查看完整变更

    性质:后台结构整理(重构)。需求:"将后台 token 充值审核页面里二维码解析项目单独建立一个页面归纳"。 无数据库变更、无前台影响、无支付口径变更。

    改动前:admin/token-orders.php(561 行)同时承担「订单审核」与「收款码/二维码解析」两件事, 二维码相关代码(POST 处理、配置读取、解析状态文案、两个上传解析表单、按套餐收款码表格)占了近 40% 篇幅。

    改动后:

    文件变化
    admin/pay-qr.php(新增,352 行)独立页面「收款码与二维码解析」,归集:① 全局收款码内容(支付宝/微信)与动态码模板;② 上传图片自动解析二维码(不落盘);③ 按套餐固定金额收款码(含图片解析);④ 新增「二维码解码能力」面板——检测到解码器则显示路径,未检测到则给出 zLinux/Windows 安装指引,避免管理员反复换图重试
    admin/token-orders.php(561 → 299 行)只保留「充值兑换比例」「订单审核(通过/拒绝)」「筛选」「订单列表」;二维码相关代码全部移出;新增一张引导卡片链接到新页;旧表单/旧书签提交 save_pay_qr_config 会自动 302 跳转到新页(兼容)
    admin/header.php「用户运营」分组新增导航项 收款码解析(与 Token 订单/在线支付订单并列),并纳入分组自动展开条件

    代码搬移方式:按行范围原样搬移(不重写逻辑),仅把表单 action 从 token-orders.php 改为 pay-qr.php; 所有解析函数(decodeQrImage / detectQrDecoder / normalizePayQrInput / parsePaymentQrPayload / payQrPackageSettingKey / getPayQrConfig / getPayQrPackageConfig)本就在 includes/token-functions.php, 两页共用,无重复实现。

    保真核验(脚本逐项断言):新页 12 项要素齐全(两个渠道输入框、模板、两个图片解析入口、套餐输入与图片解析、 保存动作、解析函数调用、状态文案、表单目标);原页 8 项保留(汇率、审核通过/拒绝、兼容跳转、入口链接、筛选、列表) 且 5 项旧内容确认移除 ✓。

    线上实测 23 / 23 通过:

    [PASS] 新页 HTTP 200 完整渲染(18,316 字符)+ 全部区块/输入项/导航入口
    [PASS] 原页 HTTP 200(17,150 字符):保留汇率/筛选/列表,新增入口卡片,旧区块与旧输入框已移除
    [PASS] 旧动作提交 → HTTP 302 Location: pay-qr.php(兼容跳转)
    [PASS] 新页保存动作(合法 CSRF + 当前值)→「收款码设置已保存」
    [PASS] 错误 CSRF → 「请求验证失败」且**未写入数据库**(已回查 settings)
    [PASS] 测试会话已删除;settings 未被改动(pay_qr_alipay / pay_qr_wechat 空、pay_qr_template={pay_url})

    过程中的一次误操作已修正:验证脚本中一项"错误 CSRF"用例误带了有效 token,把 WRONG_CSRF_TEST 写入了 pay_qr_alipay;发现后立即还原为空值并用独立用例(无效 token)重测,确认拒绝且不落库。

  • v0.4.11

    充值系统审计与加固:支付表缺失兜底 + 账目/并发实测

    随交付包发布

    性质:安全审计 + 缺陷修复 + 体验优化。需求:"优化充值系统,检测是否有bug"。

    查看完整变更

    性质:安全审计 + 缺陷修复 + 体验优化。需求:"优化充值系统,检测是否有bug"。 无既有表结构变更(仅新增 4 张支付表的自动创建能力);不改动任何支付金额口径。

    一、审计与实测结论(有证据)

    检查项方法结果
    回调验签伪造支付宝回调(无签名)POST响应 failure ✓ 被拒
    回调验签伪造微信 APIv3 回调(乱报文)HTTP 500 + 失败应答 ✓ 被拒
    回执上报无 HMAC 伪造 payment-receipt.php被拒(需 IP 白名单 + 设备密钥 + 开关)✓
    入账幂等同一订单连续两次 Ledger::credit()第一次入账、第二次 duplicate=true,仅 1 条流水 ✓
    并发重复入账两个并行请求对同一订单入账只入账一次(token_order_id 相同,余额只 +10)✓
    金额不符回调 999 分 vs 订单 100 分ok=false + PAY_1012,未入账、转人工复核 ✓
    并发扣费同内容 onceOnly 双请求并行只扣一次,第二条返回 duplicate ✓
    账目一致性逐用户比对 余额 vs 已入账 − 已消费3 个用户全部零漂移 ✓
    权限admin/token-orders.php、admin/payment-orders.php经 admin/header.php → requireAdmin() + CSRF ✓
    越权payment-status.php、payment-receipt.php GET、recharge-order.php全部 user_id = ? 归属校验,遗留页只读 ✓
    客户端金额payment-create.php金额/Token 一律服务端按套餐推导 + CSRF ✓

    结论:资金主链路(验签 / 幂等 / 并发 / 金额 / 账目)实测全部通过,未发现可被利用的入账漏洞。

    二、发现并修复的真实缺陷

    #问题影响修复
    1🔴 支付数据表从未创建:includes/payment/schema.sql(4 张表)随包分发但没有任何入口执行;线上 payment_orders 等表缺失管理员一旦填好渠道密钥,充值页会显示"支付宝/微信",用户点下单直接 500「系统繁忙」(当前线上尚未配置密钥,故未触发)① PaymentStore::schemaReady() / ensureSchema();② createForPackage() 下单前自动补建,补建失败则返回明确错误码 PAY_1021(不再 500);③ availableChannels() 表未就绪时不下发渠道(充值页不会显示会失败的入口);④ 后台「支付设置」新增一键初始化支付表按钮(CSRF 保护)+ 缺失告警;⑤ 全新安装 install/auto-install.php 自动执行 schema.sql
    2🟠 addTokens() 无条件返回 true用户行不存在时 UPDATE 影响 0 行,上层仍会把订单标为"已入账",而钱没加到任何人账上改为按影响行数 + 用户存在性判定并记日志
    3🟠 支付表缺失没有可检测的判据自检/后台无法提前发现提供 PaymentStore::schemaReady(),后台实时显示状态

    三、实测验证(线上)

    场景结果
    正常环境 schemaReady()true;4 张表齐备(每张 0 行)
    删掉一张空表 + schema.sql 不可读 → 调用下单返回 {"code":"PAY_1021","error":"支付数据表未创建:请在后台「支付设置 → 初始化支付表」一键创建…"},无未捕获异常、不再 500 ✓
    删掉一张空表 + schema.sql 可读 → 调用下单ensureSchema() 自动重建成功(ready_after_ensure=true),随后正常进入渠道判断 ✓
    表数据删表前 payment_refunds 为 0 行(用空表做实验,无数据风险);实验后 4 张表全部重建 ✓

    四、其他优化

    • 充值相关后台页面在支付表缺失时给出可点击的一键修复,不再只提示"请手动执行 SQL"
    • 错误码体系新增 PAY_1021,与既有 PAY_1001~PAY_1020 保持一致
    • 本次未改动金额口径、未调整任何订单/Tokens 数据

    五、交付物

    • 新增:PaymentStore::schemaReady()/ensureSchema()、PaymentGateway::E_SCHEMA_MISSING
    • 修改:PaymentService::createForPackage()、PaymentConfig::availableChannels()、admin/payment-settings.php、install/auto-install.php、includes/token-functions.php::addTokens()
    • 线上 DB:已执行 includes/payment/schema.sql(新增 4 张表,未动既有表)
  • v0.4.10

    优化:聊天悬浮图标

    随交付包发布

    性质:体验/性能/无障碍优化。无表结构变更、无依赖变更、无服务端逻辑变更(仅 tab-card.js + agent.css)。

    查看完整变更

    性质:体验/性能/无障碍优化。无表结构变更、无依赖变更、无服务端逻辑变更(仅 tab-card.js + agent.css)。 说明:本次未改 im-plugin 资源,故 IM_BUILD 不递增;tab-card.js、agent.css 由 PHP 的 assetVersion()(filemtime)自动破缓存,刷新即生效。

    审计发现 & 对应优化:

    #问题(代码位置实测)优化
    1图标加载原图:renderTabIcon() 直接 <img src="basePath+iconUrl">,而悬浮键只有 48px、菜单项 26px,管理员上传的图标可能是 1024px 原图改走 XDImgUrl() → img-proxy.php(按需缩放 + WebP 缓存 + 30 天强缓存),代理不可用时自动回退原图;并补 decoding="async"。实测生成:/img-proxy.php?src=uploads%2Fagent-icons%2Fchat.png&w=48&t=icon
    2iPhone 安全区被压:.tab-drawer-group 固定 right:12px,底部按钮组固定 bottom 值,未考虑刘海/手势条加 env(safe-area-inset-right/bottom)(带 0px 兜底,老内核忽略该声明)
    3键盘用户看不到焦点:.tab-trigger-btn { outline: none } 且无 :focus-visible补绿色焦点环(按钮、三键组、菜单项),深色模式换浅绿
    4键盘无法唤出聊天:只绑了 pointerdown/up,button 的 Enter/Space(触发 click)无人处理补 click 处理并默认进入聊天窗口;用 lastPointerAt 时间戳避开指针路径的重复触发(指针点击仍由 onPressEnd 处理)
    5无障碍属性缺失:仅 title,无 aria-label,菜单展开态不播报触发键补 aria-label / aria-haspopup / aria-expanded(show/hideMenu 内同步);菜单 role="menu" + aria-label,菜单项 role="menuitem"
    6触屏"粘住"放大态::hover 缩放效果在触屏上会一直保留@media (hover: none) 内中和 hover(transform/阴影/菜单底色),触屏只保留 :active 反馈
    7移动端触控目标偏小:按钮 42px < 44px 推荐值,菜单项高度不足移动端按钮 46px、菜单项 min-height 44px、菜单图标 26px、角标 11px
    8深色模式刺眼:菜单固定白底、角标白色描边prefers-color-scheme: dark 下菜单改深色底、文字 #e8eaed、角标描边随底色
    9不尊重"减弱动效"prefers-reduced-motion: reduce 关闭过渡/动画与缩放
    10角标数字跳动/挤压角标加 font-variant-numeric: tabular-nums + letter-spacing
    11拖拽手感按钮 will-change: transform;.dragging 时放大 1.06 且 cursor: grabbing

    验证(新增 qdcms-release/test-floating-icon.js,Node + DOM 桩)—— 20 / 20 通过:

    [PASS] tab-card.js 初始化无异常
    [PASS] 触发键图标走 img-proxy(/img-proxy.php?src=…&w=48&t=icon)+ decoding=async
    [PASS] aria-label=消息中心 / aria-haspopup=true / aria-expanded 初始 false
    [PASS] 菜单 role=menu、菜单项 role=menuitem
    [PASS] 键盘 click 可打开聊天(open 调用 ["chat"])
    [PASS] 保存的拖拽位置在初始化时即恢复(left=520px top=240px right=auto,无跳位闪烁)
    [PASS] CSS 8 项静态断言:安全区 / :focus-visible / @media(hover:none) / ≥44px 触控 /
           prefers-reduced-motion / prefers-color-scheme:dark / tabular-nums / will-change

    过程中发现并修正了测试桩两处失真(未把 window.XMLHttpRequest 镜像为全局导致初始化中断、 键盘用例未跳过 800ms 指针抑制窗口),保证断言确实在验证产品行为。

    既有回归同时全绿:test-media-cache 20/20、test-chat-upload 23/23、test-chat-selfpush 8/8、test-cn-browser 32/32。

  • v0.4.09

    新功能:聊天媒体缓存 —— 网络波动/刷新后视频不被清零重下

    随交付包发布

    性质:功能增强。需求:"给聊天客户端增加缓存功能,预防网络波动时缓存视频后观看结果被刷新清空"。

    查看完整变更

    性质:功能增强。需求:"给聊天客户端增加缓存功能,预防网络波动时缓存视频后观看结果被刷新清空"。 无表结构变更、无依赖变更。

    先定位"被刷新清空"的真实机制(两条都在前端):

    #机制后果
    1messages.php 的 4 秒轮询在收到新消息时执行 整屏重渲染(loadChatHistoryWithId → body.innerHTML = ...)正在播放的 <video> 被整体销毁,已缓冲内容与播放进度全部丢失 → 重新从头缓冲
    2插件与页面都没有任何媒体缓存/进度记忆网络一抖动、或列表一刷新,就只能重新下载;长视频尤其明显

    新增能力(assets/im-plugin/core/media-cache.js,纯 ES5,10KB,含降级):

    层能力
    ① 字节缓存用 Cache API 把看过的聊天视频存到本地(LRU 上限默认 120MB / 最多 60 条,超出按最久未用淘汰);命中后 <video> 直接切到 blob: 源 —— 断网、刷新后仍可秒播,不必重下。仅在首次播放时后台抓取,不抢观看带宽
    ② 进度记忆播放位置按消息 ID 存本地(1 秒节流写入、播完自动清除),重新渲染或刷新页面后自动续播到原位置
    ③ 播放器保活与"增量追加"配合:页面轮询改为只追加新消息(不再整屏重渲染),正在播放的播放器节点不再被销毁;XDChatVideo.scan() 幂等挂载,任何渲染路径后调用一次即可
    ④ 优雅降级Cache API 不可用(IE11 / 旧 X5 内核)时自动降级为"仅进度记忆",所有接口 no-op 不抛错(与 v0.4.07 的兼容层策略一致)

    同时改动:

    • messages.php:轮询由整屏重渲染改为增量追加(含去重与兜底:节点对不上时才整屏重建);行包装新增 data-msg-row;视频播放器新增 data-msg-id / data-src;渲染后统一 XDChatVideo.scan()(顺手修掉上一版遗留的一行重复 title 赋值)
    • assets/im-plugin/modules/chat.js:视频气泡加 data-msg-id / data-src;_loadMessages、_pollNewMessages(增量)、_uploadMedia(乐观气泡替换)三处渲染后均调用 scan()
    • includes/footer.php:media-cache.js 紧随 compat.js 加载(早于 img-lazy.js),所有页面可用;并加入插件 jsFiles 与资源预热清单
    • chat-video-stream.php:新增 ETag + Last-Modified + 304(与前端 Cache API 互补,浏览器自身缓存更易命中)
    • IM_BUILD 20261002-8 → 20261002-9

    回归测试(Node + DOM/缓存桩)qdcms-release/test-media-cache.js —— 20 / 20 通过:

    [PASS] Cache API 可用时 supported=true;store() 写入索引并调用 fetch
    [PASS] savePos/getPos 往返、clearPos
    [PASS] scan 找到播放器且幂等(data-xd-attached=1,不重复绑定)
    [PASS] 重新渲染后恢复播放位置(33.3s)/播放中记录进度(55.5s)/播放结束清除
    [PASS] 首次播放触发后台缓存
    [PASS] LRU:超上限后淘汰至限制内(entries=2 bytes=600 limit=700)并真的从 Cache 删除
    [PASS] 无 Cache API 时:加载不抛错、supported=false、接口安全降级、仍可记忆进度

    既有回归同时全绿:test-chat-upload 23/23、test-chat-selfpush 8/8、test-cn-browser 32/32。

  • v0.4.08

    修复:发送视频看不到进度 / 上传完成后才显示消息

    随交付包发布

    性质:体验缺陷修复(对应 v0.4.03 引入的 XHR 上传做的不彻底)。无表结构变更、无依赖变更、无服务端改动。

    查看完整变更

    性质:体验缺陷修复(对应 v0.4.03 引入的 XHR 上传做的不彻底)。无表结构变更、无依赖变更、无服务端改动。

    两个根因(都在聊天插件 assets/im-plugin/modules/chat.js):

    #现象根因
    1发送方看不到进度v0.4.03 的进度是用 showToast 显示的,而每个 toast 2 秒后自动消失、且每次进度更新都会新建一个 → 上传超过 2 秒后进度提示就没了(messages.php 页面同样只有按钮文字在变)
    2上传完成后消息才"推回"到发送方窗口显示媒体消息没有乐观气泡:上传期间聊天窗口里空无一物,成功后才通过 _loadMessages() 从服务端整屏拉取渲染出来 —— 这就是"系统又推回给发送方显示"的观感

    修复:

    #改动
    1重构为 _uploadMedia(file):上传开始立即插入乐观气泡(图片用 URL.createObjectURL 本地预览、视频用首帧预览),气泡右下角带百分比 + 进度条,通过 xhr.upload.onprogress 就地更新(不再用会消失的 toast 刷进度)
    2上传成功后用服务端返回的 data.message 原地替换该气泡(outerHTML)并推进 _lastMsgId —— 不再等服务端"推回";同时兜底:若视图已被重建(用户返回列表)则改走 _pendingRefreshId + 刷新联系人列表
    3上传失败:气泡上标红显示失败原因,并给 重试(复用同一个 File 对象,不必重新选文件)与 删除 两个按钮;onerror/onabort 同样落到气泡上
    4messages.php 页面同步改造:发送媒体时在 #chatBody 插入乐观气泡(预览 + 进度条 + 失败原因),成功后移除气泡由 loadChatHistoryAjax() 渲染真实消息
    5IM_BUILD 20261002-7 → 20261002-8

    回归测试(Node + DOM 桩)qdcms-release/test-chat-upload.js —— 23 / 23 通过,新增覆盖:

    [PASS] 上传开始后立即出现乐观气泡(data-id=temp_up_…)
    [PASS] 气泡内含进度文案(初始 0% + 进度条元素)
    [PASS] 进度就地更新为 50%(气泡文字)
    [PASS] 进度条宽度同步 50%
    [PASS] 不再用 toast 刷进度(含"上传中"的 toast = 0 条)
    [PASS] 完成后清空 _upload 并解绑离开拦截 / 提示"已发送" / 无卡死标志
    [PASS] 上传中返回列表:刷新联系人列表 + 记录待刷新会话(不误调 _loadMessages)
    [PASS] 仍在该会话:立即刷新消息、不刷新列表
    [PASS] 失败展示服务端原因、解除上传态

    既有回归同时全绿:test-chat-selfpush 8/8、test-cn-browser 32/32。

  • v0.4.07

    修复:聊天插件不兼容国内浏览器

    随交付包发布

    性质:兼容性修复。目标浏览器:IE11 / 360、QQ、搜狗、2345 等"兼容模式"、微信与 QQ 内置浏览器旧 X5 内核、UC、老版 Chromium(<53)。

    查看完整变更

    性质:兼容性修复。目标浏览器:IE11 / 360、QQ、搜狗、2345 等"兼容模式"、微信与 QQ 内置浏览器旧 X5 内核、UC、老版 Chromium(<53)。 无表结构变更、无依赖变更。

    实测审计结果(先查证据再改):

    项目实测判断
    ES6+ 语法逐行扫描(排除字符串里的中文省略号"…"与 script.async 属性)→ 插件全部为纯 ES5 写法✅ 无需改写
    attachShadowim-sdk.js 直接调用且无兜底🔴 IE11 / 旧 X5 内核没有 Shadow DOM → 抛 TypeError → 面板直接打不开(本次最严重问题)
    缺失 APIfetch、Promise、Object.assign、Element.remove/closest/matches、String.endsWith/padStart、Array.from、requestAnimationFrame、IntersectionObserver;另有 localStorage 在 IE11 隐私模式下写入抛错🔴/🟠 插件与懒加载图片会失效
    CSS146 处 var(--im-*) 全部没有字面量兜底;另有 flex gap×16、backdrop-filter×2、position:sticky×1🔴 IE11 不支持 CSS 变量 → 面板变成无样式裸 DOM

    修复内容:

    #改动文件
    1新增低版本浏览器兼容层(纯 ES5,14KB):Promise(含 then/catch/all 语义)、fetch(XHR 实现,支持 json/text/ok/status)、Object.assign、Array.from/includes/find/findIndex、String.trim/includes/startsWith/endsWith/padStart、Element.matches/closest/remove、NodeList.forEach、CustomEvent、requestAnimationFrame、IntersectionObserver(滚动探测版,保障图片懒加载)、XDStorage(localStorage 不可写时回落内存);并提供 window.XDCompat.summary() 诊断assets/im-plugin/core/compat.js(新增)
    2兼容层在所有页面最早期加载(footer.php 中位于 tab-card.js 之前),并加入插件 jsFiles 首位与资源预热清单includes/footer.php、im-sdk.js、tab-card.js
    3Shadow DOM 兜底渲染:attachShadow 不可用时降级为"普通容器 + 样式注入 <head>",宿主补 im-shadow-fallback 类与等价定位样式(不用 IE11 不支持的 inset),CSS <link> 改注入 head;可用时仍走 Shadow DOM,现代浏览器路径零变化assets/im-plugin/core/im-sdk.js
    4CSS 字面量兜底:为全部 126 条含 var(--im-*) 的声明前插入字面量等价声明(color:#07c160; color:var(--im-primary);)→ IE11 用字面量、现代浏览器仍走变量;覆盖 126/126(100%)im-container.css、im-modules.css、im-reset.css、im-dark.css
    5localStorage 调用点改用 XDStorage(隐私模式不再抛错)im-sdk.js、tab-card.js
    6IM_BUILD 20261002-6 → 20261002-7assets/js/tab-card.js

    模拟旧内核回归测试(Node + 精简 DOM 桩,刻意移除 attachShadow/Promise/fetch/remove/closest/rAF/IO 并让 localStorage 抛错)——qdcms-release/test-cn-browser.js:32 / 32 通过,关键断言:

    [PASS] attachShadow / Promise / fetch / Element.remove / rAF / IntersectionObserver 缺失(模拟生效)
    [PASS] compat.js 为纯 ES5 语法
    [PASS] 已补齐:Promise、fetch、Object.assign、Element.remove/closest、String.endsWith/padStart、Array.from、rAF、IntersectionObserver
    [PASS] Promise.then / catch / all 语义正确
    [PASS] fetch 返回 ok/status/json/text 且 json() 正常
    [PASS] XDStorage 在 localStorage 写入抛错时仍可读回
    [PASS] im-sdk.js 在旧内核环境执行无异常、window.IMPlugin 正常导出、IMPlugin.open() 无异常
    [PASS] 降级渲染生效:<body> 出现 im-root im-shadow-fallback 宿主
    [PASS] <head> 注入降级定位样式 1 个 + 样式表 <link> 5 个、容器内挂 im-shadow-scope 根节点
    [PASS] attachShadow 可用时仍走 Shadow DOM 路径(现代浏览器不受影响)

    兼容层在测试中还暴露并修掉了自身两个缺陷:XDStorage.get 在"可读但不可写"的场景没有回落内存副本;fetch polyfill 直接引用裸 XMLHttpRequest(旧内核下应取 window.XMLHttpRequest)。

    已知降级项(不影响可用性,仅外观):flex gap(16 处)与 backdrop-filter、position:sticky 在旧内核下不生效 → 元素间距略紧凑、无毛玻璃与吸顶效果,布局与功能正常。

  • v0.4.06

    修复:消息发出后又被系统推回给发送方

    随交付包发布

    性质:缺陷修复。用户反馈"发消息出去后系统又推送给发送方"。

    查看完整变更

    性质:缺陷修复。用户反馈"发消息出去后系统又推送给发送方"。 无表结构变更、无依赖变更、无服务端接口变更(纯前端判定逻辑)。

    根因链(三步):

    1. 增量接口 getChatMessagesSince() 返回的是整个会话(WHERE (sender=我 AND receiver=对方) OR (sender=对方 AND receiver=我) AND id > since) —— 自己刚发出的那条也在返回结果里(这是设计如此,用于已读回执/多端同步)。
    2. 插件发送时用的是乐观气泡(data-id="temp_<时间戳>"),发送成功后既没有替换成服务端真实记录,也没有推进 _lastMsgId。
    3. 4 秒后的增量轮询拿到自己那条消息:按真实 id 去重匹配不上临时气泡 → 重复渲染一遍; 并且 if (document.hidden) self._notifyHiddenNewMessages(msgs.length) 把自己发的也算作新消息 → 弹出"新消息"提醒。 messages.php 页面同理:会重渲染并闪出 "【新消息】" 标题。

    修复:

    位置改动
    assets/im-plugin/modules/chat.js _sendMessage()发送成功后用响应里的 data.message 替换乐观气泡,并推进 _lastMsgId
    assets/im-plugin/modules/chat.js _pollNewMessages()逐条判断:sender_id === 我 的消息只用于替换残留气泡,不计入新消息(不渲染、不提醒);_notifyHiddenNewMessages() 只统计对方消息数
    messages.php 页面 4 秒轮询统计时排除自己发的消息;incoming === 0 直接返回 → 不再重渲染、不再闪"【新消息】"
    缓存IM_BUILD 20261002-5 → 20261002-6

    回归测试(Node + DOM 桩,跑真实模块代码) —— qdcms-release/test-chat-selfpush.js:

    用例新版旧版(对照)
    A 增量返回自己发的消息 → 不渲染PASS(渲染列表为空)FAIL(渲染了 101)
    A 同场景 → 不弹提醒PASS(notify=0)FAIL(notify=1) ← 即"推送给发送方"
    B 增量返回对方消息 → 正常渲染 + 提醒PASS(渲染 102、notify=1 数量=1)PASS / PASS
    C 同一批含自己+对方 → 只对对方计数PASS(只渲染 104、提醒=1)FAIL(渲染 103+104、提醒=2)
    合计8/8 通过4/8(4 项失败,精确复现用户反馈)
  • v0.4.05

    修复:聊天视频播放容易失败

    随交付包发布

    性质:缺陷修复。用户反馈"播放聊天视频容易失败"。实测线上 10 个视频后发现 4 个更底层的问题。

    查看完整变更

    性质:缺陷修复。用户反馈"播放聊天视频容易失败"。实测线上 10 个视频后发现 4 个更底层的问题。 无表结构变更、无依赖变更。

    #实测根因修复
    🔴 1moov 在文件末尾(非 faststart):线上 10 个视频里 4 个如此(含用户当时新发的)。浏览器必须把整个文件下完才能起播,弱网/大文件即"播放失败",拖动更是直接卡死上传后自动重封装为 faststart(ffmpeg -c copy -movflags +faststart,无损失);已对线上 4 个存量文件回填(备份在 D:\BtSoft\backup\qdcms-video-normalize-*),现已 10/10 全部 faststart
    🔴 2规范化代码在 Windows 上从未生效:compressVideoAsync() 依赖 nohup sh / wc -c / [ -s ](POSIX only);Windows 分支虽存在,但只在"产物更小"时才替换,而 faststart 重封装体积可能略增 → 直接被丢弃新增跨平台 normalizeVideoForWeb()(同步执行、失败保留原文件):① 已达标 → 不动;② HEVC/H.265/VC-1/MPEG-4 等浏览器不支持的编码 → 转 H.264/AAC + faststart(限 80MB/180s,超出仅重封装);③ 仅非 faststart → 无损重封装。compressVideoAsync() 现在先保证 faststart,再做后台压缩
    🔴 3播放走的 PHP 端点有三个硬伤:① Cache-Control: no-store → 拖动/断流都要重下;② 每 8KB flush(),且 max_execution_time=300s 会腰斩长视频;③ Referer 全等校验 → 换域名/带 www/IDN 与 punycode 混用、浏览器不发 Referer(隐私设置)、企业代理剥离 Referer 时一律 403优先 X-Accel-Redirect 交给 nginx(sendfile 零拷贝、Range 由 nginx 处理、PHP worker 立即释放);Cache-Control: private, max-age=3600;set_time_limit(0);缓冲 8KB→64KB;Referer 规则放宽(空 Referer 放行 + 支持 www/裸域/IDN/punycode/SITE_URL 别名,外站仍 403 防盗链)。无 nginx 时自动退回 PHP 流式输出
    🟠 4包内缺少 Windows ffmpeg 适配:线上 fn-upload.php 比交付包多 184 行(detectFfmpegPathWindows():FFMPEG_PATH 环境变量 / config/ffmpeg.php / 固定路径 / winget、scoop 目录 / where.exe),即 Windows 客户装包后封面与压缩功能全部失效以线上版本为基线合并回包内(线上是超集,diff 确认"包内独有 0 行"),Windows 客户开箱即用
    🟠 5播放器体验:无封面时黑屏、iOS 不内联播放、失败没有任何提示messages.php 与插件 chat.js 均补:poster 兜底(video-default-thumb.jpg)、playsinline、播放失败时给出下载观看引导

    实测验证(线上):

    • 生成"moov 在尾部"的 MP4(77.7KB)走真实上传接口 → 落盘文件 faststart=是 ✓(自动规范化在真实 php-cgi 运行时生效)
    • chat-video-stream.php:正常 Referer → 200(Accept-Ranges: bytes);Range 0-1023 → 206/1024B、500000-500999 → 206/1000B(nginx 直发);无 Referer → 200(历史为 403);外站 Referer → 403(防盗链保留)
    • 全部 10 个视频终检:非 faststart = 0

第 6 / 10 页 · 每页 10 条 · 共 93 条

升级前请确认

  1. 备份

    备份站点目录与数据库,尤其是 uploads/ 与 config/。

  2. 阅读说明

    在 docs/版本升级说明.md 中找到对应版本,确认是否需要执行数据库迁移。

  3. 执行升级

    解压覆盖站点目录 → 执行 migration → 后台「系统 → 在线更新」复核版本状态。

  4. 复核功能

    检查前台首页、播放、支付与后台工作面板等关键入口是否正常。

相关入口

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